8266074: Vtable-based CHA implementation

Reviewed-by: kvn, jrose, dlong
This commit is contained in:
Vladimir Ivanov 2021-05-13 10:58:03 +00:00
parent 347d41df90
commit 127bfe44f7
11 changed files with 553 additions and 77 deletions

View File

@ -2026,7 +2026,7 @@ void GraphBuilder::invoke(Bytecodes::Code code) {
// by dynamic class loading. Be sure to test the "static" receiver
// dest_method here, as opposed to the actual receiver, which may
// falsely lead us to believe that the receiver is final or private.
dependency_recorder()->assert_unique_concrete_method(actual_recv, cha_monomorphic_target);
dependency_recorder()->assert_unique_concrete_method(actual_recv, cha_monomorphic_target, callee_holder, target);
}
code = Bytecodes::_invokespecial;
}

View File

@ -346,6 +346,9 @@ void BCEscapeAnalyzer::invoke(StateInfo &state, Bytecodes::Code code, ciMethod*
(code == Bytecodes::_invokevirtual && !target->is_final_method())) {
_dependencies.append(actual_recv);
_dependencies.append(inline_target);
_dependencies.append(callee_holder);
_dependencies.append(target);
assert(callee_holder->is_interface() == (code == Bytecodes::_invokeinterface), "sanity");
}
_dependencies.appendAll(analyzer.dependencies());
}
@ -1495,9 +1498,11 @@ void BCEscapeAnalyzer::copy_dependencies(Dependencies *deps) {
// callee will trigger recompilation.
deps->assert_evol_method(method());
}
for (int i = 0; i < _dependencies.length(); i+=2) {
ciKlass *k = _dependencies.at(i)->as_klass();
ciMethod *m = _dependencies.at(i+1)->as_method();
deps->assert_unique_concrete_method(k, m);
for (int i = 0; i < _dependencies.length(); i+=4) {
ciKlass* recv_klass = _dependencies.at(i+0)->as_klass();
ciMethod* target = _dependencies.at(i+1)->as_method();
ciKlass* resolved_klass = _dependencies.at(i+2)->as_klass();
ciMethod* resolved_method = _dependencies.at(i+3)->as_method();
deps->assert_unique_concrete_method(recv_klass, target, resolved_klass, resolved_method);
}
}

View File

