Short form install-tear-offs for common cases

Change-Id: Ic2f3827ad391ab38b1b3eb1e3460e5f8931d05c3
Reviewed-on: https://dart-review.googlesource.com/c/84053
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/js_emitter/model.dart b/pkg/compiler/lib/src/js_emitter/model.dart
index 78d8585..a9a50cf 100644
--- a/pkg/compiler/lib/src/js_emitter/model.dart
+++ b/pkg/compiler/lib/src/js_emitter/model.dart
@@ -447,8 +447,9 @@
   // this field holds a function computing the function signature.
   final js.Expression functionType;
 
-  // Signature information for this method. This is only required and stored
-  // here if the method [canBeApplied].
+  // Signature information for this method. [optionalParameterDefaultValues] is
+  // only required and stored here if the method [canBeApplied]. The count is
+  // always stored to help select specialized tear-off paths.
   final int requiredParameterCount;
   final /* Map | List */ optionalParameterDefaultValues;
 
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
index c363191..d4b1a58 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
@@ -919,7 +919,7 @@
     int applyIndex = 0;
     if (canBeApplied) {
       optionalParameterDefaultValues = _computeParameterDefaultValues(method);
-      if (element.parameterStructure.typeParameters > 0) {
+      if (parameterStructure.typeParameters > 0) {
         applyIndex = 1;
       }
     }
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 06300e8..d6d0ae4 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -312,22 +312,62 @@
   return holder;
 }
 
