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

RyuJIT: Inlined "is class statically inited" check #47901

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5026,6 +5026,10 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
compQuirkForPPPflag = compQuirkForPPP();
#endif

// Insert quick "is class statically initialized" checks in front of
// static init helper calls
DoPhase(this, PHASE_INSERT_STATINIT_CHECKS, &Compiler::fgInsertClsInitChecks);

// Insert GC Polls
DoPhase(this, PHASE_INSERT_GC_POLLS, &Compiler::fgInsertGCPolls);

Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5184,6 +5184,8 @@ class Compiler
PhaseStatus fgInsertGCPolls();
BasicBlock* fgCreateGCPoll(GCPollType pollType, BasicBlock* block);

PhaseStatus fgInsertClsInitChecks();

// Requires that "block" is a block that returns from
// a finally. Returns the number of successors (jump targets of
// of blocks in the covered "try" that did a "LEAVE".)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compphases.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ CompPhaseNameMacro(PHASE_ASSERTION_PROP_MAIN, "Assertion prop",
#endif
CompPhaseNameMacro(PHASE_OPT_UPDATE_FLOW_GRAPH, "Update flow graph opt pass", "UPD-FG-O", false, -1, false)
CompPhaseNameMacro(PHASE_COMPUTE_EDGE_WEIGHTS2, "Compute edge weights (2, false)", "EDG-WGT2", false, -1, false)
CompPhaseNameMacro(PHASE_INSERT_STATINIT_CHECKS, "Insert is-cls-stat-init Checks", "CLSSICHK", false, -1, true)
CompPhaseNameMacro(PHASE_INSERT_GC_POLLS, "Insert GC Polls", "GC-POLLS", false, -1, true)
CompPhaseNameMacro(PHASE_DETERMINE_FIRST_COLD_BLOCK, "Determine first cold block", "COLD-BLK", false, -1, true)
CompPhaseNameMacro(PHASE_RATIONALIZE, "Rationalize IR", "RAT", false, -1, false)
Expand Down
238 changes: 238 additions & 0 deletions src/coreclr/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,244 @@ static bool blockNeedsGCPoll(BasicBlock* block)
return blockMayNeedGCPoll;
}

//------------------------------------------------------------------------------
// fgInsertClsInitChecks : Wraps static init helper calls with "is class statically initialized"
// inlined checks.
//
// Returns:
// PhaseStatus indicating what, if anything, was changed.
//

