Skip to content

Conversation

@chhagedorn
Copy link
Member

@chhagedorn chhagedorn commented Feb 27, 2025

The patch fixes the issue of creating an Initialized Assertion Predicate at a loop X from a Template Assertion Predicate that was originally created for a loop Y. Using the unrelated loop values from loop Y for the Initialized Assertion Predicate will let it fail during runtime and we execute a halt instruction. This was originally reported with JDK-8305428.

Note that most of the line changes are from new tests.

The Problem

There are multiple test cases triggering the same problem. In the following, when referring to "the test case", I'm referring to testTemplateAssertionPredicateNotRemovedHalt() which was written from scratch and contains more detailed comments explaining how we end up with executing a Halt node in more details.

An Inner Loop without Parse Predicates

The graph in testTemplateAssertionPredicateNotRemovedHalt() looks like this after creating LoopNodes for the outer for and inner while (true) loop:

image

We only have Parse Predicates for the outer loop. Why?

Before beautify loop, we have the following region which merges multiple backedges - the one from the for loop and the one from the while (true) loop:

image

In IdealLoopTree::merge_many_backedges(), we notice that the hottest backedge is hot enough such that it is worth to have a separate merge point region for the inner and outer loop. We set everything up and eventually in IdealLoopTree::split_outer_loop(), we create a second LoopNode.

For this inner LoopNode, we cannot set up Parse Predicates with the same UCTs as used for the outer loop. It would be incorrect when taking the trap to re-execute the inner and outer loop again while having already executed some of the outer loop's iterations. Thus, we get the graph shape with back-to-back LoopNodes as shown above.

Predicates from a Folded Loop End up at Another Loop

As described in the previous section, we have an inner and outer LoopNode while the inner does not have Parse Predicates. In a series of events (see test case comments for more details), we first hoist a range check out of the outer loop during Loop Predication with a Template Assertion Predicate. Then, we fold the outer loop away because we find that it is only running for a single iteration and the backedge is never taken. The Template Assertion Predicate together with the Parse Predicates end up at the inner loop running from i = 80:

image

Creating Initialized Assertion Predicate with Wrong Loop Values

We now split the inner loop by creating pre-main-post loops. In this process, we create new Template Assertion Predicates with the new init value of the main and post loop. We also create Initialized Assertion Predicates from the new templates. But these now use the init value from the inner loop, even though the Assertion Predicates were created with the loop values from the outer loop:

image

iArrShort has only a size of 10 but 512 Phi takes value 80. During runtime, this Initialized Assertion Predicate fails and we crash by executing a halt instruction.


New Update from March 19, 2025:

New Proposed Solution

Why Matching During IGVN Is Difficult and Fragile

After some discussion with @rwestrel (also see comments below), we've decided to not do any predicate matching during IGVN. It is quite fragile and error prone since you need to handle all kinds of different dying predicate shapes. Sometimes it's even impossible to tell for sure if a dying node is a predicate or just an unrelated node. When in doubt, we need to assume it is a predicate because when we wrongly stop predicate iteration, we could miss to update some other predicates which could lead to failures. On the other hand, we might find other unrelated predicates by wrongly treating non-predicate nodes as predicates.

Removing Predicates during PhaseIdealLoop::eliminate_useless_predicates().

Instead of removing no longer needed Template Assertion Predicates during IGVN, we choose a different approach. With the preparation work done with #24013 and #23941, we can now remove Template Assertion Predicates created for an already folded CountedLoop during PhaseIdealLoop::eliminate_useless_predicates().

Each OpaqueTemplateAssertionPredicate node that is associated with Template Assertion Predicate now maintains a reference to its CountedLoop. During loop splitting, the CountedLoop is updated accordingly. For example, when unrolling a loop, the cloned loop will be the new loop head and thus needs to be updated as such in each OpaqueTemplateAssertionPredicate. These updates are implemented in the methods to clone/update Template Assertion Predicates.

Algorithm

