Repository navigation
Conversation
|
Azure Pipelines: Successfully started running 7 pipeline(s). 9 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
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
Contributor
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
lewing
force-pushed
the
lewing-r2r-cdac-for-wasm
branch
2 times, most recently
from
September 14, 2026 18:53
d5f26e6 to
69ad34f
Compare
This was referenced Sep 14, 2026
lewing
force-pushed
the
lewing-r2r-cdac-for-wasm
branch
from
September 16, 2026 01:48
69ad34f to
7e72c0e
Compare
lewing
force-pushed
the
lewing-r2r-cdac-for-wasm
branch
from
September 25, 2026 19:52
7e72c0e to
1166442
Compare
lewing
added a commit
that referenced
this pull request
Sep 26, 2026
…ish (#134689) ## Summary Pass `--strip-debug-info` to crossgen2 for CoreCLR browser-wasm ReadyToRun publish. This drops the R2R `DebugInfo` section (native-to-IL offset maps and variable locations) from shipped images. Opt out with `PublishReadyToRunStripDebugInfo=false` to keep the data for debugging R2R code (e.g. the cDAC work in #133086 / #133890). Also makes crossgen2 argument changes invalidate per-app R2R images. `_CreateR2RImages` only tracks file inputs, so toggling `PublishReadyToRunStripDebugInfo` (or any `PublishReadyToRunCrossgen2ExtraArgs` change) previously left stale images in `obj/R2R`. The arguments are now written to `obj/wasm-r2r-args.stamp` (only when different) and added to `_ReadyToRunCompilerInputs`, mirroring the existing P/Invoke manifest input. > [!IMPORTANT] > Stacked on #134618, which rewrites the same targets file. Retarget to `main` after it merges. ## Size impact Per-assembly R2R images compiled directly with crossgen2 using the SDK's browser-wasm arguments (`--obj-format:wasm --opt-cross-module:* --codegenopt:JitWasm*NyiToR2RUnsupported=1`), with and without `--strip-debug-info`. Total for System.Private.CoreLib, System.Text.Json, System.Linq, and System.Collections: | Compiler | Raw saved | gzip -9 saved | brotli -q 11 saved | | --- | --- | --- | --- | | Current (#134618 base) | 859,712 B (2.4%) | 624,690 B (7.1%) | 565,366 B (9.4%) | | With #133086 variable info | 2,105,520 B (5.6%) | 1,332,733 B (13.9%) | 1,121,574 B (17.0%) | <details> <summary>Per-assembly brotli sizes (bytes, keep → strip)</summary> | Assembly | Current | With #133086 | | --- | --- | --- | | System.Private.CoreLib | 4,822,231 → 4,347,541 | 5,331,799 → 4,403,849 | | System.Text.Json | 823,314 → 761,533 | 887,982 → 754,444 | | System.Linq | 219,177 → 200,471 | 238,853 → 200,441 | | System.Collections | 119,635 → 109,446 | 130,742 → 109,068 | The #133086 compiler is based on an older commit, so compare keep vs. strip within a column rather than across columns. </details> ## Validation - MSBuild evaluation: `--strip-debug-info` is present by default, absent with `PublishReadyToRunStripDebugInfo=false`, and absent when `PublishReadyToRun` is off. - Incremental harness: `_CreateR2RImages` runs on first build, skips when unchanged, reruns on opt-out, skips when repeated, and reruns when switching back. - Not yet run: an end-to-end browser-wasm publish loading stripped images in a browser (Wasm.Build.Tests in CI will cover this). Runtime-pack framework R2R images (used only by the dev-loop build) are unchanged; publish recompiles the whole closure through these targets. `--strip-inlining-info` is intentionally not included: it removes `CrossModuleInlineInfo`, and cross-module inlining is load-bearing on wasm, so it needs separate validation. > [!NOTE] > This pull request was created with assistance from GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing
added a commit
that referenced
this pull request
Sep 26, 2026
## Summary Fix cDAC live resolution of WebAssembly ReadyToRun virtual IPs by matching the runtime's existing lookup model: - expose `ExecutionManager::s_pVirtualIPRangeList` and `VirtualIPRangeSection` through the data descriptor; - resolve encoded virtual IPs through that intrusive list using cycle detection and a 65,536-node per-lookup reader resource budget, with no map fallback when an encoded VIP is absent; - mask the WebAssembly funclet flag from `RUNTIME_FUNCTION.BeginAddress` for ordering and address arithmetic while preserving funclet identity; - keep the virtual code base (`MinVirtualIP`) separate from the loaded-image base used for unwind, debug, GC, exception, and thunk RVA reads; - handle the actual WASM descriptor shape, where hot/cold metadata and delay-load thunk metadata are absent; - classify WASM filter funclets by mapping the executable filter entry to its containing runtime function. ## Root cause The model added in #130988 was already false when that PR merged. On `TARGET_WASM`, ReadyToRun modules are not added to `RangeSectionMap`; `ReadyToRunInfo::RegisterVirtualIPRange` registers them in `ExecutionManager::s_pVirtualIPRangeList`, and native `FindCodeRange` checks that list first. The prior unit test synthesized a `RangeSectionMap` entry with an address that did not satisfy native `IsVirtualIP`, so it validated a mock-only model rather than the live runtime layout. This is a test-model gap, not a reviewer fault. The prior review explicitly noted that the WebAssembly specifics had not been run locally and should be added to cDAC CI: #130988 (review). ## Blast radius and scope This affects ReadyToRun code on all CoreCLR WebAssembly hosts, including browser and WASI. Interpreter code is unaffected. The list lookup, descriptor feature gating, funclet masking, and image-base separation are inseparable: exposing the list alone would still throw while reading absent WASM fields, or could return the wrong method or read RVA data from the synthetic virtual address space. This PR is independent of #133086 and intentionally excludes variable producer/decoder work. #133890 depends on this PR for correct shared code lookup and function identity. On WASM, `FilterOffset` is the executable filter entry and can follow a synthetic funclet prolog. cDAC now mirrors the corrected native classification in #133932 by resolving that entry to its containing runtime function before comparing funclet starts. The PRs remain independent; #133917 does not depend on changing the producer offset. ## Validation - `./build.sh clr+libs+host` - `PATH="/opt/homebrew/bin:$PATH" ./build.sh -os browser -c Debug -subset clr+libs` - cDAC UnitTests: **3162 passed** - cDAC DataGeneratorTests: **46 passed** - cDAC UsageTests: **4 passed** - generated contract documentation check: **up to date** - focused ExecutionManager / RuntimeFunction / WasmR2R tests: **221 passed** The durable tests cover: - captured/live-shaped VIP `0x80010109`, exact `MethodDesc`, module, and runtime-function index; - the actual WASM descriptor shape: 8-byte `RUNTIME_FUNCTION` records with no `EndAddress`, and absent hot/cold and delay-load thunk fields; - start/end boundaries and adjacent ranges; - encoded VIP absent from the list with no `RangeSectionMap` fallback; - self-cycle, two-node cycle, inverted range, null module, and overlapping ambiguity; - unrelated partially registered nodes not blocking initialized ranges, while an uninitialized candidate fails closed; - valid 1,024/1,025-node lists, exact 65,536-node budget success, and budget+1 fail-closed behavior even when the head matches, with a read counter proving the extra node is never dereferenced; - root/funclet resolution with a flagged funclet entry that breaks raw ordering; - exact loaded-image debug, unwind, GC, and exception-clause reads while entrypoint lookup uses `MinVirtualIP`; - filter-funclet classification where `FilterOffset` follows the flagged funclet start but resolves to the same containing runtime function; - missing list capability and unchanged ordinary architecture behavior. Mutation proofs were applied, confirmed in source, run red, restored, and rerun green: 1. Removing the VIP-list branch fails the captured `0x80010109` test at the exact code-block assertion. 2. Using raw `BeginAddress` fails the funclet identity test. 3. Using `startVIP` as the loaded-image base fails the GC/unwind test with a read at `0x80010081` instead of the loaded image. 4. Raising the reader budget from 65,536 to 65,537 makes the budget+1 test fail at its read-boundary assertion (highest node index 65,536 instead of 65,535); restoring the budget returns the suite to green. 5. Replacing WASM filter-entry containing-function resolution with raw `FilterOffset == funcletStartOffset` comparison makes the filter regression fail with expected `true` and actual `false`. The finite list cutoff is an intentional diagnostic-reader resource policy, not a native registration limit or a claim that an over-budget list is corrupt. > [!NOTE] > This pull request description was generated with GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Sep 27, 2026
lewing
added a commit
that referenced
this pull request
Sep 29, 2026
…rtual IPs (#134756) > [!IMPORTANT] > Stacked on #134754 (targets its branch). Only the commits above #134754's head belong to this PR; retarget to `main` once #134754 merges. ## Problem On `FEATURE_PORTABLE_ENTRYPOINTS` targets (WebAssembly), a method's entry point slot holds a `PortableEntryPoint` address. Native maps it before handing it to diagnostics (`GetInterpreterCodeFromEntryPointIfPresent` / `GetDiagnosticCodeStartFromEntryPoint` in `precode.cpp`), but the cDAC returned the raw address. SOS values such as `DacpMethodDescData.NativeCodeAddr` and rejit `NativeCodeAddr`, plus the DBI/`ClrDataMethodInstance` paths, therefore reported an address that doesn't resolve through `ExecutionManager` for both interpreted and R2R methods. ## Change Adds `IExecutionManager.GetDiagnosticCodeStartFromEntryPoint`, which mirrors the native `GetDiagnosticCodeStartFromEntryPoint`: - **Without portable entrypoints:** delegates to `PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent`. Behavior is unchanged. - **With portable entrypoints:** 1. Returns the address unchanged if it lies in a code range. This is the native `FindCodeRange` check and includes Wasm R2R virtual-IP ranges from #133917. 2. Maps interpreted methods to `MethodDesc::m_interpreterCode`. 3. Maps native R2R methods from the function-table index in `PortableEntryPoint._pActualCode` to the synthetic virtual IP, stepping back past funclet entries. This matches `ExecutionManager::GetWasmVirtualIPFromFunctionTableIndex`. As in native code, it applies only to the method's own (temporary) entry point, and only when the entry point doesn't prefer the interpreter. The mapping lives in ExecutionManager rather than PrecodeStubs because ExecutionManager owns the virtual-IP ranges and R2R lookup. ExecutionManager already depends on PrecodeStubs, so this adds no contract cycle. Function-table-index resolution moves into `ExecutionManagerHelpers.WasmFunctionTableIndexLookup`, which now bounds the list walk and detects cycles, like the virtual-IP list walk. The stack walk's `WasmR2RInfo` becomes a thin wrapper over it. The Legacy SOS/DBI callers (`SOSDacImpl`, `ClrDataMethodInstance`, `DacDbiImpl`) now call the new API. `DacDbiImpl.EnumerateAsyncLocals` also maps its code address before querying async debug info. The native DAC's `EnumerateAsyncLocals` gets the matching `GetInterpreterCodeFromEntryPointIfPresent` mapping (as `GetMethodVarInfo` already does), so the debug-build cDAC/DAC cross-check stays consistent. ### Data descriptors - `PortableEntryPoint.ActualCode` and `PortableEntryPoint.Flags` (only under `FEATURE_PORTABLE_ENTRYPOINTS`). - `MethodDesc.InterpreterCode` (only under `FEATURE_INTERPRETER`). - The contract relies on `kPrefersInterpreterEntryPoint` and `INTERPRETER_CODE_POISON`; the native side now has `[cDAC]` comments marking that dependency. ### Interaction with #133890 #133890 adds `TryGetFunctionIdentity` and `TryIsFunclet` to `WasmR2RInfo`. Whichever PR lands second should add those two methods to `WasmFunctionTableIndexLookup` and have `WasmR2RInfo` forward to them. The `FunctionTableIndexRange*` descriptor meanings here already use #133890's exact wording, so that JSON should merge cleanly. ## Validation - cDAC: `./build.sh -s tools.cdac+tools.cdactests -c Debug -test` passes: 3196 unit tests (17 new), usage tests (contract cycles and generated docs up to date), and generator tests. - New ExecutionManager tests cover: - interpreted, R2R, funclet, poison, prefers-interpreter, not-own-entry-point and unknown-index cases; - an end-to-end check that the resolved virtual IP maps back to the MethodDesc through `GetCodeBlockHandle`; - readable `PortableEntryPoint`-shaped bytes inside a registered code range staying unchanged. This test fails if the range check is removed. - a cyclic function-table range list; - the non-portable delegation path. - CoreCLR `clr.runtime` builds for osx-arm64 Debug (with `FEATURE_INTERPRETER`) and browser-wasm Debug. The generated wasm descriptor has `PortableEntryPoint {ActualCode@0, MethodDesc@4, Flags@12}`. - A new `DacDbiImplTests` test covers `EnumerateAsyncLocals` mapping for both the code-address and MethodDesc paths. It fails without the fix. - cDAC dump tests (osx-arm64, local runtime): 260 passed, 0 failed. The 746 skips are net10.0 configurations plus by-design skips (Windows-only COM debuggees, and dump types a debuggee doesn't produce). - CoreCLR `clr.runtime` Release (osx-arm64) builds with the `dacdbiimpl.cpp` change. - Not run: any check against a live wasm target. Fixes #134753 > [!NOTE] > This PR description was generated with GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing
added a commit
that referenced
this pull request
Sep 29, 2026
…rtual IPs (#134827) This replaces #134756, which was merged into #134754's branch by mistake and reverted there. The change is otherwise identical (cherry-picked onto `main`). ## Problem On `FEATURE_PORTABLE_ENTRYPOINTS` targets (WebAssembly), a method's entry point slot holds a `PortableEntryPoint` address. Native maps it before handing it to diagnostics (`GetInterpreterCodeFromEntryPointIfPresent` / `GetDiagnosticCodeStartFromEntryPoint` in `precode.cpp`), but the cDAC returned the raw address. SOS values such as `DacpMethodDescData.NativeCodeAddr` and rejit `NativeCodeAddr`, plus the DBI/`ClrDataMethodInstance` paths, therefore reported an address that doesn't resolve through `ExecutionManager` for both interpreted and R2R methods. ## Change Adds `IExecutionManager.GetDiagnosticCodeStartFromEntryPoint`, which mirrors the native `GetDiagnosticCodeStartFromEntryPoint`: - **Without portable entrypoints:** delegates to `PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent`. Behavior is unchanged. - **With portable entrypoints:** 1. Returns the address unchanged if it lies in a code range. This is the native `FindCodeRange` check and includes Wasm R2R virtual-IP ranges from #133917. 2. Maps interpreted methods to `MethodDesc::m_interpreterCode`. 3. Maps native R2R methods from the function-table index in `PortableEntryPoint._pActualCode` to the synthetic virtual IP, stepping back past funclet entries. This matches `ExecutionManager::GetWasmVirtualIPFromFunctionTableIndex`. As in native code, it applies only to the method's own (temporary) entry point, and only when the entry point doesn't prefer the interpreter. The mapping lives in ExecutionManager rather than PrecodeStubs because ExecutionManager owns the virtual-IP ranges and R2R lookup. ExecutionManager already depends on PrecodeStubs, so this adds no contract cycle. Function-table-index resolution moves into `ExecutionManagerHelpers.WasmFunctionTableIndexLookup`, which now bounds the list walk and detects cycles, like the virtual-IP list walk. The stack walk's `WasmR2RInfo` becomes a thin wrapper over it. The Legacy SOS/DBI callers (`SOSDacImpl`, `ClrDataMethodInstance`, `DacDbiImpl`) now call the new API. `DacDbiImpl.EnumerateAsyncLocals` also maps its code address before querying async debug info. The native DAC's `EnumerateAsyncLocals` gets the matching `GetInterpreterCodeFromEntryPointIfPresent` mapping (as `GetMethodVarInfo` already does), so the debug-build cDAC/DAC cross-check stays consistent. ### Data descriptors - `PortableEntryPoint.ActualCode` and `PortableEntryPoint.Flags` (only under `FEATURE_PORTABLE_ENTRYPOINTS`). - `MethodDesc.InterpreterCode` (only under `FEATURE_INTERPRETER`). - The contract relies on `kPrefersInterpreterEntryPoint` and `INTERPRETER_CODE_POISON`; the native side now has `[cDAC]` comments marking that dependency. ### Interaction with #133890 #133890 adds `TryGetFunctionIdentity` and `TryIsFunclet` to `WasmR2RInfo`. Whichever PR lands second should add those two methods to `WasmFunctionTableIndexLookup` and have `WasmR2RInfo` forward to them. The `FunctionTableIndexRange*` descriptor meanings here already use #133890's exact wording, so that JSON should merge cleanly. ## Validation - cDAC: `./build.sh -s tools.cdac+tools.cdactests -c Debug -test` passes: 3196 unit tests (17 new), usage tests (contract cycles and generated docs up to date), and generator tests. - New ExecutionManager tests cover: - interpreted, R2R, funclet, poison, prefers-interpreter, not-own-entry-point and unknown-index cases; - an end-to-end check that the resolved virtual IP maps back to the MethodDesc through `GetCodeBlockHandle`; - readable `PortableEntryPoint`-shaped bytes inside a registered code range staying unchanged. This test fails if the range check is removed. - a cyclic function-table range list; - the non-portable delegation path. - CoreCLR `clr.runtime` builds for osx-arm64 Debug (with `FEATURE_INTERPRETER`) and browser-wasm Debug. The generated wasm descriptor has `PortableEntryPoint {ActualCode@0, MethodDesc@4, Flags@12}`. - A new `DacDbiImplTests` test covers `EnumerateAsyncLocals` mapping for both the code-address and MethodDesc paths. It fails without the fix. - cDAC dump tests (osx-arm64, local runtime): 260 passed, 0 failed. The 746 skips are net10.0 configurations plus by-design skips (Windows-only COM debuggees, and dump types a debuggee doesn't produce). - CoreCLR `clr.runtime` Release (osx-arm64) builds with the `dacdbiimpl.cpp` change. - Re-validated after cherry-picking onto `main`: `./build.sh clr -c Debug` (osx-arm64) builds, and `./build.sh -s tools.cdac+tools.cdactests -c Debug -test` passes (3196 unit tests, 46 generator tests, 4 usage tests; 0 failed). - Not run: any check against a live wasm target. Resolves #134753 > [!NOTE] > This PR description was generated with GitHub Copilot. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing
added a commit
that referenced
this pull request
Sep 30, 2026
## Summary Fixes a GC hole during exception dispatch in Wasm R2R code. It shows up in CI as a rare `SanityCheck()` assert in nested EH tests on the browser-wasm R2R leg, for example `Test_throwinfinallynestedintry_30`. ## Root cause Wasm R2R code has no interruptible ranges, and it reports every GC ref on the frame as an untracked (pinned) slot. A method and its funclets share those frame slots. When an exception is thrown from a funclet, that funclet's frame is `ExecutionAborted`. `GcInfoDecoder::EnumerateLiveSlots` finds no interruptible range covering the offset and returns without reporting anything, including the untracked slots. The parent frames (the caller of an out-of-line finally, and the main method body) are skipped as already reported by the funclet. So during dispatch nothing reports the method's untracked slots. A GC in that window can free an object still held in one of them. In the repro it's a boxed `Int32`. The next time the slot is reported, it points at freed memory and hits `SanityCheck()`. Other targets avoid this in the JIT. `CodeGenInterface::setFramePointerRequiredEH` forces every method with EH to be fully interruptible, because `EnumGcRefs` only reports slots in aborted frames that are fully interruptible. Wasm is explicitly excluded there, and Wasm R2R code can't be fully interruptible. ## Fix Add a `HAS_INTERRUPTIBLE_RANGES` trait to each `GcInfoEncoding`, `false` only for `Wasm32GcInfoEncoding`. - `EnumerateLiveSlots` skips the interruptible-range handling for that encoding and reports only untracked slots outside safe points, including for aborted frames. - The count of interruptible ranges is no longer serialized for that encoding. The encoder, the runtime decoder, the cDAC decoder and R2RDump all skip it, and `DefineInterruptibleRange` asserts it's never used for that encoding. - There's no R2R version bump: Wasm hasn't shipped, so Wasm-specific format changes don't need one. The gate is on the encoding rather than `TARGET_WASM`: on Wasm the interpreter's GC info goes through the same decoder, and interpreter code does define an interruptible range. Native targets are unchanged. The cDAC `GCInfoDecoder` mirrors the change through the same trait (default `true`), and `docs/design/datacontracts/GCInfo.md` documents it. `main` has no Wasm32 GC info traits in the cDAC yet; #133890 adds them. I fixed this in the decoder rather than having the JIT declare Wasm methods interruptible over their whole range. That claim isn't true for Wasm, and other checks rely on it (GC stress, the safe-point asserts). ## Size On browser-wasm `System.Private.CoreLib.wasm` (CI crossgen options), omitting the count saves 1,088 bytes (0.003%). A GC info blob only shrinks when the 2 saved bits cross a byte boundary (11,606 of 58,253 methods), and identical blobs are shared (5,331 distinct unwind entries across 60,323 methods and funclets). ## Validation I ran the CI Helix payload for `Methodical_d1` from build 1613981 locally, with crossgen2 built from this branch and a browser-wasm `corerun` built with and without the fix. For the final format I recompiled CoreLib and the test assemblies with this branch's crossgen2. | | Without fix | With fix | |---|---|---| | `Test_throwinfinallynestedintry_30`, `DOTNET_GCStress=0x1` | same `SanityCheck()` assert as CI, every run | pass | | `throwincascadedexcept_d`, `throwincascadedexceptnofin_d`, `DOTNET_GCStress=0x1` | assert, then timeout | pass | | `Methodical_d1` merged runner, no stress | 128 passed | 128 passed, same results | - Native osx-arm64 and browser-wasm checked builds pass. - R2RDump and the cDAC unit tests pass (3179/3179). - The final format gives the same no-stress results as a control that still writes and reads the count. - All 84 `Methodical_d1` assemblies, R2R-compiled with the checked Wasm JIT, never hit the new encoder assert. - In every full run, the same 18 out-of-process tests fail because my local runner doesn't handle out-of-process tests. - The final revert of the version bump only restores `readytorun.h` and the two `ModuleHeaders` files to `main`; it wasn't rebuilt. With this fix, the full `Methodical_d1` suite under `DOTNET_GCStress=0x1` was clean in 3 of 4 runs. The 4th hit a `RawGetMethodTable()` assert in `throw_SEH`. That's a separate, pre-existing hole, #134777: `CallDescrWorkerInternal` leaves the call's arguments unreported while `DoPrestub` runs. It reproduces identically with and without this change and is fixed in #134779. With both fixes, the full suite under stress passed 4 of 4 runs (126 passed), in a run before the count was removed from the format. I also checked native (osx-arm64, release 11.0 rc1 runtime, FullOpts JIT) with a small app. An object is held only in an address-exposed local, a `finally` throws, and the same method catches it with a filter that forces a GC. The object stayed alive 20/20 times, and JitDisasm shows the method as `; fully interruptible`, as `setFramePointerRequiredEH` requires. CI doesn't run GC stress on the Wasm R2R leg, which is why this showed up as a ~0.5% flake rather than a hard failure. Resolves #134768 > [!NOTE] > This PR description was drafted with GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Oct 1, 2026
lewing
force-pushed
the
lewing-r2r-cdac-for-wasm
branch
from
October 1, 2026 21:34
1166442 to
df98928
Compare
This was referenced Oct 1, 2026
This was referenced Oct 2, 2026
Open
lewing
added a commit
that referenced
this pull request
Oct 2, 2026
… section (#135059) Native `ReadyToRunInfo::GetDebugInfo` (`src/coreclr/vm/readytoruninfo.cpp`) returns `NULL` immediately when `m_pSectionDebugInfo == NULL`. The cDAC mirror, `ReadyToRunJitManager.GetDebugInfo`, dereferenced `ReadyToRunInfo.DebugInfoSection` without that check and built a `NativeArray` from whatever was at address 0. This now matters because #134690 makes Browser/WASI ReadyToRun publishes pass `--strip-debug-info` by default, including CoreLib and the framework. iOS, tvOS, and MacCatalyst already default to it. On wasm, linear address 0 is readable, so cDAC decoded garbage. A live run against a stripped browser image threw `BadImageFormatException: offset out of bounds` (`NativeReader.DecodeUnsigned` ← `NativeArray..ctor` ← `ReadyToRunJitManager.GetDebugInfo` ← `DebugInfo_1.GetMethodVarInfo`) instead of reporting that there was no debug info. On native targets, the same path fails with a read exception. ## Changes - `ReadyToRunJitManager.GetDebugInfo` returns `TargetPointer.Null` (with `hasFlagByte = false`) when `DebugInfoSection` is null, matching native. - `docs/design/datacontracts/ExecutionManager.md`: the R2R `GetDebugInfo` description now includes the null-section early return. - No caller changes were needed. `DebugInfo_1.HasDebugInfo`, `GetMethodNativeMap`, `GetMethodVarInfo`, and `GetAsyncSuspensionPoints` already treat a null debug-info pointer as "no debug info". The two map/var methods still compute `codeOffset`. ## Tests New test `ExecutionManagerTests.GetDebugInfo_R2R_NoDebugInfoSection_ReturnsNull` runs as a `[Theory]` over `StdArchAllVersions` (4 arch cases). It builds an R2R module whose `DebugInfoSection` is null and makes the low 4 KB of the address space readable as zeros, the way wasm linear memory is, so the unfixed code decodes instead of hitting a read fault. It asserts that: - `IExecutionManager.GetDebugInfo` returns `TargetPointer.Null` and `hasFlagByte == false` - `IDebugInfo.HasDebugInfo` is `false` - `GetMethodVarInfo` and `GetMethodNativeMap` return empty sequences with `codeOffset == 4` Results: - `./build.sh -s tools.cdactests -test`: passed. UnitTests 3236/3236, DataGeneratorTests 46/46, UsageTests 4/4 (no stale generated docs). - `./.dotnet/dotnet test src/native/managed/cdac/tests/UnitTests --filter "FullyQualifiedName~GetDebugInfo_R2R_NoDebugInfoSection"`: passed, 4/4. - Mutation check: I removed the null check and reran the same filtered command. It failed 4/4 with `System.BadImageFormatException : offset out of bounds` at `NativeReader.DecodeUnsigned` ← `NativeArray..ctor` ← `ReadyToRunJitManager.GetDebugInfo`, the same failure as the live wasm run. With the fix restored, it passes 4/4. This PR has a single concern. It leaves wasm stack-walk and variable-location work to #133890, #135044, and #135055. > [!NOTE] > This PR description was generated with GitHub Copilot. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing
added a commit
that referenced
this pull request
Oct 2, 2026
…35044) Fixes four cDAC failures found by walking stacks on a live CoreCLR browser-wasm target (nightly `12.0.0-alpha.1.26480.103`) through `IStackWalk`. On `main`, the walk fails on its first step, or never ends, and no MethodDesc can be named. ## Changes **MethodDesc validation rejects every MethodDesc (#135035).** With `FEATURE_PORTABLE_ENTRYPOINTS`, the runtime has no precode stubs and doesn't describe `PrecodeMachineDescriptor`, so constructing `PrecodeStubs` threw. `MethodValidation` swallowed the exception and reported every MethodDesc as invalid. - New `PrecodeStubs` version `c2` (`PrecodeStubs_2`) for portable entry points. `GetMethodDescFromStubAddress` reads `PortableEntryPoint.MethodDesc`, matching native `MethodDesc::GetMethodDescFromPrecode`. The runtime advertises `c2` under `FEATURE_PORTABLE_ENTRYPOINTS`, and `PrecodeStubs_1` is unchanged. **Stack walks require the Debugger contract (#135034).** WASM didn't advertise `Debugger`, but `StackWalk_1` calls it for every native-context frame. - WASM now advertises the existing `Debugger` `c1` contract. The in-process debugger isn't built there, so `g_pDebugger` stays null and `CLRJitAttachState` stays 0. `Debugger_1` already reports that as "not initialized": no debugger data, no hijacks. - The mistyped `int g_pDebugger` linker stub is replaced with correctly typed definitions. **Walk never ends on an R2R InlinedCallFrame (#135036).** `WasmFrameHandler` now handles the `INLINED_PINVOKE_FROM_R2R` marker as native `InlinedCallFrame::UpdateRegDisplay_Impl` does. SP comes from `CallSiteSP`, IP is the R2R virtual IP of the shadow frame there, and FP is that frame's base. The frame pointer doesn't account for funclets yet; that comes with #133890. If an active InlinedCallFrame's context still isn't managed code (no virtual IP could be recovered), the walk fails with `StackWalkState.Error`, as native `NextRaw` returns `SWA_FAILED`, rather than repeating. **Interpreter-only walk repeats forever (#135037).** This now matches native `StackFrameIterator`: - An active InlinedCallFrame for an interpreted P/Invoke (`InlinedCallFrame::IsInInterpreter`) moves straight to the InterpreterFrame that owns it, without touching the context. - A walk that starts in interpreted code moves the Frame cursor to the `Next` of the owning InterpreterFrame named in the first-argument register, as native `Init`/`ResetRegDisp` do. If the register is null or doesn't name an InterpreterFrame, the walk throws, where native asserts. The `StackWalk.md`, `PrecodeStubs.md` (new Version 2 section) and `Debugger.md` specs are updated, and the generated usage tables are regenerated. WASM isn't a shipping cDAC scenario for .NET 11, so readers built from this PR don't support WASM runtimes built before it: those don't advertise `Debugger` and still advertise `PrecodeStubs` `c1` with portable entry points. Follow-up: moving the ExecutionManager portable-entry-point special cases (`NonVirtualEntry2MethodDesc`, `GetDiagnosticCodeStartFromEntryPoint`) next to `PrecodeStubs_2`. I've left that out of this PR to avoid colliding with #133890's ExecutionManager changes. ## Validation - `./dotnet.sh test src/native/managed/cdac/tests/UnitTests/Microsoft.Diagnostics.DataContractReader.Tests.csproj`: **passed**, 3245/3245. - `pwsh src/native/managed/cdac/tools/CdacUsageGraph/generate-docs.ps1 -Check`: **passed**, docs up to date. - `./build.sh -os browser -c Debug -subset clr.runtime`: **passed**. The generated WASM contract descriptor advertises `Debugger` `c1` with its globals and `PrecodeStubs` `c2`. - `./build.sh -os browser -subset clr+libs+packs -c Release /p:BuildCrossgen2HostPackForWorkloadTesting=true`: **passed**. These are the packs used for the live run below. - New tests: - portable entry point lookup through `c2`; - the R2R InlinedCallFrame virtual IP; - a WASM walk ending, with the `Debugger` contract advertised and a null `g_pDebugger`; - an interpreted P/Invoke chain walked once from both kinds of starting point. The tests from the original fixes each failed with the corresponding fix removed. I didn't repeat that check after the review changes. The throw for a missing owning InterpreterFrame has no dedicated test. - Live browser-wasm runs at `289085e0e06` (before the last review round removed the older-runtime fallbacks) through Blazor-Playground/nesm, with nesm's workarounds and its frame guard turned off. Five configs, 5 walks each: R2R paused at a breakpoint (seeded from the frame chain and from a leaf `$sp`), R2R paused in a `[JSImport]` call, and interpreter-only paused in a tight loop and in a `[JSImport]` call. - Against nightly `12.0.0-alpha.1.26480.103`, which advertises no `Debugger` contract and `PrecodeStubs` `c1`, so the since-removed fallback paths ran: **all pass**. - Against Release packs built from this branch, which advertise `Debugger` `c1` and `PrecodeStubs` `c2`: **all pass**. Frame names, states and kinds match the nightly run exactly, and nesm's own `Debugger` and `PrecodeStubs` workarounds are never called. - In every run, walks complete with no errors and every frame is named. The R2R names match a separate static ReadyToRun lookup. - Live re-run at the current head `b996b7187da` with the same branch packs (the runtime code is unchanged since `289085e0e06`), nesm workarounds and frame guard off: R2R paused at a breakpoint (all seeds) and interpreter-only paused in a `[JSImport]` call **pass**, including the interpreted-code seed that now requires the owning InterpreterFrame. Against the nightly, the reader fails with `ContractMissingException` (`Debugger`) unless nesm's workarounds are on, as expected now that older WASM runtimes aren't supported. `TestPlaceholderTarget.TryGetThreadContext` now returns `false` (no OS context, as on WASM) instead of throwing, so tests can exercise the walk's fallback to the Frame chain. Resolves #135034 Resolves #135035 Resolves #135036 Resolves #135037 > [!NOTE] > This PR description was generated with assistance from GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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>
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: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 51b56d7e-45e5-464d-8c2b-78c744680ff8
Encode wasm locals in debug info as the local index biased past the reserved RegNum values instead of packing the JIT value type, matching other targets where the type is implied by the variable. Remove the now-unneeded cDAC descriptor globals. Wasm is only compiled ahead of time, where every variable scope covers the whole method, so open all scopes in the first emitted block instead of scanning per block. Update codegen-shape test pins for drift from main. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 51b56d7e-45e5-464d-8c2b-78c744680ff8
Replace exact native offsets, frame sizes, local counts, and instruction byte pins with checks of the debug-info contract: register records name a declared wasm local of the expected type, stack records are frame-relative and their slots are accessed, parameters are homed after their local range, ranges do not overlap, and GC locals cover the safepoint. Unrelated JIT codegen changes no longer break the test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 51b56d7e-45e5-464d-8c2b-78c744680ff8
Add an optimized Vector128 method and verify its parameters and local are recorded as wasm v128 locals. 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
Keep the WASM stack-walk context's SP, virtual IP, and logical frame pointer coherent with the native RtlVirtualUnwind and Frame::UpdateRegDisplay paths. - Use the funclet-aware logical frame pointer for the R2R InlinedCallFrame marker, matching native GetWasmFramePointerFromStackPointer; IP/SP and the failed-walk policy come from the shared InlinedCallFrame handling. - Use TransitionBlock's saved R2R SP, including lazy return-address recovery, and avoid treating the generic fallback argument area as a shadow frame. - Copy the full serialized WASM context for software exception frames. - Detect reverse P/Invoke from GC info so native caller bytes are never probed as a possible R2R shadow frame; retain caller SP with IP/FP cleared. - Expose a documented StackWalk function identity containing the raw shared table index, owning module, image runtime-function index, and nullable funclet classification. Reject native/interpreter handles before reading shadow-frame memory while preserving a known raw index when range lookup itself fails. Add native-layout transition fixtures, real adjacent root/funclet range coverage, parent/nested/terminator/localloc unwind coverage, and false-frame reverse-P/Invoke tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Decode the WASM register representation produced by ReadyToRun debug info as symbolic WasmLocal/WasmLocalPair locations. Each register field holds a WASM local index biased by ICorDebugInfo::WASM_LOCAL_REGNUM_BASE (3); values 0-2 remain REGNUM_PC, REGNUM_COUNT, and REGNUM_AMBIENT_SP and never name a local. As on other targets the value type is not encoded. Register-only locations whose register is a reserved pseudo-register are rejected rather than decoded as a local. Engine-owned locals remain symbolic; stack locations resolve from the logical frame pointer already maintained by the StackWalk context. Keep current stack encoding exact: base register 2 means context FP. Preserve the original biased VarLoc through DacDbi for consumers that need the ICorDebugInfo representation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep engine-private WASM locals unavailable rather than fabricating a zero value, and distinguish an unreadable VLT_STK_BYREF pointer from a legitimate stack-homed null reference. The native DAC retains one logical location when indirect pointer reads fail, but its subsequent address-zero memory read fails on native platforms. WASM linear address zero is readable, so carrying that fallback forward can return successful irrelevant bytes. Preserve the logical location count while making all address/value/object access report CORDBG_E_READVIRTUAL_FAILURE when the indirection is unreadable or resolves to null. Non-WASM dereference behavior remains unchanged. Exact controls cover an unreadable byref, a null byref pointer, a readable null reference, and a symbolic engine local with no physical location. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing
force-pushed
the
lewing-r2r-cdac-for-wasm
branch
from
October 2, 2026 22:18
df98928 to
a9f61e7
Compare
lewing
added a commit
that referenced
this pull request
Oct 2, 2026
Adapted from #133890: add IWasmR2RInfo.TryIsFunclet and WasmUnwinder.TryGetLogicalFramePointer, mirroring native GetWasmFramePointerFromStackPointer, and set WasmContext.FramePointer to the establishing method's frame base after each unwind. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Oct 3, 2026
This branch has not been deployed
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.
Summary
Decode ReadyToRun WebAssembly variable locations through cDAC while keeping wasm unwind state and function identity in the shared StackWalk contract.
localIndex + WASM_LOCAL_REGNUM_BASE) as symbolicWasmLocal(Index)/WasmLocalPairvalues.VLT_STKfrom the logical frame pointer already stored in the frame context.IStackWalk.ICorDebugInfo::VarLocregister values through DacDbi.Temporary stack
Important
This draft depends on #133086, whose head branch lives in the
lewing/runtimefork, so this upstream PR temporarily carries that lower layer. Its commits will drop out when this branch is rebased after #133086 merges.Base and lower layer:
mainat0d3ca16f488(includes [cDAC][wasm] Resolve ReadyToRun virtual IP ranges #133917, [cDAC][wasm] Map portable entry points to interpreter code and R2R virtual IPs #134827, [cDAC][wasm] Fix stack walks and MethodDesc naming on WebAssembly #135044, [cDAC][wasm] Register platform GC-info decoding #135055, and [cDAC] Return no debug info for ReadyToRun images without a DebugInfo section #135059)a5b5ddcaea5, replayed onto thatmainas 12 producer commitsc35209135e4..e0da19f58bd. [wasm] Preserve R2R variable debug information #133086's own branch contains a merge from an oldermain; replaying it linearizes the history. The changed lines match [wasm] Preserve R2R variable debug information #133086 exactly; only diff context differs, in three files thatmainalso changed.Three consumer commits:
019c45385b8:[cdac][wasm] Maintain frame context and expose function identitya90393058e5:[cdac][wasm] Decode ReadyToRun variable locationsa9f61e79341:[cdac][wasm] Handle unavailable variable valuesProducer constants, register/stack encodings, and producer invariant tests are owned by #133086. Virtual-IP traversal and filter-funclet classification came from #133917, function-table-index lookup from #134827, and consumer identity forwards through #134827's
WasmFunctionTableIndexLookup.Integration with merged PRs
InlinedCallFramehandling. An active marker frame whose virtual IP can't be recovered leaves IP null, and the walker fails the walk, matching nativeStackFrameIterator::NextRaw. This PR keeps that policy and adds two things. Inactive frames (CallerReturnAddress == 0) return early, matching nativeFrameHasActiveCall. FP comes from the funclet-awareTryGetLogicalFramePointer, matching nativeGetWasmFramePointerFromStackPointer, instead of the root frame base [cDAC][wasm] Fix stack walks and MethodDesc naming on WebAssembly #135044 left for this PR. A new test pins the funclet case: reverting to the root frame base fails it (expected parent frame0x200020, got funclet base0x200000).main'sWasmGCInfoTraits, which matches nativeWasm32GcInfoEncoding(includingHAS_INTERRUPTIBLE_RANGES = false). This PR no longer carries its own traits class, registration, or duplicate traits tests and docs. [cDAC][wasm] Register platform GC-info decoding #135055'sIsScratchRegisterreturnsfalse; the dropped copy returnedtruedefensively. Neither matters because RyuJIT does not keep GC references in WASM locals.StackWalk context and function identity
Native WASM
RtlVirtualUnwindupdatesInterpreterSP,InterpreterIP, and the funclet-resolvedInterpreterFPtogether. cDAC now maintains the same invariant:InlinedCallFramemarkerINLINED_PINVOKE_FROM_R2R: shared handling ([cDAC][wasm] Fix stack walks and MethodDesc naming on WebAssembly #135044) derives SP/IP fromCallSiteSP, and this PR supplies the funclet-aware logical FP.TransitionFrameuses its saved R2R SP, lazily derives a zero return address, and derives FP only when the saved SP/IP are valid. The generic argument-area fallback is never parsed as a shadow frame.HasReversePInvokeFramefrom the GC-info header, decoded bymain'sWasmGCInfoTraits([cDAC][wasm] Register platform GC-info decoding #135055).IStackWalk.GetWasmFunctionIdentityis valid only for ReadyToRun frameless frames and returns:Runtime table indices are globally relocated by each module's
tableBase, but V8func_indexvalues are module-local. Consumers therefore need the owning module plus image-relative runtime-function index to select the correct script and translate through its element section.Logical frame pointer and stack locations
Current producer stack records encode base register
2;REG_FPBASE,REG_SPBASE, andREGNUM_AMBIENT_SPcollapse to that value on WASM. The record identifies a logical frame-relative home, not a particular V8 local.The absolute frame pointer is reconstructed from shadow-stack memory by the shared unwinder:
localloc: logical FP aliases SP;localloc: logical FP remains the fixed pre-adjustment frame base;Measured engine-local indices (
$0,$1,$3) are current codegen observations, not cDAC format or API. No FP-local metadata is required. Variable resolution consumesWasmContext.FramePointer.Variable locations
Following #133086
c486ea8b02d, every register field in the WASM Vars stream holds a WebAssembly local index biased past the reserved register numbers:As on other targets, the value type is not encoded; it follows from the variable's type and the local's declaration. The earlier packed
(index, value type)form, theWasmDebugValueTypeenum, and theWasmDebugRegisterTypeShift/WasmDebugValueTypeCountdescriptor globals are gone from both producer and consumer. A register-only location whose register is a reserved pseudo-register (0–2) names no local and is rejected rather than reported as local$0or a readable register.The contract reports:
VLT_REG/VLT_REG_BYREFasWasmLocal;VLT_REG_REGasWasmLocalPair;For WASM,
GetMethodVarInfouses the ReadyToRun CodeBlock's controlling-method-relativeExecutionManager.GetRelativeOffsetresult, not the genericCodeVersionspath that rejects portable-entrypoint MethodDescs.Unavailable and null values
Three cases remain intentionally distinct:
GetBytes/GetAddressfail withE_NOINTERFACE.VLT_STK_BYREFindirection: one logical location remains, matching native location count, while address/value/object access fails withCORDBG_E_READVIRTUAL_FAILURE.The native DAC converts a failed byref read to address zero and subsequently fails because native address zero is unmapped. WASM linear address zero is readable, so carrying that fallback forward would fabricate a successful unrelated value. Non-WASM behavior is unchanged.
Live validation
The live validator was rewritten to check contract invariants instead of the codegen values pinned by the previous run. It no longer hardcodes slot offsets, variable ranges, break offsets, V8 function indices,
$varNframe-pointer locals, or asset file names:GcSlotIdentityandCollectAtGcSafepointare located through the loaded module'snameand code sections.CollectAtGcSafepointentry and stepping out until execution returns toGcSlotIdentity. Only same-module runtime helper frames may sit between them; this run had one R2R delay-load helper. The pause is therefore after the blockingGC.Collect()returns.local → valuebindings come from decoding the specimen's IL (ldc.i4 N; newobj GcMarker::.ctor; stloc Xandldnull; stloc Y).$0by the R2R calling convention. The logical FP and caller SP are reconstructed by cDAC from shadow-stack memory.2with one unambiguous offset. The resolved slot equalsFP + offset, lies within[SP, callerSP), and the GC info reports the sameFRAMEREG_RELslot as live for the whole method.Build: browser
clr+libsat this head (0 warnings, 0 errors), rebuilt becausemainchanged runtime descriptors (#135044). The specimen file from #133086 is linked unmodified into the browser sample. Optimized R2R publish with-p:PublishReadyToRunStripDebugInfo=false(see below). Module SHA-2566921b8c3ca5ec40bae5b92c538bab45b81e25229da9fb7e5c07f4e107f669416.The variable range end moved from
0x2B1to0x2A5with the producer update; the previous pinned validator would have failed on that alone. On the previous base, a live mutation adding 4 to the consumer's stack offset failed (resolved to 0x1FDFDC, not FP+0x48), and the restored run was green again. That code path is unchanged by this restack.What this run does not establish:
localloc), so the SP-versus-FP base-selection control is not discriminating here. The earlierGcLocalAcrossFinallyfunclet run with FP ≠ SP remains the evidence for that.0x4). The validator does not compare across those spaces. General CDP byte-offset → DebugInfo-offset translation remains a follow-up.Findings for consumers
--strip-debug-infoby default, including for CoreLib and framework libraries. Default publishes therefore contain no variable debug info. Opt out with-p:PublishReadyToRunStripDebugInfo=false.ReadyToRunJitManager.GetDebugInfodid not mirror native'sm_pSectionDebugInfo == NULLearly return (readytoruninfo.cpp). It dereferenced the null section pointer; on WASM address 0 is readable, so the result wasBadImageFormatExceptioninstead of "no debug info". Fixed separately by [cDAC] Return no debug info for ReadyToRun images without a DebugInfo section #135059 (merged).Tests
./build.sh -s tools.cdactests -test: UnitTests, DataGeneratorTests, and UsageTests passed; generated docs up to date.InlinedCallFrameFP reverted to the root frame base: the new funclet test fails (expected0x200020, got0x200000);3 → 2(8 tests fail), dropping the bias (6 tests fail), and the live stack-offset corruption above.Limitations and readiness
_legacyImplcomparison.v128and reference register locals have synthetic decode coverage only; their live V8 representations remain unproven.This PR remains draft pending #133086 and Larry's decision.
Note
This pull request description was generated with GitHub Copilot.