[wasm] Assign frame offsets to dependently promoted parameter fields#131401
Merged
Conversation
lvaAssignFrameOffsetsToPromotedStructs computes mustProcessParams from a target list that did not include wasm, so for a promoted field of a struct *parameter* the SetStackOffset(parent + lvFldOffset) fixup was skipped. The comment there assumes such fields are given offsets by lvaAssignVirtualFrameOffsetToArg, which is not true on wasm: parameters arrive in wasm locals and are homed by the prolog. The other propagation site requires lvIsRegArg and is gated to ARMARCH/LOONGARCH64/RISCV64, so it does not cover wasm either. The field therefore kept offset zero and, once the virtual-to-actual frame delta was applied, aliased frame+frameSize -- reading off the end of the frame into the caller's. It only shows up when codegen reads the field local rather than the parent, which needs the address exposure to come from a path that does not dominate the use. DateTimeFormat.Format hits exactly that: PrepareFormatU(ref dateTime) on the 'U' branch address-exposes the parameter, so its promoted field _dateData is dependently promoted and memory-homed, while the main path still sources the argument from the field. Codegen emitted "i64.load 0 256 ;; V153" against a 256-byte frame whose parameter home was at 248, so every custom/standard DateTime format read uninitialized memory and produced a constant wrong date. That accounted for 187 Utf8JsonWriter DateTime/DateTimeOffset failures under wasm R2R. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175
|
Azure Pipelines: Successfully started running 5 pipeline(s). 11 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a WebAssembly-specific JIT frame layout issue by ensuring dependently promoted struct parameter fields get their stack offsets updated during lvaAssignFrameOffsetsToPromotedStructs, matching how other targets already handle cases where argument homes aren’t established via caller-allocated space.
Changes:
- Extends the
mustProcessParamsgating inlvaAssignFrameOffsetsToPromotedStructsto includeTARGET_WASM, so dependent promoted parameter fields have their offsets derived from the parent’s home. - Adds a WASM-specific comment explaining why parameter fields must be processed in this routine on WASM.
Contributor
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
AndyAyersMS
approved these changes
Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes wrong wasm codegen that is usually masked by frame-layout coincidence.
The bug
lvaAssignFrameOffsetsToPromotedStructscomputesmustProcessParamsfrom a target list that does not include wasm:So for a dependently promoted field of a struct parameter, the
SetStackOffset(parent + lvFldOffset)fixup below is skipped on wasm. The comment there assumes such fields are given offsets bylvaAssignVirtualFrameOffsetToArg, which is not true for wasm: parameters arrive in wasm locals and are homed by the prolog. The only other propagation site requireslvIsRegArgand is gated toTARGET_ARMARCH/TARGET_LOONGARCH64/TARGET_RISCV64, so it does not cover wasm either.The field therefore keeps offset zero, and once the virtual-to-actual frame delta is applied it aliases
frame + frameSize— one slot past the end of the frame, in the caller's.Why it doesn't break everything
Two things have to line up for the bad offset to be observable:
frame + frameSizeis the caller's frame base, which very often already holds the caller's own copy of the same struct — so the miscompiled load reads the correct value by luck. It only degrades when the caller is structurally unrelated.DateTimeFormat.Formathits both:PrepareFormatU(ref dateTime)on the'U'branch address-exposes the parameter (so its promoted_dateDatafield is dependently promoted and memory-homed) while the main path still sources the argument from the field, and it is reached through a thunk, so the stale slot is genuinely garbage.Emitted code before the fix, in a 256-byte frame whose parameter home is at 248:
Every custom and standard
DateTimeformat therefore read uninitialized memory and produced a constant wrong date.Fix
Add
TARGET_WASMto the gate. This is wasm-only by construction — the condition simply never included wasm — and no other target's behavior changes.Validation
System.Text.Json.Testsunder browser wasm R2R:Also verified on WASI R2R (same repro, same fix), and
Microsoft.Bcl.Memory.Tests550/550 andSystem.Runtime.CompilerServices.Unsafe.Tests128/128 pass with R2R both off and on. A checked-configuration crossgen ofSystem.Private.CoreLibis assert-free.The 39 remaining failures are unrelated to this change: 25 involve
Int128/UInt128(a separate comparison issue —RoundtripValues<Int128>(-1)reportsExpected: -1 Actual: -1), and two also fail under the interpreter.Note on testing
A minimal repro reliably reproduces the wrong codegen but not a wrong observable value, for reason (2) above — I was unable to make the aliased slot hold garbage in a synthetic caller, including with a deliberately poisoned
stackalloc. So the meaningful end-to-end coverage is theUtf8JsonWriterDateTime tests, which fail deterministically before this change. For codegen-level coverage the signal to assert on is the emitted load offset (or SuperPMI asm diffs), not program output.Note
This pull request was created with the assistance of GitHub Copilot.