Skip to content

[cDAC][wasm] Map portable entry points to interpreter code and R2R virtual IPs - #134827

Merged
lewing merged 1 commit into
mainfrom
lewing-cdac-wasm-portable-entry-points
Sep 29, 2026
Merged

lewing merged 1 commit into
mainfrom
lewing-cdac-wasm-portable-entry-points

Conversation

@lewing

@lewing lewing commented Sep 29, 2026

Copy link
Copy Markdown
Member

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 [cDAC][wasm] Resolve ReadyToRun virtual IP ranges #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.

…rtual IPs

> [!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>
@azure-pipelines

Copy link
Copy Markdown
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.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

@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 commented Sep 29, 2026

Copy link
Copy Markdown
Member Author

/ba-g all failures have been identified

@lewing
lewing merged commit 5c9146f into main Sep 29, 2026
143 of 149 checks passed
@lewing
lewing deleted the lewing-cdac-wasm-portable-entry-points branch September 29, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Diagnostics-cdac

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[cDAC][wasm] Native code addresses for portable entry points are not mapped to interpreter code or R2R virtual IPs

2 participants