RFR: 8353290: C2: Refactor PhaseIdealLoop::is_counted_loop() [v17]
    Christian Hagedorn 
    chagedorn at openjdk.org
       
    Mon Oct 27 14:01:03 UTC 2025
    
    
  
On Wed, 22 Oct 2025 19:07:32 GMT, Kangcheng Xu <kxu at openjdk.org> wrote:
>> This PR refactors `PhaseIdealLoop::is_counted_loop()` into (mostly) `CountedLoopConverter::is_counted_loop()` and `CountedLoopConverter::convert()` to decouple the detection and conversion code. This enables us to try different loop configurations easily and finally convert once a counted loop is found. 
>> 
>> A nested `PhaseIdealLoop::CountedLoopConverter` class is created to handle the context, but I'm not if this is the best name or place for it. Please let me know what you think.
>> 
>> Blocks [JDK-8336759](https://bugs.openjdk.org/browse/JDK-8336759).
>
> Kangcheng Xu has updated the pull request incrementally with one additional commit since the last revision:
> 
>   mark LoopExitTest::is_valid_with_bt() const
Nice update, it looks very good already! I did a complete pass and left some more comments.
I'm a bit worried about breaking something which might not be noticed because we just don't create a counted loop anymore. Have you thought about some ways to test this? One idea could be to do some runs with some custom logging in place when a counted loop was successfully created and then compare the output to a baseline without your patch and the same logging in place. We should just have some confidence that we do not introduce (performance) regressions.
src/hotspot/share/opto/loopnode.cpp line 1638:
> 1636: #ifdef ASSERT
> 1637: void PhaseIdealLoop::check_counted_loop_shape(IdealLoopTree* loop, Node* x, BasicType bt) {
> 1638:   Node* back_control = loop_exit_control(x, loop);
How far away are we from just using `LoopStructure` and then `LoopStructure::is_valid()` instead?
src/hotspot/share/opto/loopnode.cpp line 1698:
> 1696:     _mask = BoolTest(_mask).commute(); // And commute the exit test
> 1697:   }
> 1698:   if (_phase->is_member(_loop, _phase->get_ctrl(_limit))) { // Limit must be loop-invariant
Here and related loop-(in)variant checks: Could we use `IdealLoopTree::is_invariant()`?
src/hotspot/share/opto/loopnode.cpp line 1709:
> 1707: 
> 1708: // Canonicalize the loop condition if it is 'ne'.
> 1709: bool PhaseIdealLoop::LoopExitTest::canonicalize_mask(jlong stride_con) {
You do not seem to use the return value and thus it can be removed.
src/hotspot/share/opto/loopnode.cpp line 1731:
> 1729: 
> 1730:   // Should not reach
> 1731:   return false;
With the assert above for `stride_con` being 1 or -1, do we need this `if` here? Or are you concerned about when this does not hold in product? But since we are not using the return value anyways, I don't think it makes a difference and you could just drop the `if`.
src/hotspot/share/opto/loopnode.cpp line 1764:
> 1762:   // Get merge point
> 1763:   _xphi = incr->in(1);
> 1764:   _node = incr->in(2);
Should we name `_node` simply `_stride_con`?
src/hotspot/share/opto/loopnode.cpp line 1803:
> 1801:   _is_valid = false;
> 1802: 
> 1803:   _back_control = _phase->loop_exit_control(_head, _loop);
You already call `loop_exit_control()` in the constructor. How about doing the following in the constructor instead?
_back_control(_phase->loop_exit_control(_head, _loop))
_exit_test(_back_control, _loop, _phase)
src/hotspot/share/opto/loopnode.cpp line 1821:
> 1819:   // if (!_iv_incr.is_valid_with_bt(_iv_bt)) {
> 1820:   //   return;
> 1821:   // }
leftover?
src/hotspot/share/opto/loopnode.cpp line 2141:
> 2139:   // If that is not the case, we need to canonicalize the loop exit check by using different values for adjusted_limit
> 2140:   // (see LoopStructure::final_limit_correction()).
> 2141:   // Note that after canonicalization:
Add some space:
Suggestion:
  //
  // Note that after canonicalization:
src/hotspot/share/opto/loopnode.cpp line 2405:
> 2403:     //  }
> 2404:     //
> 2405:     // If the array is shorter than 0x8000 this exits through a AIOOB
Suggestion:
    // If the array is shorter than 0x8000 this exits through an AIOOB
src/hotspot/share/opto/loopnode.cpp line 2412:
> 2410:     const TypeInteger* incr_t = igvn.type(_iv_incr.incr())->is_integer(_iv_bt);
> 2411:     if (limit_t->hi_as_long() > incr_t->hi_as_long()) {
> 2412:       // if the limit can have a higher value than the increment (before the0 phi)
Suggestion:
      // if the limit can have a higher value than the increment (before the phi)
src/hotspot/share/opto/loopnode.cpp line 2477:
> 2475: }
> 2476: 
> 2477: bool CountedLoopConverter::is_safepoint_invalid(SafePointNode* sfpt) {
Can be made `const`.
src/hotspot/share/opto/loopnode.cpp line 2500:
> 2498:   PhaseIterGVN* igvn = &_phase->igvn();
> 2499:   Node* init_control = _head->in(LoopNode::EntryControl);
> 2500:   const jlong stride_con = _structure.stride().compute_non_zero_stride_con(_structure.exit_test().mask(), _iv_bt);
I've noticed that you use this pattern a few times. How about having a `LoopStructure::stride_con()` method instead?
src/hotspot/share/opto/loopnode.cpp line 2505:
> 2503:     Node* cmp_limit = CmpNode::make(_structure.exit_test().limit(), igvn->integercon((stride_con > 0
> 2504:                                                               ? max_signed_integer(_iv_bt)
> 2505:                                                               : min_signed_integer(_iv_bt))
It might be easier to read when we extract the `intercon()` call to a separate variable in a line above.
src/hotspot/share/opto/loopnode.cpp line 2513:
> 2511:   Node* init_trip = _structure.phi()->in(LoopNode::EntryControl);
> 2512:   if (_insert_init_trip_limit_check) {
> 2513:     Node* cmp_limit = CmpNode::make(init_trip, _structure.exit_test().limit(), _iv_bt);
You also seem to be using `_structure.exit_test().limit()` a lot. We could also provide a `structure.limit()` method instead.
src/hotspot/share/opto/loopnode.cpp line 2559:
> 2557:   }
> 2558:   _phase->set_subtree_ctrl(adjusted_limit, false);
> 2559:   _phase->set_subtree_ctrl(adjusted_limit, false);
Duplicated
Suggestion:
src/hotspot/share/opto/loopnode.cpp line 2615:
> 2613:   Node* iffalse = iff->as_If()->proj_out(!(iftrue_op == Op_IfTrue));
> 2614: 
> 2615: // Need to swap loop-exit and loop-back control?
Suggestion:
  // Need to swap loop-exit and loop-back control?
src/hotspot/share/opto/loopnode.cpp line 2747:
> 2745: // If there is one, then we do not need to create an additional Loop Limit Check Predicate.
> 2746: bool CountedLoopConverter::has_dominating_loop_limit_check(Node* init_trip, Node* limit, const jlong stride_con,
> 2747:                                                      const BasicType iv_bt, Node* loop_entry) {
Can be made `const` and indentation is off.
src/hotspot/share/opto/loopnode.hpp line 277:
> 275: 
> 276:   // Match increment with optional truncation
> 277:   class TruncatedIncrement {
You could move this code down to the other loop structure classes.
src/hotspot/share/opto/loopnode.hpp line 1338:
> 1336:       _back_control(back_control),
> 1337:       _loop(loop),
> 1338:       _phase(phase) {}
Maybe also add an assert here that `back_control` is non-null.
src/hotspot/share/opto/loopnode.hpp line 2021:
> 2019:     PhaseIdealLoop::LoopIVStride _stride;
> 2020:     PhiNode* _phi = nullptr;
> 2021:     SafePointNode* _sfpt = nullptr;
Can we name it `_safepoint`?
src/hotspot/share/opto/loopnode.hpp line 2062:
> 2060: #ifdef ASSERT
> 2061:   bool _checked_for_counted_loop = false;
> 2062: #endif
For single lines, you can use `DEBUG_ONLY()`, same at other places as well in the patch.
Suggestion:
  DEBUG_ONLY(bool _checked_for_counted_loop = false;)
src/hotspot/share/opto/loopnode.hpp line 2093:
> 2091:     assert(head != nullptr, "");
> 2092:     assert(loop != nullptr, "");
> 2093:     assert(iv_bt == T_INT || iv_bt == T_LONG, ""); // Loops can be either int or long.
Maybe add an assertion message:
Suggestion:
    assert(phase != nullptr, "must be"); // Fail early if mandatory parameters are null.
    assert(head != nullptr, "must be");
    assert(loop != nullptr, "must be");
    assert(iv_bt == T_INT || iv_bt == T_LONG, "either int or long loops");
src/hotspot/share/opto/loopopts.cpp line 4273:
> 4271:     // With an extra phi for the candidate iv?
> 4272:     // Or the region node is the loop head
> 4273:     if (!loop_exit.incr()->is_Phi() || loop_exit.incr()->in(0) == head) {
You seem to query `incr()` many times - might be worth to extract to a separate `loop_incr` variable and reuse it.
-------------
PR Review: https://git.openjdk.org/jdk/pull/24458#pullrequestreview-3383037106
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465494653
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465359232
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465561539
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465569195
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465429129
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465334860
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465349597
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465517176
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465402120
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465401282
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465572745
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465627831
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465648729
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465653480
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465676111
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465706245
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465716571
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465736725
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465343886
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465575237
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465582423
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465327913
PR Review Comment: https://git.openjdk.org/jdk/pull/24458#discussion_r2465756399
    
    
More information about the hotspot-compiler-dev
mailing list