Repository navigation
Description
Description
Follow-up to dotnet/runtime#135374 and dotnet/runtime#135381. The descriptor-based unspill fix addresses the reported reload sequence, but two other paths can leave a native-int local's continuing register home incorrectly marked as a byref:
- Permanent scalar
GT_COPYmoves use the individual use-node's GC type when producing the new home. - Address lifetime-extension helpers temporarily mark the operand's register using its node type, then cannot undo that mark if the register remains a live local home.
These are source-analysis findings, not newly observed runtime crashes.
Reproduction Steps
No executable reproducer has been constructed or run for these additional paths.
The relevant source-level state is a still-live register-candidate local with descriptor type TYP_I_IMPL and an inlined use node typed TYP_BYREF. This mismatch is explicitly permitted by the local-type invariant and constructed by inliner argument substitution.
The copy and loop paths below can positively mark the continuing home as TYP_BYREF. A subsequent native-typed use in the same basic block does not repair that GC classification.
Expected behavior
A continuing local register home retains the local descriptor's GC classification. Genuine temporary GC roots remain reported until their final machine-code use, without changing the classification of a still-live native local home.
Actual behavior
The continuing home can retain an incorrect byref bit. GCInfo::gcMarkRegSetNpt intentionally does not clear registers in GetMaskVars(), so the ordinary cleanup does not undo the positive mark.
LinearScan::spillGCRefs checks the interval type and its most recent use-node type. After a subsequent native-typed use, those checks can miss the stale byref classification retained in codegen's GC masks. If such a register is callee-saved and remains reported at an unmanaged call, this recreates the incorrect GC-reporting condition described in #135374.
Regression?
Pre-existing relative to #135381; these paths are unchanged by that PR. The introducing change has not been identified.
Known Workarounds
None verified.
Configuration
CoreCLR JIT, fixed-register code generation. The scalar copy path is shared; the affected address lifetime-extension sites span xarch, ARM/ARM64, LoongArch64, and RISC-V. No released-version or OS-specific reproduction has been established.
Source references below are pinned to the reviewed #135381 head, 7e5263c8d9e0a5e89846dfd4891fa53e2ccf537a. The relevant paths were also checked against #135415 head, 313f777aa09607a97fe43e4a2cc17f10696569f2, where they remain unchanged.
Other information
Permanent scalar copies: LSRA's fixed-register-conflict path can select moveReg for a local. insertCopyOrReload creates the copy using tree->TypeGet(); only temporary copies get the copy's last-use flag. genRegCopy installs the permanent destination as the live local's new home, then genProduceReg marks it using the copy-node type. Variable-count memory shifts provide a relevant fixed-register conflict through BuildShiftRotate's RCX requirement.
Address lifetime extension: These sites pair gcMarkRegPtrVal(addressReg, operand->TypeGet()) with gcMarkRegSetNpt. Their address need not be a dying temporary: BuildUse uses the original local interval for register-candidate local operands.
| Target | Affected sites |
|---|---|
| xarch | Returned-value Interlocked.And/Or retry loop; GC-struct zeroing loop |
| ARM64 | Non-LSE exchange/add and compare-exchange retry loops |
| ARM/ARM64 | GC-struct zeroing loop |
| LoongArch64 | GC-struct zeroing loop |
| RISC-V | Compare-exchange retry loop; GC-struct zeroing loop |
dotnet/runtime#135415 does not address these sites. Its changes fix emitter GC-state updates for register swaps and memory-result overwrites, plus inactive InlinedCallFrame handling during suspension. The issue here is the continuing local-home classification in codegen's GC masks, not those emitter update omissions.
A fix must preserve genuine GC locals and temporary roots; globally clearing live local homes in gcMarkRegSetNpt would violate its existing contract. Coverage should distinguish permanent home moves from temporary copies and address lifetime extension from last-use cleanup.
Note
This issue was drafted by GitHub Copilot from source analysis.
Activity
dotnet-policy-service commentedon Oct 8, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.