Skip to content

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

Merged
lewing merged 2 commits into
lewing-fix-wasm-r2r-statemachine-profiler-callsfrom
lewing-cdac-wasm-portable-entrypoint-code
Sep 29, 2026
Merged

lewing merged 2 commits into
lewing-fix-wasm-r2r-statemachine-profiler-callsfrom
lewing-cdac-wasm-portable-entrypoint-code

Conversation

@lewing

@lewing lewing commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

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 [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.
  • Not run: any check against a live wasm target.

Fixes #134753

Note

This PR description was generated with GitHub Copilot.

…rtual IPs

On FEATURE_PORTABLE_ENTRYPOINTS targets (WebAssembly), a method's entry point is a
PortableEntryPoint address that does not resolve through ExecutionManager lookups, so
SOS/DBI reported unusable native code addresses.

Add IExecutionManager.GetDiagnosticCodeStartFromEntryPoint, mirroring the native
GetDiagnosticCodeStartFromEntryPoint:

- Without portable entrypoints it delegates to
  PrecodeStubs.GetInterpreterCodeFromInterpreterPrecodeIfPresent (unchanged behavior).
- With portable entrypoints it first checks whether the address is in a code range
  (including Wasm R2R virtual-IP ranges), then maps interpreted methods to their
  InterpByteCodeStart and native Wasm R2R methods from their function-table index to
  the synthetic virtual IP (skipping funclet entries). As in native code, the R2R path
  applies only to the method's own (temporary) entry point.

Extract function-table-index resolution into ExecutionManagerHelpers.WasmFunctionTableIndexLookup
with bounded, cycle-detecting list traversal; the stack walk's WasmR2RInfo wraps it.
Switch the Legacy SOS/DBI callers to the new API.

Adds the PortableEntryPoint.ActualCode/Flags and MethodDesc.InterpreterCode data
descriptors, ExecutionManager contract docs, and unit tests.

Fixes #134753

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.

@lewing
lewing added this pull request to stack #134757 September 27, 2026 22:26
@lewing lewing added area-Diagnostics-cdac arch-wasm WebAssembly architecture labels Sep 27, 2026
@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.

@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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved issues affect entry-point publication checks, non-WASM gating, async-local enumeration, and ReadyToRun classification.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds cDAC support for resolving WebAssembly portable entry points to interpreter code or ReadyToRun virtual IPs.

Changes:

  • Adds diagnostic entry-point mapping and shared WASM function-table lookup.
  • Updates SOS/DBI callers, descriptors, tests, and documentation.
  • Adds cycle and bounds protection for function-table traversal.
File Description
src/​native/​managed/​cdac/​tests/​UnitTests/​SOSDacInterface5Tests.cs Updates mocks for diagnostic address mapping.
src/​native/​managed/​cdac/​tests/​UnitTests/​ExecutionManager/​ExecutionManagerTests.cs Adds portable-entry-point and cycle tests.
src/​native/​managed/​cdac/​tests/​UnitTests/​DacDbiImplTests.cs Updates diagnostic mapping mocks.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Legacy/​SOSDacImpl.IXCLRDataProcess.cs Uses resolved diagnostic addresses.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Legacy/​SOSDacImpl.cs Maps SOS native-code addresses.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Legacy/​Dbi/​DacDbiImpl.cs Maps DBI native-code addresses.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Legacy/​ClrDataMethodInstance.cs Maps method-instance code addresses.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Contracts/​Data/​PortableEntryPoint.cs Adds portable-entry-point fields.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Contracts/​Data/​MethodDesc.cs Adds interpreter-code metadata.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Contracts/​Contracts/​StackWalk/​Context/​Wasm/​WasmR2RInfo.cs Reuses shared WASM lookup logic.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Contracts/​Contracts/​ExecutionManager/​Helpers/​WasmFunctionTableIndexLookup.cs Implements bounded function-table traversal.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Contracts/​Contracts/​ExecutionManager/​ExecutionManagerCore.cs Resolves diagnostic code starts.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Contracts/​Contracts/​ExecutionManager/​ExecutionManager_1.cs Exposes the new contract implementation.
src/​native/​managed/​cdac/​Microsoft.Diagnostics.DataContractReader.Abstractions/​Contracts/​IExecutionManager.cs Adds the diagnostic mapping contract method.
src/​coreclr/​vm/​precode_portable.hpp Exposes portable-entry-point descriptor offsets.
src/​coreclr/​vm/​method.hpp Exposes interpreter-code descriptor metadata.
src/​coreclr/​vm/​datadescriptor/​datadescriptor.inc Registers new data descriptors.
docs/​design/​datacontracts/​RuntimeTypeSystem.md Documents interpreter-code metadata.
docs/​design/​datacontracts/​ExecutionManager.md Documents mapping behavior and WASM data.
docs/​design/​datacontracts/​data-descriptor-meanings.json Adds descriptor meanings.

EnumerateAsyncLocals passed the raw native-code/entry-point value to
DebugInfo.GetAsyncSuspensionPoints, which requires a resolvable code block. Map it
through GetDiagnosticCodeStartFromEntryPoint, and apply the matching
GetInterpreterCodeFromEntryPointIfPresent mapping in the native DAC so the
cDAC/DAC cross-validation stays consistent (mirrors GetMethodVarInfo in daccess.cpp).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing

lewing commented Sep 29, 2026

Copy link
Copy Markdown
Member Author

/ba-g unrelated *DAC windows failures that BA can't match

@lewing
lewing merged commit 257bac4 into lewing-fix-wasm-r2r-statemachine-profiler-calls Sep 29, 2026
135 of 140 checks passed
@lewing
lewing deleted the lewing-cdac-wasm-portable-entrypoint-code branch September 29, 2026 00:41
lewing added a commit that referenced this pull request Sep 29, 2026
…d R2R virtual IPs (#134756)"

This reverts commit 257bac4. The cDAC change was merged into this
branch by mistake while stacked; it will be submitted separately
against main.

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>
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