Skip to content

Fix R2R GC ref map mismatch for unboxing stubs#129829

Merged
jtschuster merged 1 commit into
dotnet:mainfrom
jtschuster:jtschuster-fantastic-barnacle
Jun 25, 2026
Merged

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

Conversation

@jtschuster

@jtschuster jtschuster commented Jun 25, 2026

Copy link
Copy Markdown
Member

Fixes #129778 - The test is now passing in the manual CI run.

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

@jtschuster
jtschuster enabled auto-merge (squash) June 25, 2026 18:05
@jtschuster
jtschuster merged commit d0f246b into dotnet:main Jun 25, 2026
136 of 144 checks passed
@github-project-automation github-project-automation Bot moved this to Done in AppModel Jun 25, 2026
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jun 26, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
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)**.

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

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

3 participants