When visiting all predicates from all loop in PhaseIdealLoop::eliminate_useless_predicates(), we also check if the loop node, from which we started the predicate iteration, matches the loop node stored in the OpaqueTemplateAssertionPredicate nodes. If not, then we do not mark them useless. This allows us to remove the unrelated OpaqueTemplateAssertionPredicate nodes whose loop nodes were removed during IGVN.

As additional verification, I want to check that all now useless OpaqueTemplateAssertionPredicate nodes do not contain any references to CountedLoop nodes that are still in the graph. However, that does not reliably work before having the full fix with JDK-8350577. I therefore filed JDK-8352418 to keep track of that and follow up with the verification code after JDK-8350577 is integrated.

Additional Updates

  • I had to move PhaseIdealLoop::update_main_loop_assertion_predicates() down to after the loop cloning because we need to have a reference to the cloned CountedLoop node that becomes the new loop head. I adjusted the involved code accordingly.
  • I print the associated loop index in OpaqueTemplateAssertionPredicateNode::dump_spec() for debugging purposes.
  • I applied the review feedback from @eme64 about the test.

I added some PR comments in the code for additional help.

Outdated previous solution - left here to preserve history

Old Proposed Solution

We should remove any Template Assertion Predicate when a CountedLoopNode is folded away. This is implemented in CountedLoopNode::Ideal() to do that right during IGVN when a loop node is folded. This ensures that we do not miss any dying loop.

Implementation Details

  • I introduced a new KillTemplateAssertionPredicates visitor to do that. This required a new TemplateAssertionPredicate::kill_during_igvn() method to directly operate on PhaseIterGVN instead of PhaseIdealLoop.
  • Regular Predicates (i.e. Runtime or Assertion Predicates) all use If nodes with some specific inputs (i.e. flavors of Opaque* nodes) or outputs (i.e. Halt or UCTs). Since we now use PredicateIterator during IGVN, we need to be more careful when a Regular Predicate is being folded away to still recognize it as a Regular Predicate. When we fail to do so, we could stop the iteration and miss predicates above. The existing checks are not strong enough and required the following tweaks for some situations:
    • An Assertion Predicate If has a ConI as input because the Opaque* node was already folded.
      • -> Also check that we have a Halt node on the false path (done with AssertionPredicate::may_be_assertion_predicate_if()).
    • A Regular Predicate If already lost one of its output.
      • -> Treat such a predicate as Runtime Predicate and assume that an Assertion Predicate If always has two outputs (done with RuntimePredicate::is_being_folded_without_uncommon_proj() and AssertionPredicate::may_be_assertion_predicate_if()).
    • Added some comments at the predicate classes to reflect these changes.

Tests

  • Hand written tests together with some tests that triggered issues during the implementation in the initial full Assertion Predicate prototype patch.
  • Tests from the report of JDK-8305428 and its duplicates.

Thanks,
Christian


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8350579: Remove Template Assertion Predicates belonging to a loop once it is folded away (Sub-task - P3)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/23823/head:pull/23823
$ git checkout pull/23823

Update a local copy of the PR:
$ git checkout pull/23823
$ git pull https://git.openjdk.org/jdk.git pull/23823/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 23823

View PR using the GUI difftool:
$ git pr show -t 23823

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/23823.diff

Using Webrev

Link to Webrev Comment

 loop once it is folded away during IGVN
@bridgekeeper
Copy link

bridgekeeper bot commented Feb 27, 2025

👋 Welcome back chagedorn! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Feb 27, 2025

@chhagedorn This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8350579: Remove Template Assertion Predicates belonging to a loop once it is folded away

Reviewed-by: epeter, roland

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been no new commits pushed to the master branch. If another commit should be pushed before you perform the /integrate command, your PR will be automatically rebased. If you prefer to avoid any potential automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot changed the title 8350579: Remove Template Assertion Predicates belonging to a loop once it is folded away during IGVN 8350579: Remove Template Assertion Predicates belonging to a loop once it is folded away during IGVN Feb 27, 2025
@openjdk openjdk bot added the rfr Pull request is ready for review label Feb 27, 2025
@openjdk
Copy link

