-
Notifications
You must be signed in to change notification settings - Fork 5.2k
JIT: Expand runtime lookups in a late phase #81635
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
Changes from 68 commits
2dcd5e4
2c7a4a3
5754331
dca8140
e303869
aa760e5
20e79dc
22f34a1
c3681c1
2110ec0
54b224c
e4d4944
3e272e0
2671e14
64ae149
d991533
e20dd2d
8fc0374
269cb36
3c1cb1c
47524ed
05ae42d
ba8fdc2
5d6cf4a
ec00f5b
ec893da
f81882f
d1d0bc8
eea468a
3d5d61a
9af1079
ea3bf76
8a2a8ad
777def6
b18b12e
0f7fc3d
5eee49b
2ff3799
11cac11
936c7d2
a982e68
aad1188
f3853a7
f2d22fe
3e3b15f
0f40259
2a29ac3
598ff71
733fcd9
a62a135
730ed35
9c6c89f
f2c1508
c1cc980
6f587df
2834318
79b5076
f0f14a7
6061fa7
2a3d83b
07cf9e7
3a3f4ca
8af93a9
afe2dd4
7909435
258cbae
1afb6f8
a116695
386446e
d2f3fec
19034d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1780,15 +1780,36 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken | |
} | ||
#endif | ||
|
||
// Slot pointer | ||
GenTree* slotPtrTree = ctxTree; | ||
|
||
if (pRuntimeLookup->testForNull) | ||
{ | ||
slotPtrTree = impCloneExpr(ctxTree, &ctxTree, NO_CLASS_HANDLE, CHECK_SPILL_ALL, | ||
nullptr DEBUGARG("impRuntimeLookup slot")); | ||
assert(pRuntimeLookup->indirections != 0); | ||
|
||
// Call the helper | ||
// - Setup argNode with the pointer to the signature returned by the lookup | ||
GenTree* argNode = | ||
gtNewIconEmbHndNode(pRuntimeLookup->signature, nullptr, GTF_ICON_GLOBAL_PTR, compileTimeHandle); | ||
GenTreeCall* helperCall = gtNewHelperCallNode(pRuntimeLookup->helper, TYP_I_IMPL, ctxTree, argNode); | ||
|
||
// No need to perform CSE/hoisting for signature node - it is expected to end up in a rarely-taken block after | ||
// "Expand runtime lookups" phase. | ||
argNode->gtFlags |= GTF_DONT_CSE; | ||
|
||
// Leave a note that this method has runtime lookups we might want to expand (nullchecks, size checks) later. | ||
// We can also consider marking current block as a runtime lookup holder to improve TP for Tier0 | ||
impInlineRoot()->setMethodHasExpRuntimeLookup(); | ||
helperCall->SetExpRuntimeLookup(); | ||
if (!impInlineRoot()->GetSignatureToLookupInfoMap()->Lookup(pRuntimeLookup->signature)) | ||
{ | ||
JITDUMP("Registering %p in SignatureToLookupInfoMap\n", pRuntimeLookup->signature) | ||
impInlineRoot()->GetSignatureToLookupInfoMap()->Set(pRuntimeLookup->signature, *pRuntimeLookup); | ||
} | ||
EgorBo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unsigned callLclNum = lvaGrabTemp(true DEBUGARG("spilling helperCall")); | ||
impAssignTempGen(callLclNum, helperCall); | ||
return gtNewLclvNode(callLclNum, helperCall->TypeGet()); | ||
} | ||
|
||
// Slot pointer | ||
GenTree* slotPtrTree = ctxTree; | ||
GenTree* indOffTree = nullptr; | ||
GenTree* lastIndOfTree = nullptr; | ||
|
||
|
@@ -1834,107 +1855,47 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken | |
} | ||
|
||
// No null test required | ||
if (!pRuntimeLookup->testForNull) | ||
{ | ||
if (pRuntimeLookup->indirections == 0) | ||
{ | ||
return slotPtrTree; | ||
} | ||
|
||
slotPtrTree = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); | ||
slotPtrTree->gtFlags |= GTF_IND_NONFAULTING; | ||
|
||
if (!pRuntimeLookup->testForFixup) | ||
{ | ||
return slotPtrTree; | ||
} | ||
|
||
impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark0")); | ||
|
||
unsigned slotLclNum = lvaGrabTemp(true DEBUGARG("impRuntimeLookup test")); | ||
impAssignTempGen(slotLclNum, slotPtrTree, NO_CLASS_HANDLE, CHECK_SPILL_ALL, nullptr, impCurStmtDI); | ||
|
||
GenTree* slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); | ||
// downcast the pointer to a TYP_INT on 64-bit targets | ||
slot = impImplicitIorI4Cast(slot, TYP_INT); | ||
// Use a GT_AND to check for the lowest bit and indirect if it is set | ||
GenTree* test = gtNewOperNode(GT_AND, TYP_INT, slot, gtNewIconNode(1)); | ||
GenTree* relop = gtNewOperNode(GT_EQ, TYP_INT, test, gtNewIconNode(0)); | ||
assert(!pRuntimeLookup->testForNull); | ||
|
||
// slot = GT_IND(slot - 1) | ||
slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); | ||
GenTree* add = gtNewOperNode(GT_ADD, TYP_I_IMPL, slot, gtNewIconNode(-1, TYP_I_IMPL)); | ||
GenTree* indir = gtNewOperNode(GT_IND, TYP_I_IMPL, add); | ||
indir->gtFlags |= GTF_IND_NONFAULTING; | ||
indir->gtFlags |= GTF_IND_INVARIANT; | ||
|
||
slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); | ||
GenTree* asg = gtNewAssignNode(slot, indir); | ||
GenTreeColon* colon = new (this, GT_COLON) GenTreeColon(TYP_VOID, gtNewNothingNode(), asg); | ||
GenTreeQmark* qmark = gtNewQmarkNode(TYP_VOID, relop, colon); | ||
impAppendTree(qmark, CHECK_SPILL_NONE, impCurStmtDI); | ||
|
||
return gtNewLclvNode(slotLclNum, TYP_I_IMPL); | ||
if (pRuntimeLookup->indirections == 0) | ||
{ | ||
return slotPtrTree; | ||
} | ||
|
||
assert(pRuntimeLookup->indirections != 0); | ||
|
||
impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark1")); | ||
|
||
// Extract the handle | ||
GenTree* handleForNullCheck = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); | ||
handleForNullCheck->gtFlags |= GTF_IND_NONFAULTING; | ||
|
||
// Call the helper | ||
// - Setup argNode with the pointer to the signature returned by the lookup | ||
GenTree* argNode = gtNewIconEmbHndNode(pRuntimeLookup->signature, nullptr, GTF_ICON_GLOBAL_PTR, compileTimeHandle); | ||
|
||
GenTreeCall* helperCall = gtNewHelperCallNode(pRuntimeLookup->helper, TYP_I_IMPL, ctxTree, argNode); | ||
|
||
// Check for null and possibly call helper | ||
GenTree* nullCheck = gtNewOperNode(GT_NE, TYP_INT, handleForNullCheck, gtNewIconNode(0, TYP_I_IMPL)); | ||
GenTree* handleForResult = gtCloneExpr(handleForNullCheck); | ||
|
||
GenTree* result = nullptr; | ||
slotPtrTree = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); | ||
slotPtrTree->gtFlags |= GTF_IND_NONFAULTING; | ||
|
||
if (pRuntimeLookup->sizeOffset != CORINFO_NO_SIZE_CHECK) | ||
if (!pRuntimeLookup->testForFixup) | ||
{ | ||
// Dynamic dictionary expansion support | ||
return slotPtrTree; | ||
} | ||
|
||
assert((lastIndOfTree != nullptr) && (pRuntimeLookup->indirections > 0)); | ||
impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark0")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any benefit to deferring expansion of the rest of this in a manner similar to the case above? Seems like this is also creating a hoistable/cseable complex. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't touch this path because it wasn't clear for me when it's hit. From what I see it's not used by R2R and NAOT. Perhaps some dynamic context In a follow I'll see if it's worth supporting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Thanks, will delete in #83430 |
||
|
||
// sizeValue = dictionary[pRuntimeLookup->sizeOffset] | ||
GenTreeIntCon* sizeOffset = gtNewIconNode(pRuntimeLookup->sizeOffset, TYP_I_IMPL); | ||
GenTree* sizeValueOffset = gtNewOperNode(GT_ADD, TYP_I_IMPL, lastIndOfTree, sizeOffset); | ||
GenTree* sizeValue = gtNewOperNode(GT_IND, TYP_I_IMPL, sizeValueOffset); | ||
sizeValue->gtFlags |= GTF_IND_NONFAULTING; | ||
unsigned slotLclNum = lvaGrabTemp(true DEBUGARG("impRuntimeLookup test")); | ||
impAssignTempGen(slotLclNum, slotPtrTree, NO_CLASS_HANDLE, CHECK_SPILL_ALL, nullptr, impCurStmtDI); | ||
|
||
// sizeCheck fails if sizeValue < pRuntimeLookup->offsets[i] | ||
GenTree* offsetValue = gtNewIconNode(pRuntimeLookup->offsets[pRuntimeLookup->indirections - 1], TYP_I_IMPL); | ||
GenTree* sizeCheck = gtNewOperNode(GT_LE, TYP_INT, sizeValue, offsetValue); | ||
GenTree* slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); | ||
// downcast the pointer to a TYP_INT on 64-bit targets | ||
slot = impImplicitIorI4Cast(slot, TYP_INT); | ||
// Use a GT_AND to check for the lowest bit and indirect if it is set | ||
GenTree* test = gtNewOperNode(GT_AND, TYP_INT, slot, gtNewIconNode(1)); | ||
GenTree* relop = gtNewOperNode(GT_EQ, TYP_INT, test, gtNewIconNode(0)); | ||
|
||
// revert null check condition. | ||
nullCheck->ChangeOperUnchecked(GT_EQ); | ||
// slot = GT_IND(slot - 1) | ||
slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); | ||
GenTree* add = gtNewOperNode(GT_ADD, TYP_I_IMPL, slot, gtNewIconNode(-1, TYP_I_IMPL)); | ||
GenTree* indir = gtNewOperNode(GT_IND, TYP_I_IMPL, add); | ||
indir->gtFlags |= GTF_IND_NONFAULTING; | ||
indir->gtFlags |= GTF_IND_INVARIANT; | ||
|
||
// ((sizeCheck fails || nullCheck fails))) ? (helperCall : handle). | ||
// Add checks and the handle as call arguments, indirect call transformer will handle this. | ||
NewCallArg nullCheckArg = NewCallArg::Primitive(nullCheck); | ||
NewCallArg sizeCheckArg = NewCallArg::Primitive(sizeCheck); | ||
NewCallArg handleForResultArg = NewCallArg::Primitive(handleForResult); | ||
helperCall->gtArgs.PushFront(this, nullCheckArg, sizeCheckArg, handleForResultArg); | ||
result = helperCall; | ||
addExpRuntimeLookupCandidate(helperCall); | ||
} | ||
else | ||
{ | ||
GenTreeColon* colonNullCheck = new (this, GT_COLON) GenTreeColon(TYP_I_IMPL, handleForResult, helperCall); | ||
result = gtNewQmarkNode(TYP_I_IMPL, nullCheck, colonNullCheck); | ||
} | ||
slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); | ||
GenTree* asg = gtNewAssignNode(slot, indir); | ||
GenTreeColon* colon = new (this, GT_COLON) GenTreeColon(TYP_VOID, gtNewNothingNode(), asg); | ||
GenTreeQmark* qmark = gtNewQmarkNode(TYP_VOID, relop, colon); | ||
impAppendTree(qmark, CHECK_SPILL_NONE, impCurStmtDI); | ||
|
||
unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling Runtime Lookup tree")); | ||
|
||
impAssignTempGen(tmp, result, CHECK_SPILL_NONE); | ||
return gtNewLclvNode(tmp, TYP_I_IMPL); | ||
return gtNewLclvNode(slotLclNum, TYP_I_IMPL); | ||
} | ||
|
||
struct RecursiveGuard | ||
|
@@ -14081,12 +14042,6 @@ methodPointerInfo* Compiler::impAllocateMethodPointerInfo(const CORINFO_RESOLVED | |
return memory; | ||
} | ||
|
||
void Compiler::addExpRuntimeLookupCandidate(GenTreeCall* call) | ||
{ | ||
setMethodHasExpRuntimeLookup(); | ||
call->SetExpRuntimeLookup(); | ||
} | ||
|
||
//------------------------------------------------------------------------ | ||
// impIsClassExact: check if a class handle can only describe values | ||
// of exactly one class. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is checking for the case where
gtSplitTree
didn't do anything?Perhaps deserves a comment to that effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a comment in a follow up if you don't mind to avoid re-running CI for it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really about
gtSplitTree
? Isn't it here because we don't have afgSplitBlockBefore
?gtSplitTree
can make changes even without introducing new statements -- the return value needs to be used for that kind of check.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's here just because I need basically
fgSplitBlockBefore
and only havefgSplitBlockAfter
APIThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so when it's not the first statement in the current block I do
fgSplitBlockAfter (stmt->PrevStmt())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose I should look at what
gtSplitTree
does, but I'm still confused exactly what this method is supposed to be doing. Maybe an example would help?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general idea that we want to split a block (e.g.
BB0
) into two (say,BBa
andBBb
) at a specific node (e.g.callX
) and make sure that all side-effects are moved toBBa
and the actualcallX
is now inBBb
.We have a phase where we insert GC safe points after specific
call
nodes - that one didn't have to care about any execution ordering since we just wanted to make sure GC is polled (it doesn't even matter whether to emit the poll before or after the calls). The runtime lookup case is a lot more complicated since we needed to insert verbose tree in front of the call and re-use its arguments, respect all kinds of complex COMMAs,