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

Commit 8ac7d8d

Browse files
AndyAyersMSmorganbr
authored andcommitted
JIT: block general cloning of candidate calls (#21418)
Follow-up from #21270 and #21414. Block general cloning from inadvertently cloning a candidate call. Add a separate path for cloning calls that are inline and guarded devirtualization candidates for use by guarded devirtualization. Callers need to take extra steps after cloning one of these calls to properly fix everything up. Also fix up some issues in the large comment for the fat calli transformation.
1 parent 4bae653 commit 8ac7d8d

File tree

3 files changed

+159
-87
lines changed

3 files changed

+159
-87
lines changed

src/jit/compiler.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2482,11 +2482,20 @@ class Compiler
24822482

24832483
// Create a copy of `tree`, optionally adding specifed flags, and optionally mapping uses of local
24842484
// `varNum` to int constants with value `varVal`.
2485-
GenTree* gtCloneExpr(GenTree* tree, unsigned addFlags = 0, unsigned varNum = (unsigned)-1, int varVal = 0)
2485+
GenTree* gtCloneExpr(GenTree* tree, unsigned addFlags = 0, unsigned varNum = BAD_VAR_NUM, int varVal = 0)
24862486
{
24872487
return gtCloneExpr(tree, addFlags, varNum, varVal, varNum, varVal);
24882488
}
24892489

2490+
// Internal helper for cloning a call
2491+
GenTreeCall* gtCloneExprCallHelper(GenTreeCall* call,
2492+
unsigned addFlags = 0,
2493+
unsigned deepVarNum = BAD_VAR_NUM,
2494+
int deepVarVal = 0);
2495+
2496+
// Create copy of an inline or guarded devirtualization candidate tree.
2497+
GenTreeCall* gtCloneCandidateCall(GenTreeCall* call);
2498+
24902499
GenTree* gtReplaceTree(GenTree* stmt, GenTree* tree, GenTree* replacementTree);
24912500

24922501
void gtUpdateSideEffects(GenTree* stmt, GenTree* tree);

src/jit/gentree.cpp

Lines changed: 131 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7273,82 +7273,14 @@ GenTree* Compiler::gtCloneExpr(
72737273

72747274
case GT_CALL:
72757275

7276-
copy = new (this, GT_CALL) GenTreeCall(tree->TypeGet());
7277-
7278-
copy->gtCall.gtCallObjp = tree->gtCall.gtCallObjp
7279-
? gtCloneExpr(tree->gtCall.gtCallObjp, addFlags, deepVarNum, deepVarVal)
7280-
: nullptr;
7281-
copy->gtCall.gtCallArgs =
7282-
tree->gtCall.gtCallArgs
7283-
? gtCloneExpr(tree->gtCall.gtCallArgs, addFlags, deepVarNum, deepVarVal)->AsArgList()
7284-
: nullptr;
7285-
copy->gtCall.gtCallMoreFlags = tree->gtCall.gtCallMoreFlags;
7286-
copy->gtCall.gtCallLateArgs =
7287-
tree->gtCall.gtCallLateArgs
7288-
? gtCloneExpr(tree->gtCall.gtCallLateArgs, addFlags, deepVarNum, deepVarVal)->AsArgList()
7289-
: nullptr;
7290-
7291-
#if !FEATURE_FIXED_OUT_ARGS
7292-
copy->gtCall.regArgList = tree->gtCall.regArgList;
7293-
copy->gtCall.regArgListCount = tree->gtCall.regArgListCount;
7294-
#endif
7295-
7296-
// The call sig comes from the EE and doesn't change throughout the compilation process, meaning
7297-
// we only really need one physical copy of it. Therefore a shallow pointer copy will suffice.
7298-
// (Note that this still holds even if the tree we are cloning was created by an inlinee compiler,
7299-
// because the inlinee still uses the inliner's memory allocator anyway.)
7300-
copy->gtCall.callSig = tree->gtCall.callSig;
7301-
7302-
copy->gtCall.gtCallType = tree->gtCall.gtCallType;
7303-
copy->gtCall.gtReturnType = tree->gtCall.gtReturnType;
7304-
copy->gtCall.gtControlExpr = tree->gtCall.gtControlExpr;
7305-
7306-
/* Copy the union */
7307-
if (tree->gtCall.gtCallType == CT_INDIRECT)
7308-
{
7309-
copy->gtCall.gtCallCookie =
7310-
tree->gtCall.gtCallCookie ? gtCloneExpr(tree->gtCall.gtCallCookie, addFlags, deepVarNum, deepVarVal)
7311-
: nullptr;
7312-
copy->gtCall.gtCallAddr = tree->gtCall.gtCallAddr
7313-
? gtCloneExpr(tree->gtCall.gtCallAddr, addFlags, deepVarNum, deepVarVal)
7314-
: nullptr;
7315-
}
7316-
else if (tree->gtCall.IsVirtualStub())
7317-
{
7318-
copy->gtCall.gtCallMethHnd = tree->gtCall.gtCallMethHnd;
7319-
copy->gtCall.gtStubCallStubAddr = tree->gtCall.gtStubCallStubAddr;
7320-
}
7321-
else
7276+
// We can't safely clone calls that have GT_RET_EXPRs via gtCloneExpr.
7277+
// You must use gtCloneCandidateCall for these calls (and then do appropriate other fixup)
7278+
if (tree->gtCall.IsInlineCandidate() || tree->gtCall.IsGuardedDevirtualizationCandidate())
73227279
{
7323-
copy->gtCall.gtCallMethHnd = tree->gtCall.gtCallMethHnd;
7324-
copy->gtCall.gtInlineCandidateInfo = tree->gtCall.gtInlineCandidateInfo;
7280+
NO_WAY("Cloning of calls with associated GT_RET_EXPR nodes is not supported");
73257281
}
73267282

7327-
if (tree->gtCall.fgArgInfo)
7328-
{
7329-
// Create and initialize the fgArgInfo for our copy of the call tree
7330-
copy->gtCall.fgArgInfo = new (this, CMK_Unknown) fgArgInfo(copy->AsCall(), tree->AsCall());
7331-
}
7332-
else
7333-
{
7334-
copy->gtCall.fgArgInfo = nullptr;
7335-
}
7336-
copy->gtCall.gtRetClsHnd = tree->gtCall.gtRetClsHnd;
7337-
7338-
#if FEATURE_MULTIREG_RET
7339-
copy->gtCall.gtReturnTypeDesc = tree->gtCall.gtReturnTypeDesc;
7340-
#endif
7341-
7342-
#ifdef FEATURE_READYTORUN_COMPILER
7343-
copy->gtCall.setEntryPoint(tree->gtCall.gtEntryPoint);
7344-
#endif
7345-
7346-
#if defined(DEBUG) || defined(INLINE_DATA)
7347-
copy->gtCall.gtInlineObservation = tree->gtCall.gtInlineObservation;
7348-
copy->gtCall.gtRawILOffset = tree->gtCall.gtRawILOffset;
7349-
#endif
7350-
7351-
copy->AsCall()->CopyOtherRegFlags(tree->AsCall());
7283+
copy = gtCloneExprCallHelper(tree->AsCall(), addFlags, deepVarNum, deepVarVal);
73527284
break;
73537285

73547286
case GT_FIELD:
@@ -7486,6 +7418,131 @@ GenTree* Compiler::gtCloneExpr(
74867418
return copy;
74877419
}
74887420

7421+
//------------------------------------------------------------------------
7422+
// gtCloneExprCallHelper: clone a call tree
7423+
//
7424+
// Notes:
7425+
// Do not invoke this method directly, instead call either gtCloneExpr
7426+
// or gtCloneCandidateCall, as appropriate.
7427+
//
7428+
// Arguments:
7429+
// tree - the call to clone
7430+
// addFlags - GTF_* flags to add to the copied tree nodes
7431+
// deepVarNum - lclNum to replace uses of beyond the root, or BAD_VAR_NUM for no replacement
7432+
// deepVarVal - If replacing beyond root, replace `deepVarNum` with IntCns `deepVarVal`
7433+
//
7434+
// Returns:
7435+
// Cloned copy of call and all subtrees.
7436+
7437+
GenTreeCall* Compiler::gtCloneExprCallHelper(GenTreeCall* tree, unsigned addFlags, unsigned deepVarNum, int deepVarVal)
7438+
{
7439+
GenTreeCall* copy = new (this, GT_CALL) GenTreeCall(tree->TypeGet());
7440+
7441+
copy->gtCallObjp = tree->gtCallObjp ? gtCloneExpr(tree->gtCallObjp, addFlags, deepVarNum, deepVarVal) : nullptr;
7442+
copy->gtCallArgs =
7443+
tree->gtCallArgs ? gtCloneExpr(tree->gtCallArgs, addFlags, deepVarNum, deepVarVal)->AsArgList() : nullptr;
7444+
copy->gtCallMoreFlags = tree->gtCallMoreFlags;
7445+
copy->gtCallLateArgs = tree->gtCallLateArgs
7446+
? gtCloneExpr(tree->gtCallLateArgs, addFlags, deepVarNum, deepVarVal)->AsArgList()
7447+
: nullptr;
7448+
7449+
#if !FEATURE_FIXED_OUT_ARGS
7450+
copy->regArgList = tree->regArgList;
7451+
copy->regArgListCount = tree->regArgListCount;
7452+
#endif
7453+
7454+
// The call sig comes from the EE and doesn't change throughout the compilation process, meaning
7455+
// we only really need one physical copy of it. Therefore a shallow pointer copy will suffice.
7456+
// (Note that this still holds even if the tree we are cloning was created by an inlinee compiler,
7457+
// because the inlinee still uses the inliner's memory allocator anyway.)
7458+
copy->callSig = tree->callSig;
7459+
7460+
copy->gtCallType = tree->gtCallType;
7461+
copy->gtReturnType = tree->gtReturnType;
7462+
copy->gtControlExpr = tree->gtControlExpr;
7463+
7464+
/* Copy the union */
7465+
if (tree->gtCallType == CT_INDIRECT)
7466+
{
7467+
copy->gtCallCookie =
7468+
tree->gtCallCookie ? gtCloneExpr(tree->gtCallCookie, addFlags, deepVarNum, deepVarVal) : nullptr;
7469+
copy->gtCallAddr = tree->gtCallAddr ? gtCloneExpr(tree->gtCallAddr, addFlags, deepVarNum, deepVarVal) : nullptr;
7470+
}
7471+
else if (tree->IsVirtualStub())
7472+
{
7473+
copy->gtCallMethHnd = tree->gtCallMethHnd;
7474+
copy->gtStubCallStubAddr = tree->gtStubCallStubAddr;
7475+
}
7476+
else
7477+
{
7478+
copy->gtCallMethHnd = tree->gtCallMethHnd;
7479+
copy->gtInlineCandidateInfo = nullptr;
7480+
}
7481+
7482+
if (tree->fgArgInfo)
7483+
{
7484+
// Create and initialize the fgArgInfo for our copy of the call tree
7485+
copy->fgArgInfo = new (this, CMK_Unknown) fgArgInfo(copy, tree);
7486+
}
7487+
else
7488+
{
7489+
copy->fgArgInfo = nullptr;
7490+
}
7491+
7492+
copy->gtRetClsHnd = tree->gtRetClsHnd;
7493+
7494+
#if FEATURE_MULTIREG_RET
7495+
copy->gtReturnTypeDesc = tree->gtReturnTypeDesc;
7496+
#endif
7497+
7498+
#ifdef FEATURE_READYTORUN_COMPILER
7499+
copy->setEntryPoint(tree->gtEntryPoint);
7500+
#endif
7501+
7502+
#if defined(DEBUG) || defined(INLINE_DATA)
7503+
copy->gtInlineObservation = tree->gtInlineObservation;
7504+
copy->gtRawILOffset = tree->gtCall.gtRawILOffset;
7505+
#endif
7506+
7507+
copy->CopyOtherRegFlags(tree);
7508+
7509+
return copy;
7510+
}
7511+
7512+
//------------------------------------------------------------------------
7513+
// gtCloneCandidateCall: clone a call that is an inline or guarded
7514+
// devirtualization candidate (~ any call that can have a GT_RET_EXPR)
7515+
//
7516+
// Notes:
7517+
// If the call really is a candidate, the caller must take additional steps
7518+
// after cloning to re-establish candidate info and the relationship between
7519+
// the candidate and any associated GT_RET_EXPR.
7520+
//
7521+
// Arguments:
7522+
// call - the call to clone
7523+
//
7524+
// Returns:
7525+
// Cloned copy of call and all subtrees.
7526+
7527+
GenTreeCall* Compiler::gtCloneCandidateCall(GenTreeCall* call)
7528+
{
7529+
assert(call->IsInlineCandidate() || call->IsGuardedDevirtualizationCandidate());
7530+
7531+
GenTreeCall* result = gtCloneExprCallHelper(call);
7532+
7533+
// There is some common post-processing in gtCloneExpr that we reproduce
7534+
// here, for the fields that make sense for candidate calls.
7535+
result->gtFlags |= call->gtFlags;
7536+
7537+
#if defined(DEBUG)
7538+
result->gtDebugFlags |= (call->gtDebugFlags & ~GTF_DEBUG_NODE_MASK);
7539+
#endif
7540+
7541+
result->CopyReg(call);
7542+
7543+
return result;
7544+
}
7545+
74897546
//------------------------------------------------------------------------
74907547
// gtReplaceTree: Replace a tree with a new tree.
74917548
//
@@ -12079,7 +12136,7 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
1207912136
// Compare the two method tables
1208012137
GenTree* const compare = gtCreateHandleCompare(oper, objMT, knownMT, typeCheckInliningResult);
1208112138

12082-
// Drop any any now irrelevant flags
12139+
// Drop any now irrelevant flags
1208312140
compare->gtFlags |= tree->gtFlags & (GTF_RELOP_JMP_USED | GTF_RELOP_QMARK | GTF_DONT_CSE);
1208412141

1208512142
// And we're done

src/jit/indirectcalltransformer.cpp

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,26 @@
77
#pragma hdrstop
88
#endif
99

10-
// The IndirectCallTransformer transforms indirect calls that involve fat pointers
11-
// or guarded devirtualization candidates.
10+
// The IndirectCallTransformer transforms indirect calls that involve fat function
11+
// pointers or guarded devirtualization candidates. These transformations introduce
12+
// control flow and so can't easily be done in the importer.
1213
//
13-
// A fat function pointer is pointer with the second least significant bit set,
14-
// if the bit is set, the pointer (after clearing the bit) actually points to
15-
// a tuple <method pointer, instantiation argument pointer> where
14+
// A fat function pointer is a pointer with the second least significant bit
15+
// (aka FAT_POINTER_MASK) set. If the bit is set, the pointer (after clearing the bit)
16+
// actually points to a tuple <method pointer, instantiation argument pointer> where
1617
// instantiationArgument is a hidden first argument required by method pointer.
1718
//
1819
// Fat pointers are used in CoreRT as a replacement for instantiating stubs,
1920
// because CoreRT can't generate stubs in runtime.
2021
//
21-
// Jit is responsible for the checking the bit, do the regular call if it is not set
22-
// or load hidden argument, fix the pointer and make a call with the fixed pointer and
23-
// the instantiation argument.
22+
// The JIT is responsible for emitting code to check the bit at runtime, branching
23+
// to one of two call sites.
24+
//
25+
// When the bit is not set, the code should execute the original indirect call.
26+
//
27+
// When the bit is set, the code should mask off the bit, use the resulting pointer
28+
// to load the real target address and the extra argument, and then call indirect
29+
// via the target, passing the extra argument.
2430
//
2531
// before:
2632
// current block
@@ -40,15 +46,15 @@
4046
// } BBJ_NONE check block
4147
// check block
4248
// {
43-
// jump to else if function ptr has GTF_CALL_M_FAT_POINTER_CHECK set.
49+
// jump to else if function ptr has the FAT_POINTER_MASK bit set.
4450
// } BBJ_COND then block, else block
4551
// then block
4652
// {
4753
// original statement
4854
// } BBJ_ALWAYS remainder block
4955
// else block
5056
// {
51-
// unset GTF_CALL_M_FAT_POINTER_CHECK
57+
// clear FAT_POINTER_MASK bit
5258
// load actual function pointer
5359
// load instantiation argument
5460
// create newArgList = (instantiation argument, original argList)
@@ -663,8 +669,8 @@ class IndirectCallTransformer
663669
GenTreeStmt* assignStmt = compiler->gtNewStmt(assign);
664670
compiler->fgInsertStmtAtEnd(thenBlock, assignStmt);
665671

666-
// Clone call
667-
GenTreeCall* call = compiler->gtCloneExpr(origCall)->AsCall();
672+
// Clone call. Note we must use the special candidate helper.
673+
GenTreeCall* call = compiler->gtCloneCandidateCall(origCall);
668674
call->gtCallObjp = compiler->gtNewLclvNode(thisTemp, TYP_REF);
669675
call->SetIsGuarded();
670676

0 commit comments

Comments
 (0)