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;