[vm/compiler] Make the compiler use dyn:* selectors when calling getters

We already make the callsites use dyn:* selectors for normal method
calls as well as setters, this is doing the same thing for getters.

Once callsites use dyn:get:* various pieces in runtime need to be
adjusted to accomodate for that (e.g. NSM handling, etc).

A follow-up CL will then start actually generating dyn:* getters in
certain situations.

Issue https://github.com/dart-lang/sdk/issues/40876

Change-Id: If219603bc0b8eb119edd08b211a8897d21ec0fb6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/154320
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
This commit is contained in:
Martin Kustermann 2020-07-15 12:38:04 +00:00 committed by commit-bot@chromium.org
parent a48bebea1e
commit 06183779d7
12 changed files with 223 additions and 123 deletions

View file

@ -437,10 +437,11 @@ class _TraceReader {
callNodesBySelector[targetName]?.connectTo(funNode);
callNodesBySelector['$dynPrefix$targetName']?.connectTo(funNode);
} else if (name.startsWith(extractorPrefix)) {
// Handle method tear-off: [tear-off-extractor] get:foo is hit
// by get:foo.
callNodesBySelector[name.substring(extractorPrefix.length)]
?.connectTo(funNode);
// Handle method tear-off: [tear-off-extractor] get:foo can be hit
// by dyn:get:foo and get:foo.
final targetName = name.substring(extractorPrefix.length);
callNodesBySelector[targetName]?.connectTo(funNode);
callNodesBySelector['$dynPrefix$targetName']?.connectTo(funNode);
}
}
}

View file

