[vm] Detect and expand calls through getters in AOT call specializer.

If we know that receiver is a subclass of a certain type and that this
type has accessor get:m then call o.m(...) is guaranteed to be an
invocation through a getter. Such invocations are executed most
efficiently when expanded into o.get:m().call(...).

Source based pipeline handles this case (at least for invocations on
`this`) directly in the parser, but Kernel based graph builder does not
have this sort of special case.

Instead of teaching Kernel flow graph builder to specially handle
invocations on `this` we teach AOT call specializer to specially handle
all invocations where receiver is known to be a subclass of certain
class. Such optimization is more generic and handles things that
previously were not handled by the optimization.

AOT compiler also has heuristics for injecting field dispatchers into
classes but these heuristics only handle fields of function type and
don't work for fields like `Function f` or `var f`.

Improves ParserCombinators benchmark by 4x.

This relands eea2c168f9 with a fix.

Change-Id: I13a41544c737b980efd431e31e4d15ad31da853e
Reviewed-on: https://dart-review.googlesource.com/30455
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Vyacheslav Egorov <vegorov@google.com>
This commit is contained in:
Vyacheslav Egorov 2017-12-19 21:02:04 +00:00 committed by commit-bot@chromium.org
parent 166cc11a64
commit d290d8ecf4
7 changed files with 189 additions and 24 deletions

View file

