RFR: JDK-8287284: C2: loop optimization performs split_thru_phi infinitely many times

Tobias Hartmann thartmann at openjdk.org
Tue Oct 24 10:29:33 UTC 2023


On Fri, 1 Sep 2023 13:13:20 GMT, Tobias Holenstein <tholenstein at openjdk.org> wrote:

> ### Node* phi = PhaseIdealLoop::split_thru_phi(Node* n, Node* region, int policy) 
> The function `PhaseIdealLoop::split_thru_phi` splits Node `n` through a merge point (phi) if there is enough "win". First a clone x<sub>i</sub> of `n` is created for each input of the merge point. The general idea is that we want to replace enough of the x<sub>i</sub> clones with existing nodes or constants. The `policy` indicates how many "wins" are needed for success. A "win" is defined as one of the following:
> - Identity(x<sub>i</sub>) has singleton type (i.e. a constant)
> - Identity(x<sub>i</sub>) returns an already existing node
> - x<sub>i</sub> commons up with an existing node (i.e. hash_find(x<sub>i</sub> != null)
> 
> _Example_
> Looking at the phi (119 Phi) of `n` (135 LoadI) a clone of `n` is created for each input to the phi: 
> - x<sub>1</sub> (140 LoadI) for the input [1] (127 Phi)
> - x<sub>2</sub> (141 LoadI) for the input [2] (90 StoreI)
> 
> If there are enough "wins" (win $\geq$ policy) the wins are applied and a new merge point `phi` (139 Phi) is returned. 
> `n` (135 LoadI) is replaced with `phi` (139 Phi) outside of `PhaseIdealLoop::split_thru_phi` if successful.
> 
> Example with **n =** `135 LoadI`, **region =** `129 Loop`, **policy =** `1` returns **phi =** `139 Phi`
> <img width="286" alt="split_phi" src="https://github.com/openjdk/jdk/assets/71546117/4a1bcd8d-bbfd-4267-9d2b-5ba4e057df72">
> Success because 1 "win" occurs: `88 AddI` = Identity(`141 LoadI`)
> <img width="186" alt="88_identity" src="https://github.com/openjdk/jdk/assets/71546117/af3cb37a-c575-445a-b8d4-967013dd3fb0">
> 
> 
> 
> ### run() has infinite loop of split_thru_phi
> 
> // java -Xbatch -XX:-PartialPeelLoop -XX:CompileCommand=compileonly,Test::run Test.java
> 
> public class Test {
> 
>     public static int cnt = 1;
> 
>     public static void run() {
>         int j = 0;
>         do {
>             j = cnt;
>             for (int k = 0; k < 20000; k++) {
>                 cnt += 2;
>             }
> 
>         } while (++j < 10);
>     }
> 
>     public static void main(String[] args) {
>         for (int i = 0; i < 10; i++ ) {
>             run();
>         }
>     }
> }
> 
> `Compile::optimize_loops` ends up in an endless loop when optimizing run().
> In each `PhaseIdealLoop iteration` a `LoadI` (e.g. 135) is split through a `Phi` (e.g. 119) and the newly created `LoadI` (e.g. 140) is again split through a `Phi` (e.g. 127) creating an endless repeating pattern of `LoadI` (e.g. 144) always connected to the circular dependent `Phis` (119, 127). This results in an ...

Nice analysis, Toby! I added a few suggestions/comments.

src/hotspot/share/opto/loopnode.hpp line 1739:

> 1737:   void update_addp_chain_base(Node* x, Node* old_base, Node* new_base);
> 1738: 
> 1739:   bool has_moved_to_inner_loop(Node* n, Node* region, Node* x);

Suggestion:

  bool moved_to_inner_loop(Node* n, Node* region, Node* x);

src/hotspot/share/opto/loopopts.cpp line 164:

> 162:     } else if (region->is_Loop() && i == LoopNode::LoopBackControl) {
> 163:       // it is not a win if work has moved from an outer to an inner loop
> 164:       if (has_moved_to_inner_loop(n, region, x)) {

Suggestion:

      if (moved_to_inner_loop(n, region, x)) {

src/hotspot/share/opto/loopopts.cpp line 167:

> 165:         wins = 0;
> 166:         break;
> 167:       }

Suggestion:

    } else if (region->is_Loop() && i == LoopNode::LoopBackControl &&
               moved_to_inner_loop(n, region, x)) {
      // it is not a win if 'x' moved from an outer to an inner loop
      wins = 0;
      break;

src/hotspot/share/opto/loopopts.cpp line 232:

> 230: }
> 231: 
> 232: // Check if Node 'x' has moved to inner loop compared to Node 'n'

Suggestion:

// Check if node 'x' moved to an inner loop relative to node 'n'

src/hotspot/share/opto/loopopts.cpp line 233:

> 231: 
> 232: // Check if Node 'x' has moved to inner loop compared to Node 'n'
> 233: bool PhaseIdealLoop::has_moved_to_inner_loop(Node* n, Node* region, Node* x) {

Suggestion:

bool PhaseIdealLoop::moved_to_inner_loop(Node* n, Node* region, Node* x) {

src/hotspot/share/opto/loopopts.cpp line 236:

> 234:   assert(region->is_Loop(), "region should be a loop");
> 235:   IdealLoopTree* n_loop_tree = get_loop(region);
> 236:   for (uint j = 1; j < n->req(); j++) {

Why do you need this loop at all? Wouldn't it be sufficient to just check `x_loop_tree->is_member(n_loop_tree)`?

-------------

PR Review: https://git.openjdk.org/jdk/pull/15536#pullrequestreview-1694406585
PR Review Comment: https://git.openjdk.org/jdk/pull/15536#discussion_r1369882151
PR Review Comment: https://git.openjdk.org/jdk/pull/15536#discussion_r1369882309
PR Review Comment: https://git.openjdk.org/jdk/pull/15536#discussion_r1369878335
PR Review Comment: https://git.openjdk.org/jdk/pull/15536#discussion_r1369878665
PR Review Comment: https://git.openjdk.org/jdk/pull/15536#discussion_r1369882683
PR Review Comment: https://git.openjdk.org/jdk/pull/15536#discussion_r1369939990


More information about the hotspot-compiler-dev mailing list