VM: Improve specialization of calls on receiver in AOT mode.

When encountering call this.m(...) we can check if m resolves to the same
method in all concrete subclasses of the receiver class. If it does then
we don't need any checks and can just use a StaticCall instead of InstanceCall.

This is allows us to improve code quality for cases like:

    class Base {
      var _field;

      foo() { _field = true; }
    }

    class A01 extends Base { }
    ...
    class A16 extends Base { }

Previously AOT would generate InstanceCall(get:_field) in the method foo.

However with this change we generate StaticCall(...) which subsequently gets
handled by the inliner.

R=fschneider@google.com
BUG=

Review URL: https://codereview.chromium.org/2055263002 .
This commit is contained in:
Vyacheslav Egorov 2016-06-10 18:12:49 +02:00
parent 51bb8b289e
commit ec9b1c385a
2 changed files with 105 additions and 40 deletions

View file

@ -2465,58 +2465,104 @@ void AotOptimizer::VisitInstanceCall(InstanceCallInstr* instr) {
flow_graph_->IsReceiver(callee_receiver)) {
// Call receiver is method receiver.
Class& receiver_class = Class::Handle(Z, function.Owner());
GrowableArray<intptr_t> class_ids(6);
if (thread()->cha()->ConcreteSubclasses(receiver_class, &class_ids)) {
if (class_ids.length() <= FLAG_max_exhaustive_polymorphic_checks) {
if (FLAG_trace_cha) {
THR_Print(" **(CHA) Only %" Pd " concrete subclasses of %s for %s\n",
class_ids.length(),
receiver_class.ToCString(),
instr->function_name().ToCString());
}
// First check if all subclasses end up calling the same method.
// If this is the case we will replace instance call with a direct
// static call.
// Otherwise we will try to create ICData that contains all possible
// targets with appropriate checks.
Function& single_target = Function::Handle(Z);
ICData& ic_data = ICData::Handle(Z);
const Array& args_desc_array = Array::Handle(Z,
ArgumentsDescriptor::New(instr->ArgumentCount(),
instr->argument_names()));
ArgumentsDescriptor args_desc(args_desc_array);
const Array& args_desc_array = Array::Handle(Z,
ArgumentsDescriptor::New(instr->ArgumentCount(),
instr->argument_names()));
ArgumentsDescriptor args_desc(args_desc_array);
const ICData& ic_data = ICData::Handle(
ICData::New(function,
instr->function_name(),
args_desc_array,
Thread::kNoDeoptId,
/* args_tested = */ 1));
Function& target = Function::Handle(Z);
Class& cls = Class::Handle(Z);
for (intptr_t i = 0; i < class_ids.length(); i++) {
const intptr_t cid = class_ids[i];
cls = isolate()->class_table()->At(cid);
target = Resolver::ResolveDynamicForReceiverClass(
cls,
instr->function_name(),
args_desc);
Function& target = Function::Handle(Z);
Class& cls = Class::Handle(Z);
bool includes_dispatcher_case = false;
for (intptr_t i = 0; i < class_ids.length(); i++) {
intptr_t cid = class_ids[i];
cls = isolate()->class_table()->At(cid);
target = Resolver::ResolveDynamicForReceiverClass(
cls,
instr->function_name(),
args_desc);
if (target.IsNull()) {
// noSuchMethod, call through getter or closurization
includes_dispatcher_case = true;
} else {
ic_data.AddReceiverCheck(cid, target);
if (target.IsNull()) {
// Can't resolve the target. It might be a noSuchMethod,
// call through getter or closurization.
single_target = Function::null();
ic_data = ICData::null();
break;
} else if (ic_data.IsNull()) {
// First we are trying to compute a single target for all subclasses.
if (single_target.IsNull()) {
ASSERT(i == 0);
single_target = target.raw();
continue;
} else if (single_target.raw() == target.raw()) {
continue;
}
// The call does not resolve to a single target within the hierarchy.
// If we have too many subclasses abort the optimization.
if (class_ids.length() > FLAG_max_exhaustive_polymorphic_checks) {
single_target = Function::null();
break;
}
// Create an ICData and map all previously seen classes (< i) to
// the computed single_target.
ic_data = ICData::New(function,
instr->function_name(),
args_desc_array,
Thread::kNoDeoptId,
/* args_tested = */ 1);
for (intptr_t j = 0; j < i; j++) {
ic_data.AddReceiverCheck(class_ids[j], single_target);
}
single_target = Function::null();
}
if (!includes_dispatcher_case && (ic_data.NumberOfChecks() > 0)) {
PolymorphicInstanceCallInstr* call =
new(Z) PolymorphicInstanceCallInstr(instr, ic_data,
/* with_checks = */ true,
/* complete = */ true);
instr->ReplaceWith(call, current_iterator());
return;
ASSERT(ic_data.raw() != ICData::null());
ASSERT(single_target.raw() == Function::null());
ic_data.AddReceiverCheck(cid, target);
}
if (single_target.raw() != Function::null()) {
// We have computed that there is only a single target for this call
// within the whole hierarchy. Replace InstanceCall with StaticCall.
ZoneGrowableArray<PushArgumentInstr*>* args =
new (Z) ZoneGrowableArray<PushArgumentInstr*>(
instr->ArgumentCount());
for (intptr_t i = 0; i < instr->ArgumentCount(); i++) {
args->Add(instr->PushArgumentAt(i));
}
StaticCallInstr* call = new (Z) StaticCallInstr(
instr->token_pos(),
Function::ZoneHandle(Z, single_target.raw()),
instr->argument_names(),
args,
instr->deopt_id());
instr->ReplaceWith(call, current_iterator());
return;
} else if ((ic_data.raw() != ICData::null()) &&
(ic_data.NumberOfChecks() > 0)) {
PolymorphicInstanceCallInstr* call =
new(Z) PolymorphicInstanceCallInstr(instr, ic_data,
/* with_checks = */ true,
/* complete = */ true);
instr->ReplaceWith(call, current_iterator());
return;
}
}
}
// More than one targets. Generate generic polymorphic call without
// More than one target. Generate generic polymorphic call without
// deoptimization.
if (instr->ic_data()->NumberOfUsedChecks() > 0) {
ASSERT(!FLAG_polymorphic_with_deopt);

View file

@ -3267,6 +3267,25 @@ class StaticCallInstr : public TemplateDefinition<0, Throws> {
ASSERT(argument_names.IsZoneHandle() || argument_names.InVMHeap());
}
StaticCallInstr(TokenPosition token_pos,
const Function& function,
const Array& argument_names,
ZoneGrowableArray<PushArgumentInstr*>* arguments,
intptr_t deopt_id)
: TemplateDefinition(deopt_id),
ic_data_(NULL),
token_pos_(token_pos),
function_(function),
argument_names_(argument_names),
arguments_(arguments),
result_cid_(kDynamicCid),
is_known_list_constructor_(false),
is_native_list_factory_(false),
identity_(AliasIdentity::Unknown()) {
ASSERT(function.IsZoneHandle());
ASSERT(argument_names.IsZoneHandle() || argument_names.InVMHeap());
}
// ICData for static calls carries call count.
const ICData* ic_data() const { return ic_data_; }
bool HasICData() const {