@ -513,6 +513,20 @@ bool AotCallSpecializer::TryOptimizeStaticCallUsingStaticTypes(
return false;
}
static void EnsureICData(Zone* zone,
const Function& function,
InstanceCallInstr* call) {
if (!call->HasICData()) {
const Array& arguments_descriptor =
Array::Handle(zone, call->GetArgumentsDescriptor());
const ICData& ic_data = ICData::ZoneHandle(
zone, ICData::New(function, call->function_name(), arguments_descriptor,
call->deopt_id(), call->checked_argument_count(),
ICData::kInstance));
call->set_ic_data(&ic_data);
}
}
// Tries to optimize instance call by replacing it with a faster instruction
// (e.g, binary op, field load, ..).
// TODO(dartbug.com/30635) Evaluate how much this can be shared with
@ -780,6 +794,12 @@ void AotCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) {
return;
}
}
// Detect if o.m(...) is a call through a getter and expand it
// into o.get:m().call(...).
if (TryExpandCallThroughGetter(receiver_class, instr)) {
return;
}
}
// More than one target. Generate generic polymorphic call without
@ -797,6 +817,104 @@ void AotCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) {
}
}
bool AotCallSpecializer::TryExpandCallThroughGetter(const Class& receiver_class,
InstanceCallInstr* call) {
// If it's an accessor call it can't be a call through getter.
if (call->token_kind() == Token::kGET || call->token_kind() == Token::kSET) {
return false;
}
// Ignore callsites like f.call() for now. Those need to be handled
// specially if f is a closure.
if (call->function_name().raw() == Symbols::Call().raw()) {
return false;
}
Function& target = Function::Handle(Z);
const String& getter_name = String::ZoneHandle(
Z, Symbols::LookupFromGet(thread(), call->function_name()));
if (getter_name.IsNull()) {
return false;
}
const Array& args_desc_array = Array::Handle(
Z, ArgumentsDescriptor::New(/*type_args_len=*/0, /*num_arguments=*/1));
ArgumentsDescriptor args_desc(args_desc_array);
target = Resolver::ResolveDynamicForReceiverClass(
receiver_class, getter_name, args_desc, /*allow_add=*/false);
if (target.raw() == Function::null() || target.IsMethodExtractor()) {
return false;
}
// We found a getter with the same name as the method this
// call tries to invoke. This implies call through getter
// because methods can't override getters. Build
// o.get:m().call(...) sequence and replace o.m(...) invocation.
const intptr_t receiver_idx = call->type_args_len() > 0 ? 1 : 0;
PushArgumentsArray* get_arguments = new (Z) PushArgumentsArray(1);
get_arguments->Add(new (Z) PushArgumentInstr(
call->PushArgumentAt(receiver_idx)->value()->CopyWithType()));
InstanceCallInstr* invoke_get = new (Z) InstanceCallInstr(
call->token_pos(), getter_name, Token::kGET, get_arguments,
/*type_args_len=*/0,
/*argument_names=*/Object::empty_array(),
/*checked_argument_count=*/1, thread()->GetNextDeoptId());
// Arguments to the .call() are the same as arguments to the
// original call (including type arguments), but receiver
// is replaced with the result of the get.
PushArgumentsArray* call_arguments =
new (Z) PushArgumentsArray(call->ArgumentCount());
if (call->type_args_len() > 0) {
call_arguments->Add(new (Z) PushArgumentInstr(
call->PushArgumentAt(0)->value()->CopyWithType()));
}
call_arguments->Add(new (Z) PushArgumentInstr(new (Z) Value(invoke_get)));
for (intptr_t i = receiver_idx + 1; i < call->ArgumentCount(); i++) {
call_arguments->Add(new (Z) PushArgumentInstr(
call->PushArgumentAt(i)->value()->CopyWithType()));
}
InstanceCallInstr* invoke_call = new (Z) InstanceCallInstr(
call->token_pos(), Symbols::Call(), Token::kILLEGAL, call_arguments,
call->type_args_len(), call->argument_names(),
/*checked_argument_count=*/1, thread()->GetNextDeoptId());
// Insert all new instructions, except .call() invocation into the
// graph.
for (intptr_t i = 0; i < invoke_get->ArgumentCount(); i++) {
InsertBefore(call, invoke_get->PushArgumentAt(i), NULL, FlowGraph::kEffect);
}
InsertBefore(call, invoke_get, call->env(), FlowGraph::kValue);
for (intptr_t i = 0; i < invoke_call->ArgumentCount(); i++) {
InsertBefore(call, invoke_call->PushArgumentAt(i), NULL,
FlowGraph::kEffect);
}
// Remove original PushArguments from the graph.
for (intptr_t i = 0; i < call->ArgumentCount(); i++) {
call->PushArgumentAt(i)->RemoveFromGraph();
}
// Replace original call with .call(...) invocation.
call->ReplaceWith(invoke_call, current_iterator());
// AOT compiler expects all calls to have an ICData.
EnsureICData(Z, flow_graph()->function(), invoke_get);
EnsureICData(Z, flow_graph()->function(), invoke_call);
// Specialize newly inserted calls.
TryCreateICData(invoke_get);
VisitInstanceCall(invoke_get);
TryCreateICData(invoke_call);
VisitInstanceCall(invoke_call);
// Success.
return true;
}
void AotCallSpecializer::VisitPolymorphicInstanceCall(
PolymorphicInstanceCallInstr* call) {
const intptr_t receiver_idx = call->type_args_len() > 0 ? 1 : 0;

View file

@ -53,6 +53,13 @@ class AotCallSpecializer : public CallSpecializer {
virtual bool TryOptimizeStaticCallUsingStaticTypes(StaticCallInstr* call);
// Check if o.m(...) [call] is actually an invocation through a getter
// o.get:m().call(...) given that the receiver of the call is a subclass
// of the [receiver_class]. If it is - then expand it into
// o.get:m.call(...) to avoid hitting dispatch through noSuchMethod.
bool TryExpandCallThroughGetter(const Class& receiver_class,
InstanceCallInstr* call);
Precompiler* precompiler_;
bool has_unique_no_such_method_;

View file

@ -2954,13 +2954,15 @@ struct ArgumentsInfo {
const Array& argument_names;
};
typedef ZoneGrowableArray<PushArgumentInstr*> PushArgumentsArray;
template <intptr_t kInputCount>
class TemplateDartCall : public TemplateDefinition<kInputCount, Throws> {
public:
TemplateDartCall(intptr_t deopt_id,
intptr_t type_args_len,
const Array& argument_names,
ZoneGrowableArray<PushArgumentInstr*>* arguments,
PushArgumentsArray* arguments,
TokenPosition token_pos,
intptr_t argument_check_bits = 0,
intptr_t type_argument_check_bits = 0)
@ -3001,7 +3003,7 @@ class TemplateDartCall : public TemplateDefinition<kInputCount, Throws> {
private:
intptr_t type_args_len_;
const Array& argument_names_;
ZoneGrowableArray<PushArgumentInstr*>* arguments_;
PushArgumentsArray* arguments_;
TokenPosition token_pos_;
// One bit per argument (up to word size) which helps the callee decide which
@ -3017,7 +3019,7 @@ class ClosureCallInstr : public TemplateDartCall<1> {
public:
ClosureCallInstr(Value* function,
ClosureCallNode* node,
ZoneGrowableArray<PushArgumentInstr*>* arguments,
PushArgumentsArray* arguments,
intptr_t deopt_id)
: TemplateDartCall(deopt_id,
node->arguments()->type_args_len(),
@ -3029,7 +3031,7 @@ class ClosureCallInstr : public TemplateDartCall<1> {
}
ClosureCallInstr(Value* function,
ZoneGrowableArray<PushArgumentInstr*>* arguments,
PushArgumentsArray* arguments,
intptr_t type_args_len,
const Array& argument_names,
TokenPosition token_pos,
@ -3064,7 +3066,7 @@ class InstanceCallInstr : public TemplateDartCall<0> {
TokenPosition token_pos,
const String& function_name,
Token::Kind token_kind,
ZoneGrowableArray<PushArgumentInstr*>* arguments,
PushArgumentsArray* arguments,
intptr_t type_args_len,
const Array& argument_names,
intptr_t checked_argument_count,
@ -3100,6 +3102,44 @@ class InstanceCallInstr : public TemplateDartCall<0> {
token_kind == Token::kSET || token_kind == Token::kILLEGAL);
}
InstanceCallInstr(
TokenPosition token_pos,
const String& function_name,
Token::Kind token_kind,
PushArgumentsArray* arguments,
intptr_t type_args_len,
const Array& argument_names,
intptr_t checked_argument_count,
intptr_t deopt_id,
const Function& interface_target = Function::null_function(),
intptr_t argument_check_bits = 0,
intptr_t type_argument_check_bits = 0)
: TemplateDartCall(deopt_id,
type_args_len,
argument_names,
arguments,
token_pos,
argument_check_bits,
type_argument_check_bits),
ic_data_(NULL),
function_name_(function_name),
token_kind_(token_kind),
checked_argument_count_(checked_argument_count),
interface_target_(interface_target),
has_unique_selector_(false) {
ASSERT(function_name.IsNotTemporaryScopedHandle());
ASSERT(interface_target_.IsNotTemporaryScopedHandle());
ASSERT(!arguments->is_empty());
ASSERT(Token::IsBinaryOperator(token_kind) ||
Token::IsEqualityOperator(token_kind) ||
Token::IsRelationalOperator(token_kind) ||
Token::IsUnaryOperator(token_kind) ||
Token::IsIndexOperator(token_kind) ||
Token::IsTypeTestOperator(token_kind) ||
Token::IsTypeCastOperator(token_kind) || token_kind == Token::kGET ||
token_kind == Token::kSET || token_kind == Token::kILLEGAL);
}
DECLARE_INSTRUCTION(InstanceCall)
const ICData* ic_data() const { return ic_data_; }
@ -3523,7 +3563,7 @@ class StaticCallInstr : public TemplateDartCall<0> {
const Function& function,
intptr_t type_args_len,
const Array& argument_names,
ZoneGrowableArray<PushArgumentInstr*>* arguments,
PushArgumentsArray* arguments,
const ZoneGrowableArray<const ICData*>& ic_data_array,
intptr_t deopt_id,
ICData::RebindRule rebind_rule,
@ -3552,7 +3592,7 @@ class StaticCallInstr : public TemplateDartCall<0> {
const Function& function,
intptr_t type_args_len,
const Array& argument_names,
ZoneGrowableArray<PushArgumentInstr*>* arguments,
PushArgumentsArray* arguments,
intptr_t deopt_id,
intptr_t call_count,
ICData::RebindRule rebind_rule,
@ -3580,8 +3620,8 @@ class StaticCallInstr : public TemplateDartCall<0> {
static StaticCallInstr* FromCall(Zone* zone,
const C* call,
const Function& target) {
ZoneGrowableArray<PushArgumentInstr*>* args =
new (zone) ZoneGrowableArray<PushArgumentInstr*>(call->ArgumentCount());
PushArgumentsArray* args =
new (zone) PushArgumentsArray(call->ArgumentCount());
for (intptr_t i = 0; i < call->ArgumentCount(); i++) {
args->Add(call->PushArgumentAt(i));
}
@ -3774,7 +3814,7 @@ class NativeCallInstr : public TemplateDartCall<0> {
const Function* function,
bool link_lazily,
TokenPosition position,
ZoneGrowableArray<PushArgumentInstr*>* args)
PushArgumentsArray* args)
: TemplateDartCall(Thread::kNoDeoptId,
0,
Array::null_array(),
@ -4397,7 +4437,7 @@ class AllocateObjectInstr : public TemplateDefinition<0, NoThrow> {
public:
AllocateObjectInstr(TokenPosition token_pos,
const Class& cls,
ZoneGrowableArray<PushArgumentInstr*>* arguments)
PushArgumentsArray* arguments)
: token_pos_(token_pos),
cls_(cls),
arguments_(arguments),
@ -4435,7 +4475,7 @@ class AllocateObjectInstr : public TemplateDefinition<0, NoThrow> {
private:
const TokenPosition token_pos_;
const Class& cls_;
ZoneGrowableArray<PushArgumentInstr*>* const arguments_;
PushArgumentsArray* const arguments_;
AliasIdentity identity_;
Function& closure_function_;

View file

@ -356,7 +356,7 @@ void StubCode::GenerateFixAllocationStubTargetStub(Assembler* assembler) {
// Input parameters:
// R2: smi-tagged argument count, may be zero.
// FP[kParamEndSlotFromFp + 1]: last argument.
static void PushArgumentsArray(Assembler* assembler) {
static void PushArrayOfArguments(Assembler* assembler) {
// Allocate array to store arguments of caller.
__ LoadObject(R1, Object::null_object());
// R1: null element type for raw Array.
@ -592,7 +592,7 @@ static void GenerateDispatcherCode(Assembler* assembler,
__ AddImmediate(R2, R2, Smi::RawValue(1), NE); // Include the type arguments.
// R2: Smi-tagged arguments array length.
PushArgumentsArray(assembler);
PushArrayOfArguments(assembler);
const intptr_t kNumArgs = 4;
__ CallRuntime(kInvokeNoSuchMethodDispatcherRuntimeEntry, kNumArgs);
__ Drop(4);
@ -1234,7 +1234,7 @@ void StubCode::GenerateCallClosureNoSuchMethodStub(Assembler* assembler) {
__ AddImmediate(R2, R2, Smi::RawValue(1), NE); // Include the type arguments.
// R2: Smi-tagged arguments array length.
PushArgumentsArray(assembler);
PushArrayOfArguments(assembler);
const intptr_t kNumArgs = 3;
__ CallRuntime(kInvokeClosureNoSuchMethodRuntimeEntry, kNumArgs);

View file

@ -391,7 +391,7 @@ void StubCode::GenerateFixAllocationStubTargetStub(Assembler* assembler) {
// Input parameters:
// R2: smi-tagged argument count, may be zero.
// FP[kParamEndSlotFromFp + 1]: last argument.
static void PushArgumentsArray(Assembler* assembler) {
static void PushArrayOfArguments(Assembler* assembler) {
// Allocate array to store arguments of caller.
__ LoadObject(R1, Object::null_object());
// R1: null element type for raw Array.
@ -614,7 +614,7 @@ static void GenerateDispatcherCode(Assembler* assembler,
__ csinc(R2, R2, TMP, EQ); // R2 <- (R3 == 0) ? R2 : TMP + 1 (R2 : R2 + 2).
// R2: Smi-tagged arguments array length.
PushArgumentsArray(assembler);
PushArrayOfArguments(assembler);
const intptr_t kNumArgs = 4;
__ CallRuntime(kInvokeNoSuchMethodDispatcherRuntimeEntry, kNumArgs);
__ Drop(4);
@ -1268,7 +1268,7 @@ void StubCode::GenerateCallClosureNoSuchMethodStub(Assembler* assembler) {
__ csinc(R2, R2, TMP, EQ); // R2 <- (R3 == 0) ? R2 : TMP + 1 (R2 : R2 + 2).
// R2: Smi-tagged arguments array length.
PushArgumentsArray(assembler);
PushArrayOfArguments(assembler);
const intptr_t kNumArgs = 3;
__ CallRuntime(kInvokeClosureNoSuchMethodRuntimeEntry, kNumArgs);

View file

@ -298,7 +298,7 @@ void StubCode::GenerateFixAllocationStubTargetStub(Assembler* assembler) {
// EDX: smi-tagged argument count, may be zero.
// EBP[kParamEndSlotFromFp + 1]: last argument.
// Uses EAX, EBX, ECX, EDX, EDI.
static void PushArgumentsArray(Assembler* assembler) {
static void PushArrayOfArguments(Assembler* assembler) {
// Allocate array to store arguments of caller.
const Immediate& raw_null =
Immediate(reinterpret_cast<intptr_t>(Object::null()));
@ -507,7 +507,7 @@ static void GenerateDispatcherCode(Assembler* assembler,
__ Bind(&args_count_ok);
// EDX: Smi-tagged arguments array length.
PushArgumentsArray(assembler);
PushArrayOfArguments(assembler);
const intptr_t kNumArgs = 4;
__ CallRuntime(kInvokeNoSuchMethodDispatcherRuntimeEntry, kNumArgs);
__ Drop(4);
@ -1146,7 +1146,7 @@ void StubCode::GenerateCallClosureNoSuchMethodStub(Assembler* assembler) {
__ Bind(&args_count_ok);
// EDX: Smi-tagged arguments array length.
PushArgumentsArray(assembler);
PushArrayOfArguments(assembler);
const intptr_t kNumArgs = 3;
__ CallRuntime(kInvokeClosureNoSuchMethodRuntimeEntry, kNumArgs);

View file

@ -321,7 +321,7 @@ void StubCode::GenerateFixAllocationStubTargetStub(Assembler* assembler) {
// Input parameters:
// R10: smi-tagged argument count, may be zero.
// RBP[kParamEndSlotFromFp + 1]: last argument.
static void PushArgumentsArray(Assembler* assembler) {
static void PushArrayOfArguments(Assembler* assembler) {
__ LoadObject(R12, Object::null_object());
// Allocate array to store arguments of caller.
__ movq(RBX, R12); // Null element type for raw Array.
@ -554,7 +554,7 @@ static void GenerateDispatcherCode(Assembler* assembler,
__ Bind(&args_count_ok);
// R10: Smi-tagged arguments array length.
PushArgumentsArray(assembler);
PushArrayOfArguments(assembler);
const intptr_t kNumArgs = 4;
__ CallRuntime(kInvokeNoSuchMethodDispatcherRuntimeEntry, kNumArgs);
__ Drop(4);
@ -1205,7 +1205,7 @@ void StubCode::GenerateCallClosureNoSuchMethodStub(Assembler* assembler) {
__ Bind(&args_count_ok);
// R10: Smi-tagged arguments array length.
PushArgumentsArray(assembler);
PushArrayOfArguments(assembler);
const intptr_t kNumArgs = 3;
__ CallRuntime(kInvokeClosureNoSuchMethodRuntimeEntry, kNumArgs);