Skip to content

Conversation

@chhagedorn
Copy link
Member

@chhagedorn chhagedorn commented Mar 12, 2025

This patch cleans the Parse and Template Assertion Predicate elimination code up. We now use a single PredicateVisitor and share code in a new EliminateUselessPredicates class which contains the code previously found in PhaseIdealLoop::eliminate_useless_predicates().

Unified Logic to Clean Up Parse and Template Assertion Predicates

We now use the following algorithm:

// Do the following to find and eliminate useless Parse and Template Assertion Predicates:
// 1. Mark all Parse and Template Assertion Predicates "maybe useful".
// 2. Walk through the loop tree and iterate over all Predicates above each loop head. All found Parse and Template
// Assertion Predicates are marked "useful"
// 3. Those Parse and Template Assertion Predicates that are still marked "maybe useful" are now marked "useless" and
// removed in the next round of IGVN.

This is different from the old algorithm where we used a single boolean state _useless. But that does no longer work because when we first mark Template Assertion Predicates useless, we are no longer visiting them when iterating through predicates:

TemplateAssertionPredicate template_assertion_predicate(_current_node->as_IfTrue());
if (!template_assertion_predicate.is_useless()) {
// Only visit if not useless. Otherwise, just skip over it to possibly process other predicates above.
predicate_visitor.visit(template_assertion_predicate);
}

We therefore require a third state. Thus, I introduced a new tri-state PredicateState that provides a special MaybeUseful value which we can set each Predicate to.

Ignoring Useless Parse Predicates

While working on this patch, I've noticed that we are always visiting Parse Predicates - even when they useless. We should change that to align it with what we have for the other Predicates (changed in JDK-8351280). To make this work, we also replace the _useless state in ParsePredicateNode with a new PredicateState.

Sharing Code for Parse and Template Assertion Predicates

With all the mentioned changes in place, I could nicely share code for the elimination of Parse and Template Assertion Predicates in EliminateUselessPredicates by using templates. The following additional changes were required:

  • Changing the template parameter of _template_assertion_predicate_opaques to the more specific OpaqueTemplateAssertionPredicateNode type.
  • Adding accessor methods to get the Predicate lists from Compile.
  • Updating ParsePredicate::mark_useless() to pass in PhaseIterGVN, as done for Assertion Predicates

Note that we still do not directly replace the useless Predicates but rather mark them useless as initiated by JDK-8351280.

Other Included Changes

  • During the various refactoring steps, I somehow dropped the code to add newly cloned Template Assertion Predicate to the _template_assertion_predicate_opaques list. It was done directly in the old cloning methods. This is not relevant for correctness but could hinder some optimizations. I've added the code now in Node::clone() to make sure we do not miss any Template Assertion Predicates (similar to what we do for Parse Predicates already):
    if (n->is_OpaqueTemplateAssertionPredicate()) {
    C->add_template_assertion_predicate_opaque(n->as_OpaqueTemplateAssertionPredicate());
    }
  • Adding verification code to TemplateAssertionPredicate::is_predicate() and InitializedAssertionPredicate::is_predicate() to verify that when we find an Opaque*AssertionPredicate node that we also find the associated Halt node.
  • Some small refactorings here and there like renamings.

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-8350578: Refactor useless Parse and Template Assertion Predicate elimination code by using a PredicateVisitor (Sub-task - P4)

Reviewers

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 24013

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

Using diff file

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

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Mar 12, 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 Mar 12, 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:

8350578: Refactor useless Parse and Template Assertion Predicate elimination code by using a PredicateVisitor

Reviewed-by: epeter, kvn, 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 106 new commits pushed to the master branch:

  • a37d843: 8352015: LIBVERIFY_OPTIMIZATION remove special optimization settings
  • b891bfa: 8352022: RISC-V: Support Zfa fminm_h/fmaxm_h for float16
  • 20f1bca: 8351187: Add JFR monitor notification event
  • 46b3d1d: 8351382: New test containers/docker/TestMemoryWithSubgroups.java is failing
  • 558c015: 8351921: G1: Pinned regions with pinned objects only reachable by native code crash VM
  • f8c2122: 8352138: G1: Remove G1AddMetaspaceDependency.java test
  • e1bcff3: 8345687: Improve the implementation of SegmentFactories::allocateSegment
  • f4ddac5: 8331201: UBSAN enabled build reports on Linux x86_64 runtime error: shift exponent 65 is too large for 64-bit type 'long unsigned int'
  • 38499b3: 8352084: Add more test code in TestSetupAOT.java
  • 19154f7: 8351970: Retire JavaLangAccess::exit
  • ... and 96 more: https://git.openjdk.org/jdk/compare/d90b79a2bd2f8bb6e50aa32aafe47748ef6ebeff...master

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this 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.

@chhagedorn chhagedorn changed the title eibccbtbjbkcbuchvfueljrhftgfvhetnhvvinckgdreJdk 8350578 8350578: Refactor useless Parse and Template Assertion Predicate elimination code by using a PredicateVisitor Mar 12, 2025
@openjdk
Copy link

