Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: Support delegate GDV guards in loop cloning #75140

Merged
merged 12 commits into from
Oct 20, 2022
8 changes: 5 additions & 3 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7380,17 +7380,17 @@ class Compiler
Statement* stmt;
const unsigned loopNum;
const bool cloneForArrayBounds;
const bool cloneForTypeTests;
const bool cloneForGDVTests;
LoopCloneVisitorInfo(LoopCloneContext* context,
unsigned loopNum,
Statement* stmt,
bool cloneForArrayBounds,
bool cloneForTypeTests)
bool cloneForGDVTests)
: context(context)
, stmt(nullptr)
, loopNum(loopNum)
, cloneForArrayBounds(cloneForArrayBounds)
, cloneForTypeTests(cloneForTypeTests)
, cloneForGDVTests(cloneForGDVTests)
{
}
};
Expand All @@ -7404,6 +7404,8 @@ class Compiler
fgWalkResult optCanOptimizeByLoopCloning(GenTree* tree, LoopCloneVisitorInfo* info);
bool optObtainLoopCloningOpts(LoopCloneContext* context);
bool optIsLoopClonable(unsigned loopInd);
bool optCheckLoopCloningGDVTestProfitable(GenTreeOp* guard, LoopCloneVisitorInfo* info);
bool optIsHandleOrIndirOfHandle(GenTree* tree, GenTreeFlags handleType);

bool optLoopCloningEnabled();

Expand Down
76 changes: 2 additions & 74 deletions src/coreclr/jit/indirectcalltransformer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,9 @@ class IndirectCallTransformer

