From 59e5c1c785950a9b33b1642fb7a97012e84ef155 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 15 Jul 2026 18:02:26 -0700 Subject: [PATCH 1/3] JIT: handle adjacent no-GC regions (#115719) Ensure the first instruction of each non-interruptible GC range is excluded from the range. This breaks up adjacent no-GC regions, where previously only the first instruction of the first range was excluded. Fixes #115719. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/gcencode.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/gcencode.cpp b/src/coreclr/jit/gcencode.cpp index 4016723ad02806..ada1d5537e7ae9 100644 --- a/src/coreclr/jit/gcencode.cpp +++ b/src/coreclr/jit/gcencode.cpp @@ -4067,15 +4067,16 @@ class InterruptibleRangeReporter } assert(igOffs >= m_uninterruptibleEnd); - if (igOffs > m_uninterruptibleEnd) + // Include the first instruction unless this is a prolog. + // + unsigned interruptibleEnd = igOffs; + if (!isInProlog) + { + interruptibleEnd += firstInstrSize; + } + + if (interruptibleEnd > m_uninterruptibleEnd) { - // Once the first instruction in IG executes, we cannot have GC. - // But it is ok to have GC while the IP is on the first instruction, unless we are in prolog. - unsigned interruptibleEnd = igOffs; - if (!isInProlog) - { - interruptibleEnd += firstInstrSize; - } m_gcInfoEncoder->DefineInterruptibleRange(m_uninterruptibleEnd, interruptibleEnd - m_uninterruptibleEnd); } m_uninterruptibleEnd = igOffs + igSize; From e1e41f532b8adbb359dbc494558c2f4ce221b7c0 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 16 Jul 2026 12:31:34 -0700 Subject: [PATCH 2/3] JIT: insert NOPs instead Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/gcencode.cpp | 17 ++++++++--------- src/coreclr/jit/lower.cpp | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/gcencode.cpp b/src/coreclr/jit/gcencode.cpp index ada1d5537e7ae9..4016723ad02806 100644 --- a/src/coreclr/jit/gcencode.cpp +++ b/src/coreclr/jit/gcencode.cpp @@ -4067,16 +4067,15 @@ class InterruptibleRangeReporter } assert(igOffs >= m_uninterruptibleEnd); - // Include the first instruction unless this is a prolog. - // - unsigned interruptibleEnd = igOffs; - if (!isInProlog) - { - interruptibleEnd += firstInstrSize; - } - - if (interruptibleEnd > m_uninterruptibleEnd) + if (igOffs > m_uninterruptibleEnd) { + // Once the first instruction in IG executes, we cannot have GC. + // But it is ok to have GC while the IP is on the first instruction, unless we are in prolog. + unsigned interruptibleEnd = igOffs; + if (!isInProlog) + { + interruptibleEnd += firstInstrSize; + } m_gcInfoEncoder->DefineInterruptibleRange(m_uninterruptibleEnd, interruptibleEnd - m_uninterruptibleEnd); } m_uninterruptibleEnd = igOffs + igSize; diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index f1dd62dfcc2ae8..c4f78345b11cf0 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -10346,6 +10346,26 @@ void Lowering::LowerCopyBlockStore(GenTreeBlk* blkNode) // Mark the block non-interruptible so it can be unrolled: the copy does not report the GC // references held in its temporary register(s). blkNode->gtBlkOpGcUnsafe = true; + + // Insert a nop so debug sequence points remain interruptible between adjacent non-interruptible copies. + // + if (m_compiler->opts.compDbgCode) + { + bool isClosed; + LIR::ReadOnlyRange treeRange = BlockRange().GetTreeRange(blkNode, &isClosed); + assert(isClosed); + + GenTree* ilOffset = treeRange.FirstNode()->gtPrev; + if ((ilOffset != nullptr) && ilOffset->OperIs(GT_IL_OFFSET)) + { + GenTree* previous = ilOffset->gtPrev; + if ((previous != nullptr) && previous->OperIs(GT_STORE_BLK) && previous->AsBlk()->gtBlkOpGcUnsafe) + { + GenTree* noOp = new (m_compiler, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID); + BlockRange().InsertBefore(treeRange.FirstNode(), noOp); + } + } + } } #else // TARGET_WASM // WASM lowers this to a single `memory.copy` opcode (not a GC-safe point), so it is safe for From ec4034b1140ce1658a1cc7e6cf7941cfd93af5ab Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 17 Jul 2026 12:28:34 -0700 Subject: [PATCH 3/3] revise, handle in emitDisableGC Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/emit.cpp | 36 ++++++++++++++++++++++++++++++++++-- src/coreclr/jit/emit.h | 9 +++++---- src/coreclr/jit/lower.cpp | 20 -------------------- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index af557d5d49de3b..a87cb2f4a6c982 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -1076,6 +1076,10 @@ insGroup* emitter::emitSavIG(bool emitAdd) ig->igInsCnt = (BYTE)emitCurIGinsCnt; ig->igSize = (unsigned short)emitCurIGsize; + if (ig->igSize != 0) + { + emitLastSavedIGWasNoGC = (ig->igFlags & IGF_NOGCINTERRUPT) != 0; + } emitCurCodeOffset += emitCurIGsize; assert(IsCodeAligned(emitCurCodeOffset)); @@ -1372,6 +1376,7 @@ void emitter::emitBegFN(bool hasFramePtr emitFwdJumps = false; emitNoGCRequestCount = 0; emitNoGCIG = false; + emitLastSavedIGWasNoGC = false; emitForceNewIG = false; emitContainsRemovableJmpCandidates = false; @@ -2190,6 +2195,7 @@ void emitter::emitCreatePlaceholderIG(insGroupPlaceholderType igType, // increment emitCurCodeOffset since we are not calling emitNewIG() // emitCurIGsize += MAX_PLACEHOLDER_IG_SIZE; + emitLastSavedIGWasNoGC = true; emitCurCodeOffset += emitCurIGsize; // Add the appropriate IP mapping debugging record for this placeholder @@ -10820,6 +10826,21 @@ regMaskTP emitter::emitGetGCRegsKilledByNoGCCall(CorInfoHelpFunc helper) void emitter::emitDisableGC() { + // For debuggable codegen, ensure there is an interruptible instruction between + // adjacent no-gc regions, if we're at a stack-empty point. + // + if (m_compiler->opts.compDbgCode && (emitNoGCRequestCount == 0) && emitLastCodeIsNoGC() && + !m_compiler->genIPmappings.empty()) + { + const IPmappingDsc& mapping = m_compiler->genIPmappings.back(); + if ((mapping.ipmdKind == IPmappingDscKind::Normal) && + ((mapping.ipmdLoc.GetSourceTypes() & ICorDebugInfo::STACK_EMPTY) != 0) && + mapping.ipmdNativeLoc.IsCurrentLocation(this)) + { + emitIns(INS_nop); + } + } + assert(emitNoGCRequestCount < 10); // We really shouldn't have many nested "no gc" requests. ++emitNoGCRequestCount; @@ -10846,9 +10867,20 @@ void emitter::emitDisableGC() } } -bool emitter::emitGCDisabled() +//------------------------------------------------------------------------ +// emitLastCodeIsNoGC: Check whether the last emitted native code is in a non-interruptible region. +// +// Return Value: +// true if the last non-empty instruction group is non-interruptible. +// +bool emitter::emitLastCodeIsNoGC() const { - return emitNoGCIG == true; + if ((emitCurIG != nullptr) && (emitCurIGsize != 0)) + { + return (emitCurIG->igFlags & IGF_NOGCINTERRUPT) != 0; + } + + return emitLastSavedIGWasNoGC; } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/emit.h b/src/coreclr/jit/emit.h index 780f06bd1b138c..7a6c916cc1742c 100644 --- a/src/coreclr/jit/emit.h +++ b/src/coreclr/jit/emit.h @@ -2983,9 +2983,10 @@ class emitter void emitCheckFuncletBranch(instrDesc* jmp, insGroup* jmpIG); // Check for illegal branches between funclets - bool emitFwdJumps; // forward jumps present? - unsigned emitNoGCRequestCount; // Count of number of nested "NO GC" region requests we have. - bool emitNoGCIG; // Are we generating IGF_NOGCINTERRUPT insGroups (for prologs, epilogs, etc.) + bool emitFwdJumps; // forward jumps present? + unsigned emitNoGCRequestCount; // Count of number of nested "NO GC" region requests we have. + bool emitNoGCIG; // Are we generating IGF_NOGCINTERRUPT insGroups (for prologs, epilogs, etc.) + bool emitLastSavedIGWasNoGC; // Was the last non-empty saved IG non-interruptible? bool emitForceNewIG; // If we generate an instruction, and not another instruction group, force create a new emitAdd // instruction group. @@ -3072,7 +3073,7 @@ class emitter void emitDisableGC(); void emitEnableGC(); - bool emitGCDisabled(); + bool emitLastCodeIsNoGC() const; #if defined(TARGET_XARCH) static bool emitAlignInstHasNoCode(instrDesc* id); diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index c4f78345b11cf0..f1dd62dfcc2ae8 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -10346,26 +10346,6 @@ void Lowering::LowerCopyBlockStore(GenTreeBlk* blkNode) // Mark the block non-interruptible so it can be unrolled: the copy does not report the GC // references held in its temporary register(s). blkNode->gtBlkOpGcUnsafe = true; - - // Insert a nop so debug sequence points remain interruptible between adjacent non-interruptible copies. - // - if (m_compiler->opts.compDbgCode) - { - bool isClosed; - LIR::ReadOnlyRange treeRange = BlockRange().GetTreeRange(blkNode, &isClosed); - assert(isClosed); - - GenTree* ilOffset = treeRange.FirstNode()->gtPrev; - if ((ilOffset != nullptr) && ilOffset->OperIs(GT_IL_OFFSET)) - { - GenTree* previous = ilOffset->gtPrev; - if ((previous != nullptr) && previous->OperIs(GT_STORE_BLK) && previous->AsBlk()->gtBlkOpGcUnsafe) - { - GenTree* noOp = new (m_compiler, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID); - BlockRange().InsertBefore(treeRange.FirstNode(), noOp); - } - } - } } #else // TARGET_WASM // WASM lowers this to a single `memory.copy` opcode (not a GC-safe point), so it is safe for