Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
JIT: optimize calls on boxed objects
Browse files Browse the repository at this point in the history
For calls with boxed this objects, invoke the unboxed entry point if available
and modify the box to copy the original value or struct to a local. Pass the
address of this local as the first argument to the unboxed entry point.

Defer for cases where the unboxed entry point requires an extra type parameter.

This may enable subsequent inlining of the method, and if the method has other
boxed parameters, may enable undoing those boxes as well.

The jit is not yet as proficient at eliminating the struct copies, but they
are present with or without this change, and some may even be required when the
methods mutate the fields of `this`.

Closes #14213.
  • Loading branch information
AndyAyersMS committed Oct 30, 2017
1 parent 2f4acd6 commit 50bc237
Show file tree
Hide file tree
Showing 16 changed files with 344 additions and 61 deletions.
5 changes: 5 additions & 0 deletions src/ToolBox/superpmi/superpmi-shared/icorjitinfoimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ CORINFO_METHOD_HANDLE resolveVirtualMethod(CORINFO_METHOD_HANDLE virtualMethod,
CORINFO_CLASS_HANDLE implementingClass,
CORINFO_CONTEXT_HANDLE ownerType);

// Get the unboxed entry point for a method, if possible.
CORINFO_METHOD_HANDLE getUnboxedEntry(
CORINFO_METHOD_HANDLE ftn,
bool* requiresInstMethodTableArg /* OUT */);

// Given T, return the type of the default EqualityComparer<T>.
// Returns null if the type can't be determined exactly.
CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE elemType);
Expand Down
1 change: 1 addition & 0 deletions src/ToolBox/superpmi/superpmi-shared/lwmlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ LWM(GetThreadTLSIndex, DWORD, DLD)
LWM(GetTokenTypeAsHandle, GetTokenTypeAsHandleValue, DWORDLONG)
LWM(GetTypeForBox, DWORDLONG, DWORDLONG)
LWM(GetTypeForPrimitiveValueClass, DWORDLONG, DWORD)
LWM(GetUnboxedEntry, DWORDLONG, DLD);
LWM(GetUnBoxHelper, DWORDLONG, DWORD)
LWM(GetUnmanagedCallConv, DWORDLONG, DWORD)
LWM(GetVarArgsHandle, GetVarArgsHandleValue, DLDL)
Expand Down
49 changes: 49 additions & 0 deletions src/ToolBox/superpmi/superpmi-shared/methodcontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3018,6 +3018,55 @@ CORINFO_METHOD_HANDLE MethodContext::repResolveVirtualMethod(CORINFO_METHOD_HAND
return (CORINFO_METHOD_HANDLE)result;
}

void MethodContext::recGetUnboxedEntry(CORINFO_METHOD_HANDLE ftn,
bool *requiresInstMethodTableArg,
CORINFO_METHOD_HANDLE result)
{
if (GetUnboxedEntry == nullptr)
{
GetUnboxedEntry = new LightWeightMap<DWORDLONG, DLD>();
}

DWORDLONG key = (DWORDLONG) ftn;
DLD value;
value.A = (DWORDLONG) result;
if (requiresInstMethodTableArg != nullptr)
{
value.B = (DWORD) *requiresInstMethodTableArg ? 1 : 0;
}
else
{
value.B = 0;
}
GetUnboxedEntry->Add(key, value);
DEBUG_REC(dmpGetUnboxedEntry(key, value));
}

void MethodContext::dmpGetUnboxedEntry(DWORDLONG key, DLD value)
{
printf("GetUnboxedEntry ftn-%016llX, result-%016llX, requires-inst-%u",
key, value.A, value.B);
}

CORINFO_METHOD_HANDLE MethodContext::repGetUnboxedEntry(CORINFO_METHOD_HANDLE ftn,
bool* requiresInstMethodTableArg)
{
DWORDLONG key = (DWORDLONG)ftn;

AssertCodeMsg(GetUnboxedEntry != nullptr, EXCEPTIONCODE_MC, "No GetUnboxedEntry map for %016llX", key);
AssertCodeMsg(GetUnboxedEntry->GetIndex(key) != -1, EXCEPTIONCODE_MC, "Didn't find %016llX", key);
DLD result = GetUnboxedEntry->Get(key);

DEBUG_REP(dmpGetUnboxedEntry(key, result));

if (requiresInstMethodTableArg != nullptr)
{
*requiresInstMethodTableArg = (result.B == 1);
}

return (CORINFO_METHOD_HANDLE)(result.A);
}

