JDK-8244719 : CTW: C2 compilation fails with "assert(!VerifyHashTableKeys || _hash_lock == 0) failed: remove node from hash table before modifying it"
  • Type: Bug
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 11,12,13,14,15
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2020-05-11
  • Updated: 2024-10-17
  • Resolved: 2020-06-10
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 11 JDK 15 JDK 16
11.0.9-oracleFixed 15 b27Fixed 16Fixed
Description
$ cd test/hotspot/jtreg/testlibrary/ctw 
$ make 
$ cd dist 
$ wget https://cr.openjdk.java.net/~shade/8244719/comsat-jetty-loader-0.7.0.jar
$ ./ctw.sh comsat-jetty-loader-0.7.0.jar

#  Internal Error (/home/shade/trunks/jdk-jdk/src/hotspot/share/opto/node.hpp:414), pid=8257, tid=8307
#  assert(!VerifyHashTableKeys || _hash_lock == 0) failed: remove node from hash table before modifying it
#
# JRE version: OpenJDK Runtime Environment (15.0) (fastdebug build 15-internal+0-adhoc.shade.jdk-jdk)
# Java VM: OpenJDK 64-Bit Server VM (fastdebug 15-internal+0-adhoc.shade.jdk-jdk, mixed mode, sharing, tiered, compressed oops, g1 gc, linux-amd64)
# Problematic frame:
# V  [libjvm.so+0x3cca84]  Node::set_req(unsigned int, Node*)+0x2d4

Current CompileTask:
C2:  13564 16720   !b  4       co.paralleluniverse.strands.concurrent.AbstractQueuedLongSynchronizer$ConditionObject::awaitUntil (758 bytes)

Stack: [0x00007f59968bf000,0x00007f59969c0000],  sp=0x00007f59969bcd00,  free space=1015k
Native frames: (J=compiled Java code, A=aot compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x3cca84]  Node::set_req(unsigned int, Node*)+0x2d4
V  [libjvm.so+0x12e2070]  Parse::merge_common(Parse::Block*, int)+0x4d0
V  [libjvm.so+0x99194f]  Parse::catch_inline_exceptions(SafePointNode*)+0x15cf
V  [libjvm.so+0x12dc777]  Parse::do_exceptions()+0xe7
V  [libjvm.so+0x12e2ec6]  Parse::do_one_block()+0x286
V  [libjvm.so+0x12e41b1]  Parse::do_all_blocks()+0xe1
V  [libjvm.so+0x12e80aa]  Parse::Parse(JVMState*, ciMethod*, float)+0xcba
V  [libjvm.so+0x6d7091]  ParseGenerator::generate(JVMState*)+0x121
V  [libjvm.so+0x8639da]  Compile::Compile(ciEnv*, ciMethod*, int, bool, bool, bool, DirectiveSet*)+0xaba
V  [libjvm.so+0x6d58dc]  C2Compiler::compile_method(ciEnv*, ciMethod*, int, DirectiveSet*)+0xfc
V  [libjvm.so+0x86ef8b]  CompileBroker::invoke_compiler_on_method(CompileTask*)+0x2db
V  [libjvm.so+0x86ffc8]  CompileBroker::compiler_thread_loop()+0x538
V  [libjvm.so+0x166d706]  JavaThread::thread_main_inner()+0x206
V  [libjvm.so+0x1672566]  Thread::call_run()+0xf6
V  [libjvm.so+0x129c03e]  thread_native_entry(Thread*)+0x10e

Logs:
 https://cr.openjdk.java.net/~shade/8244719/hs_err_pid8257.log
 https://cr.openjdk.java.net/~shade/8244719/replay_pid8257.log
Comments
Changeset: bf22f822 Author: Christian Hagedorn <chagedorn@openjdk.org> Date: 2020-06-10 17:56:23 +0000 URL: https://git.openjdk.java.net/lanai/commit/bf22f822
02-07-2020

Fix request (11u) -- will label after testing completed. I would like to downport this for parity with 11.0.9-oracle. Applies clean, but I had to adapt the test to make it compile with 11. http://mail.openjdk.java.net/pipermail/jdk-updates-dev/2020-June/003366.html
25-06-2020

