JDK-8296318 : use-def assert: special case undetected loops nested in infinite loops
  • Type: Bug
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 11,17,18,19,20,21
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2022-11-03
  • Updated: 2022-12-19
  • Resolved: 2022-12-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 21
21 b02Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
During the work of JDK-8280126, I found this P2.java, which fails with:

java -Xcomp -XX:CompileCommand=compileonly,P2::test -XX:-TieredCompilation -XX:PerMethodTrapLimit=0 P2.java

# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (/home/emanuel/Documents/fork2-jdk/open/src/hotspot/share/opto/block.cpp:1382), pid=59759, tid=59772
#  assert(is_loop || block->find_node(def) < j) failed: uses must follow definitions
#
# JRE version: Java(TM) SE Runtime Environment (20.0) (slowdebug build 20-internal-2022-10-06-1045569.emanuel...)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (slowdebug 20-internal-2022-10-06-1045569.emanuel..., compiled mode, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
# Problematic frame:
# V  [libjvm.so+0x659166]  PhaseCFG::verify() const+0x4c8

Current CompileTask:
C2:   5503   83    b        P2::test (27 bytes)

Stack: [0x00007f9fde757000,0x00007f9fde858000],  sp=0x00007f9fde853000,  free space=1008k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x659166]  PhaseCFG::verify() const+0x4c8  (block.cpp:1382)
V  [libjvm.so+0x89baf3]  Compile::Code_Gen()+0x229  (compile.cpp:2948)
V  [libjvm.so+0x89229f]  Compile::Compile(ciEnv*, ciMethod*, int, Options, DirectiveSet*)+0x159f  (compile.cpp:863)
V  [libjvm.so+0x780a9b]  C2Compiler::compile_method(ciEnv*, ciMethod*, int, bool, DirectiveSet*)+0x179  (c2compiler.cpp:113)
V  [libjvm.so+0x8b0d32]  CompileBroker::invoke_compiler_on_method(CompileTask*)+0x916  (compileBroker.cpp:2240)
V  [libjvm.so+0x8af99b]  CompileBroker::compiler_thread_loop()+0x3ed  (compileBroker.cpp:1916)
V  [libjvm.so+0x8d00e4]  CompilerThread::thread_entry(JavaThread*, JavaThread*)+0x72  (compilerThread.cpp:58)
V  [libjvm.so+0xc5e006]  JavaThread::thread_main_inner()+0x144  (javaThread.cpp:699)
V  [libjvm.so+0xc5debe]  JavaThread::run()+0x182  (javaThread.cpp:684)
V  [libjvm.so+0x1330627]  Thread::call_run()+0x195  (thread.cpp:224)
V  [libjvm.so+0x10dde55]  thread_native_entry(Thread*)+0x19b  (os_linux.cpp:710)
Comments
Changeset: 736fcd49 Author: Emanuel Peter <epeter@openjdk.org> Date: 2022-12-14 17:25:49 +0000 URL: https://git.openjdk.org/jdk/commit/736fcd49f7cd3aa6f226b2e088415eaf05f97ee8
14-12-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/11642 Date: 2022-12-13 07:49:47 +0000
13-12-2022

Currently testing my fix
12-12-2022

Quick summary: This is another casualty of infinite loops. Just like JDK-8297642. An inner loop of an infinite loop is not attached to the loop-tree. Later, during scheduling we assume that in a block all uses are after the definitions. Except for LoopNodes - there we accept that a phi may have some operations below, that feed back into the phi. Sadly, we do not replace the inner loop's region-node with a LoopNode, as the infinite loop is not attached to the loop-tree. We assert. Idea: can we uncommon_trap infinite loops? Alternative: special case the assert. Check if we are (nested) in an infinite loop, and so maybe the loop head was not turned into a LoopNode.
02-12-2022

I suspect that this is only a but in the verification code, so it would not reproduce in product builds. Did not reproduce on product build of: openjdk 17.0.4 2022-07-19 The assert is wrong in this case. So we only fail in debug builds. Argument: If we had registered the Loop, then we would not have checked/executed the assert. We did not register the Loop since it was part of an infinite-loop subgraph, and we do not attach such loops to the loop tree, and then we do not replace the RegionNode with a LoopNode in beautify_loop.
04-11-2022