openjdk bot commented Feb 27, 2025

@chhagedorn The following label will be automatically applied to this pull request:

  • hotspot-compiler

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the hotspot-compiler hotspot-compiler-dev@openjdk.org label Feb 27, 2025
@mlbridge
Copy link

mlbridge bot commented Feb 27, 2025

Webrevs

@rwestrel
Copy link
Contributor

That looks reasonable to me but imaking sure that predicates in the process of being removed are properly stepped over feels like something that could be fragile. So I'm wondering if there would be a way to mark predicates as being for a particular loop (maybe storing the loop's node id they apply to in predicate nodes and making sure it's properly updated as loops are cloned etc.) so when there is a mismatch between the loop and predicate it can be detected?

@chhagedorn
Copy link
Member Author

chhagedorn commented Feb 27, 2025

Thanks Roland for having a look. I agree that it is indeed quite fragile. It started out with a quite simple fix but then I found more and more cases with fuzzing where we have some weird in-between states in IGVN while a predicate is being folded where matching failed. I was not super happy with matching predicates during IGVN which is difficult and error-prone to get right.

So I'm wondering if there would be a way to mark predicates as being for a particular loop (maybe storing the loop's node id they apply to in predicate nodes and making sure it's properly updated as loops are cloned etc.) so when there is a mismatch between the loop and predicate it can be detected?

That's an interesting idea that could work more reliably. Let me think about that more.

Copy link
Contributor

@eme64 eme64 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good, I have a few suggestions and questions :)