openjdk bot commented Mar 12, 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 Mar 12, 2025
Comment on lines 504 to 508
bool is_useless() const;
void mark_useless(PhaseIterGVN& igvn);
void mark_maybe_useful();
bool is_useful() const;
void 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.

Needed to move these definitions to the source file because I cannot include predicates.hpp here due to circular dependencies. I solved this by forward declaring PredicateState and moving the definitions to the source file.

Same for these methods for OpaqueTemplateAssertionPredicate further down.

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 add predicates.inline.hpp for this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you mean the enum definition for PredicateState? That could work, so I can just include that header when I need to use the enum. Should I then rather name the hpp file something like predicates_enums.hpp? IIUC, xyz.inline.hpp should be used primarily for inline methods.

Having the separate enum header also allows us to put this one there which makes things cleaner:

// Assertion Predicates are either emitted to check the initial value of a range check in the first iteration or the last
// value of a range check in the last iteration of a loop.
enum class AssertionPredicateType {
None, // Not an Assertion Predicate
InitValue,
LastValue,
// Used for the Initialized Assertion Predicate emitted during Range Check Elimination for the final IV value.
FinalIv
};

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've pushed an update introducing predicates_enums.hpp and also moving some typedefs for predicates in there. Let me know if that's what you had in mind. I could now move almost all definitions back to the header file except for mark_useless() which uses PhaseIterGVN which is not a complete type in the header file - I guess that's okay to just move this one to the source file.

Comment on lines +2219 to +2220
assert(_predicate_state != PredicateState::MaybeUseful, "should only be MaybeUseful when eliminating useless "
"predicates during loop opts");
Copy link
Member Author

Choose a reason for hiding this comment

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

Best effort assert to ensure we are not seeing MaybeUseful anywhere else except during Predicate elimination. Same for OpaqueTemplateAssertionPredicate.

