Filter instantiation stubs

Closes #32992

Change-Id: Ib0521f24eb734b42a1a55663119204220f263265
Reviewed-on: https://dart-review.googlesource.com/53740
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/js_emitter/instantiation_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/instantiation_stub_generator.dart
index 35ff906..865fc15 100644
--- a/pkg/compiler/lib/src/js_emitter/instantiation_stub_generator.dart
+++ b/pkg/compiler/lib/src/js_emitter/instantiation_stub_generator.dart
@@ -4,6 +4,7 @@
 
 library dart2js.js_emitter.instantiation_stub_generator;
 
+import '../common/names.dart';
 import '../common_elements.dart' show CommonElements;
 import '../elements/entities.dart';
 import '../io/source_information.dart';
@@ -147,22 +148,49 @@
     Map<Selector, SelectorConstraints> callSelectors =
         _codegenWorldBuilder.invocationsByName(call);
 
+    Set<ParameterStructure> computeLiveParameterStructures() {
+      Set<ParameterStructure> parameterStructures =
+          new Set<ParameterStructure>();
+
+      void process(Iterable<FunctionEntity> functions) {
+        for (FunctionEntity function in functions) {
+          if (function.parameterStructure.typeParameters == typeArgumentCount) {
+            parameterStructures.add(function.parameterStructure);
+          }
+        }
+      }
+
+      process(_codegenWorldBuilder.closurizedStatics);
+      process(_codegenWorldBuilder.closurizedMembers);
+      process(_codegenWorldBuilder.genericInstanceMethods.where(
+          (FunctionEntity function) =>
+              function.name == Identifiers.call &&
+              function.enclosingClass.isClosure));
+
+      return parameterStructures;
+    }
+
     List<StubMethod> stubs = <StubMethod>[];
 
     // For every call-selector generate a stub to the corresponding selector
     // with filled-in type arguments.
 
+    Set<ParameterStructure> parameterStructures;
     for (Selector selector in callSelectors.keys) {
       CallStructure callStructure = selector.callStructure;
       if (callStructure.typeArgumentCount != 0) continue;
-      // TODO(sra): Eliminate selectors that are not supported by any generic
-      // function with [typeArgumentCount] type arguments.
-      CallStructure genericCallStructrure =
+      CallStructure genericCallStructure =
           callStructure.withTypeArgumentCount(typeArgumentCount);
-      Selector genericSelector =
-          new Selector.call(selector.memberName, genericCallStructrure);
-      stubs.add(_generateStub(
-          instantiationClass, functionField, selector, genericSelector));
+      parameterStructures ??= computeLiveParameterStructures();
+      for (ParameterStructure parameterStructure in parameterStructures) {
+        if (genericCallStructure.signatureApplies(parameterStructure)) {
+          Selector genericSelector =
+              new Selector.call(selector.memberName, genericCallStructure);
+          stubs.add(_generateStub(
+              instantiationClass, functionField, selector, genericSelector));
+          break;
+        }
+      }
     }
 
     stubs.add(_generateSignatureStub(functionField));
