diff --git a/src/coreclr/gcinfo/gcinfoencoder.cpp b/src/coreclr/gcinfo/gcinfoencoder.cpp index 115f7cdcf253ff..0abf65047b8d9a 100644 --- a/src/coreclr/gcinfo/gcinfoencoder.cpp +++ b/src/coreclr/gcinfo/gcinfoencoder.cpp @@ -922,7 +922,11 @@ void GcInfoEncoder::FinalizeSlotIds() #endif } -bool GcInfoEncoder::IsAlwaysScratch(GcSlotDesc &slotDesc) +#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED + +// tells whether a slot cannot contain an object reference +// at call instruction or right after returning +bool GcInfoEncoder::DoNotTrackInPartiallyInterruptible(GcSlotDesc &slotDesc) { #if defined(TARGET_ARM) @@ -933,7 +937,31 @@ bool GcInfoEncoder::IsAlwaysScratch(GcSlotDesc &slotDesc) _ASSERTE(regNum >= 0 && regNum <= 14); _ASSERTE(regNum != 13); // sp - return ((regNum <= 3) || (regNum >= 12)); // R12 and R14/LR are both scratch registers + return ((regNum <= 3) || (regNum >= 12)) // R12 is volatile and SP/LR can't contain objects around calls + && regNum != 0 // R0 can contain return value + ; + } + else if (!slotDesc.IsUntracked() && (slotDesc.Slot.Stack.Base == GC_SP_REL) && + ((UINT32)slotDesc.Slot.Stack.SpOffset < m_SizeOfStackOutgoingAndScratchArea)) + { + return TRUE; + } + else + return FALSE; + +#elif defined(TARGET_ARM64) + + _ASSERTE(m_SizeOfStackOutgoingAndScratchArea != (UINT32)-1); + if (slotDesc.IsRegister()) + { + int regNum = (int)slotDesc.Slot.RegisterNumber; + _ASSERTE(regNum >= 0 && regNum <= 30); + _ASSERTE(regNum != 18); + + return (regNum <= 17 || regNum >= 29) // X0 through X17 are scratch, FP/LR can't be used for objects around calls + && regNum != 0 // X0 can contain return value + && regNum != 1 // X1 can contain return value + ; } else if (!slotDesc.IsUntracked() && (slotDesc.Slot.Stack.Base == GC_SP_REL) && ((UINT32)slotDesc.Slot.Stack.SpOffset < m_SizeOfStackOutgoingAndScratchArea)) @@ -953,7 +981,7 @@ bool GcInfoEncoder::IsAlwaysScratch(GcSlotDesc &slotDesc) _ASSERTE(regNum != 4); // rsp UINT16 PreservedRegMask = - (1 << 3) // rbx + (1 << 3) // rbx | (1 << 5) // rbp #ifndef UNIX_AMD64_ABI | (1 << 6) // rsi @@ -962,7 +990,12 @@ bool GcInfoEncoder::IsAlwaysScratch(GcSlotDesc &slotDesc) | (1 << 12) // r12 | (1 << 13) // r13 | (1 << 14) // r14 - | (1 << 15); // r15 + | (1 << 15) // r15 + | (1 << 0) // rax - may contain return value +#ifdef UNIX_AMD64_ABI + | (1 << 2) // rdx - may contain return value +#endif + ; return !(PreservedRegMask & (1 << regNum)); } @@ -978,6 +1011,7 @@ bool GcInfoEncoder::IsAlwaysScratch(GcSlotDesc &slotDesc) return FALSE; #endif } +#endif // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED void GcInfoEncoder::Build() { @@ -1396,7 +1430,7 @@ void GcInfoEncoder::Build() else { UINT32 slotIndex = pCurrent->SlotId; - if(!IsAlwaysScratch(m_SlotTable[slotIndex])) + if(!DoNotTrackInPartiallyInterruptible(m_SlotTable[slotIndex])) { BYTE becomesLive = pCurrent->BecomesLive; _ASSERTE((liveState.ReadBit(slotIndex) && !becomesLive) diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index ddc7c79506ad4e..7005e25226ce4f 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -505,6 +505,7 @@ CONFIG_DWORD_INFO(INTERNAL_DiagnosticSuspend, W("DiagnosticSuspend"), 0, "") CONFIG_DWORD_INFO(INTERNAL_SuspendDeadlockTimeout, W("SuspendDeadlockTimeout"), 40000, "") CONFIG_DWORD_INFO(INTERNAL_SuspendThreadDeadlockTimeoutMs, W("SuspendThreadDeadlockTimeoutMs"), 2000, "") RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadSuspendInjection, W("INTERNAL_ThreadSuspendInjection"), 1, "Specifies whether to inject activations for thread suspension on Unix") +RETAIL_CONFIG_DWORD_INFO(INTERNAL_InterruptibleCallSites, W("InterruptibleCallSites"), 1, "Specifies whether to allow asynchronous thread interruptions at call sites (requires GCInfo v3)") /// /// Thread (miscellaneous) diff --git a/src/coreclr/inc/eetwain.h b/src/coreclr/inc/eetwain.h index bee2f658ee7c08..c7b1be02e5c638 100644 --- a/src/coreclr/inc/eetwain.h +++ b/src/coreclr/inc/eetwain.h @@ -457,6 +457,9 @@ virtual bool IsGcSafe( EECodeInfo *pCodeInfo, DWORD dwRelOffset); +static +bool InterruptibleSafePointsEnabled(); + #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) virtual bool HasTailCalls(EECodeInfo *pCodeInfo); diff --git a/src/coreclr/inc/gcinfo.h b/src/coreclr/inc/gcinfo.h index 16bff25525a97d..d526405c9f2cff 100644 --- a/src/coreclr/inc/gcinfo.h +++ b/src/coreclr/inc/gcinfo.h @@ -36,7 +36,7 @@ const unsigned this_OFFSET_FLAG = 0x2; // the offset is "this" // The current GCInfo Version //----------------------------------------------------------------------------- -#define GCINFO_VERSION 2 +#define GCINFO_VERSION 3 //----------------------------------------------------------------------------- // GCInfoToken: A wrapper that contains the GcInfo data and version number. @@ -65,9 +65,16 @@ struct GCInfoToken } #endif - static uint32_t ReadyToRunVersionToGcInfoVersion(uint32_t readyToRunMajorVersion) + static uint32_t ReadyToRunVersionToGcInfoVersion(uint32_t readyToRunMajorVersion, uint32_t readyToRunMinorVersion) { - // GcInfo version is current from ReadyToRun version 2.0 + // Once MINIMUM_READYTORUN_MAJOR_VERSION is bumped to 10+ + // delete the following and just return GCINFO_VERSION + // + // R2R 9.0 and 9.1 use GCInfo v2 + // R2R 9.2 uses GCInfo v3 + if (readyToRunMajorVersion == 9 && readyToRunMinorVersion < 2) + return 2; + return GCINFO_VERSION; } }; diff --git a/src/coreclr/inc/gcinfodecoder.h b/src/coreclr/inc/gcinfodecoder.h index b42f5aae8f6034..8534bc2508c5a4 100644 --- a/src/coreclr/inc/gcinfodecoder.h +++ b/src/coreclr/inc/gcinfodecoder.h @@ -502,12 +502,17 @@ class GcInfoDecoder //------------------------------------------------------------------------ bool IsInterruptible(); + bool HasInterruptibleRanges(); #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED + bool IsSafePoint(); + bool AreSafePointsInterruptible(); + bool IsInterruptibleSafePoint(); + // This is used for gccoverage bool IsSafePoint(UINT32 codeOffset); - typedef void EnumerateSafePointsCallback (UINT32 offset, void * hCallback); + typedef void EnumerateSafePointsCallback (GcInfoDecoder* decoder, UINT32 offset, void * hCallback); void EnumerateSafePoints(EnumerateSafePointsCallback * pCallback, void * hCallback); #endif diff --git a/src/coreclr/inc/gcinfoencoder.h b/src/coreclr/inc/gcinfoencoder.h index 5085e5971a8ebd..b3199a1a956160 100644 --- a/src/coreclr/inc/gcinfoencoder.h +++ b/src/coreclr/inc/gcinfoencoder.h @@ -542,7 +542,9 @@ class GcInfoEncoder void SizeofSlotStateVarLengthVector(const BitArray& vector, UINT32 baseSkip, UINT32 baseRun, UINT32 * pSizeofSimple, UINT32 * pSizeofRLE, UINT32 * pSizeofRLENeg); UINT32 WriteSlotStateVarLengthVector(BitStreamWriter &writer, const BitArray& vector, UINT32 baseSkip, UINT32 baseRun); - bool IsAlwaysScratch(GcSlotDesc &slot); +#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED + bool DoNotTrackInPartiallyInterruptible(GcSlotDesc &slot); +#endif // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED // Assumes that "*ppTransitions" is has size "numTransitions", is sorted by CodeOffset then by SlotId, // and that "*ppEndTransitions" points one beyond the end of the array. If "*ppTransitions" contains diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 122906341d00b4..762656a3dbbcff 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID; #define GUID_DEFINED #endif // !GUID_DEFINED -constexpr GUID JITEEVersionIdentifier = { /* bd8c41d4-8531-49c1-a600-0ae9bfe05de1 */ - 0xbd8c41d4, - 0x8531, - 0x49c1, - {0xa6, 0x00, 0x0a, 0xe9, 0xbf, 0xe0, 0x5d, 0xe1} +constexpr GUID JITEEVersionIdentifier = { /* 43854594-cd60-45df-a89f-5b7697586f46 */ + 0x43854594, + 0xcd60, + 0x45df, + {0xa8, 0x9f, 0x5b, 0x76, 0x97, 0x58, 0x6f, 0x46} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 1c3ce8237ef7fc..a9050dae85f7ae 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -35,7 +35,7 @@ // R2R Version 9.1 adds new helpers to allocate objects on frozen segments // R2R Version 9.2 adds MemZero and NativeMemSet helpers // R2R Version 9.3 adds BulkWriteBarrier helper - +// uses GCInfo v3, which makes safe points in partially interruptible code interruptible. struct READYTORUN_CORE_HEADER { diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 90447292dbe837..1cb7ae7985c274 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -3503,25 +3503,35 @@ void CodeGen::genCallInstruction(GenTreeCall* call) emitAttr retSize = EA_PTRSIZE; emitAttr secondRetSize = EA_UNKNOWN; - if (call->HasMultiRegRetVal()) + // unused values are of no interest to GC. + if (!call->IsUnusedValue()) { - retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); - secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); - } - else - { - assert(call->gtType != TYP_STRUCT); - - if (call->gtType == TYP_REF) + if (call->HasMultiRegRetVal()) { - retSize = EA_GCREF; + retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); + secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); } - else if (call->gtType == TYP_BYREF) + else { - retSize = EA_BYREF; + assert(call->gtType != TYP_STRUCT); + + if (call->gtType == TYP_REF) + { + retSize = EA_GCREF; + } + else if (call->gtType == TYP_BYREF) + { + retSize = EA_BYREF; + } } } +#ifdef TARGET_ARM + // ARM32 support multireg returns, but only to return 64bit primitives. + assert(secondRetSize != EA_GCREF); + assert(secondRetSize != EA_BYREF); +#endif + DebugInfo di; // We need to propagate the debug information to the call instruction, so we can emit // an IL to native mapping record for the call, to support managed return value debugging. diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 6dcbba13b48533..27fd021ba3d2c2 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -997,8 +997,8 @@ void CodeGen::genLogLabel(BasicBlock* bb) void CodeGen::genDefineTempLabel(BasicBlock* label) { genLogLabel(label); - label->bbEmitCookie = GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur DEBUG_ARG(label)); + label->bbEmitCookie = + GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur); } // genDefineInlineTempLabel: Define an inline label that does not affect the GC diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 786f40c2f4ca7d..4c3d53acfa5db8 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -348,7 +348,7 @@ void CodeGen::genCodeForBBlist() // Mark a label and update the current set of live GC refs block->bbEmitCookie = GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, - gcInfo.gcRegByrefSetCur DEBUG_ARG(block)); + gcInfo.gcRegByrefSetCur, block->Prev()); } if (block->IsFirstColdBlock(compiler)) @@ -714,8 +714,7 @@ void CodeGen::genCodeForBBlist() } // Do likewise for blocks that end in DOES_NOT_RETURN calls // that were not caught by the above rules. This ensures that - // gc register liveness doesn't change across call instructions - // in fully-interruptible mode. + // gc register liveness doesn't change to some random state after call instructions else { GenTree* call = block->lastNode(); diff --git a/src/coreclr/jit/codegenloongarch64.cpp b/src/coreclr/jit/codegenloongarch64.cpp index 6fceb11807ed33..6090ad39e555dc 100644 --- a/src/coreclr/jit/codegenloongarch64.cpp +++ b/src/coreclr/jit/codegenloongarch64.cpp @@ -6356,22 +6356,26 @@ void CodeGen::genCallInstruction(GenTreeCall* call) emitAttr retSize = EA_PTRSIZE; emitAttr secondRetSize = EA_UNKNOWN; - if (call->HasMultiRegRetVal()) + // unused values are of no interest to GC. + if (!call->IsUnusedValue()) { - retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); - secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); - } - else - { - assert(call->gtType != TYP_STRUCT); - - if (call->gtType == TYP_REF) + if (call->HasMultiRegRetVal()) { - retSize = EA_GCREF; + retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); + secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); } - else if (call->gtType == TYP_BYREF) + else { - retSize = EA_BYREF; + assert(call->gtType != TYP_STRUCT); + + if (call->gtType == TYP_REF) + { + retSize = EA_GCREF; + } + else if (call->gtType == TYP_BYREF) + { + retSize = EA_BYREF; + } } } diff --git a/src/coreclr/jit/codegenriscv64.cpp b/src/coreclr/jit/codegenriscv64.cpp index 84a2663dcbcce5..fd9ce985cd25e0 100644 --- a/src/coreclr/jit/codegenriscv64.cpp +++ b/src/coreclr/jit/codegenriscv64.cpp @@ -6500,22 +6500,26 @@ void CodeGen::genCallInstruction(GenTreeCall* call) emitAttr retSize = EA_PTRSIZE; emitAttr secondRetSize = EA_UNKNOWN; - if (call->HasMultiRegRetVal()) + // unused values are of no interest to GC. + if (!call->IsUnusedValue()) { - retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); - secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); - } - else - { - assert(call->gtType != TYP_STRUCT); - - if (call->gtType == TYP_REF) + if (call->HasMultiRegRetVal()) { - retSize = EA_GCREF; + retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); + secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); } - else if (call->gtType == TYP_BYREF) + else { - retSize = EA_BYREF; + assert(call->gtType != TYP_STRUCT); + + if (call->gtType == TYP_REF) + { + retSize = EA_GCREF; + } + else if (call->gtType == TYP_BYREF) + { + retSize = EA_BYREF; + } } } diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index a2a17e735c9038..aa8d9426b84db0 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -6212,22 +6212,26 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA emitAttr retSize = EA_PTRSIZE; emitAttr secondRetSize = EA_UNKNOWN; - if (call->HasMultiRegRetVal()) + // unused values are of no interest to GC. + if (!call->IsUnusedValue()) { - retSize = emitTypeSize(retTypeDesc->GetReturnRegType(0)); - secondRetSize = emitTypeSize(retTypeDesc->GetReturnRegType(1)); - } - else - { - assert(!varTypeIsStruct(call)); - - if (call->gtType == TYP_REF) + if (call->HasMultiRegRetVal()) { - retSize = EA_GCREF; + retSize = emitTypeSize(retTypeDesc->GetReturnRegType(0)); + secondRetSize = emitTypeSize(retTypeDesc->GetReturnRegType(1)); } - else if (call->gtType == TYP_BYREF) + else { - retSize = EA_BYREF; + assert(!varTypeIsStruct(call)); + + if (call->gtType == TYP_REF) + { + retSize = EA_GCREF; + } + else if (call->gtType == TYP_BYREF) + { + retSize = EA_BYREF; + } } } diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index f8b320c07d44cd..61fcf9447137de 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -2217,6 +2217,10 @@ void emitter::emitCreatePlaceholderIG(insGroupPlaceholderType igType, emitCurIG->igFlags &= ~IGF_PROPAGATE_MASK; } + // since we have emitted a placeholder, the last ins is not longer the last. + emitLastIns = nullptr; + emitLastInsIG = nullptr; + #ifdef DEBUG if (emitComp->verbose) { @@ -2834,6 +2838,16 @@ bool emitter::emitNoGChelper(CorInfoHelpFunc helpFunc) case CORINFO_HELP_INIT_PINVOKE_FRAME: + case CORINFO_HELP_FAIL_FAST: + case CORINFO_HELP_STACK_PROBE: + + case CORINFO_HELP_CHECK_OBJ: + + // never present on stack at the time of GC + case CORINFO_HELP_TAILCALL: + case CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER: + case CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS: + case CORINFO_HELP_VALIDATE_INDIRECT_CALL: return true; @@ -2868,10 +2882,36 @@ bool emitter::emitNoGChelper(CORINFO_METHOD_HANDLE methHnd) * Mark the current spot as having a label. */ -void* emitter::emitAddLabel(VARSET_VALARG_TP GCvars, - regMaskTP gcrefRegs, - regMaskTP byrefRegs DEBUG_ARG(BasicBlock* block)) +void* emitter::emitAddLabel(VARSET_VALARG_TP GCvars, regMaskTP gcrefRegs, regMaskTP byrefRegs, BasicBlock* prevBlock) { + // if starting a new block that can be a target of a branch and the last instruction was GC-capable call. + if ((prevBlock != nullptr) && emitComp->compCurBB->HasFlag(BBF_HAS_LABEL) && emitLastInsIsCallWithGC()) + { + // no GC-capable calls expected in prolog + assert(!emitIGisInEpilog(emitLastInsIG)); + + // We have just emitted a call that can do GC and conservatively recorded what is alive after the call. + // Now we see that the next instruction may be reachable by a branch with a different liveness. + // We want to maintain the invariant that the GC info at IP after a GC-capable call is the same + // regardless how it is reached. + // One way to ensure that is by adding an instruction (NOP or BRK) after the call. + if ((emitThisGCrefRegs != gcrefRegs) || (emitThisByrefRegs != byrefRegs) || + !VarSetOps::Equal(emitComp, emitThisGCrefVars, GCvars)) + { + if (prevBlock->KindIs(BBJ_THROW)) + { + emitIns(INS_BREAKPOINT); + } + else + { + // other block kinds should emit something at the end that is not a call. + assert(prevBlock->KindIs(BBJ_ALWAYS)); + // CONSIDER: In many cases we could instead patch up the GC info for previous call instruction. + emitIns(INS_nop); + } + } + } + /* Create a new IG if the current one is non-empty */ if (emitCurIGnonEmpty()) @@ -3632,6 +3672,7 @@ emitter::instrDesc* emitter::emitNewInstrCallInd(int argCnt, /* Make sure we didn't waste space unexpectedly */ assert(!id->idIsLargeCns()); + id->idSetIsCall(); #ifdef TARGET_XARCH /* Store the displacement and make sure the value fit */ @@ -3711,6 +3752,7 @@ emitter::instrDesc* emitter::emitNewInstrCallDir(int argCnt, /* Make sure we didn't waste space unexpectedly */ assert(!id->idIsLargeCns()); + id->idSetIsCall(); /* Save the live GC registers in the unused register fields */ assert((gcrefRegs & RBM_CALLEE_TRASH) == 0); @@ -8720,6 +8762,16 @@ void emitter::emitUpdateLiveGCvars(VARSET_VALARG_TP vars, BYTE* addr) emitThisGCrefVset = true; } +/***************************************************************************** + * + * Last emitted instruction is a call and it is not a NoGC call. + */ + +bool emitter::emitLastInsIsCallWithGC() +{ + return emitLastIns != nullptr && emitLastIns->idIsCall() && !emitLastIns->idIsNoGC(); +} + /***************************************************************************** * * Record a call location for GC purposes (we know that this is a method that @@ -10008,7 +10060,7 @@ void emitter::emitStackPopLargeStk(BYTE* addr, bool isCall, unsigned char callIn // We make a bitmask whose bits correspond to callee-saved register indices (in the sequence // of callee-saved registers only). - for (unsigned calleeSavedRegIdx = 0; calleeSavedRegIdx < CNT_CALLEE_SAVED; calleeSavedRegIdx++) + for (unsigned calleeSavedRegIdx = 0; calleeSavedRegIdx < CNT_CALL_GC_REGS; calleeSavedRegIdx++) { regMaskTP calleeSavedRbm = raRbmCalleeSaveOrder[calleeSavedRegIdx]; if (emitThisGCrefRegs & calleeSavedRbm) diff --git a/src/coreclr/jit/emit.h b/src/coreclr/jit/emit.h index 4fb37df5149111..3e74bca557263d 100644 --- a/src/coreclr/jit/emit.h +++ b/src/coreclr/jit/emit.h @@ -762,10 +762,10 @@ class emitter // loongarch64: 28 bits // risc-v: 28 bits - unsigned _idSmallDsc : 1; // is this a "small" descriptor? - unsigned _idLargeCns : 1; // does a large constant follow? - unsigned _idLargeDsp : 1; // does a large displacement follow? - unsigned _idLargeCall : 1; // large call descriptor used + unsigned _idSmallDsc : 1; // is this a "small" descriptor? + unsigned _idLargeCns : 1; // does a large constant follow? (or if large call descriptor used) + unsigned _idLargeDsp : 1; // does a large displacement follow? + unsigned _idCall : 1; // this is a call // We have several pieces of information we need to encode but which are only applicable // to a subset of instrDescs. To accommodate that, we define a several _idCustom# bitfields @@ -1565,7 +1565,7 @@ class emitter bool idIsLargeCns() const { - return _idLargeCns != 0; + return _idLargeCns != 0 && !idIsCall(); } void idSetIsLargeCns() { @@ -1585,13 +1585,23 @@ class emitter _idLargeDsp = 0; } + bool idIsCall() const + { + return _idCall != 0; + } + void idSetIsCall() + { + _idCall = 1; + } + bool idIsLargeCall() const { - return _idLargeCall != 0; + return idIsCall() && _idLargeCns == 1; } void idSetIsLargeCall() { - _idLargeCall = 1; + idSetIsCall(); + _idLargeCns = 1; } bool idIsBound() const @@ -2857,9 +2867,11 @@ class emitter // Mark this instruction group as having a label; return the new instruction group. // Sets the emitter's record of the currently live GC variables // and registers. - void* emitAddLabel(VARSET_VALARG_TP GCvars, - regMaskTP gcrefRegs, - regMaskTP byrefRegs DEBUG_ARG(BasicBlock* block = nullptr)); + // prevBlock is passed when starting a new block. + void* emitAddLabel(VARSET_VALARG_TP GCvars, + regMaskTP gcrefRegs, + regMaskTP byrefRegs, + BasicBlock* prevBlock = nullptr); // Same as above, except the label is added and is conceptually "inline" in // the current block. Thus it extends the previous block and the emitter @@ -3190,6 +3202,8 @@ class emitter void emitRecordGCcall(BYTE* codePos, unsigned char callInstrSize); + bool emitLastInsIsCallWithGC(); + // Helpers for the above void emitStackPushLargeStk(BYTE* addr, GCtype gcType, unsigned count = 1); diff --git a/src/coreclr/jit/emitarm.cpp b/src/coreclr/jit/emitarm.cpp index 65a6e098b2976a..ea88e01adbd90b 100644 --- a/src/coreclr/jit/emitarm.cpp +++ b/src/coreclr/jit/emitarm.cpp @@ -4754,11 +4754,22 @@ void emitter::emitIns_Call(EmitCallType callType, /* Update the emitter's live GC ref sets */ + // If the method returns a GC ref, mark R0 appropriately + if (retSize == EA_GCREF) + { + gcrefRegs |= RBM_R0; + } + else if (retSize == EA_BYREF) + { + byrefRegs |= RBM_R0; + } + VarSetOps::Assign(emitComp, emitThisGCrefVars, ptrVars); emitThisGCrefRegs = gcrefRegs; emitThisByrefRegs = byrefRegs; - id->idSetIsNoGC(emitNoGChelper(methHnd)); + // for the purpose of GC safepointing tail-calls are not real calls + id->idSetIsNoGC(isJump || emitNoGChelper(methHnd)); /* Set the instruction - special case jumping a function */ instruction ins; diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 3c988633e798b9..b0a63e41a07eca 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -9020,11 +9020,32 @@ void emitter::emitIns_Call(EmitCallType callType, /* Update the emitter's live GC ref sets */ + // If the method returns a GC ref, mark RBM_INTRET appropriately + if (retSize == EA_GCREF) + { + gcrefRegs |= RBM_INTRET; + } + else if (retSize == EA_BYREF) + { + byrefRegs |= RBM_INTRET; + } + + // If is a multi-register return method is called, mark RBM_INTRET_1 appropriately + if (secondRetSize == EA_GCREF) + { + gcrefRegs |= RBM_INTRET_1; + } + else if (secondRetSize == EA_BYREF) + { + byrefRegs |= RBM_INTRET_1; + } + VarSetOps::Assign(emitComp, emitThisGCrefVars, ptrVars); emitThisGCrefRegs = gcrefRegs; emitThisByrefRegs = byrefRegs; - id->idSetIsNoGC(emitNoGChelper(methHnd)); + // for the purpose of GC safepointing tail-calls are not real calls + id->idSetIsNoGC(isJump || emitNoGChelper(methHnd)); /* Set the instruction - special case jumping a function */ instruction ins; diff --git a/src/coreclr/jit/emitloongarch64.cpp b/src/coreclr/jit/emitloongarch64.cpp index 43281e536e8b31..6647d935f9e3b7 100644 --- a/src/coreclr/jit/emitloongarch64.cpp +++ b/src/coreclr/jit/emitloongarch64.cpp @@ -2464,11 +2464,32 @@ void emitter::emitIns_Call(EmitCallType callType, /* Update the emitter's live GC ref sets */ + // If the method returns a GC ref, mark RBM_INTRET appropriately + if (retSize == EA_GCREF) + { + gcrefRegs |= RBM_INTRET; + } + else if (retSize == EA_BYREF) + { + byrefRegs |= RBM_INTRET; + } + + // If is a multi-register return method is called, mark RBM_INTRET_1 appropriately + if (secondRetSize == EA_GCREF) + { + gcrefRegs |= RBM_INTRET_1; + } + else if (secondRetSize == EA_BYREF) + { + byrefRegs |= RBM_INTRET_1; + } + VarSetOps::Assign(emitComp, emitThisGCrefVars, ptrVars); emitThisGCrefRegs = gcrefRegs; emitThisByrefRegs = byrefRegs; - id->idSetIsNoGC(emitNoGChelper(methHnd)); + // for the purpose of GC safepointing tail-calls are not real calls + id->idSetIsNoGC(isJump || emitNoGChelper(methHnd)); /* Set the instruction - special case jumping a function */ instruction ins; diff --git a/src/coreclr/jit/emitriscv64.cpp b/src/coreclr/jit/emitriscv64.cpp index 932c9a1125b016..a05831c473d6f0 100644 --- a/src/coreclr/jit/emitriscv64.cpp +++ b/src/coreclr/jit/emitriscv64.cpp @@ -1373,11 +1373,32 @@ void emitter::emitIns_Call(EmitCallType callType, /* Update the emitter's live GC ref sets */ + // If the method returns a GC ref, mark RBM_INTRET appropriately + if (retSize == EA_GCREF) + { + gcrefRegs |= RBM_INTRET; + } + else if (retSize == EA_BYREF) + { + byrefRegs |= RBM_INTRET; + } + + // If is a multi-register return method is called, mark RBM_INTRET_1 appropriately + if (secondRetSize == EA_GCREF) + { + gcrefRegs |= RBM_INTRET_1; + } + else if (secondRetSize == EA_BYREF) + { + byrefRegs |= RBM_INTRET_1; + } + VarSetOps::Assign(emitComp, emitThisGCrefVars, ptrVars); emitThisGCrefRegs = gcrefRegs; emitThisByrefRegs = byrefRegs; - id->idSetIsNoGC(emitNoGChelper(methHnd)); + // for the purpose of GC safepointing tail-calls are not real calls + id->idSetIsNoGC(isJump || emitNoGChelper(methHnd)); /* Set the instruction - special case jumping a function */ instruction ins; diff --git a/src/coreclr/jit/emitxarch.cpp b/src/coreclr/jit/emitxarch.cpp index 6fe7f00dddc359..637db7ba08b6f6 100644 --- a/src/coreclr/jit/emitxarch.cpp +++ b/src/coreclr/jit/emitxarch.cpp @@ -9578,6 +9578,28 @@ void emitter::emitIns_Call(EmitCallType callType, /* Update the emitter's live GC ref sets */ + // If the method returns a GC ref, mark EAX appropriately + if (retSize == EA_GCREF) + { + gcrefRegs |= RBM_EAX; + } + else if (retSize == EA_BYREF) + { + byrefRegs |= RBM_EAX; + } + +#ifdef UNIX_AMD64_ABI + // If is a multi-register return method is called, mark RDX appropriately (for System V AMD64). + if (secondRetSize == EA_GCREF) + { + gcrefRegs |= RBM_RDX; + } + else if (secondRetSize == EA_BYREF) + { + byrefRegs |= RBM_RDX; + } +#endif // UNIX_AMD64_ABI + VarSetOps::Assign(emitComp, emitThisGCrefVars, ptrVars); emitThisGCrefRegs = gcrefRegs; emitThisByrefRegs = byrefRegs; @@ -9598,7 +9620,8 @@ void emitter::emitIns_Call(EmitCallType callType, } id->idIns(ins); - id->idSetIsNoGC(emitNoGChelper(methHnd)); + // for the purpose of GC safepointing tail-calls are not real calls + id->idSetIsNoGC(isJump || emitNoGChelper(methHnd)); UNATIVE_OFFSET sz; @@ -16496,9 +16519,6 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) case IF_METHOD: case IF_METHPTR: { - // Assume we'll be recording this call - recCall = true; - // Get hold of the argument count and field Handle args = emitGetInsCDinfo(id); @@ -16525,12 +16545,6 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) addr = (BYTE*)id->idAddr()->iiaAddr; assert(addr != nullptr); - // Some helpers don't get recorded in GC tables - if (id->idIsNoGC()) - { - recCall = false; - } - // What kind of a call do we have here? if (id->idInsFmt() == IF_METHPTR) { @@ -16616,6 +16630,15 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) DONE_CALL: + // Assume we'll be recording this call + recCall = true; + + // Some helpers don't get recorded in GC tables + if (id->idIsNoGC()) + { + recCall = false; + } + /* We update the variable (not register) GC info before the call as the variables cannot be used by the call. Killing variables before the call helps with boundary conditions if the call is CORINFO_HELP_THROW - see bug 50029. @@ -16988,8 +17011,6 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) sz = sizeof(instrDesc); } - recCall = true; - goto DONE_CALL; default: diff --git a/src/coreclr/jit/gcencode.cpp b/src/coreclr/jit/gcencode.cpp index e21fe864984ef7..6e1fa8bab9b7cf 100644 --- a/src/coreclr/jit/gcencode.cpp +++ b/src/coreclr/jit/gcencode.cpp @@ -4471,8 +4471,8 @@ void GCInfo::gcMakeRegPtrTable( assert(call->u1.cdArgMask == 0 && call->cdArgCnt == 0); // Other than that, we just have to deal with the regmasks. - regMaskSmall gcrefRegMask = call->cdGCrefRegs & RBM_CALLEE_SAVED; - regMaskSmall byrefRegMask = call->cdByrefRegs & RBM_CALLEE_SAVED; + regMaskSmall gcrefRegMask = call->cdGCrefRegs & RBM_CALL_GC_REGS; + regMaskSmall byrefRegMask = call->cdByrefRegs & RBM_CALL_GC_REGS; assert((gcrefRegMask & byrefRegMask) == 0); diff --git a/src/coreclr/jit/jitgcinfo.h b/src/coreclr/jit/jitgcinfo.h index 02fd49cead9cb3..6c62a0816113a5 100644 --- a/src/coreclr/jit/jitgcinfo.h +++ b/src/coreclr/jit/jitgcinfo.h @@ -185,8 +185,8 @@ class GCInfo unsigned short rpdIsThis : 1; // is it the 'this' pointer unsigned short rpdCall : 1; // is this a true call site? unsigned short : 1; // Padding bit, so next two start on a byte boundary - unsigned short rpdCallGCrefRegs : CNT_CALLEE_SAVED; // Callee-saved registers containing GC pointers. - unsigned short rpdCallByrefRegs : CNT_CALLEE_SAVED; // Callee-saved registers containing byrefs. + unsigned short rpdCallGCrefRegs : CNT_CALL_GC_REGS; // Callee-saved and return registers containing GC pointers. + unsigned short rpdCallByrefRegs : CNT_CALL_GC_REGS; // Callee-saved and return registers containing byrefs. #ifndef JIT32_GCENCODER bool rpdIsCallInstr() diff --git a/src/coreclr/jit/regset.cpp b/src/coreclr/jit/regset.cpp index 12975850a404ba..dfc6df891bcf85 100644 --- a/src/coreclr/jit/regset.cpp +++ b/src/coreclr/jit/regset.cpp @@ -946,19 +946,16 @@ regNumber genRegArgNext(regNumber argReg) /***************************************************************************** * - * The following table determines the order in which callee-saved registers - * are encoded in GC information at call sites (perhaps among other things). - * In any case, they establish a mapping from ordinal callee-save reg "indices" to - * register numbers and corresponding bitmaps. + * The following table determines the order in which callee registers + * are encoded in GC information at call sites. */ -const regNumber raRegCalleeSaveOrder[] = {REG_CALLEE_SAVED_ORDER}; -const regMaskTP raRbmCalleeSaveOrder[] = {RBM_CALLEE_SAVED_ORDER}; +const regMaskTP raRbmCalleeSaveOrder[] = {RBM_CALL_GC_REGS_ORDER}; regMaskSmall genRegMaskFromCalleeSavedMask(unsigned short calleeSaveMask) { regMaskSmall res = 0; - for (int i = 0; i < CNT_CALLEE_SAVED; i++) + for (int i = 0; i < CNT_CALL_GC_REGS; i++) { if ((calleeSaveMask & ((regMaskTP)1 << i)) != 0) { diff --git a/src/coreclr/jit/target.h b/src/coreclr/jit/target.h index 06777fa9d5f709..d57d9baade310f 100644 --- a/src/coreclr/jit/target.h +++ b/src/coreclr/jit/target.h @@ -673,8 +673,7 @@ inline regMaskTP genRegMask(regNumber regNum, var_types type) * These arrays list the callee-saved register numbers (and bitmaps, respectively) for * the current architecture. */ -extern const regNumber raRegCalleeSaveOrder[CNT_CALLEE_SAVED]; -extern const regMaskTP raRbmCalleeSaveOrder[CNT_CALLEE_SAVED]; +extern const regMaskTP raRbmCalleeSaveOrder[CNT_CALL_GC_REGS]; // This method takes a "compact" bitset of the callee-saved registers, and "expands" it to a full register mask. regMaskSmall genRegMaskFromCalleeSavedMask(unsigned short); diff --git a/src/coreclr/jit/targetamd64.h b/src/coreclr/jit/targetamd64.h index 7e72da9cf2ccdc..1575177643eb1e 100644 --- a/src/coreclr/jit/targetamd64.h +++ b/src/coreclr/jit/targetamd64.h @@ -295,13 +295,14 @@ #define CNT_CALLEE_SAVED (5 + REG_ETW_FRAMED_EBP_COUNT) #define CNT_CALLEE_TRASH (9) #define CNT_CALLEE_ENREG (CNT_CALLEE_SAVED) + #define CNT_CALL_GC_REGS (CNT_CALLEE_SAVED + 2) #define CNT_CALLEE_SAVED_FLOAT (0) #define CNT_CALLEE_TRASH_FLOAT_INIT (16) #define CNT_CALLEE_TRASH_HIGHFLOAT (16) /* NOTE: Sync with variable name defined in compiler.h */ - #define REG_CALLEE_SAVED_ORDER REG_EBX,REG_ETW_FRAMED_EBP_LIST REG_R12,REG_R13,REG_R14,REG_R15 - #define RBM_CALLEE_SAVED_ORDER RBM_EBX,RBM_ETW_FRAMED_EBP_LIST RBM_R12,RBM_R13,RBM_R14,RBM_R15 + #define RBM_CALL_GC_REGS_ORDER RBM_EBX,RBM_ETW_FRAMED_EBP_LIST RBM_R12,RBM_R13,RBM_R14,RBM_R15,RBM_INTRET,RBM_INTRET_1 + #define RBM_CALL_GC_REGS (RBM_EBX|RBM_ETW_FRAMED_EBP|RBM_R12|RBM_R13|RBM_R14|RBM_R15|RBM_INTRET|RBM_INTRET_1) // For SysV we have more volatile registers so we do not save any callee saves for EnC. #define RBM_ENC_CALLEE_SAVED 0 @@ -309,13 +310,14 @@ #define CNT_CALLEE_SAVED (7 + REG_ETW_FRAMED_EBP_COUNT) #define CNT_CALLEE_TRASH (7) #define CNT_CALLEE_ENREG (CNT_CALLEE_SAVED) + #define CNT_CALL_GC_REGS (CNT_CALLEE_SAVED + 1) #define CNT_CALLEE_SAVED_FLOAT (10) #define CNT_CALLEE_TRASH_FLOAT_INIT (6) #define CNT_CALLEE_TRASH_HIGHFLOAT (16) /* NOTE: Sync with variable name defined in compiler.h */ - #define REG_CALLEE_SAVED_ORDER REG_EBX,REG_ESI,REG_EDI,REG_ETW_FRAMED_EBP_LIST REG_R12,REG_R13,REG_R14,REG_R15 - #define RBM_CALLEE_SAVED_ORDER RBM_EBX,RBM_ESI,RBM_EDI,RBM_ETW_FRAMED_EBP_LIST RBM_R12,RBM_R13,RBM_R14,RBM_R15 + #define RBM_CALL_GC_REGS_ORDER RBM_EBX,RBM_ESI,RBM_EDI,RBM_ETW_FRAMED_EBP_LIST RBM_R12,RBM_R13,RBM_R14,RBM_R15,RBM_INTRET + #define RBM_CALL_GC_REGS (RBM_EBX|RBM_ESI|RBM_EDI|RBM_ETW_FRAMED_EBP|RBM_R12|RBM_R13|RBM_R14|RBM_R15|RBM_INTRET) // Callee-preserved registers we always save and allow use of for EnC code, since there are quite few volatile registers. #define RBM_ENC_CALLEE_SAVED (RBM_RSI | RBM_RDI) diff --git a/src/coreclr/jit/targetarm.h b/src/coreclr/jit/targetarm.h index 0f56ebe1ce989a..46c41329b0565b 100644 --- a/src/coreclr/jit/targetarm.h +++ b/src/coreclr/jit/targetarm.h @@ -90,12 +90,13 @@ #define RBM_LOW_REGS (RBM_R0|RBM_R1|RBM_R2|RBM_R3|RBM_R4|RBM_R5|RBM_R6|RBM_R7) #define RBM_HIGH_REGS (RBM_R8|RBM_R9|RBM_R10|RBM_R11|RBM_R12|RBM_SP|RBM_LR|RBM_PC) - #define REG_CALLEE_SAVED_ORDER REG_R4,REG_R5,REG_R6,REG_R7,REG_R8,REG_R9,REG_R10,REG_R11 - #define RBM_CALLEE_SAVED_ORDER RBM_R4,RBM_R5,RBM_R6,RBM_R7,RBM_R8,RBM_R9,RBM_R10,RBM_R11 + #define RBM_CALL_GC_REGS_ORDER RBM_R4,RBM_R5,RBM_R6,RBM_R7,RBM_R8,RBM_R9,RBM_R10,RBM_R11,RBM_INTRET + #define RBM_CALL_GC_REGS (RBM_R4|RBM_R5|RBM_R6|RBM_R7|RBM_R8|RBM_R9|RBM_R10|RBM_R11|RBM_INTRET) #define CNT_CALLEE_SAVED (8) #define CNT_CALLEE_TRASH (6) #define CNT_CALLEE_ENREG (CNT_CALLEE_SAVED-1) + #define CNT_CALL_GC_REGS (CNT_CALLEE_SAVED+1) #define CNT_CALLEE_SAVED_FLOAT (16) #define CNT_CALLEE_TRASH_FLOAT (16) diff --git a/src/coreclr/jit/targetarm64.h b/src/coreclr/jit/targetarm64.h index 6d33d378bcd96e..8f5f1d68a6ef43 100644 --- a/src/coreclr/jit/targetarm64.h +++ b/src/coreclr/jit/targetarm64.h @@ -90,7 +90,8 @@ REG_R6, REG_R7, REG_R8, REG_R9, REG_R10, \ REG_R11, REG_R13, REG_R14, \ REG_R12, REG_R15, REG_IP0, REG_IP1, \ - REG_CALLEE_SAVED_ORDER, REG_LR + REG_R19,REG_R20,REG_R21,REG_R22,REG_R23,REG_R24,REG_R25,REG_R26,REG_R27,REG_R28,\ + REG_LR #define REG_VAR_ORDER_FLT REG_V16, REG_V17, REG_V18, REG_V19, \ REG_V20, REG_V21, REG_V22, REG_V23, \ @@ -101,12 +102,13 @@ REG_V12, REG_V13, REG_V14, REG_V15, \ REG_V3, REG_V2, REG_V1, REG_V0 - #define REG_CALLEE_SAVED_ORDER REG_R19,REG_R20,REG_R21,REG_R22,REG_R23,REG_R24,REG_R25,REG_R26,REG_R27,REG_R28 - #define RBM_CALLEE_SAVED_ORDER RBM_R19,RBM_R20,RBM_R21,RBM_R22,RBM_R23,RBM_R24,RBM_R25,RBM_R26,RBM_R27,RBM_R28 + #define RBM_CALL_GC_REGS_ORDER RBM_R19,RBM_R20,RBM_R21,RBM_R22,RBM_R23,RBM_R24,RBM_R25,RBM_R26,RBM_R27,RBM_R28,RBM_INTRET,RBM_INTRET_1 + #define RBM_CALL_GC_REGS (RBM_R19|RBM_R20|RBM_R21|RBM_R22|RBM_R23|RBM_R24|RBM_R25|RBM_R26|RBM_R27|RBM_R28|RBM_INTRET|RBM_INTRET_1) #define CNT_CALLEE_SAVED (11) #define CNT_CALLEE_TRASH (17) #define CNT_CALLEE_ENREG (CNT_CALLEE_SAVED-1) + #define CNT_CALL_GC_REGS (CNT_CALLEE_SAVED+2) #define CNT_CALLEE_SAVED_FLOAT (8) #define CNT_CALLEE_TRASH_FLOAT (24) diff --git a/src/coreclr/jit/targetloongarch64.h b/src/coreclr/jit/targetloongarch64.h index d27bffa3aa698f..dcc87944beb163 100644 --- a/src/coreclr/jit/targetloongarch64.h +++ b/src/coreclr/jit/targetloongarch64.h @@ -83,7 +83,7 @@ // REG_VAR_ORDER is: (CALLEE_TRASH & ~CALLEE_TRASH_NOGC), CALLEE_TRASH_NOGC, CALLEE_SAVED #define REG_VAR_ORDER REG_A0,REG_A1,REG_A2,REG_A3,REG_A4,REG_A5,REG_A6,REG_A7, \ REG_T0,REG_T1,REG_T2,REG_T3,REG_T4,REG_T5,REG_T6,REG_T7,REG_T8, \ - REG_CALLEE_SAVED_ORDER + REG_S0,REG_S1,REG_S2,REG_S3,REG_S4,REG_S5,REG_S6,REG_S7,REG_S8 #define REG_VAR_ORDER_FLT REG_F12,REG_F13,REG_F14,REG_F15,REG_F16,REG_F17,REG_F18,REG_F19, \ REG_F2,REG_F3,REG_F4,REG_F5,REG_F6,REG_F7,REG_F8,REG_F9,REG_F10, \ @@ -91,12 +91,13 @@ REG_F24,REG_F25,REG_F26,REG_F27,REG_F28,REG_F29,REG_F30,REG_F31, \ REG_F1,REG_F0 - #define REG_CALLEE_SAVED_ORDER REG_S0,REG_S1,REG_S2,REG_S3,REG_S4,REG_S5,REG_S6,REG_S7,REG_S8 - #define RBM_CALLEE_SAVED_ORDER RBM_S0,RBM_S1,RBM_S2,RBM_S3,RBM_S4,RBM_S5,RBM_S6,RBM_S7,RBM_S8 + #define RBM_CALL_GC_REGS_ORDER RBM_S0,RBM_S1,RBM_S2,RBM_S3,RBM_S4,RBM_S5,RBM_S6,RBM_S7,RBM_S8,RBM_INTRET,RBM_INTRET_1 + #define RBM_CALL_GC_REGS (RBM_S0|RBM_S1|RBM_S2|RBM_S3|RBM_S4|RBM_S5|RBM_S6|RBM_S7|RBM_S8|RBM_INTRET|RBM_INTRET_1) #define CNT_CALLEE_SAVED (10) //s0-s8,fp. #define CNT_CALLEE_TRASH (17) #define CNT_CALLEE_ENREG (CNT_CALLEE_SAVED-1) + #define CNT_CALL_GC_REGS (CNT_CALLEE_SAVED+2) #define CNT_CALLEE_SAVED_FLOAT (8) #define CNT_CALLEE_TRASH_FLOAT (24) diff --git a/src/coreclr/jit/targetriscv64.h b/src/coreclr/jit/targetriscv64.h index 33c1b0d4919096..a0d5254dc5d174 100644 --- a/src/coreclr/jit/targetriscv64.h +++ b/src/coreclr/jit/targetriscv64.h @@ -78,7 +78,7 @@ // REG_VAR_ORDER is: (CALLEE_TRASH & ~CALLEE_TRASH_NOGC), CALLEE_TRASH_NOGC, CALLEE_SAVED #define REG_VAR_ORDER REG_A0,REG_A1,REG_A2,REG_A3,REG_A4,REG_A5,REG_A6,REG_A7, \ REG_T0,REG_T1,REG_T2,REG_T3,REG_T4,REG_T5,REG_T6, \ - REG_CALLEE_SAVED_ORDER + REG_S1,REG_S2,REG_S3,REG_S4,REG_S5,REG_S6,REG_S7,REG_S8,REG_S9,REG_S10,REG_S11 #define REG_VAR_ORDER_FLT REG_F12,REG_F13,REG_F14,REG_F15,REG_F16,REG_F17,REG_F18,REG_F19, \ REG_F2,REG_F3,REG_F4,REG_F5,REG_F6,REG_F7,REG_F8,REG_F9,REG_F10, \ @@ -86,12 +86,13 @@ REG_F24,REG_F25,REG_F26,REG_F27,REG_F28,REG_F29,REG_F30,REG_F31, \ REG_F1,REG_F0 - #define REG_CALLEE_SAVED_ORDER REG_S1,REG_S2,REG_S3,REG_S4,REG_S5,REG_S6,REG_S7,REG_S8,REG_S9,REG_S10,REG_S11 - #define RBM_CALLEE_SAVED_ORDER RBM_S1,RBM_S2,RBM_S3,RBM_S4,RBM_S5,RBM_S6,RBM_S7,RBM_S8,RBM_S9,RBM_S10,RBM_S11 + #define RBM_CALL_GC_REGS_ORDER RBM_S1,RBM_S2,RBM_S3,RBM_S4,RBM_S5,RBM_S6,RBM_S7,RBM_S8,RBM_S9,RBM_S10,RBM_S11,RBM_INTRET,RBM_INTRET_1 + #define RBM_CALL_GC_REGS (RBM_S1|RBM_S2|RBM_S3|RBM_S4|RBM_S5|RBM_S6|RBM_S7|RBM_S8|RBM_S9|RBM_S10|RBM_S11|RBM_INTRET|RBM_INTRET_1) #define CNT_CALLEE_SAVED (11) #define CNT_CALLEE_TRASH (15) #define CNT_CALLEE_ENREG (CNT_CALLEE_SAVED-1) + #define CNT_CALL_GC_REGS (CNT_CALLEE_SAVED+2) #define CNT_CALLEE_SAVED_FLOAT (12) #define CNT_CALLEE_TRASH_FLOAT (20) diff --git a/src/coreclr/jit/targetx86.h b/src/coreclr/jit/targetx86.h index dfeb96ae9e977c..aa142f6cff0de4 100644 --- a/src/coreclr/jit/targetx86.h +++ b/src/coreclr/jit/targetx86.h @@ -145,12 +145,15 @@ #define MAX_VAR_ORDER_SIZE 6 // The order here is fixed: it must agree with an order assumed in eetwain... - #define REG_CALLEE_SAVED_ORDER REG_EDI,REG_ESI,REG_EBX,REG_EBP - #define RBM_CALLEE_SAVED_ORDER RBM_EDI,RBM_ESI,RBM_EBX,RBM_EBP + // NB: x86 GC decoder does not report return registers at call sites. + #define RBM_CALL_GC_REGS_ORDER RBM_EDI,RBM_ESI,RBM_EBX,RBM_EBP + #define RBM_CALL_GC_REGS (RBM_EDI|RBM_ESI|RBM_EBX|RBM_EBP) #define CNT_CALLEE_SAVED (4) #define CNT_CALLEE_TRASH (3) #define CNT_CALLEE_ENREG (CNT_CALLEE_SAVED-1) + // NB: x86 GC decoder does not report return registers at call sites. + #define CNT_CALL_GC_REGS (CNT_CALLEE_SAVED) #define CNT_CALLEE_SAVED_FLOAT (0) #define CNT_CALLEE_TRASH_FLOAT (6) diff --git a/src/coreclr/nativeaot/Runtime/TypeManager.cpp b/src/coreclr/nativeaot/Runtime/TypeManager.cpp index 77eda2c4c60405..96dc357136d90a 100644 --- a/src/coreclr/nativeaot/Runtime/TypeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/TypeManager.cpp @@ -29,9 +29,12 @@ TypeManager * TypeManager::Create(HANDLE osModule, void * pModuleHeader, void** if (pReadyToRunHeader->Signature != ReadyToRunHeaderConstants::Signature) return nullptr; - // Only the current major version is supported currently - ASSERT(pReadyToRunHeader->MajorVersion == ReadyToRunHeaderConstants::CurrentMajorVersion); - if (pReadyToRunHeader->MajorVersion != ReadyToRunHeaderConstants::CurrentMajorVersion) + // Only the current version is supported currently + ASSERT((pReadyToRunHeader->MajorVersion == ReadyToRunHeaderConstants::CurrentMajorVersion) && + (pReadyToRunHeader->MinorVersion == ReadyToRunHeaderConstants::CurrentMinorVersion)); + + if ((pReadyToRunHeader->MajorVersion != ReadyToRunHeaderConstants::CurrentMajorVersion) || + (pReadyToRunHeader->MinorVersion != ReadyToRunHeaderConstants::CurrentMinorVersion)) return nullptr; return new (nothrow) TypeManager(osModule, pReadyToRunHeader, pClasslibFunctions, nClasslibFunctions); diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp index 6759662d5683a3..3ccf59f611ecba 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp @@ -193,7 +193,13 @@ bool UnixNativeCodeManager::IsSafePoint(PTR_VOID pvAddress) codeOffset ); - return decoder.IsInterruptible(); + if (decoder.IsInterruptible()) + return true; + + if (decoder.IsInterruptibleSafePoint()) + return true; + + return false; } void UnixNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, @@ -226,6 +232,24 @@ void UnixNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, codeOffset ); + if (isActiveStackFrame) + { + // CONSIDER: We can optimize this by remembering the need to adjust in IsSafePoint and propagating into here. + // Or, better yet, maybe we should change the decoder to not require this adjustment. + // The scenario that adjustment tries to handle (fallthrough into BB with random liveness) + // does not seem possible. + if (!decoder.HasInterruptibleRanges()) + { + decoder = GcInfoDecoder( + GCInfoToken(gcInfo), + GcInfoDecoderFlags(DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), + codeOffset - 1 + ); + + assert(decoder.IsInterruptibleSafePoint()); + } + } + ICodeManagerFlags flags = (ICodeManagerFlags)0; if (executionAborted) flags = ICodeManagerFlags::ExecutionAborted; diff --git a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp index 0f2aa4f73669c2..b0eb24759bb526 100644 --- a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp @@ -415,7 +415,13 @@ bool CoffNativeCodeManager::IsSafePoint(PTR_VOID pvAddress) codeOffset ); - return decoder.IsInterruptible(); + if (decoder.IsInterruptible()) + return true; + + if (decoder.IsInterruptibleSafePoint()) + return true; + + return false; #else // Extract the necessary information from the info block header hdrInfo info; @@ -457,7 +463,25 @@ void CoffNativeCodeManager::EnumGcRefs(MethodInfo * pMethodInfo, GCInfoToken(gcInfo), GcInfoDecoderFlags(DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), codeOffset - ); + ); + + if (isActiveStackFrame) + { + // CONSIDER: We can optimize this by remembering the need to adjust in IsSafePoint and propagating into here. + // Or, better yet, maybe we should change the decoder to not require this adjustment. + // The scenario that adjustment tries to handle (fallthrough into BB with random liveness) + // does not seem possible. + if (!decoder.HasInterruptibleRanges()) + { + decoder = GcInfoDecoder( + GCInfoToken(gcInfo), + GcInfoDecoderFlags(DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), + codeOffset - 1 + ); + + assert(decoder.IsInterruptibleSafePoint()); + } + } if (!decoder.EnumerateLiveSlots( pRegisterSet, diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs index d01c636ef3b39e..b934e36719e8a2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs @@ -46,7 +46,6 @@ public SafePointOffset(int index, uint value) } } - private const int GCINFO_VERSION = 2; private const int MIN_GCINFO_VERSION_WITH_RETURN_KIND = 2; private const int MIN_GCINFO_VERSION_WITH_REV_PINVOKE_FRAME = 2; @@ -86,7 +85,7 @@ public GcInfo() { } /// /// based on GcInfoDecoder::GcInfoDecoder /// - public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion) + public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion, ushort minorVersion) { Offset = offset; _gcInfoTypes = new GcInfoTypes(machine); @@ -101,7 +100,7 @@ public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion) SizeOfEditAndContinuePreservedArea = 0xffffffff; ReversePInvokeFrameStackSlot = -1; - Version = ReadyToRunVersionToGcInfoVersion(majorVersion); + Version = ReadyToRunVersionToGcInfoVersion(majorVersion, minorVersion); int bitOffset = offset * 8; ParseHeaderFlags(image, ref bitOffset); @@ -388,9 +387,17 @@ private List EnumerateInterruptibleRanges(byte[] image, int /// GcInfo version is 1 up to ReadyTorun version 1.x. /// GcInfo version is current from ReadyToRun version 2.0 /// - private int ReadyToRunVersionToGcInfoVersion(int readyToRunMajorVersion) + private int ReadyToRunVersionToGcInfoVersion(int readyToRunMajorVersion, int readyToRunMinorVersion) { - return (readyToRunMajorVersion == 1) ? 1 : GCINFO_VERSION; + if (readyToRunMajorVersion == 1) + return 1; + + // R2R 2.0+ uses GCInfo v2 + // R2R 9.2+ uses GCInfo v3 + if (readyToRunMajorVersion < 9 || (readyToRunMajorVersion == 9 && readyToRunMinorVersion < 2)) + return 2; + + return 3; } private List> GetLiveSlotsAtSafepoints(byte[] image, ref int bitOffset) diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs index 790c7142475b54..982d678cdce12e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs @@ -492,12 +492,17 @@ private void EnsureInitialized() int gcInfoOffset = _readyToRunReader.CompositeReader.GetOffset(GcInfoRva); if (_readyToRunReader.Machine == Machine.I386) { - _gcInfo = new x86.GcInfo(_readyToRunReader.Image, gcInfoOffset, _readyToRunReader.Machine, _readyToRunReader.ReadyToRunHeader.MajorVersion); + _gcInfo = new x86.GcInfo(_readyToRunReader.Image, gcInfoOffset); } else { // Arm, Arm64, LoongArch64 and RISCV64 use the same GcInfo format as Amd64 - _gcInfo = new Amd64.GcInfo(_readyToRunReader.Image, gcInfoOffset, _readyToRunReader.Machine, _readyToRunReader.ReadyToRunHeader.MajorVersion); + _gcInfo = new Amd64.GcInfo( + _readyToRunReader.Image, + gcInfoOffset, + _readyToRunReader.Machine, + _readyToRunReader.ReadyToRunHeader.MajorVersion, + _readyToRunReader.ReadyToRunHeader.MinorVersion); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs index 197fcfb1ee0324..ddb44a1d312ad1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/x86/GcInfo.cs @@ -20,7 +20,7 @@ public GcInfo() { } /// /// based on GCDump::DumpGCTable /// - public GcInfo(byte[] image, int offset, Machine machine, ushort majorVersion) + public GcInfo(byte[] image, int offset) { Offset = offset; diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 3bb10ac6c6bb64..ff096681a07421 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -5555,7 +5555,7 @@ UINT32 ReadyToRunJitManager::JitTokenToGCInfoVersion(const METHODTOKEN& MethodTo READYTORUN_HEADER * header = JitTokenToReadyToRunInfo(MethodToken)->GetReadyToRunHeader(); - return GCInfoToken::ReadyToRunVersionToGcInfoVersion(header->MajorVersion); + return GCInfoToken::ReadyToRunVersionToGcInfoVersion(header->MajorVersion, header->MinorVersion); } PTR_RUNTIME_FUNCTION ReadyToRunJitManager::JitTokenToRuntimeFunction(const METHODTOKEN& MethodToken) diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 3f28d4b7431530..42a6776123f1d4 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -843,7 +843,22 @@ bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo, dwRelOffset ); - return gcInfoDecoder.IsInterruptible(); + if (gcInfoDecoder.IsInterruptible()) + return true; + + if (InterruptibleSafePointsEnabled() && gcInfoDecoder.IsInterruptibleSafePoint()) + return true; + + return false; +} + +bool EECodeManager::InterruptibleSafePointsEnabled() +{ + LIMITED_METHOD_CONTRACT; + + // zero initialized + static ConfigDWORD interruptibleCallSitesEnabled; + return interruptibleCallSitesEnabled.val(CLRConfig::INTERNAL_InterruptibleCallSites) != 0; } #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) @@ -1421,7 +1436,8 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, DECODE_INTERRUPTIBILITY, curOffs ); - if(!_gcInfoDecoder.IsInterruptible()) + if(!_gcInfoDecoder.IsInterruptible() && + !(InterruptibleSafePointsEnabled() && _gcInfoDecoder.IsInterruptibleSafePoint())) { // This must be the offset after a call #ifdef _DEBUG @@ -1441,7 +1457,8 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, DECODE_INTERRUPTIBILITY, curOffs ); - _ASSERTE(_gcInfoDecoder.IsInterruptible()); + _ASSERTE(_gcInfoDecoder.IsInterruptible() || + (InterruptibleSafePointsEnabled() && _gcInfoDecoder.IsInterruptibleSafePoint())); } #endif @@ -1530,6 +1547,24 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD, curOffs ); + if ((flags & ActiveStackFrame) != 0) + { + // CONSIDER: We can optimize this by remembering the need to adjust in IsSafePoint and propagating into here. + // Or, better yet, maybe we should change the decoder to not require this adjustment. + // The scenario that adjustment tries to handle (fallthrough into BB with random liveness) + // does not seem possible. + if (!gcInfoDecoder.HasInterruptibleRanges()) + { + gcInfoDecoder = GcInfoDecoder( + gcInfoToken, + GcInfoDecoderFlags(DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG), + curOffs - 1 + ); + + _ASSERTE((InterruptibleSafePointsEnabled() && gcInfoDecoder.IsInterruptibleSafePoint())); + } + } + if (!gcInfoDecoder.EnumerateLiveSlots( pRD, reportScratchSlots, diff --git a/src/coreclr/vm/gccover.cpp b/src/coreclr/vm/gccover.cpp index 44ccfd4c46bec7..70ab39ea681b02 100644 --- a/src/coreclr/vm/gccover.cpp +++ b/src/coreclr/vm/gccover.cpp @@ -37,7 +37,7 @@ MethodDesc* AsMethodDesc(size_t addr); static PBYTE getTargetOfCall(PBYTE instrPtr, PCONTEXT regs, PBYTE*nextInstr); #if defined(TARGET_ARM) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) -static void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID codeStart); +static void replaceSafePointInstructionWithGcStressInstr(GcInfoDecoder* decoder, UINT32 safePointOffset, LPVOID codeStart); static bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 stopOffset, LPVOID codeStart); #endif @@ -544,11 +544,16 @@ void GCCoverageInfo::SprinkleBreakpoints( _ASSERTE(len > 0); _ASSERTE(len <= (size_t)(codeEnd-cur)); + // For non-fully interruptible code, we want to at least + // patch the return sites after the call instructions. + // Specially so that we can verify stack-walking through the call site via a simulated hijack. + // We would need to know the return kind of the callee, so this may not always be possible. switch(instructionType) { case InstructionType::Call_IndirectUnconditional: #ifdef TARGET_AMD64 - if(safePointDecoder.IsSafePoint((UINT32)(cur + len - codeStart + regionOffsetAdj))) + if(!(EECodeManager::InterruptibleSafePointsEnabled() && safePointDecoder.AreSafePointsInterruptible()) && + safePointDecoder.IsSafePoint((UINT32)(cur + len - codeStart + regionOffsetAdj))) #endif { *(cur + writeableOffset) = INTERRUPT_INSTR_CALL; // return value. May need to protect @@ -559,7 +564,8 @@ void GCCoverageInfo::SprinkleBreakpoints( if(fGcStressOnDirectCalls.val(CLRConfig::INTERNAL_GcStressOnDirectCalls)) { #ifdef TARGET_AMD64 - if(safePointDecoder.IsSafePoint((UINT32)(cur + len - codeStart + regionOffsetAdj))) + if(!(EECodeManager::InterruptibleSafePointsEnabled() && safePointDecoder.AreSafePointsInterruptible()) && + safePointDecoder.IsSafePoint((UINT32)(cur + len - codeStart + regionOffsetAdj))) #endif { PBYTE nextInstr; @@ -589,10 +595,8 @@ void GCCoverageInfo::SprinkleBreakpoints( ReplaceInstrAfterCall(cur + writeableOffset, prevDirectCallTargetMD); } - // For fully interruptible code, we end up whacking every instruction - // to INTERRUPT_INSTR. For non-fully interruptible code, we end - // up only touching the call instructions (specially so that we - // can really do the GC on the instruction just after the call). + // For fully interruptible locations, we end up whacking every instruction + // to INTERRUPT_INSTR. size_t dwRelOffset = (cur - codeStart) + regionOffsetAdj; _ASSERTE(FitsIn(dwRelOffset)); if (codeMan->IsGcSafe(&codeInfo, static_cast(dwRelOffset))) @@ -689,7 +693,7 @@ enum #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED -void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID pGCCover) +void replaceSafePointInstructionWithGcStressInstr(GcInfoDecoder* decoder, UINT32 safePointOffset, LPVOID pGCCover) { PCODE pCode = NULL; IJitManager::MethodRegionInfo *ptr = &(((GCCoverageInfo*)pGCCover)->methodRegion); @@ -712,6 +716,28 @@ void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID PBYTE instrPtr = (BYTE*)PCODEToPINSTR(pCode); + // if this is an interruptible safe point, just replace it with an interrupt instr and we are done. + if (EECodeManager::InterruptibleSafePointsEnabled() && decoder->AreSafePointsInterruptible()) + { + // The instruction about to be replaced cannot already be a gcstress instruction + _ASSERTE(!IsGcCoverageInterruptInstruction(instrPtr)); + + ExecutableWriterHolder instrPtrWriterHolder(instrPtr, sizeof(DWORD)); +#if defined(TARGET_ARM) + size_t instrLen = GetARMInstructionLength(instrPtr); + + if (instrLen == 2) + *((WORD*)instrPtrWriterHolder.GetRW()) = INTERRUPT_INSTR; + else + { + *((DWORD*)instrPtrWriterHolder.GetRW()) = INTERRUPT_INSTR_32; + } +#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) + *((DWORD*)instrPtrWriterHolder.GetRW()) = INTERRUPT_INSTR; +#endif // TARGET_XXXX_ + return; + } + // For code sequences of the type // BL func1 // BL func2 // Safe point 1 diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index 855aa24f627f6b..66347d8bf8d28c 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -363,14 +363,18 @@ GcInfoDecoder::GcInfoDecoder( } #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED - if(flags & (DECODE_GC_LIFETIMES)) + if(flags & (DECODE_GC_LIFETIMES | DECODE_INTERRUPTIBILITY)) { if(m_NumSafePoints) { - m_SafePointIndex = FindSafePoint(m_InstructionOffset); + // Safepoints are encoded with a -1 adjustment + // DECODE_GC_LIFETIMES adjusts the offset accordingly, but DECODE_INTERRUPTIBILITY does not + // adjust here + UINT32 offset = flags & DECODE_INTERRUPTIBILITY ? m_InstructionOffset - 1 : m_InstructionOffset; + m_SafePointIndex = FindSafePoint(offset); } } - else if(flags & (DECODE_FOR_RANGES_CALLBACK | DECODE_INTERRUPTIBILITY)) + else if(flags & DECODE_FOR_RANGES_CALLBACK) { // Note that normalization as a code offset can be different than // normalization as code length @@ -381,7 +385,13 @@ GcInfoDecoder::GcInfoDecoder( } #endif - if(!m_IsInterruptible && (flags & DECODE_INTERRUPTIBILITY)) + // we do not support both DECODE_INTERRUPTIBILITY and DECODE_FOR_RANGES_CALLBACK at the same time + // as both will enumerate and consume interruptible ranges. + _ASSERTE((flags & (DECODE_INTERRUPTIBILITY | DECODE_FOR_RANGES_CALLBACK)) != + (DECODE_INTERRUPTIBILITY | DECODE_FOR_RANGES_CALLBACK)); + + _ASSERTE(!m_IsInterruptible); + if(flags & DECODE_INTERRUPTIBILITY) { EnumerateInterruptibleRanges(&SetIsInterruptibleCB, this); } @@ -393,6 +403,28 @@ bool GcInfoDecoder::IsInterruptible() return m_IsInterruptible; } +bool GcInfoDecoder::HasInterruptibleRanges() +{ + _ASSERTE(m_Flags & (DECODE_INTERRUPTIBILITY | DECODE_GC_LIFETIMES)); + return m_NumInterruptibleRanges > 0; +} + +bool GcInfoDecoder::IsSafePoint() +{ + _ASSERTE(m_Flags & (DECODE_INTERRUPTIBILITY | DECODE_GC_LIFETIMES)); + return m_SafePointIndex != m_NumSafePoints; +} + +bool GcInfoDecoder::AreSafePointsInterruptible() +{ + return m_Version >= 3; +} + +bool GcInfoDecoder::IsInterruptibleSafePoint() +{ + return IsSafePoint() && AreSafePointsInterruptible(); +} + bool GcInfoDecoder::HasMethodDescGenericsInstContext() { _ASSERTE( m_Flags & DECODE_GENERICS_INST_CONTEXT ); @@ -515,7 +547,7 @@ void GcInfoDecoder::EnumerateSafePoints(EnumerateSafePointsCallback *pCallback, offset--; #endif - pCallback(offset, hCallback); + pCallback(this, offset, hCallback); } } #endif diff --git a/src/tests/JIT/Directed/lifetime/lifetime2.csproj b/src/tests/JIT/Directed/lifetime/lifetime2.csproj index 7913879515ebfc..0e79bac317c9b6 100644 --- a/src/tests/JIT/Directed/lifetime/lifetime2.csproj +++ b/src/tests/JIT/Directed/lifetime/lifetime2.csproj @@ -10,6 +10,7 @@ None True True + true