-
Notifications
You must be signed in to change notification settings - Fork 4.6k
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: Validate loop invariant base case as part of IV analysis #97122
Conversation
Move the validation of the base case of the IV analysis into `FlowGraphNaturalLoop::AnalyzeIteration`. Previously the validation for the base case was left up to the users: - Loop cloning tried to accomplish this by checking whether loop inversion had run, but this is not sufficient (dotnet#96623) as nothing guarantees the loop is dominated by the inverted test. Loop cloning also skipped the check entirely for GDV, leading to dotnet#95315. - Unrolling completely neglected to check the condition leading to dotnet#96591. Furthermore, unrolling made implicit assumptions that any `BBJ_COND` init block was an inverted test and downright removed the condition without any checks. This also led to another issue dotnet#97040 where unrolling could uncover new natural loops that had not been canonicalized. This change makes `FlowGraphNaturalLoop::AnalyzeIteration` try to prove that the loop invariant it returns is true in the base case as well as being maintained by the loop. If it cannot prove this then it fails. This fixes all the issues, but sadly uncovers that we were doing a lot of cloning in OSR methods without actually proving legality. We no longer clone in these cases, but we can look into what to do about them separately. Fix dotnet#95315 Fix dotnet#96591 Fix dotnet#96623 Fix dotnet#97040
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsMove the validation of the base case of the IV analysis into
This change makes In addition, loop unrolling no longer removes the init block condition. We leave that up to RBO which is able to remove the trivial checks that loop inversion sometimes creates in generality. This does lead to a few diffs from removing the check later in the JIT pipeline. Diffs expected from less cloning in OSR and from leaving the trivial inverted tests around until RBO. Fix #95315
|
{ | ||
GenTree* enteringJTrue = initBlock->lastStmt()->GetRootNode(); | ||
assert(enteringJTrue->OperIs(GT_JTRUE)); | ||
if (enteringJTrue->gtGetOp1()->OperIsCompare() && ((enteringJTrue->gtGetOp1()->gtFlags & GTF_RELOP_ZTT) != 0)) |
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.
Note that loop cloning checks GTF_RELOP_ZTT
that is set on the test tree within the loop, while this is checking for GTF_RELOP_ZTT
set on the actual zero-trip test that we find through the init block. Loop inversion sets both, but checking the one inside the loop doesn't make sense because nothing validates that the test outside the loop actually dominates the loop. optExtractInitTestIncr
does guarantee that the init block dominates the loop (as far as I can tell), which is why this fixes #96623.
I want to switch this to an IR based check and remove GTF_RELOP_ZTT
so that we do not have this cross phase dependency on loop inversion. We'll be able to remove GTF_RELOP_ZTT
when we do that. However, I'll do that separately.
@@ -5340,7 +5466,7 @@ var_types NaturalLoopIterInfo::IterOperType() | |||
// | |||
bool NaturalLoopIterInfo::IsReversed() | |||
{ | |||
return TestTree->gtGetOp2()->OperIs(GT_LCL_VAR) && ((TestTree->gtGetOp2()->gtFlags & GTF_VAR_ITERATOR) != 0); | |||
return TestTree->gtGetOp2()->OperIsScalarLocal() && (TestTree->gtGetOp2()->AsLclVar()->GetLclNum() == IterVar); |
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.
We'll also be able to delete GTF_VAR_ITERATOR
soon. Currently there is a use left in old loop finding.
/azp run runtime-coreclr jitstress, runtime-coreclr libraries-jitstress, Fuzzlyn |
Azure Pipelines successfully started running 3 pipeline(s). |
cc @dotnet/jit-contrib PTAL @BruceForstall Diffs. As mentioned above primarily from no longer cloning in some OSR methods where we cannot prove it to be safe. Some small ones outside OSR methods from leaving trivial loop-inverted tests around until RBO instead of removing them in unrolling. |
// Is the loop exited when TestTree is true? | ||
bool ExitedOnTrue : 1; |
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.
Since I spent time understanding the logic I added this now, even though it is always expected to be false currently. I expect to make a future change that removes the use of GetLexicallyBottomMostBlock
in AnalyzeIteration
, at which point this will be able to be true.
[Fact] | ||
public static void TestEntryPoint() | ||
{ | ||
for (int i = 0; i < 4; i++) |
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.
Why are the constants in this test chosen to be the values they are?
e.g., is "4" because the constant 4-count loop gets unrolled? 200 because it doesn't get unrolled?
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.
It's to make sure we tier up completely (handling up to 4 potential tiers...)
|
||
iface.Foo(); | ||
|
||
int i = 10000000; |
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.
why 10M? presumably it will fail after 10 iterations? (if it doesn't fail, we don't want the test writing 10MB of data)
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 picked a large constant that has high probability to hit AV if we wrongly elide the bounds check. But you're right, we shouldn't be doing Console.WriteLine
with it, will fix that
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.
Note that arr.Length
is only 1
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.
On second thought, if we hit valid data at + 10000000 then we only print one time anyway, since i < arr.Length
will immediately be false.
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.
Going to leave it like this since it'll at most print one time.
Assert.Throws<IndexOutOfRangeException>(() => Bar(new int[10], new C())); | ||
} | ||
|
||
Thread.Sleep(100); |
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 to allow tiering up?
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.
Yeah, this loop is just to tier up Bar
. The problem is inside Bar
@@ -2006,14 +2006,16 @@ struct NaturalLoopIterInfo | |||
// The local that is the induction variable. | |||
unsigned IterVar = BAD_VAR_NUM; | |||
|
|||
#ifdef DEBUG | |||
// Tree that initializes induction varaible outside the loop. |
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.
// Tree that initializes induction varaible outside the loop. | |
// Tree that initializes induction variable outside the loop. |
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.
Going to address this as part of a follow-up.
…#97122) Move the validation of the base case of the IV analysis into `FlowGraphNaturalLoop::AnalyzeIteration`. Previously the validation for the base case was left up to the users: - Loop cloning tried to accomplish this by checking whether loop inversion had run, but this is not sufficient (dotnet#96623) as nothing guarantees the loop is dominated by the inverted test. Loop cloning also skipped the check entirely for GDV, leading to dotnet#95315. - Unrolling completely neglected to check the condition leading to dotnet#96591. Furthermore, unrolling made implicit assumptions that any `BBJ_COND` init block was an inverted test and downright removed the condition without any checks. This also led to another issue dotnet#97040 where unrolling could uncover new natural loops that had not been canonicalized. This change makes `FlowGraphNaturalLoop::AnalyzeIteration` try to prove that the loop invariant it returns is true in the base case as well as being maintained by the loop. If it cannot prove this then it fails. This fixes all the issues, but sadly uncovers that we were doing a lot of cloning in OSR methods without actually proving legality. We no longer clone in these cases, but we can look into what to do about them separately. Fix dotnet#95315 Fix dotnet#96591 Fix dotnet#96623 Fix dotnet#97040
Move the validation of the base case of the IV analysis into
FlowGraphNaturalLoop::AnalyzeIteration
. Previously the validation for the base case was left up to the users:Loop cloning tried to accomplish this by checking whether loop inversion had run, but this is not sufficient (JIT: Improper zero-trip checking in loop cloning/unrolling #96623) as nothing guarantees the loop is dominated by the inverted test. Loop cloning also skipped the check entirely for GDV, leading to JIT: Loop cloning when the method has GDV skips necessary validity checks #95315.
Unrolling completely neglected to check the condition leading to JIT: Loop unrolling does not validate predecessor BBJ_COND condition properly #96591. Furthermore, unrolling made implicit assumptions that any
BBJ_COND
init block was an inverted test and downright removed the condition without any checks. This also led to another issue Assertion failed 'loop->EntryEdge(0)->getSourceBlock()->KindIs(BBJ_ALWAYS)' #97040 where unrolling could uncover new natural loops that had not been canonicalized.This change makes
FlowGraphNaturalLoop::AnalyzeIteration
try to prove that the loop invariant it returns is true in the base case as well as being maintained by the loop. If it cannot prove this then it fails. This fixes all the issues, but sadly uncovers that we were doing a lot of cloning in OSR methods without actually proving legality, and where it is actually illegal. We no longer clone in these cases, but we can look into what to do about them separately.In addition, loop unrolling no longer removes the init block condition. We leave that up to RBO which is able to remove the trivial checks that loop inversion sometimes creates in generality. This does lead to a few diffs from removing the check later in the JIT pipeline.
Diffs expected from less cloning in OSR and from leaving the trivial inverted tests around until RBO.
Fix #95315
Fix #96591
Fix #96623
Fix #97040