Skip to content

[clr-interp] Preserve byref-like locals when an await does not suspend - #136172

Merged
radekdoulik merged 1 commit into
dotnet:mainfrom
radekdoulik:radekdoulik-interpreter-async-locals-fix
Oct 20, 2026
Merged

radekdoulik merged 1 commit into
dotnet:mainfrom
radekdoulik:radekdoulik-interpreter-async-locals-fix

Conversation

@radekdoulik

Copy link
Copy Markdown
Member

Summary

Move the existing no-continuation fast path ahead of local zeroing in the shared interpreter continuation handler.

For a synchronously completed await, the method does not suspend and must preserve its initialized locals. The existing order clears those locals before taking the no-continuation fast path.

For actual suspension, retain the existing zeroing before handling the continuation. Both the ordinary and generic continuation opcodes share this handler.

Regression coverage

Expand the existing Runtime_136000.TestEntryPoint into a four-case conditional theory: generic/nongeneric containing type crossed with ref-struct/byref local. Every case checks preservation across Task.CompletedTask, an incomplete result for a pending task, and defaulting after actual suspension.

Use a no-inline ref identity helper for the ordinary byref, analogous to the original payload's opaque initialization. This keeps a byref local represented so the test exercises continuation-local clearing rather than recomputation of a local's address. Checked interpreter dumps confirm both ordinary and generic continuation handlers are exercised. Existing feature conditions are retained; no new files or skips are added.

A direct-address prototype exposed a separate native-JIT address-rematerialization issue after suspension. The opaque initializer isolates the interpreter-local clearing path; this PR does not change JIT code or disable the byref cases.

The test was added by #136085, which fixes a different native-JIT continuation-capture problem. The interpreter's clearing-before-check behavior predates that test.

Validation

Built actual CoreCLR/browser-Wasm Checked runtimes and Release managed libraries from d260b5c598ab285046e217c012f6d7126947b06a, then rebuilt the corrected native Wasm runtime and refreshed Core_Root. Also built the same-source macOS/arm64 Checked runtime for a native-JIT control.

Validation Result
Unchanged original regression, fresh unfixed Wasm runtime 1 expected first-assertion failure
Final expanded regression, retained unfixed Wasm runtime 16/16 expected first-assertion failures
Byte-identical original regression, fixed Wasm runtime 4/4 passed
Final expanded regression, fixed Wasm runtime 16/16 passed
Related async coverage through the generated browser wrapper 36/36 passed
Final expanded regression, same-source native ARM64 JIT 4/4 passed

The final positive runs contain 60 passing selected test-case executions, zero selected failures, and zero selected skips. Filtered-out tests are not counted as executed coverage.

Wasm runs used Node.js v26.11.0 and the four (DOTNET_ReadyToRun, DOTNET_TieredCompilation) settings (0,1), (0,0), (1,1), and (1,0). The broader coverage comprises nine selected async cases per setting: shared generics, byref/out parameters, array updates, void results, generic virtual awaits and exception propagation, and emitted runtime-async methods.

The native control disables R2R and interpretation. Eight FullOpts ARM64 emitted-method assembly listings verify actual native JIT compilation. The final native and Wasm regression DLLs are byte-identical: 74ae40379b4da41bfbc31c56e87ea515398a13a4053238b599ab4355f62fecf3.

Commands ran from the repository root with DOTNET_WASM_TOOL_CACHE_DIR="$PWD/.dotnet/wasm-tools".

# Clean browser/Wasm baseline: passed on the single identical retry.
./build.sh clr+libs+host -os browser -c Release -rc Checked

# Original regression, related async runner, and initial test layout: passed.
src/tests/build.sh -os browser -arch wasm Checked -skipnative -priority1 \
  -test:JIT/Regression_ro_2/Regression_ro_2.csproj \
  -test:async/async.csproj /p:TasksConfiguration=Release

# Corrected native Wasm runtime, including interpexec.cpp and host relinking: passed.
./build.sh clr -os browser -c Release -rc Checked

# Refresh the browser test layout: passed.
src/tests/build.sh -os browser -arch wasm Checked -generatelayoutonly \
  /p:TasksConfiguration=Release

# Same-source native macOS/arm64 runtime: passed.
./build.sh clr+libs+host -os osx -arch arm64 -c Release -rc Checked