void MethodContext::recGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result)
{
if (GetDefaultEqualityComparerClass == nullptr)
Expand Down
10 changes: 9 additions & 1 deletion src/ToolBox/superpmi/superpmi-shared/methodcontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,13 @@ class MethodContext
CORINFO_CLASS_HANDLE implClass,
CORINFO_CONTEXT_HANDLE ownerType);

void recGetUnboxedEntry(CORINFO_METHOD_HANDLE ftn,
bool* requiresInstMethodTableArg,
CORINFO_METHOD_HANDLE result);
void dmpGetUnboxedEntry(DWORDLONG key, DLD value);
CORINFO_METHOD_HANDLE repGetUnboxedEntry(CORINFO_METHOD_HANDLE ftn,
bool* requiresInstMethodTableArg);

void recGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls, CORINFO_CLASS_HANDLE result);
void dmpGetDefaultEqualityComparerClass(DWORDLONG key, DWORDLONG value);
CORINFO_CLASS_HANDLE repGetDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls);
Expand Down Expand Up @@ -1269,7 +1276,7 @@ class MethodContext
};

// ********************* Please keep this up-to-date to ease adding more ***************
// Highest packet number: 164
// Highest packet number: 165
// *************************************************************************************
enum mcPackets
{
Expand Down Expand Up @@ -1384,6 +1391,7 @@ enum mcPackets
Packet_GetTokenTypeAsHandle = 89,
Packet_GetTypeForBox = 90,
Packet_GetTypeForPrimitiveValueClass = 91,
Packet_GetUnboxedEntry = 165, // Added 10/26/17
Packet_GetUnBoxHelper = 92,
Packet_GetReadyToRunHelper = 150, // Added 10/10/2014
Packet_GetReadyToRunDelegateCtorHelper = 157, // Added 3/30/2016
Expand Down
15 changes: 15 additions & 0 deletions src/ToolBox/superpmi/superpmi-shim-collector/icorjitinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,21 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HAND
return result;
}

// Get the unboxed entry point for a method, if possible.
CORINFO_METHOD_HANDLE interceptor_ICJI::getUnboxedEntry(CORINFO_METHOD_HANDLE ftn, bool* requiresInstMethodTableArg)
{
mc->cr->AddCall("getUnboxedEntry");
bool localRequiresInstMethodTableArg = false;
CORINFO_METHOD_HANDLE result =
original_ICorJitInfo->getUnboxedEntry(ftn, &localRequiresInstMethodTableArg);
mc->recGetUnboxedEntry(ftn, &localRequiresInstMethodTableArg, result);
if (requiresInstMethodTableArg != nullptr)
{
*requiresInstMethodTableArg = localRequiresInstMethodTableArg;
}
return result;
}

// Given T, return the type of the default EqualityComparer<T>.
// Returns null if the type can't be determined exactly.
CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls)
Expand Down
7 changes: 7 additions & 0 deletions src/ToolBox/superpmi/superpmi-shim-counter/icorjitinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HAND
return original_ICorJitInfo->resolveVirtualMethod(virtualMethod, implementingClass, ownerType);
}

// Get the unboxed entry point for a method, if possible.
CORINFO_METHOD_HANDLE interceptor_ICJI::getUnboxedEntry(CORINFO_METHOD_HANDLE ftn, bool* requiresInstMethodTableArg)
{
mcs->AddCall("getUnboxedEntry");
return original_ICorJitInfo->getUnboxedEntry(ftn, requiresInstMethodTableArg);
}

// Given T, return the type of the default EqualityComparer<T>.
// Returns null if the type can't be determined exactly.
CORINFO_CLASS_HANDLE interceptor_ICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls)
Expand Down
6 changes: 6 additions & 0 deletions src/ToolBox/superpmi/superpmi-shim-simple/icorjitinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ void interceptor_ICJI::getMethodVTableOffset(CORINFO_METHOD_HANDLE method,
original_ICorJitInfo->getMethodVTableOffset(method, offsetOfIndirection, offsetAfterIndirection, isRelative);
}

