You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[cDAC][wasm] Resolve ReadyToRun virtual IP ranges - #133917
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.
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:
Removing the VIP-list branch fails the captured 0x80010109 test at the exact code-block assertion.
Using raw BeginAddress fails the funclet identity test.
Using startVIP as the loaded-image base fails the GC/unwind test with a read at 0x80010081 instead of the loaded image.
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.
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.
Expose WebAssembly ReadyToRun virtual IP ranges through the runtime data descriptor and resolve them before the RangeSectionMap. Mask funclet flags and keep virtual code identity separate from loaded-image RVA reads.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.
Handle feature-gated hot/cold metadata, isolate candidate module validation during registration, and remove the unsupported virtual-IP list length limit. Match WASM descriptor layouts and cover long lists, partial registration, and root/funclet image metadata.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Allow up to 65,536 nodes while retaining full-list ambiguity and cycle checks. Reject an over-budget chain before reading its next node, even after a match. Document the budget as reader policy and cover exact-budget success, budget+1 rejection, and the read boundary with a compact fixture.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.
Cover the shared native and cDAC invariant that a filter clause offset equals the flagged funclet start relative to its controlling method.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Map the executable filter entry to its containing runtime function before comparing funclet starts, matching native WASM behavior while preserving non-WASM offset comparisons.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
GcScanner.FindGCRefMap passes StubDispatchFrame/ExternalMethodFrame.Indirection here, but that value is an import-cell address in the loaded image (the next code computes its RVA by subtracting LoadedImageBase). On WASM R2R ranges are registered only in s_pVirtualIPRangeList, and RangeSection.Find consults that list only for encoded VIPs; the non-VIP path still queries RangeSectionMap, so this returns null and the scanner falls back to signature scanning/deferred-frame handling instead of the GCRefMap. Please add a WASM loaded-image-to-module resolution path and a framed-GC regression test, or otherwise keep this caller on a resolver that handles both address domains.
[!NOTE] This review comment was generated with GitHub Copilot.
Treat the WASM-omitted delay-load thunk descriptor field as optional, verify eager descriptor reads, and annotate the RangeSection flag values consumed by the ExecutionManager contract.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolve the ExecutionManagerCore conflict by combining main's flush-aware prestub cache and JIT-manager APIs with the PR's WebAssembly virtual-IP range lookup.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…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>
…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>
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
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
Fix cDAC live resolution of WebAssembly ReadyToRun virtual IPs by matching the runtime's existing lookup model:
ExecutionManager::s_pVirtualIPRangeListandVirtualIPRangeSectionthrough the data descriptor;RUNTIME_FUNCTION.BeginAddressfor ordering and address arithmetic while preserving funclet identity;MinVirtualIP) separate from the loaded-image base used for unwind, debug, GC, exception, and thunk RVA reads;Root cause
The model added in #130988 was already false when that PR merged. On
TARGET_WASM, ReadyToRun modules are not added toRangeSectionMap;ReadyToRunInfo::RegisterVirtualIPRangeregisters them inExecutionManager::s_pVirtualIPRangeList, and nativeFindCodeRangechecks that list first.The prior unit test synthesized a
RangeSectionMapentry with an address that did not satisfy nativeIsVirtualIP, 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,
FilterOffsetis 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+hostPATH="/opt/homebrew/bin:$PATH" ./build.sh -os browser -c Debug -subset clr+libsThe durable tests cover:
0x80010109, exactMethodDesc, module, and runtime-function index;RUNTIME_FUNCTIONrecords with noEndAddress, and absent hot/cold and delay-load thunk fields;RangeSectionMapfallback;MinVirtualIP;FilterOffsetfollows the flagged funclet start but resolves to the same containing runtime function;Mutation proofs were applied, confirmed in source, run red, restored, and rerun green:
0x80010109test at the exact code-block assertion.BeginAddressfails the funclet identity test.startVIPas the loaded-image base fails the GC/unwind test with a read at0x80010081instead of the loaded image.FilterOffset == funcletStartOffsetcomparison makes the filter regression fail with expectedtrueand actualfalse.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.