RFR: 8366888: C2: incorrect assertion predicate with short running long counted loop
Christian Hagedorn
chagedorn at openjdk.org
Fri Oct 10 14:49:53 UTC 2025
On Fri, 12 Sep 2025 08:57:57 GMT, Roland Westrelin <roland at openjdk.org> wrote:
> In:
>
>
> for (int i = 100; i < 1100; i++) {
> v += floatArray[i - 100];
> Objects.checkIndex(i, longRange);
> }
>
>
> The int counted loop has both an int range check and a long range. The
> int range check is optimized first. Assertion predicates are inserted
> above the loop. One predicates checks that:
>
>
> init - 100 <u floatArray.length
>
>
> The loop is then transformed to enable the optimization of the long
> range check. The loop is short running, so there's no need to create a
> loop nest. The counted loop is mostly left as is but, the loop's
> bounds are changed from:
>
>
> for (int i = 100; i < 1100; i++) {
>
>
> to:
>
>
> for (int i = 0; i < 1000; i++) {
>
>
> The reason for that the long range check transformation expects the
> loop to start at 0.
>
> Pre/main/post loops are created. Template Assertion predicates are
> added above the main loop. The loop is unrolled. Initialized assertion
> predicates are created. The one created from the condition:
>
>
> init - 100 <u floatArray.length
>
>
> checks the value of `i` out of the pre loop which is 1. That check fails.
>
> The root cause of the failure is that when bounds of the counted loop
> are changed, template assertion predicates need to be updated with and
> adjusted init input.
>
> When the bounds of the loop are known, the assertion predicates can be
> updated in place. Otherwise, when the loop is speculated to be short
> running, the assertion predicates are updated when they are cloned.
Sorry for letting you wait - I had this on my TODO list for quite some time. The fix looks good to me! I only have some small comments.
Thanks for the attribution and nice that you were able to extract another test that shows the root problem directly!
src/hotspot/share/opto/loopnode.cpp line 1165:
> 1163: ClonePredicateToTargetLoop _clone_predicate_to_loop;
> 1164: PhaseIdealLoop* const _phase;
> 1165: Node* _new_init;
Can be made `const`:
Suggestion:
Node* const _new_init;
src/hotspot/share/opto/loopnode.cpp line 1196:
> 1194: // for (int = 0; i < stop - start; i+= stride) {
> 1195: // Assertion Predicate added so far were with an init value of start. They need to be updated with the new init value of
> 1196: // 0.
Some small suggestions here. I suggest to also add some visualization to quickly see what we do here.
Suggestion:
// For an int counted loop, try_make_short_running_loop() transforms the loop from:
// for (int = start; i < stop; i+= stride) { ... }
// to
// for (int = 0; i < stop - start; i+= stride) { ... }
// Template Assertion Predicates added so far were with an init value of start. They need to be updated with the new
// init value of 0:
// zero
// init |
// | ===> OpaqueLoopInit init
// OpaqueLoopInit \ /
// AddI
//
src/hotspot/share/opto/node.hpp line 2168:
> 2166:
> 2167: // Defines an action that should be taken when we visit a target node in the BFS traversal.
> 2168: virtual void target_node_action(Node* node, uint i) = 0;
Maybe you can name `node` `child` and add the following comment to better describe it:
Suggestion:
// Defines an action that should be taken when we visit a target node in the BFS traversal.
// To give more freedom, we pass the direct child node to the target node such that
// child->in(i) == target node. This allows to also directly replace the target node instead
// of only updating its inputs.
virtual void target_node_action(Node* child, uint i) = 0;
src/hotspot/share/opto/predicates.cpp line 206:
> 204: }
> 205:
> 206: // Clone this Template Assertion Predicate and use the expression passed as argument as init.
Suggestion:
// Clone this Template Assertion Predicate and replace the old OpaqueLoopInit node with 'new_init'.
// Note: 'new_init' could also have the 'OpaqueLoopInit` as parent node further up.
src/hotspot/share/opto/predicates.cpp line 249:
> 247: }
> 248:
> 249: void target_node_action(Node* node, uint i) override {
I also suggest to add an assert here just to be sure:
assert(node->in(i)->is_OpaqueLoopStride(), "must be OpaqueLoopStride");
src/hotspot/share/opto/predicates.cpp line 279:
> 277: }
> 278:
> 279: void target_node_action(Node* node, uint i) override {
Should hold but might still not hurt to add the following assertion:
assert(node->in(i)->is_OpaqueLoopInit(), "must be old OpaqueLoopInit);
src/hotspot/share/opto/predicates.cpp line 1144:
> 1142:
> 1143: // Clones the provided Template Assertion Predicate to the head of the current predicate chain at the target loop and
> 1144: // replace current OpaqueLoopInit with the expression passed as argument.
Suggestion:
// replaces the current OpaqueLoopInit with 'new_init'.
// Note: 'new_init' could also have the 'OpaqueLoopInit` as parent node further up.
test/hotspot/jtreg/compiler/longcountedloops/TestShortCountedLoopWithLongRCBadAssertPredicate2.java line 1:
> 1: /*
Could the two tests also be merged?
-------------
Marked as reviewed by chagedorn (Reviewer).
PR Review: https://git.openjdk.org/jdk/pull/27250#pullrequestreview-3323632416
PR Review Comment: https://git.openjdk.org/jdk/pull/27250#discussion_r2420490429
PR Review Comment: https://git.openjdk.org/jdk/pull/27250#discussion_r2420281093
PR Review Comment: https://git.openjdk.org/jdk/pull/27250#discussion_r2420413917
PR Review Comment: https://git.openjdk.org/jdk/pull/27250#discussion_r2420584197
PR Review Comment: https://git.openjdk.org/jdk/pull/27250#discussion_r2420591008
PR Review Comment: https://git.openjdk.org/jdk/pull/27250#discussion_r2420424787
PR Review Comment: https://git.openjdk.org/jdk/pull/27250#discussion_r2420496983
PR Review Comment: https://git.openjdk.org/jdk/pull/27250#discussion_r2420602420
More information about the hotspot-compiler-dev
mailing list