Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@

#include <minipal/guid.h>

constexpr GUID JITEEVersionIdentifier = { /* 65743063-e8fa-41d4-9496-c436974c00f5 */
0x65743063,
0xe8fa,
0x41d4,
{0x94, 0x96, 0xc4, 0x36, 0x97, 0x4c, 0x00, 0xf5}
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
1 change: 1 addition & 0 deletions src/coreclr/jit/ICorJitInfo_names_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -4077,6 +4077,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);
Expand Down
64 changes: 62 additions & 2 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16733,9 +16733,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());
Comment thread
jakobbotsch marked this conversation as resolved.

// If we don't recognize the form of the store, bail.
Expand Down Expand Up @@ -16811,6 +16812,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();

Expand Down
94 changes: 82 additions & 12 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
//
Expand Down Expand Up @@ -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);

Expand All @@ -9724,6 +9789,11 @@ void Compiler::impTransformDevirtualizedCall(GenTreeCall* call,

derivedMethod = unboxedEntryMethod;
pDerivedResolvedToken = dcInfo->pUnboxedResolvedToken;

if (madeLocalCopy)
{
Metrics.DevirtualizedCallRemovedBox++;
}
Metrics.DevirtualizedCallUnboxedEntry++;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/jitmetadatalist.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 32 additions & 1 deletion src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<TypeDesc> 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;
Expand Down
17 changes: 17 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -205,6 +206,7 @@ static ICorJitInfoCallbacks()
}

public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_METHOD_STRUCT_*, byte> isIntrinsic;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_METHOD_STRUCT_*, byte> canValueClassInstancePointerEscape;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_METHOD_STRUCT_*, byte> notifyMethodInfoUsage;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_METHOD_STRUCT_*, uint> getMethodAttribs;
public delegate* unmanaged<IntPtr, IntPtr*, CORINFO_METHOD_STRUCT_*, CorInfoMethodRuntimeFlags, void> setMethodAttribs;
Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
Loading
Loading