// Get the unboxed entry point for a method, if possible.
CORINFO_METHOD_HANDLE interceptor_ICJI::getUnboxedEntry(CORINFO_METHOD_HANDLE ftn, bool* requiresInstMethodTableArg)
{
return original_ICorJitInfo->getUnboxedEntry(ftn, requiresInstMethodTableArg);
}

// Find the virtual method in implementingClass that overrides virtualMethod.
// Return null if devirtualization is not possible.
CORINFO_METHOD_HANDLE interceptor_ICJI::resolveVirtualMethod(CORINFO_METHOD_HANDLE virtualMethod,
Expand Down
9 changes: 9 additions & 0 deletions src/ToolBox/superpmi/superpmi/icorjitinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ CORINFO_METHOD_HANDLE MyICJI::resolveVirtualMethod(CORINFO_METHOD_HANDLE virtua
return result;
}

// Get the unboxed entry point for a method, if possible.
CORINFO_METHOD_HANDLE MyICJI::getUnboxedEntry(CORINFO_METHOD_HANDLE ftn, bool* requiresInstMethodTableArg)
{
jitInstance->mc->cr->AddCall("getUnboxedEntry");
CORINFO_METHOD_HANDLE result =
jitInstance->mc->repGetUnboxedEntry(ftn, requiresInstMethodTableArg);
return result;
}

// Given T, return the type of the default EqualityComparer<T>.
// Returns null if the type can't be determined exactly.
CORINFO_CLASS_HANDLE MyICJI::getDefaultEqualityComparerClass(CORINFO_CLASS_HANDLE cls)
Expand Down
16 changes: 11 additions & 5 deletions src/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,11 @@ TODO: Talk about initializing strutures before use
#define SELECTANY extern __declspec(selectany)
#endif

SELECTANY const GUID JITEEVersionIdentifier = { /* 860df660-2919-440a-ad6d-865f014e5360 */
0x860df660,
0x2919,
0x440a,
{ 0xad, 0x6d, 0x86, 0x5f, 0x01, 0x4e, 0x53, 0x60 }
SELECTANY const GUID JITEEVersionIdentifier = { /* b6af83a1-ca48-46c6-b7b0-5d7d6a79a5c5 */
0xb6af83a1,
0xca48,
0x46c6,
{0xb7, 0xb0, 0x5d, 0x7d, 0x6a, 0x79, 0xa5, 0xc5}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -2094,6 +2094,12 @@ class ICorStaticInfo
CORINFO_CONTEXT_HANDLE ownerType = NULL /* IN */
) = 0;

// Get the unboxed entry point for a method, if possible.
virtual CORINFO_METHOD_HANDLE getUnboxedEntry(
CORINFO_METHOD_HANDLE ftn,
bool* requiresInstMethodTableArg = NULL /* OUT */
) = 0;

// Given T, return the type of the default EqualityComparer<T>.
// Returns null if the type can't be determined exactly.
virtual CORINFO_CLASS_HANDLE getDefaultEqualityComparerClass(
Expand Down
3 changes: 2 additions & 1 deletion src/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2283,7 +2283,8 @@ class Compiler
BR_REMOVE_AND_NARROW, // remove effects, minimize remaining work, return possibly narrowed source tree
BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE, // remove effects and minimize remaining work, return type handle tree
BR_REMOVE_BUT_NOT_NARROW, // remove effects, return original source tree
BR_DONT_REMOVE // just check if removal is possible
BR_DONT_REMOVE, // just check if removal is possible
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
93 changes: 77 additions & 16 deletions src/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13150,27 +13150,21 @@ GenTreePtr Compiler::gtFoldExprSpecial(GenTreePtr tree)

GenTree* Compiler::gtTryRemoveBoxUpstreamEffects(GenTree* op, BoxRemovalOptions options)
{
JITDUMP("gtTryRemoveBoxUpstreamEffects called for [%06u]\n", dspTreeID(op));
assert(op->IsBoxedValue());

// grab related parts for the optimization
GenTree* asgStmt = op->gtBox.gtAsgStmtWhenInlinedBoxValue;
GenTreeBox* box = op->AsBox();
GenTree* asgStmt = box->gtAsgStmtWhenInlinedBoxValue;
GenTree* copyStmt = box->gtCopyStmtWhenInlinedBoxValue;

assert(asgStmt->gtOper == GT_STMT);
GenTree* copyStmt = op->gtBox.gtCopyStmtWhenInlinedBoxValue;
assert(copyStmt->gtOper == GT_STMT);

#ifdef DEBUG
if (verbose)
{
printf("\n%s to remove side effects of BOX (valuetype)\n",
options == BR_DONT_REMOVE ? "Checking if it is possible" : "Attempting");
gtDispTree(op);
printf("\nWith assign\n");
gtDispTree(asgStmt);
printf("\nAnd copy\n");
gtDispTree(copyStmt);
}
#endif
JITDUMP("gtTryRemoveBoxUpstreamEffects: %s to %s of BOX (valuetype)"
" [%06u] (assign/newobj [%06u] copy [%06u])\n",
(options == BR_DONT_REMOVE) ? "checking if it is possible" : "attempting",
(options == BR_MAKE_LOCAL_COPY) ? "make local unboxed version" : "remove side effects", dspTreeID(op),
dspTreeID(asgStmt), dspTreeID(copyStmt));

// If we don't recognize the form of the assign, bail.
GenTree* asg = asgStmt->gtStmt.gtStmtExpr;
Expand Down Expand Up @@ -13205,7 +13199,7 @@ GenTree* Compiler::gtTryRemoveBoxUpstreamEffects(GenTree* op, BoxRemovalOptions
if (newobjArgs == nullptr)
{
assert(newobjCall->IsHelperCall(this, CORINFO_HELP_READYTORUN_NEW));
JITDUMP("bailing; newobj via R2R helper\n");
JITDUMP(" bailing; newobj via R2R helper\n");
return nullptr;
}

Expand Down Expand Up @@ -13241,6 +13235,73 @@ 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 copyDst has the expected shape
// (blk|obj|ind (add (boxTempLcl, ptr-size)))
//
// The shape here is constrained to the patterns we produce
// over in impImportAndPushBox for the inlined box case.
GenTree* copyDst = copy->gtOp.gtOp1;

if (!copyDst->OperIs(GT_BLK, GT_IND, GT_OBJ))
{
JITDUMP("Unexpected copy dest operator %s\n", GenTree::OpName(copyDst->gtOper));
return nullptr;
}

GenTree* copyDstAddr = copyDst->gtOp.gtOp1;
if (copyDstAddr->OperGet() != GT_ADD)
{
JITDUMP("Unexpected copy dest address tree\n");
return nullptr;
}

GenTree* copyDstAddrOp1 = copyDstAddr->gtOp.gtOp1;
if ((copyDstAddrOp1->OperGet() != GT_LCL_VAR) || (copyDstAddrOp1->gtLclVarCommon.gtLclNum != boxTempLcl))
{
JITDUMP("Unexpected copy dest address 1st addend\n");
return nullptr;
}

GenTree* copyDstAddrOp2 = copyDstAddr->gtOp.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 assigment to box temp
JITDUMP("Bashing NEWOBJ [%06u] to NOP\n", dspTreeID(asg));
asg->gtBashToNOP();

// Update the copy from the value to be boxed to the box temp
GenTree* newDst = gtNewOperNode(GT_ADDR, TYP_BYREF, gtNewLclvNode(boxTempLcl, TYP_STRUCT));
copyDst->gtOp.gtOp1 = newDst;

// Return the address of the now-struct typed box temp
GenTree* retValue = gtNewOperNode(GT_ADDR, TYP_BYREF, gtNewLclvNode(boxTempLcl, TYP_STRUCT));

return retValue;
}

// If the copy is a struct copy, make sure we know how to isolate
// any source side effects.
GenTree* copySrc = copy->gtOp.gtOp2;
Expand Down
Loading

0 comments on commit 50bc237

Please sign in to comment.