-// TODO(sra): Minify properties of 'hunkHelpers'.
-var #hunkHelpers = {
-  inherit: inherit,
-  inheritMany: inheritMany,
-  mixin: mixin,
-  installStaticTearOff: installStaticTearOff,
-  installInstanceTearOff: installInstanceTearOff,
-  makeConstList: makeConstList,
-  lazy: lazy,
-  updateHolder: updateHolder,
-  convertToFastObject: convertToFastObject,
-  setFunctionNamesIfNecessary: setFunctionNamesIfNecessary,
-  updateTypes: updateTypes,
-  setOrUpdateInterceptorsByTag: setOrUpdateInterceptorsByTag,
-  setOrUpdateLeafTags: setOrUpdateLeafTags,
-};
+var #hunkHelpers = (function(){
+  var mkInstance = function(
+      isIntercepted, requiredParameterCount, optionalParameterDefaultValues,
+      callNames, applyIndex) {
+    return function(container, getterName, name, funType) {
+      return installInstanceTearOff(
+          container, getterName, isIntercepted,
+          requiredParameterCount, optionalParameterDefaultValues,
+          callNames, [name], funType, applyIndex);
+    }
+  },
+
+  mkStatic = function(
+      requiredParameterCount, optionalParameterDefaultValues,
+      callNames, applyIndex) {
+    return function(container, getterName, name, funType) {
+      return installStaticTearOff(
+          container, getterName,
+          requiredParameterCount, optionalParameterDefaultValues,
+          callNames, [name], funType, applyIndex);
+    }
+  };
+
+  // TODO(sra): Minify properties of 'hunkHelpers'.
+  return {
+    inherit: inherit,
+    inheritMany: inheritMany,
+    mixin: mixin,
+    installStaticTearOff: installStaticTearOff,
+    installInstanceTearOff: installInstanceTearOff,
+
+        // Unintercepted methods.
+    _instance_0u: mkInstance(0, 0, null, [#call0selector], 0),
+    _instance_1u: mkInstance(0, 1, null, [#call1selector], 0),
+    _instance_2u: mkInstance(0, 2, null, [#call2selector], 0),
+
+        // Intercepted methods.
+    _instance_0i: mkInstance(1, 0, null, [#call0selector], 0),
+    _instance_1i: mkInstance(1, 1, null, [#call1selector], 0),
+    _instance_2i: mkInstance(1, 2, null, [#call2selector], 0),
+
+        // Static methods.
+    _static_0: mkStatic(0, null, [#call0selector], 0),
+    _static_1: mkStatic(1, null, [#call1selector], 0),
+    _static_2: mkStatic(2, null, [#call2selector], 0),
+
+    makeConstList: makeConstList,
+    lazy: lazy,
+    updateHolder: updateHolder,
+    convertToFastObject: convertToFastObject,
+    setFunctionNamesIfNecessary: setFunctionNamesIfNecessary,
+    updateTypes: updateTypes,
+    setOrUpdateInterceptorsByTag: setOrUpdateInterceptorsByTag,
+    setOrUpdateLeafTags: setOrUpdateLeafTags,
+  };
+})();
 
 // Every deferred hunk (i.e. fragment) is a function that we can invoke to
 // initialize it. At this moment it contributes its data to the main hunk.
@@ -535,6 +575,14 @@
   final ModelEmitter modelEmitter;
   final JClosedWorld _closedWorld;
 
+  js.Name _call0Name, _call1Name, _call2Name;
+  js.Name get call0Name =>
+      _call0Name ??= namer.getNameForJsGetName(null, JsGetName.CALL_PREFIX0);
+  js.Name get call1Name =>
+      _call1Name ??= namer.getNameForJsGetName(null, JsGetName.CALL_PREFIX1);
+  js.Name get call2Name =>
+      _call2Name ??= namer.getNameForJsGetName(null, JsGetName.CALL_PREFIX2);
+
   FragmentEmitter(this.compiler, this.namer, this.backend, this.constantEmitter,
       this.modelEmitter, this._closedWorld);
 
@@ -603,6 +651,10 @@
               compiler.codegenWorldBuilder, _closedWorld.nativeData, namer) ??
           new js.EmptyStatement(),
       'invokeMain': fragment.invokeMain,
+
+      'call0selector': js.quoteName(call0Name),
+      'call1selector': js.quoteName(call1Name),
+      'call2selector': js.quoteName(call2Name),
     });
     if (program.hasSoftDeferredClasses) {
       return new js.Block([
@@ -998,9 +1050,7 @@
 
       // Closures taking exactly one argument are common.
       properties.add(js.Property(
-          js.string(namer.callCatchAllName),
-          js.quoteName(
-              namer.getNameForJsGetName(null, JsGetName.CALL_PREFIX1))));
+          js.string(namer.callCatchAllName), js.quoteName(call1Name)));
       properties.add(
           js.Property(js.string(namer.requiredParameterField), js.number(1)));
 
@@ -1126,7 +1176,7 @@
     List<js.Statement> inheritCalls = [];
     List<js.Statement> mixinCalls = [];
     // local caches of functions to allow minifaction of function name in call.
-    Map<String, js.Expression> locals = {};
+    LocalAliases locals = LocalAliases();
 
     Set<Class> classesInFragment = Set();
     for (Library library in fragment.libraries) {
@@ -1154,11 +1204,12 @@
       for (Class cls in library.classes) {
         if (cls.isSoftDeferred != softDeferred) continue;
         collect(cls);
-
         if (cls.mixinClass != null) {
-          locals['_mixin'] ??= js.js('hunkHelpers.mixin');
-          mixinCalls.add(js.js.statement('_mixin(#, #)',
-              [classReference(cls), classReference(cls.mixinClass)]));
+          mixinCalls.add(js.js.statement('#(#, #)', [
+            locals.find('_mixin', 'hunkHelpers.mixin'),
+            classReference(cls),
+            classReference(cls.mixinClass),
+          ]));
         }
       }
     }
@@ -1169,35 +1220,30 @@
           ? new js.LiteralNull()
           : classReference(superclass);
       if (list.length == 1) {
-        locals['_inherit'] ??= js.js('hunkHelpers.inherit');
-        inheritCalls.add(js.js.statement('_inherit(#, #)',
-            [classReference(list.single), superclassReference]));
+        inheritCalls.add(js.js.statement('#(#, #)', [
+          locals.find('_inherit', 'hunkHelpers.inherit'),
+          classReference(list.single),
+          superclassReference
+        ]));
       } else {
-        locals['_inheritMany'] ??= js.js('hunkHelpers.inheritMany');
         var listElements = list.map(classReference).toList();
-        inheritCalls.add(js.js.statement('_inheritMany(#, #)',
-            [superclassReference, js.ArrayInitializer(listElements)]));
+        inheritCalls.add(js.js.statement('#(#, #)', [
+          locals.find('_inheritMany', 'hunkHelpers.inheritMany'),
+          superclassReference,
+          js.ArrayInitializer(listElements)
+        ]));
       }
     }
 
     List<js.Statement> statements = [];
     if (locals.isNotEmpty) {
-      statements.add(localAliasInitializations(locals));
+      statements.add(locals.toStatement());
     }
     statements.addAll(inheritCalls);
     statements.addAll(mixinCalls);
     return wrapPhase('inheritance', statements);
   }
 
-  js.Statement localAliasInitializations(Map<String, js.Expression> locals) {
-    List<js.VariableInitialization> initializations = [];
-    locals.forEach((local, value) {
-      initializations
-          .add(js.VariableInitialization(js.VariableDeclaration(local), value));
-    });
-    return js.ExpressionStatement(js.VariableDeclarationList(initializations));
-  }
-
   /// Emits the setup of method aliases.
   ///
   /// This step consists of simply copying JavaScript functions to their
@@ -1275,8 +1321,7 @@
   /// Emits the section that installs tear-off getters.
   js.Statement emitInstallTearOffs(Fragment fragment,
       {bool softDeferred = false}) {
-    var aliasForInstallStaticTearOff;
-    var aliasForInstallInstanceTearOff;
+    LocalAliases locals = LocalAliases();
 
     /// Emits the statement that installs a tear off for a method.
     ///
@@ -1332,12 +1377,37 @@
       var applyIndex = js.number(method.applyIndex);
 
       if (method.isStatic) {
-        aliasForInstallStaticTearOff ??= '_static';
+        if (requiredParameterCount <= 2 &&
+            callNames.length == 1 &&
+            optionalParameterDefaultValues is js.LiteralNull &&
+            method.applyIndex == 0) {
+          js.Statement finish(int arity) {
+            // Short form for exactly 0/1/2 arguments.
+            var install =
+                locals.find('_static_${arity}', 'hunkHelpers._static_${arity}');
+            return js.js.statement('''
+                #install(#container, #getterName, #name, #funType)''', {
+              "install": install,
+              "container": container,
+              "getterName": js.quoteName(method.tearOffName),
+              "name": funsOrNames.single,
+              "funType": method.functionType,
+            });
+          }
+
+          var installedName = callNames.single;
+          if (installedName == call0Name) return finish(0);
+          if (installedName == call1Name) return finish(1);
+          if (installedName == call2Name) return finish(2);
+        }
+
+        var install =
+            locals.find('_static', 'hunkHelpers.installStaticTearOff');
         return js.js.statement('''
             #install(#container, #getterName,
                      #requiredParameterCount, #optionalParameterDefaultValues,
-                     #callNames, #funsOrNames, #funType, #applyIndex)''', {
-          "install": aliasForInstallStaticTearOff,
+                      #callNames, #funsOrNames, #funType, #applyIndex)''', {
+          "install": install,
           "container": container,
           "getterName": js.quoteName(method.tearOffName),
           "requiredParameterCount": js.number(requiredParameterCount),
@@ -1348,12 +1418,38 @@
           "applyIndex": applyIndex,
         });
       } else {
-        aliasForInstallInstanceTearOff ??= '_instance';
+        if (requiredParameterCount <= 2 &&
+            callNames.length == 1 &&
+            optionalParameterDefaultValues is js.LiteralNull &&
+            method.applyIndex == 0) {
+          js.Statement finish(int arity) {
+            // Short form for exactly 0/1/2 arguments.
+            String isInterceptedTag = isIntercepted ? 'i' : 'u';
+            var install = locals.find('_instance_${arity}_${isInterceptedTag}',
+                'hunkHelpers._instance_${arity}${isInterceptedTag}');
+            return js.js.statement('''
+                #install(#container, #getterName, #name, #funType)''', {
+              "install": install,
+              "container": container,
+              "getterName": js.quoteName(method.tearOffName),
+              "name": funsOrNames.single,
+              "funType": method.functionType,
+            });
+          }
+
+          var installedName = callNames.single;
+          if (installedName == call0Name) return finish(0);
+          if (installedName == call1Name) return finish(1);
+          if (installedName == call2Name) return finish(2);
+        }
+
+        var install =
+            locals.find('_instance', 'hunkHelpers.installInstanceTearOff');
         return js.js.statement('''
             #install(#container, #getterName, #isIntercepted,
                      #requiredParameterCount, #optionalParameterDefaultValues,
                      #callNames, #funsOrNames, #funType, #applyIndex)''', {
-          "install": aliasForInstallInstanceTearOff,
+          "install": install,
           "container": container,
           "getterName": js.quoteName(method.tearOffName),
           // 'Truthy' values are ok for `isIntercepted`.
@@ -1402,20 +1498,8 @@
       }
     }
 
-    List<js.VariableInitialization> locals = [];
-    if (aliasForInstallStaticTearOff != null) {
-      locals.add(js.VariableInitialization(
-          js.VariableDeclaration(aliasForInstallStaticTearOff),
-          js.js('hunkHelpers.installStaticTearOff')));
-    }
-    if (aliasForInstallInstanceTearOff != null) {
-      locals.add(js.VariableInitialization(
-          js.VariableDeclaration(aliasForInstallInstanceTearOff),
-          js.js('hunkHelpers.installInstanceTearOff')));
-    }
     if (locals.isNotEmpty) {
-      inits.insert(
-          0, js.ExpressionStatement(js.VariableDeclarationList(locals)));
+      inits.insert(0, locals.toStatement());
     }
 
     return wrapPhase('installTearOffs', inits);
@@ -1471,11 +1555,11 @@
   js.Statement emitLazilyInitializedStatics(Fragment fragment) {
     List<StaticField> fields = fragment.staticLazilyInitializedFields;
     List<js.Statement> statements = [];
-    Map<String, js.Expression> locals = {};
+    LocalAliases locals = LocalAliases();
     for (StaticField field in fields) {
       assert(field.holder.isStaticStateHolder);
-      locals['_lazy'] ??= js.js('hunkHelpers.lazy');
-      statements.add(js.js.statement("_lazy(#, #, #, #);", [
+      statements.add(js.js.statement("#(#, #, #, #);", [
+        locals.find('_lazy', 'hunkHelpers.lazy'),
         field.holder.name,
         js.quoteName(field.name),
         js.quoteName(namer.deriveLazyInitializerName(field.name)),
@@ -1484,7 +1568,7 @@
     }
 
     if (locals.isNotEmpty) {
-      statements.insert(0, localAliasInitializations(locals));
+      statements.insert(0, locals.toStatement());
     }
 
     return wrapPhase('lazyInitializers', statements);
@@ -1760,6 +1844,27 @@
   }
 }
 
+class LocalAliases {
+  final Map<String, js.Expression> _locals = {};
+
+  bool get isEmpty => _locals.isEmpty;
+  bool get isNotEmpty => !isEmpty;
+
+  String find(String alias, String expression) {
+    _locals[alias] ??= js.js(expression);
+    return alias;
+  }
+
+  js.Statement toStatement() {
+    List<js.VariableInitialization> initializations = [];
+    _locals.forEach((local, value) {
+      initializations
+          .add(js.VariableInitialization(js.VariableDeclaration(local), value));
+    });
+    return js.ExpressionStatement(js.VariableDeclarationList(initializations));
+  }
+}
+
 /// Code to initialize holder with ancillary information.
 class HolderCode {
   final List<Holder> activeHolders;