Skip to content

[wasm] Keep larger field alignment for Align8 auto-layout structs - #135481

Open
lewing wants to merge 2 commits into
mainfrom
lewing-fix-wasm-composite-int128-reverse-pinvok
Open

lewing wants to merge 2 commits into
mainfrom
lewing-fix-wasm-composite-int128-reverse-pinvok

Conversation

@lewing

@lewing lewing commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

On FEATURE_64BIT_ALIGNMENT targets (ARM32 and wasm), MethodTableBuilder::HandleAutoLayout set the alignment of an auto-layout value type that is an Align8 candidate to exactly 8, even when one of its fields requires more. On wasm, Int128/UInt128, Decimal128 and Vector128<T> require 16, so the runtime aligned an auto-layout struct containing them to 8 while crossgen2 (and the runtime's own sequential layout) align it to 16.

The runtime now uses max(8, largest field alignment) (still 8 when the struct contains GC pointers), the same rule crossgen2 applies in MetadataFieldLayoutAlgorithm.ComputeAutoFieldLayout. Field offsets inside the struct are unchanged; only the struct's own alignment, and therefore its tail padding and its placement as a field of another type, changes. On ARM32 no field requires more than 8, so behavior there is unchanged.

Why this matters: perf pipeline

This fix is required to get the wasm CoreCLR composite ReadyToRun perf pipeline green. BenchmarkDotNet's generated Runnable_N classes store benchmark arguments in an auto-layout FieldsContainer struct field. For Perf_Int128 (TryFormat, ToString, CopySign), that struct holds Int128 fields. With composite R2R, crossgen2 bakes its field offsets into the R2R code, so the code wrote the container at offset 16 while the runtime sized the object for offset 8, writing past the end of the object. The run then aborted with:

Fatal error. Invalid Program: attempted to call a UnmanagedCallersOnly method from managed code.

The link between the out-of-bounds write and that particular fatal error is inferred (no stack connects them), but it is removed by this fix: with it, the locally reproduced Perf_Int128 composite R2R run completes for all of the previously failing benchmarks.

Tests

  • readytorun/fieldlayout is re-enabled on browser CoreCLR. Its ActiveIssue pointed at [wasm] Use ordinary struct alignment for Int128/UInt128 on wasm #131421 and attributed the failure to Int128 alignment, but the failure was this bug. Without the fix it fails --verify-type-and-field-layout on Auto.Wrapper_int8x16x2 (a Vector128 case), so the problem was not Int128-specific.
  • A case is added there with the issue's shape: an auto-layout Int128 pair as a field of a derived class, read back through reflection so the check does not depend on the verify flag.
  • Wasm32 rows are added to TestAlignmentBehavior_AutoAlignmentRulesWithOSDependence, pinning crossgen2's 16/32 for the Int128/UInt128/Decimal128 auto wrappers.

Validation:

  • dotnet test ILCompiler.TypeSystem.Tests --filter ArchitectureSpecificFieldLayoutTests: 44 passed, including the 3 new Wasm32 rows.
  • fieldlayouttests.cs sources published as a composite wasm R2R app with --verify-type-and-field-layout, run under node:
    • Without the fix: Verify_FieldOffset 'Derived.Pair' Field offset 16!=8(actual), and Verify_TypeLayout 'Auto.Wrapper_int8x16x2' failed.
    • With the fix: passes.
  • dotnet/performance Perf_Int128 with --wasm-ready-to-run-composite: reproduced the fatal error; with the fix, the TryFormat, ToString and CopySign benchmarks (benchmarkId 2, 5, 6, 9) run to completion.
  • Not run: the test through the src/tests harness (per-test, non-composite crossgen with the verify flag); the browser R2R CI lane covers it. No ARM32 build.

The cDAC needs no change: it reads stored field offsets and sizes, and GetClassAlignmentRequirement mirrors getClassAlignmentRequirementStatic, which does not read the auto-layout alignment changed here.

Follow-up (not in this PR): Vector256/Vector512 alignment on wasm (#135483)

With this change, auto-layout structs containing Vector256<T>/Vector512<T> on wasm get alignment 32/64. That matches crossgen2 (both fall through to the generic branch in CheckForSystemTypes/VectorFieldLayoutAlgorithm), so there is no runtime/crossgen2 disagreement, but 32/64 is questionable for wasm:

  • The Wasm Basic C ABI defines nothing above v128. Checked with clang 22 targeting wasm32: __BIGGEST_ALIGNMENT__ is 16, the stack alignment is S128, and struct { v128 lo, hi; } has alignment 16. Only clang's generic vector_size(32/64) extension types get 32/64, and those are not part of the ABI.
  • In [wasm] Use ordinary struct alignment for Int128/UInt128 on wasm #131421 the expected model was a struct of 2×/4× V128, i.e. alignment 16.
  • The GC heap on wasm only guarantees 8-byte object alignment, so alignment above 8 only affects offsets relative to the start of the object; 32/64 adds padding without real alignment.

Changing this is a layout and R2R compatibility decision. It should be made before wasm CoreCLR ships, as a separate change to both the VM and crossgen2. Tracked by #135483.

Resolves #135368

Note

This pull request description was generated with GitHub Copilot.

lewing and others added 2 commits October 9, 2026 10:27
On FEATURE_64BIT_ALIGNMENT targets, an auto-layout value type that is an
Align8 candidate always got an alignment requirement of 8, even when a field
required more. On wasm, Int128/UInt128, Vector128 and Decimal128 require 16,
so an auto-layout struct containing them was 8-aligned by the runtime but
16-aligned by crossgen2 and by the runtime's own sequential layout.

With composite ReadyToRun, crossgen2 bakes the field offsets of such a struct
inside a class, so R2R code wrote past the end of the object and corrupted
the GC heap. In BenchmarkDotNet runs this surfaced as "Invalid Program:
attempted to call a UnmanagedCallersOnly method from managed code".

Use max(8, largest field alignment) for Align8 candidates without GC
pointers. ARM32 is unaffected since no type there requires more than 8.

Re-enable readytorun/fieldlayout on browser CoreCLR and add a case for an
auto-layout Int128 struct in a derived class.

Fixes #135368

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 05ba961f-8f2d-42b8-ac78-8f138880e420
- Shorten the Align8 comment in HandleAutoLayout to the invariant.
- Drop an assertion in the fieldlayout test that did not exercise the bug.
- Add Wasm32 rows to TestAlignmentBehavior_AutoAlignmentRulesWithOSDependence
  so crossgen2's 16-byte alignment for auto-layout structs containing
  Int128/UInt128/Decimal128 is pinned as the layout the runtime must match.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 05ba961f-8f2d-42b8-ac78-8f138880e420
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/crossgen-contrib
See info in area-owners.md if you want to be subscribed.

@lewing

lewing commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

cc @dotnet/wasm-contrib

@lewing lewing added the arch-wasm WebAssembly architecture label Oct 9, 2026
@lewing
lewing requested a review from DrewScoggins October 9, 2026 09:55
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

@lewing
lewing requested review from LoopedBard3 and a balanced review from Copilot October 9, 2026 09:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The logic appears consistent with crossgen2, but changing low-level wasm type layout, including wide-vector alignment, warrants final maintainer review.

0 open findings

What changed in this PR

Aligns CoreCLR’s wasm auto-layout rules with crossgen2 to prevent ReadyToRun field-offset and size mismatches.

Changes:

  • Preserves field alignment above 8 bytes for Align8 auto-layout structs.
  • Re-enables browser CoreCLR field-layout testing.
  • Adds wasm alignment and Int128 regression coverage.
File Description
methodtablebuilder.cpp Corrects auto-layout alignment calculation.
fieldlayouttests.cs Adds an Int128 derived-class regression test.
ArchitectureSpecificFieldLayoutTests.cs Adds Wasm32 alignment cases.

🧠 Review effort: Balanced

@radekdoulik radekdoulik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, it would be nice to also extend src/tests/Interop/StructPacking/StructPacking.cs

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-ReadyToRun

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[browser][CoreCLR][Composite R2R] Int128 benchmark startup aborts with an invalid reverse P/Invoke transition

5 participants