diff --git a/tests/compiler/dart2js/generic_methods/instantiation_stub_test.dart b/tests/compiler/dart2js/generic_methods/instantiation_stub_test.dart
new file mode 100644
index 0000000..6d2478b
--- /dev/null
+++ b/tests/compiler/dart2js/generic_methods/instantiation_stub_test.dart
@@ -0,0 +1,97 @@
+// Copyright (c) 2018, 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:async_helper/async_helper.dart';
+import 'package:compiler/src/commandline_options.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/elements/entities.dart';
+import 'package:compiler/src/js_emitter/model.dart';
+import 'package:compiler/src/world.dart';
+import 'package:expect/expect.dart';
+import '../helpers/program_lookup.dart';
+import '../memory_compiler.dart';
+
+const String code = '''
+import 'package:meta/dart2js.dart';
+
+// This needs one-arg instantiation.
+@noInline
+T f1a<T>(T t) => t;
+
+// This needs no instantiation because it is not closurized.
+@noInline
+T f1b<T>(T t1, T t2) => t1;
+
+class Class {
+  // This needs two-arg instantiation.
+  @noInline
+  bool f2a<T, S>(T t, S s) => t == s;
+
+  // This needs no instantiation because it is not closurized.
+  @noInline
+  bool f2b<T, S>(T t, S s1, S s2) => t == s1;
+}
+
+@noInline
+int method1(int i, int Function(int) f) => f(i);
+
+@noInline
+bool method2(int a, int b, bool Function(int, int) f) => f(a, b);
+
+@noInline
+int method3(int a, int b, int c, int Function(int, int, int) f) => f(a, b, c);
+
+main() {
+  // This needs three-arg instantiation.
+  T local1<T, S, U>(T t, S s, U u) => t;
+
+  // This needs no instantiation because but a local function is always
+  // closurized so we assume it does.
+  T local2<T, S, U>(T t, S s, U u1, U u2) => t;
+
+  print(method1(42, f1a));
+  print(f1b(42, 87));
+
+  Class c = new Class();
+  print(method2(0, 1, c.f2a));
+  print(c.f2b(42, 87, 123));
+
+  print(method3(0, 1, 2, local1));
+  print(local2(42, 87, 123, 256));
+}
+''';
+
+main() {
+  asyncTest(() async {
+    CompilationResult result = await runCompiler(
+        memorySourceFiles: {'main.dart': code},
+        options: [Flags.strongMode, Flags.omitImplicitChecks]);
+    Expect.isTrue(result.isSuccess);
+    Compiler compiler = result.compiler;
+    ClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
+    ProgramLookup programLookup = new ProgramLookup(compiler);
+
+    void checkStubs(ClassEntity element, List<String> expectedStubs) {
+      Class cls = programLookup.getClass(element);
+      List<String> actualStubs = <String>[];
+      if (cls != null) {
+        for (StubMethod stub in cls.callStubs) {
+          actualStubs.add(stub.name.key);
+        }
+      }
+      Expect.setEquals(
+          expectedStubs,
+          actualStubs,
+          "Unexpected stubs for $element:\n "
+          "Expected: $expectedStubs\n Actual: $actualStubs");
+    }
+
+    checkStubs(closedWorld.commonElements.instantiation1Class,
+        [r'call$1', r'$signature']);
+    checkStubs(closedWorld.commonElements.instantiation2Class,
+        [r'call$2', r'$signature']);
+    checkStubs(closedWorld.commonElements.instantiation3Class,
+        [r'call$3', r'call$4', r'$signature']);
+  });
+}
diff --git a/tests/compiler/dart2js_extra/instantiation_stub_test.dart b/tests/compiler/dart2js_extra/instantiation_stub_test.dart
new file mode 100644
index 0000000..d62266f
--- /dev/null
+++ b/tests/compiler/dart2js_extra/instantiation_stub_test.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2018, 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+// This needs one-arg instantiation.
+@NoInline()
+T f1a<T>(T t) => t;
+
+// This needs no instantiation because it is not closurized.
+@NoInline()
+T f1b<T>(T t1, T t2) => t1;
+
+class Class {
+  // This needs two-arg instantiation.
+  @NoInline()
+  bool f2a<T, S>(T t, S s) => t == s;
+
+  // This needs no instantiation because it is not closurized.
+  @NoInline()
+  bool f2b<T, S>(T t, S s1, S s2) => t == s1;
+}
+
+@NoInline()
+int method1(int i, int Function(int) f) => f(i);
+
+@NoInline()
+bool method2(int a, int b, bool Function(int, int) f) => f(a, b);
+
+@NoInline()
+int method3(int a, int b, int c, int Function(int, int, int) f) => f(a, b, c);
+
+main() {
+  // This needs three-arg instantiation.
+  T local1<T, S, U>(T t, S s, U u) => t;
+
+  // This needs no instantiation because but a local function is always
+  // closurized so we assume it does.
+  T local2<T, S, U>(T t, S s, U u1, U u2) => t;
+
+  Expect.equals(42, method1(42, f1a));
+  Expect.equals(f1b(42, 87), 42);
+
+  Class c = new Class();
+  Expect.isFalse(method2(0, 1, c.f2a));
+  Expect.isFalse(c.f2b(42, 87, 123));
+
+  Expect.equals(0, method3(0, 1, 2, local1));
+  Expect.equals(42, local2(42, 87, 123, 256));
+}