RFR: 8307084: C2: Vectorized drain loop is not executed for some small trip counts [v4]
Fei Gao
fgao at openjdk.org
Thu Jan 15 18:30:49 UTC 2026
On Thu, 15 Jan 2026 15:04:30 GMT, Fei Gao <fgao at openjdk.org> wrote:
>> Also: how sure are you that the backedge `main_phi->in(LoopNode::LoopBackControl)` is the same as the the value after main `iv_after_main`?
>> What if we did some split-through-phi action at some point? Example:
>>
>> x = ...
>> LOOP:
>> x = op(x);
>> // x now serves as exit value and backedge value
>> exit check;
>> goto LOOP;
>>
>> If we split `op` through the LOOP Phi, we get:
>>
>> x = ...
>> x = op(x);
>> LOOP:
>> // the exit value is the phi
>> exit check;
>> x = op(x);
>> // x after op is the backedge
>> goto LOOP;
>>
>>
>> I'm not sure this currently ever happens, but what if it did?
>
>> * If we cover all phis: we should probably change the naming of the variables to indicate that it is any `phi` and not just the `iv`.
>>
>> What do you think?
>
> Yes, we’re looking at all variables that change as the loop iterates, not just the trip counter. How about renaming it to `main_exit_value` or `value_after_main_loop`?
> Also: how sure are you that the backedge `main_phi->in(LoopNode::LoopBackControl)` is the same as the the value after main `iv_after_main`? What if we did some split-through-phi action at some point? Example:
>
> ```
> x = ...
> LOOP:
> x = op(x);
> // x now serves as exit value and backedge value
> exit check;
> goto LOOP;
> ```
>
> If we split `op` through the LOOP Phi, we get:
>
> ```
> x = ...
> x = op(x);
> LOOP:
> // the exit value is the phi
> exit check;
> x = op(x);
> // x after op is the backedge
> goto LOOP;
> ```
>
> I'm not sure this currently ever happens, but what if it did?
Great question—and yes, that’s exactly why this logic is a bit more involved.
If I understand correctly, in the second scenario—after a split-through-phi—the value we need after the main loop is not the original x directly. Instead, we also need to apply an additional op(x) so that the resulting value can serve as the input to the drain loop.
x = ...
y = op(x);
MAIN_LOOP:
// the exit value is the phi
exit check;
y = op(x);
// x after op is the backedge
goto MAIN_LOOP;
MAIN_EXIT:
y = op(x); // the newly created op(x) outside the main-loop body
DRAIN_LOOP:
// the exit value is the phi
exit check;
y = op(x);
// x after op is the backedge
goto DRAIN_LOOP;
Let's walk through the code step by step.
Node* drain_input = nullptr;
Node* iv_after_main = main_phi->in(LoopNode::LoopBackControl);
At this point, we do **not** assume that `iv_after_main` is already the correct “after-main” value.
if (get_ctrl(iv_after_main) != main_backedge_ctrl) {
drain_input = find_merge_phi_for_vectorized_drain(iv_after_main, main_merge_region);
}
We first check whether `main_phi->in(LoopNode::LoopBackControl)` is pinned in the backedge block.
1. If `get_ctrl(iv_after_main) != main_backedge_ctrl`, then `main_phi->in(LoopNode::LoopBackControl)` is **not** in the backedge block. It should be **either in the main loop body or in the main exit block.**
- If it is still in the main loop body, we won’t be able to find a valid merge phi via its uses, so **`drain_input` remains nullptr**.
- If it is already in the main exit block, we can find an existing valid merge phi via its uses—this corresponds to the first scenario you described, **`drain_input` won't be nullptr**.
2. If `get_ctrl(iv_after_main) == main_backedge_ctrl`, then the `main_phi->in(LoopNode::LoopBackControl)` is pinned in the backedge block. In this case, especially in the presence of transformations like split-through-phi (your second scenario), it clearly cannot be treated as the correct “after-main” value, and **`drain_input` will also remain nullptr**.
if (drain_input == nullptr) {
iv_after_main = clone_up_backedge_goo(main_backedge_ctrl, main_merge_region->in(2),
iv_after_main, visited, clones);
drain_input = PhiNode::make(main_merge_region, iv_after_main);
}
When `drain_input` is still nullptr, we enter this path and call `clone_up_backedge_goo()`. This helper recursively walks and clones the input chain of `main_phi->in(LoopNode::LoopBackControl)` as needed, producing a version of the value that is legal at the main-exit control (`main_merge_region->in(2)`), which is the newly created op(x) outside the main-loop body.
At this point, the returned `iv_after_main` represents the correct value to use after the main loop, even if split-through-phi or similar transformations have occurred. We can then safely create (or later reuse) a merge phi based on this value.
Does this address your concern?
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/22629#discussion_r2695018811
More information about the hotspot-compiler-dev
mailing list