Skip to content

Fix unprepared delegate target signatures under GCStress - #136864

Open
davidwrighton wants to merge 4 commits into
dotnet:mainfrom
davidwrighton:regression/delegate-signature-gcstress-136814
Open

davidwrighton wants to merge 4 commits into
dotnet:mainfrom
davidwrighton:regression/delegate-signature-gcstress-136814

Conversation

@davidwrighton

Copy link
Copy Markdown
Member

Fixes a GCStress assertion (clsload.cpp, type load on the GC thread) hit when a GC occurs in MethodDesc::DoPrestub for a delegate target whose signature contains value types that were never loaded. #135805 removed caller-side signature loading, and delegate targets published via getFunctionFixedEntryPoint were not prepared, so the prestub had no GCRefMap and fell back to signature walking.

Changes:

  • getFunctionFixedEntryPoint: call PrepareForUseAsACallTargetOfANativeImage() for delegate targets (covers JIT-constructed delegates).
  • crossgen2: emit a precomputed GCRefMap for delegate constructor fixups in a new DelegateCtorImports section (only for non-generic, non-virtual, non-unboxing targets, not Wasm). The runtime's DynamicHelperFixup uses it, with a debug assert that it matches the computed map.
  • Test harness: don't pass --verify-type-and-field-layout to crossgen2 when GCStress is enabled, since it can hide early type loads.
  • Regression test GitHub_136814 (R2R caller + JIT target); it relies on the outerloop GCStress runs and does not force GCStress itself.

Validation (Checked CoreCLR, x64 Linux): the test asserts under GCStress=0xC / JitStress=1 / tiering off without the fix and passes with it; passes normally; crossgen2smoke and coldsignaturedispatch pass under GCStress=0xC. Not run: NativeLibrary tests, the full R2R suite, Windows or Wasm paths.

Resolves #136814

Note

This PR description was AI/Copilot-generated.

davidwrighton and others added 3 commits November 2, 2026 18:50
…Stress

An R2R caller invoking a JIT-compiled delegate target whose signature
contains an unloaded value type asserts in the class loader when a GC
stress GC walks the prestub frame.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eagerly prepare delegate target signatures when publishing a fixed entry point, and have crossgen2 emit a precomputed GCRefMap for delegate constructor fixups so the prestub need not load types on the GC thread.

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

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
12 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: @dotnet/crossgen-contrib
See info in area-owners.md if you want to be subscribed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidwrighton
davidwrighton requested a balanced review from Copilot November 3, 2026 19:29
@davidwrighton
davidwrighton marked this pull request as ready for review November 3, 2026 19:30

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.

🟡 Changes recommended

Delegate fixups can cache stale GC maps for struct layouts that change across versions, causing missed GC roots, while regression tests do not cover the new mapped DelegateCtor path.

2 open issues
What changed in this PR

Addresses a delegate-target signature preparation gap exposed by GCStress after caller-side signature loading was removed. The changes span CoreCLR target preparation, crossgen2 import generation, and regression testing.

Changes:

  • Prepare JIT-created delegate targets before publication and reuse precomputed GCRefMaps for eligible ReadyToRun delegate constructors.
  • Share GCRefMap signature support and add a dedicated delegate-constructor import section.
  • Disable early layout verification under GCStress and add an R2R-caller/JIT-target regression test.
File Description
src/​tests/​Regressions/​coreclr/​GitHub_136814/​test136814.csproj Configures isolation, crossgen2, and a JIT-only target assembly.
src/​tests/​Regressions/​coreclr/​GitHub_136814/​test136814.cs Adds the delegate target and regression entry point.
src/​tests/​Regressions/​coreclr/​GitHub_136814/​delegatecaller.csproj Defines the helper library project.
src/​tests/​Regressions/​coreclr/​GitHub_136814/​delegatecaller.cs Provides the non-inlined R2R delegate caller.
src/​tests/​Common/​CLRTest.CrossGen.targets Skips layout verification when GCStress is enabled.
src/​coreclr/​vm/​prestub.cpp Consumes delegate-constructor GCRefMaps during target preparation.
src/​coreclr/​vm/​jitinterface.cpp Prepares delegate targets before exposing entry points.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRunSymbolNodeFactory.cs Routes eligible delegate constructors to mapped imports.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRunCodegenNodeFactory.cs Creates and roots the delegate-constructor import section.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRun/​SignatureEmbeddedPointerIndirectionNode.cs Uses shared GCRefMap support for signature offsets.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRun/​Signature.cs Introduces the shared GCRefMap signature base class.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRun/​MethodFixupSignature.cs Moves existing map storage into the shared base class.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRun/​ImportSectionNode.cs Enables map emission through the shared signature abstraction.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRun/​DelegateCtorSignature.cs Defines eligibility and emits delegate-target maps.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun.Tests/​TestCases/​R2RTestSuites.cs Allows GCRefMaps on additional executable-pointer import sections.

Review effort: Balanced

Comment on lines +47 to +49
return !method.Unboxing
&& !target.HasInstantiation
&& !target.OwningType.HasInstantiation
Comment on lines +125 to +129
bool hasMaps = (section.Flags & ReadyToRunImportSectionFlags.GCRefMap) != 0;
if (section.Type == ReadyToRunImportSectionType.StubDispatch)
Assert.True(hasMaps);
else if ((section.Flags & ReadyToRunImportSectionFlags.PCode) == 0)
Assert.False(hasMaps);
<Optimize>true</Optimize>
<!-- R2R code in the helper assembly calls a JIT-compiled delegate target. -->
<AlwaysUseCrossGen2>true</AlwaysUseCrossGen2>
<CLRTestTargetUnsupported Condition="'$(TargetArchitecture)' == 'wasm'">true</CLRTestTargetUnsupported>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this necessary?

Comment on lines +3 to +4
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<CLRTestPriority>1</CLRTestPriority>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use the [OuterLoop] attribute instead so we can reduce the number of processes?

Comment on lines +15 to +16
<!-- Keep the delegate target JIT-compiled so it has no precompiled GC ref map. -->
<CLRTestEnvironmentVariable Include="DOTNET_ReadyToRunExcludeList" Value="$(MSBuildProjectName)" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Replace this with BypassReadyToRunAttribute on the target?

This branch has not been deployed

No deployments
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.

Assert failure: !IsGCThread() && !IsStackWalkerThread()

3 participants