# Initial native regression and Core_Root layout: passed.
src/tests/build.sh -os osx -arch arm64 Checked -skipnative -priority1 \
  -test:JIT/Regression_ro_2/Regression_ro_2.csproj \
  /p:TasksConfiguration=Release

# Final regression build, native: passed.
src/tests/build.sh -os osx -arch arm64 Checked -skipnative \
  -skipgeneratelayout -priority1 \
  -test:JIT/Regression_ro_2/Regression_ro_2.csproj \
  /p:TasksConfiguration=Release

# Final regression build, browser/Wasm: passed.
src/tests/build.sh -os browser -arch wasm Checked -skipnative \
  -skipgeneratelayout -priority1 \
  -test:JIT/Regression_ro_2/Regression_ro_2.csproj \
  /p:TasksConfiguration=Release

The initial clean baseline hit an MSB3883 compiler-lock exception before any source edits. The one identical retry passed without a source or build-setting workaround; all listed successful build commands completed with zero warnings and errors.

Representative final regression invocations, with root set to the repository root and each run executed in a separate output directory:

# Browser/Wasm: 4 selected passes; repeat for each switch combination above.
export CORE_ROOT="$root/artifacts/tests/coreclr/browser.wasm.Checked/Tests/Core_Root"
DOTNET_ReadyToRun=0 DOTNET_TieredCompilation=0 \
  node --experimental-wasm-exnref --stack-size=8192 \
  "$root/artifacts/bin/coreclr/browser.wasm.Checked/corerun.js" -c "$CORE_ROOT" \
  "$root/artifacts/tests/coreclr/browser.wasm.Checked/JIT/Regression_ro_2/Regression_ro_2/Regression_ro_2.dll" \
  Runtime_136000

# Native ARM64 JIT: 4 selected passes, with native assembly listings.
export CORE_ROOT="$root/artifacts/tests/coreclr/osx.arm64.Checked/Tests/Core_Root"
DOTNET_ReadyToRun=0 DOTNET_TieredCompilation=0 DOTNET_InterpMode=0 \
  DOTNET_Interpreter='' DOTNET_JitDisasm='*:Test' \
  "$CORE_ROOT/corerun" \
  "$root/artifacts/tests/coreclr/osx.arm64.Checked/JIT/Regression_ro_2/Regression_ro_2/Regression_ro_2.dll" \
  Runtime_136000

The merged runner can return process exit 100 even for failed tests. Results above come from the selected xUnit XML entries and exact-count checks, not process exit status alone. Broader browser async tests were run through their generated async.sh wrapper for host-side subprocess orchestration.

Scope

The product correction only changes ordering in the shared interpreter handler. Real-suspension zeroing and continuation capture remain unchanged. No native JIT source, Reflection.Emit support, R2R folding, pipeline coverage, or reporting automation is changed.

Validation uses the actual browser/Wasm target through Node, not a Chrome tab or the exact CI binaries. The ReadyToRun settings above are environment controls, not verified R2R-compiled callers or composite CI payloads. No full async suite, Mono, or other native architectures are claimed.

Resolves #136171

Note

This pull request description was generated with GitHub Copilot.

Check for a continuation before zeroing byref and byref-like locals in the shared interpreter handler. Preserve existing zeroing for actual suspension and cover generic/nongeneric methods with ref-struct and byref locals.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b691535f-9dc8-49e3-95fe-2e83af8ef957
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 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: @agocke
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: @JulieLeeMSFT, @BrzVlad, @janvorli
See info in area-owners.md if you want to be subscribed.

@radekdoulik
radekdoulik enabled auto-merge (squash) October 20, 2026 19:52
@radekdoulik

Copy link
Copy Markdown
Member Author

/ba-g failures are unrelated

# Job Verdict Issue
1 Wasm R2R Known GC assertion [#135966](#135966)
2 macOS ARM64 TLS Pre-existing timeout [#136096](#136096)

The four red checks reflect these two failures—not four regressions.

@radekdoulik
radekdoulik disabled auto-merge October 20, 2026 21:16
@radekdoulik
radekdoulik merged commit 6f6de27 into dotnet:main Oct 20, 2026
117 of 119 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CoreCLR interpreter clears byref and byref-like locals when an await does not suspend

2 participants