8223213: Implement fast class initialization checks on x86-64
Reviewed-by: kvn, redestad, dholmes, mdoerr, coleenp
--- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp Thu May 30 13:39:13 2019 +0300
@@ -316,6 +316,9 @@
return start_offset;
}
+void LIR_Assembler::clinit_barrier(ciMethod* method) {
+ ShouldNotReachHere(); // not implemented
+}
void LIR_Assembler::jobject2reg(jobject o, Register reg) {
if (o == NULL) {
--- a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp Thu May 30 13:39:13 2019 +0300
@@ -191,6 +191,9 @@
return offset;
}
+void LIR_Assembler::clinit_barrier(ciMethod* method) {
+ ShouldNotReachHere(); // not implemented
+}
void LIR_Assembler::jobject2reg_with_patching(Register reg, CodeEmitInfo* info) {
jobject o = (jobject)Universe::non_oop_word();
--- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp Thu May 30 13:39:13 2019 +0300
@@ -79,6 +79,9 @@
return offset;
}
+void LIR_Assembler::clinit_barrier(ciMethod* method) {
+ ShouldNotReachHere(); // not implemented
+}
void LIR_Assembler::osr_entry() {
// On-stack-replacement entry sequence:
--- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp Thu May 30 13:39:13 2019 +0300
@@ -81,6 +81,10 @@
return offset;
}
+void LIR_Assembler::clinit_barrier(ciMethod* method) {
+ ShouldNotReachHere(); // not implemented
+}
+
void LIR_Assembler::osr_entry() {
// On-stack-replacement entry sequence (interpreter frame layout described in interpreter_sparc.cpp):
//
--- a/src/hotspot/cpu/sparc/c1_LIRAssembler_sparc.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/sparc/c1_LIRAssembler_sparc.cpp Thu May 30 13:39:13 2019 +0300
@@ -172,6 +172,9 @@
return offset;
}
+void LIR_Assembler::clinit_barrier(ciMethod* method) {
+ ShouldNotReachHere(); // not implemented
+}
void LIR_Assembler::osr_entry() {
// On-stack-replacement entry sequence (interpreter frame layout described in interpreter_sparc.cpp):
--- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp Thu May 30 13:39:13 2019 +0300
@@ -359,6 +359,23 @@
return offset;
}
+void LIR_Assembler::clinit_barrier(ciMethod* method) {
+ assert(VM_Version::supports_fast_class_init_checks(), "sanity");
+ assert(method->holder()->is_being_initialized() || method->holder()->is_initialized(),
+ "initialization should have been started");
+
+ Label L_skip_barrier;
+ Register klass = rscratch1;
+ Register thread = LP64_ONLY( r15_thread ) NOT_LP64( noreg );
+ assert(thread != noreg, "x86_32 not implemented");
+
+ __ mov_metadata(klass, method->holder()->constant_encoding());
+ __ clinit_barrier(klass, thread, &L_skip_barrier /*L_fast_path*/);
+
+ __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub()));
+
+ __ bind(L_skip_barrier);
+}
void LIR_Assembler::jobject2reg_with_patching(Register reg, CodeEmitInfo* info) {
jobject o = NULL;
--- a/src/hotspot/cpu/x86/interp_masm_x86.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp Thu May 30 13:39:13 2019 +0300
@@ -487,7 +487,8 @@
Register tmp,
int bcp_offset,
size_t index_size) {
- assert(cache != tmp, "must use different register");
+ assert_different_registers(cache, tmp);
+
get_cache_index_at_bcp(tmp, bcp_offset, index_size);
assert(sizeof(ConstantPoolCacheEntry) == 4 * wordSize, "adjust code below");
// convert from field index to ConstantPoolCacheEntry index
@@ -501,8 +502,9 @@
}
// Load object from cpool->resolved_references(index)
-void InterpreterMacroAssembler::load_resolved_reference_at_index(
- Register result, Register index, Register tmp) {
+void InterpreterMacroAssembler::load_resolved_reference_at_index(Register result,
+ Register index,
+ Register tmp) {
assert_different_registers(result, index);
get_constant_pool(result);
@@ -516,14 +518,32 @@
}
// load cpool->resolved_klass_at(index)
-void InterpreterMacroAssembler::load_resolved_klass_at_index(Register cpool,
- Register index, Register klass) {
+void InterpreterMacroAssembler::load_resolved_klass_at_index(Register klass,
+ Register cpool,
+ Register index) {
+ assert_different_registers(cpool, index);
+
movw(index, Address(cpool, index, Address::times_ptr, sizeof(ConstantPool)));
Register resolved_klasses = cpool;
movptr(resolved_klasses, Address(cpool, ConstantPool::resolved_klasses_offset_in_bytes()));
movptr(klass, Address(resolved_klasses, index, Address::times_ptr, Array<Klass*>::base_offset_in_bytes()));
}
+void InterpreterMacroAssembler::load_resolved_method_at_index(int byte_no,
+ Register method,
+ Register cache,
+ Register index) {
+ assert_different_registers(cache, index);
+
+ const int method_offset = in_bytes(
+ ConstantPoolCache::base_offset() +
+ ((byte_no == TemplateTable::f2_byte)
+ ? ConstantPoolCacheEntry::f2_offset()
+ : ConstantPoolCacheEntry::f1_offset()));
+
+ movptr(method, Address(cache, index, Address::times_ptr, method_offset)); // get f1 Method*
+}
+
// Generate a subtype check: branch to ok_is_subtype if sub_klass is a
// subtype of super_klass.
//
--- a/src/hotspot/cpu/x86/interp_masm_x86.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/interp_masm_x86.hpp Thu May 30 13:39:13 2019 +0300
@@ -124,9 +124,14 @@
void load_resolved_reference_at_index(Register result, Register index, Register tmp = rscratch2);
// load cpool->resolved_klass_at(index)
- void load_resolved_klass_at_index(Register cpool, // the constant pool (corrupted on return)
- Register index, // the constant pool index (corrupted on return)
- Register klass); // contains the Klass on return
+ void load_resolved_klass_at_index(Register klass, // contains the Klass on return
+ Register cpool, // the constant pool (corrupted on return)
+ Register index); // the constant pool index (corrupted on return)
+
+ void load_resolved_method_at_index(int byte_no,
+ Register method,
+ Register cache,
+ Register index);
NOT_LP64(void f2ieee();) // truncate ftos to 32bits
NOT_LP64(void d2ieee();) // truncate dtos to 64bits
--- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp Thu May 30 13:39:13 2019 +0300
@@ -4603,6 +4603,29 @@
}
+void MacroAssembler::clinit_barrier(Register klass, Register thread, Label* L_fast_path, Label* L_slow_path) {
+ assert(L_fast_path != NULL || L_slow_path != NULL, "at least one is required");
+
+ Label L_fallthrough;
+ if (L_fast_path == NULL) {
+ L_fast_path = &L_fallthrough;
+ }
+
+ // Fast path check: class is fully initialized
+ cmpb(Address(klass, InstanceKlass::init_state_offset()), InstanceKlass::fully_initialized);
+ jcc(Assembler::equal, *L_fast_path);
+
+ // Fast path check: current thread is initializer thread
+ cmpptr(thread, Address(klass, InstanceKlass::init_thread_offset()));
+ if (L_slow_path != NULL) {
+ jcc(Assembler::notEqual, *L_slow_path);
+ } else {
+ jcc(Assembler::equal, *L_fast_path);
+ }
+
+ bind(L_fallthrough);
+}
+
void MacroAssembler::cmov32(Condition cc, Register dst, Address src) {
if (VM_Version::supports_cmov()) {
cmovl(cc, dst, src);
@@ -5195,20 +5218,22 @@
void MacroAssembler::load_mirror(Register mirror, Register method, Register tmp) {
// get mirror
const int mirror_offset = in_bytes(Klass::java_mirror_offset());
- movptr(mirror, Address(method, Method::const_offset()));
- movptr(mirror, Address(mirror, ConstMethod::constants_offset()));
- movptr(mirror, Address(mirror, ConstantPool::pool_holder_offset_in_bytes()));
+ load_method_holder(mirror, method);
movptr(mirror, Address(mirror, mirror_offset));
resolve_oop_handle(mirror, tmp);
}
void MacroAssembler::load_method_holder_cld(Register rresult, Register rmethod) {
- movptr(rresult, Address(rmethod, Method::const_offset()));
- movptr(rresult, Address(rresult, ConstMethod::constants_offset()));
- movptr(rresult, Address(rresult, ConstantPool::pool_holder_offset_in_bytes()));
+ load_method_holder(rresult, rmethod);
movptr(rresult, Address(rresult, InstanceKlass::class_loader_data_offset()));
}
+void MacroAssembler::load_method_holder(Register holder, Register method) {
+ movptr(holder, Address(method, Method::const_offset())); // ConstMethod*
+ movptr(holder, Address(holder, ConstMethod::constants_offset())); // ConstantPool*
+ movptr(holder, Address(holder, ConstantPool::pool_holder_offset_in_bytes())); // InstanceKlass*
+}
+
void MacroAssembler::load_klass(Register dst, Register src) {
#ifdef _LP64
if (UseCompressedClassPointers) {
--- a/src/hotspot/cpu/x86/macroAssembler_x86.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/macroAssembler_x86.hpp Thu May 30 13:39:13 2019 +0300
@@ -317,6 +317,8 @@
void load_mirror(Register mirror, Register method, Register tmp = rscratch2);
void load_method_holder_cld(Register rresult, Register rmethod);
+ void load_method_holder(Register holder, Register method);
+
// oop manipulations
void load_klass(Register dst, Register src);
void store_klass(Register dst, Register src);
@@ -581,6 +583,11 @@
Register temp_reg,
Label& L_success);
+ void clinit_barrier(Register klass,
+ Register thread,
+ Label* L_fast_path = NULL,
+ Label* L_slow_path = NULL);
+
// method handles (JSR 292)
Address argument_address(RegisterOrConstant arg_slot, int extra_slot_offset = 0);
--- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp Thu May 30 13:39:13 2019 +0300
@@ -974,6 +974,27 @@
BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler();
bs->c2i_entry_barrier(masm);
+ // Class initialization barrier for static methods
+ if (VM_Version::supports_fast_class_init_checks()) {
+ Label L_skip_barrier;
+ Register method = rbx;
+
+ { // Bypass the barrier for non-static methods
+ Register flags = rscratch1;
+ __ movl(flags, Address(method, Method::access_flags_offset()));
+ __ testl(flags, JVM_ACC_STATIC);
+ __ jcc(Assembler::zero, L_skip_barrier); // non-static
+ }
+
+ Register klass = rscratch1;
+ __ load_method_holder(klass, method);
+ __ clinit_barrier(klass, r15_thread, &L_skip_barrier /*L_fast_path*/);
+
+ __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path
+
+ __ bind(L_skip_barrier);
+ }
+
gen_c2i_adapter(masm, total_args_passed, comp_args_on_stack, sig_bt, regs, skip_fixup);
__ flush();
@@ -2140,6 +2161,17 @@
int vep_offset = ((intptr_t)__ pc()) - start;
+ if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) {
+ Label L_skip_barrier;
+ Register klass = r10;
+ __ mov_metadata(klass, method->method_holder()); // InstanceKlass*
+ __ clinit_barrier(klass, r15_thread, &L_skip_barrier /*L_fast_path*/);
+
+ __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path
+
+ __ bind(L_skip_barrier);
+ }
+
#ifdef COMPILER1
// For Object.hashCode, System.identityHashCode try to pull hashCode from object header if available.
if ((InlineObjectHash && method->intrinsic_id() == vmIntrinsics::_hashCode) || (method->intrinsic_id() == vmIntrinsics::_identityHashCode)) {
--- a/src/hotspot/cpu/x86/templateTable_x86.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/templateTable_x86.cpp Thu May 30 13:39:13 2019 +0300
@@ -2719,12 +2719,13 @@
}
void TemplateTable::resolve_cache_and_index(int byte_no,
- Register Rcache,
+ Register cache,
Register index,
size_t index_size) {
const Register temp = rbx;
- assert_different_registers(Rcache, index, temp);
-
+ assert_different_registers(cache, index, temp);
+
+ Label L_clinit_barrier_slow;
Label resolved;
Bytecodes::Code code = bytecode();
@@ -2735,17 +2736,32 @@
}
assert(byte_no == f1_byte || byte_no == f2_byte, "byte_no out of range");
- __ get_cache_and_index_and_bytecode_at_bcp(Rcache, index, temp, byte_no, 1, index_size);
+ __ get_cache_and_index_and_bytecode_at_bcp(cache, index, temp, byte_no, 1, index_size);
__ cmpl(temp, code); // have we resolved this bytecode?
__ jcc(Assembler::equal, resolved);
// resolve first time through
+ // Class initialization barrier slow path lands here as well.
+ __ bind(L_clinit_barrier_slow);
address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache);
__ movl(temp, code);
__ call_VM(noreg, entry, temp);
// Update registers with resolved info
- __ get_cache_and_index_at_bcp(Rcache, index, 1, index_size);
+ __ get_cache_and_index_at_bcp(cache, index, 1, index_size);
+
__ bind(resolved);
+
+ // Class initialization barrier for static methods
+ if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) {
+ const Register method = temp;
+ const Register klass = temp;
+ const Register thread = LP64_ONLY(r15_thread) NOT_LP64(noreg);
+ assert(thread != noreg, "x86_32 not supported");
+
+ __ load_resolved_method_at_index(byte_no, method, cache, index);
+ __ load_method_holder(klass, method);
+ __ clinit_barrier(klass, thread, NULL /*L_fast_path*/, &L_clinit_barrier_slow);
+ }
}
// The cache and index registers must be set before call
@@ -2794,11 +2810,6 @@
assert_different_registers(itable_index, cache, index);
// determine constant pool cache field offsets
assert(is_invokevirtual == (byte_no == f2_byte), "is_invokevirtual flag redundant");
- const int method_offset = in_bytes(
- ConstantPoolCache::base_offset() +
- ((byte_no == f2_byte)
- ? ConstantPoolCacheEntry::f2_offset()
- : ConstantPoolCacheEntry::f1_offset()));
const int flags_offset = in_bytes(ConstantPoolCache::base_offset() +
ConstantPoolCacheEntry::flags_offset());
// access constant pool cache fields
@@ -2807,7 +2818,7 @@
size_t index_size = (is_invokedynamic ? sizeof(u4) : sizeof(u2));
resolve_cache_and_index(byte_no, cache, index, index_size);
- __ movptr(method, Address(cache, index, Address::times_ptr, method_offset));
+ __ load_resolved_method_at_index(byte_no, method, cache, index);
if (itable_index != noreg) {
// pick up itable or appendix index from f2 also:
@@ -3862,9 +3873,7 @@
__ profile_virtual_call(rdx, rbcp, rlocals);
// Get declaring interface class from method, and itable index
- __ movptr(rax, Address(rbx, Method::const_offset()));
- __ movptr(rax, Address(rax, ConstMethod::constants_offset()));
- __ movptr(rax, Address(rax, ConstantPool::pool_holder_offset_in_bytes()));
+ __ load_method_holder(rax, rbx);
__ movl(rbx, Address(rbx, Method::itable_index_offset()));
__ subl(rbx, Method::itable_index_max);
__ negl(rbx);
@@ -4003,7 +4012,7 @@
__ jcc(Assembler::notEqual, slow_case_no_pop);
// get InstanceKlass
- __ load_resolved_klass_at_index(rcx, rdx, rcx);
+ __ load_resolved_klass_at_index(rcx, rcx, rdx);
__ push(rcx); // save the contexts of klass for initializing the header
// make sure klass is initialized & doesn't have finalizer
@@ -4197,7 +4206,7 @@
// Get superklass in rax and subklass in rbx
__ bind(quicked);
__ mov(rdx, rax); // Save object in rdx; rax needed for subtype check
- __ load_resolved_klass_at_index(rcx, rbx, rax);
+ __ load_resolved_klass_at_index(rax, rcx, rbx);
__ bind(resolved);
__ load_klass(rbx, rdx);
@@ -4263,7 +4272,7 @@
// Get superklass in rax and subklass in rdx
__ bind(quicked);
__ load_klass(rdx, rax);
- __ load_resolved_klass_at_index(rcx, rbx, rax);
+ __ load_resolved_klass_at_index(rax, rcx, rbx);
__ bind(resolved);
--- a/src/hotspot/cpu/x86/vm_version_x86.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/vm_version_x86.hpp Thu May 30 13:39:13 2019 +0300
@@ -936,6 +936,11 @@
// the intrinsic for java.lang.Thread.onSpinWait()
static bool supports_on_spin_wait() { return supports_sse2(); }
+ // x86_64 supports fast class initialization checks for static methods.
+ static bool supports_fast_class_init_checks() {
+ return LP64_ONLY(true) NOT_LP64(false); // not implemented on x86_32
+ }
+
// support functions for virtualization detection
private:
static void check_virt_cpuid(uint32_t idx, uint32_t *regs);
--- a/src/hotspot/cpu/x86/x86_64.ad Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/cpu/x86/x86_64.ad Thu May 30 13:39:13 2019 +0300
@@ -874,6 +874,22 @@
int framesize = C->frame_size_in_bytes();
int bangsize = C->bang_size_in_bytes();
+ if (C->clinit_barrier_on_entry()) {
+ assert(VM_Version::supports_fast_class_init_checks(), "sanity");
+ assert(C->method()->holder()->is_being_initialized() || C->method()->holder()->is_initialized(),
+ "initialization should have been started");
+
+ Label L_skip_barrier;
+ Register klass = rscratch1;
+
+ __ mov_metadata(klass, C->method()->holder()->constant_encoding());
+ __ clinit_barrier(klass, r15_thread, &L_skip_barrier /*L_fast_path*/);
+
+ __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path
+
+ __ bind(L_skip_barrier);
+ }
+
__ verified_entry(framesize, C->need_stack_bang(bangsize)?bangsize:0, false, C->stub_function() != NULL);
C->set_frame_complete(cbuf.insts_size());
--- a/src/hotspot/share/c1/c1_LIRAssembler.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/c1/c1_LIRAssembler.cpp Thu May 30 13:39:13 2019 +0300
@@ -162,6 +162,9 @@
return !method->is_static();
}
+bool LIR_Assembler::needs_clinit_barrier_on_entry(ciMethod* method) const {
+ return VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier();
+}
int LIR_Assembler::code_offset() const {
return _masm->offset();
@@ -621,6 +624,9 @@
}
offsets()->set_value(CodeOffsets::Verified_Entry, _masm->offset());
_masm->verified_entry();
+ if (needs_clinit_barrier_on_entry(compilation()->method())) {
+ clinit_barrier(compilation()->method());
+ }
build_frame();
offsets()->set_value(CodeOffsets::Frame_Complete, _masm->offset());
break;
--- a/src/hotspot/share/c1/c1_LIRAssembler.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/c1/c1_LIRAssembler.hpp Thu May 30 13:39:13 2019 +0300
@@ -81,6 +81,9 @@
// returns offset of icache check
int check_icache();
+ bool needs_clinit_barrier_on_entry(ciMethod* method) const;
+ void clinit_barrier(ciMethod* method);
+
void jobject2reg(jobject o, Register reg);
void jobject2reg_with_patching(Register reg, CodeEmitInfo* info);
--- a/src/hotspot/share/ci/ciMethod.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/ci/ciMethod.cpp Thu May 30 13:39:13 2019 +0300
@@ -933,6 +933,13 @@
return get_Method()->is_ignored_by_security_stack_walk();
}
+// ------------------------------------------------------------------
+// ciMethod::needs_clinit_barrier
+//
+bool ciMethod::needs_clinit_barrier() const {
+ check_is_loaded();
+ return is_static() && !holder()->is_initialized();
+}
// ------------------------------------------------------------------
// invokedynamic support
--- a/src/hotspot/share/ci/ciMethod.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/ci/ciMethod.hpp Thu May 30 13:39:13 2019 +0300
@@ -245,6 +245,8 @@
ResourceBitMap live_local_oops_at_bci(int bci);
+ bool needs_clinit_barrier() const;
+
#ifdef COMPILER1
const BitMap& bci_block_start();
#endif
--- a/src/hotspot/share/oops/cpCache.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/oops/cpCache.cpp Thu May 30 13:39:13 2019 +0300
@@ -261,11 +261,22 @@
method->name() != vmSymbols::object_initializer_name()) {
do_resolve = false;
}
- // Don't mark invokestatic to method as resolved if the holder class has not yet completed
- // initialization. An invokestatic must only proceed if the class is initialized, but if
- // we resolve it before then that class initialization check is skipped.
- if (invoke_code == Bytecodes::_invokestatic && !method->method_holder()->is_initialized()) {
- do_resolve = false;
+ if (invoke_code == Bytecodes::_invokestatic) {
+ assert(method->method_holder()->is_initialized() ||
+ method->method_holder()->is_reentrant_initialization(Thread::current()),
+ "invalid class initialization state for invoke_static");
+
+ if (!VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) {
+ // Don't mark invokestatic to method as resolved if the holder class has not yet completed
+ // initialization. An invokestatic must only proceed if the class is initialized, but if
+ // we resolve it before then that class initialization check is skipped.
+ //
+ // When fast class initialization checks are supported (VM_Version::supports_fast_class_init_checks() == true),
+ // template interpreter supports fast class initialization check for
+ // invokestatic which doesn't require call site re-resolution to
+ // enforce class initialization barrier.
+ do_resolve = false;
+ }
}
if (do_resolve) {
set_bytecode_1(invoke_code);
--- a/src/hotspot/share/oops/method.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/oops/method.cpp Thu May 30 13:39:13 2019 +0300
@@ -704,6 +704,10 @@
return name() == vmSymbols::object_initializer_name();
}
+bool Method::needs_clinit_barrier() const {
+ return is_static() && !method_holder()->is_initialized();
+}
+
objArrayHandle Method::resolved_checked_exceptions_impl(Method* method, TRAPS) {
int length = method->checked_exceptions_length();
if (length == 0) { // common case
--- a/src/hotspot/share/oops/method.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/oops/method.hpp Thu May 30 13:39:13 2019 +0300
@@ -699,6 +699,8 @@
bool has_aot_code() const { return aot_code() != NULL; }
#endif
+ bool needs_clinit_barrier() const;
+
// sizing
static int header_size() {
return align_up((int)sizeof(Method), wordSize) / wordSize;
--- a/src/hotspot/share/oops/methodData.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/oops/methodData.hpp Thu May 30 13:39:13 2019 +0300
@@ -2012,7 +2012,7 @@
// Whole-method sticky bits and flags
enum {
- _trap_hist_limit = 24 JVMCI_ONLY(+5), // decoupled from Deoptimization::Reason_LIMIT
+ _trap_hist_limit = 25 JVMCI_ONLY(+5), // decoupled from Deoptimization::Reason_LIMIT
_trap_hist_mask = max_jubyte,
_extra_data_count = 4 // extra DataLayout headers, for trap history
}; // Public flag values
--- a/src/hotspot/share/opto/compile.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/opto/compile.cpp Thu May 30 13:39:13 2019 +0300
@@ -654,6 +654,7 @@
_trace_opto_output(directive->TraceOptoOutputOption),
#endif
_has_method_handle_invokes(false),
+ _clinit_barrier_on_entry(false),
_comp_arena(mtCompiler),
_barrier_set_state(BarrierSet::barrier_set()->barrier_set_c2()->create_barrier_state(comp_arena())),
_env(ci_env),
@@ -988,6 +989,7 @@
_trace_opto_output(directive->TraceOptoOutputOption),
#endif
_has_method_handle_invokes(false),
+ _clinit_barrier_on_entry(false),
_comp_arena(mtCompiler),
_env(ci_env),
_directive(directive),
@@ -1170,6 +1172,9 @@
}
}
#endif
+ if (VM_Version::supports_fast_class_init_checks() && has_method() && !is_osr_compilation() && method()->needs_clinit_barrier()) {
+ set_clinit_barrier_on_entry(true);
+ }
if (debug_info()->recording_non_safepoints()) {
set_node_note_array(new(comp_arena()) GrowableArray<Node_Notes*>
(comp_arena(), 8, 0, NULL));
--- a/src/hotspot/share/opto/compile.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/opto/compile.hpp Thu May 30 13:39:13 2019 +0300
@@ -416,6 +416,7 @@
bool _has_method_handle_invokes; // True if this method has MethodHandle invokes.
RTMState _rtm_state; // State of Restricted Transactional Memory usage
int _loop_opts_cnt; // loop opts round
+ bool _clinit_barrier_on_entry; // True if clinit barrier is needed on nmethod entry
// Compilation environment.
Arena _comp_arena; // Arena with lifetime equivalent to Compile
@@ -714,6 +715,8 @@
bool profile_rtm() const { return _rtm_state == ProfileRTM; }
uint max_node_limit() const { return (uint)_max_node_limit; }
void set_max_node_limit(uint n) { _max_node_limit = n; }
+ bool clinit_barrier_on_entry() { return _clinit_barrier_on_entry; }
+ void set_clinit_barrier_on_entry(bool z) { _clinit_barrier_on_entry = z; }
// check the CompilerOracle for special behaviours for this compile
bool method_has_option(const char * option) {
--- a/src/hotspot/share/opto/parse.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/opto/parse.hpp Thu May 30 13:39:13 2019 +0300
@@ -482,6 +482,8 @@
// Helper function to compute array addressing
Node* array_addressing(BasicType type, int vals, const Type* *result2=NULL);
+ void clinit_deopt();
+
void rtm_deopt();
// Pass current map to exits
--- a/src/hotspot/share/opto/parse1.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/opto/parse1.cpp Thu May 30 13:39:13 2019 +0300
@@ -584,6 +584,11 @@
}
if (depth() == 1 && !failing()) {
+ if (C->clinit_barrier_on_entry()) {
+ // Add check to deoptimize the nmethod once the holder class is fully initialized
+ clinit_deopt();
+ }
+
// Add check to deoptimize the nmethod if RTM state was changed
rtm_deopt();
}
@@ -1192,7 +1197,7 @@
// The main thing to do is lock the receiver of a synchronized method.
void Parse::do_method_entry() {
set_parse_bci(InvocationEntryBci); // Pseudo-BCP
- set_sp(0); // Java Stack Pointer
+ set_sp(0); // Java Stack Pointer
NOT_PRODUCT( count_compiled_calls(true/*at_method_entry*/, false/*is_inline*/); )
@@ -2102,11 +2107,36 @@
set_control( _gvn.transform(result_rgn) );
}
+// Add check to deoptimize once holder klass is fully initialized.
+void Parse::clinit_deopt() {
+ assert(C->has_method(), "only for normal compilations");
+ assert(depth() == 1, "only for main compiled method");
+ assert(is_normal_parse(), "no barrier needed on osr entry");
+ assert(method()->holder()->is_being_initialized() || method()->holder()->is_initialized(),
+ "initialization should have been started");
+
+ set_parse_bci(0);
+
+ Node* holder = makecon(TypeKlassPtr::make(method()->holder()));
+ int init_state_off = in_bytes(InstanceKlass::init_state_offset());
+ Node* adr = basic_plus_adr(top(), holder, init_state_off);
+ Node* init_state = make_load(control(), adr, TypeInt::BYTE, T_BYTE, MemNode::unordered);
+
+ Node* fully_initialized_state = makecon(TypeInt::make(InstanceKlass::fully_initialized));
+
+ Node* chk = gvn().transform(new CmpINode(init_state, fully_initialized_state));
+ Node* tst = gvn().transform(new BoolNode(chk, BoolTest::ne));
+
+ { BuildCutout unless(this, tst, PROB_MAX);
+ uncommon_trap(Deoptimization::Reason_initialized, Deoptimization::Action_reinterpret);
+ }
+}
+
// Add check to deoptimize if RTM state is not ProfileRTM
void Parse::rtm_deopt() {
#if INCLUDE_RTM_OPT
if (C->profile_rtm()) {
- assert(C->method() != NULL, "only for normal compilations");
+ assert(C->has_method(), "only for normal compilations");
assert(!C->method()->method_data()->is_empty(), "MDO is needed to record RTM state");
assert(depth() == 1, "generate check only for main compiled method");
--- a/src/hotspot/share/runtime/deoptimization.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/runtime/deoptimization.cpp Thu May 30 13:39:13 2019 +0300
@@ -2176,6 +2176,7 @@
"profile_predicate",
"unloaded",
"uninitialized",
+ "initialized",
"unreached",
"unhandled",
"constraint",
--- a/src/hotspot/share/runtime/deoptimization.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/runtime/deoptimization.hpp Thu May 30 13:39:13 2019 +0300
@@ -72,6 +72,7 @@
// recorded per method
Reason_unloaded, // unloaded class or constant pool entry
Reason_uninitialized, // bad class state (uninitialized)
+ Reason_initialized, // class has been fully initialized
Reason_unreached, // code is not reached, compiler
Reason_unhandled, // arbitrary compiler limitation
Reason_constraint, // arbitrary runtime constraint violated
--- a/src/hotspot/share/runtime/sharedRuntime.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/runtime/sharedRuntime.cpp Thu May 30 13:39:13 2019 +0300
@@ -1314,6 +1314,12 @@
}
}
} else {
+ if (VM_Version::supports_fast_class_init_checks() &&
+ invoke_code == Bytecodes::_invokestatic &&
+ callee_method->needs_clinit_barrier() &&
+ callee != NULL && (callee->is_compiled_by_jvmci() || callee->is_aot())) {
+ return true; // skip patching for JVMCI or AOT code
+ }
CompiledStaticCall* ssc = caller_nm->compiledStaticCall_before(caller_frame.pc());
if (ssc->is_clean()) ssc->set(static_call_info);
}
@@ -1376,12 +1382,20 @@
}
#endif
- // Do not patch call site for static call when the class is not
- // fully initialized.
- if (invoke_code == Bytecodes::_invokestatic &&
- !callee_method->method_holder()->is_initialized()) {
- assert(callee_method->method_holder()->is_linked(), "must be");
- return callee_method;
+ if (invoke_code == Bytecodes::_invokestatic) {
+ assert(callee_method->method_holder()->is_initialized() ||
+ callee_method->method_holder()->is_reentrant_initialization(thread),
+ "invalid class initialization state for invoke_static");
+ if (!VM_Version::supports_fast_class_init_checks() && callee_method->needs_clinit_barrier()) {
+ // In order to keep class initialization check, do not patch call
+ // site for static call when the class is not fully initialized.
+ // Proper check is enforced by call site re-resolution on every invocation.
+ //
+ // When fast class initialization checks are supported (VM_Version::supports_fast_class_init_checks() == true),
+ // explicit class initialization check is put in nmethod entry (VEP).
+ assert(callee_method->method_holder()->is_linked(), "must be");
+ return callee_method;
+ }
}
// JSR 292 key invariant:
--- a/src/hotspot/share/runtime/vmStructs.cpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/runtime/vmStructs.cpp Thu May 30 13:39:13 2019 +0300
@@ -2388,6 +2388,7 @@
declare_constant(Deoptimization::Reason_profile_predicate) \
declare_constant(Deoptimization::Reason_unloaded) \
declare_constant(Deoptimization::Reason_uninitialized) \
+ declare_constant(Deoptimization::Reason_initialized) \
declare_constant(Deoptimization::Reason_unreached) \
declare_constant(Deoptimization::Reason_unhandled) \
declare_constant(Deoptimization::Reason_constraint) \
--- a/src/hotspot/share/runtime/vm_version.hpp Thu May 30 11:42:53 2019 +0200
+++ b/src/hotspot/share/runtime/vm_version.hpp Thu May 30 13:39:13 2019 +0300
@@ -171,6 +171,9 @@
// Does this CPU support spin wait instruction?
static bool supports_on_spin_wait() { return false; }
+ // Does platform support fast class initialization checks for static methods?
+ static bool supports_fast_class_init_checks() { return false; }
+
static bool print_matching_lines_from_file(const char* filename, outputStream* st, const char* keywords_to_match[]);
};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/clinit/ClassInitBarrier.java Thu May 30 13:39:13 2019 +0300
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @library /test/lib
+ *
+ * @requires !vm.graal.enabled
+ *
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -Xint -DTHROW=false ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -Xint -DTHROW=true ClassInitBarrier
+ *
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true ClassInitBarrier
+ *
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true ClassInitBarrier
+ *
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=dontinline,*::static* ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true -XX:CompileCommand=dontinline,*::static* ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false -XX:CompileCommand=dontinline,*::static* ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true -XX:CompileCommand=dontinline,*::static* ClassInitBarrier
+ *
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=false -XX:CompileCommand=exclude,*::static* ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:TieredStopAtLevel=1 -DTHROW=true -XX:CompileCommand=exclude,*::static* ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=false -XX:CompileCommand=exclude,*::static* ClassInitBarrier
+ * @run main/othervm/native -Xbatch -XX:CompileCommand=dontinline,*::test* -XX:-TieredCompilation -DTHROW=true -XX:CompileCommand=exclude,*::static* ClassInitBarrier
+ */
+
+import jdk.test.lib.Asserts;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+public class ClassInitBarrier {
+ static {
+ System.loadLibrary("ClassInitBarrier");
+
+ if (!init()) {
+ throw new Error("init failed");
+ }
+ }
+
+ static native boolean init();
+
+ static final boolean THROW = Boolean.getBoolean("THROW");
+
+ static class Test {
+ static class A {
+ static {
+ changePhase(Phase.IN_PROGRESS);
+ runTests(); // interpreted mode
+ warmup(); // trigger compilation
+ runTests(); // compiled mode
+
+ ensureBlocked(); // ensure still blocked
+ maybeThrow(); // fail initialization if needed
+
+ changePhase(Phase.FINISHED);
+ }
+
+ static void staticM(Runnable action) { action.run(); }
+ static synchronized void staticS(Runnable action) { action.run(); }
+ static native void staticN(Runnable action);
+
+ static int staticF;
+
+ int f;
+ void m() {}
+ }
+
+ static class B extends A {}
+
+ static void testInvokeStatic(Runnable action) { A.staticM(action); }
+ static void testInvokeStaticSync(Runnable action) { A.staticS(action); }
+ static void testInvokeStaticNative(Runnable action) { A.staticN(action); }
+
+ static int testGetStatic(Runnable action) { int v = A.staticF; action.run(); return v; }
+ static void testPutStatic(Runnable action) { A.staticF = 1; action.run(); }
+ static A testNewInstanceA(Runnable action) { A obj = new A(); action.run(); return obj; }
+ static B testNewInstanceB(Runnable action) { B obj = new B(); action.run(); return obj; }
+
+ static int testGetField(A recv, Runnable action) { int v = recv.f; action.run(); return v; }
+ static void testPutField(A recv, Runnable action) { recv.f = 1; action.run(); }
+ static void testInvokeVirtual(A recv, Runnable action) { recv.m(); action.run(); }
+
+ static void runTests() {
+ checkBlockingAction(Test::testInvokeStatic); // invokestatic
+ checkBlockingAction(Test::testInvokeStaticNative); // invokestatic
+ checkBlockingAction(Test::testInvokeStaticSync); // invokestatic
+ checkBlockingAction(Test::testGetStatic); // getstatic
+ checkBlockingAction(Test::testPutStatic); // putstatic
+ checkBlockingAction(Test::testNewInstanceA); // new
+
+ A recv = testNewInstanceB(NON_BLOCKING.get()); // trigger B initialization
+ checkNonBlockingAction(Test::testNewInstanceB); // new: NO BLOCKING: same thread: A being initialized, B fully initialized
+
+ checkNonBlockingAction(recv, Test::testGetField); // getfield
+ checkNonBlockingAction(recv, Test::testPutField); // putfield
+ checkNonBlockingAction(recv, Test::testInvokeVirtual); // invokevirtual
+ }
+
+ static void warmup() {
+ for (int i = 0; i < 20_000; i++) {
+ testInvokeStatic( NON_BLOCKING_WARMUP);
+ testInvokeStaticNative(NON_BLOCKING_WARMUP);
+ testInvokeStaticSync( NON_BLOCKING_WARMUP);
+ testGetStatic( NON_BLOCKING_WARMUP);
+ testPutStatic( NON_BLOCKING_WARMUP);
+ testNewInstanceA( NON_BLOCKING_WARMUP);
+ testNewInstanceB( NON_BLOCKING_WARMUP);
+
+ testGetField(new B(), NON_BLOCKING_WARMUP);
+ testPutField(new B(), NON_BLOCKING_WARMUP);
+ testInvokeVirtual(new B(), NON_BLOCKING_WARMUP);
+ }
+ }
+
+ static void run() {
+ execute(ExceptionInInitializerError.class, () -> triggerInitialization(A.class));
+
+ ensureFinished();
+ }
+ }
+
+ // ============================================================================================================== //
+
+ static void execute(Class<? extends Throwable> expectedExceptionClass, Runnable action) {
+ try {
+ action.run();
+ if (THROW) throw new AssertionError("no exception thrown");
+ } catch (Throwable e) {
+ if (THROW) {
+ if (e.getClass() == expectedExceptionClass) {
+ // expected
+ } else {
+ String msg = String.format("unexpected exception thrown: expected %s, caught %s",
+ expectedExceptionClass.getName(), e.getClass().getName());
+ throw new AssertionError(msg, e);
+ }
+ } else {
+ throw new AssertionError("no exception expected", e);
+ }
+ }
+ }
+
+ static final List<Thread> BLOCKED_THREADS = Collections.synchronizedList(new ArrayList<>());
+ static final Consumer<Thread> ON_BLOCK = BLOCKED_THREADS::add;
+
+ static final Map<Thread,Throwable> FAILED_THREADS = Collections.synchronizedMap(new HashMap<>());
+ static final Thread.UncaughtExceptionHandler ON_FAILURE = FAILED_THREADS::put;
+
+ private static void ensureBlocked() {
+ for (Thread thr : BLOCKED_THREADS) {
+ try {
+ thr.join(100);
+ if (!thr.isAlive()) {
+ dump(thr);
+ throw new AssertionError("not blocked");
+ }
+ } catch (InterruptedException e) {
+ throw new Error(e);
+ }
+ }
+ }
+
+
+ private static void ensureFinished() {
+ for (Thread thr : BLOCKED_THREADS) {
+ try {
+ thr.join(15_000);
+ } catch (InterruptedException e) {
+ throw new Error(e);
+ }
+ if (thr.isAlive()) {
+ dump(thr);
+ throw new AssertionError(thr + ": still blocked");
+ }
+ }
+ for (Thread thr : BLOCKED_THREADS) {
+ if (THROW) {
+ if (!FAILED_THREADS.containsKey(thr)) {
+ throw new AssertionError(thr + ": exception not thrown");
+ }
+
+ Throwable ex = FAILED_THREADS.get(thr);
+ if (ex.getClass() != NoClassDefFoundError.class) {
+ throw new AssertionError(thr + ": wrong exception thrown", ex);
+ }
+ } else {
+ if (FAILED_THREADS.containsKey(thr)) {
+ Throwable ex = FAILED_THREADS.get(thr);
+ throw new AssertionError(thr + ": exception thrown", ex);
+ }
+ }
+ }
+ if (THROW) {
+ Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
+ } else {
+ Asserts.assertEquals(BLOCKING_COUNTER.get(), BLOCKING_ACTIONS.get());
+ }
+
+ dumpInfo();
+ }
+
+ interface TestCase0 {
+ void run(Runnable runnable);
+ }
+
+ interface TestCase1<T> {
+ void run(T arg, Runnable runnable);
+ }
+
+ enum Phase { BEFORE_INIT, IN_PROGRESS, FINISHED, INIT_FAILURE }
+
+ static volatile Phase phase = Phase.BEFORE_INIT;
+
+ static void changePhase(Phase newPhase) {
+ dumpInfo();
+
+ Phase oldPhase = phase;
+ switch (oldPhase) {
+ case BEFORE_INIT:
+ Asserts.assertEquals(NON_BLOCKING_ACTIONS.get(), 0);
+ Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), 0);
+
+ Asserts.assertEquals(BLOCKING_ACTIONS.get(), 0);
+ Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
+ break;
+ case IN_PROGRESS:
+ Asserts.assertEquals(NON_BLOCKING_COUNTER.get(), NON_BLOCKING_ACTIONS.get());
+
+ Asserts.assertEquals(BLOCKING_COUNTER.get(), 0);
+ break;
+ default: throw new Error("wrong phase transition " + oldPhase);
+ }
+ phase = newPhase;
+ }
+
+ static void dumpInfo() {
+ System.out.println("Phase: " + phase);
+ System.out.println("Non-blocking actions: " + NON_BLOCKING_COUNTER.get() + " / " + NON_BLOCKING_ACTIONS.get());
+ System.out.println("Blocking actions: " + BLOCKING_COUNTER.get() + " / " + BLOCKING_ACTIONS.get());
+ }
+
+ static final Runnable NON_BLOCKING_WARMUP = () -> {
+ if (phase != Phase.IN_PROGRESS) {
+ throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
+ }
+ };
+
+ static Runnable disposableAction(final Phase validPhase, final AtomicInteger invocationCounter, final AtomicInteger actionCounter) {
+ actionCounter.incrementAndGet();
+
+ final AtomicBoolean cnt = new AtomicBoolean(false);
+ return () -> {
+ if (cnt.getAndSet(true)) {
+ throw new Error("repeated invocation");
+ }
+ invocationCounter.incrementAndGet();
+ if (phase != validPhase) {
+ throw new AssertionError("NON_BLOCKING: wrong phase: " + phase);
+ }
+ };
+ }
+
+ @FunctionalInterface
+ interface Factory<V> {
+ V get();
+ }
+
+ static final AtomicInteger NON_BLOCKING_COUNTER = new AtomicInteger(0);
+ static final AtomicInteger NON_BLOCKING_ACTIONS = new AtomicInteger(0);
+ static final Factory<Runnable> NON_BLOCKING = () -> disposableAction(Phase.IN_PROGRESS, NON_BLOCKING_COUNTER, NON_BLOCKING_ACTIONS);
+
+ static final AtomicInteger BLOCKING_COUNTER = new AtomicInteger(0);
+ static final AtomicInteger BLOCKING_ACTIONS = new AtomicInteger(0);
+ static final Factory<Runnable> BLOCKING = () -> disposableAction(Phase.FINISHED, BLOCKING_COUNTER, BLOCKING_ACTIONS);
+
+ static void checkBlockingAction(TestCase0 r) {
+ r.run(NON_BLOCKING.get()); // same thread
+ checkBlocked(ON_BLOCK, ON_FAILURE, r); // different thread
+ }
+
+ static void checkNonBlockingAction(TestCase0 r) {
+ r.run(NON_BLOCKING.get());
+ checkNotBlocked(r); // different thread
+ }
+
+ static <T> void checkNonBlockingAction(T recv, TestCase1<T> r) {
+ r.run(recv, NON_BLOCKING.get()); // same thread
+ checkNotBlocked((action) -> r.run(recv, action)); // different thread
+ }
+
+ static void triggerInitialization(Class<?> cls) {
+ try {
+ Class<?> loadedClass = Class.forName(cls.getName(), true, cls.getClassLoader());
+ if (loadedClass != cls) {
+ throw new Error("wrong class");
+ }
+ } catch (ClassNotFoundException e) {
+ throw new Error(e);
+ }
+ }
+
+ static void checkBlocked(Consumer<Thread> onBlockHandler, Thread.UncaughtExceptionHandler onException, TestCase0 r) {
+ Thread thr = new Thread(() -> {
+ try {
+ r.run(BLOCKING.get());
+ System.out.println("Thread " + Thread.currentThread() + ": Finished successfully");
+ } catch(Throwable e) {
+ System.out.println("Thread " + Thread.currentThread() + ": Exception thrown: " + e);
+ if (!THROW) {
+ e.printStackTrace();
+ }
+ throw e;
+ }
+ } );
+ thr.setUncaughtExceptionHandler(onException);
+
+ thr.start();
+ try {
+ thr.join(100);
+
+ dump(thr);
+ if (thr.isAlive()) {
+ onBlockHandler.accept(thr); // blocked
+ } else {
+ throw new AssertionError("not blocked");
+ }
+ } catch (InterruptedException e) {
+ throw new Error(e);
+ }
+ }
+
+ static void checkNotBlocked(TestCase0 r) {
+ Thread thr = new Thread(() -> r.run(NON_BLOCKING.get()));
+
+ thr.start();
+ try {
+ thr.join(15_000);
+ if (thr.isAlive()) {
+ dump(thr);
+ throw new AssertionError("blocked");
+ }
+ } catch (InterruptedException e) {
+ throw new Error(e);
+ }
+ }
+
+ static void maybeThrow() {
+ if (THROW) {
+ changePhase(Phase.INIT_FAILURE);
+ throw new RuntimeException("failed class initialization");
+ }
+ }
+
+ private static void dump(Thread thr) {
+ System.out.println("Thread: " + thr);
+ System.out.println("Thread state: " + thr.getState());
+ if (thr.isAlive()) {
+ for (StackTraceElement frame : thr.getStackTrace()) {
+ System.out.println(frame);
+ }
+ } else {
+ if (FAILED_THREADS.containsKey(thr)) {
+ System.out.println("Failed with an exception: ");
+ FAILED_THREADS.get(thr).toString();
+ } else {
+ System.out.println("Finished successfully");
+ }
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Test.run();
+ System.out.println("TEST PASSED");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/clinit/libClassInitBarrier.cpp Thu May 30 13:39:13 2019 +0300
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <jni.h>
+
+static jmethodID methodId;
+
+extern "C" {
+ JNIEXPORT jboolean JNICALL Java_ClassInitBarrier_init(JNIEnv* env, jclass cls) {
+ jclass runnable = env->FindClass("java/lang/Runnable");
+ if (runnable == NULL) return JNI_FALSE;
+
+ methodId = env->GetMethodID(runnable, "run", "()V");
+ if (methodId == NULL) return JNI_FALSE;
+
+ return JNI_TRUE;
+ }
+
+ JNIEXPORT void JNICALL Java_ClassInitBarrier_00024Test_00024A_staticN(JNIEnv* env, jclass cls, jobject action) {
+ env->CallVoidMethod(action, methodId);
+ }
+}