Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8263227

C2: inconsistent spilling due to dead nodes in exception block



    • b19




        The attached program Test.java (reduced from the original report, see below) triggers an assertion failure when compiled by C2 as follows:

        $ java -Xbatch -XX:+StressGCM -XX:StressSeed=0 -XX:CompileCommand=dontinline,java.lang.Integer::* Test.java
        # Internal Error (../../src/hotspot/share/opto/buildOopMap.cpp:240), pid=19789, tid=19802
        # assert(def) failed: since live better have reaching def


        The failure is caused by an unfortunate interaction between global code motion, call-catch cleanup, and live-range splitting:

        1. Global code motion (on stress mode) places some users of a call result in between the call and its corresponding catch node.

        2. After local scheduling, call-catch cleanup (PhaseCFG::call_catch_cleanup()) sinks the call result users to the call's fall-through and exception blocks, failing to remove the dead cloned users in the exception block.

         - 3. During register allocation, the call result value is selected for spilling. When its live range is split (PhaseChaitin::split_DEF()), PhaseChaitin::insert_proj() places the spill in the call's fall-through block (on the assumption that call results are only used in their fall-through paths), but marks the spilled value as reaching the exception path as well.

         - 4. When visiting the exception block, live-range splitting replaces uses of the call result by the value spilled in the fall-through path, following the inaccurate reaching definitions information. This leads to an inconsistent program form in which the spill in the fall-through block does not dominate its use in the exception block. This inconsistency manifests itself late in PhaseOutput::Output(), when reaching definitions are re-computed.

        The attached PDF illustrates the failure for Test.java. After global and local code motion (page 1), the users 29,30-35,40 of the result value 36 from call 12 are placed in between the call (12) and the catch node (10). After call-catch cleanup (page 2), the users are sunk into the fall-through block (67-74) and the exception block (75-82), despite 75-82 being dead. Finally, after register allocation (page 3), the call value 36 is spilled in the fall-through block (89), and the spilled value is used by 84 and 98 in the non-dominated exception block.

        The current frequency-based global code motion heuristics prevent this failure, as they never hoist call result users to the call block. Hence, the failure can only happen on stress mode (StressGCM), and possibly for irreducible control-flow graphs where execution frequency information can be inaccurate (JDK-8255763, JDK-8258895).

        Potential solutions include: ensuring dead code cloned by PhaseCFG::call_catch_cleanup() is always removed, forbidding global code motion to place call result users in the call block, and extending live-range splitting to introduce spills in exception blocks as well.


        $ cd test/hotspot/jtreg/testlibrary/ctw
        $ make
        $ cd dist
        $ wget https://repo1.maven.org/maven2/com/flagstone/transform/3.0/transform-3.0.jar
        $ JAVA_OPTIONS="-XX:+StressGCM" ./ctw.sh transform-3.0.jar

        # Internal Error (/home/shade/trunks/jdk/src/hotspot/share/opto/buildOopMap.cpp:240), pid=2468897, tid=2469076
        # assert(def) failed: since live better have reaching def
        # JRE version: OpenJDK Runtime Environment (17.0) (fastdebug build 17-internal+0-adhoc.shade.jdk)
        # Java VM: OpenJDK 64-Bit Server VM (fastdebug 17-internal+0-adhoc.shade.jdk, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
        # Problematic frame:
        # V [libjvm.so+0x70ab0c][279] com.flagstone.transform.video.VideoFrame
          OopFlow::build_oop_map(Node*, int, PhaseRegAlloc*, int*)+0x63c

        Note: this issue seems intermittent and dependent on randomness for stress options? Run multiple times to get the failure. The CTW on full JAR takes about 10 seconds.


          1. failure-example.pdf
            77 kB
          2. hs_err_pid2471501.log
            89 kB
          3. replay_pid2471501.log
            155 kB
          4. Test.java
            0.3 kB

          Issue Links



                rcastanedalo Roberto Castaneda Lozano
                shade Aleksey Shipilev
                0 Vote for this issue
                6 Start watching this issue