URL: https://hg.openjdk.java.net/jdk/jdk/rev/e8d34f3f6833 User: chagedorn Date: 2020-06-10 16:49:16 +0000
10-06-2020

http://cr.openjdk.java.net/~chagedorn/8244719/webrev.00/ The assertion failure at [5] can be traced back to a wrong assumption made in Parse::Block::init_graph(). It explicitly states in a comment there that we never call next_path_num() along exception paths [1]. But it turns out that this is only true for bytecode generated by Javac which does not seem to produce bytecode where an exception handler is reached by an explicit jump or "fall through" bytecode. An exception handler is only reached with an athrow. However, it is possible to break that assumption with some custom bytecode. The jasm testcase generates such a valid bytecode sequence where an exception handler is reached by jumps from another exception handler: 69: astore_1 70: aload_1 71: aload_0 72: getfield #5 // Field loopCounter:I 75: bipush 10 77: if_icmpge 93 // Explicit jump to exception handler, non-Javac 90: goto 93 // Explicit jump to exception handler, non-Javac 93: astore_1 94: return Exception table: from to target type 0 66 69 Class java/lang/RuntimeException 0 66 93 Class java/lang/Throwable This means that the first time Parse::merge_exception() is called for the exception handler block at bci 93, pnum is set to 3 since there are 2 predecessors (2 jumps to it). In the very first call to merge_common(), is_merged() is still false and we record a state. All following calls to merge_common() for this exception block will take the else case [2]. Once we are processing the blocks for the exception handler at bci 69, we call merge() (and therefore next_path_num()) in do_one_block() [3] twice with target_bci = 93 (2 jumps to bci 93). The last time with pnum = 1 for bci 90: goto and we transform the phi with gvn and set the hash_lock for it to 1 at [4]. Now comes a second bytecode modification trick where we first hit a trap while parsing a block in do_all_blocks(). Therefore, all successor blocks on that path are not merged and skipped in the first iteration of the loop in do_all_blocks() (at this point these blocks seem to be dead). But later we can have a jump back to such a seemingly dead block again. Those are then processed in the second iteration of the loop in do_all_blocks(). If one of these blocks now additionally throw an exception, we can hit this assertion failure. An example could look as follows: Example: // First iteration in do_all_blocks() Parse B1; Parse B2; // Hit trap. Stop parsing on that path, skip on B3 and B4 which immediately follow B2 and have no other predecessors Skip B3; // Was not merged. Assumed to be dead at this point Skip B4; // Was not merged. Assumed to be dead at this point Parse B5; // Discover jump to B3 -> merge B3. Will be processed but only in the next iteration since rpo of B2 is smaller than the one of B5 Parse E1; // Parse exception handler 1 at bci 69 Parse E2; // Parse exception handler 2 at bci 93, apply gvn for phi // Next iteration in do_all_blocks() Parse B3; // Is now merged and ready to be parsed. Has exception to E2: call merge_exception() -> merge_common() with E2 as target and pnum > 1. We hit the assertion at [5] since we already applied a transformation for a phi in the last iteration and therefore have a non-zero hash_lock. As a solution to this problem, I suggest to fix the wrong assumption by changing Parse::Block::init_graph() to also count predecessors for exception blocks. This ensures that [4] is really the last merge for a phi. [1] http://hg.openjdk.java.net/jdk/jdk/file/71ec718a0bd0/src/hotspot/share/opto/parse1.cpp#l1314 [2] http://hg.openjdk.java.net/jdk/jdk/file/71ec718a0bd0/src/hotspot/share/opto/parse1.cpp#l1678 [3] http://hg.openjdk.java.net/jdk/jdk/file/71ec718a0bd0/src/hotspot/share/opto/parse1.cpp#l1508 [4] http://hg.openjdk.java.net/jdk/jdk/file/71ec718a0bd0/src/hotspot/share/opto/parse1.cpp#l1773 [5] http://hg.openjdk.java.net/jdk/jdk/file/71ec718a0bd0/src/hotspot/share/opto/parse1.cpp#l1764
05-06-2020

ILW = Assert during C2 compilation, reproduces with CTW (never observed in normal execution), no known workaround (but disable C2 compilation of affected method) = HLM = P3
11-05-2020