@ -89,8 +89,8 @@ void main() async {
expect(retainedClasses, containsAll(['A', 'B', 'K']));
expect(retainedFunctions, containsAll(['print', 'tearOff']));
final getTearOffCall =
callGraph.dynamicCalls.firstWhere((n) => n.data == 'get:tornOff');
final getTearOffCall = callGraph.dynamicCalls
.firstWhere((n) => n.data == 'dyn:get:tornOff');
expect(
getTearOffCall.dominated.map((n) => n.data.qualifiedName),
equals([

View file

@ -52,11 +52,11 @@ DEFINE_FLAG(int,
static void GetUniqueDynamicTarget(Isolate* isolate,
const String& fname,
Object* function) {
UniqueFunctionsSet functions_set(
UniqueFunctionsMap functions_map(
isolate->object_store()->unique_dynamic_targets());
ASSERT(fname.IsSymbol());
*function = functions_set.GetOrNull(fname);
ASSERT(functions_set.Release().raw() ==
*function = functions_map.GetOrNull(fname);
ASSERT(functions_map.Release().raw() ==
isolate->object_store()->unique_dynamic_targets());
}

View file

@ -1299,18 +1299,24 @@ void Precompiler::CheckForNewDynamicFunctions() {
// hit method extractor get:foo, because it will hit an existing
// method foo first.
selector2 = Field::NameFromGetter(selector);
selector3 = Symbols::Lookup(thread(), selector2);
if (IsSent(selector3)) {
if (IsSent(selector2)) {
AddFunction(function);
}
selector2 = Function::CreateDynamicInvocationForwarderName(selector2);
selector3 = Symbols::Lookup(thread(), selector2);
if (IsSent(selector3)) {
AddFunction(function);
if (IsSent(selector2)) {
selector2 =
Function::CreateDynamicInvocationForwarderName(selector);
function2 = function.GetDynamicInvocationForwarder(selector2);
AddFunction(function2);
}
} else if (function.kind() == FunctionLayout::kRegularFunction) {
selector2 = Field::LookupGetterSymbol(selector);
if (IsSent(selector2)) {
selector3 = String::null();
if (!selector2.IsNull()) {
selector3 =
Function::CreateDynamicInvocationForwarderName(selector2);
}
if (IsSent(selector2) || IsSent(selector3)) {
metadata = kernel::ProcedureAttributesOf(function, Z);
found_metadata = true;
@ -1327,21 +1333,35 @@ void Precompiler::CheckForNewDynamicFunctions() {
}
}
if (function.kind() == FunctionLayout::kImplicitSetter ||
function.kind() == FunctionLayout::kSetterFunction ||
function.kind() == FunctionLayout::kRegularFunction) {
const bool is_getter =
function.kind() == FunctionLayout::kImplicitGetter ||
function.kind() == FunctionLayout::kGetterFunction;
const bool is_setter =
function.kind() == FunctionLayout::kImplicitSetter ||
function.kind() == FunctionLayout::kSetterFunction;
const bool is_regular =
function.kind() == FunctionLayout::kRegularFunction;
if (is_getter || is_setter || is_regular) {
selector2 = Function::CreateDynamicInvocationForwarderName(selector);
if (IsSent(selector2)) {
if (function.kind() == FunctionLayout::kImplicitSetter) {
if (function.kind() == FunctionLayout::kImplicitGetter ||
function.kind() == FunctionLayout::kImplicitSetter) {
field = function.accessor_field();
metadata = kernel::ProcedureAttributesOf(field, Z);
} else if (!found_metadata) {
metadata = kernel::ProcedureAttributesOf(function, Z);
}
if (metadata.method_or_setter_called_dynamically) {
function2 = function.GetDynamicInvocationForwarder(selector2);
AddFunction(function2);
if (is_getter) {
if (metadata.getter_called_dynamically) {
function2 = function.GetDynamicInvocationForwarder(selector2);
AddFunction(function2);
}
} else {
if (metadata.method_or_setter_called_dynamically) {
function2 = function.GetDynamicInvocationForwarder(selector2);
AddFunction(function2);
}
}
}
}
@ -1376,16 +1396,38 @@ static void AddNameToFunctionsTable(Zone* zone,
table->UpdateValue(fname, farray);
}
static void AddNamesToFunctionsTable(Zone* zone,
Table* table,
const String& fname,
const Function& function,
String* mangled_name,
Function* dyn_function) {
AddNameToFunctionsTable(zone, table, fname, function);
*dyn_function = function.raw();
if (kernel::NeedsDynamicInvocationForwarder(function)) {
*mangled_name = function.name();
*mangled_name =
Function::CreateDynamicInvocationForwarderName(*mangled_name);
*dyn_function = function.GetDynamicInvocationForwarder(*mangled_name,
/*allow_add=*/true);
}
*mangled_name = Function::CreateDynamicInvocationForwarderName(fname);
AddNameToFunctionsTable(zone, table, *mangled_name, *dyn_function);
}
void Precompiler::CollectDynamicFunctionNames() {
if (!FLAG_collect_dynamic_function_names) {
return;
}
Library& lib = Library::Handle(Z);
Class& cls = Class::Handle(Z);
Array& functions = Array::Handle(Z);
Function& function = Function::Handle(Z);
String& fname = String::Handle(Z);
Array& farray = Array::Handle(Z);
auto& lib = Library::Handle(Z);
auto& cls = Class::Handle(Z);
auto& functions = Array::Handle(Z);
auto& function = Function::Handle(Z);
auto& fname = String::Handle(Z);
auto& farray = Array::Handle(Z);
auto& mangled_name = String::Handle(Z);
auto& dyn_function = Function::Handle(Z);
Table table(HashTables::New<Table>(100));
for (intptr_t i = 0; i < libraries_.Length(); i++) {
@ -1394,27 +1436,34 @@ void Precompiler::CollectDynamicFunctionNames() {
while (it.HasNext()) {
cls = it.GetNextClass();
functions = cls.functions();
for (intptr_t j = 0; j < functions.Length(); j++) {
const intptr_t length = functions.Length();
for (intptr_t j = 0; j < length; j++) {
function ^= functions.At(j);
if (function.IsDynamicFunction()) {
fname = function.name();
if (function.IsSetterFunction() ||
function.IsImplicitSetterFunction()) {
AddNameToFunctionsTable(zone(), &table, fname, function);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
} else if (function.IsGetterFunction() ||
function.IsImplicitGetterFunction()) {
// Enter both getter and non getter name.
AddNameToFunctionsTable(zone(), &table, fname, function);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
fname = Field::NameFromGetter(fname);
AddNameToFunctionsTable(zone(), &table, fname, function);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
} else if (function.IsMethodExtractor()) {
// Skip. We already add getter names for regular methods below.
continue;
} else {
// Regular function. Enter both getter and non getter name.
AddNameToFunctionsTable(zone(), &table, fname, function);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
fname = Field::GetterName(fname);
AddNameToFunctionsTable(zone(), &table, fname, function);
AddNamesToFunctionsTable(zone(), &table, fname, function,
&mangled_name, &dyn_function);
}
}
}
@ -1424,7 +1473,8 @@ void Precompiler::CollectDynamicFunctionNames() {
// Locate all entries with one function only
Table::Iterator iter(&table);
String& key = String::Handle(Z);
UniqueFunctionsSet functions_set(HashTables::New<UniqueFunctionsSet>(20));
String& key_demangled = String::Handle(Z);
UniqueFunctionsMap functions_map(HashTables::New<UniqueFunctionsMap>(20));
while (iter.MoveNext()) {
intptr_t curr_key = iter.Current();
key ^= table.GetKey(curr_key);
@ -1432,8 +1482,25 @@ void Precompiler::CollectDynamicFunctionNames() {
ASSERT(!farray.IsNull());
if (farray.Length() == 1) {
function ^= farray.At(0);
cls = function.Owner();
functions_set.Insert(function);
// It looks like there is exactly one target for the given name. Though we
// have to be careful: e.g. A name like `dyn:get:foo` might have a target
// `foo()`. Though the actual target would be a lazily created method
// extractor `get:foo` for the `foo` function.
//
// We'd like to prevent eager creation of functions which we normally
// create lazily.
// => We disable unique target optimization if the target belongs to the
// lazily created functions.
key_demangled = key.raw();
if (Function::IsDynamicInvocationForwarderName(key)) {
key_demangled = Function::DemangleDynamicInvocationForwarderName(key);
}
if (function.name() != key.raw() &&
function.name() != key_demangled.raw()) {
continue;
}
functions_map.UpdateOrInsert(key, function);
}
}
@ -1442,18 +1509,18 @@ void Precompiler::CollectDynamicFunctionNames() {
get_runtime_type_is_unique_ = !farray.IsNull() && (farray.Length() == 1);
if (FLAG_print_unique_targets) {
UniqueFunctionsSet::Iterator unique_iter(&functions_set);
UniqueFunctionsMap::Iterator unique_iter(&functions_map);
while (unique_iter.MoveNext()) {
intptr_t curr_key = unique_iter.Current();
function ^= functions_set.GetKey(curr_key);
function ^= functions_map.GetPayload(curr_key, 0);
THR_Print("* %s\n", function.ToQualifiedCString());
}
THR_Print("%" Pd " of %" Pd " dynamic selectors are unique\n",
functions_set.NumOccupied(), table.NumOccupied());
functions_map.NumOccupied(), table.NumOccupied());
}
isolate()->object_store()->set_unique_dynamic_targets(
functions_set.Release());
functions_map.Release());
table.Release();
}

View file

@ -381,26 +381,12 @@ class FunctionsTraits {
static bool ReportStats() { return false; }
static bool IsMatch(const Object& a, const Object& b) {
Zone* zone = Thread::Current()->zone();
String& a_s = String::Handle(zone);
String& b_s = String::Handle(zone);
a_s = a.IsFunction() ? Function::Cast(a).name() : String::Cast(a).raw();
b_s = b.IsFunction() ? Function::Cast(b).name() : String::Cast(b).raw();
ASSERT(a_s.IsSymbol() && b_s.IsSymbol());
return a_s.raw() == b_s.raw();
return String::Cast(a).raw() == String::Cast(b).raw();
}
static uword Hash(const Object& obj) {
if (obj.IsFunction()) {
return String::Handle(Function::Cast(obj).name()).Hash();
} else {
ASSERT(String::Cast(obj).IsSymbol());
return String::Cast(obj).Hash();
}
}
static ObjectPtr NewKey(const Function& function) { return function.raw(); }
static uword Hash(const Object& obj) { return String::Cast(obj).Hash(); }
};
typedef UnorderedHashSet<FunctionsTraits> UniqueFunctionsSet;
typedef UnorderedHashMap<FunctionsTraits> UniqueFunctionsMap;
#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
// ObfuscationMap maps Strings to Strings.

View file

@ -2331,16 +2331,27 @@ Fragment StreamingFlowGraphBuilder::BuildPropertyGet(TokenPosition* p) {
instructions += CheckNull(position, receiver, getter_name);
}
if (!direct_call.target_.IsNull()) {
const String* mangled_name = &getter_name;
const Function* direct_call_target = &direct_call.target_;
if (H.IsRoot(itarget_name)) {
mangled_name = &String::ZoneHandle(
Z, Function::CreateDynamicInvocationForwarderName(getter_name));
if (!direct_call_target->IsNull()) {
direct_call_target = &Function::ZoneHandle(
direct_call.target_.GetDynamicInvocationForwarder(*mangled_name));
}
}
if (!direct_call_target->IsNull()) {
ASSERT(CompilerState::Current().is_aot());
instructions +=
StaticCall(position, direct_call.target_, 1, Array::null_array(),
StaticCall(position, *direct_call_target, 1, Array::null_array(),
ICData::kNoRebind, &result_type);
} else {
const intptr_t kTypeArgsLen = 0;
const intptr_t kNumArgsChecked = 1;
instructions +=
InstanceCall(position, getter_name, Token::kGET, kTypeArgsLen, 1,
InstanceCall(position, *mangled_name, Token::kGET, kTypeArgsLen, 1,
Array::null_array(), kNumArgsChecked, *interface_target,
*tearoff_interface_target, &result_type);
}

View file

@ -255,15 +255,7 @@ void MethodRecognizer::Libraries(GrowableArray<Library*>* libs) {
libs->Add(&Library::ZoneHandle(Library::FfiLibrary()));
}
Token::Kind MethodTokenRecognizer::RecognizeTokenKind(const String& name_) {
Thread* thread = Thread::Current();
REUSABLE_STRING_HANDLESCOPE(thread);
String& name = thread->StringHandle();
name = name_.raw();
ASSERT(name.IsSymbol());
if (Function::IsDynamicInvocationForwarderName(name)) {
name = Function::DemangleDynamicInvocationForwarderName(name);
}
static Token::Kind RecognizeTokenKindHelper(const String& name) {
if (name.raw() == Symbols::Plus().raw()) {
return Token::kADD;
} else if (name.raw() == Symbols::Minus().raw()) {
@ -310,6 +302,18 @@ Token::Kind MethodTokenRecognizer::RecognizeTokenKind(const String& name_) {
return Token::kILLEGAL;
}
Token::Kind MethodTokenRecognizer::RecognizeTokenKind(const String& name) {
ASSERT(name.IsSymbol());
if (Function::IsDynamicInvocationForwarderName(name)) {
Thread* thread = Thread::Current();
const auto& demangled_name = String::Handle(
thread->zone(), Function::DemangleDynamicInvocationForwarderName(name));
return RecognizeTokenKindHelper(demangled_name);
} else {
return RecognizeTokenKindHelper(name);
}
}
#define RECOGNIZE_FACTORY(symbol, class_name, constructor_name, cid, fp) \
{Symbols::k##symbol##Id, cid, fp, #symbol ", " #cid}, // NOLINT

View file

@ -723,7 +723,13 @@ bool NeedsDynamicInvocationForwarder(const Function& function) {
// dynamic invocation forwarder. So dynamic invocation forwarder is only
// needed if there are non-covariant parameters of non-top type.
ASSERT(!function.IsImplicitGetterFunction());
// TODO(dartbug.com/40876): Implement dynamic invocation forwarders for
// getters.
if (function.IsImplicitGetterFunction() ||
function.IsImplicitClosureFunction() || function.IsMethodExtractor()) {
return false;
}
if (function.IsImplicitSetterFunction()) {
const auto& field = Field::Handle(zone, function.accessor_field());
return !(field.is_covariant() || field.is_generic_covariant_impl());

View file

@ -3674,6 +3674,10 @@ StringPtr Function::DemangleDynamicInvocationForwarderName(const String& name) {
name.Length() - kDynamicPrefixLength);
}
StringPtr Function::CreateDynamicInvocationForwarderName(const String& name) {
return Symbols::FromConcat(Thread::Current(), Symbols::DynamicPrefix(), name);
}
#if !defined(DART_PRECOMPILED_RUNTIME)
FunctionPtr Function::CreateDynamicInvocationForwarder(
const String& mangled_name) const {
@ -3716,10 +3720,6 @@ FunctionPtr Function::CreateDynamicInvocationForwarder(
return forwarder.raw();
}
StringPtr Function::CreateDynamicInvocationForwarderName(const String& name) {
return Symbols::FromConcat(Thread::Current(), Symbols::DynamicPrefix(), name);
}
FunctionPtr Function::GetDynamicInvocationForwarder(
const String& mangled_name,
bool allow_add /* = true */) const {

View file

@ -3513,9 +3513,9 @@ class Function : public Object {
static StringPtr DemangleDynamicInvocationForwarderName(const String& name);
#if !defined(DART_PRECOMPILED_RUNTIME)
static StringPtr CreateDynamicInvocationForwarderName(const String& name);
#if !defined(DART_PRECOMPILED_RUNTIME)
FunctionPtr CreateDynamicInvocationForwarder(
const String& mangled_name) const;

View file

@ -71,61 +71,50 @@ FunctionPtr Resolver::ResolveDynamicAnyArgs(Zone* zone,
}
Function& function = Function::Handle(zone);
String& demangled = String::Handle(zone);
const String& demangled = String::Handle(
zone,
Function::IsDynamicInvocationForwarderName(function_name)
? Function::DemangleDynamicInvocationForwarderName(function_name)
: function_name.raw());
const bool is_getter = Field::IsGetterName(function_name);
const bool is_getter = Field::IsGetterName(demangled);
String& demangled_getter_name = String::Handle();
if (is_getter) {
demangled = Field::NameFromGetter(function_name);
demangled_getter_name = Field::NameFromGetter(demangled);
}
if (Function::IsDynamicInvocationForwarderName(function_name)) {
demangled = Function::DemangleDynamicInvocationForwarderName(function_name);
#ifdef DART_PRECOMPILED_RUNTIME
// In precompiled mode, the non-dynamic version of the function may be
// tree-shaken away, so can't necessarily resolve the demanged name.
while (!cls.IsNull()) {
const bool is_dyn_call = demangled.raw() != function_name.raw();
while (!cls.IsNull()) {
if (is_dyn_call) {
// Try to find a dyn:* forwarder & return it.
function = cls.GetInvocationDispatcher(
function_name, Array::null_array(),
FunctionLayout::kDynamicInvocationForwarder,
/*create_if_absent=*/false);
if (!function.IsNull()) break;
cls = cls.SuperClass();
}
// Some functions don't require dynamic invocation forwarders, for example
// if there are no parameters or all the parameters are marked
// `generic-covariant` (meaning there's no work for the dynamic invocation
// forwarder to do, see `kernel::DynamicInvocationForwarder`). For these
// functions, we won't have built a `dyn:` version, but it's safe to just
// return the original version directly.
return !function.IsNull() ? function.raw()
: ResolveDynamicAnyArgs(zone, receiver_class,
demangled, allow_add);
#else
function =
ResolveDynamicAnyArgs(zone, receiver_class, demangled, allow_add);
return function.IsNull() ? function.raw()
: function.GetDynamicInvocationForwarder(
function_name, allow_add);
#endif
}
if (!function.IsNull()) return function.raw();
// Now look for an instance function whose name matches function_name
// in the class.
while (!cls.IsNull()) {
function = cls.LookupDynamicFunction(function_name);
if (!function.IsNull()) {
return function.raw();
function = cls.LookupDynamicFunction(demangled);
#if !defined(DART_PRECOMPILED_RUNTIME)
// In JIT we might need to lazily create a dyn:* forwarder.
if (is_dyn_call && !function.IsNull()) {
function =
function.GetDynamicInvocationForwarder(function_name, allow_add);
}
#endif
if (!function.IsNull()) return function.raw();
// Getter invocation might actually be a method extraction.
if (is_getter && function.IsNull()) {
function = cls.LookupDynamicFunction(demangled);
if (is_getter) {
function = cls.LookupDynamicFunction(demangled_getter_name);
if (!function.IsNull()) {
if (allow_add && FLAG_lazy_dispatchers) {
// We were looking for the getter but found a method with the same
// name. Create a method extractor and return it.
// The extractor does not exist yet, so using GetMethodExtractor is
// not necessary here.
function = function.CreateMethodExtractor(function_name);
function = function.CreateMethodExtractor(demangled);
return function.raw();
} else {
return Function::null();

View file

@ -2180,8 +2180,10 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromCallStub, 4) {
const bool is_dynamic_call =
Function::IsDynamicInvocationForwarderName(target_name);
String& demangled_target_name = String::Handle(zone, target_name.raw());
if (is_dynamic_call) {
target_name = Function::DemangleDynamicInvocationForwarderName(target_name);
demangled_target_name =
Function::DemangleDynamicInvocationForwarderName(target_name);
}
Class& cls = Class::Handle(zone, receiver.clazz());
@ -2194,8 +2196,9 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromCallStub, 4) {
#define NO_SUCH_METHOD() \
const Object& result = Object::Handle( \
zone, DartEntry::InvokeNoSuchMethod( \
receiver, target_name, orig_arguments, orig_arguments_desc)); \
zone, \
DartEntry::InvokeNoSuchMethod(receiver, demangled_target_name, \
orig_arguments, orig_arguments_desc)); \
ThrowIfError(result); \
arguments.SetReturn(result);
@ -2206,13 +2209,23 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromCallStub, 4) {
zone, closure_function.ImplicitInstanceClosure(receiver)); \
arguments.SetReturn(result);
const bool is_getter = Field::IsGetterName(target_name);
const bool is_getter = Field::IsGetterName(demangled_target_name);
if (is_getter) {
// Tear-off of a method
// o.foo (o.get:foo) failed, closurize o.foo() if it exists.
String& field_name =
String::Handle(zone, Field::NameFromGetter(target_name));
const auto& function_name =
String::Handle(zone, Field::NameFromGetter(demangled_target_name));
const auto& dyn_function_name = String::Handle(
zone, is_dynamic_call ? Function::CreateDynamicInvocationForwarderName(
function_name)
: function_name.raw());
while (!cls.IsNull()) {
function = cls.LookupDynamicFunction(field_name);
if (is_dynamic_call) {
function = cls.LookupDynamicFunction(dyn_function_name);
}
if (function.IsNull()) {
function = cls.LookupDynamicFunction(function_name);
}
if (!function.IsNull()) {
CLOSURIZE(function);
return;
@ -2222,6 +2235,7 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromCallStub, 4) {
// Fall through for noSuchMethod
} else {
// Call through field.
// o.foo(...) failed, invoke noSuchMethod is foo exists but has the wrong
// number of arguments, or try (o.foo).call(...)
@ -2247,16 +2261,38 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromCallStub, 4) {
return;
}
const String& getter_name =
String::Handle(zone, Field::GetterName(target_name));
// Dynamic call sites have to use the dynamic getter as well (if it was
// created).
const auto& getter_name =
String::Handle(zone, Field::GetterName(demangled_target_name));
const auto& dyn_getter_name = String::Handle(
zone, is_dynamic_call
? Function::CreateDynamicInvocationForwarderName(getter_name)
: getter_name.raw());
ArgumentsDescriptor args_desc(orig_arguments_desc);
while (!cls.IsNull()) {
// If there is a function with the target name but mismatched arguments
// we need to call `receiver.noSuchMethod()`.
function = cls.LookupDynamicFunction(target_name);
if (!function.IsNull()) {
ASSERT(!function.AreValidArguments(args_desc, NULL));
break; // mismatch, invoke noSuchMethod
}
function = cls.LookupDynamicFunction(getter_name);
if (is_dynamic_call) {
function = cls.LookupDynamicFunction(demangled_target_name);
if (!function.IsNull()) {
ASSERT(!function.AreValidArguments(args_desc, NULL));
break; // mismatch, invoke noSuchMethod
}
}
// If there is a getter we need to call-through-getter.
if (is_dynamic_call) {
function = cls.LookupDynamicFunction(dyn_getter_name);
}
if (function.IsNull()) {
function = cls.LookupDynamicFunction(getter_name);
}
if (!function.IsNull()) {
const Array& getter_arguments = Array::Handle(Array::New(1));
getter_arguments.SetAt(0, receiver);