From 6189b46491e85ad95c4bd8e83154aa36bf641871 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Mon, 27 Jul 2026 09:59:09 -0500 Subject: [PATCH 1/2] [wasm] Use ordinary struct alignment for Int128/UInt128 on wasm Int128/UInt128 were given a 16-byte alignment requirement on wasm by both the VM and crossgen2, annotated "sizeof(v128)". Int128 is not a v128: the only 128-bit value type in WebAssembly is v128, and WasmLowering.IsWasmV128Type recognizes only Vector128 and a 128-bit Vector, so Int128 uses the generic by-reference struct ABI. Every other alignment override in CheckForSystemTypes exists because the managed type corresponds to a fundamental data type in the target ABI (Vector128 to __m128, and so on). Int128 has no counterpart on wasm, so it takes ordinary struct alignment, which is the 8 its two ulong fields produce. On the crossgen2 side that means routing wasm through the existing 32-bit path. The 16 was observable as wrong codegen at the interpreter-to-R2R boundary. Wasm signature encoding spells a by-reference struct argument as S, and RaiseSignature resolves S16 through GetCachedStructOfSize, which keys only on size and keeps the first struct seen at that size. The thunk therefore used that struct's 8-byte alignment while the interpreter used Int128's 16, placing the argument at argsBase+24 instead of argsBase+32. Int128.Equals and CompareTo then read a shifted value while operator== was unaffected. This also makes S sound rather than accidentally correct: single-field structs wrapping a v128 are unwrapped by LowerType and encode as V, and a v128 field is the only source of 16-byte alignment on wasm, so once Int128 takes its natural alignment nothing reaching S requires more than 8. The VM and crossgen2 must change together or managed field layout desyncs. Validated on browser wasm against builds with and without the change, rebuilding both corerun and the R2R composite for each: Int128 repro, R2R on: before, Equals=False and CompareTo=+/-1 on equal values; after, all correct. R2R off correct in both. System.Text.Json, the Int128-bearing classes, 1496 tests, on a branch carrying the other outstanding wasm R2R fixes: before, R2R-on 20 failed (15 Int128); after, 2 failed (0 Int128). Those 2 also fail with R2R off, so they are pre-existing and unrelated. Microsoft.Bcl.Memory 550/550 and Unsafe 128/128, R2R on and off. Encoding Int128 as V instead does not work: V selects a different calling convention, passing the value in a wasm v128 local rather than by reference, and the JIT has no notion of Int128, so R2R code faults with "function signature mismatch". Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- .../tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs | 2 +- src/coreclr/vm/methodtablebuilder.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs index c38a7bdbead108..ad98ae3cc1e101 100644 --- a/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/Compiler/Int128FieldLayoutAlgorithm.cs @@ -31,7 +31,7 @@ public override ComputedInstanceFieldLayout ComputeInstanceLayout(DefType defTyp ComputedInstanceFieldLayout layoutFromMetadata = _fallbackAlgorithm.ComputeInstanceLayout(defType, layoutKind); // 32bit platforms use standard metadata layout engine - if (defType.Context.Target.Architecture == TargetArchitecture.ARM) + if (defType.Context.Target.Architecture is TargetArchitecture.ARM or TargetArchitecture.Wasm32) { layoutFromMetadata.LayoutAbiStable = true; layoutFromMetadata.IsInt128OrHasInt128Fields = true; diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index c18e0dd914fb21..de320a56206e8b 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -10817,7 +10817,8 @@ void MethodTableBuilder::CheckForSystemTypes() // even on X86 pLayout->SetAlignmentRequirement(16); // sizeof(__int128) #elif defined(TARGET_WASM) - pLayout->SetAlignmentRequirement(16); // sizeof(v128) + // Wasm has no 128-bit scalar type to match, so use the natural field alignment. + pLayout->SetAlignmentRequirement(8); #else #error Unknown architecture #endif // TARGET_64BIT From 5fe365d41abf8324dc0440dc75567c2860fbfd66 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Mon, 27 Jul 2026 10:10:23 -0500 Subject: [PATCH 2/2] Add Wasm32 cases to the Int128/UInt128 architecture layout tests Covers the new Wasm32 branch in Int128FieldLayoutAlgorithm alongside the existing ARM/ARM64/X86/X64 cases. Both new cases fail without the layout change (alignment 16 rather than 8). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- .../ArchitectureSpecificFieldLayoutTests.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs index 987d49ce274c66..c972dcbe8a60e8 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs @@ -22,6 +22,8 @@ public class ArchitectureSpecificFieldLayoutTests private ModuleDesc _testModuleARM; private TestTypeSystemContext _contextARM64; private ModuleDesc _testModuleARM64; + private TestTypeSystemContext _contextWasm32; + private ModuleDesc _testModuleWasm32; public ArchitectureSpecificFieldLayoutTests() { @@ -60,6 +62,12 @@ public ArchitectureSpecificFieldLayoutTests() _contextARM64.SetSystemModule(systemModuleARM64); _testModuleARM64 = systemModuleARM64; + + _contextWasm32 = new TestTypeSystemContext(TargetArchitecture.Wasm32); + var systemModuleWasm32 = _contextWasm32.CreateModuleForSimpleName("CoreTestAssembly"); + _contextWasm32.SetSystemModule(systemModuleWasm32); + + _testModuleWasm32 = systemModuleWasm32; } [Fact] @@ -501,11 +509,13 @@ public void TestAlignmentBehavior_AutoAlignmentRules(string wrapperType, int[] a [InlineData("StructStructByte_Int128StructAuto", "X86", 16, 32)] [InlineData("StructStructByte_Int128StructAuto", "X64Linux", 16, 32)] [InlineData("StructStructByte_Int128StructAuto", "X64Windows", 16, 32)] + [InlineData("StructStructByte_Int128StructAuto", "Wasm32", 8, 24)] [InlineData("StructStructByte_UInt128StructAuto", "ARM64", 16, 32)] [InlineData("StructStructByte_UInt128StructAuto", "ARM", 8, 24)] [InlineData("StructStructByte_UInt128StructAuto", "X86", 16, 32)] [InlineData("StructStructByte_UInt128StructAuto", "X64Linux", 16, 32)] [InlineData("StructStructByte_UInt128StructAuto", "X64Windows", 16, 32)] + [InlineData("StructStructByte_UInt128StructAuto", "Wasm32", 8, 24)] // Variation of TestAlignmentBehavior_AutoAlignmentRules above that is able to deal with os specific behavior public void TestAlignmentBehavior_AutoAlignmentRulesWithOSDependence(string wrapperType, string osArch, int alignment, int size) { @@ -530,6 +540,9 @@ public void TestAlignmentBehavior_AutoAlignmentRulesWithOSDependence(string wrap case "X86": testModule = _testModuleX86; break; + case "Wasm32": + testModule = _testModuleWasm32; + break; default: throw new Exception(); }