Repository navigation
[wasm] Use GetDelegateCtor for CoreCLR R2R delegate construction - #134611
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b
…asm-r2r-delegate-lowering
…asm-r2r-delegate-lowering
…asm-r2r-delegate-lowering
…asm-r2r-delegate-lowering
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b
…asm-r2r-delegate-lowering
|
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. |
|
Tagging subscribers to this area: @dotnet/crossgen-contrib |
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Three moderate review issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
What changed in this PR
Enables browser-Wasm ReadyToRun delegate-constructor lowering with shared DC0/DC1 thunks and portable fixup metadata.
Changes:
- Adds Wasm fixup handling, constructor rooting, and interpreter-transition dependencies.
- Emits shared delegate-constructor thunks with GC argument tracking.
- Adds runtime, IL, and ReadyToRun image coverage.
Review findings:
- Moderate: Image tests do not verify the matching interpreter-thunk keys.
- Moderate: The fallback condition excludes open-static delegates with hidden return buffers.
- Moderate: The closed-static return-buffer test does not invoke the delegate.
| File | Description |
|---|---|
src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.csproj |
Adds the IL helper project reference. |
src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.cs |
Adds delegate runtime coverage. |
src/tests/readytorun/wasm/WasmInterpreterTransitions/OpenInstanceDelegateHelper.ilproj |
Defines the helper IL project. |
src/tests/readytorun/wasm/WasmInterpreterTransitions/OpenInstanceDelegateHelper.il |
Provides the open-instance delegate fixture. |
src/coreclr/vm/wasm/helpers.hpp |
Declares return-buffer classification. |
src/coreclr/vm/wasm/helpers.cpp |
Implements Wasm return-buffer classification. |
src/coreclr/vm/wasm/dynamichelpers.cpp |
Adds the Wasm delay-load bridge. |
src/coreclr/vm/prestub.cpp |
Resolves delegate-constructor fixups. |
src/coreclr/vm/precode_portable.hpp |
Defines portable constructor entry-point data. |
src/coreclr/vm/method.hpp |
Declares the constructor R2R publication helper. |
src/coreclr/vm/frames.cpp |
Tracks Wasm object argument roots. |
src/coreclr/vm/cgensys.h |
Declares the typed Wasm helper entry point. |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj |
Includes the new compiler components. |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunDelegateCtorRootProvider.cs |
Roots delegate constructors. |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs |
Registers Wasm constructor roots. |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs |
Caches constructor thunk nodes. |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunkPortableEntrypoint.cs |
Connects delegate fixups to shared thunks. |
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmDelegateCtorThunkNode.cs |
Emits DC0 and DC1 Wasm thunks. |
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmDelegateConstructors.cs |
Adds ReadyToRun image fixtures. |
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs |
Validates delegate fixups and thunks. |
src/coreclr/jit/flowgraph.cpp |
Enables the Wasm R2R optimization. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Virtual targets may be incorrectly routed to CtorClosed, and coverage for excluded generic/value-type shapes is incomplete.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (2)
Resolved since last review (1)
davidwrighton
left a comment
There was a problem hiding this comment.
Lets use an R2R helper for this case. We'll need to modify the jit interface, but that's ok.
…ate-lowering # Conflicts: # src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.cs
Replace the Crossgen GetDelegateCtor override (which returned CoreLib MethodDescs across module boundaries) with a new JIT-EE callback, getDelegateCtorHelper, that selects a fixed managed helper: - CORINFO_HELP_DELEGATE_CTOR_CLOSED (Delegate.CtorClosed) for closed non-virtual, non-generic reference-type instance targets. - CORINFO_HELP_DELEGATE_CONSTRUCT (Delegate.DelegateConstruct) for all other shapes; this is the managed implementation of every delegate ctor. The helpers are exposed as READYTORUN_HELPER_DelegateConstruct/ DelegateCtorClosed (R2R 30.1), so images stay valid across split composite and non-cross-module-inlining builds. Removes the dedicated root provider and manifest token pre-creation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b
Revert the R2R-helper lowering (b970b2f) and go back to having Wasm ReadyToRun use the same GetDelegateCtor transformation as the JIT, with Crossgen2 selecting Delegate.CtorClosed or Delegate.DelegateConstruct. The CoreLib method references are pre-seeded in the manifest the same way as the runtime-async helpers the JIT synthesizes calls to. Compared to the earlier version of this approach: - Keep main's structure in fgOptimizeDelegateConstructor; Wasm only skips the dynamically composed ReadyToRun helper branch. - Implement GetDelegateCtor in the ReadyToRun/RyuJit partial files. - Exclude virtual targets from CtorClosed. - Seed the manifest tokens lazily, alongside the async references. - Drop the dedicated root provider; CoreLib compiles these bodies anyway. - Assert the selected constructor per method in the image test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b
…ate-lowering # Conflicts: # src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.cs # src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.csproj
Delegate constructor calls use lazily bound delay-load call cells, which are not part of a method's eager fixup list, so the constructor selected for a given method cannot be attributed from the image. Check the image's import signatures instead; the runtime test covers per-shape behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b
Match COMDelegate::GetDelegateCtor: closed delegates over instance methods on reference types use CtorClosed regardless of whether the target is virtual or generic. The method pointer has already been resolved by the caller (ldftn/ldvirtftn, including instantiating stubs), so CtorClosed only stores the target and pointer. Add runtime test coverage for a virtual target bound through an override and for shared and unshared generic method targets. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dd9dfb0-13d5-4a8c-9419-3f808cbd9b8b