PhaseStatus Compiler::fgInsertClsInitChecks()
{
if (!opts.OptimizationEnabled())
{
return PhaseStatus::MODIFIED_NOTHING;
}

bool modified = false;
BasicBlock* block;
BasicBlock* prevBb = nullptr;
for (block = fgFirstBB; block; block = block->bbNext)
{
if (!block->isRunRarely())
{
for (Statement* stmt : block->Statements())
{
for (GenTree* tree = stmt->GetTreeList(); tree != nullptr; tree = tree->gtNext)
{
// we only need GT_CALL nodes with helper funcs
if (!tree->IsCall())
{
continue;
}

GenTreeCall* call = tree->AsCall();
if (call->gtCallType != CT_HELPER)
{
continue;
}

CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
if ((helpFunc != CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE) || (call->fgArgInfo->ArgCount() != 2))
{
continue;
}

GenTree* moduleIdArg = call->fgArgInfo->GetArgNode(0);
GenTree* clsIdArg = call->fgArgInfo->GetArgNode(1);

if (!moduleIdArg->IsCnsIntOrI() || !clsIdArg->IsCnsIntOrI())
{
// Looks like moduleId or/and clsId were passed as indirect loads
continue;
}

bool hasNotSupportedPreds = false;
for (flowList* pred = block->bbPreds; pred != nullptr; pred = pred->flNext)
{
BasicBlock* predBlock = pred->getBlock();
if ((predBlock->bbJumpKind != BBJ_NONE) && (predBlock->bbJumpKind != BBJ_ALWAYS) &&
(predBlock->bbJumpKind != BBJ_COND))
{
hasNotSupportedPreds = true;
break;
}
if (!BasicBlock::sameEHRegion(predBlock, block))
{
hasNotSupportedPreds = true;
break;
}
}
if (hasNotSupportedPreds)
{
continue;
}

if (prevBb == nullptr)
{
// We're going to emit a BB in front of fgFirstBB
fgEnsureFirstBBisScratch();
prevBb = fgFirstBB;
if (prevBb == block)
{
continue;
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
}
}

// So, we found a helper call inside the "block" - let's extract it to a
// separate block "callInitBb" and guard it with a fast "isInitedBb" bb.
// The final layout should look like this:

// BB0 "prevBb":
// ...
//
// BB1 "isInitedBb": (preds: BB0 + %current preds of BB3%)
//
// * JTRUE void
// \--* NE int
// +--* AND int
// | +--* IND ubyte
// | | \--* CNS_INT long moduleIdArg + clsIdArg + dataBlobOffset
// | \--* CNS_INT int isInitMask
// \--* CNS_INT int 0
//
//
// BB2 "callInitBb": (preds: BB1)
//
// * CALL help long HELPER.CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
// +--* CNS_INT long moduleIdArg
// \--* CNS_INT int clsIdArg
//
// BB3 "block" (preds: BB1, BB2)
// ...
//

// TODO: ask VM for these constants:
const int dataBlobOffset = 48; // DomainLocalModule::GetOffsetOfDataBlob()
const int isInitMask = 1; // ClassInitFlags::INITIALIZED_FLAG;

UINT8* isInitAdr = (UINT8*)moduleIdArg->AsIntCon()->IconValue() + dataBlobOffset +
clsIdArg->AsIntCon()->IconValue();

GenTree* isInitAdrNode =
gtNewIndOfIconHandleNode(TYP_UBYTE, (size_t)isInitAdr, GTF_ICON_CONST_PTR, true);


// Let's start from emitting that BB2 "callInitBb"
BasicBlock* callInitBb = fgNewBBbefore(BBJ_NONE, block, true);
// it's executed only once so can be marked as cold
callInitBb->bbSetRunRarely();
callInitBb->bbFlags |= (BBF_INTERNAL | BBF_HAS_CALL | BBF_HAS_LABEL);
GenTree* clonedHelperCall = gtCloneExprCallHelper(call);
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
clonedHelperCall->gtFlags |= call->gtFlags;

Statement* callStmt = fgNewStmtFromTree(clonedHelperCall);
if (fgStmtListThreaded)
{
gtSetStmtInfo(callStmt);
fgSetStmtSeq(callStmt);
}
fgInsertStmtAtEnd(callInitBb, callStmt);
gtUpdateStmtSideEffects(callStmt);

// BB1 "isInitedBb"
BasicBlock* isInitedBb = fgNewBBbefore(BBJ_COND, callInitBb, true);
isInitedBb->inheritWeight(block);
isInitedBb->bbFlags |= (BBF_INTERNAL | BBF_HAS_LABEL | BBF_HAS_JMP);

GenTree* isInitedMask =
gtNewOperNode(GT_AND, TYP_INT, gtNewCastNode(TYP_INT, isInitAdrNode, true, TYP_INT),
gtNewIconNode(isInitMask));

GenTree* isInitedCmp = gtNewOperNode(GT_NE, TYP_INT, isInitedMask, gtNewIconNode(0));
isInitedCmp->gtFlags |= (GTF_RELOP_JMP_USED | GTF_DONT_CSE);
gtSetEvalOrder(isInitedCmp);

Statement* isInitedStmt = fgNewStmtFromTree(gtNewOperNode(GT_JTRUE, TYP_VOID, isInitedCmp));
if (fgStmtListThreaded)
{
gtSetStmtInfo(isInitedStmt);
fgSetStmtSeq(isInitedStmt);
}

fgInsertStmtAtEnd(isInitedBb, isInitedStmt);
isInitedBb->bbJumpDest = block;
block->bbFlags |= BBF_JMP_TARGET;

// Now we can remove the call from the current block
// We're going to replace the call with just "moduleId" node (it's what it was supposed to return)

GenTree* clonedHelperCall2 = gtCloneExprCallHelper(call);
clonedHelperCall2->gtFlags |= call->gtFlags;
EgorBo marked this conversation as resolved.
Show resolved Hide resolved

gtReplaceTree(stmt, call, gtClone(moduleIdArg));

// Now we need to fix all the preds:

// isInitedBb is a pred of callInitBb
fgAddRefPred(callInitBb, isInitedBb);
for (flowList* pred = block->bbPreds; pred != nullptr; pred = pred->flNext)
{
// Redirect all the preds from the current block to isInitedBb
BasicBlock* predBlock = pred->getBlock();
assert((predBlock->bbJumpKind == BBJ_NONE) || (predBlock->bbJumpKind == BBJ_ALWAYS) ||
(predBlock->bbJumpKind == BBJ_COND));

if (predBlock->bbJumpDest == block)
{
predBlock->bbJumpDest = isInitedBb;
isInitedBb->bbFlags |= BBF_JMP_TARGET;
}
if (predBlock->bbNext == block)
{
predBlock->bbNext = isInitedBb;
}
fgRemoveRefPred(block, predBlock);
fgAddRefPred(isInitedBb, predBlock);
}
// Both callInitBb and isInitedBb are preds of block now
fgAddRefPred(block, callInitBb);
fgAddRefPred(block, isInitedBb);

// Make sure all three basic blocks are in the same EH region:
assert(BasicBlock::sameEHRegion(callInitBb, block));
assert(BasicBlock::sameEHRegion(isInitedBb, block));

assert(isInitedBb->bbNext == callInitBb);
assert(callInitBb->bbNext == block);
assert(isInitedBb->bbJumpDest == block);

modified = true;
}
if (modified)
{
// clear GTF_CALL and GTF_EXC flags (we've just removed a call)
gtUpdateStmtSideEffects(stmt);
}
}
}
prevBb = block;
}

if (modified)
{
#ifdef DEBUG
if (verbose)
{
printf("*************** After fgInsertClsInitChecks()\n");
fgDispBasicBlocks(true);
}
#endif // DEBUG
fgReorderBlocks();

// TODO: do I need to call it since I fixed all the preds by hands?
fgUpdateChangedFlowGraph(false);
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
return PhaseStatus::MODIFIED_EVERYTHING;
}
return PhaseStatus::MODIFIED_NOTHING;
}

//------------------------------------------------------------------------------
// fgInsertGCPolls : Insert GC polls for basic blocks containing calls to methods
// with SuppressGCTransitionAttribute.
Expand Down