diff --git a/runtime/vm/compiler/aot/aot_call_specializer.cc b/runtime/vm/compiler/aot/aot_call_specializer.cc index 29f986ad0a9..2db84f6b456 100644 --- a/runtime/vm/compiler/aot/aot_call_specializer.cc +++ b/runtime/vm/compiler/aot/aot_call_specializer.cc @@ -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; diff --git a/runtime/vm/compiler/aot/aot_call_specializer.h b/runtime/vm/compiler/aot/aot_call_specializer.h index e9de84c85bf..aba476603b6 100644 --- a/runtime/vm/compiler/aot/aot_call_specializer.h +++ b/runtime/vm/compiler/aot/aot_call_specializer.h @@ -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_; diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h index ce537e95f4d..4a72d274d33 100644 --- a/runtime/vm/compiler/backend/il.h +++ b/runtime/vm/compiler/backend/il.h @@ -2954,13 +2954,15 @@ struct ArgumentsInfo { const Array& argument_names; }; +typedef ZoneGrowableArray PushArgumentsArray; + template class TemplateDartCall : public TemplateDefinition { public: TemplateDartCall(intptr_t deopt_id, intptr_t type_args_len, const Array& argument_names, - ZoneGrowableArray* 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 { private: intptr_t type_args_len_; const Array& argument_names_; - ZoneGrowableArray* 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* 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* 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* 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* arguments, + PushArgumentsArray* arguments, const ZoneGrowableArray& 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* 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* args = - new (zone) ZoneGrowableArray(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* 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* 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* const arguments_; + PushArgumentsArray* const arguments_; AliasIdentity identity_; Function& closure_function_; diff --git a/runtime/vm/stub_code_arm.cc b/runtime/vm/stub_code_arm.cc index 8e38412fcda..4ded3092581 100644 --- a/runtime/vm/stub_code_arm.cc +++ b/runtime/vm/stub_code_arm.cc @@ -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); diff --git a/runtime/vm/stub_code_arm64.cc b/runtime/vm/stub_code_arm64.cc index 918a1a997d1..a071130e20a 100644 --- a/runtime/vm/stub_code_arm64.cc +++ b/runtime/vm/stub_code_arm64.cc @@ -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); diff --git a/runtime/vm/stub_code_ia32.cc b/runtime/vm/stub_code_ia32.cc index 14ac31a7993..2707fc7cc4c 100644 --- a/runtime/vm/stub_code_ia32.cc +++ b/runtime/vm/stub_code_ia32.cc @@ -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(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); diff --git a/runtime/vm/stub_code_x64.cc b/runtime/vm/stub_code_x64.cc index 879603e053d..0363b27574b 100644 --- a/runtime/vm/stub_code_x64.cc +++ b/runtime/vm/stub_code_x64.cc @@ -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);