Summary
Browser-Wasm CoreCLR ReadyToRun skipped delegate-constructor lowering. Wasm can't use the dynamically composed
READYTORUN_DELEGATE_CTORhelpers, and because ReadyToRun isIsAot(), it also skipped theGetDelegateCtorpath the JIT uses. As a result, everynew D(target, ftn)called the original runtime-implemented.ctor(object, native int)through the interpreter.With this change, Wasm ReadyToRun uses the same
GetDelegateCtortransformation as the JIT. Crossgen2 selects the CoreLib constructor:Delegate.CtorClosedInvoke(including virtual and generic targets)Delegate.DelegateConstructDelegateConstructis the managed method the runtime already maps every delegate constructor to (ecall.cpp), so the fallback matches today's behavior for all shapes.CtorClosedshapes match those for whichCOMDelegate::GetDelegateCtorpicksCtorClosed. The caller has already resolved the method pointer (ldftn/ldvirtftn, including instantiating stubs), soCtorClosedonly stores the target and the pointer.AsyncHelpersmethods such asRestoreInlinedFrameContexts. The JIT synthesizes these calls, so the caller's IL has no token for them. Crossgen2 therefore pre-seeds their manifest tokens, mirroringAddNecessaryAsyncReferences.Changes
flowgraph.cpp:TARGET_WASM, the ReadyToRun branch offgOptimizeDelegateConstructoris taken only for NativeAOT, so Wasm ReadyToRun falls through toGetDelegateCtor.getFunctionEntryPoint, because the importer cached the original constructor's entry point.GetDelegateCtoris implemented in the ReadyToRun partial; the RyuJit partial throwsNotImplementedException, as before.CtorClosedandDelegateConstructare seeded lazily on Wasm.Validation
Local runs on macOS arm64, branch merged with current
main:Builds: Crossgen2, the cross-targeting Wasm JIT, the osx JIT and SuperPMI; browser-Wasm
clr+libs+host+packsRelease.Image tests:
ILCompiler.ReadyToRun.Testswith the Wasm target: 123 passed, 0 failed.WasmDelegateConstructorsasserts that noDelegateCtorfixups remain and that both CoreLib constructors are imported.Runtime test:
readytorun/wasm/WasmInterpreterTransitionspasses in these configurations:DOTNET_GCStress=0xCDOTNET_GCStress=0xC--opt-cross-module:*CtorClosedis inlined under aCHECK_IL_BODY/VERIFY_IL_BODYfixup, and the inlined null-receiver throw calls the private CoreLib helper through a manifest reference--inputbubbleIt covers the open static, open instance (via an IL helper), closed instance, closed static, virtual (including a derived override), generic owner, shared and unshared generic method, and null-receiver shapes, plus open- and closed-static retbuf shapes. Each is value-checked, with
Target/Methodidentity and repeated construction.Image dumps:
METHOD_ENTRY_REF_TOKENmanifest references.Other:
jitformatandgit diff --check; independent code review with no findings.Performance
100,000 constructions per sample; median of 3 runs × 4 samples, from the same build:
--opt-cross-module:*Before this change, R2R construction took ~44–46 ms for both shapes in an earlier build (directional only). Open static might get faster later from a dedicated
CtorOpenpath, which would need the shuffle thunk and collectible data; that would be a follow-up.Related work
Resolves #134564
Note
This pull request description was generated with GitHub Copilot.