class GuardedDevirtualizationTransformer final : public Transformer
{
unsigned m_targetLclNum;

public:
GuardedDevirtualizationTransformer(Compiler* compiler, BasicBlock* block, Statement* stmt)
: Transformer(compiler, block, stmt), m_targetLclNum(BAD_VAR_NUM), returnTemp(BAD_VAR_NUM)
: Transformer(compiler, block, stmt), returnTemp(BAD_VAR_NUM)
{
}

Expand Down Expand Up @@ -607,25 +605,10 @@ class IndirectCallTransformer
// which case the check will be moved into the success case of
// a previous GDV and thus may not execute when we hit the cold
// path.
// TODO-GDV: Consider duplicating the store at the end of the
// cold case for the previous GDV. Then we can reuse the target
// if the second check of a chained GDV fails.
bool reuseTarget = (origCall->gtCallMoreFlags & GTF_CALL_M_GUARDED_DEVIRT_CHAIN) == 0;
if (origCall->IsVirtualVtable())
{
GenTree* tarTree = compiler->fgExpandVirtualVtableCallTarget(origCall);

if (reuseTarget)
{
m_targetLclNum = compiler->lvaGrabTemp(false DEBUGARG("guarded devirt call target temp"));

GenTree* asgTree = compiler->gtNewTempAssign(m_targetLclNum, tarTree);
Statement* asgStmt = compiler->fgNewStmtFromTree(asgTree, stmt->GetDebugInfo());
compiler->fgInsertStmtAtEnd(checkBlock, asgStmt);

tarTree = compiler->gtNewLclvNode(m_targetLclNum, TYP_I_IMPL);
}

CORINFO_METHOD_HANDLE methHnd = guardedInfo->guardedMethodHandle;
CORINFO_CONST_LOOKUP lookup;
compiler->info.compCompHnd->getFunctionEntryPoint(methHnd, &lookup);
Expand All @@ -635,35 +618,12 @@ class IndirectCallTransformer
}
else
{
// Reusing the call target for delegates is more
// complicated. Essentially we need to do the
// transformation done in LowerDelegateInvoke by converting
// the call to CT_INDIRECT and reusing the target address.
// We will do that transformation in CreateElse, but here
// we need to stash the target.
CLANG_FORMAT_COMMENT_ANCHOR;
#ifdef TARGET_ARM
// Not impossible to support, but would additionally
// require us to load the wrapper delegate cell when
// expanding.
reuseTarget &= (origCall->gtCallMoreFlags & GTF_CALL_M_WRAPPER_DELEGATE_INV) == 0;
#endif

GenTree* offset =
compiler->gtNewIconNode((ssize_t)compiler->eeGetEEInfo()->offsetOfDelegateFirstTarget,
TYP_I_IMPL);
GenTree* tarTree = compiler->gtNewOperNode(GT_ADD, TYP_BYREF, thisTree, offset);
tarTree = compiler->gtNewIndir(TYP_I_IMPL, tarTree);

if (reuseTarget)
{
m_targetLclNum = compiler->lvaGrabTemp(false DEBUGARG("guarded devirt call target temp"));

GenTree* asgTree = compiler->gtNewTempAssign(m_targetLclNum, tarTree);
Statement* asgStmt = compiler->fgNewStmtFromTree(asgTree, stmt->GetDebugInfo());
compiler->fgInsertStmtAtEnd(checkBlock, asgStmt);
tarTree = compiler->gtNewLclvNode(m_targetLclNum, TYP_I_IMPL);
}
tarTree->gtFlags |= GTF_IND_INVARIANT;

CORINFO_METHOD_HANDLE methHnd = guardedInfo->guardedMethodHandle;
CORINFO_CONST_LOOKUP lookup;
Expand Down Expand Up @@ -970,38 +930,6 @@ class IndirectCallTransformer
newStmt->SetRootNode(assign);
}

if (m_targetLclNum != BAD_VAR_NUM)
{
if (call->IsVirtualVtable())
{
// We already loaded the target once for the check, so reuse it from the temp.
call->gtControlExpr = compiler->gtNewLclvNode(m_targetLclNum, TYP_I_IMPL);
call->SetExpandedEarly();
}
else if (call->IsDelegateInvoke())
{
// Target was saved into a temp during check. We expand the
// delegate call to a CT_INDIRECT call that uses the target
// directly, somewhat similarly to LowerDelegateInvoke.
call->gtCallType = CT_INDIRECT;
call->gtCallAddr = compiler->gtNewLclvNode(m_targetLclNum, TYP_I_IMPL);
call->gtCallCookie = nullptr;
call->gtCallMoreFlags &= ~GTF_CALL_M_DELEGATE_INV;

GenTree* thisOffset =
compiler->gtNewIconNode((ssize_t)compiler->eeGetEEInfo()->offsetOfDelegateInstance, TYP_I_IMPL);
CallArg* thisArg = call->gtArgs.GetThisArg();
GenTree* delegateObj = thisArg->GetNode();

assert(delegateObj->OperIsLocal());
GenTree* newThis =
compiler->gtNewOperNode(GT_ADD, TYP_BYREF, compiler->gtCloneExpr(delegateObj), thisOffset);
newThis = compiler->gtNewIndir(TYP_REF, newThis);

thisArg->SetEarlyNode(newThis);
}
}

compiler->fgInsertStmtAtEnd(elseBlock, newStmt);

// Set the original statement to a nop.
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ CONFIG_INTEGER(JitBreakOnBadCode, W("JitBreakOnBadCode"), 0)
CONFIG_INTEGER(JitBreakOnMinOpts, W("JITBreakOnMinOpts"), 0) // Halt if jit switches to MinOpts
CONFIG_INTEGER(JitBreakOnUnsafeCode, W("JitBreakOnUnsafeCode"), 0)
CONFIG_INTEGER(JitCloneLoops, W("JitCloneLoops"), 1) // If 0, don't clone. Otherwise clone loops for optimizations.
CONFIG_INTEGER(JitCloneLoopsWithTypeTests, W("JitCloneLoopsWithTypeTests"), 1) // If 0, don't clone loops based on
// invariant type tests
CONFIG_INTEGER(JitCloneLoopsWithGdvTests, W("JitCloneLoopsWithGdvTests"), 1) // If 0, don't clone loops based on
// invariant type/method address tests
CONFIG_INTEGER(JitDebugLogLoopCloning, W("JitDebugLogLoopCloning"), 0) // In debug builds log places where loop cloning
// optimizations are performed on the fast path.
CONFIG_INTEGER(JitDefaultFill, W("JitDefaultFill"), 0xdd) // In debug builds, initialize the memory allocated by the nra
Expand Down
Loading