fatal("unknown kind");
}
if (_useless) {
if (_predicate_state == PredicateState::Useless) {
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 only print useless since MaybeUseful is only set for a very brief moment and should normally not be visible when dumping/in IGV dumps.

Comment on lines +514 to +516
if (n->is_OpaqueTemplateAssertionPredicate()) {
C->add_template_assertion_predicate_opaque(n->as_OpaqueTemplateAssertionPredicate());
}
Copy link
Member Author

Choose a reason for hiding this comment

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

See PR description "Other Included Changes".

Comment on lines +176 to +177
assert(!is_template_assertion_predicate || AssertionPredicate::has_halt(maybe_success_proj->as_IfTrue()),
"Template Assertion Predicate must have a Halt Node on the failing path");
Copy link
Member Author

Choose a reason for hiding this comment

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

New verification - see "Other Included Changes" in PR description.

Comment on lines +328 to +333
// Or we have a Region that merges serval paths to a single Halt node. Even though OpaqueInitializedAssertionPredicate
// nodes do not common up (i.e. NO_HASH), we could have Initialized Assertion Predicates from already folded loops
// being now part of the innermost loop. When then further splitting this loop, we could be cloning the If node
// of the Initialized Assertion Predicate (part of the loop body) while the OpaqueInitializedAssertionPredicate is not
// cloned because it's outside the loop body. We end up sharing the OpaqueInitializedAssertionPredicate between the
// original and the cloned If. This should be fine.
Copy link
Member Author

Choose a reason for hiding this comment

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

Reason for not reusing AssertionPredicate::has_halt(). I will revisit the AssertionPredicate class in a future PR, so I have not tried to unify these methods in a way.

return phase->type(in(1));
}

OpaqueTemplateAssertionPredicateNode::OpaqueTemplateAssertionPredicateNode(BoolNode* bol): Node(nullptr, bol),
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 some methods to source file - see comment above for ParsePredicateNode.

Comment on lines +29 to +30
#include "opto/subnode.hpp"

Copy link
Member Author

Choose a reason for hiding this comment

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

Noticed that opcodes.hpp was unused and subnode.hpp missed the opto prefix. Fixed here as well.

// the ParsePredicateNode is not marked useless.
bool is_valid() const {
return _parse_predicate_node != nullptr;
return _parse_predicate_node != nullptr && !_parse_predicate_node->is_useless();
Copy link
Member Author

Choose a reason for hiding this comment

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

Avoids visiting useless Parse Predicates during Predicate iteration.

Copy link
Contributor

Choose a reason for hiding this comment

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

When can _parse_predicate_node be null?

Copy link
Member Author

Choose a reason for hiding this comment

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

When iterating through predicates, we use a PredicateBlockIterator for each Predicate Block, which consists of an optional Parse Predicate and a number of Regular Predicates (Runtime and Assertion Predicates). We could have either already removed the Parse Predicate before here:

// Keep loop predicates and perform optimizations with them
// until no more loop optimizations could be done.
// After that switch predicates off and do more loop optimizations.
if (!C->major_progress() && (C->parse_predicate_count() > 0)) {
C->mark_parse_predicate_nodes_useless(_igvn);
assert(C->parse_predicate_count() == 0, "should be zero now");
if (TraceLoopOpts) {
tty->print_cr("PredicatesOff");
}
C->set_major_progress();
}
}

or there is just no Parse Predicate for this loop. So, when initializing the PredicateBlockIterator, we could pass here a non-Parse-Predicate projection to the constructor of ParsePredicate:

_parse_predicate(start_node, deopt_reason),

We then set _parse_predicate_node to null here due to this mismatch:

static IfTrueNode* init_success_proj(const Node* parse_predicate_proj) {
assert(parse_predicate_proj != nullptr, "must not be null");
return parse_predicate_proj->isa_IfTrue();
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Is the last code snippet the relevant one?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, I copy-pasted the wrong snippet. The null is coming from here:

// Returns the Parse Predicate node if the provided node is a Parse Predicate success proj. Otherwise, return null.
ParsePredicateNode* ParsePredicate::init_parse_predicate(const Node* parse_predicate_proj,
Deoptimization::DeoptReason deopt_reason) {
assert(parse_predicate_proj != nullptr, "must not be null");
if (parse_predicate_proj->is_IfTrue() && parse_predicate_proj->in(0)->is_ParsePredicate()) {
ParsePredicateNode* parse_predicate_node = parse_predicate_proj->in(0)->as_ParsePredicate();
if (parse_predicate_node->deopt_reason() == deopt_reason) {
return parse_predicate_node;
}
}
return nullptr;
}

@chhagedorn chhagedorn marked this pull request as ready for review March 13, 2025 06:51
@openjdk openjdk bot added the rfr Pull request is ready for review label Mar 13, 2025
@mlbridge
Copy link

mlbridge bot commented Mar 13, 2025

Webrevs

Copy link
Contributor

@vnkozlov vnkozlov left a comment

Choose a reason for hiding this comment

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

This looks good to me.

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

Thanks Vladimir for your review!

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.

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 only gave it a quick scan. I'm wondering if there is a naming inconsistency, see below ;)

Comment on lines 1192 to 1195
void EliminateUselessPredicates::mark_all_predicates_non_useful() const {
mark_predicates_on_list_maybe_useful(_parse_predicates);
mark_predicates_on_list_maybe_useful(_template_assertion_predicate_opaques);
}
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
void EliminateUselessPredicates::mark_all_predicates_non_useful() const {
mark_predicates_on_list_maybe_useful(_parse_predicates);
mark_predicates_on_list_maybe_useful(_template_assertion_predicate_opaques);
}
void EliminateUselessPredicates::mark_all_predicates_maybe_useful() const {
mark_predicates_on_list_maybe_useful(_parse_predicates);
mark_predicates_on_list_maybe_useful(_template_assertion_predicate_opaques);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

For consistency?

Copy link
Member Author

Choose a reason for hiding this comment

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

Definitely an inconsistency, good catch! Will update this and the other occurrences.

Comment on lines 1184 to 1190
void EliminateUselessPredicates::eliminate() const {
mark_all_predicates_non_useful();
if (C->has_loops()) {
mark_loop_associated_predicates_useful();
}
mark_non_useful_predicates_useless();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would replace non_useful to maybe_useful to keep it consistent with the comments above.

@chhagedorn
Copy link
Member Author

Thanks Roland and Emanuel for your reviews! I've pushed an updated with your suggestions Emanuel.

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Mar 18, 2025
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.

Thanks for the updates. I'm rubber stamping this, only did a quick scan ;)

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

Thanks Emanuel! I will run some testing again with the recent updates before integration.

@chhagedorn
Copy link
Member Author

/integrate

@openjdk
Copy link

openjdk bot commented Mar 19, 2025

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

  • 577ede7: 8352302: Test sun/security/tools/jarsigner/TimestampCheck.java is failing
  • 20d4fe3: 8351464: Shenandoah: Hang on ShenandoahController::handle_alloc_failure when run test TestAllocHumongousFragment#generational
  • 8e53063: 8352275: Clean up dead code in jpackage revealed with improved negative test coverage
  • c8a11f2: 8352293: jpackage tests build rpm packages on Ubuntu test machines after JDK-8351372
  • 4a02de8: 8352098: -Xrunjdwp fails on static JDK
  • 355b2f3: 8351374: Improve comment about queue.remove timeout in CleanerImpl.run
  • a3540be: 8352163: [AIX] SIGILL in AttachOperation::ReplyWriter::write_fully after 8319055
  • 53c5b93: 8352180: AttachListenerThread causes many tests to timeout on Windows
  • b025d8c: 8350663: AArch64: Enable UseSignumIntrinsic by default
  • a37d843: 8352015: LIBVERIFY_OPTIMIZATION remove special optimization settings
  • ... and 105 more: https://git.openjdk.org/jdk/compare/d90b79a2bd2f8bb6e50aa32aafe47748ef6ebeff...master

Your commit was automatically rebased without conflicts.

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

openjdk bot commented Mar 19, 2025

@chhagedorn Pushed as commit e57b272.

💡 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.

5 participants