From 469b08180f87697ad3f227fc2aea1e6f1e0ddd7f Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Thu, 23 Jul 2026 23:42:27 -0500 Subject: [PATCH 1/6] [wasm][R2R] Fix Vector128 argument alignment in crossgen ArgIterator On wasm, the crossgen R2R<->interpreter thunk and the interpreter/VM ArgIterator disagreed on the stack alignment of Vector128 arguments. The crossgen ArgIterator (Wasm32 case) derived value-type alignment from GetFieldAlignment() (InstanceFieldAlignment), which the crossgen type system reports as 8 for Vector128, while the interpreter and runtime 16-align v128 args (getClassAlignmentRequirement). The resulting 8-byte desync accumulated per v128 argument, corrupting later arguments (e.g. the trailing generic-context byref) and producing a spurious NullReferenceException on the vectorized Base64Url.DecodeFromUtf8 path. Add ITypeHandle.RequiresAlign16OnWasm() in the RequiresAlign8 family and use it in the Wasm32 ArgIterator case to 16-align exactly the v128 SIMD types (Vector128 / 128-bit Vector, via WasmLowering.IsWasmV128Type), matching the runtime and interpreter. Non-v128 value types keep their prior alignment. Implement the predicate correctly for the cDAC reader by exposing IRuntimeTypeSystem.GetVectorElementSize (delegating to the existing live-metadata GetVectorHFAElementSize detection), so wasm stack-walk argument layout is reconstructed correctly instead of throwing. Fixes dotnet/runtime#131299 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- docs/design/datacontracts/RuntimeTypeSystem.md | 9 +++++++++ .../tools/Common/CallingConvention/ArgIterator.cs | 4 +++- .../tools/Common/CallingConvention/ITypeHandle.cs | 8 ++++++++ src/coreclr/tools/Common/JitInterface/WasmLowering.cs | 2 +- .../DependencyAnalysis/ReadyToRun/TypeHandle.cs | 11 +++++++++++ .../Contracts/IRuntimeTypeSystem.cs | 6 ++++++ .../Contracts/CallingConvention/CdacTypeHandle.cs | 7 +++++++ .../Contracts/RuntimeTypeSystem_1.cs | 5 +++++ 8 files changed, 50 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 181bf1ad41ad3e..2c9de74a9609af 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -84,6 +84,11 @@ partial interface IRuntimeTypeSystem : IContract // define FEATURE_HFA). Mirrors MethodTable::GetHFAType in // src/coreclr/vm/class.cpp. public virtual bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize); + // Returns the intrinsic SIMD vector element size derived from type metadata: 16 for + // Vector128, 8 for Vector64, and 8 or 16 for System.Numerics.Vector (based on its + // instance size); 0 for non-vector types. Unlike TryGetHFAElementSize this is not gated on + // FEATURE_HFA, so it is valid on wasm, where ArgIterator uses it to 16-byte align v128 args. + public virtual int GetVectorElementSize(ITypeHandle typeHandle); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) public virtual bool RequiresAlign8(ITypeHandle typeHandle); // Returns the cached SystemV AMD64 eightbyte register-passing classification for a value type @@ -827,6 +832,10 @@ static class RuntimeTypeSystem_1_Helpers // return elem public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) { ... } + // Exposes the metadata-derived intrinsic vector element size (see GetVectorHFAElementSize + // above) without FEATURE_HFA gating, so it is usable on wasm. + public int GetVectorElementSize(ITypeHandle typeHandle) => GetVectorHFAElementSize(typeHandle); + public bool RequiresAlign8(ITypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; public bool IsCanonicalMethodTable(ITypeHandle typeHandle) diff --git a/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs b/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs index 76cf68a550c3a2..e35289ab9f7a47 100644 --- a/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs +++ b/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs @@ -929,7 +929,9 @@ public int GetNextOffset() int align; if (isValueType) { - align = Math.Clamp(_argTypeHandle.GetFieldAlignment(), 8, 16); + align = _argTypeHandle.RequiresAlign16OnWasm() + ? 16 + : Math.Clamp(_argTypeHandle.GetFieldAlignment(), 8, 16); } else { diff --git a/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs b/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs index cb304e80f56484..fa1862b796c040 100644 --- a/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs +++ b/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs @@ -26,6 +26,14 @@ internal interface ITypeHandle CorElementType GetCorElementType(); bool RequiresAlign8(); + // Wasm: reports whether a value type argument must be 16-byte aligned (wasm v128). + // Vector128 / 128-bit Vector require 16-byte alignment to match the runtime's + // getClassAlignmentRequirement, but the crossgen type system under-reports their field + // alignment as 8 (pointer-sized), so the ArgIterator wasm case cannot rely on + // GetFieldAlignment alone. Named in the RequiresAlignN family for parity with + // RequiresAlign8; on non-wasm targets this is always false. + bool RequiresAlign16OnWasm(); + // HFA - ARM/ARM64 bool IsHomogeneousAggregate(); int GetHomogeneousAggregateElementSize(); diff --git a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs index 58447a357d4151..6a4638acd7fde2 100644 --- a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs +++ b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs @@ -113,7 +113,7 @@ public static TypeDesc LowerToAbiType(TypeDesc type) /// and non-primitive instantiations (e.g. the shared __Canon form) are not ABI /// primitives and continue to use the generic struct ABI. /// - private static bool IsWasmV128Type(TypeDesc type) + internal static bool IsWasmV128Type(TypeDesc type) { if (!type.IsIntrinsic || type.Instantiation.Length != 1 || diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs index 38d0f673a5de50..a548648dd1792e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs @@ -66,6 +66,17 @@ public bool RequiresAlign8() return _type.RequiresAlign8(); } + public bool RequiresAlign16OnWasm() + { + // On wasm, Vector128 / 128-bit Vector arguments must be 16-byte aligned to match + // the interpreter and runtime ArgIterator (getClassAlignmentRequirement). The crossgen + // type system under-reports their field alignment as 8 (pointer-sized), so detect the + // SIMD v128 types explicitly rather than relying on GetFieldAlignment. + return _type.Context.Target.Architecture == TargetArchitecture.Wasm32 + && !_isByRef + && WasmLowering.IsWasmV128Type(_type); + } + public bool IsHomogeneousAggregate() { TargetArchitecture targetArch = _type.Context.Target.Architecture; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index e5dd64fd005a3e..cb922e8f40ba61 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -178,6 +178,12 @@ public interface IRuntimeTypeSystem : IContract // define FEATURE_HFA). Mirrors MethodTable::GetHFAType in // src/coreclr/vm/class.cpp. bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) => throw new NotImplementedException(); + // Returns the intrinsic SIMD vector element size derived from type metadata: 16 for + // Vector128, 8 for Vector64, and 8 or 16 for System.Numerics.Vector (based on its + // instance size). Returns 0 for non-vector types. Unlike TryGetHFAElementSize this is not + // gated on FEATURE_HFA, so it is valid on wasm, where ArgIterator uses it to give v128 + // (16-byte) arguments the stack alignment the runtime and interpreter require. + int GetVectorElementSize(ITypeHandle typeHandle) => throw new NotImplementedException(); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) bool RequiresAlign8(ITypeHandle typeHandle) => throw new NotImplementedException(); // Returns the cached SystemV AMD64 eightbyte register-passing classification for a value type diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs index 1747dadf8e8282..5e8d2033e303cd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs @@ -235,6 +235,13 @@ public int GetFieldAlignment() throw new NotImplementedException("Field alignment is not yet implemented."); } + // Only used by ArgIterator on WASM32 for 16-byte (v128) stack alignment of value types. + // Vector128 / 128-bit Vector must be 16-byte aligned to match the runtime and + // interpreter ArgIterator; the crossgen type system under-reports their field alignment as + // 8, so the shared ArgIterator relies on this predicate instead of GetFieldAlignment. + public bool RequiresAlign16OnWasm() + => _typeHandle is not null && Rts.GetVectorElementSize(_typeHandle) == 16; + /// /// Maps cDAC CorElementType (short names like I4) to the shared CorElementType /// (ELEMENT_TYPE_* names). The numeric values are identical, so we cast directly. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index cde7ab5f9ee74b..907f242f511a8a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -735,6 +735,11 @@ public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) return false; } + // Public contract entry point: exposes the metadata-derived intrinsic vector element size + // (see GetVectorHFAElementSize) independent of FEATURE_HFA gating. Used by ArgIterator on + // wasm to 16-byte align v128 arguments. + public int GetVectorElementSize(ITypeHandle typeHandle) => GetVectorHFAElementSize(typeHandle); + // Mirrors MethodTable::GetVectorHFA in src/coreclr/vm/class.cpp. Any // metadata decode failure returns 0 (treated as "not an HVA"). private int GetVectorHFAElementSize(ITypeHandle typeHandle) From bf029bf91fa2177e4ec0ebc9158e95825d6621e6 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 24 Jul 2026 12:59:56 -0500 Subject: [PATCH 2/6] [wasm][R2R] Fold v128 alignment into GetFieldAlignment per review Addresses review feedback on #131328: - Drop the added ITypeHandle.RequiresAlign16OnWasm method; instead make crossgen's TypeHandle.GetFieldAlignment report 16 for wasm v128 types (Vector128 / 128-bit Vector) directly. GetFieldAlignment's sole caller is the Wasm32 ArgIterator case, so this has identical containment to the predicate approach with no new interface method and no call-site change. The value is correct on all architectures (InstanceFieldAlignment for Vector128 is already 16 on x64/arm64; only crossgen's wasm type system under-reported 8). (max-charlamb) - Rename the cDAC GetVectorHFAElementSize helper to a public GetVectorElementSize contract method instead of adding a thin wrapper; CdacTypeHandle.GetFieldAlignment reports 16 for v128 via it. (max-charlamb) - Clarify that GetVectorElementSize returns 0 for Vector256/Vector512. (adamperlin) The v128 argument alignment behavior is unchanged (byte-identical crossgen output); this only simplifies the shape per maintainer feedback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- .../design/datacontracts/RuntimeTypeSystem.md | 17 ++++++++-------- .../Common/CallingConvention/ArgIterator.cs | 4 +--- .../Common/CallingConvention/ITypeHandle.cs | 12 +++-------- .../ReadyToRun/TypeHandle.cs | 20 +++++++++---------- .../Contracts/IRuntimeTypeSystem.cs | 3 ++- .../CallingConvention/CdacTypeHandle.cs | 17 ++++++++-------- .../Contracts/RuntimeTypeSystem_1.cs | 17 ++++++++-------- .../Debuggees/CallSignatures/Program.cs | 2 +- 8 files changed, 42 insertions(+), 50 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 2c9de74a9609af..c04d71ee895e47 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -86,8 +86,9 @@ partial interface IRuntimeTypeSystem : IContract public virtual bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize); // Returns the intrinsic SIMD vector element size derived from type metadata: 16 for // Vector128, 8 for Vector64, and 8 or 16 for System.Numerics.Vector (based on its - // instance size); 0 for non-vector types. Unlike TryGetHFAElementSize this is not gated on - // FEATURE_HFA, so it is valid on wasm, where ArgIterator uses it to 16-byte align v128 args. + // instance size); 0 for non-vector types and for Vector256/Vector512 (never HVA + // elements). Unlike TryGetHFAElementSize this is not gated on FEATURE_HFA, so it is valid on + // wasm, where ArgIterator uses it to 16-byte align v128 args. public virtual int GetVectorElementSize(ITypeHandle typeHandle); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) public virtual bool RequiresAlign8(ITypeHandle typeHandle); @@ -811,7 +812,7 @@ static class RuntimeTypeSystem_1_Helpers // if targetArch is ARM: return (true, th.Flags.RequiresAlign8 ? 8 : 4) // mt = th // loop (bounded depth): - // if (elem = GetVectorHFAElementSize(mt)): return (true, elem) + // if (elem = GetVectorElementSize(mt)): return (true, elem) // field = first non-static field of mt // if field is null: return false // switch field.ElementType: @@ -820,21 +821,21 @@ static class RuntimeTypeSystem_1_Helpers // ValueType: mt = GetFieldDescApproxTypeHandle(field); continue // default: return false // - // GetVectorHFAElementSize(mt): // detects HVA shapes + // GetVectorElementSize(mt): // detects HVA / SIMD vector shapes // if !mt.Flags.IsIntrinsicType: return 0 // (ns, name) = typedef name+namespace via EcmaMetadata // elem = match on (ns, name): // "System.Numerics", "Vector`1": NumInstanceFieldBytes (8 or 16, else 0) // "System.Runtime.Intrinsics", "Vector128`1": 16 // "System.Runtime.Intrinsics", "Vector64`1": 8 - // _: return 0 + // _: return 0 // incl. Vector256/512 // if !CorIsNumericalType(GetInstantiation(mt)[0]): return 0 // return elem public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) { ... } - // Exposes the metadata-derived intrinsic vector element size (see GetVectorHFAElementSize - // above) without FEATURE_HFA gating, so it is usable on wasm. - public int GetVectorElementSize(ITypeHandle typeHandle) => GetVectorHFAElementSize(typeHandle); + // Metadata-derived intrinsic vector element size, without FEATURE_HFA gating so it is usable + // on wasm (see GetVectorElementSize pseudocode above). + public int GetVectorElementSize(ITypeHandle typeHandle) { ... } public bool RequiresAlign8(ITypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; diff --git a/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs b/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs index e35289ab9f7a47..76cf68a550c3a2 100644 --- a/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs +++ b/src/coreclr/tools/Common/CallingConvention/ArgIterator.cs @@ -929,9 +929,7 @@ public int GetNextOffset() int align; if (isValueType) { - align = _argTypeHandle.RequiresAlign16OnWasm() - ? 16 - : Math.Clamp(_argTypeHandle.GetFieldAlignment(), 8, 16); + align = Math.Clamp(_argTypeHandle.GetFieldAlignment(), 8, 16); } else { diff --git a/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs b/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs index fa1862b796c040..5d01bdf5dbef87 100644 --- a/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs +++ b/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs @@ -26,14 +26,6 @@ internal interface ITypeHandle CorElementType GetCorElementType(); bool RequiresAlign8(); - // Wasm: reports whether a value type argument must be 16-byte aligned (wasm v128). - // Vector128 / 128-bit Vector require 16-byte alignment to match the runtime's - // getClassAlignmentRequirement, but the crossgen type system under-reports their field - // alignment as 8 (pointer-sized), so the ArgIterator wasm case cannot rely on - // GetFieldAlignment alone. Named in the RequiresAlignN family for parity with - // RequiresAlign8; on non-wasm targets this is always false. - bool RequiresAlign16OnWasm(); - // HFA - ARM/ARM64 bool IsHomogeneousAggregate(); int GetHomogeneousAggregateElementSize(); @@ -47,7 +39,9 @@ internal interface ITypeHandle // x86 - trivial pointer-sized struct check for register passing bool IsTrivialPointerSizedStruct(); - // LoongArch64/Wasm alignment + // LoongArch64/Wasm alignment. On wasm this also reports 16 for Vector128 / 128-bit + // Vector (whose InstanceFieldAlignment under-reports as 8) so the Wasm32 ArgIterator + // 16-aligns v128 arguments to match the runtime and interpreter. int GetFieldAlignment(); private static readonly int[] s_elemSizes = new int[] diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs index a548648dd1792e..912fd8127c7349 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs @@ -66,17 +66,6 @@ public bool RequiresAlign8() return _type.RequiresAlign8(); } - public bool RequiresAlign16OnWasm() - { - // On wasm, Vector128 / 128-bit Vector arguments must be 16-byte aligned to match - // the interpreter and runtime ArgIterator (getClassAlignmentRequirement). The crossgen - // type system under-reports their field alignment as 8 (pointer-sized), so detect the - // SIMD v128 types explicitly rather than relying on GetFieldAlignment. - return _type.Context.Target.Architecture == TargetArchitecture.Wasm32 - && !_isByRef - && WasmLowering.IsWasmV128Type(_type); - } - public bool IsHomogeneousAggregate() { TargetArchitecture targetArch = _type.Context.Target.Architecture; @@ -189,6 +178,15 @@ public bool IsTrivialPointerSizedStruct() public int GetFieldAlignment() { + // Vector128 / 128-bit Vector arguments must be 16-byte aligned on wasm to match the + // interpreter and runtime ArgIterator (getClassAlignmentRequirement). The crossgen type + // system reports their InstanceFieldAlignment as 8 (pointer-sized), so pin the wasm v128 + // SIMD types to 16 here; the ArgIterator wasm case is the sole caller of this method. + if (WasmLowering.IsWasmV128Type(_type)) + { + return 16; + } + return ((DefType)_type).InstanceFieldAlignment.AsInt; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index cb922e8f40ba61..bf39e77dfcc2e9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -180,7 +180,8 @@ public interface IRuntimeTypeSystem : IContract bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) => throw new NotImplementedException(); // Returns the intrinsic SIMD vector element size derived from type metadata: 16 for // Vector128, 8 for Vector64, and 8 or 16 for System.Numerics.Vector (based on its - // instance size). Returns 0 for non-vector types. Unlike TryGetHFAElementSize this is not + // instance size). Returns 0 for non-vector types, and also for Vector256/Vector512 + // (never HVA elements; no caller needs their size). Unlike TryGetHFAElementSize this is not // gated on FEATURE_HFA, so it is valid on wasm, where ArgIterator uses it to give v128 // (16-byte) arguments the stack alignment the runtime and interpreter require. int GetVectorElementSize(ITypeHandle typeHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs index 5e8d2033e303cd..2137ba06a0ef55 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs @@ -229,19 +229,20 @@ public bool IsTrivialPointerSizedStruct() } } - // Only used by ArgIterator on WASM32 for stack alignment of value types. + // Only used by ArgIterator on WASM32 for stack alignment of value types. Vector128 / + // 128-bit Vector must be 16-byte aligned to match the runtime and interpreter ArgIterator; + // the crossgen type system under-reports their field alignment as 8, so report 16 for the wasm + // v128 SIMD types here. Non-v128 value types are not yet reconstructed by the cDAC reader. public int GetFieldAlignment() { + if (_typeHandle is not null && Rts.GetVectorElementSize(_typeHandle) == 16) + { + return 16; + } + throw new NotImplementedException("Field alignment is not yet implemented."); } - // Only used by ArgIterator on WASM32 for 16-byte (v128) stack alignment of value types. - // Vector128 / 128-bit Vector must be 16-byte aligned to match the runtime and - // interpreter ArgIterator; the crossgen type system under-reports their field alignment as - // 8, so the shared ArgIterator relies on this predicate instead of GetFieldAlignment. - public bool RequiresAlign16OnWasm() - => _typeHandle is not null && Rts.GetVectorElementSize(_typeHandle) == 16; - /// /// Maps cDAC CorElementType (short names like I4) to the shared CorElementType /// (ELEMENT_TYPE_* names). The numeric values are identical, so we cast directly. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 907f242f511a8a..ac5bf2bfc8ca44 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -693,7 +693,7 @@ public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) ITypeHandle current = typeHandle; for (int depth = 0; depth < 16; depth++) { - int vectorElem = GetVectorHFAElementSize(current); + int vectorElem = GetVectorElementSize(current); if (vectorElem != 0) { elementSize = vectorElem; @@ -735,14 +735,13 @@ public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) return false; } - // Public contract entry point: exposes the metadata-derived intrinsic vector element size - // (see GetVectorHFAElementSize) independent of FEATURE_HFA gating. Used by ArgIterator on - // wasm to 16-byte align v128 arguments. - public int GetVectorElementSize(ITypeHandle typeHandle) => GetVectorHFAElementSize(typeHandle); - - // Mirrors MethodTable::GetVectorHFA in src/coreclr/vm/class.cpp. Any - // metadata decode failure returns 0 (treated as "not an HVA"). - private int GetVectorHFAElementSize(ITypeHandle typeHandle) + // Metadata-derived intrinsic vector element size, independent of FEATURE_HFA gating so it is + // usable on wasm (where ArgIterator uses it to 16-byte align v128 arguments) as well as by the + // HFA/HVA classification below. Returns 8 for Vector64 and 8-byte Vector, 16 for + // Vector128 and 16-byte Vector, and 0 for anything else. Vector256/Vector512 return + // 0: they are never HVA elements and no caller needs their size. Mirrors + // MethodTable::GetVectorHFA in src/coreclr/vm/class.cpp. Any metadata decode failure returns 0. + public int GetVectorElementSize(ITypeHandle typeHandle) { if (!typeHandle.IsMethodTable() || !_methodTables[typeHandle.Address].Flags.IsIntrinsicType) return 0; diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs b/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs index 95521b24c47cfd..887e0ef83535bc 100644 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs @@ -532,7 +532,7 @@ private struct NestedDouble4 { public Double2 First; public Double2 Second; } // --- HVA shapes (intrinsic Vector types). On non-FEATURE_HFA targets // these go through the regular struct path; on ARM64 they hit the - // GetVectorHFAElementSize TypeDef-name match (Vector64/128 in + // GetVectorElementSize TypeDef-name match (Vector64/128 in // System.Runtime.Intrinsics, Vector in System.Numerics). --- [MethodImpl(MethodImplOptions.NoInlining)] private static void Vec64FloatArg(Vector64 v) { AllocBurst(); GC.KeepAlive((object)v.GetElement(0)); } [MethodImpl(MethodImplOptions.NoInlining)] private static void Vec128FloatArg(Vector128 v) { AllocBurst(); GC.KeepAlive((object)v.GetElement(0)); } From 41df12a10b1cbbf10074dc466285a896aeb5f552 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 24 Jul 2026 13:52:44 -0500 Subject: [PATCH 3/6] [wasm][R2R] Give Vector 16-byte field alignment on wasm Replaces the ArgIterator/GetFieldAlignment special case with a fix to the underlying field alignment, per review feedback. Every type that lowers to a wasm v128 is 16 bytes and should be 16-byte aligned, but System.Numerics.Vector was left with the 8-byte alignment its metadata layout produces (its two UInt64 fields). Since the wasm thunks encode all v128 types as a single 'V' signature character and RaiseSignature turns that back into whichever v128 type was lowered first, a Vector reaching the cache gave every raised signature 8-byte aligned v128 arguments. That disagreed with the runtime and the interpreter, which 16-align v128 arguments, and the resulting desync corrupted later arguments - producing a spurious NullReferenceException on the vectorized Base64Url.DecodeFromUtf8 path. - crossgen2: the ReadyToRun VectorOfTFieldLayoutAlgorithm now takes the field alignment from the matching intrinsic vector (Vector128) on wasm, where Vector does follow the intrinsic vector calling convention. Other targets are unchanged and keep the metadata alignment. - runtime: MethodTableBuilder::CheckForSystemTypes sets a 16-byte alignment requirement for System.Numerics.Vector on wasm, matching the treatment of Vector128 and Int128. - Assert the invariant in CacheV128Type, which sees every v128 type that is lowered. This caught the missing crossgen2 case above. On the cDAC side CdacTypeHandle.GetFieldAlignment now reports 16 for v128 types so wasm argument layout is reconstructed the same way. Other value types still throw NotImplementedException, as before. Fixes dotnet/runtime#131299 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- .../tools/Common/CallingConvention/ITypeHandle.cs | 4 +--- .../Compiler/CompilerTypeSystemContext.Wasm.cs | 14 ++++++++++++-- .../tools/Common/JitInterface/WasmLowering.cs | 2 +- .../DependencyAnalysis/ReadyToRun/TypeHandle.cs | 9 --------- .../Compiler/ReadyToRunCompilerContext.cs | 8 +++++++- src/coreclr/vm/methodtablebuilder.cpp | 12 ++++++++++++ .../Contracts/CallingConvention/CdacTypeHandle.cs | 6 +++--- .../Contracts/RuntimeTypeSystem_1.cs | 2 +- 8 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs b/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs index 5d01bdf5dbef87..cb304e80f56484 100644 --- a/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs +++ b/src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs @@ -39,9 +39,7 @@ internal interface ITypeHandle // x86 - trivial pointer-sized struct check for register passing bool IsTrivialPointerSizedStruct(); - // LoongArch64/Wasm alignment. On wasm this also reports 16 for Vector128 / 128-bit - // Vector (whose InstanceFieldAlignment under-reports as 8) so the Wasm32 ArgIterator - // 16-aligns v128 arguments to match the runtime and interpreter. + // LoongArch64/Wasm alignment int GetFieldAlignment(); private static readonly int[] s_elemSizes = new int[] diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs index bd6ec1ef7b184a..e07c81406286fc 100644 --- a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs +++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs @@ -5,6 +5,8 @@ using Internal.TypeSystem; +using Debug = System.Diagnostics.Debug; + namespace ILCompiler { public partial class CompilerTypeSystemContext @@ -16,15 +18,23 @@ public partial class CompilerTypeSystemContext /// /// Gets the first SIMD v128 type encountered during lowering, or null if none has been seen. - /// Used by RaiseSignature to produce a roundtrippable type for the 'V' encoding. + /// Used by RaiseSignature to produce a roundtrippable type for the 'V' encoding. Any v128 + /// type is usable there because all of them share the same wasm ABI (see CacheV128Type). /// public TypeDesc CachedV128Type => _cachedV128Type; /// - /// Caches a SIMD v128 type discovered during lowering. Only the first one is retained. + /// Caches a SIMD v128 type discovered during lowering. Only the first one is retained: + /// every type that lowers to a wasm v128 is 16 bytes with 16-byte alignment, so they + /// are interchangeable for the signature round-trip that RaiseSignature performs. The assert + /// guards that invariant, since a v128 type with a smaller alignment would silently give + /// raised signatures a different argument layout depending on which type was lowered first. /// public void CacheV128Type(TypeDesc type) { + Debug.Assert(((DefType)type).InstanceFieldAlignment.AsInt == 16, + $"v128 type {type} must be 16-byte aligned to be interchangeable in raised signatures"); + _cachedV128Type ??= type; } diff --git a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs index 6a4638acd7fde2..58447a357d4151 100644 --- a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs +++ b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs @@ -113,7 +113,7 @@ public static TypeDesc LowerToAbiType(TypeDesc type) /// and non-primitive instantiations (e.g. the shared __Canon form) are not ABI /// primitives and continue to use the generic struct ABI. /// - internal static bool IsWasmV128Type(TypeDesc type) + private static bool IsWasmV128Type(TypeDesc type) { if (!type.IsIntrinsic || type.Instantiation.Length != 1 || diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs index 912fd8127c7349..38d0f673a5de50 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs @@ -178,15 +178,6 @@ public bool IsTrivialPointerSizedStruct() public int GetFieldAlignment() { - // Vector128 / 128-bit Vector arguments must be 16-byte aligned on wasm to match the - // interpreter and runtime ArgIterator (getClassAlignmentRequirement). The crossgen type - // system reports their InstanceFieldAlignment as 8 (pointer-sized), so pin the wasm v128 - // SIMD types to 16 here; the ArgIterator wasm case is the sole caller of this method. - if (WasmLowering.IsWasmV128Type(_type)) - { - return 16; - } - return ((DefType)_type).InstanceFieldAlignment.AsInt; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs index edf32f30df371a..ceff1bcc1e6c60 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs @@ -311,7 +311,13 @@ public override ComputedInstanceFieldLayout ComputeInstanceLayout(DefType type, { ByteCountUnaligned = layoutFromSimilarIntrinsicVector.ByteCountUnaligned, ByteCountAlignment = layoutFromMetadata.ByteCountAlignment, - FieldAlignment = layoutFromMetadata.FieldAlignment, + // On wasm Vector is passed as a v128, exactly like the similar intrinsic + // vector, so it has to share that type's 16-byte alignment. Elsewhere Vector + // does not follow the intrinsic vector calling convention yet, and keeps the + // alignment its metadata layout produces (see MATCHING_HARDWARE_VECTOR above). + FieldAlignment = type.Context.Target.Architecture == TargetArchitecture.Wasm32 + ? layoutFromSimilarIntrinsicVector.FieldAlignment + : layoutFromMetadata.FieldAlignment, FieldSize = layoutFromSimilarIntrinsicVector.FieldSize, Offsets = layoutFromMetadata.Offsets, LayoutAbiStable = true, diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index ca229d3e0056df..1078093fdf2249 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -10700,6 +10700,18 @@ void MethodTableBuilder::CheckForSystemTypes() return; } + +#ifdef TARGET_WASM + // System.Numerics.Vector is a v128 value on wasm, so it needs the same 16-byte + // alignment as System.Runtime.Intrinsics.Vector128 above. Its metadata layout is + // already 16 bytes (two UInt64 fields), but those only give it 8-byte alignment, + // which disagrees with crossgen2 and the interpreter. + if ((strcmp(nameSpace, g_NumericsNS) == 0) && (strcmp(name, "Vector`1") == 0)) + { + pClass->GetLayoutInfo()->SetAlignmentRequirement(16); // sizeof(v128) + return; + } +#endif // TARGET_WASM } if (g_pNullableClass != NULL) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs index 2137ba06a0ef55..b75f8dd01d21b6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs @@ -230,9 +230,9 @@ public bool IsTrivialPointerSizedStruct() } // Only used by ArgIterator on WASM32 for stack alignment of value types. Vector128 / - // 128-bit Vector must be 16-byte aligned to match the runtime and interpreter ArgIterator; - // the crossgen type system under-reports their field alignment as 8, so report 16 for the wasm - // v128 SIMD types here. Non-v128 value types are not yet reconstructed by the cDAC reader. + // 128-bit Vector are 16-byte aligned by the runtime and interpreter ArgIterator + // (getClassAlignmentRequirement), so report 16 for the wasm v128 SIMD types to reconstruct + // the same argument layout. Non-v128 value types are not yet reconstructed by the cDAC reader. public int GetFieldAlignment() { if (_typeHandle is not null && Rts.GetVectorElementSize(_typeHandle) == 16) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index ac5bf2bfc8ca44..b7ab2f37a38dc7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -737,7 +737,7 @@ public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) // Metadata-derived intrinsic vector element size, independent of FEATURE_HFA gating so it is // usable on wasm (where ArgIterator uses it to 16-byte align v128 arguments) as well as by the - // HFA/HVA classification below. Returns 8 for Vector64 and 8-byte Vector, 16 for + // HFA/HVA classification above. Returns 8 for Vector64 and 8-byte Vector, 16 for // Vector128 and 16-byte Vector, and 0 for anything else. Vector256/Vector512 return // 0: they are never HVA elements and no caller needs their size. Mirrors // MethodTable::GetVectorHFA in src/coreclr/vm/class.cpp. Any metadata decode failure returns 0. From 908c33d43212a84da75df4d7fecfbf7b5bd9ee6c Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 24 Jul 2026 14:24:29 -0500 Subject: [PATCH 4/6] [wasm][R2R] Drop cDAC v128 alignment changes Remove the cDAC-side changes (GetVectorElementSize contract method and the CdacTypeHandle.GetFieldAlignment v128 special case) from this PR. They re-derived "v128 means 16-byte aligned" by matching type metadata names, which is a partial reimplementation of the runtime's alignment policy that cannot converge on it: the runtime already 16-aligns Int128/UInt128 on wasm too (not vector types), so a Vector-name match can never cover the same set. The faithful cDAC implementation is to read EEClassLayoutInfo's alignment requirement - mirroring CEEInfo::getClassAlignmentRequirementStatic, which is what the runtime wasm ArgIterator uses - which needs its own data descriptor and contract surface and fixes all value types at once. That is left as a separate cDAC-side change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- docs/design/datacontracts/RuntimeTypeSystem.md | 16 +++------------- .../Contracts/IRuntimeTypeSystem.cs | 7 ------- .../CallingConvention/CdacTypeHandle.cs | 10 +--------- .../Contracts/RuntimeTypeSystem_1.cs | 12 ++++-------- .../Debuggees/CallSignatures/Program.cs | 2 +- 5 files changed, 9 insertions(+), 38 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index c04d71ee895e47..181bf1ad41ad3e 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -84,12 +84,6 @@ partial interface IRuntimeTypeSystem : IContract // define FEATURE_HFA). Mirrors MethodTable::GetHFAType in // src/coreclr/vm/class.cpp. public virtual bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize); - // Returns the intrinsic SIMD vector element size derived from type metadata: 16 for - // Vector128, 8 for Vector64, and 8 or 16 for System.Numerics.Vector (based on its - // instance size); 0 for non-vector types and for Vector256/Vector512 (never HVA - // elements). Unlike TryGetHFAElementSize this is not gated on FEATURE_HFA, so it is valid on - // wasm, where ArgIterator uses it to 16-byte align v128 args. - public virtual int GetVectorElementSize(ITypeHandle typeHandle); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) public virtual bool RequiresAlign8(ITypeHandle typeHandle); // Returns the cached SystemV AMD64 eightbyte register-passing classification for a value type @@ -812,7 +806,7 @@ static class RuntimeTypeSystem_1_Helpers // if targetArch is ARM: return (true, th.Flags.RequiresAlign8 ? 8 : 4) // mt = th // loop (bounded depth): - // if (elem = GetVectorElementSize(mt)): return (true, elem) + // if (elem = GetVectorHFAElementSize(mt)): return (true, elem) // field = first non-static field of mt // if field is null: return false // switch field.ElementType: @@ -821,22 +815,18 @@ static class RuntimeTypeSystem_1_Helpers // ValueType: mt = GetFieldDescApproxTypeHandle(field); continue // default: return false // - // GetVectorElementSize(mt): // detects HVA / SIMD vector shapes + // GetVectorHFAElementSize(mt): // detects HVA shapes // if !mt.Flags.IsIntrinsicType: return 0 // (ns, name) = typedef name+namespace via EcmaMetadata // elem = match on (ns, name): // "System.Numerics", "Vector`1": NumInstanceFieldBytes (8 or 16, else 0) // "System.Runtime.Intrinsics", "Vector128`1": 16 // "System.Runtime.Intrinsics", "Vector64`1": 8 - // _: return 0 // incl. Vector256/512 + // _: return 0 // if !CorIsNumericalType(GetInstantiation(mt)[0]): return 0 // return elem public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) { ... } - // Metadata-derived intrinsic vector element size, without FEATURE_HFA gating so it is usable - // on wasm (see GetVectorElementSize pseudocode above). - public int GetVectorElementSize(ITypeHandle typeHandle) { ... } - public bool RequiresAlign8(ITypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8; public bool IsCanonicalMethodTable(ITypeHandle typeHandle) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index bf39e77dfcc2e9..e5dd64fd005a3e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -178,13 +178,6 @@ public interface IRuntimeTypeSystem : IContract // define FEATURE_HFA). Mirrors MethodTable::GetHFAType in // src/coreclr/vm/class.cpp. bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) => throw new NotImplementedException(); - // Returns the intrinsic SIMD vector element size derived from type metadata: 16 for - // Vector128, 8 for Vector64, and 8 or 16 for System.Numerics.Vector (based on its - // instance size). Returns 0 for non-vector types, and also for Vector256/Vector512 - // (never HVA elements; no caller needs their size). Unlike TryGetHFAElementSize this is not - // gated on FEATURE_HFA, so it is valid on wasm, where ArgIterator uses it to give v128 - // (16-byte) arguments the stack alignment the runtime and interpreter require. - int GetVectorElementSize(ITypeHandle typeHandle) => throw new NotImplementedException(); // True if the type requires 8-byte alignment on platforms that don't 8-byte align by default (FEATURE_64BIT_ALIGNMENT) bool RequiresAlign8(ITypeHandle typeHandle) => throw new NotImplementedException(); // Returns the cached SystemV AMD64 eightbyte register-passing classification for a value type diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs index b75f8dd01d21b6..1747dadf8e8282 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs @@ -229,17 +229,9 @@ public bool IsTrivialPointerSizedStruct() } } - // Only used by ArgIterator on WASM32 for stack alignment of value types. Vector128 / - // 128-bit Vector are 16-byte aligned by the runtime and interpreter ArgIterator - // (getClassAlignmentRequirement), so report 16 for the wasm v128 SIMD types to reconstruct - // the same argument layout. Non-v128 value types are not yet reconstructed by the cDAC reader. + // Only used by ArgIterator on WASM32 for stack alignment of value types. public int GetFieldAlignment() { - if (_typeHandle is not null && Rts.GetVectorElementSize(_typeHandle) == 16) - { - return 16; - } - throw new NotImplementedException("Field alignment is not yet implemented."); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index b7ab2f37a38dc7..cde7ab5f9ee74b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -693,7 +693,7 @@ public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) ITypeHandle current = typeHandle; for (int depth = 0; depth < 16; depth++) { - int vectorElem = GetVectorElementSize(current); + int vectorElem = GetVectorHFAElementSize(current); if (vectorElem != 0) { elementSize = vectorElem; @@ -735,13 +735,9 @@ public bool TryGetHFAElementSize(ITypeHandle typeHandle, out int elementSize) return false; } - // Metadata-derived intrinsic vector element size, independent of FEATURE_HFA gating so it is - // usable on wasm (where ArgIterator uses it to 16-byte align v128 arguments) as well as by the - // HFA/HVA classification above. Returns 8 for Vector64 and 8-byte Vector, 16 for - // Vector128 and 16-byte Vector, and 0 for anything else. Vector256/Vector512 return - // 0: they are never HVA elements and no caller needs their size. Mirrors - // MethodTable::GetVectorHFA in src/coreclr/vm/class.cpp. Any metadata decode failure returns 0. - public int GetVectorElementSize(ITypeHandle typeHandle) + // Mirrors MethodTable::GetVectorHFA in src/coreclr/vm/class.cpp. Any + // metadata decode failure returns 0 (treated as "not an HVA"). + private int GetVectorHFAElementSize(ITypeHandle typeHandle) { if (!typeHandle.IsMethodTable() || !_methodTables[typeHandle.Address].Flags.IsIntrinsicType) return 0; diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs b/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs index 887e0ef83535bc..95521b24c47cfd 100644 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs @@ -532,7 +532,7 @@ private struct NestedDouble4 { public Double2 First; public Double2 Second; } // --- HVA shapes (intrinsic Vector types). On non-FEATURE_HFA targets // these go through the regular struct path; on ARM64 they hit the - // GetVectorElementSize TypeDef-name match (Vector64/128 in + // GetVectorHFAElementSize TypeDef-name match (Vector64/128 in // System.Runtime.Intrinsics, Vector in System.Numerics). --- [MethodImpl(MethodImplOptions.NoInlining)] private static void Vec64FloatArg(Vector64 v) { AllocBurst(); GC.KeepAlive((object)v.GetElement(0)); } [MethodImpl(MethodImplOptions.NoInlining)] private static void Vec128FloatArg(Vector128 v) { AllocBurst(); GC.KeepAlive((object)v.GetElement(0)); } From 69ee1f23bfb312992160a1720a6ca46acbc29341 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 24 Jul 2026 14:37:30 -0500 Subject: [PATCH 5/6] [wasm][R2R] Trim v128 alignment comments Cut the explanatory comments down to the essential invariant; the crossgen2 alignment change and the assert are self-evident from the code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- .../Common/Compiler/CompilerTypeSystemContext.Wasm.cs | 11 ++++------- .../Compiler/ReadyToRunCompilerContext.cs | 5 +---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs index e07c81406286fc..6f846952a20ad3 100644 --- a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs +++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs @@ -18,20 +18,17 @@ public partial class CompilerTypeSystemContext /// /// Gets the first SIMD v128 type encountered during lowering, or null if none has been seen. - /// Used by RaiseSignature to produce a roundtrippable type for the 'V' encoding. Any v128 - /// type is usable there because all of them share the same wasm ABI (see CacheV128Type). + /// Used by RaiseSignature to produce a roundtrippable type for the 'V' encoding. /// public TypeDesc CachedV128Type => _cachedV128Type; /// - /// Caches a SIMD v128 type discovered during lowering. Only the first one is retained: - /// every type that lowers to a wasm v128 is 16 bytes with 16-byte alignment, so they - /// are interchangeable for the signature round-trip that RaiseSignature performs. The assert - /// guards that invariant, since a v128 type with a smaller alignment would silently give - /// raised signatures a different argument layout depending on which type was lowered first. + /// Caches a SIMD v128 type discovered during lowering. Only the first one is retained. /// public void CacheV128Type(TypeDesc type) { + // All v128 types share the same wasm ABI (16-byte aligned), so any one round-trips the + // 'V' encoding identically; a smaller alignment would change raised signatures silently. Debug.Assert(((DefType)type).InstanceFieldAlignment.AsInt == 16, $"v128 type {type} must be 16-byte aligned to be interchangeable in raised signatures"); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs index ceff1bcc1e6c60..2ee6dcd7e046fe 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs @@ -311,10 +311,7 @@ public override ComputedInstanceFieldLayout ComputeInstanceLayout(DefType type, { ByteCountUnaligned = layoutFromSimilarIntrinsicVector.ByteCountUnaligned, ByteCountAlignment = layoutFromMetadata.ByteCountAlignment, - // On wasm Vector is passed as a v128, exactly like the similar intrinsic - // vector, so it has to share that type's 16-byte alignment. Elsewhere Vector - // does not follow the intrinsic vector calling convention yet, and keeps the - // alignment its metadata layout produces (see MATCHING_HARDWARE_VECTOR above). + // On wasm Vector is passed as a v128 and must share its 16-byte alignment. FieldAlignment = type.Context.Target.Architecture == TargetArchitecture.Wasm32 ? layoutFromSimilarIntrinsicVector.FieldAlignment : layoutFromMetadata.FieldAlignment, From b0c64d669bc0c2fdbe324c0c9f1d1699ec3390c7 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 24 Jul 2026 14:54:03 -0500 Subject: [PATCH 6/6] [wasm][R2R] Pattern-match the v128 alignment assert Use `type is DefType defType` in the CacheV128Type assert so an unexpected non-DefType fails via Debug.Assert with the intended message instead of an InvalidCastException from the cast. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- .../tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs index 6f846952a20ad3..3cd7f44f927d69 100644 --- a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs +++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs @@ -29,7 +29,7 @@ public void CacheV128Type(TypeDesc type) { // All v128 types share the same wasm ABI (16-byte aligned), so any one round-trips the // 'V' encoding identically; a smaller alignment would change raised signatures silently. - Debug.Assert(((DefType)type).InstanceFieldAlignment.AsInt == 16, + Debug.Assert(type is DefType defType && defType.InstanceFieldAlignment.AsInt == 16, $"v128 type {type} must be 16-byte aligned to be interchangeable in raised signatures"); _cachedV128Type ??= type;