-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8348572: C2 compilation asserts due to unexpected irreducible loop #23363
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
9a4b906
8003c38
d7b2f3b
352ebb9
209360d
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 |
---|---|---|
|
@@ -5631,14 +5631,23 @@ int PhaseIdealLoop::build_loop_tree_impl(Node* n, int pre_order) { | |
// l is irreducible: we just found a second entry m. | ||
_has_irreducible_loops = true; | ||
RegionNode* secondary_entry = m->as_Region(); | ||
DEBUG_ONLY(secondary_entry->verify_can_be_irreducible_entry();) | ||
|
||
if (!secondary_entry->can_be_irreducible_entry()) { | ||
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. Typo? Why negation? 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. If a secondary entry can not be a secondary (irreducible) entry, then we have a problem, so we do a bailout. Does that make sense? 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. Not completely. The issue is we should avoid creating new irreducible loops. 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.
Absolutely. This is not a fix at all. But since we can catch that the state is wrong here, we should bail out instead of continuing on in production. That is at least a little improvement. The bug-fix would come in a second step, and may be much more complicated as it would have to reconsider what to do about
If But if one of the
If we marked a Does that answer your question? If not, we may have to talk about it offline ;) PS: 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. First, I am fine with this "band-aid" change. I understand that it simple replaces assert with bailout which is fine. There are few states when we come to this part of code:
So we have 8 combinations. I would like to hear reasons in which cases we should bailout and in which not. 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. Yeah, the existing code is not exactly pretty or straight forward 😅 Let me explain what we know when we get here, with
We know that But now we just found out that That means the traversal has left the We now know that This is the
Here about the combinations:
That is already sufficient to justify the bailout here. But let me consider if What if
But we only turn After we know that the
Hence, if we found any secondary entry into a 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. @vnkozlov Does that help you, or do you have more questions? 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. Yes, thank you for explaining this to me. I think I got it finally. |
||
assert(!VerifyNoNewIrreducibleLoops, "A new irreducible loop was created after parsing."); | ||
C->record_method_not_compilable("A new irreducible loop was created after parsing."); | ||
return pre_order; | ||
} | ||
|
||
// Walk up the loop-tree, mark all loops that are already post-visited as irreducible | ||
// Since m is a secondary entry to them all. | ||
while( is_postvisited(l->_head) ) { | ||
l->_irreducible = 1; // = true | ||
RegionNode* head = l->_head->as_Region(); | ||
DEBUG_ONLY(head->verify_can_be_irreducible_entry();) | ||
if (!head->can_be_irreducible_entry()) { | ||
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. Same issue here? 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. Same explanation as above :) |
||
assert(!VerifyNoNewIrreducibleLoops, "A new irreducible loop was created after parsing."); | ||
C->record_method_not_compilable("A new irreducible loop was created after parsing."); | ||
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. If you haven't done that yet, I would suggest to hardcode these bailouts to "always bail" out and run testing to check if the bailout always works. You'll of course get all kinds of test failures but the VM should not crash/assert (you can filter for these in the test results and ignore anything else). 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. As discussed offline: 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. Yes, makes sense. Thanks for checking. |
||
return pre_order; | ||
} | ||
l = l->_parent; | ||
// Check for bad CFG here to prevent crash, and bailout of compile | ||
if (l == nullptr) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. | ||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
* | ||
* This code is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License version 2 only, as | ||
* published by the Free Software Foundation. | ||
* | ||
* This code is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
* version 2 for more details (a copy is included in the LICENSE file that | ||
* accompanied this code). | ||
* | ||
* You should have received a copy of the GNU General Public License version | ||
* 2 along with this work; if not, write to the Free Software Foundation, | ||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
* | ||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
* or visit www.oracle.com if you need additional information or have any | ||
* questions. | ||
*/ | ||
|
||
/* | ||
* @test | ||
* @bug 8348572 | ||
* @summary Tests where new irreducible loop is introduced by split_if. | ||
* | ||
* @run driver compiler.loopopts.TestSplitIfNewIrreducibleLoop | ||
* | ||
* @run main/othervm -Xcomp -XX:PerMethodTrapLimit=0 | ||
* -XX:CompileCommand=compileonly,compiler.loopopts.TestSplitIfNewIrreducibleLoop::test | ||
* compiler.loopopts.TestSplitIfNewIrreducibleLoop | ||
*/ | ||
|
||
package compiler.loopopts; | ||
|
||
public class TestSplitIfNewIrreducibleLoop { | ||
|
||
static class A { | ||
public A parent; | ||
} | ||
|
||
static class B extends A {} | ||
|
||
public static void main(String[] args) { | ||
// Instantiate one each: classes are loaded. | ||
A a = new A(); | ||
B b = new B(); | ||
test(b); | ||
} | ||
|
||
static int test(A parent) { | ||
do { | ||
if (parent instanceof B b) { return 1; } | ||
if (parent != null) { parent = parent.parent; } | ||
if (parent == null) { return 0; } | ||
} while (true); | ||
} | ||
|
||
// Before split_if it looks like this (the instanceof check has already been partial peeled): | ||
// | ||
// if (parent instanceof B b) { return 1; } | ||
// do { | ||
// if (parent != null) { parent = parent.parent; } | ||
// if (parent == null) { return 0; } | ||
// if (parent instanceof B b) { return 1; } | ||
// } while (true); | ||
// | ||
// | ||
// Now, we want to split_if the first if in the loop body, like this: | ||
// | ||
// if (parent instanceof B b) { return 1; } | ||
// if (parent != null) { goto LOOP2; } else { goto LOOP1; } | ||
// do { | ||
// :LOOP1 | ||
// parent = parent.parent; | ||
// :LOOP2 | ||
// if (parent == null) { return 0; } | ||
// if (parent instanceof B b) { return 1; } | ||
// if (parent != null) { goto LOOP2; } else { goto LOOP1; } | ||
// } while (true); | ||
// | ||
// As the comment in ifnode.cpp / split_if says: we know that on the backedge | ||
// "parent" cannot be null, and so we would be able to split the if through | ||
// the region, and on the backedge it would constant fold away, and we would | ||
// only have to check the split if in the loop entry. | ||
// | ||
// Problem: we have introduced an irreducible 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.
Do you plan to add it to our stress testing?
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 cannot currently do that:
And we can do this later:
Does that sound ok to you?
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.
Sounds good. My question was about your future plan when "everything" is fixed.