ILW = assert in debug, unknown in product; reproduces with specific test and flags; no workaround = MMH = P3
03-11-2022

Please see P2.java.crash.png, it displays the relevant nodes of the graph at the time of the assert. Analysis: We have an infinite loop: "while(true)" (16 Region) During PhaseIdealLoop::build_loop_tree, we build the loop-tree bottom up. However, at that time the loop has no exit, hence we add the NeverBranch node. One other unfortunate consequence is that none of the bottom-up detected loops (16/52 and 26/55) are attached to the loop-tree. We only attach loops once we find a loop exit (we "sort" loops). The inner loop can be attached to the outer, but the outer has no exit and is never attached to the root of the loop-tree. The consequence of this is that we do not do any "optimizations" on the non-attached loops. This is intentional - why waste compile time for loops that are infinite anyway. The unfortunate effect is that we hence do not make LoopNodes out of the found loop-heads for the non-attached loops. During PhaseCFG::verify we check if blocks look reasonable. In the picture, I marked all the control nodes orange, and the data-nodes dark blue. block: B8: # out( B10 B9 ) <- in( N56 N57 ) Freq: 1e-35 26 Region === 26 55 54 [[ 26 23 24 35 ]] !jvms: P2::test @ bci:13 (line 16) 35 Phi === 26 31 36 [[ 32 ]] #int !jvms: P2::test @ bci:13 (line 16) 32 decI_rReg === _ 35 [[ 33 31 ]] #-1/0xffffffff 33 MachProj === 32 [[ ]] #1 31 xorI_rReg_imm === _ 32 [[ 34 35 41 23 23 22 17 ]] #1/0x00000001 !jvms: P2::test @ bci:17 (line 16) 34 MachProj === 31 [[ ]] #1 29 tlsLoadP === 7 [[ 24 18 ]] !jvms: P2::test @ bci:20 (line 23) 24 loadP === 26 10 29 [[ 23 ]] rawptr:BotPTR 23 safePoint_poll_tls === 26 0 10 0 0 24 30 0 31 31 [[ 25 21 ]] !jvms: P2::test @ bci:20 (line 23) 25 MachProj === 23 [[ ]] #1 22 testI_reg === _ 31 [[ 21 ]] #0/0x00000000 21 jmpCon === 23 22 [[ 27 20 ]] P=0.900000, C=-1.000000 !jvms: P2::test @ bci:20 (line 23) 27 IfTrue === 21 [[ 56 ]] #1 !jvms: P2::test @ bci:20 (line 23) 20 IfFalse === 21 [[ 53 ]] #0 !jvms: P2::test @ bci:20 (line 23) We assert for these nodes: def: 31 xorI_rReg_imm === _ 32 [[ 34 35 41 23 23 22 17 ]] n: 35 Phi === 26 31 36 [[ 32 ]] We find that 31 is scheduled after 35, even though that 31 is an input to 35. We have a use-before-def assert. This would be problematic, except we are actually in a loop, we just have not realized it. In fact, had we found the 26 Region to be a LoopNode, we would not have checked this assert. For loops it is ok that Phi nodes have inputs that are scheduled after the Phi, in the same block, since those correspond to the values of the backedge. Why does this bug only reproduce with the flag -XX:PerMethodTrapLimit=0 ? This is because if we do not disallow traps, we insert predicates before the inner loop, which effectively are loop-exits, and then the nested-loop is not an infinite loop anymore. I was able to trigger this bug with irreducible loops, and without this flag, see P.jasm in JDK-8280126.
03-11-2022

Some solution ideas: We could attach loops to the loop-tree even if the subgraph is an infinite loop. For one, it is very rare to have "true-infinite-loops". In most cases, we have some predicate, uncommon trap etc that lead us out of the loop to a HaltNode. We also insert NeverBranch nodes once we detect true-infinite-loops, and on any subsequent pass we then have a loop-exit, and the loop does not count as infinite any more. Thus, the cost would probably be low. It may also be desired to optimize even infinite loops, even if they never terminate they can still do more work, and do the work more efficiently. We may be able to attach the loops directly, or just rerun build_loop_tree. Alternative: Adapt the assert. The difficulty is that we do not know which Regions should actually have been Loop heads. Disabling the assert would be unfortunate. Tagging all regions of infinite loops would also complicate the code unnecessarily, and be bug-prone.
03-11-2022