Repository navigation
[wasm] Preserve R2R variable debug information - #133086
Conversation
|
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. |
AndyAyersMS
left a comment
There was a problem hiding this comment.
Does this handle describing locals that are on the linear stack? Last I looked those SP/FP based encodings were wonky for Wasm.
Note the exact Wasm locals that hold SP/FP can vary and may even vary between main method and its funclets, which I doubt we can encode.
An easy test for this is to look at any GC-typed local, these are always on the linear stack.
|
@lewing, what value does this debugging data provide? Given my understanding that debugging is likely to only be supported on mostly interpreted scenarios, why should we make this correct for R2R scenarios. (I expect that nativeaot will make good use of this data, so I think overall its goodness, but I don't see why its particularly useful for R2RDump.) |
|
It came out of the DWARF discussion and I'm working on something in the nesm debugger and I'm trying to plumb out more info, it is isn't meant to be user facing currently. |
Preserve variable debug information produced by RyuJIT for ReadyToRun WebAssembly code, including scope ranges across relooper block ordering and packed wasm local register locations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The end-to-end variable-debug-info validation exposed the hidden wasm portable-entry-pointer argument as a source local. The argument is appended after user arguments, but unlike the wasm stack-pointer argument it was not recorded or excluded by compMap2ILvarNum. AddDoubles therefore reported the hidden i32 argument as source local 0, alongside the real f64 parameters. Record the argument's local number when it is created, map it to UNKNOWN_ILNUM, and account for it when mapping later internal locals back to IL variable numbers. Replace the count-only wasm R2R checks with complete exact records for the AddDoubles parameters and SumWithFinally local: variable identity, native range, location kind, packed wasm local, and frame-pointer-relative offset. Mutate the local-index bits of one packed register and prove the exact oracle rejects the corrupted record. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
8553c54 to
3979515
Compare
|
I opened #133890 (which includes these commits) with cDAC consumer side changes |
Define the packed WASM debug-register bit layout in ICorDebugInfo and have the JIT derive its register masks from that shared encoding contract. Assert that the JIT register representation and WasmValueType count remain compatible with the debug-info format. Document that the static ReadyToRun reader's compiled-in shift must move with a versioned R2R debug-info format change, since it has no live target descriptor from which to discover a different layout. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Publish the shared WASM register type shift and value-type count through the target data descriptor so version-skewed readers can reject incompatible variable debug information. Document the producer-owned encoding and extend the static ReadyToRun reader coverage for reserved and unsupported value-type codes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a no-opt object local that remains live across a GC call in a finally funclet. Assert its IL class type, complete ReadyToRun variable tuples, frame-relative GC slot, and safepoint coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add exact optimized tracked-variable coverage, frame-base ABI variations including localloc and funclets, and same-type GC slot identity with a legitimate null reference. Pin the current stack VarLoc base encoding and document that absolute frame reconstruction is independent of unstable wasm local indices. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 51b56d7e-45e5-464d-8c2b-78c744680ff8
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 51b56d7e-45e5-464d-8c2b-78c744680ff8
There was a problem hiding this comment.
🔵 Needs a closer look
Human verification is needed for the cross-component ReadyToRun DebugInfo contract change, including persisted Wasm encodings and end-to-end cDAC/R2RDump compatibility, because the required browser/WASI, NativeAOT, and cDAC validation was not run.
No open issues 🎉
Review effort: Lite
|
/azp run runtime |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
cDAC support for resolving wasm R2R variable locations from the debug info in #133086 (merged). The change is confined to `ClrDataFrame`'s variable resolution and `DebugInfo.md`; the `DebugInfo` contract reports wasm locations with the existing kinds and register numbers. - Resolve `VLT_STK` / `VLT_STK2` against the frame pointer in the stack-walk context. For ReadyToRun code (base register `2`) that is the logical frame pointer, which for a funclet is the establishing method's frame (#135140). For interpreted code (base `REGNUM_FP`, which is `0` on wasm) it is `InterpMethodContextFrame.pStack`. - Report register-based kinds as having no location. A wasm "register" is a local index biased by `WASM_LOCAL_REGNUM_BASE`, and wasm locals are engine-private, not readable through the data target. A consumer attached to the engine subtracts the bias and reads the local itself. - Fail an unreadable or null `VLT_STK_BYREF` indirection with `VirtualReadException` (`CORDBG_E_READVIRTUAL_FAILURE`) instead of reading linear address 0, which is valid memory on wasm. - Debug-assert that a wasm stack record uses one of those two base registers. - List `WASM_LOCAL_REGNUM_BASE` (`3`) and the wasm stack base register (`2`) in DebugInfo.md's constants table. Local index spaces are per wasm function, and funclets are separate functions, so a consumer must know which function a frame is in before using a local index. The existing `IExecutionManager` APIs (`GetCodeBlockHandle`, `IsFunclet`, `GetUnwindInfo`) identify it. ## Null and unavailable values 1. Engine-private wasm local: zero locations; `GetBytes` / `GetAddress` fail with `E_NOINTERFACE`. 2. Unreadable or null `VLT_STK_BYREF` indirection: resolution fails with `CORDBG_E_READVIRTUAL_FAILURE`. 3. Direct stack-homed null reference: one readable location that reads as zero. Native `DereferenceByRefVar` turns a failed byref read into address 0, which is safe natively because address 0 is unmapped. On wasm, linear address 0 is readable, so the same representation would read unrelated bytes as the variable's value. Non-wasm behavior is unchanged. ## Tests On head `efdab45a717` (on `main` `f4d81ef0a30`): - `./build.sh -s tools.cdactests -test`: **passed** (UnitTests 3929/3929, DataGeneratorTests 46/46, UsageTests 4/4). - `pwsh src/native/managed/cdac/tools/CdacUsageGraph/generate-docs.ps1 -Check`: **passed**. - Mutations, each confirmed applied by diff, failing the listed tests, then restored: | Mutation | Fails | | --- | --- | | Resolve stack slots against SP instead of FP | 5 tests, including the funclet establishing-frame test | | Skip the byref dereference | 3 `VLT_STK_BYREF` tests | | Drop the null-byref check | the null-byref case | | Let `Register` resolve like a stack slot | 4 register-kind cases | | Drop the interpreter base `0` | the interpreted-frame test, on the debug assertion | Live browser validation on the earlier head `b126a409cee` (browser `clr+libs` Debug build, sample published with `-p:PublishReadyToRunStripDebugInfo=false`, headless Chrome over CDP): **passed**. Paused in R2R `GcSlotIdentity` after a `GC.Collect()` reached through a call, the cDAC resolved each IL local to a single `VLT_STK` home at `FP + offset` inside the frame, reported by the GC info as a frame-relative root, and read the two marker objects back (17 and 29) and the null local as zero. A live mutation adding 4 to the stack address failed as expected. The stack-slot resolution code is unchanged since then; the live run has not been repeated on this head. ## Limitations - No native DAC is built for wasm, so these results are covered by managed unit tests, not live `_legacyImpl` comparison. - On wasm, `GetMethodVarInfo`'s `codeOffset` is in the synthetic virtual-IP space, while variable ranges are JIT emitter offsets, so `ClrDataFrame`'s range selection is only reliable for method-wide ranges. Unoptimized wasm scopes are method-wide, but a variable whose location changes mid-method can select the wrong record. A fix needs virtual-IP-space ranges from the producer or a virtual-IP → native-offset map. - Default Browser/WASI publishing strips R2R debug info (#134690). Use `-p:PublishReadyToRunStripDebugInfo=false` to keep it. > [!NOTE] > This pull request description was generated with GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Re-enables RyuJIT variable debug info for ReadyToRun WebAssembly code, which was disabled before wasm locals could be encoded, and defines the encoding that readers consume.
localIndex + WASM_LOCAL_REGNUM_BASE(3); values 0–2 keep their meanings (REGNUM_PC,REGNUM_COUNT,REGNUM_AMBIENT_SP).REG_FPBASE=REG_SPBASE=REGNUM_AMBIENT_SPon wasm), so they are slots relative to the logical frame, which readers reconstruct from the stack walk.getVarsalways reports method-wide scopes, and wasm block order doesn't follow IL order.Both constants are fixed for DebugInfo contract
c1and documented inDebugInfo.md. Changing either needs a new contract version and, because R2R images persist the encoding, a ReadyToRun format version change. Browser and WASI publishes strip debug info by default (#134690), so these records reach an image only withPublishReadyToRunStripDebugInfo=false; generated code is unchanged. The cDAC consumer is #133890.Test coverage and follow-ups
WasmWebcilModulechecks invariants rather than exact codegen: register records name declared locals with the expected wasm types, stack records use base 2 without overlapping ranges, recorded frame slots are actually accessed (including from a finally funclet), and GC locals' ranges cover the safepoint. Removing the+3bias makes it fail.Follow-ups: NativeAOT DWARF (#135740) can describe locals with
DW_OP_WASM_location(local index = register − 3); frame-slot variables additionally need the engine local holding the frame pointer, which this format doesn't record. #134808 plans R2R DWARF on top of these records.Note
This pull request description was updated with the assistance of GitHub Copilot.