Skip to content

Commit 9329995

Browse files
sstricklcommit-bot@chromium.org
authored andcommitted
[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: #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 <[email protected]> Reviewed-by: Alexander Markov <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent ca29064 commit 9329995

12 files changed

+241
-106
lines changed

runtime/vm/compiler/aot/precompiler.cc

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,11 @@ void Precompiler::AddCalleesOf(const Function& function, intptr_t gop_offset) {
705705
}
706706
}
707707

708+
static bool IsPotentialClosureCall(const String& selector) {
709+
return selector.raw() == Symbols::Call().raw() ||
710+
selector.raw() == Symbols::DynamicCall().raw();
711+
}
712+
708713
void Precompiler::AddCalleesOfHelper(const Object& entry,
709714
String* temp_selector,
710715
Class* temp_cls) {
@@ -713,22 +718,20 @@ void Precompiler::AddCalleesOfHelper(const Object& entry,
713718
// A dynamic call.
714719
*temp_selector = call_site.target_name();
715720
AddSelector(*temp_selector);
716-
if (temp_selector->raw() == Symbols::Call().raw()) {
717-
// Potential closure call.
721+
if (IsPotentialClosureCall(*temp_selector)) {
718722
const Array& arguments_descriptor =
719723
Array::Handle(Z, call_site.arguments_descriptor());
720-
AddClosureCall(arguments_descriptor);
724+
AddClosureCall(*temp_selector, arguments_descriptor);
721725
}
722726
} else if (entry.IsMegamorphicCache()) {
723727
// A dynamic call.
724728
const auto& cache = MegamorphicCache::Cast(entry);
725729
*temp_selector = cache.target_name();
726730
AddSelector(*temp_selector);
727-
if (temp_selector->raw() == Symbols::Call().raw()) {
728-
// Potential closure call.
731+
if (IsPotentialClosureCall(*temp_selector)) {
729732
const Array& arguments_descriptor =
730733
Array::Handle(Z, cache.arguments_descriptor());
731-
AddClosureCall(arguments_descriptor);
734+
AddClosureCall(*temp_selector, arguments_descriptor);
732735
}
733736
} else if (entry.IsField()) {
734737
// Potential need for field initializer.
@@ -952,12 +955,13 @@ void Precompiler::AddConstObject(const class Instance& instance) {
952955
instance.raw()->ptr()->VisitPointers(&visitor);
953956
}
954957

955-
void Precompiler::AddClosureCall(const Array& arguments_descriptor) {
958+
void Precompiler::AddClosureCall(const String& call_selector,
959+
const Array& arguments_descriptor) {
956960
const Class& cache_class =
957961
Class::Handle(Z, I->object_store()->closure_class());
958962
const Function& dispatcher =
959963
Function::Handle(Z, cache_class.GetInvocationDispatcher(
960-
Symbols::Call(), arguments_descriptor,
964+
call_selector, arguments_descriptor,
961965
FunctionLayout::kInvokeFieldDispatcher,
962966
true /* create_if_absent */));
963967
AddFunction(dispatcher);

runtime/vm/compiler/aot/precompiler.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ class Precompiler : public ValueObject {
269269
String* temp_selector,
270270
Class* temp_cls);
271271
void AddConstObject(const class Instance& instance);
272-
void AddClosureCall(const Array& arguments_descriptor);
272+
void AddClosureCall(const String& selector,
273+
const Array& arguments_descriptor);
273274
void AddFunction(const Function& function, bool retain = true);
274275
void AddInstantiatedClass(const Class& cls);
275276
void AddSelector(const String& selector);

runtime/vm/compiler/frontend/bytecode_reader.cc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -911,14 +911,12 @@ intptr_t BytecodeReaderHelper::ReadConstantPool(const Function& function,
911911
case ConstantPoolTag::kDynamicCall: {
912912
name ^= ReadObject();
913913
ASSERT(name.IsSymbol());
914-
// Do not mangle == or call:
914+
// Do not mangle ==:
915915
// * operator == takes an Object so it is either not checked or
916916
// checked at the entry because the parameter is marked covariant,
917-
// neither of those cases require a dynamic invocation forwarder;
918-
// * we assume that all closures are entered in a checked way.
917+
// neither of those cases require a dynamic invocation forwarder
919918
if (!Field::IsGetterName(name) &&
920-
(name.raw() != Symbols::EqualOperator().raw()) &&
921-
(name.raw() != Symbols::Call().raw())) {
919+
(name.raw() != Symbols::EqualOperator().raw())) {
922920
name = Function::CreateDynamicInvocationForwarderName(name);
923921
}
924922
// DynamicCall constant occupies 2 entries: selector and arguments

runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2989,14 +2989,13 @@ Fragment StreamingFlowGraphBuilder::BuildMethodInvocation(TokenPosition* p) {
29892989
}
29902990

29912991
const String* mangled_name = &name;
2992-
// Do not mangle == or call:
2992+
// Do not mangle ==:
29932993
// * operator == takes an Object so its either not checked or checked
29942994
// at the entry because the parameter is marked covariant, neither of
2995-
// those cases require a dynamic invocation forwarder;
2996-
// * we assume that all closures are entered in a checked way.
2995+
// those cases require a dynamic invocation forwarder.
29972996
const Function* direct_call_target = &direct_call.target_;
2998-
if ((name.raw() != Symbols::EqualOperator().raw()) &&
2999-
(name.raw() != Symbols::Call().raw()) && H.IsRoot(itarget_name)) {
2997+
if (H.IsRoot(itarget_name) &&
2998+
(name.raw() != Symbols::EqualOperator().raw())) {
30002999
mangled_name = &String::ZoneHandle(
30013000
Z, Function::CreateDynamicInvocationForwarderName(name));
30023001
if (!direct_call_target->IsNull()) {

runtime/vm/compiler/frontend/kernel_to_il.cc

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1933,7 +1933,15 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
19331933
// Find the name of the field we should dispatch to.
19341934
const Class& owner = Class::Handle(Z, function.Owner());
19351935
ASSERT(!owner.IsNull());
1936-
const String& field_name = String::Handle(Z, function.name());
1936+
auto& field_name = String::Handle(Z, function.name());
1937+
// If the field name has a dyn: tag, then remove it. We don't add dynamic
1938+
// invocation forwarders for field getters used for invoking, we just use
1939+
// the tag in the name of the invoke field dispatcher to detect dynamic calls.
1940+
const bool is_dynamic_call =
1941+
Function::IsDynamicInvocationForwarderName(field_name);
1942+
if (is_dynamic_call) {
1943+
field_name = Function::DemangleDynamicInvocationForwarderName(field_name);
1944+
}
19371945
const String& getter_name = String::ZoneHandle(
19381946
Z, Symbols::New(thread_,
19391947
String::Handle(Z, Field::GetterSymbol(field_name))));
@@ -1990,6 +1998,13 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
19901998

19911999
// The closure itself is the first argument.
19922000
body += LoadLocal(closure);
2001+
2002+
if (is_dynamic_call) {
2003+
// TODO(dartbug.com/40813): Move checks that are currently compiled
2004+
// in the closure body to here, using the dynamic versions of
2005+
// AssertSubtype to typecheck the type arguments using the runtime types
2006+
// available in the closure object.
2007+
}
19932008
} else {
19942009
// Invoke the getter to get the field value.
19952010
body += LoadLocal(parsed_function_->ParameterVariable(0));
@@ -2003,6 +2018,12 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
20032018
intptr_t pos = 1;
20042019
for (; pos < descriptor.Count(); pos++) {
20052020
body += LoadLocal(parsed_function_->ParameterVariable(pos));
2021+
if (is_closure_call && is_dynamic_call) {
2022+
// TODO(dartbug.com/40813): Move checks that are currently compiled
2023+
// in the closure body to here, using the dynamic versions of
2024+
// AssertAssignable to typecheck the parameters using the runtime types
2025+
// available in the closure object.
2026+
}
20062027
}
20072028

20082029
if (is_closure_call) {

runtime/vm/dart_entry.cc

Lines changed: 102 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,101 @@ ObjectPtr DartEntry::InvokeCode(const Code& code,
206206
#endif
207207
}
208208

209+
ObjectPtr DartEntry::ResolveCallable(const Array& arguments,
210+
const Array& arguments_descriptor) {
211+
auto thread = Thread::Current();
212+
auto isolate = thread->isolate();
213+
auto zone = thread->zone();
214+
215+
const ArgumentsDescriptor args_desc(arguments_descriptor);
216+
const intptr_t receiver_index = args_desc.FirstArgIndex();
217+
const intptr_t type_args_len = args_desc.TypeArgsLen();
218+
const intptr_t args_count = args_desc.Count();
219+
const intptr_t named_args_count = args_desc.NamedCount();
220+
const auto& getter_name = Symbols::GetCall();
221+
222+
auto& instance = Instance::Handle(zone);
223+
auto& function = Function::Handle(zone);
224+
auto& cls = Class::Handle(zone);
225+
226+
// The null instance cannot resolve to a callable, so we can stop there.
227+
for (instance ^= arguments.At(receiver_index); !instance.IsNull();
228+
instance ^= arguments.At(receiver_index)) {
229+
// If the current instance is a compatible callable, return its function.
230+
if (instance.IsCallable(&function) &&
231+
function.AreValidArgumentCounts(type_args_len, args_count,
232+
named_args_count, nullptr)) {
233+
return function.raw();
234+
}
235+
236+
// Special case: closures are implemented with a call getter instead of a
237+
// call method, so checking for a call getter would cause an infinite loop.
238+
if (instance.IsClosure()) {
239+
break;
240+
}
241+
242+
// Find a call getter, if any, in the class hierarchy.
243+
for (cls = instance.clazz(); !cls.IsNull(); cls = cls.SuperClass()) {
244+
function = cls.LookupDynamicFunction(getter_name);
245+
if (function.IsNull()) {
246+
continue;
247+
}
248+
249+
if (!OSThread::Current()->HasStackHeadroom()) {
250+
const Instance& exception =
251+
Instance::Handle(zone, isolate->object_store()->stack_overflow());
252+
return UnhandledException::New(exception, StackTrace::Handle(zone));
253+
}
254+
255+
const Array& getter_arguments = Array::Handle(zone, Array::New(1));
256+
getter_arguments.SetAt(0, instance);
257+
const Object& getter_result = Object::Handle(
258+
zone, DartEntry::InvokeFunction(function, getter_arguments));
259+
if (getter_result.IsError()) {
260+
return getter_result.raw();
261+
}
262+
ASSERT(getter_result.IsNull() || getter_result.IsInstance());
263+
264+
// We have a new possibly compatible callable, so set the first argument
265+
// accordingly so it gets picked up in the main loop.
266+
arguments.SetAt(receiver_index, getter_result);
267+
break;
268+
}
269+
270+
// No call getter was found in the hierarchy, so stop the search.
271+
if (cls.IsNull()) {
272+
break;
273+
}
274+
}
275+
276+
// No compatible callable was found.
277+
return Function::null();
278+
}
279+
280+
ObjectPtr DartEntry::InvokeCallable(const Function& callable_function,
281+
const Array& arguments,
282+
const Array& arguments_descriptor) {
283+
if (!callable_function.IsNull()) {
284+
return InvokeFunction(callable_function, arguments, arguments_descriptor);
285+
}
286+
287+
// No compatible callable was found, so invoke noSuchMethod.
288+
Thread* thread = Thread::Current();
289+
Zone* zone = thread->zone();
290+
const ArgumentsDescriptor args_desc(arguments_descriptor);
291+
auto& instance =
292+
Instance::CheckedHandle(zone, arguments.At(args_desc.FirstArgIndex()));
293+
auto& target_name = String::Handle(zone, Symbols::Call().raw());
294+
if (instance.IsClosure()) {
295+
const auto& closure = Closure::Cast(instance);
296+
// For closures, use the name of the closure, not 'call'.
297+
const auto& function = Function::Handle(zone, closure.function());
298+
target_name = function.QualifiedUserVisibleName();
299+
}
300+
return InvokeNoSuchMethod(instance, target_name, arguments,
301+
arguments_descriptor);
302+
}
303+
209304
ObjectPtr DartEntry::InvokeClosure(const Array& arguments) {
210305
const int kTypeArgsLen = 0; // No support to pass type args to generic func.
211306

@@ -217,68 +312,15 @@ ObjectPtr DartEntry::InvokeClosure(const Array& arguments) {
217312

218313
ObjectPtr DartEntry::InvokeClosure(const Array& arguments,
219314
const Array& arguments_descriptor) {
220-
Thread* thread = Thread::Current();
221-
Zone* zone = thread->zone();
222-
const ArgumentsDescriptor args_desc(arguments_descriptor);
223-
Instance& instance = Instance::Handle(zone);
224-
instance ^= arguments.At(args_desc.FirstArgIndex());
225-
// Get the entrypoint corresponding to the closure function or to the call
226-
// method of the instance. This will result in a compilation of the function
227-
// if it is not already compiled.
228-
Function& function = Function::Handle(zone);
229-
if (instance.IsCallable(&function)) {
230-
// Only invoke the function if its arguments are compatible.
231-
if (function.AreValidArgumentCounts(args_desc.TypeArgsLen(),
232-
args_desc.Count(),
233-
args_desc.NamedCount(), NULL)) {
234-
// The closure or non-closure object (receiver) is passed as implicit
235-
// first argument. It is already included in the arguments array.
236-
return InvokeFunction(function, arguments, arguments_descriptor);
237-
}
315+
const Object& resolved_result =
316+
Object::Handle(ResolveCallable(arguments, arguments_descriptor));
317+
if (resolved_result.IsError()) {
318+
return resolved_result.raw();
238319
}
239320

240-
// There is no compatible 'call' method, see if there's a getter.
241-
if (instance.IsClosure()) {
242-
// Special case: closures are implemented with a call getter instead of a
243-
// call method. If the arguments didn't match, go to noSuchMethod instead
244-
// of infinitely recursing on the getter.
245-
} else {
246-
const String& getter_name = Symbols::GetCall();
247-
Class& cls = Class::Handle(zone, instance.clazz());
248-
while (!cls.IsNull()) {
249-
function = cls.LookupDynamicFunction(getter_name);
250-
if (!function.IsNull()) {
251-
Isolate* isolate = thread->isolate();
252-
if (!OSThread::Current()->HasStackHeadroom()) {
253-
const Instance& exception =
254-
Instance::Handle(zone, isolate->object_store()->stack_overflow());
255-
return UnhandledException::New(exception, StackTrace::Handle(zone));
256-
}
257-
258-
const Array& getter_arguments = Array::Handle(zone, Array::New(1));
259-
getter_arguments.SetAt(0, instance);
260-
const Object& getter_result = Object::Handle(
261-
zone, DartEntry::InvokeFunction(function, getter_arguments));
262-
if (getter_result.IsError()) {
263-
return getter_result.raw();
264-
}
265-
ASSERT(getter_result.IsNull() || getter_result.IsInstance());
266-
267-
arguments.SetAt(0, getter_result);
268-
// This otherwise unnecessary handle is used to prevent clang from
269-
// doing tail call elimination, which would make the stack overflow
270-
// check above ineffective.
271-
Object& result = Object::Handle(
272-
zone, InvokeClosure(arguments, arguments_descriptor));
273-
return result.raw();
274-
}
275-
cls = cls.SuperClass();
276-
}
277-
}
278-
279-
// No compatible method or getter so invoke noSuchMethod.
280-
return InvokeNoSuchMethod(instance, Symbols::Call(), arguments,
281-
arguments_descriptor);
321+
const auto& function =
322+
Function::Handle(Function::RawCast(resolved_result.raw()));
323+
return InvokeCallable(function, arguments, arguments_descriptor);
282324
}
283325

284326
ObjectPtr DartEntry::InvokeNoSuchMethod(const Instance& receiver,

runtime/vm/dart_entry.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,23 @@ class DartEntry : public AllStatic {
206206
const Array& arguments_descriptor,
207207
uword current_sp = OSThread::GetCurrentStackPointer());
208208

209+
// Resolves the first argument to a callable compatible with the arguments.
210+
//
211+
// If no errors occur, the first argument is changed to be either the resolved
212+
// callable or, if Function::null() is returned, an appropriate target for
213+
// invoking noSuchMethod.
214+
//
215+
// On success, returns a RawFunction. On failure, a RawError.
216+
static ObjectPtr ResolveCallable(const Array& arguments,
217+
const Array& arguments_descriptor);
218+
219+
// Invokes the function returned by ResolveCallable.
220+
//
221+
// On success, returns a RawInstance. On failure, a RawError.
222+
static ObjectPtr InvokeCallable(const Function& callable_function,
223+
const Array& arguments,
224+
const Array& arguments_descriptor);
225+
209226
// Invokes the closure object given as the first argument.
210227
// On success, returns a RawInstance. On failure, a RawError.
211228
// This is used when there is no type argument vector and

runtime/vm/interpreter.cc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3431,29 +3431,39 @@ ObjectPtr Interpreter::Call(FunctionPtr function,
34313431
const intptr_t receiver_idx = type_args_len > 0 ? 1 : 0;
34323432
const intptr_t argc =
34333433
InterpreterHelpers::ArgDescArgCount(argdesc_) + receiver_idx;
3434-
34353434
ObjectPtr receiver = FrameArguments(FP, argc)[receiver_idx];
34363435

3437-
// Invoke field getter on receiver.
3436+
// Possibly demangle field name and invoke field getter on receiver.
34383437
{
34393438
SP[1] = argdesc_; // Save argdesc_.
34403439
SP[2] = 0; // Result of runtime call.
34413440
SP[3] = receiver; // Receiver.
3442-
SP[4] = function->ptr()->name_; // Field name.
3441+
SP[4] = function->ptr()->name_; // Field name (may change during call).
34433442
Exit(thread, FP, SP + 5, pc);
34443443
if (!InvokeRuntime(thread, this, DRT_GetFieldForDispatch,
34453444
NativeArguments(thread, 2, SP + 3, SP + 2))) {
34463445
HANDLE_EXCEPTION;
34473446
}
3447+
function = FrameFunction(FP);
34483448
argdesc_ = Array::RawCast(SP[1]);
34493449
}
34503450

3451+
// If the field name in the arguments is different after the call, then
3452+
// this was a dynamic call.
3453+
StringPtr field_name = String::RawCast(SP[4]);
3454+
const bool is_dynamic_call = function->ptr()->name_ != field_name;
3455+
34513456
// Replace receiver with field value, keep all other arguments, and
34523457
// invoke 'call' function, or if not found, invoke noSuchMethod.
34533458
FrameArguments(FP, argc)[receiver_idx] = receiver = SP[2];
34543459

34553460
// If the field value is a closure, no need to resolve 'call' function.
34563461
if (InterpreterHelpers::GetClassId(receiver) == kClosureCid) {
3462+
if (is_dynamic_call) {
3463+
// TODO(dartbug.com/40813): Move checks that are currently compiled
3464+
// in the closure body to here as they are also moved to
3465+
// FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher.
3466+
}
34573467
SP[1] = Closure::RawCast(receiver)->ptr()->function_;
34583468
goto TailCallSP1;
34593469
}

0 commit comments

Comments
 (0)