// The visitor visits all Template Assertion Predicates and kills them by marking them useless. They will be removed
// during next round of IGVN.
class KillTemplateAssertionPredicates : public PredicateVisitor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class KillTemplateAssertionPredicates : public PredicateVisitor {
class KillTemplateAssertionPredicateVisitor : public PredicateVisitor {

Comment on lines 2515 to 2517
KillTemplateAssertionPredicates kill_template_assertion_predicates(phase->is_IterGVN());
PredicateIterator predicate_iterator(skip_strip_mined()->in(EntryControl));
predicate_iterator.for_each(kill_template_assertion_predicates);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
KillTemplateAssertionPredicates kill_template_assertion_predicates(phase->is_IterGVN());
PredicateIterator predicate_iterator(skip_strip_mined()->in(EntryControl));
predicate_iterator.for_each(kill_template_assertion_predicates);
KillTemplateAssertionPredicateVisitor kill_template_assertion_predicate_visitor(phase->is_IterGVN());
PredicateIterator predicate_iterator(skip_strip_mined()->in(EntryControl));
predicate_iterator.for_each(kill_template_assertion_predicate_visitor);

Nit:
KillTemplateAssertionPredicateVisitor might be nicer because it tells me from the beginning that it is a visitor.
KillTemplateAssertionPredicates had me thinking that is some kind of constructor that goes ahead and kills things already. The plural "predicates" also indicated that it would do that for all predicates.

return false;
}
return has_assertion_predicate_opaque(maybe_success_proj) && has_halt(maybe_success_proj);
return has_assertion_predicate_opaque_or_con_input(maybe_success_proj);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick control question: Could skipping over constants also mean that we traverse up too far at some point? Like skipping not not just the predicates but also an unrelated check that is about to constant fold? I suppose that should not really create issues?

// during next round of IGVN.
class KillTemplateAssertionPredicates : public PredicateVisitor {
PhaseIterGVN* _igvn;
public:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public:
public:

/*
* @test id=NoFlags
* @bug 8288981 8350579
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+AbortVMOnCompilationFailure
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why you are enabling AbortVMOnCompilationFailure?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an additional comment further up in the test to explain the reason.

Comment on lines 158 to 159
// Runs most of the tests except the really time-consuming ones.
static void runAllTests() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a bit of a contradiction 😅

runAllTests -> runAllFastTests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which ones are the really time-consuming ones? And why do you not run them here?

Copy link
Member Author

@chhagedorn chhagedorn Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the comment for now - we run all tests here. This only applied to the full test added with JDK-8350577 from which I extracted these bits. I can revisit this comment and method name there again :-)

@chhagedorn chhagedorn marked this pull request as draft March 7, 2025 09:54
@openjdk openjdk bot removed the rfr Pull request is ready for review label Mar 7, 2025
@chhagedorn
Copy link
Member Author

chhagedorn commented Mar 7, 2025

Thanks Emanuel for your review! Forgot to move this to draft state. As Roland has pointed out, it is quite fragile to do a matching during IGVN where you need to handle all kinds of of dying predicate shapes. I'm currently moving to a non-IGVN solution (#23941 is a first step). I will then update this patch. The problem to fix is still the same though, just with a different solution.

I will get to this once I have integrated some preparatory changes.

@openjdk
Copy link

openjdk bot commented Mar 19, 2025

@chhagedorn this pull request can not be integrated into master due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:

git checkout JDK-8350579
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push

@openjdk openjdk bot added the merge-conflict Pull request has merge conflict with target branch label Mar 19, 2025
@chhagedorn chhagedorn changed the title 8350579: Remove Template Assertion Predicates belonging to a loop once it is folded away during IGVN 8350579: Remove Template Assertion Predicates belonging to a loop once it is folded away Mar 19, 2025
… of IGVN by storing a reference to the loop it originally was created for.
@openjdk openjdk bot removed the merge-conflict Pull request has merge conflict with target branch label Mar 19, 2025
// Search the Assertion Predicates added by loop predication and/or range check elimination and update them according
// to the new stride.
void PhaseIdealLoop::update_main_loop_assertion_predicates(CountedLoopNode* main_loop_head) {
Node* init = main_loop_head->init_trip();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused

loop->record_for_igvn();
loop_head->clear_strip_mined();

update_main_loop_assertion_predicates(clone_head, stride_con);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved down here (see PR description).


// This class is used to replace the input to OpaqueLoopStrideNode with a new node while leaving the other nodes
// unchanged.
class ReplaceOpaqueStrideInput : public BFSActions {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this up because we now call it from TemplateAssertionPredicate and not TempalteAssertionPredicateExpression.

Comment on lines -217 to -218
TemplateAssertionExpression expression(opaque_node());
expression.replace_opaque_stride_input(new_stride, igvn);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was just an indirection which I removed which makes it easier to use.


// Clone this Template Assertion Predicate without modifying any OpaqueLoop*Node inputs.
TemplateAssertionPredicate TemplateAssertionPredicate::clone(Node* new_control, PhaseIdealLoop* phase) const {
TemplateAssertionPredicate TemplateAssertionPredicate::clone(Node* new_control, CountedLoopNode* new_loop_node,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most changes in this file: Changing phase -> _phase and piping the new CountedLoopNode through the code such that we can initialized the new OpaqueTemplateAssertionPredicate nodes with them accordingly.

Comment on lines +577 to +580
OpaqueTemplateAssertionPredicateNode* opaque_clone =
new OpaqueTemplateAssertionPredicateNode(bool_into_opaque_node_clone, new_loop_node);
_phase->C->add_template_assertion_predicate_opaque(opaque_clone);
_phase->register_new_node(opaque_clone, new_control);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't clone the OpaqueTemplateAssertionPredicateNode anymore with DataNodeGraph::clone_with_opaque_loop_transform_strategy but directly here. This allows us to easily set the new_loop_node for it.

void ClonePredicateToTargetLoop::clone_template_assertion_predicate(
const TemplateAssertionPredicate& template_assertion_predicate) {
TemplateAssertionPredicate cloned_template_assertion_predicate =
template_assertion_predicate.clone(_old_target_loop_entry, _target_loop_head->as_CountedLoop(), _phase);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved method from .hpp file here because of incomplete CountedLoopNode when calling as_CountedLoop().

return;
}
replace_opaque_stride_input(template_assertion_predicate);
template_assertion_predicate.update_associated_loop_node(_loop_node);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to clone the Template Assertion Expression and hence we only need to update the loop node.

@chhagedorn chhagedorn marked this pull request as ready for review March 19, 2025 14:31
@chhagedorn
Copy link
Member Author

@rwestrel @eme64 I pushed an updated and added a new section New Proposed Solution in the PR description to explain the changes. I completely reverted the original IGVN based approach and implemented a new one inside PhaseIdealLoop::eliminate_useless_predicates().

@openjdk openjdk bot added the rfr Pull request is ready for review label Mar 19, 2025
Copy link
Contributor

@rwestrel rwestrel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Mar 21, 2025
@chhagedorn
Copy link
Member Author

Thanks Roland for your review!

Copy link
Contributor

@eme64 eme64 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @chhagedorn !

Comment on lines -1707 to +1706
int unrolled_stride_con = main_loop_head->stride_con() * 2;
int unrolled_stride_con = stride_con_before_unroll * 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we assert that stride_con_before_unroll == main_loop_head->stride_con()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not, could we assert something similar?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about somehow asserting here that as well. But the problem is that at this point, we already concatenated the original and the new loop together to represent one round of unrolling. So, we do not find the original loop exit check anymore from which we could have read the stride. That's why I explicitly take the cached stride_con_before_unroll and double it here.

We could have maybe cached the original loop exit node somehow to query it. But I don't think it adds much value since it's as good the original stride which was read from the loop exit node.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. The assert is not that important here.

Comment on lines 1247 to 1267
if (!_loop_node->is_CountedLoop()) {
// Template Assertion Predicate above LoopNode? Must be unrelated because they can only be created above
// CountedLoopNodes during Loop Predication or Range Check Elimination.
return;
}

mark_template_useful_if_matching_loop(template_assertion_predicate);
}

// If the stored loop node does not match the current loop node from which we iterate from, we found a Template
// Assertion Predicate belonging to an already earlier folded loop in the graph. We need to drop this Template
// Assertion Predicate because we are no longer splitting a loop which it belongs to. Moreover, if we do not remove
// this Template Assertion Predicate, we could wrongly be creating Initialized Assertion Predicates from it at the
// new loop which has completely unrelated loop values. These Initialized Assertion Predicates can then fail at
// runtime, and we crash by executing a halt instruction.
void mark_template_useful_if_matching_loop(const TemplateAssertionPredicate& template_assertion_predicate) const {
OpaqueTemplateAssertionPredicateNode* opaque_node = template_assertion_predicate.opaque_node();
if (opaque_node->loop_node() == _loop_node) {
template_assertion_predicate.opaque_node()->mark_useful();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it makes to split this into two methods, but that's subjective 😅

It seems to me that the code in visit is an optimization for what happens in mark_template_useful_if_matching_loop, and does not really make sense on its own.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasons I've split it is the following:

  • The bailout for non-counted loops is actually separate to the marking. So I have a two-step algorithm: bailout + marking which can nicely be split.
  • Having mark_template_useful_if_matching_loop() allows me to quickly read visit() and understand what's going on. Additionally, I can put the details about why we do the marking at the method comment for more interested code readers. Without the extracted method, I would probably need to put an extra "mark template useful if matching loop" comment + the 6 lines of comments at mark_template_useful_if_matching_loop() into the visit() method which makes it harder to grasp.

I would prefer to stick to what I have now - but I admit it's a subjective matter :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I leave it up to you :)

But could opaque_node->loop_node() == _loop_node even be true if we have !_loop_node->is_CountedLoop()? or do we actually need a CountedLoop to even have the match?

Copy link
Member Author

@chhagedorn chhagedorn Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it cannot be true. opaque_node->loop_node() is a CountedLoop. We could have only opaque_node->loop_node() == _loop_node. I think I first had that as an assertion in place but then turned it into a bailout. But you're right, it would already be covered by the check. Given it's a rare edge case, I guess we can get rid of it. Then the reason above does not apply anymore that we have 2 steps but only 1. Then it does not make sense to split it further. I merged it back together.

Copy link
Member Author

@chhagedorn chhagedorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Emanuel for your review and comments!

Comment on lines 1247 to 1267
if (!_loop_node->is_CountedLoop()) {
// Template Assertion Predicate above LoopNode? Must be unrelated because they can only be created above
// CountedLoopNodes during Loop Predication or Range Check Elimination.
return;
}

mark_template_useful_if_matching_loop(template_assertion_predicate);
}

// If the stored loop node does not match the current loop node from which we iterate from, we found a Template
// Assertion Predicate belonging to an already earlier folded loop in the graph. We need to drop this Template
// Assertion Predicate because we are no longer splitting a loop which it belongs to. Moreover, if we do not remove
// this Template Assertion Predicate, we could wrongly be creating Initialized Assertion Predicates from it at the
// new loop which has completely unrelated loop values. These Initialized Assertion Predicates can then fail at
// runtime, and we crash by executing a halt instruction.
void mark_template_useful_if_matching_loop(const TemplateAssertionPredicate& template_assertion_predicate) const {
OpaqueTemplateAssertionPredicateNode* opaque_node = template_assertion_predicate.opaque_node();
if (opaque_node->loop_node() == _loop_node) {
template_assertion_predicate.opaque_node()->mark_useful();
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasons I've split it is the following:

  • The bailout for non-counted loops is actually separate to the marking. So I have a two-step algorithm: bailout + marking which can nicely be split.
  • Having mark_template_useful_if_matching_loop() allows me to quickly read visit() and understand what's going on. Additionally, I can put the details about why we do the marking at the method comment for more interested code readers. Without the extracted method, I would probably need to put an extra "mark template useful if matching loop" comment + the 6 lines of comments at mark_template_useful_if_matching_loop() into the visit() method which makes it harder to grasp.

I would prefer to stick to what I have now - but I admit it's a subjective matter :-)

Comment on lines -1707 to +1706
int unrolled_stride_con = main_loop_head->stride_con() * 2;
int unrolled_stride_con = stride_con_before_unroll * 2;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about somehow asserting here that as well. But the problem is that at this point, we already concatenated the original and the new loop together to represent one round of unrolling. So, we do not find the original loop exit check anymore from which we could have read the stride. That's why I explicitly take the cached stride_con_before_unroll and double it here.

We could have maybe cached the original loop exit node somehow to query it. But I don't think it adds much value since it's as good the original stride which was read from the loop exit node.

Copy link
Contributor

@eme64 eme64 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still approved. My comments were just suggestions / control questions. Thanks for answering 😊

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Mar 25, 2025
@chhagedorn
Copy link
Member Author

Thanks Emanuel for your review! I will submit some more testing with the latest changes before integration.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Mar 25, 2025
@chhagedorn
Copy link
Member Author

Testing looked good.

/integrate

@openjdk
Copy link

openjdk bot commented Mar 25, 2025

Going to push as commit c953e0e.
Since your change was applied there have been 10 commits pushed to the master branch:

  • 67c4405: 8352866: TestLogJIT.java runs wrong test class
  • bab9372: 8352618: Remove old deprecated functionality in the build system
  • 512b9b1: 8196896: Use SYSROOT_CFLAGS in dtrace gensrc
  • 721ef76: 8352696: JFR: assert(false): EA: missing memory path
  • c002b97: 8352676: Opensource JMenu tests - series1
  • bdcac98: 8347459: C2: missing transformation for chain of shifts/multiplications by constants
  • 3d3b782: 8352607: RISC-V: use cmove in min/max when Zicond is supported
  • 9f582e5: 8320997: RISC-V: C2 ReverseV
  • 6879c44: 8351405: G1: Collection set early pruning causes suboptimal region selection
  • aee4d69: 8348829: Remove ObjectMonitor perf counters

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Mar 25, 2025
@openjdk openjdk bot closed this Mar 25, 2025
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Mar 25, 2025
@openjdk
Copy link

openjdk bot commented Mar 25, 2025

@chhagedorn Pushed as commit c953e0e.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hotspot-compiler hotspot-compiler-dev@openjdk.org integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

3 participants