diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index cd6b359c9f5d76..649c467d7ec5a8 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -2128,6 +2128,16 @@ class ICorStaticInfo // Quick check whether the method is a jit intrinsic. Returns the same value as getMethodAttribs(ftn) & CORINFO_FLG_INTRINSIC, except faster. virtual bool isIntrinsic(CORINFO_METHOD_HANDLE ftn) = 0; + // Check whether the value type instance pointer ('this') passed to a value + // type instance method 'ftn' could possibly escape the method when it is + // called. + // + // A 'false' result means the runtime can guarantee, based on the ECMA-335 + // augment III.1.7.7, that the instance pointer does not escape 'ftn'. A + // 'true' (conservative) result means no such guarantee can be made. + // + virtual bool canValueClassInstancePointerEscape(CORINFO_METHOD_HANDLE ftn) = 0; + // Notify EE about intent to rely on given MethodInfo in the current method // EE returns false if we're not allowed to do so and the methodinfo may change. // Example of a scenario addressed by notifyMethodInfoUsage: diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 6c0c39f71f0293..21dfb633498260 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -24,6 +24,9 @@ bool isIntrinsic( CORINFO_METHOD_HANDLE ftn) override; +bool canValueClassInstancePointerEscape( + CORINFO_METHOD_HANDLE ftn) override; + bool notifyMethodInfoUsage( CORINFO_METHOD_HANDLE ftn) override; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 08a3bcde5744d6..f2bb33c42ef99e 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 5bb9b1e5-9941-4762-a195-351b6a736588 */ - 0x5bb9b1e5, - 0x9941, - 0x4762, - {0xa1, 0x95, 0x35, 0x1b, 0x6a, 0x73, 0x65, 0x88} +constexpr GUID JITEEVersionIdentifier = { /* 12e9a95b-fa7c-43ed-a339-fbbc6e74bffa */ + 0x12e9a95b, + 0xfa7c, + 0x43ed, + {0xa3, 0x39, 0xfb, 0xbc, 0x6e, 0x74, 0xbf, 0xfa} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index b3ccde2acebc8f..fe8fa377332a9a 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -5,6 +5,7 @@ // To regenerate run the gen script in src/coreclr/tools/Common/JitInterface/ThunkGenerator // and follow the instructions in docs/project/updating-jitinterface.md DEF_CLR_API(isIntrinsic) +DEF_CLR_API(canValueClassInstancePointerEscape) DEF_CLR_API(notifyMethodInfoUsage) DEF_CLR_API(getMethodAttribs) DEF_CLR_API(setMethodAttribs) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 3f40e71e6bbb26..5291f7a8c8d287 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -21,6 +21,15 @@ bool WrapICorJitInfo::isIntrinsic( return temp; } +bool WrapICorJitInfo::canValueClassInstancePointerEscape( + CORINFO_METHOD_HANDLE ftn) +{ + API_ENTER(canValueClassInstancePointerEscape); + bool temp = wrapHnd->canValueClassInstancePointerEscape(ftn); + API_LEAVE(canValueClassInstancePointerEscape); + return temp; +} + bool WrapICorJitInfo::notifyMethodInfoUsage( CORINFO_METHOD_HANDLE ftn) { diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 59de8d694033d9..f645fac21fe2f7 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4085,6 +4085,7 @@ class Compiler BR_REMOVE_BUT_NOT_NARROW, // remove effects, return original source tree BR_DONT_REMOVE, // check if removal is possible, return copy source tree BR_DONT_REMOVE_WANT_TYPE_HANDLE, // check if removal is possible, return type handle tree + BR_MAKE_LOCAL_COPY // revise box to copy to temp local and return local's address }; GenTree* gtTryRemoveBoxUpstreamEffects(GenTree* tree, BoxRemovalOptions options = BR_REMOVE_AND_NARROW); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index f050c5eeeee885..a1357523a02804 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -16757,9 +16757,10 @@ GenTree* Compiler::gtTryRemoveBoxUpstreamEffects(GenTree* op, BoxRemovalOptions Statement* allocStmt = box->gtDefStmtWhenInlinedBoxValue; Statement* copyStmt = box->gtCopyStmtWhenInlinedBoxValue; - JITDUMP("gtTryRemoveBoxUpstreamEffects: %s of BOX (valuetype)" + JITDUMP("gtTryRemoveBoxUpstreamEffects: %s to %s of BOX (valuetype)" " [%06u] (assign/newobj " FMT_STMT " copy " FMT_STMT "\n", - (options == BR_DONT_REMOVE) ? "checking if it is possible" : "attempting", dspTreeID(op), + (options == BR_DONT_REMOVE) ? "checking if it is possible" : "attempting", + (options == BR_MAKE_LOCAL_COPY) ? "make local unboxed version" : "remove side effects", dspTreeID(op), allocStmt->GetID(), copyStmt->GetID()); // If we don't recognize the form of the store, bail. @@ -16835,6 +16836,65 @@ GenTree* Compiler::gtTryRemoveBoxUpstreamEffects(GenTree* op, BoxRemovalOptions return nullptr; } + // Handle case where we are optimizing the box into a local copy + if (options == BR_MAKE_LOCAL_COPY) + { + // Drill into the box to get at the box temp local and the box type + GenTree* boxTemp = box->BoxOp(); + assert(boxTemp->IsLocal()); + const unsigned boxTempLcl = boxTemp->AsLclVar()->GetLclNum(); + assert(lvaTable[boxTempLcl].lvType == TYP_REF); + CORINFO_CLASS_HANDLE boxClass = lvaTable[boxTempLcl].lvClassHnd; + assert(boxClass != nullptr); + + // Verify that the copy has the expected shape + // (store_blk|store_ind (add (boxTempLcl, ptr-size))) + // + // The shape here is constrained to the patterns we produce + // over in impImportAndPushBox for the inlined box case. + // + GenTree* copyDstAddr = copy->AsIndir()->Addr(); + if (!copyDstAddr->OperIs(GT_ADD)) + { + JITDUMP("Unexpected copy dest address tree\n"); + return nullptr; + } + + GenTree* copyDstAddrOp1 = copyDstAddr->AsOp()->gtOp1; + if (!copyDstAddrOp1->OperIs(GT_LCL_VAR) || (copyDstAddrOp1->AsLclVarCommon()->GetLclNum() != boxTempLcl)) + { + JITDUMP("Unexpected copy dest address 1st addend\n"); + return nullptr; + } + + GenTree* copyDstAddrOp2 = copyDstAddr->AsOp()->gtOp2; + if (!copyDstAddrOp2->IsIntegralConst(TARGET_POINTER_SIZE)) + { + JITDUMP("Unexpected copy dest address 2nd addend\n"); + return nullptr; + } + + // Screening checks have all passed. Do the transformation. + // + // Retype the box temp to be a struct + JITDUMP("Retyping box temp V%02u to struct %s\n", boxTempLcl, eeGetClassName(boxClass)); + lvaTable[boxTempLcl].lvType = TYP_UNDEF; + const bool isUnsafeValueClass = false; + lvaSetStruct(boxTempLcl, boxClass, isUnsafeValueClass); + + // Remove the newobj and store to box temp + JITDUMP("Bashing NEWOBJ [%06u] to NOP\n", dspTreeID(boxLclDef)); + boxLclDef->gtBashToNOP(); + + // Update the copy from the value to be boxed to the box temp + copy->AsIndir()->Addr() = gtNewLclVarAddrNode(boxTempLcl, TYP_BYREF); + + // Return the address of the now-struct typed box temp + GenTree* retValue = gtNewLclVarAddrNode(boxTempLcl, TYP_BYREF); + + return retValue; + } + // If the copy is a struct copy, make sure we know how to isolate any source side effects. GenTree* copySrc = copy->Data(); diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 47db1fa2801116..4bd51ca89d39d8 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -9656,8 +9656,62 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, CORINFO_SIG_INFO unboxedEntrySig; info.compCompHnd->getMethodSig(unboxedEntryMethod, &unboxedEntrySig); - bool canUseUnboxedEntry = true; + bool canUseUnboxedEntry = true; + bool madeLocalCopy = false; + GenTree* boxTypeHandle = nullptr; + bool const needsClassTypeArg = + unboxedEntrySig.hasTypeArg() && + (((SIZE_T)dcInfo->tokenLookupContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS); + + // If the 'this' object is a local box, and the unboxed entry provably keeps the receiver + // from escaping, replace the heap allocation with a stack-local copy of the boxed value. + // + if (thisObj->IsBoxedValue() && + !info.compCompHnd->canValueClassInstancePointerEscape(unboxedEntryMethod)) + { + // If the shared unboxed entry needs the exact class as a type arg, recover the type + // handle statically from the box before it is bashed; the stack copy won't carry a + // method table at runtime. + // + bool haveTypeArg = true; + if (needsClassTypeArg) + { + boxTypeHandle = gtTryRemoveBoxUpstreamEffects(thisObj, BR_DONT_REMOVE_WANT_TYPE_HANDLE); + haveTypeArg = (boxTypeHandle != nullptr); + } + + GenTree* localCopyThis = + haveTypeArg ? gtTryRemoveBoxUpstreamEffects(thisObj, BR_MAKE_LOCAL_COPY) : nullptr; + + if (localCopyThis != nullptr) + { + JITDUMP("Success! invoking unboxed entry point on local copy\n"); + assert(localCopyThis->IsLclVarAddr()); + assert(thisObj == thisArg->GetEarlyNode()); + thisArg->SetEarlyNode(localCopyThis); + + // We may end up inlining this call, so the local copy must be marked as "aliased", + // making sure the inlinee importer will know when to spill references to its value. + // + lvaGetDesc(localCopyThis->AsLclFld())->lvHasLdAddrOp = true; + madeLocalCopy = true; + +#if FEATURE_TAILCALL_OPT + if (call->IsImplicitTailCall()) + { + // We just introduced a new address taken local variable, so clear the + // implicit tail call flag. + // + JITDUMP("Clearing the implicit tail call flag\n"); + call->gtCallMoreFlags &= ~GTF_CALL_M_IMPLICIT_TAILCALL; + } +#endif // FEATURE_TAILCALL_OPT + } + } + + // Compute the instantiation parameter the shared unboxed entry may need. + // if (unboxedEntrySig.hasTypeArg()) { if (((SIZE_T)dcInfo->tokenLookupContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD) @@ -9668,10 +9722,17 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, instParam = getLookupTree(dcInfo->pInstParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle); JITDUMP("revising call to invoke unboxed entry with additional method desc arg\n"); } + else if (madeLocalCopy) + { + // The exact class handle is known statically from the box. + // + assert(needsClassTypeArg && (boxTypeHandle != nullptr)); + instParam = boxTypeHandle; + JITDUMP("revising call to invoke unboxed entry with additional method table arg from box\n"); + } else { - assert(((SIZE_T)dcInfo->tokenLookupContext & CORINFO_CONTEXTFLAGS_MASK) == - CORINFO_CONTEXTFLAGS_CLASS); + assert(needsClassTypeArg); // Get the method table from the boxed object. // @@ -9699,16 +9760,20 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, if (canUseUnboxedEntry) { - // Rewrite the call to target the unboxed entry on the box payload. Keep the heap box, - // since the callee may return an interior managed pointer into it; object stack allocation - // can later promote the box to the stack when escape analysis proves the receiver does not - // escape. - // - GenTree* const payloadOffset = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); - GenTree* const boxPayload = - gtNewOperNode(GT_ADD, TYP_BYREF, thisArg->GetEarlyNode(), payloadOffset); + if (!madeLocalCopy) + { + // Rewrite the call to target the unboxed entry on the box payload. Keep the heap box, + // since the callee may return an interior managed pointer into it; object stack + // allocation can later promote the box to the stack when escape analysis proves the + // receiver does not escape. + // + GenTree* const payloadOffset = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); + GenTree* const boxPayload = + gtNewOperNode(GT_ADD, TYP_BYREF, thisArg->GetEarlyNode(), payloadOffset); + + thisArg->SetEarlyNode(boxPayload); + } - thisArg->SetEarlyNode(boxPayload); call->gtCallMethHnd = unboxedEntryMethod; INDEBUG(call->gtCallDebugFlags |= GTF_CALL_MD_UNBOXED); @@ -9724,6 +9789,11 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call, derivedMethod = unboxedEntryMethod; pDerivedResolvedToken = dcInfo->pUnboxedResolvedToken; + + if (madeLocalCopy) + { + Metrics.DevirtualizedCallRemovedBox++; + } Metrics.DevirtualizedCallUnboxedEntry++; } } diff --git a/src/coreclr/jit/jitmetadatalist.h b/src/coreclr/jit/jitmetadatalist.h index 7fc8865617b9dc..db90f3a2932f41 100644 --- a/src/coreclr/jit/jitmetadatalist.h +++ b/src/coreclr/jit/jitmetadatalist.h @@ -58,6 +58,7 @@ JITMETADATAMETRIC(ImporterBranchFold, int, 0) JITMETADATAMETRIC(ImporterSwitchFold, int, 0) JITMETADATAMETRIC(DevirtualizedCall, int, 0) JITMETADATAMETRIC(DevirtualizedCallUnboxedEntry, int, 0) +JITMETADATAMETRIC(DevirtualizedCallRemovedBox, int, 0) JITMETADATAMETRIC(GDV, int, 0) JITMETADATAMETRIC(ClassGDV, int, 0) JITMETADATAMETRIC(MethodGDV, int, 0) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 329abbf03b7f78..5a0c37b14e523b 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -6,10 +6,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Text; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; +using System.Text; using System.Text.Unicode; using Internal.Text; @@ -1082,6 +1083,36 @@ private bool isIntrinsic(CORINFO_METHOD_STRUCT_* ftn) return method.IsIntrinsic || HardwareIntrinsicHelpers.IsHardwareIntrinsic(method); } + private bool canValueClassInstancePointerEscape(CORINFO_METHOD_STRUCT_* ftn) + { + MethodDesc method = HandleToObject(ftn); + + Debug.Assert(!method.Signature.IsStatic); + + if (method.GetTypicalMethodDefinition() is not EcmaMethod ecmaMethod) + return true; + + // ECMA augment III.1.7.7 allows making this escaping assumption + // based on RefSafetyRules and UnscopedRef attributes. + if (!ModuleOptsIntoRefSafetyRules(ecmaMethod.Module, 11)) + return true; + + return ecmaMethod.HasCustomAttribute("System.Diagnostics.CodeAnalysis", "UnscopedRefAttribute"); + } + + private static bool ModuleOptsIntoRefSafetyRules(EcmaModule module, int minVersion) + { + MetadataReader reader = module.MetadataReader; + CustomAttributeHandle handle = reader.GetCustomAttributeHandle( + reader.GetModuleDefinition().GetCustomAttributes(), + "System.Runtime.CompilerServices", "RefSafetyRulesAttribute"); + if (handle.IsNil) + return false; + + CustomAttributeValue value = reader.GetCustomAttribute(handle).DecodeValue(new CustomAttributeTypeProvider(module)); + return value.FixedArguments.Length == 1 && value.FixedArguments[0].Value is int version && version >= minVersion; + } + private uint getMethodAttribsInternal(MethodDesc method) { CorInfoFlag result = 0; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index ef00dcf0188b1b..5be5fd01b69eb9 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -21,6 +21,7 @@ private struct ICorJitInfoCallbacks static ICorJitInfoCallbacks() { s_callbacks.isIntrinsic = &_isIntrinsic; + s_callbacks.canValueClassInstancePointerEscape = &_canValueClassInstancePointerEscape; s_callbacks.notifyMethodInfoUsage = &_notifyMethodInfoUsage; s_callbacks.getMethodAttribs = &_getMethodAttribs; s_callbacks.setMethodAttribs = &_setMethodAttribs; @@ -205,6 +206,7 @@ static ICorJitInfoCallbacks() } public delegate* unmanaged isIntrinsic; + public delegate* unmanaged canValueClassInstancePointerEscape; public delegate* unmanaged notifyMethodInfoUsage; public delegate* unmanaged getMethodAttribs; public delegate* unmanaged setMethodAttribs; @@ -408,6 +410,21 @@ private static byte _isIntrinsic(IntPtr thisHandle, IntPtr* ppException, CORINFO } } + [UnmanagedCallersOnly] + private static byte _canValueClassInstancePointerEscape(IntPtr thisHandle, IntPtr* ppException, CORINFO_METHOD_STRUCT_* ftn) + { + var _this = GetThis(thisHandle); + try + { + return _this.canValueClassInstancePointerEscape(ftn) ? (byte)1 : (byte)0; + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + return default; + } + } + [UnmanagedCallersOnly] private static byte _notifyMethodInfoUsage(IntPtr thisHandle, IntPtr* ppException, CORINFO_METHOD_STRUCT_* ftn) { diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 8a6ab34f947c4b..f478dc79563f29 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -176,6 +176,7 @@ ICorJitInfo::errorTrapFunction,void* FUNCTIONS bool isIntrinsic( CORINFO_METHOD_HANDLE ftn ); + bool canValueClassInstancePointerEscape( CORINFO_METHOD_HANDLE ftn ); bool notifyMethodInfoUsage( CORINFO_METHOD_HANDLE ftn ); uint32_t getMethodAttribs( CORINFO_METHOD_HANDLE ftn ); void setMethodAttribs( CORINFO_METHOD_HANDLE ftn, CorInfoMethodRuntimeFlags attribs ); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AttributePresenceFilterNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AttributePresenceFilterNode.cs index f6f85fb14fe7df..4bf6941c0335d6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AttributePresenceFilterNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AttributePresenceFilterNode.cs @@ -80,6 +80,15 @@ private List GetCustomAttributeEntries() addToTable = true; } } + else if (customAttributeTypeNamespace == "System.Diagnostics.CodeAnalysis") + { + // Consulted by the JIT (via canValueClassInstancePointerEscape) to reason + // about escaping receivers of value type instance methods. + if (customAttributeTypeName == "UnscopedRefAttribute") + { + addToTable = true; + } + } if (!addToTable) continue; diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 3a0514e49ad2a7..75648980ac0a7b 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -12,6 +12,7 @@ struct JitInterfaceCallbacks { bool (* isIntrinsic)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn); + bool (* canValueClassInstancePointerEscape)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn); bool (* notifyMethodInfoUsage)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn); uint32_t (* getMethodAttribs)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn); void (* setMethodAttribs)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, CorInfoMethodRuntimeFlags attribs); @@ -217,6 +218,15 @@ class JitInterfaceWrapper : public ICorJitInfo return temp; } + virtual bool canValueClassInstancePointerEscape( + CORINFO_METHOD_HANDLE ftn) +{ + CorInfoExceptionClass* pException = nullptr; + bool temp = _callbacks->canValueClassInstancePointerEscape(_thisHandle, &pException, ftn); + if (pException != nullptr) throw pException; + return temp; +} + virtual bool notifyMethodInfoUsage( CORINFO_METHOD_HANDLE ftn) { diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index e44cf227fbd412..1616fc79d2916f 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -174,6 +174,7 @@ LWM(DoesFieldBelongToClass, DLDL, DWORD) DENSELWM(SigInstHandleMap, DWORDLONG) LWM(GetWasmTypeSymbol, Agnostic_GetWasmTypeSymbol, DWORDLONG) LWM(GetAddressAlignment, DWORDLONG, DWORD) +LWM(CanValueClassInstancePointerEscape, DWORDLONG, DWORD) LWM(GetWasmWellKnownGlobals, DWORD, Agnostic_CORINFO_WASM_WELLKNOWN_GLOBALS) #undef LWM diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index db89aaed33f8a7..ed39fdc6cc2207 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -7788,6 +7788,28 @@ bool MethodContext::IsStringContentEqual(LightWeightMap* prev, Lig } } +void MethodContext::recCanValueClassInstancePointerEscape(CORINFO_METHOD_HANDLE ftn, bool result) +{ + if (CanValueClassInstancePointerEscape == nullptr) + CanValueClassInstancePointerEscape = new LightWeightMap(); + + DWORDLONG key = CastHandle(ftn); + DWORD value = result ? 1 : 0; + CanValueClassInstancePointerEscape->Add(key, value); + DEBUG_REC(dmpCanValueClassInstancePointerEscape(key, value)); +} +void MethodContext::dmpCanValueClassInstancePointerEscape(DWORDLONG key, DWORD value) +{ + printf("CanValueClassInstancePointerEscape key ftn-%016" PRIX64 ", value res-%u", key, value); +} +bool MethodContext::repCanValueClassInstancePointerEscape(CORINFO_METHOD_HANDLE ftn) +{ + DWORDLONG key = CastHandle(ftn); + DWORD value = LookupByKeyOrMiss(CanValueClassInstancePointerEscape, key, ": key %016" PRIX64 "", key); + DEBUG_REP(dmpCanValueClassInstancePointerEscape(key, value)); + return value != 0; +} + bool g_debugRec = false; bool g_debugRep = false; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index 5ca689f36ce384..32eb7aef721b6d 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -122,6 +122,10 @@ class MethodContext void dmpIsIntrinsic(DWORDLONG key, DWORD value); bool repIsIntrinsic(CORINFO_METHOD_HANDLE ftn); + void recCanValueClassInstancePointerEscape(CORINFO_METHOD_HANDLE ftn, bool result); + void dmpCanValueClassInstancePointerEscape(DWORDLONG key, DWORD value); + bool repCanValueClassInstancePointerEscape(CORINFO_METHOD_HANDLE ftn); + void recNotifyMethodInfoUsage(CORINFO_METHOD_HANDLE ftn, bool result); void dmpNotifyMethodInfoUsage(DWORDLONG key, DWORD value); bool repNotifyMethodInfoUsage(CORINFO_METHOD_HANDLE ftn); @@ -1224,6 +1228,7 @@ enum mcPackets Packet_GetAwaitReturnCall = 238, Packet_GetAddressAlignment = 239, Packet_GetWasmWellKnownGlobals = 240, + Packet_CanValueClassInstancePointerEscape = 241, }; void SetDebugDumpVariables(); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 9744fc70aaa470..9a708cd3f7b420 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -28,6 +28,14 @@ bool interceptor_ICJI::isIntrinsic(CORINFO_METHOD_HANDLE ftn) return temp; } +bool interceptor_ICJI::canValueClassInstancePointerEscape(CORINFO_METHOD_HANDLE ftn) +{ + mc->cr->AddCall("canValueClassInstancePointerEscape"); + bool temp = original_ICorJitInfo->canValueClassInstancePointerEscape(ftn); + mc->recCanValueClassInstancePointerEscape(ftn, temp); + return temp; +} + bool interceptor_ICJI::notifyMethodInfoUsage(CORINFO_METHOD_HANDLE ftn) { mc->cr->AddCall("notifyMethodInfoUsage"); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index 53166c4d1538d6..931dad3bb9cce0 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -19,6 +19,13 @@ bool interceptor_ICJI::isIntrinsic( return original_ICorJitInfo->isIntrinsic(ftn); } +bool interceptor_ICJI::canValueClassInstancePointerEscape( + CORINFO_METHOD_HANDLE ftn) +{ + mcs->AddCall("canValueClassInstancePointerEscape"); + return original_ICorJitInfo->canValueClassInstancePointerEscape(ftn); +} + bool interceptor_ICJI::notifyMethodInfoUsage( CORINFO_METHOD_HANDLE ftn) { diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index 458287f1d32ac2..023658229bbf74 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -18,6 +18,12 @@ bool interceptor_ICJI::isIntrinsic( return original_ICorJitInfo->isIntrinsic(ftn); } +bool interceptor_ICJI::canValueClassInstancePointerEscape( + CORINFO_METHOD_HANDLE ftn) +{ + return original_ICorJitInfo->canValueClassInstancePointerEscape(ftn); +} + bool interceptor_ICJI::notifyMethodInfoUsage( CORINFO_METHOD_HANDLE ftn) { diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index e71c1dd6071c4a..e5d65f8e046f0a 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -31,6 +31,12 @@ bool MyICJI::isIntrinsic(CORINFO_METHOD_HANDLE ftn) return jitInstance->mc->repIsIntrinsic(ftn); } +bool MyICJI::canValueClassInstancePointerEscape(CORINFO_METHOD_HANDLE ftn) +{ + jitInstance->mc->cr->AddCall("canValueClassInstancePointerEscape"); + return jitInstance->mc->repCanValueClassInstancePointerEscape(ftn); +} + bool MyICJI::notifyMethodInfoUsage(CORINFO_METHOD_HANDLE ftn) { jitInstance->mc->cr->AddCall("notifyMethodInfoUsage"); diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index a4c20b1d0273e8..57ce1fece148c4 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -1226,6 +1226,42 @@ BOOL Module::IsRuntimeMarshallingEnabled() return hr != S_OK; } +BOOL Module::OptsIntoRefSafetyRulesV11() +{ + CONTRACTL + { + THROWS; + if (OptsIntoRefSafetyRulesV11Cached()) GC_NOTRIGGER; else GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END + + if (OptsIntoRefSafetyRulesV11Cached()) + { + return !!(m_dwPersistedFlags & REF_SAFETY_RULES_V11); + } + + bool optsIn = false; + + const void *pVal; + ULONG cbVal; + if (GetCustomAttribute(TokenFromRid(1, mdtModule), WellKnownAttribute::RefSafetyRules, &pVal, &cbVal) == S_OK) + { + // RefSafetyRulesAttribute(int version): a 2-byte prolog followed by the int32 version argument. + CustomAttributeParser cap(pVal, cbVal); + INT32 version; + if (SUCCEEDED(cap.SkipProlog()) && SUCCEEDED(cap.GetI4(&version)) && version >= 11) + { + optsIn = true; + } + } + + InterlockedOr((LONG*)&m_dwPersistedFlags, REF_SAFETY_RULES_V11_IS_CACHED | + (optsIn ? REF_SAFETY_RULES_V11 : 0)); + + return optsIn; +} + //--------------------------------------------------------------------------------------- // // Returns managed representation of the module (Module or ModuleBuilder). diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 076bfe497c5c06..cde7da2d65d1c5 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -693,6 +693,11 @@ class Module : public ModuleBase RUNTIME_MARSHALLING_ENABLED = 0x00010000, SKIP_TYPE_VALIDATION = 0x00020000, + + //If the RefSafetyRules >= v11 setting has been cached + REF_SAFETY_RULES_V11_IS_CACHED = 0x00040000, + //If this module opted into RefSafetyRules version 11 or above + REF_SAFETY_RULES_V11 = 0x00080000, }; Volatile m_dwTransientFlags; @@ -1594,6 +1599,18 @@ class Module : public ModuleBase return (m_dwPersistedFlags & RUNTIME_MARSHALLING_ENABLED_IS_CACHED); } + //----------------------------------------------------------------------------------------- + // If true, this module opted into the ECMA-335 augment tied to RefSafetyRulesAttribute with a + // version of at least 11 (i.e. RefSafetyRulesAttribute(version) with version >= 11). + //----------------------------------------------------------------------------------------- + BOOL OptsIntoRefSafetyRulesV11(); + + BOOL OptsIntoRefSafetyRulesV11Cached() + { + LIMITED_METHOD_CONTRACT; + return (m_dwPersistedFlags & REF_SAFETY_RULES_V11_IS_CACHED); + } + protected: // For reflection emit modules we set this flag when we emit the attribute, and always consider // the current setting of the flag to be set. diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 5bb833b15d8527..ff2f309983a63c 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -6431,6 +6431,35 @@ bool CEEInfo::isIntrinsic(CORINFO_METHOD_HANDLE ftn) return ret; } +bool CEEInfo::canValueClassInstancePointerEscape(CORINFO_METHOD_HANDLE ftn) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + bool result = true; + + JIT_TO_EE_TRANSITION(); + + _ASSERTE(ftn != NULL); + + MethodDesc* pMD = GetMethod(ftn); + _ASSERTE(!pMD->IsStatic()); + + // ECMA augment III.1.7.7 allows making this escaping assumption based on + // RefSafetyRules and UnscopedRef attributes. + if (pMD->GetModule()->OptsIntoRefSafetyRulesV11()) + { + result = (pMD->GetCustomAttribute(WellKnownAttribute::UnscopedRef, NULL, NULL) == S_OK); + } + + EE_TO_JIT_TRANSITION(); + + return result; +} + bool CEEInfo::notifyMethodInfoUsage(CORINFO_METHOD_HANDLE ftn) { CONTRACTL { diff --git a/src/coreclr/vm/wellknownattributes.h b/src/coreclr/vm/wellknownattributes.h index df72f546c95bae..498aae64d1f71f 100644 --- a/src/coreclr/vm/wellknownattributes.h +++ b/src/coreclr/vm/wellknownattributes.h @@ -38,6 +38,8 @@ enum class WellKnownAttribute : DWORD UnsafeAccessorAttribute, UnsafeAccessorTypeAttribute, ExtendedLayoutAttribute, + UnscopedRef, + RefSafetyRules, CountOfWellKnownAttributes }; @@ -145,6 +147,12 @@ inline const char *GetWellKnownAttributeName(WellKnownAttribute attribute) case WellKnownAttribute::ExtendedLayoutAttribute: ret = "System.Runtime.InteropServices.ExtendedLayoutAttribute"; break; + case WellKnownAttribute::UnscopedRef: + ret = "System.Diagnostics.CodeAnalysis.UnscopedRefAttribute"; + break; + case WellKnownAttribute::RefSafetyRules: + ret = "System.Runtime.CompilerServices.RefSafetyRulesAttribute"; + break; case WellKnownAttribute::CountOfWellKnownAttributes: default: ret = nullptr; @@ -163,6 +171,7 @@ inline const char *GetWellKnownAttributeName(WellKnownAttribute attribute) attribute == WellKnownAttribute::ThreadStatic || attribute == WellKnownAttribute::ParamArray || attribute == WellKnownAttribute::DefaultMember + || attribute == WellKnownAttribute::UnscopedRef || strncmp(prefix, ret, ARRAY_SIZE(prefix) - 1) == 0; _ASSERTE(readyToRunAware); #endif // _DEBUG