-
Notifications
You must be signed in to change notification settings - Fork 5.2k
JIT: Allow forward sub to reorder trees throwing the same single exception #85647
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 all commits
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 |
---|---|---|
|
@@ -190,16 +190,7 @@ class ForwardSubVisitor final : public GenTreeVisitor<ForwardSubVisitor> | |
}; | ||
|
||
ForwardSubVisitor(Compiler* compiler, unsigned lclNum, bool livenessBased) | ||
: GenTreeVisitor(compiler) | ||
, m_use(nullptr) | ||
, m_node(nullptr) | ||
, m_parentNode(nullptr) | ||
, m_lclNum(lclNum) | ||
, m_parentLclNum(BAD_VAR_NUM) | ||
, m_useFlags(GTF_EMPTY) | ||
, m_accumulatedFlags(GTF_EMPTY) | ||
, m_treeSize(0) | ||
, m_livenessBased(livenessBased) | ||
: GenTreeVisitor(compiler), m_lclNum(lclNum), m_livenessBased(livenessBased) | ||
{ | ||
LclVarDsc* dsc = compiler->lvaGetDesc(m_lclNum); | ||
if (dsc->lvIsStructField) | ||
|
@@ -238,10 +229,11 @@ class ForwardSubVisitor final : public GenTreeVisitor<ForwardSubVisitor> | |
|
||
if (!isDef && !isCallTarget && IsLastUse(node->AsLclVar())) | ||
{ | ||
m_node = node; | ||
m_use = use; | ||
m_useFlags = m_accumulatedFlags; | ||
m_parentNode = parent; | ||
m_node = node; | ||
m_use = use; | ||
m_useFlags = m_accumulatedFlags; | ||
m_useExceptions = m_accumulatedExceptions; | ||
m_parentNode = parent; | ||
} | ||
} | ||
} | ||
|
@@ -274,6 +266,20 @@ class ForwardSubVisitor final : public GenTreeVisitor<ForwardSubVisitor> | |
} | ||
|
||
m_accumulatedFlags |= (node->gtFlags & GTF_GLOB_EFFECT); | ||
if ((node->gtFlags & GTF_CALL) != 0) | ||
{ | ||
m_accumulatedExceptions = ExceptionSetFlags::All; | ||
} | ||
else if ((node->gtFlags & GTF_EXCEPT) != 0) | ||
{ | ||
// We can never reorder in the face of different exception types, | ||
// so stop calling 'OperExceptions' once we've seen more than one | ||
// different exception type. | ||
if (genCountBits(static_cast<uint32_t>(m_accumulatedExceptions)) <= 1) | ||
{ | ||
m_accumulatedExceptions |= node->OperExceptions(m_compiler); | ||
} | ||
} | ||
|
||
return fgWalkResult::WALK_CONTINUE; | ||
} | ||
|
@@ -305,6 +311,23 @@ class ForwardSubVisitor final : public GenTreeVisitor<ForwardSubVisitor> | |
return m_useFlags; | ||
} | ||
|
||
//------------------------------------------------------------------------ | ||
// GetExceptions: Get precise exceptions thrown by the trees executed | ||
// before the use. | ||
// | ||
// Returns: | ||
// Exception set. | ||
// | ||
// Remarks: | ||
// The visitor stops tracking precise exceptions once it finds that 2 or | ||
// more different exceptions can be thrown, so this set cannot be used | ||
// for determining the precise different exceptions thrown in that case. | ||
// | ||
ExceptionSetFlags GetExceptions() const | ||
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. It's probably worth additional comments here and/or on m_useExceptions/m_accumulatedExceptions that a value with >1 exceptions isn't trustworthy to avoid some accidental misuse. 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. Will add that. |
||
{ | ||
return m_useExceptions; | ||
} | ||
|
||
bool IsCallArg() const | ||
{ | ||
return m_parentNode->IsCall(); | ||
|
@@ -366,18 +389,23 @@ class ForwardSubVisitor final : public GenTreeVisitor<ForwardSubVisitor> | |
} | ||
|
||
private: | ||
GenTree** m_use; | ||
GenTree* m_node; | ||
GenTree* m_parentNode; | ||
GenTree** m_use = nullptr; | ||
GenTree* m_node = nullptr; | ||
GenTree* m_parentNode = nullptr; | ||
unsigned m_lclNum; | ||
unsigned m_parentLclNum; | ||
unsigned m_parentLclNum = BAD_VAR_NUM; | ||
#ifdef DEBUG | ||
unsigned m_useCount = 0; | ||
#endif | ||
GenTreeFlags m_useFlags; | ||
GenTreeFlags m_accumulatedFlags; | ||
unsigned m_treeSize; | ||
bool m_livenessBased; | ||
GenTreeFlags m_useFlags = GTF_EMPTY; | ||
GenTreeFlags m_accumulatedFlags = GTF_EMPTY; | ||
// Precise exceptions thrown by the nodes that were visited so far. Note | ||
// that we stop updating this field once we find that two or more separate | ||
// exceptions. | ||
ExceptionSetFlags m_accumulatedExceptions = ExceptionSetFlags::None; | ||
ExceptionSetFlags m_useExceptions = ExceptionSetFlags::None; | ||
unsigned m_treeSize = 0; | ||
bool m_livenessBased; | ||
}; | ||
|
||
//------------------------------------------------------------------------ | ||
|
@@ -689,23 +717,56 @@ bool Compiler::fgForwardSubStatement(Statement* stmt) | |
// | ||
// if the next tree can't change the value of fwdSubNode or be impacted by fwdSubNode effects | ||
// | ||
const bool fwdSubNodeInvariant = ((fwdSubNode->gtFlags & GTF_ALL_EFFECT) == 0); | ||
const bool nextTreeIsPureUpToUse = ((fsv.GetFlags() & (GTF_EXCEPT | GTF_GLOB_REF | GTF_CALL)) == 0); | ||
if (!fwdSubNodeInvariant && !nextTreeIsPureUpToUse) | ||
if (((fwdSubNode->gtFlags & GTF_CALL) != 0) && ((fsv.GetFlags() & GTF_ALL_EFFECT) != 0)) | ||
{ | ||
// Fwd sub may impact global values and or reorder exceptions... | ||
// | ||
JITDUMP(" potentially interacting effects\n"); | ||
JITDUMP(" cannot reorder call with any side effect\n"); | ||
return false; | ||
} | ||
if (((fwdSubNode->gtFlags & GTF_GLOB_REF) != 0) && ((fsv.GetFlags() & GTF_PERSISTENT_SIDE_EFFECTS) != 0)) | ||
{ | ||
JITDUMP(" cannot reorder global reference with persistent side effects\n"); | ||
return false; | ||
} | ||
if (((fwdSubNode->gtFlags & GTF_ORDER_SIDEEFF) != 0) && | ||
((fsv.GetFlags() & (GTF_GLOB_REF | GTF_ORDER_SIDEEFF)) != 0)) | ||
{ | ||
JITDUMP(" cannot reorder ordering side effect with global reference/ordering side effect\n"); | ||
return false; | ||
} | ||
if ((fwdSubNode->gtFlags & GTF_EXCEPT) != 0) | ||
{ | ||
if ((fsv.GetFlags() & GTF_PERSISTENT_SIDE_EFFECTS) != 0) | ||
{ | ||
JITDUMP(" cannot reorder exception with persistent side effect\n"); | ||
return false; | ||
} | ||
|
||
if ((fsv.GetFlags() & GTF_EXCEPT) != 0) | ||
{ | ||
assert(fsv.GetExceptions() != ExceptionSetFlags::None); | ||
if (genCountBits(static_cast<uint32_t>(fsv.GetExceptions())) > 1) | ||
{ | ||
JITDUMP(" cannot reorder different thrown exceptions\n"); | ||
return false; | ||
} | ||
|
||
ExceptionSetFlags fwdSubNodeExceptions = gtCollectExceptions(fwdSubNode); | ||
assert(fwdSubNodeExceptions != ExceptionSetFlags::None); | ||
if (fwdSubNodeExceptions != fsv.GetExceptions()) | ||
{ | ||
JITDUMP(" cannot reorder different thrown exceptions\n"); | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
// If we're relying on purity of fwdSubNode for legality of forward sub, | ||
// do some extra checks for global uses that might not be reflected in the flags. | ||
// | ||
// TODO: remove this once we can trust upstream phases and/or gtUpdateStmtSideEffects | ||
// to set GTF_GLOB_REF properly. | ||
// | ||
if (fwdSubNodeInvariant && ((fsv.GetFlags() & (GTF_CALL | GTF_ASG)) != 0)) | ||
if ((fsv.GetFlags() & GTF_PERSISTENT_SIDE_EFFECTS) != 0) | ||
{ | ||
EffectsVisitor ev(this); | ||
ev.WalkTree(&fwdSubNode, nullptr); | ||
|
@@ -874,7 +935,7 @@ bool Compiler::fgForwardSubStatement(Statement* stmt) | |
fgForwardSubUpdateLiveness(firstLcl, lhsNode->gtPrev); | ||
} | ||
|
||
if (!fwdSubNodeInvariant) | ||
if ((fwdSubNode->gtFlags & GTF_ALL_EFFECT) != 0) | ||
{ | ||
gtUpdateStmtSideEffects(nextStmt); | ||
} | ||
|
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.
Seems odd that
OperExceptions
doesn't really handleGT_CALL
. I wonder if there's any gain to be had by allowing some helper calls to forward sub... probably not much.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 was intentional when I first added this API. It's really because of what @markples mentions below:
OperExceptions
does not make sense for user calls. The return value ofExceptionSetFlags::All
indicates that something can throw all the built-in exceptions, but user calls can throw many more exceptions types. So to model that we would probably add someExceptionSetFlags::UnknownException
, but then the callers would need to either handle it specially or assert that they didn't see it.The handling here by setting
All
is really just relying on forward-sub specific knowledge that the caller is going to handle that in the correct way, it's not actually correct to say thatGTF_CALL
nodes can throw those exceptions only.Definitely something we could try. We would need to generalize things a bit here to track more precise effects, since at that point we are throwing away the "
GTF_CALL
means user call" assumption that the current interference checks in the caller are relying on. (But that might also make sense if we are going to end up with aGT_BOX
that hasGTF_CALL
set on it in the future.)