diff --git a/runtime/tests/vm/dart/single_target_and_method_extractors_test.dart b/runtime/tests/vm/dart/single_target_and_method_extractors_test.dart new file mode 100644 index 00000000000..eb82966e43c --- /dev/null +++ b/runtime/tests/vm/dart/single_target_and_method_extractors_test.dart @@ -0,0 +1,49 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// 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. + +import "package:expect/expect.dart"; + +// Note: below we are using tear-offs instead of calling methods directly +// to guarantee very specific compilation order: +// +// - compile main, add get:methodM and get:callMethodM to selector set. +// - compile A.callMethodM and add get:createB to selector set. +// - compile A.createB and mark B as allocated. +// +// Class B is not marked as allocated until A.createB is compiled, which means +// that when A.callMethodM is compiled only class A has get:methodM method +// extractor injected. +// +// This test is verifying that optimizing compiler does not treat this method +// extractor as a single target for this.get:methodM call. +main() { + // This adds selector 'get:methodM' into the sent selector set and + // marks class A as allocated. + new A().methodM; + + // This adds get:callMethodM to the sent selector set. + final callMethodMOnA = new A().callMethodM; + final b = callMethodMOnA("A"); + final callMethodMOnB = b.callMethodM; + callMethodMOnB("B"); +} + +class A { + B callMethodM(String expected) { + final f = methodM; + Expect.equals(expected, f()); + + final newB = createB; + return newB(); + } + + B createB() => new B(); + + String methodM() => 'A'; +} + +class B extends A { + @override + String methodM() => 'B'; +} diff --git a/runtime/vm/compiler/aot/aot_call_specializer.cc b/runtime/vm/compiler/aot/aot_call_specializer.cc index 2db84f6b456..13190f9d251 100644 --- a/runtime/vm/compiler/aot/aot_call_specializer.cc +++ b/runtime/vm/compiler/aot/aot_call_specializer.cc @@ -719,10 +719,18 @@ void AotCallSpecializer::VisitInstanceCall(InstanceCallInstr* instr) { for (intptr_t i = 0; i < class_ids.length(); i++) { const intptr_t cid = class_ids[i]; cls = isolate()->class_table()->At(cid); + // Even if we are resolving get:M on a class that has method M + // ResolveForReceiverClass would not inject a method extractor into + // a class becuase FLAG_lazy_dispatchers is set to false during AOT + // compilation. Precompiler however does inject method extractors + // (see Precompiler::CheckForNewDynamicFunctions). This means that that + // lookup for get:m might overlook a method M in subclass and return + // get:m (method extractor for M) from a superclass. + // For this reason we abort optimization if lookup returns any + // artificial functions that can be inserted lazily. target = instr->ResolveForReceiverClass(cls); - if (target.IsNull()) { - // Can't resolve the target. It might be a noSuchMethod, - // call through getter or closurization. + if (target.IsNull() || target.IsMethodExtractor() || + target.IsInvokeFieldDispatcher()) { single_target = Function::null(); ic_data = ICData::null(); break;