@ -714,7 +714,15 @@ ciMethod* ciMethod::find_monomorphic_target(ciInstanceKlass* caller,
{
MutexLocker locker(Compile_lock);
InstanceKlass* context = actual_recv->get_instanceKlass();
target = methodHandle(THREAD, Dependencies::find_unique_concrete_method(context, root_m->get_Method()));
if (UseVtableBasedCHA) {
target = methodHandle(THREAD, Dependencies::find_unique_concrete_method(context,
root_m->get_Method(),
callee_holder->get_Klass(),
this->get_Method()));
} else {
target = methodHandle(THREAD, Dependencies::find_unique_concrete_method(context, root_m->get_Method()));
}
assert(target() == NULL || !target()->is_abstract(), "not allowed");
// %%% Should upgrade this ciMethod API to look for 1 or 2 concrete methods.
}

View File

@ -1184,10 +1184,18 @@ void CodeCache::flush_dependents_on(InstanceKlass* dependee) {
if (number_of_nmethods_with_dependencies() == 0) return;
KlassDepChange changes(dependee);
int marked = 0;
if (dependee->is_linked()) {
// Class initialization state change.
KlassInitDepChange changes(dependee);
marked = mark_for_deoptimization(changes);
} else {
// New class is loaded.
NewKlassDepChange changes(dependee);
marked = mark_for_deoptimization(changes);
}
// Compute the dependent nmethods
if (mark_for_deoptimization(changes) > 0) {
if (marked > 0) {
// At least one nmethod has been marked for deoptimization
Deoptimization::deoptimize_all_marked();
}

View File

@ -103,7 +103,17 @@ void Dependencies::assert_abstract_with_unique_concrete_subtype(ciKlass* ctxk, c
void Dependencies::assert_unique_concrete_method(ciKlass* ctxk, ciMethod* uniqm) {
check_ctxk(ctxk);
check_unique_method(ctxk, uniqm);
assert_common_2(unique_concrete_method, ctxk, uniqm);
assert_common_2(unique_concrete_method_2, ctxk, uniqm);
}
void Dependencies::assert_unique_concrete_method(ciKlass* ctxk, ciMethod* uniqm, ciKlass* resolved_klass, ciMethod* resolved_method) {
check_ctxk(ctxk);
check_unique_method(ctxk, uniqm);
if (UseVtableBasedCHA) {
assert_common_4(unique_concrete_method_4, ctxk, uniqm, resolved_klass, resolved_method);
} else {
assert_common_2(unique_concrete_method_2, ctxk, uniqm);
}
}
void Dependencies::assert_has_no_finalizable_subclasses(ciKlass* ctxk) {
@ -166,7 +176,7 @@ void Dependencies::assert_abstract_with_unique_concrete_subtype(Klass* ctxk, Kla
void Dependencies::assert_unique_concrete_method(Klass* ctxk, Method* uniqm) {
check_ctxk(ctxk);
check_unique_method(ctxk, uniqm);
assert_common_2(unique_concrete_method, DepValue(_oop_recorder, ctxk), DepValue(_oop_recorder, uniqm));
assert_common_2(unique_concrete_method_2, DepValue(_oop_recorder, ctxk), DepValue(_oop_recorder, uniqm));
}
void Dependencies::assert_call_site_target_value(oop call_site, oop method_handle) {
@ -247,6 +257,36 @@ void Dependencies::assert_common_2(DepType dept,
deps->append(x1);
}
void Dependencies::assert_common_4(DepType dept,
ciKlass* ctxk, ciBaseObject* x1, ciBaseObject* x2, ciBaseObject* x3) {
assert(has_explicit_context_arg(dept), "sanity");
assert(dep_context_arg(dept) == 0, "sanity");
assert(dep_args(dept) == 4, "sanity");
log_dependency(dept, ctxk, x1, x2, x3);
GrowableArray<ciBaseObject*>* deps = _deps[dept];
// see if the same (or a similar) dep is already recorded
if (note_dep_seen(dept, x1) && note_dep_seen(dept, x2) && note_dep_seen(dept, x3)) {
// look in this bucket for redundant assertions
const int stride = 4;
for (int i = deps->length(); (i -= stride) >= 0; ) {
ciBaseObject* y1 = deps->at(i+1);
ciBaseObject* y2 = deps->at(i+2);
ciBaseObject* y3 = deps->at(i+3);
if (x1 == y1 && x2 == y2 && x3 == y3) { // same subjects; check the context
if (maybe_merge_ctxk(deps, i+0, ctxk)) {
return;
}
}
}
}
// append the assertion in the correct bucket:
deps->append(ctxk);
deps->append(x1);
deps->append(x2);
deps->append(x3);
}
#if INCLUDE_JVMCI
bool Dependencies::maybe_merge_ctxk(GrowableArray<DepValue>* deps,
int ctxk_i, DepValue ctxk2_dv) {
@ -343,6 +383,8 @@ static int sort_dep_arg_2(ciBaseObject** p1, ciBaseObject** p2)
{ return sort_dep(p1, p2, 2); }
static int sort_dep_arg_3(ciBaseObject** p1, ciBaseObject** p2)
{ return sort_dep(p1, p2, 3); }
static int sort_dep_arg_4(ciBaseObject** p1, ciBaseObject** p2)
{ return sort_dep(p1, p2, 4); }
#if INCLUDE_JVMCI
// metadata deps are sorted before object deps
@ -386,6 +428,7 @@ void Dependencies::sort_all_deps() {
case 1: deps->sort(sort_dep_arg_1, 1); break;
case 2: deps->sort(sort_dep_arg_2, 2); break;
case 3: deps->sort(sort_dep_arg_3, 3); break;
case 4: deps->sort(sort_dep_arg_4, 4); break;
default: ShouldNotReachHere(); break;
}
}
@ -413,7 +456,8 @@ size_t Dependencies::estimate_size_in_bytes() {
ciKlass* Dependencies::ctxk_encoded_as_null(DepType dept, ciBaseObject* x) {
switch (dept) {
case unique_concrete_method:
case unique_concrete_method_2:
case unique_concrete_method_4:
return x->as_metadata()->as_method()->holder();
default:
return NULL; // let NULL be NULL
@ -423,7 +467,8 @@ ciKlass* Dependencies::ctxk_encoded_as_null(DepType dept, ciBaseObject* x) {
Klass* Dependencies::ctxk_encoded_as_null(DepType dept, Metadata* x) {
assert(must_be_in_vm(), "raw oops here");
switch (dept) {
case unique_concrete_method:
case unique_concrete_method_2:
case unique_concrete_method_4:
assert(x->is_method(), "sanity");
return ((Method*)x)->method_holder();
default:
@ -526,7 +571,8 @@ const char* Dependencies::_dep_name[TYPE_LIMIT] = {
"evol_method",
"leaf_type",
"abstract_with_unique_concrete_subtype",
"unique_concrete_method",
"unique_concrete_method_2",
"unique_concrete_method_4",
"no_finalizable_subclasses",
"call_site_target_value"
};
@ -536,7 +582,8 @@ int Dependencies::_dep_args[TYPE_LIMIT] = {
1, // evol_method m
1, // leaf_type ctxk
2, // abstract_with_unique_concrete_subtype ctxk, k
2, // unique_concrete_method ctxk, m
2, // unique_concrete_method_2 ctxk, m
4, // unique_concrete_method_4 ctxk, m, resolved_klass, resolved_method
1, // no_finalizable_subclasses ctxk
2 // call_site_target_value call_site, method_handle
};
@ -978,7 +1025,7 @@ class AbstractClassHierarchyWalker {
static PerfCounter* _perf_find_witness_in_calls_count;
protected:
virtual Klass* find_witness_in(KlassDepChange* changes) = 0;
virtual Klass* find_witness_in(KlassDepChange& changes) = 0;
virtual Klass* find_witness_anywhere(InstanceKlass* context_type) = 0;
AbstractClassHierarchyWalker(Klass* participant) : _record_witnesses(0), _num_participants(0)
@ -1112,7 +1159,7 @@ Klass* AbstractClassHierarchyWalker::find_witness(InstanceKlass* context_type, K
if (UsePerfData) {
_perf_find_witness_in_calls_count->inc();
}
return find_witness_in(changes);
return find_witness_in(*changes);
} else {
if (UsePerfData) {
_perf_find_witness_anywhere_calls_count->inc();
@ -1126,7 +1173,7 @@ class ConcreteSubtypeFinder : public AbstractClassHierarchyWalker {
bool is_witness(Klass* k);
protected:
virtual Klass* find_witness_in(KlassDepChange* changes);
virtual Klass* find_witness_in(KlassDepChange& changes);
virtual Klass* find_witness_anywhere(InstanceKlass* context_type);
public:
@ -1141,15 +1188,15 @@ bool ConcreteSubtypeFinder::is_witness(Klass* k) {
}
}
Klass* ConcreteSubtypeFinder::find_witness_in(KlassDepChange* changes) {
Klass* ConcreteSubtypeFinder::find_witness_in(KlassDepChange& changes) {
// When looking for unexpected concrete types, do not look beneath expected ones:
// * CX > CC > C' is OK, even if C' is new.
// * CX > { CC, C' } is not OK if C' is new, and C' is the witness.
Klass* new_type = changes->new_type();
Klass* new_type = changes.as_new_klass_change()->new_type();
assert(!is_participant(new_type), "only old classes are participants");
// If the new type is a subtype of a participant, we are done.
for (uint i = 0; i < num_participants(); i++) {
if (changes->involves_context(participant(i))) {
if (changes.involves_context(participant(i))) {
// new guy is protected from this check by previous participant
return NULL;
}
@ -1187,7 +1234,7 @@ class ConcreteMethodFinder : public AbstractClassHierarchyWalker {
bool is_witness(Klass* k);
protected:
virtual Klass* find_witness_in(KlassDepChange* changes);
virtual Klass* find_witness_in(KlassDepChange& changes);
virtual Klass* find_witness_anywhere(InstanceKlass* context_type);
bool witnessed_reabstraction_in_supers(Klass* k);
@ -1286,10 +1333,10 @@ bool ConcreteMethodFinder::is_witness(Klass* k) {
}
}
Klass* ConcreteMethodFinder::find_witness_in(KlassDepChange* changes) {
Klass* ConcreteMethodFinder::find_witness_in(KlassDepChange& changes) {
// When looking for unexpected concrete methods, look beneath expected ones, to see if there are overrides.
// * CX.m > CC.m > C'.m is not OK, if C'.m is new, and C' is the witness.
Klass* new_type = changes->new_type();
Klass* new_type = changes.as_new_klass_change()->new_type();
assert(!is_participant(new_type), "only old classes are participants");
if (is_witness(new_type)) {
return new_type;
@ -1342,6 +1389,175 @@ Klass* ConcreteMethodFinder::find_witness_anywhere(InstanceKlass* context_type)
return NULL;
}
// For some method m and some class ctxk (subclass of method holder),
// enumerate all distinct overrides of m in concrete subclasses of ctxk.
// It relies on vtable/itable information to perform method selection on each linked subclass
// and ignores all non yet linked ones (speculatively treat them as "effectively abstract").
class LinkedConcreteMethodFinder : public AbstractClassHierarchyWalker {
private:
InstanceKlass* _resolved_klass; // resolved class (JVMS-5.4.3.1)
InstanceKlass* _declaring_klass; // the holder of resolved method (JVMS-5.4.3.3)
int _vtable_index; // vtable/itable index of the resolved method
bool _do_itable_lookup; // choose between itable and vtable lookup logic
// cache of method lookups
Method* _found_methods[PARTICIPANT_LIMIT+1];
bool is_witness(Klass* k);
Method* select_method(InstanceKlass* recv_klass);
static int compute_vtable_index(InstanceKlass* resolved_klass, Method* resolved_method, bool& is_itable_index);
static bool is_concrete_klass(InstanceKlass* ik);
void add_participant(Method* m, Klass* participant) {
uint np = num_participants();
AbstractClassHierarchyWalker::add_participant(participant);
assert(np + 1 == num_participants(), "sanity");
_found_methods[np] = m; // record the method for the participant
}
bool record_witness(Klass* witness, Method* m) {
for (uint i = 0; i < num_participants(); i++) {
if (found_method(i) == m) {
return false; // already recorded
}
}
// Record not yet seen method.
_found_methods[num_participants()] = m;
return AbstractClassHierarchyWalker::record_witness(witness);
}
void initialize(Method* participant) {
for (uint i = 0; i < PARTICIPANT_LIMIT+1; i++) {
_found_methods[i] = NULL;
}
if (participant != NULL) {
add_participant(participant, participant->method_holder());
}
}
protected:
virtual Klass* find_witness_in(KlassDepChange& changes);
virtual Klass* find_witness_anywhere(InstanceKlass* context_type);
public:
// In order to perform method selection, the following info is needed:
// (1) interface or virtual call;
// (2) vtable/itable index;
// (3) declaring class (in case of interface call).
//
// It is prepared based on the results of method resolution: resolved class and resolved method (as specified in JVMS-5.4.3.3).
// Optionally, a method which was previously determined as a unique target (uniqm) is added as a participant
// to enable dependency spot-checking and speed up the search.
LinkedConcreteMethodFinder(InstanceKlass* resolved_klass, Method* resolved_method, Method* uniqm = NULL) : AbstractClassHierarchyWalker(NULL) {
assert(UseVtableBasedCHA, "required");
assert(resolved_klass->is_linked(), "required");
assert(resolved_method->method_holder()->is_linked(), "required");
assert(!resolved_method->can_be_statically_bound(), "no vtable index available");
_resolved_klass = resolved_klass;
_declaring_klass = resolved_method->method_holder();
_vtable_index = compute_vtable_index(resolved_klass, resolved_method,
_do_itable_lookup); // out parameter
assert(_vtable_index >= 0, "invalid vtable index");
initialize(uniqm);
}
// Note: If n==num_participants, returns NULL.
Method* found_method(uint n) {
assert(n <= num_participants(), "oob");
assert(participant(n) != NULL || n == num_participants(), "proper usage");
return _found_methods[n];
}
};
Klass* LinkedConcreteMethodFinder::find_witness_in(KlassDepChange& changes) {
Klass* type = changes.type();
assert(!is_participant(type), "only old classes are participants");
if (is_witness(type)) {
return type;
}
return NULL; // No witness found. The dependency remains unbroken.
}
Klass* LinkedConcreteMethodFinder::find_witness_anywhere(InstanceKlass* context_type) {
for (CountingClassHierarchyIterator iter(context_type); !iter.done(); iter.next()) {
Klass* sub = iter.klass();
if (is_witness(sub)) {
return sub;
}
if (sub->is_instance_klass() && !InstanceKlass::cast(sub)->is_linked()) {
iter.skip_subclasses(); // ignore not yet linked classes
}
}
return NULL; // No witness found. The dependency remains unbroken.
}
bool LinkedConcreteMethodFinder::is_witness(Klass* k) {
if (is_participant(k)) {
return false; // do not report participant types
} else if (k->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(k);
if (is_concrete_klass(ik)) {
Method* m = select_method(ik);
return record_witness(ik, m);
} else {
return false; // ignore non-concrete holder class
}
} else {
return false; // no methods to find in an array type
}
}
Method* LinkedConcreteMethodFinder::select_method(InstanceKlass* recv_klass) {
Method* selected_method = NULL;
if (_do_itable_lookup) {
assert(_declaring_klass->is_interface(), "sanity");
bool implements_interface; // initialized by method_at_itable_or_null()
selected_method = recv_klass->method_at_itable_or_null(_declaring_klass, _vtable_index,
implements_interface); // out parameter
assert(implements_interface, "not implemented");
} else {
selected_method = recv_klass->method_at_vtable(_vtable_index);
}
return selected_method; // NULL when corresponding slot is empty (AbstractMethodError case)
}
int LinkedConcreteMethodFinder::compute_vtable_index(InstanceKlass* resolved_klass, Method* resolved_method,
// out parameter
bool& is_itable_index) {
if (resolved_klass->is_interface() && resolved_method->has_itable_index()) {
is_itable_index = true;
return resolved_method->itable_index();
}
// Check for default or miranda method first.
InstanceKlass* declaring_klass = resolved_method->method_holder();
if (!resolved_klass->is_interface() && declaring_klass->is_interface()) {
is_itable_index = false;
return resolved_klass->vtable_index_of_interface_method(resolved_method);
}
// At this point we are sure that resolved_method is virtual and not
// a default or miranda method; therefore, it must have a valid vtable index.
assert(resolved_method->has_vtable_index(), "");
is_itable_index = false;
return resolved_method->vtable_index();
}
bool LinkedConcreteMethodFinder::is_concrete_klass(InstanceKlass* ik) {
if (!Dependencies::is_concrete_klass(ik)) {
return false; // not concrete
}
if (ik->is_interface()) {
return false; // interfaces aren't concrete
}
if (!ik->is_linked()) {
return false; // not yet linked classes don't have instances
}
return true;
}
#ifdef ASSERT
// Assert that m is inherited into ctxk, without intervening overrides.
// (May return true even if this is not true, in corner cases where we punt.)
@ -1501,7 +1717,7 @@ Klass* Dependencies::check_leaf_type(InstanceKlass* ctxk) {
// when dealing with the types of actual instances.
Klass* Dependencies::check_abstract_with_unique_concrete_subtype(InstanceKlass* ctxk,
Klass* conck,
KlassDepChange* changes) {
NewKlassDepChange* changes) {
ConcreteSubtypeFinder wf(conck);
Klass* k = wf.find_witness(ctxk, changes);
return k;
@ -1538,12 +1754,11 @@ Klass* Dependencies::find_unique_concrete_subtype(InstanceKlass* ctxk) {
}
}
// If a class (or interface) has a unique concrete method uniqm, return NULL.
// Otherwise, return a class that contains an interfering method.
Klass* Dependencies::check_unique_concrete_method(InstanceKlass* ctxk,
Method* uniqm,
KlassDepChange* changes) {
NewKlassDepChange* changes) {
// Here is a missing optimization: If uniqm->is_final(),
// we don't really need to search beneath it for overrides.
// This is probably not important, since we don't use dependencies
@ -1557,7 +1772,7 @@ Klass* Dependencies::check_unique_concrete_method(InstanceKlass* ctxk,
// (The method m must be defined or inherited in ctxk.)
// Include m itself in the set, unless it is abstract.
// If this set has exactly one element, return that element.
Method* Dependencies::find_unique_concrete_method(InstanceKlass* ctxk, Method* m) {
Method* Dependencies::find_unique_concrete_method(InstanceKlass* ctxk, Method* m, Klass** participant) {
// Return NULL if m is marked old; must have been a redefined method.
if (m->is_old()) {
return NULL;
@ -1568,6 +1783,9 @@ Method* Dependencies::find_unique_concrete_method(InstanceKlass* ctxk, Method* m
Klass* wit = wf.find_witness(ctxk);
if (wit != NULL) return NULL; // Too many witnesses.
Method* fm = wf.found_method(0); // Will be NULL if num_parts == 0.
if (participant != NULL) {
(*participant) = wf.participant(0);
}
if (Dependencies::is_concrete_method(m, ctxk)) {
if (fm == NULL) {
// It turns out that m was always the only implementation.
@ -1588,7 +1806,95 @@ Method* Dependencies::find_unique_concrete_method(InstanceKlass* ctxk, Method* m
return fm;
}
Klass* Dependencies::check_has_no_finalizable_subclasses(InstanceKlass* ctxk, KlassDepChange* changes) {
// If a class (or interface) has a unique concrete method uniqm, return NULL.
// Otherwise, return a class that contains an interfering method.
Klass* Dependencies::check_unique_concrete_method(InstanceKlass* ctxk,
Method* uniqm,
Klass* resolved_klass,
Method* resolved_method,
KlassDepChange* changes) {
assert(UseVtableBasedCHA, "required");
assert(!ctxk->is_interface() || ctxk == resolved_klass, "sanity");
assert(!resolved_method->can_be_statically_bound() || resolved_method == uniqm, "sanity");
assert(resolved_klass->is_subtype_of(resolved_method->method_holder()), "sanity");
if (!InstanceKlass::cast(resolved_klass)->is_linked() ||
!resolved_method->method_holder()->is_linked() ||
resolved_method->can_be_statically_bound()) {
// Dependency is redundant, but benign. Just keep it to avoid unnecessary recompilation.
return NULL; // no vtable index available
}
LinkedConcreteMethodFinder mf(InstanceKlass::cast(resolved_klass), resolved_method, uniqm);
return mf.find_witness(ctxk, changes);
}
// Find the set of all non-abstract methods under ctxk that match m.
// (The method m must be defined or inherited in ctxk.)
// Include m itself in the set, unless it is abstract.
// If this set has exactly one element, return that element.
// Not yet linked subclasses of ctxk are ignored since they don't have any instances yet.
// Additionally, resolved_klass and resolved_method complete the description of the call site being analyzed.
Method* Dependencies::find_unique_concrete_method(InstanceKlass* ctxk, Method* m, Klass* resolved_klass, Method* resolved_method) {
// Return NULL if m is marked old; must have been a redefined method.
if (m->is_old()) {
return NULL;
}
if (!InstanceKlass::cast(resolved_klass)->is_linked() ||
!resolved_method->method_holder()->is_linked() ||
resolved_method->can_be_statically_bound()) {
return m; // nothing to do: no witness under ctxk
}
LinkedConcreteMethodFinder wf(InstanceKlass::cast(resolved_klass), resolved_method);
assert(Dependencies::verify_method_context(ctxk, m), "proper context");
wf.record_witnesses(1);
Klass* wit = wf.find_witness(ctxk);
if (wit != NULL) {
return NULL; // Too many witnesses.
}
// p == NULL when no participants are found (wf.num_participants() == 0).
// fm == NULL case has 2 meanings:
// * when p == NULL: no method found;
// * when p != NULL: AbstractMethodError-throwing method found.
// Also, found method should always be accompanied by a participant class.
Klass* p = wf.participant(0);
Method* fm = wf.found_method(0);
assert(fm == NULL || p != NULL, "no participant");
// Normalize all error-throwing cases to NULL.
if (fm == Universe::throw_illegal_access_error() ||
fm == Universe::throw_no_such_method_error() ||
!Dependencies::is_concrete_method(fm, p)) {
fm = NULL; // error-throwing method
}
if (Dependencies::is_concrete_method(m, ctxk)) {
if (p == NULL) {
// It turns out that m was always the only implementation.
assert(fm == NULL, "sanity");
fm = m;
}
}
#ifndef PRODUCT
// Make sure the dependency mechanism will pass this discovery:
if (VerifyDependencies && fm != NULL) {
guarantee(NULL == check_unique_concrete_method(ctxk, fm, resolved_klass, resolved_method),
"verify dep.");
}
#endif // PRODUCT
assert(fm == NULL || !fm->is_abstract(), "sanity");
// Old CHA conservatively reports concrete methods in abstract classes
// irrespective of whether they have concrete subclasses or not.
#ifdef ASSERT
Klass* uniqp = NULL;
Method* uniqm = Dependencies::find_unique_concrete_method(ctxk, m, &uniqp);
assert(uniqm == NULL || uniqm == fm ||
uniqm->method_holder()->is_abstract() ||
(fm == NULL && uniqm != NULL && uniqp != NULL && !InstanceKlass::cast(uniqp)->is_linked()),
"sanity");
#endif // ASSERT
return fm;
}
Klass* Dependencies::check_has_no_finalizable_subclasses(InstanceKlass* ctxk, NewKlassDepChange* changes) {
Klass* search_at = ctxk;
if (changes != NULL)
search_at = changes->new_type(); // just look at the new bit
@ -1624,8 +1930,7 @@ void Dependencies::DepStream::trace_and_log_witness(Klass* witness) {
}
}
Klass* Dependencies::DepStream::check_klass_dependency(KlassDepChange* changes) {
Klass* Dependencies::DepStream::check_new_klass_dependency(NewKlassDepChange* changes) {
assert_locked_or_safepoint(Compile_lock);
Dependencies::check_valid_dependency_type(type());
@ -1640,9 +1945,12 @@ Klass* Dependencies::DepStream::check_klass_dependency(KlassDepChange* changes)
case abstract_with_unique_concrete_subtype:
witness = check_abstract_with_unique_concrete_subtype(context_type(), type_argument(1), changes);
break;
case unique_concrete_method:
case unique_concrete_method_2:
witness = check_unique_concrete_method(context_type(), method_argument(1), changes);
break;
case unique_concrete_method_4:
witness = check_unique_concrete_method(context_type(), method_argument(1), type_argument(2), method_argument(3), changes);
break;
case no_finalizable_subclasses:
witness = check_has_no_finalizable_subclasses(context_type(), changes);
break;
@ -1654,6 +1962,41 @@ Klass* Dependencies::DepStream::check_klass_dependency(KlassDepChange* changes)
return witness;
}
Klass* Dependencies::DepStream::check_klass_init_dependency(KlassInitDepChange* changes) {
assert_locked_or_safepoint(Compile_lock);
Dependencies::check_valid_dependency_type(type());
// No new types added. Only unique_concrete_method_4 is sensitive to class initialization changes.
Klass* witness = NULL;
switch (type()) {
case unique_concrete_method_4:
witness = check_unique_concrete_method(context_type(), method_argument(1), type_argument(2), method_argument(3), changes);
break;
default:
witness = NULL;
break;
}
trace_and_log_witness(witness);
return witness;
}
Klass* Dependencies::DepStream::check_klass_dependency(KlassDepChange* changes) {
assert_locked_or_safepoint(Compile_lock);
Dependencies::check_valid_dependency_type(type());
if (changes != NULL) {
if (UseVtableBasedCHA && changes->is_klass_init_change()) {
return check_klass_init_dependency(changes->as_klass_init_change());
} else {
return check_new_klass_dependency(changes->as_new_klass_change());
}
} else {
Klass* witness = check_new_klass_dependency(NULL);
// check_klass_init_dependency duplicates check_new_klass_dependency checks when class hierarchy change info is absent.
assert(witness != NULL || check_klass_init_dependency(NULL) == NULL, "missed dependency");
return witness;
}
}
Klass* Dependencies::DepStream::check_call_site_dependency(CallSiteDepChange* changes) {
assert_locked_or_safepoint(Compile_lock);
@ -1719,9 +2062,9 @@ void DepChange::print() {
}
void DepChange::ContextStream::start() {
Klass* new_type = _changes.is_klass_change() ? _changes.as_klass_change()->new_type() : (Klass*) NULL;
_change_type = (new_type == NULL ? NO_CHANGE : Start_Klass);
_klass = new_type;
Klass* type = (_changes.is_klass_change() ? _changes.as_klass_change()->type() : (Klass*) NULL);
_change_type = (type == NULL ? NO_CHANGE : Start_Klass);
_klass = type;
_ti_base = NULL;
_ti_index = 0;
_ti_limit = 0;
@ -1791,7 +2134,7 @@ bool KlassDepChange::involves_context(Klass* k) {
}
InstanceKlass* ik = InstanceKlass::cast(k);
bool is_contained = ik->is_marked_dependent();
assert(is_contained == new_type()->is_subtype_of(k),
assert(is_contained == type()->is_subtype_of(k),
"correct marking of potential context types");
return is_contained;
}

View File

@ -60,6 +60,8 @@ class CompileLog;
class CompileTask;
class DepChange;
class KlassDepChange;
class NewKlassDepChange;
class KlassInitDepChange;
class CallSiteDepChange;
class NoSafepointVerifier;
@ -132,7 +134,14 @@ class Dependencies: public ResourceObj {
// context class CX. M1 must be either inherited in CX or defined
// in a subtype* of CX. It asserts that MM(CX, M1) is no greater
// than {M1}.
unique_concrete_method, // one unique concrete method under CX
unique_concrete_method_2, // one unique concrete method under CX
// In addition to the method M1 and the context class CX, the parameters
// to this dependency are the resolved class RC1 and the
// resolved method RM1. It asserts that MM(CX, M1, RC1, RM1)
// is no greater than {M1}. RC1 and RM1 are used to improve the precision
// of the analysis.
unique_concrete_method_4, // one unique concrete method under CX
// This dependency asserts that no instances of class or it's
// subclasses require finalization registration.
@ -156,7 +165,7 @@ class Dependencies: public ResourceObj {
implicit_ctxk_types = 0,
explicit_ctxk_types = all_types & ~(non_ctxk_types | implicit_ctxk_types),
max_arg_count = 3, // current maximum number of arguments (incl. ctxk)
max_arg_count = 4, // current maximum number of arguments (incl. ctxk)
// A "context type" is a class or interface that
// provides context for evaluating a dependency.
@ -325,6 +334,7 @@ class Dependencies: public ResourceObj {
void assert_common_1(DepType dept, ciBaseObject* x);
void assert_common_2(DepType dept, ciBaseObject* x0, ciBaseObject* x1);
void assert_common_4(DepType dept, ciKlass* ctxk, ciBaseObject* x1, ciBaseObject* x2, ciBaseObject* x3);
public:
// Adding assertions to a new dependency set at compile time:
@ -332,6 +342,7 @@ class Dependencies: public ResourceObj {
void assert_leaf_type(ciKlass* ctxk);
void assert_abstract_with_unique_concrete_subtype(ciKlass* ctxk, ciKlass* conck);
void assert_unique_concrete_method(ciKlass* ctxk, ciMethod* uniqm);
void assert_unique_concrete_method(ciKlass* ctxk, ciMethod* uniqm, ciKlass* resolved_klass, ciMethod* resolved_method);
void assert_has_no_finalizable_subclasses(ciKlass* ctxk);
void assert_call_site_target_value(ciCallSite* call_site, ciMethodHandle* method_handle);
@ -398,9 +409,10 @@ class Dependencies: public ResourceObj {
// Checking old assertions at run-time (in the VM only):
static Klass* check_evol_method(Method* m);
static Klass* check_leaf_type(InstanceKlass* ctxk);
static Klass* check_abstract_with_unique_concrete_subtype(InstanceKlass* ctxk, Klass* conck, KlassDepChange* changes = NULL);
static Klass* check_unique_concrete_method(InstanceKlass* ctxk, Method* uniqm, KlassDepChange* changes = NULL);
static Klass* check_has_no_finalizable_subclasses(InstanceKlass* ctxk, KlassDepChange* changes = NULL);
static Klass* check_abstract_with_unique_concrete_subtype(InstanceKlass* ctxk, Klass* conck, NewKlassDepChange* changes = NULL);
static Klass* check_unique_concrete_method(InstanceKlass* ctxk, Method* uniqm, NewKlassDepChange* changes = NULL);
static Klass* check_unique_concrete_method(InstanceKlass* ctxk, Method* uniqm, Klass* resolved_klass, Method* resolved_method, KlassDepChange* changes = NULL);
static Klass* check_has_no_finalizable_subclasses(InstanceKlass* ctxk, NewKlassDepChange* changes = NULL);
static Klass* check_call_site_target_value(oop call_site, oop method_handle, CallSiteDepChange* changes = NULL);
// A returned Klass* is NULL if the dependency assertion is still
// valid. A non-NULL Klass* is a 'witness' to the assertion
@ -418,7 +430,9 @@ class Dependencies: public ResourceObj {
// Detecting possible new assertions:
static Klass* find_unique_concrete_subtype(InstanceKlass* ctxk);
static Method* find_unique_concrete_method(InstanceKlass* ctxk, Method* m);
static Method* find_unique_concrete_method(InstanceKlass* ctxk, Method* m,
Klass** participant = NULL); // out parameter
static Method* find_unique_concrete_method(InstanceKlass* ctxk, Method* m, Klass* resolved_klass, Method* resolved_method);
#ifdef ASSERT
static bool verify_method_context(InstanceKlass* ctxk, Method* m);
@ -456,7 +470,8 @@ class Dependencies: public ResourceObj {
void log_dependency(DepType dept,
ciBaseObject* x0,
ciBaseObject* x1 = NULL,
ciBaseObject* x2 = NULL) {
ciBaseObject* x2 = NULL,
ciBaseObject* x3 = NULL) {
if (log() == NULL) {
return;
}
@ -472,6 +487,9 @@ class Dependencies: public ResourceObj {
if (x2 != NULL) {
ciargs->push(x2);
}
if (x3 != NULL) {
ciargs->push(x3);
}
assert(ciargs->length() == dep_args(dept), "");
log_dependency(dept, ciargs);
}
@ -549,6 +567,8 @@ class Dependencies: public ResourceObj {
inline oop recorded_oop_at(int i);
Klass* check_klass_dependency(KlassDepChange* changes);
Klass* check_new_klass_dependency(NewKlassDepChange* changes);
Klass* check_klass_init_dependency(KlassInitDepChange* changes);
Klass* check_call_site_dependency(CallSiteDepChange* changes);
void trace_and_log_witness(Klass* witness);
@ -647,8 +667,10 @@ class DependencySignature : public ResourceObj {
class DepChange : public StackObj {
public:
// What kind of DepChange is this?
virtual bool is_klass_change() const { return false; }
virtual bool is_call_site_change() const { return false; }
virtual bool is_klass_change() const { return false; }
virtual bool is_new_klass_change() const { return false; }
virtual bool is_klass_init_change() const { return false; }
virtual bool is_call_site_change() const { return false; }
virtual void mark_for_deoptimization(nmethod* nm) = 0;
@ -657,6 +679,14 @@ class DepChange : public StackObj {
assert(is_klass_change(), "bad cast");
return (KlassDepChange*) this;
}
NewKlassDepChange* as_new_klass_change() {
assert(is_new_klass_change(), "bad cast");
return (NewKlassDepChange*) this;
}
KlassInitDepChange* as_klass_init_change() {
assert(is_klass_init_change(), "bad cast");
return (KlassInitDepChange*) this;
}
CallSiteDepChange* as_call_site_change() {
assert(is_call_site_change(), "bad cast");
return (CallSiteDepChange*) this;
@ -716,28 +746,27 @@ class DepChange : public StackObj {
// A class hierarchy change coming through the VM (under the Compile_lock).
// The change is structured as a single new type with any number of supers
// and implemented interface types. Other than the new type, any of the
// The change is structured as a single type with any number of supers
// and implemented interface types. Other than the type, any of the
// super types can be context types for a relevant dependency, which the
// new type could invalidate.
// type could invalidate.
class KlassDepChange : public DepChange {
private:
// each change set is rooted in exactly one new type (at present):
InstanceKlass* _new_type;
// each change set is rooted in exactly one type (at present):
InstanceKlass* _type;
void initialize();
public:
// notes the new type, marks it and all its super-types
KlassDepChange(InstanceKlass* new_type)
: _new_type(new_type)
{
protected:
// notes the type, marks it and all its super-types
KlassDepChange(InstanceKlass* type) : _type(type) {
initialize();
}
// cleans up the marks
~KlassDepChange();
public:
// What kind of DepChange is this?
virtual bool is_klass_change() const { return true; }
@ -745,12 +774,31 @@ class KlassDepChange : public DepChange {
nm->mark_for_deoptimization(/*inc_recompile_counts=*/true);
}
InstanceKlass* new_type() { return _new_type; }
InstanceKlass* type() { return _type; }
// involves_context(k) is true if k is new_type or any of the super types
// involves_context(k) is true if k == _type or any of its super types
bool involves_context(Klass* k);
};
// A class hierarchy change: new type is loaded.
class NewKlassDepChange : public KlassDepChange {
public:
NewKlassDepChange(InstanceKlass* new_type) : KlassDepChange(new_type) {}
// What kind of DepChange is this?
virtual bool is_new_klass_change() const { return true; }
InstanceKlass* new_type() { return type(); }
};
// Change in initialization state of a loaded class.
class KlassInitDepChange : public KlassDepChange {
public:
KlassInitDepChange(InstanceKlass* type) : KlassDepChange(type) {}
// What kind of DepChange is this?
virtual bool is_klass_init_change() const { return true; }
};
// A CallSite has changed its target.
class CallSiteDepChange : public DepChange {

View File

@ -40,6 +40,7 @@
#include "classfile/verifier.hpp"
#include "classfile/vmClasses.hpp"
#include "classfile/vmSymbols.hpp"
#include "code/codeCache.hpp"
#include "code/dependencyContext.hpp"
#include "compiler/compilationPolicy.hpp"
#include "compiler/compileBroker.hpp"
@ -955,7 +956,17 @@ bool InstanceKlass::link_class_impl(TRAPS) {
// In case itable verification is ever added.
// itable().verify(tty, true);
#endif
set_init_state(linked);
if (UseVtableBasedCHA) {
MutexLocker ml(THREAD, Compile_lock);
set_init_state(linked);
// Now flush all code that assume the class is not linked.
if (Universe::is_fully_initialized()) {
CodeCache::flush_dependents_on(this);
}
} else {
set_init_state(linked);
}
if (JvmtiExport::should_post_class_prepare()) {
JvmtiExport::post_class_prepare(THREAD->as_Java_thread(), this);
}

View File

@ -877,7 +877,7 @@ class Compile : public Phase {
const TypeOopPtr* receiver_type, bool is_virtual,
bool &call_does_dispatch, int &vtable_index,
bool check_access = true);
ciMethod* optimize_inlining(ciMethod* caller, ciInstanceKlass* klass,
ciMethod* optimize_inlining(ciMethod* caller, ciInstanceKlass* klass, ciKlass* holder,
ciMethod* callee, const TypeOopPtr* receiver_type,
bool check_access = true);

View File

@ -324,7 +324,7 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool
CallGenerator* cg = CallGenerator::for_guarded_call(holder, miss_cg, hit_cg);
if (hit_cg != NULL && cg != NULL) {
dependencies()->assert_unique_concrete_method(declared_interface, cha_monomorphic_target);
dependencies()->assert_unique_concrete_method(declared_interface, cha_monomorphic_target, declared_interface, callee);
return cg;
}
}
@ -1071,7 +1071,7 @@ ciMethod* Compile::optimize_virtual_call(ciMethod* caller, ciInstanceKlass* klas
vtable_index = Method::invalid_vtable_index;
// Choose call strategy.
ciMethod* optimized_virtual_method = optimize_inlining(caller, klass, callee,
ciMethod* optimized_virtual_method = optimize_inlining(caller, klass, holder, callee,
receiver_type, check_access);
// Have the call been sufficiently improved such that it is no longer a virtual?
@ -1086,7 +1086,7 @@ ciMethod* Compile::optimize_virtual_call(ciMethod* caller, ciInstanceKlass* klas
}
// Identify possible target method and inlining style
ciMethod* Compile::optimize_inlining(ciMethod* caller, ciInstanceKlass* klass,
ciMethod* Compile::optimize_inlining(ciMethod* caller, ciInstanceKlass* klass, ciKlass* holder,
ciMethod* callee, const TypeOopPtr* receiver_type,
bool check_access) {
// only use for virtual or interface calls
@ -1165,7 +1165,7 @@ ciMethod* Compile::optimize_inlining(ciMethod* caller, ciInstanceKlass* klass,
// by dynamic class loading. Be sure to test the "static" receiver
// dest_method here, as opposed to the actual receiver, which may
// falsely lead us to believe that the receiver is final or private.
dependencies()->assert_unique_concrete_method(actual_receiver, cha_monomorphic_target);
dependencies()->assert_unique_concrete_method(actual_receiver, cha_monomorphic_target, holder, callee);
}
return cha_monomorphic_target;
}

View File

@ -994,6 +994,9 @@ const intx ObjectAlignmentInBytes = 8;
develop(bool, UseCHA, true, \
"Enable CHA") \
\
product(bool, UseVtableBasedCHA, true, DIAGNOSTIC, \
"Use vtable information during CHA") \
\
product(bool, UseTypeProfile, true, \
"Check interpreter profile for historically monomorphic calls") \
\

View File

@ -23,7 +23,7 @@
/*
* @test
* @requires !vm.graal.enabled
* @requires !vm.graal.enabled & vm.opt.final.UseVtableBasedCHA == true
* @modules java.base/jdk.internal.org.objectweb.asm
* java.base/jdk.internal.misc
* java.base/jdk.internal.vm.annotation
@ -263,7 +263,8 @@ public class StrengthReduceInterfaceCall {
interface K1 extends I {}
interface K2 extends I { Object m(); }
interface K3 extends I { default Object m() { return WRONG; }}
interface K3 extends I { default Object m() { return WRONG; }}
interface K4 extends I { default Object m() { return CORRECT; }}
static class D implements I { public Object m() { return WRONG; }}
@ -318,19 +319,43 @@ public class StrengthReduceInterfaceCall {
checkInvalidReceiver(); // ensure proper type check on receiver is preserved
// 1. Dependency invalidation
// 1. No invalidation: interfaces don't participate in CHA.
initialize(K3.class); // intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
assertNotCompiled();
assertCompiled();
// 2. Recompilation: still inlines
// FIXME: no default method support in CHA yet
compile(megamorphic());
// 2. Dependency invalidation on K3n <: I with concrete method.
call(new K3() { public Object m() { return CORRECT; }}); // K3n.m <: intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m ABSTRACT
assertNotCompiled();
// 3. Recompilation: no inlining, no dependencies
compile(megamorphic());
call(new K3() { public Object m() { return CORRECT; }}); // Kn.m <: intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
call(new K3() { public Object m() { return CORRECT; }}); // K3n.m <: intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
assertCompiled();
checkInvalidReceiver(); // ensure proper type check on receiver is preserved
}
@TestCase
public void testMega3() {
// 0. Trigger compilation of a megamorphic call site
compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I.m ABSTRACT <: intf J.m DEFAULT
assertCompiled();
// Dependency: type = unique_concrete_method, context = I, method = C.m
checkInvalidReceiver(); // ensure proper type check on receiver is preserved
// 1. No invalidation: interfaces don't participate in CHA.
initialize(K4.class); // intf K4.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
assertCompiled();
// 2. Dependency invalidation on K4n <: I with default method.
call(new K4() { /* default method K4.m */ }); // K4n <: intf K4.m DEFAULT <: intf I.m ABSTRACT <: intf J.m ABSTRACT
assertNotCompiled();
// 3. Recompilation: no inlining, no dependencies
compile(megamorphic());
call(new K4() { /* default method K4.m */ }); // K4n <: intf K3.m DEFAULT <: intf I.m ABSTRACT <: intf J.m DEFAULT
assertCompiled();
checkInvalidReceiver(); // ensure proper type check on receiver is preserved
@ -360,7 +385,8 @@ public class StrengthReduceInterfaceCall {
interface K1 extends I {}
interface K2 extends I { Object m(); }
interface K3 extends I { default Object m() { return WRONG; }}
interface K3 extends I { default Object m() { return WRONG; }}
interface K4 extends I { default Object m() { return CORRECT; }}
static class C implements I { public Object m() { return CORRECT; }}
@ -413,12 +439,8 @@ public class StrengthReduceInterfaceCall {
checkInvalidReceiver(); // ensure proper type check on receiver is preserved
// Dependency invalidation
// No invalidation: interfaces don't participate in CHA.
initialize(K3.class); // intf K3.m DEFAULT <: intf I;
assertNotCompiled(); // FIXME: default methods in sub-interfaces shouldn't be taken into account by CHA
// Recompilation with a dependency
compile(megamorphic());
assertCompiled();
// Dependency: type = unique_concrete_method, context = I, method = C.m
@ -436,6 +458,34 @@ public class StrengthReduceInterfaceCall {
assertCompiled();
}
@TestCase
public void testMega3() {
compile(megamorphic()); // C1,C2,C3 <: C.m <: intf I <: intf J.m ABSTRACT
assertCompiled();
// Dependency: type = unique_concrete_method, context = I, method = C.m
checkInvalidReceiver(); // ensure proper type check on receiver is preserved
// No invalidation: interfaces don't participate in CHA.
initialize(K4.class); // intf K3.m DEFAULT <: intf I;
assertCompiled();
// Dependency: type = unique_concrete_method, context = I, method = C.m
checkInvalidReceiver(); // ensure proper type check on receiver is preserved
call(new K4() { /* default method K4.m */ }); // K4n <: K4.m DEFAULT <: intf I <: intf J.m ABSTRACT
assertNotCompiled();
// Recompilation w/o a dependency
compile(megamorphic());
// Dependency: none
checkInvalidReceiver(); // ensure proper type check on receiver is preserved
call(new C() { public Object m() { return CORRECT; }}); // Cn.m <: C.m <: intf I <: intf J.m ABSTRACT
assertCompiled();
}
@Override
public void checkInvalidReceiver() {
shouldThrow(IncompatibleClassChangeError.class, () -> {