RFR: 8371674: C2 fails with Missed optimization opportunity in PhaseIterGVN for MoveL2D

Emanuel Peter epeter at openjdk.org
Fri Nov 14 16:11:33 UTC 2025


On Thu, 13 Nov 2025 11:46:14 GMT, Benoît Maillard <bmaillard at openjdk.org> wrote:

> This PR addresses yet another missed optimization in `PhaseIterGVN`. The way this optimization is triggered is a bit different this time though, and the notification is missing in `Node::has_special_unique_user`.
> 
> ## Analysis
> 
> The affected optimization is the transformation of `MoveX2Y (LoadX mem)` into `LoadY mem`. This is implemented in `MoveNode::Ideal`. The optimization is as follows:
> 
> ```c++
> // Fold reinterpret cast into memory operation:
> //    MoveX2Y (LoadX mem) => LoadY mem
> LoadNode* ld = in(1)->isa_Load();
> if (ld != nullptr && (ld->outcnt() == 1)) { // replace only
>   const Type* rt = bottom_type();
>   if (ld->has_reinterpret_variant(rt)) {
>     if (phase->C->post_loop_opts_phase()) {
>       return ld->convert_to_reinterpret_load(*phase, rt);
>     } else {
>       // attempt the transformation once loop opts are over
>       phase->C->record_for_post_loop_opts_igvn(this);
>     }
>   }
> }
> 
> 
> The optimization is triggered only if the input is a `LoadNode` and the `MoveNode` is its only user. This is a relatively unusual pattern.
> 
> The bug was found by the fuzzer. At some point during IGVN, we have the following subgraph:
> 
> 
> CountedLoop    LoadL
>           \     /  \
>             Phi    MoveL2D
> 
> In `RegionNode::Ideal`, we end up calling `set_req_X` on the `Phi` node to delete the edge from the `Phi` node to `LoadL`. As a result, the `LoadL` node only has one user left, and the `MoveNode::Ideal` gets triggered at the next verification pass.
> 
> ## Proposed Solution
> 
> Add this particular case to `Node::has_special_unique_user`, which gets called by `Node::set_req_X`.
> 
> ## Summary of changes
> 
> This PR brings the following changes:
> - Detect the optimization pattern in `Node::has_special_unique_user`.
> - Add new test `TestMissingOptMoveX2YLoadX.java`, initially obtained from the fuzzer and then heavily reduced, both with the usual tools and manually. I tried to get a reproducer for each of the `Move` nodes, but I was only able to get one for `MoveL2D`.
> 
> ### Testing
> 
> - [x] https://github.com/benoitmaillard/jdk/actions?query=branch%3AJDK-8371674
> - [x] tier1-4, plus some internal testing
> 
> Thank you for reviewing!

test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java line 54:

> 52:         while (++e < 37) {
> 53:             for (f = 1; f < 7; f++) {
> 54:                 h >>>= (int)(--g - Double.longBitsToDouble(j[e]));

Drive-by comment, might review more fully next week: could the same happen with `MoveI2F`? Or with `MoveD2L`, i.e. `Double.doubleRawBitsToLong`? Probably yes. Not sure if it's worth duplicating the test, up to you.

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

PR Review Comment: https://git.openjdk.org/jdk/pull/28290#discussion_r2528039234


More information about the hotspot-compiler-dev mailing list