8223213: Implement fast class initialization checks on x86-64
Reviewed-by: kvn, redestad, dholmes, mdoerr, coleenp
This commit is contained in:
parent
ba723fbdfb
commit
e72bfe15ad
src/hotspot
cpu
aarch64
arm
ppc
s390
sparc
x86
share
test/hotspot/jtreg/runtime/clinit
@ -316,6 +316,9 @@ int LIR_Assembler::check_icache() {
|
||||
return start_offset;
|
||||
}
|
||||
|
||||
void LIR_Assembler::clinit_barrier(ciMethod* method) {
|
||||
ShouldNotReachHere(); // not implemented
|
||||
}
|
||||
|
||||
void LIR_Assembler::jobject2reg(jobject o, Register reg) {
|
||||
if (o == NULL) {
|
||||
|
@ -191,6 +191,9 @@ int LIR_Assembler::check_icache() {
|
||||
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();
|
||||
|
@ -79,6 +79,9 @@ int LIR_Assembler::check_icache() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
void LIR_Assembler::clinit_barrier(ciMethod* method) {
|
||||
ShouldNotReachHere(); // not implemented
|
||||
}
|
||||
|
||||
void LIR_Assembler::osr_entry() {
|
||||
// On-stack-replacement entry sequence:
|
||||
|
@ -81,6 +81,10 @@ int LIR_Assembler::check_icache() {
|
||||
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):
|
||||
//
|
||||
|
@ -172,6 +172,9 @@ int LIR_Assembler::check_icache() {
|
||||
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):
|
||||
|
@ -359,6 +359,23 @@ int LIR_Assembler::check_icache() {
|
||||
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;
|
||||
|
@ -487,7 +487,8 @@ void InterpreterMacroAssembler::get_cache_entry_pointer_at_bcp(Register cache,
|
||||
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 @@ void InterpreterMacroAssembler::get_cache_entry_pointer_at_bcp(Register cache,
|
||||
}
|
||||
|
||||
// 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 @@ void InterpreterMacroAssembler::load_resolved_reference_at_index(
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
|
@ -124,9 +124,14 @@ class InterpreterMacroAssembler: public MacroAssembler {
|
||||
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
|
||||
|
@ -4603,6 +4603,29 @@ void MacroAssembler::check_klass_subtype_slow_path(Register sub_klass,
|
||||
}
|
||||
|
||||
|
||||
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::resolve_weak_handle(Register rresult, Register rtmp) {
|
||||
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) {
|
||||
|
@ -317,6 +317,8 @@ class MacroAssembler: public Assembler {
|
||||
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 @@ class MacroAssembler: public Assembler {
|
||||
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);
|
||||
|
||||
|
@ -974,6 +974,27 @@ AdapterHandlerEntry* SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm
|
||||
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 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm,
|
||||
|
||||
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)) {
|
||||
|
@ -2719,12 +2719,13 @@ void TemplateTable::volatile_barrier(Assembler::Membar_mask_bits order_constrain
|
||||
}
|
||||
|
||||
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 @@ void TemplateTable::resolve_cache_and_index(int byte_no,
|
||||
}
|
||||
|
||||
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 @@ void TemplateTable::load_invoke_cp_cache_entry(int byte_no,
|
||||
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 @@ void TemplateTable::load_invoke_cp_cache_entry(int byte_no,
|
||||
|
||||
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 @@ void TemplateTable::invokeinterface(int byte_no) {
|
||||
__ 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 @@ void TemplateTable::_new() {
|
||||
__ 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 @@ void TemplateTable::checkcast() {
|
||||
// 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 @@ void TemplateTable::instanceof() {
|
||||
// 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);
|
||||
|
||||
|
@ -936,6 +936,11 @@ public:
|
||||
// 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);
|
||||
|
@ -874,6 +874,22 @@ void MachPrologNode::emit(CodeBuffer &cbuf, PhaseRegAlloc *ra_) const {
|
||||
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());
|
||||
|
@ -162,6 +162,9 @@ bool LIR_Assembler::needs_icache(ciMethod* method) const {
|
||||
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 @@ void LIR_Assembler::emit_op0(LIR_Op0* op) {
|
||||
}
|
||||
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;
|
||||
|
@ -81,6 +81,9 @@ class LIR_Assembler: public CompilationResourceObj {
|
||||
// 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);
|
||||
|
||||
|
@ -933,6 +933,13 @@ bool ciMethod::is_ignored_by_security_stack_walk() const {
|
||||
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
|
||||
|
@ -245,6 +245,8 @@ class ciMethod : public ciMetadata {
|
||||
|
||||
ResourceBitMap live_local_oops_at_bci(int bci);
|
||||
|
||||
bool needs_clinit_barrier() const;
|
||||
|
||||
#ifdef COMPILER1
|
||||
const BitMap& bci_block_start();
|
||||
#endif
|
||||
|
@ -261,11 +261,22 @@ void ConstantPoolCacheEntry::set_direct_or_vtable_call(Bytecodes::Code invoke_co
|
||||
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);
|
||||
|
@ -704,6 +704,10 @@ bool Method::is_object_initializer() const {
|
||||
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
|
||||
|
@ -699,6 +699,8 @@ public:
|
||||
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;
|
||||
|
@ -2012,7 +2012,7 @@ public:
|
||||
|
||||
// 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
|
||||
|
@ -654,6 +654,7 @@ Compile::Compile( ciEnv* ci_env, C2Compiler* compiler, ciMethod* target, int osr
|
||||
_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 @@ Compile::Compile( ciEnv* ci_env,
|
||||
_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 @@ void Compile::Init(int aliaslevel) {
|
||||
}
|
||||
}
|
||||
#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));
|
||||
|
@ -416,6 +416,7 @@ class Compile : public Phase {
|
||||
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 @@ class Compile : public Phase {
|
||||
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) {
|
||||
|
@ -482,6 +482,8 @@ class Parse : public GraphKit {
|
||||
// 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
|
||||
|
@ -584,6 +584,11 @@ Parse::Parse(JVMState* caller, ciMethod* parse_method, float expected_uses)
|
||||
}
|
||||
|
||||
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 @@ SafePointNode* Parse::create_entry_map() {
|
||||
// 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 @@ void Parse::call_register_finalizer() {
|
||||
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");
|
||||
|
||||
|
@ -2176,6 +2176,7 @@ const char* Deoptimization::_trap_reason_name[] = {
|
||||
"profile_predicate",
|
||||
"unloaded",
|
||||
"uninitialized",
|
||||
"initialized",
|
||||
"unreached",
|
||||
"unhandled",
|
||||
"constraint",
|
||||
|
@ -72,6 +72,7 @@ class Deoptimization : AllStatic {
|
||||
// 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
|
||||
|
@ -1314,6 +1314,12 @@ bool SharedRuntime::resolve_sub_helper_internal(methodHandle callee_method, cons
|
||||
}
|
||||
}
|
||||
} 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 @@ methodHandle SharedRuntime::resolve_sub_helper(JavaThread *thread,
|
||||
}
|
||||
#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:
|
||||
|
@ -2388,6 +2388,7 @@ typedef PaddedEnd<ObjectMonitor> PaddedObjectMonitor;
|
||||
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) \
|
||||
|
@ -171,6 +171,9 @@ class Abstract_VM_Version: AllStatic {
|
||||
// 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[]);
|
||||
};
|
||||
|
||||
|
401
test/hotspot/jtreg/runtime/clinit/ClassInitBarrier.java
Normal file
401
test/hotspot/jtreg/runtime/clinit/ClassInitBarrier.java
Normal file
@ -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");
|
||||
}
|
||||
}
|
42
test/hotspot/jtreg/runtime/clinit/libClassInitBarrier.cpp
Normal file
42
test/hotspot/jtreg/runtime/clinit/libClassInitBarrier.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user