Implement space-efficient ValueTuple.ToString and GetHashCode for NativeAOT#124450
Implement space-efficient ValueTuple.ToString and GetHashCode for NativeAOT#124450
Conversation
Add a shared ToStringShared helper on ValueTuple that uses __GetFieldHelper to enumerate fields, replacing per-arity ToString/ToStringEnd implementations under #if NATIVEAOT. This leads to smaller code size in NativeAOT by sharing a single implementation across all ValueTuple arities. Co-authored-by: MichalStrehovsky <[email protected]>
The ToStringEnd() call on the last field should only happen for the 8-tuple (which has exactly 8 fields), not for any ValueTuple where the last field happens to implement IValueTupleInternal. Co-authored-by: MichalStrehovsky <[email protected]>
|
Implemented the space-efficient The shared Both CoreCLR and NativeAOT CoreLib build successfully, and all 97 ValueTuple tests pass. |
|
@copilot test failure: |
…lueTuple types The compiler was previously skipping __GetFieldHelper generation for ValueTuple types, and also short-circuiting to a memcompare-only path for ValueTuples with simple fields. Both of these prevented the ToStringShared helper from working. Now ValueTuple types always get per-field __GetFieldHelper overrides, enabling field enumeration for ToString. Co-authored-by: MichalStrehovsky <[email protected]>
... Fixed in 236dd9f. The NativeAOT compiler was not generating per-field |
|
Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib |
|
@copilot now add similar optimization to the GetHashCode overrides. Introduce a shared helper based on |
Add a shared GetHashCodeShared helper on ValueTuple that uses __GetFieldHelper to enumerate fields under #if NATIVEAOT. This replaces per-arity GetHashCode() implementations with a single shared implementation, leading to smaller code size in NativeAOT. For arities 1-7, the helper uses HashCode struct to combine field hash codes. For the 8-tuple, it replicates the existing flattening behavior based on Rest.Length. The existing per-arity implementations are preserved under #else for non-NativeAOT runtimes. Co-authored-by: MichalStrehovsky <[email protected]>
Added in cd06740. The shared |
Description
Adds shared
ToStringSharedandGetHashCodeSharedhelper methods on the non-genericValueTuplestruct that use__GetFieldHelperto enumerate fields at runtime under#if NATIVEAOT. These replace the per-arityToString(),ToStringEnd(), andGetHashCode()implementations (arities 1–8) with single shared implementations, leading to smaller code size in NativeAOT. The existing per-arity implementations are preserved under#elsefor non-NativeAOT runtimes.How it works
ToString
ValueType.__GetFieldHelper(-1, out _)to get the total field count, then iterates fields using__GetFieldHelper(i, out MethodTable*)to obtain each field's byte offset and type.RuntimeImports.RhBoxAnyandToString()is called on the boxed value.ValueTuple<T1..T7, TRest>), when the last field (TRest) implementsIValueTupleInternal, the helper callsToStringEnd()instead ofToString()to flatten nested tuple representations (e.g.,(1, 2, 3, 4, 5, 6, 7, 8)instead of(1, 2, 3, 4, 5, 6, 7, (8))). This check is guarded bynumFields == 8 && i == 7to ensure it only applies to the 8-tuple, not to other arities that might happen to have aValueTupleas their last field.includeOpenParenparameter supports bothToString()(includes opening() andToStringEnd()(omits it).GetHashCode
__GetFieldHelperfield enumeration pattern to box each field and callGetHashCode()on it.HashCodestruct (Add/ToHashCode).RestimplementsIValueTupleInternal, it usesRest.LengthandRest.GetHashCode()to determine which subset ofItem1..Item7to include in the hash, matching the original behavior. WhenRestis notIValueTupleInternal, it hashes only the first 7 fields.Changes
src/libraries/System.Private.CoreLib/src/System/ValueTuple.cs:usingdirectives (System.Runtime,System.Text,Internal.Runtime)ValueTuple.ToStringShared(ValueType, bool)static helper under#if NATIVEAOTValueTuple.GetHashCodeShared(ValueType)static helper under#if NATIVEAOTToString()andToStringEnd()methods (arities 1–8) with#if NATIVEAOT/#else/#endif, delegating to the shared helper on NativeAOTGetHashCode()overrides (arities 1–8) with#if NATIVEAOT/#else/#endif, delegating to the shared helper on NativeAOTsrc/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.GetFieldMethodOverrides.cs:RequiresValueTypeGetFieldHelperMethodto returntruefor ValueTuple types (was previously returningfalsesince ValueTuples override Equals/GetHashCode directly). ValueTuple types now need__GetFieldHelperfor field enumeration inToStringandGetHashCode.src/coreclr/tools/Common/TypeSystem/IL/Stubs/GetFieldHelperMethodOverride.cs:EmitILCommonto skip the memcompare shortcut for ValueTuple types. Previously, ValueTuple instantiations with simple fields (e.g.,ValueTuple<byte, byte, nint>) would get a__GetFieldHelperthat only returned a negative memcompare size instead of per-field enumeration, causing the shared helpers to fail with an assertion.Testing
Original prompt
ValueTuple.ToString#124449💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.