Skip to content

Fix R2R GC ref map mismatch for unboxing stubs#129829

Draft
jtschuster wants to merge 1 commit into
dotnet:mainfrom
jtschuster:jtschuster-fantastic-barnacle
Draft

Fix R2R GC ref map mismatch for unboxing stubs#129829
jtschuster wants to merge 1 commit into
dotnet:mainfrom
jtschuster:jtschuster-fantastic-barnacle

Conversation

@jtschuster

@jtschuster jtschuster commented Jun 25, 2026

Copy link
Copy Markdown
Member

Fixes #129778

Problem

A checked/debug build hits the assert

GC ref map mismatch detected for method: System.Int32::ToString
  Runtime GC ref map: I(00)
Crossgen2 GC ref map: R(00)

when a boxed value type is dispatched through an unboxing stub in a ReadyToRun image compiled in a Large Version Bubble. The repro is box int; tail. callvirt System.Object::ToString() (test JIT/Regression/JitBlue/DevDiv_754566), which only asserts when System.Private.CoreLib is in the version bubble.

Root cause

In MethodFixupSignature.GetData, crossgen2 optimizes MethodEntry fixups on primary, non-instantiating, non-constrained methods into a compact MethodEntry_DefToken / MethodEntry_RefToken form that emits only a token. That compact form has no flag bits, so it cannot carry READYTORUN_METHOD_SIG_UnboxingStub.

For an unboxing stub this produces an inconsistency:

  • The GC ref map node reads the same signature's IsUnboxingStub (== _method.Unboxing == true) and emits a full GC ref (R) for the boxed object reference.
  • At load time the runtime decodes the flagless token as the non-unboxing entry and computes an interior pointer (I).

R != I → the GC ref map check asserts. The interior result is also the unsafe one — calling the non-unboxing entry with a full object reference would be incorrect — so crossgen2's R is the correct value and the dropped flag is the bug.

The existing guard already excluded instantiating stubs (!IsInstantiatingStub) for exactly this reason but did not exclude unboxing stubs.

Fix

Add && !_method.Unboxing to the compact-token optimization guard so unboxing stubs fall through to the full EmitMethodSignature path, which writes the UnboxingStub flag. The common, non-unboxing case is unchanged and still uses the compact token form (no size/perf regression).

Validation

  • Reproduced the assert with crossgen2 --inputbubble (exit 134); without LVB the bug is latent and the test passes.
  • With the fix, the native (shipping) crossgen2 recompiles the image and the test runs to exit 100 / === PASSED ===.
  • R2RDump confirms the Int32.ToString() import cell now carries [UNBOX] ... (METHOD_ENTRY), while ordinary calls (Program.Test, String.Concat, Console.WriteLine) still use the compact METHOD_ENTRY_DEF_TOKEN / METHOD_ENTRY_REF_TOKEN form.

Note

This PR description was drafted with GitHub Copilot.

crossgen2 optimizes MethodEntry fixups into a compact MethodEntry_DefToken/
_RefToken form that emits only a token. This compact form cannot carry the
READYTORUN_METHOD_SIG_UnboxingStub signature flag, so for an unboxing stub the
emitted import cell lost its unboxing-ness. The GC ref map node, however, reads
the same signature's IsUnboxingStub (== _method.Unboxing) and correctly emits a
full GC ref (R) for the boxed object reference. At load time the runtime decoded
the flagless token as a non-unboxing entry and computed an interior pointer (I),
producing the checked-build assert:

  GC ref map mismatch detected for method: System.Int32::ToString

This reproduces with a box + tail. callvirt Object::ToString() (test
JIT/Regression/JitBlue/DevDiv_754566) when compiled in a Large Version Bubble,
where System.Private.CoreLib is in the version bubble and the compact-token
optimization applies to the cross-assembly call.

Exclude unboxing stubs from the compact-token optimization (mirroring the
existing !IsInstantiatingStub exclusion) so they use the full method signature,
which preserves the UnboxingStub flag. The common, non-unboxing case is
unaffected and still uses the compact form.

Fixes dotnet#129778

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 25, 2026 00:12
@github-actions github-actions Bot added the area-crossgen2-coreclr only use for closed issues label Jun 25, 2026
@jtschuster jtschuster added area-ReadyToRun and removed area-crossgen2-coreclr only use for closed issues labels Jun 25, 2026

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.

Pull request overview

This PR adjusts crossgen2 ReadyToRun fixup signature emission so MethodEntry fixups for unboxing stubs don’t take the “compact token-only” encoding path, ensuring method signature flags (notably the unboxing-stub flag) are preserved and the runtime/import interpretation stays consistent.

Changes:

  • Extend the compact MethodEntry token-only optimization guard to exclude unboxing stubs (&& !_method.Unboxing).
  • Clarify the nearby comment to document why instantiating/unboxing stubs must use the full method signature encoding.

@jtschuster

Copy link
Copy Markdown
Member Author

/azp run runtime-coreclr crossgen2-composite

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Assert failure: GC ref map mismatch detected for method: System.Int32::ToString

2 participants