8223213: Implement fast class initialization checks on x86-64

Reviewed-by: kvn, redestad, dholmes, mdoerr, coleenp
This commit is contained in:
Vladimir Ivanov 2019-05-30 13:39:13 +03:00
parent ba723fbdfb
commit e72bfe15ad
33 changed files with 734 additions and 44 deletions

@ -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[]);
};

@ -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");
}
}

@ -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);
}
}