From 9329995dd8bd16cebde8826442a0a5632dfb5dbe Mon Sep 17 00:00:00 2001 From: Tess Strickland Date: Thu, 2 Jul 2020 13:16:19 +0000 Subject: [PATCH] [vm] Add a separate invoke field dispatcher for dynamic closure calls. Adds TODO comments in appropriate places for future work that will move non-covariant type checks out of the closure body. Instead, the VM will perform them in the invoke field dispatcher (or NoSuchMethodFromCallStub if --no-lazy-dispatchers is used) when a dynamic call is detected. This change has minimal negative effects on the code size. Here are the code size change percentages for the Flutter Gallery in release mode: * ARM7 * Instructions: +0.0391% * ROData: -0.0040% * Total: +0.0239% * ARM8: * Instructions: No change * ROData: +0.0015% * Total: +0.0004% All other code size benchmarks are also <0.01% increase. Bug: https://github.com/dart-lang/sdk/issues/40813 Change-Id: I4bf145803bb9e2d4ba5c22c12b6fd3bb5368441d Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try,vm-dartkb-linux-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151826 Commit-Queue: Tess Strickland Reviewed-by: Alexander Markov Reviewed-by: Martin Kustermann --- runtime/vm/compiler/aot/precompiler.cc | 20 ++- runtime/vm/compiler/aot/precompiler.h | 3 +- .../vm/compiler/frontend/bytecode_reader.cc | 8 +- .../frontend/kernel_binary_flowgraph.cc | 9 +- runtime/vm/compiler/frontend/kernel_to_il.cc | 23 ++- runtime/vm/dart_entry.cc | 162 +++++++++++------- runtime/vm/dart_entry.h | 17 ++ runtime/vm/interpreter.cc | 16 +- runtime/vm/runtime_entry.cc | 64 +++++-- runtime/vm/symbols.h | 1 + .../no_such_args_error_message_vm_test.dart | 12 +- .../no_such_args_error_message_vm_test.dart | 12 +- 12 files changed, 241 insertions(+), 106 deletions(-) diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc index ad2531456b1..cc40782a4e9 100644 --- a/runtime/vm/compiler/aot/precompiler.cc +++ b/runtime/vm/compiler/aot/precompiler.cc @@ -705,6 +705,11 @@ void Precompiler::AddCalleesOf(const Function& function, intptr_t gop_offset) { } } +static bool IsPotentialClosureCall(const String& selector) { + return selector.raw() == Symbols::Call().raw() || + selector.raw() == Symbols::DynamicCall().raw(); +} + void Precompiler::AddCalleesOfHelper(const Object& entry, String* temp_selector, Class* temp_cls) { @@ -713,22 +718,20 @@ void Precompiler::AddCalleesOfHelper(const Object& entry, // A dynamic call. *temp_selector = call_site.target_name(); AddSelector(*temp_selector); - if (temp_selector->raw() == Symbols::Call().raw()) { - // Potential closure call. + if (IsPotentialClosureCall(*temp_selector)) { const Array& arguments_descriptor = Array::Handle(Z, call_site.arguments_descriptor()); - AddClosureCall(arguments_descriptor); + AddClosureCall(*temp_selector, arguments_descriptor); } } else if (entry.IsMegamorphicCache()) { // A dynamic call. const auto& cache = MegamorphicCache::Cast(entry); *temp_selector = cache.target_name(); AddSelector(*temp_selector); - if (temp_selector->raw() == Symbols::Call().raw()) { - // Potential closure call. + if (IsPotentialClosureCall(*temp_selector)) { const Array& arguments_descriptor = Array::Handle(Z, cache.arguments_descriptor()); - AddClosureCall(arguments_descriptor); + AddClosureCall(*temp_selector, arguments_descriptor); } } else if (entry.IsField()) { // Potential need for field initializer. @@ -952,12 +955,13 @@ void Precompiler::AddConstObject(const class Instance& instance) { instance.raw()->ptr()->VisitPointers(&visitor); } -void Precompiler::AddClosureCall(const Array& arguments_descriptor) { +void Precompiler::AddClosureCall(const String& call_selector, + const Array& arguments_descriptor) { const Class& cache_class = Class::Handle(Z, I->object_store()->closure_class()); const Function& dispatcher = Function::Handle(Z, cache_class.GetInvocationDispatcher( - Symbols::Call(), arguments_descriptor, + call_selector, arguments_descriptor, FunctionLayout::kInvokeFieldDispatcher, true /* create_if_absent */)); AddFunction(dispatcher); diff --git a/runtime/vm/compiler/aot/precompiler.h b/runtime/vm/compiler/aot/precompiler.h index 86fcc461082..78ff5734364 100644 --- a/runtime/vm/compiler/aot/precompiler.h +++ b/runtime/vm/compiler/aot/precompiler.h @@ -269,7 +269,8 @@ class Precompiler : public ValueObject { String* temp_selector, Class* temp_cls); void AddConstObject(const class Instance& instance); - void AddClosureCall(const Array& arguments_descriptor); + void AddClosureCall(const String& selector, + const Array& arguments_descriptor); void AddFunction(const Function& function, bool retain = true); void AddInstantiatedClass(const Class& cls); void AddSelector(const String& selector); diff --git a/runtime/vm/compiler/frontend/bytecode_reader.cc b/runtime/vm/compiler/frontend/bytecode_reader.cc index 521120846c4..ba1f36c3cff 100644 --- a/runtime/vm/compiler/frontend/bytecode_reader.cc +++ b/runtime/vm/compiler/frontend/bytecode_reader.cc @@ -911,14 +911,12 @@ intptr_t BytecodeReaderHelper::ReadConstantPool(const Function& function, case ConstantPoolTag::kDynamicCall: { name ^= ReadObject(); ASSERT(name.IsSymbol()); - // Do not mangle == or call: + // Do not mangle ==: // * operator == takes an Object so it is either not checked or // checked at the entry because the parameter is marked covariant, - // neither of those cases require a dynamic invocation forwarder; - // * we assume that all closures are entered in a checked way. + // neither of those cases require a dynamic invocation forwarder if (!Field::IsGetterName(name) && - (name.raw() != Symbols::EqualOperator().raw()) && - (name.raw() != Symbols::Call().raw())) { + (name.raw() != Symbols::EqualOperator().raw())) { name = Function::CreateDynamicInvocationForwarderName(name); } // DynamicCall constant occupies 2 entries: selector and arguments diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index b68233d1eac..a7cbc05de69 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -2989,14 +2989,13 @@ Fragment StreamingFlowGraphBuilder::BuildMethodInvocation(TokenPosition* p) { } const String* mangled_name = &name; - // Do not mangle == or call: + // Do not mangle ==: // * operator == takes an Object so its either not checked or checked // at the entry because the parameter is marked covariant, neither of - // those cases require a dynamic invocation forwarder; - // * we assume that all closures are entered in a checked way. + // those cases require a dynamic invocation forwarder. const Function* direct_call_target = &direct_call.target_; - if ((name.raw() != Symbols::EqualOperator().raw()) && - (name.raw() != Symbols::Call().raw()) && H.IsRoot(itarget_name)) { + if (H.IsRoot(itarget_name) && + (name.raw() != Symbols::EqualOperator().raw())) { mangled_name = &String::ZoneHandle( Z, Function::CreateDynamicInvocationForwarderName(name)); if (!direct_call_target->IsNull()) { diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 00b748e9c22..4ecc8739b15 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -1933,7 +1933,15 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher( // Find the name of the field we should dispatch to. const Class& owner = Class::Handle(Z, function.Owner()); ASSERT(!owner.IsNull()); - const String& field_name = String::Handle(Z, function.name()); + auto& field_name = String::Handle(Z, function.name()); + // If the field name has a dyn: tag, then remove it. We don't add dynamic + // invocation forwarders for field getters used for invoking, we just use + // the tag in the name of the invoke field dispatcher to detect dynamic calls. + const bool is_dynamic_call = + Function::IsDynamicInvocationForwarderName(field_name); + if (is_dynamic_call) { + field_name = Function::DemangleDynamicInvocationForwarderName(field_name); + } const String& getter_name = String::ZoneHandle( Z, Symbols::New(thread_, String::Handle(Z, Field::GetterSymbol(field_name)))); @@ -1990,6 +1998,13 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher( // The closure itself is the first argument. body += LoadLocal(closure); + + if (is_dynamic_call) { + // TODO(dartbug.com/40813): Move checks that are currently compiled + // in the closure body to here, using the dynamic versions of + // AssertSubtype to typecheck the type arguments using the runtime types + // available in the closure object. + } } else { // Invoke the getter to get the field value. body += LoadLocal(parsed_function_->ParameterVariable(0)); @@ -2003,6 +2018,12 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher( intptr_t pos = 1; for (; pos < descriptor.Count(); pos++) { body += LoadLocal(parsed_function_->ParameterVariable(pos)); + if (is_closure_call && is_dynamic_call) { + // TODO(dartbug.com/40813): Move checks that are currently compiled + // in the closure body to here, using the dynamic versions of + // AssertAssignable to typecheck the parameters using the runtime types + // available in the closure object. + } } if (is_closure_call) { diff --git a/runtime/vm/dart_entry.cc b/runtime/vm/dart_entry.cc index 8a1350e6490..3a82369294a 100644 --- a/runtime/vm/dart_entry.cc +++ b/runtime/vm/dart_entry.cc @@ -206,6 +206,101 @@ ObjectPtr DartEntry::InvokeCode(const Code& code, #endif } +ObjectPtr DartEntry::ResolveCallable(const Array& arguments, + const Array& arguments_descriptor) { + auto thread = Thread::Current(); + auto isolate = thread->isolate(); + auto zone = thread->zone(); + + const ArgumentsDescriptor args_desc(arguments_descriptor); + const intptr_t receiver_index = args_desc.FirstArgIndex(); + const intptr_t type_args_len = args_desc.TypeArgsLen(); + const intptr_t args_count = args_desc.Count(); + const intptr_t named_args_count = args_desc.NamedCount(); + const auto& getter_name = Symbols::GetCall(); + + auto& instance = Instance::Handle(zone); + auto& function = Function::Handle(zone); + auto& cls = Class::Handle(zone); + + // The null instance cannot resolve to a callable, so we can stop there. + for (instance ^= arguments.At(receiver_index); !instance.IsNull(); + instance ^= arguments.At(receiver_index)) { + // If the current instance is a compatible callable, return its function. + if (instance.IsCallable(&function) && + function.AreValidArgumentCounts(type_args_len, args_count, + named_args_count, nullptr)) { + return function.raw(); + } + + // Special case: closures are implemented with a call getter instead of a + // call method, so checking for a call getter would cause an infinite loop. + if (instance.IsClosure()) { + break; + } + + // Find a call getter, if any, in the class hierarchy. + for (cls = instance.clazz(); !cls.IsNull(); cls = cls.SuperClass()) { + function = cls.LookupDynamicFunction(getter_name); + if (function.IsNull()) { + continue; + } + + if (!OSThread::Current()->HasStackHeadroom()) { + const Instance& exception = + Instance::Handle(zone, isolate->object_store()->stack_overflow()); + return UnhandledException::New(exception, StackTrace::Handle(zone)); + } + + const Array& getter_arguments = Array::Handle(zone, Array::New(1)); + getter_arguments.SetAt(0, instance); + const Object& getter_result = Object::Handle( + zone, DartEntry::InvokeFunction(function, getter_arguments)); + if (getter_result.IsError()) { + return getter_result.raw(); + } + ASSERT(getter_result.IsNull() || getter_result.IsInstance()); + + // We have a new possibly compatible callable, so set the first argument + // accordingly so it gets picked up in the main loop. + arguments.SetAt(receiver_index, getter_result); + break; + } + + // No call getter was found in the hierarchy, so stop the search. + if (cls.IsNull()) { + break; + } + } + + // No compatible callable was found. + return Function::null(); +} + +ObjectPtr DartEntry::InvokeCallable(const Function& callable_function, + const Array& arguments, + const Array& arguments_descriptor) { + if (!callable_function.IsNull()) { + return InvokeFunction(callable_function, arguments, arguments_descriptor); + } + + // No compatible callable was found, so invoke noSuchMethod. + Thread* thread = Thread::Current(); + Zone* zone = thread->zone(); + const ArgumentsDescriptor args_desc(arguments_descriptor); + auto& instance = + Instance::CheckedHandle(zone, arguments.At(args_desc.FirstArgIndex())); + auto& target_name = String::Handle(zone, Symbols::Call().raw()); + if (instance.IsClosure()) { + const auto& closure = Closure::Cast(instance); + // For closures, use the name of the closure, not 'call'. + const auto& function = Function::Handle(zone, closure.function()); + target_name = function.QualifiedUserVisibleName(); + } + return InvokeNoSuchMethod(instance, target_name, arguments, + arguments_descriptor); +} + ObjectPtr DartEntry::InvokeClosure(const Array& arguments) { const int kTypeArgsLen = 0; // No support to pass type args to generic func. @@ -217,68 +312,15 @@ ObjectPtr DartEntry::InvokeClosure(const Array& arguments) { ObjectPtr DartEntry::InvokeClosure(const Array& arguments, const Array& arguments_descriptor) { - Thread* thread = Thread::Current(); - Zone* zone = thread->zone(); - const ArgumentsDescriptor args_desc(arguments_descriptor); - Instance& instance = Instance::Handle(zone); - instance ^= arguments.At(args_desc.FirstArgIndex()); - // Get the entrypoint corresponding to the closure function or to the call - // method of the instance. This will result in a compilation of the function - // if it is not already compiled. - Function& function = Function::Handle(zone); - if (instance.IsCallable(&function)) { - // Only invoke the function if its arguments are compatible. - if (function.AreValidArgumentCounts(args_desc.TypeArgsLen(), - args_desc.Count(), - args_desc.NamedCount(), NULL)) { - // The closure or non-closure object (receiver) is passed as implicit - // first argument. It is already included in the arguments array. - return InvokeFunction(function, arguments, arguments_descriptor); - } + const Object& resolved_result = + Object::Handle(ResolveCallable(arguments, arguments_descriptor)); + if (resolved_result.IsError()) { + return resolved_result.raw(); } - // There is no compatible 'call' method, see if there's a getter. - if (instance.IsClosure()) { - // Special case: closures are implemented with a call getter instead of a - // call method. If the arguments didn't match, go to noSuchMethod instead - // of infinitely recursing on the getter. - } else { - const String& getter_name = Symbols::GetCall(); - Class& cls = Class::Handle(zone, instance.clazz()); - while (!cls.IsNull()) { - function = cls.LookupDynamicFunction(getter_name); - if (!function.IsNull()) { - Isolate* isolate = thread->isolate(); - if (!OSThread::Current()->HasStackHeadroom()) { - const Instance& exception = - Instance::Handle(zone, isolate->object_store()->stack_overflow()); - return UnhandledException::New(exception, StackTrace::Handle(zone)); - } - - const Array& getter_arguments = Array::Handle(zone, Array::New(1)); - getter_arguments.SetAt(0, instance); - const Object& getter_result = Object::Handle( - zone, DartEntry::InvokeFunction(function, getter_arguments)); - if (getter_result.IsError()) { - return getter_result.raw(); - } - ASSERT(getter_result.IsNull() || getter_result.IsInstance()); - - arguments.SetAt(0, getter_result); - // This otherwise unnecessary handle is used to prevent clang from - // doing tail call elimination, which would make the stack overflow - // check above ineffective. - Object& result = Object::Handle( - zone, InvokeClosure(arguments, arguments_descriptor)); - return result.raw(); - } - cls = cls.SuperClass(); - } - } - - // No compatible method or getter so invoke noSuchMethod. - return InvokeNoSuchMethod(instance, Symbols::Call(), arguments, - arguments_descriptor); + const auto& function = + Function::Handle(Function::RawCast(resolved_result.raw())); + return InvokeCallable(function, arguments, arguments_descriptor); } ObjectPtr DartEntry::InvokeNoSuchMethod(const Instance& receiver, diff --git a/runtime/vm/dart_entry.h b/runtime/vm/dart_entry.h index 9ab0115b128..f635cb29957 100644 --- a/runtime/vm/dart_entry.h +++ b/runtime/vm/dart_entry.h @@ -206,6 +206,23 @@ class DartEntry : public AllStatic { const Array& arguments_descriptor, uword current_sp = OSThread::GetCurrentStackPointer()); + // Resolves the first argument to a callable compatible with the arguments. + // + // If no errors occur, the first argument is changed to be either the resolved + // callable or, if Function::null() is returned, an appropriate target for + // invoking noSuchMethod. + // + // On success, returns a RawFunction. On failure, a RawError. + static ObjectPtr ResolveCallable(const Array& arguments, + const Array& arguments_descriptor); + + // Invokes the function returned by ResolveCallable. + // + // On success, returns a RawInstance. On failure, a RawError. + static ObjectPtr InvokeCallable(const Function& callable_function, + const Array& arguments, + const Array& arguments_descriptor); + // Invokes the closure object given as the first argument. // On success, returns a RawInstance. On failure, a RawError. // This is used when there is no type argument vector and diff --git a/runtime/vm/interpreter.cc b/runtime/vm/interpreter.cc index a48d019cbf5..d296e6ecdbb 100644 --- a/runtime/vm/interpreter.cc +++ b/runtime/vm/interpreter.cc @@ -3431,29 +3431,39 @@ SwitchDispatch: const intptr_t receiver_idx = type_args_len > 0 ? 1 : 0; const intptr_t argc = InterpreterHelpers::ArgDescArgCount(argdesc_) + receiver_idx; - ObjectPtr receiver = FrameArguments(FP, argc)[receiver_idx]; - // Invoke field getter on receiver. + // Possibly demangle field name and invoke field getter on receiver. { SP[1] = argdesc_; // Save argdesc_. SP[2] = 0; // Result of runtime call. SP[3] = receiver; // Receiver. - SP[4] = function->ptr()->name_; // Field name. + SP[4] = function->ptr()->name_; // Field name (may change during call). Exit(thread, FP, SP + 5, pc); if (!InvokeRuntime(thread, this, DRT_GetFieldForDispatch, NativeArguments(thread, 2, SP + 3, SP + 2))) { HANDLE_EXCEPTION; } + function = FrameFunction(FP); argdesc_ = Array::RawCast(SP[1]); } + // If the field name in the arguments is different after the call, then + // this was a dynamic call. + StringPtr field_name = String::RawCast(SP[4]); + const bool is_dynamic_call = function->ptr()->name_ != field_name; + // Replace receiver with field value, keep all other arguments, and // invoke 'call' function, or if not found, invoke noSuchMethod. FrameArguments(FP, argc)[receiver_idx] = receiver = SP[2]; // If the field value is a closure, no need to resolve 'call' function. if (InterpreterHelpers::GetClassId(receiver) == kClosureCid) { + if (is_dynamic_call) { + // TODO(dartbug.com/40813): Move checks that are currently compiled + // in the closure body to here as they are also moved to + // FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher. + } SP[1] = Closure::RawCast(receiver)->ptr()->function_; goto TailCallSP1; } diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc index f611487dc1e..98813d153e2 100644 --- a/runtime/vm/runtime_entry.cc +++ b/runtime/vm/runtime_entry.cc @@ -511,13 +511,17 @@ static void ThrowIfError(const Object& result) { // Invoke field getter before dispatch. // Arg0: instance. -// Arg1: field name. +// Arg1: field name (may be demangled during call). // Return value: field value. DEFINE_RUNTIME_ENTRY(GetFieldForDispatch, 2) { ASSERT(FLAG_enable_interpreter); const Instance& receiver = Instance::CheckedHandle(zone, arguments.ArgAt(0)); - const String& name = String::CheckedHandle(zone, arguments.ArgAt(1)); + String& name = String::CheckedHandle(zone, arguments.ArgAt(1)); const Class& receiver_class = Class::Handle(zone, receiver.clazz()); + if (Function::IsDynamicInvocationForwarderName(name)) { + name = Function::DemangleDynamicInvocationForwarderName(name); + arguments.SetArgAt(1, name); // Reflect change in arguments. + } const String& getter_name = String::Handle(zone, Field::GetterName(name)); const int kTypeArgsLen = 0; const int kNumArguments = 1; @@ -1086,10 +1090,10 @@ DEFINE_RUNTIME_ENTRY(SingleStepHandler, 0) { // non-closure, attempt to invoke "call" on it. static bool ResolveCallThroughGetter(const Class& receiver_class, const String& target_name, + const String& demangled, const Array& arguments_descriptor, Function* result) { - // 1. Check if there is a getter with the same name. - const String& getter_name = String::Handle(Field::GetterName(target_name)); + const String& getter_name = String::Handle(Field::GetterName(demangled)); const int kTypeArgsLen = 0; const int kNumArguments = 1; ArgumentsDescriptor args_desc(Array::Handle( @@ -1100,6 +1104,9 @@ static bool ResolveCallThroughGetter(const Class& receiver_class, if (getter.IsNull() || getter.IsMethodExtractor()) { return false; } + // We do this on the target_name, _not_ on the demangled name, so that + // FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher can detect dynamic + // calls from the dyn: tag on the name of the dispatcher. const Function& target_function = Function::Handle(receiver_class.GetInvocationDispatcher( target_name, arguments_descriptor, @@ -1119,21 +1126,21 @@ static bool ResolveCallThroughGetter(const Class& receiver_class, FunctionPtr InlineCacheMissHelper(const Class& receiver_class, const Array& args_descriptor, const String& target_name) { - // Handle noSuchMethod for dyn:methodName by getting a noSuchMethod dispatcher - // (or a call-through getter for methodName). + // Create a demangled version of the target_name, if necessary, This is used + // for the field getter in ResolveCallThroughGetter and as the target name + // for the NoSuchMethod dispatcher (if needed). + const String* demangled = &target_name; if (Function::IsDynamicInvocationForwarderName(target_name)) { - const String& demangled = String::Handle( + demangled = &String::Handle( Function::DemangleDynamicInvocationForwarderName(target_name)); - return InlineCacheMissHelper(receiver_class, args_descriptor, demangled); } - Function& result = Function::Handle(); - if (!ResolveCallThroughGetter(receiver_class, target_name, args_descriptor, - &result)) { + if (!ResolveCallThroughGetter(receiver_class, target_name, *demangled, + args_descriptor, &result)) { ArgumentsDescriptor desc(args_descriptor); const Function& target_function = Function::Handle(receiver_class.GetInvocationDispatcher( - target_name, args_descriptor, + *demangled, args_descriptor, FunctionLayout::kNoSuchMethodDispatcher, FLAG_lazy_dispatchers)); if (FLAG_trace_ic) { OS::PrintErr( @@ -2170,7 +2177,9 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromCallStub, 4) { target_name = MegamorphicCache::Cast(ic_data_or_cache).target_name(); } - if (Function::IsDynamicInvocationForwarderName(target_name)) { + const bool is_dynamic_call = + Function::IsDynamicInvocationForwarderName(target_name); + if (is_dynamic_call) { target_name = Function::DemangleDynamicInvocationForwarderName(target_name); } @@ -2219,8 +2228,19 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromCallStub, 4) { // Special case: closures are implemented with a call getter instead of a // call method and with lazy dispatchers the field-invocation-dispatcher // would perform the closure call. - const Object& result = Object::Handle( - zone, DartEntry::InvokeClosure(orig_arguments, orig_arguments_desc)); + auto& result = Object::Handle( + zone, + DartEntry::ResolveCallable(orig_arguments, orig_arguments_desc)); + ThrowIfError(result); + const Function& callable_function = + Function::Handle(zone, Function::RawCast(result.raw())); + if (is_dynamic_call && !callable_function.IsNull()) { + // TODO(dartbug.com/40813): Move checks that are currently compiled + // in the closure body to here as they are also moved to + // FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher. + } + result = DartEntry::InvokeCallable(callable_function, orig_arguments, + orig_arguments_desc); ThrowIfError(result); arguments.SetReturn(result); return; @@ -2245,9 +2265,19 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromCallStub, 4) { ASSERT(getter_result.IsNull() || getter_result.IsInstance()); orig_arguments.SetAt(args_desc.FirstArgIndex(), getter_result); - const Object& call_result = Object::Handle( + auto& call_result = Object::Handle( zone, - DartEntry::InvokeClosure(orig_arguments, orig_arguments_desc)); + DartEntry::ResolveCallable(orig_arguments, orig_arguments_desc)); + ThrowIfError(call_result); + const Function& callable_function = + Function::Handle(zone, Function::RawCast(call_result.raw())); + if (is_dynamic_call && !callable_function.IsNull()) { + // TODO(dartbug.com/40813): Move checks that are currently compiled + // in the closure body to here as they are also moved to + // FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher. + } + call_result = DartEntry::InvokeCallable( + callable_function, orig_arguments, orig_arguments_desc); ThrowIfError(call_result); arguments.SetReturn(call_result); return; diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 8598430cc26..8924ddc40d9 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -107,6 +107,7 @@ class ObjectPointerVisitor; V(DotWithType, "._withType") \ V(Double, "double") \ V(Dynamic, "dynamic") \ + V(DynamicCall, "dyn:call") \ V(DynamicPrefix, "dyn:") \ V(EntryPointsTemp, ":entry_points_temp") \ V(EqualOperator, "==") \ diff --git a/tests/language/vm/no_such_args_error_message_vm_test.dart b/tests/language/vm/no_such_args_error_message_vm_test.dart index 3402472fae4..d3ca5100692 100644 --- a/tests/language/vm/no_such_args_error_message_vm_test.dart +++ b/tests/language/vm/no_such_args_error_message_vm_test.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // VMOptions=--optimization-counter-threshold=10 --no-use-osr --no-background-compilation +// VMOptions=--optimization-counter-threshold=10 --no-use-osr --no-background-compilation --no-lazy-dispatchers import "package:expect/expect.dart"; @@ -14,8 +15,10 @@ testClosureMessage() { try { call_with_bar(() {}); } catch (e) { - Expect.isTrue(e.toString().contains( - "Tried calling: testClosureMessage.(\"bar\")")); + final expectedStrings = [ + 'Tried calling: testClosureMessage.("bar")', + ]; + Expect.stringContainsInOrder(e.toString(), expectedStrings); } } @@ -25,7 +28,10 @@ testFunctionMessage() { try { call_with_bar(noargs); } catch (e) { - Expect.isTrue(e.toString().contains("Tried calling: noargs(\"bar\")")); + final expectedStrings = [ + 'Tried calling: noargs("bar")', + ]; + Expect.stringContainsInOrder(e.toString(), expectedStrings); } } diff --git a/tests/language_2/vm/no_such_args_error_message_vm_test.dart b/tests/language_2/vm/no_such_args_error_message_vm_test.dart index 3402472fae4..d3ca5100692 100644 --- a/tests/language_2/vm/no_such_args_error_message_vm_test.dart +++ b/tests/language_2/vm/no_such_args_error_message_vm_test.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // VMOptions=--optimization-counter-threshold=10 --no-use-osr --no-background-compilation +// VMOptions=--optimization-counter-threshold=10 --no-use-osr --no-background-compilation --no-lazy-dispatchers import "package:expect/expect.dart"; @@ -14,8 +15,10 @@ testClosureMessage() { try { call_with_bar(() {}); } catch (e) { - Expect.isTrue(e.toString().contains( - "Tried calling: testClosureMessage.(\"bar\")")); + final expectedStrings = [ + 'Tried calling: testClosureMessage.("bar")', + ]; + Expect.stringContainsInOrder(e.toString(), expectedStrings); } } @@ -25,7 +28,10 @@ testFunctionMessage() { try { call_with_bar(noargs); } catch (e) { - Expect.isTrue(e.toString().contains("Tried calling: noargs(\"bar\")")); + final expectedStrings = [ + 'Tried calling: noargs("bar")', + ]; + Expect.stringContainsInOrder(e.toString(), expectedStrings); } }