Support sharing function signatures in deferred parts for fast startup

Change-Id: I2ee08817241512269fe04d7fb0e3367df847d37a
Reviewed-on: https://dart-review.googlesource.com/56940
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
This commit is contained in:
Johnni Winther 2018-05-30 13:25:48 +00:00 committed by commit-bot@chromium.org
parent ae9f5d2a1b
commit 9d9eff44c9
9 changed files with 157 additions and 32 deletions

View file

@ -115,8 +115,7 @@ class TokenCounter extends BaseVisitor {
}
}
// TODO(28763): Remove `<dynamic>` when issue 28763 is fixed.
void countTokens(Node node) => node.accept<dynamic>(this);
void countTokens(Node node) => node.accept(this);
}
abstract class ReferenceCountedAstNode implements Node {

View file

@ -1646,6 +1646,10 @@ class Namer {
String get futureOrTypeTag => r'type';
// The name of the variable used to offset function signatures in deferred
// parts with the fast-startup emitter.
String get typesOffsetName => r'typesOffset';
Map<FunctionType, jsAst.Name> functionTypeNameMap =
new HashMap<FunctionType, jsAst.Name>();

View file

@ -65,6 +65,8 @@ class _BoundMetadataEntry extends _MetadataEntry {
}
int compareTo(covariant _MetadataEntry other) => other._rc - this._rc;
String toString() => '_BoundMetadataEntry($hashCode,rc=$_rc,_value=$_value)';
}
class _MetadataList extends jsAst.DeferredExpression {

View file

@ -149,6 +149,7 @@ class RuntimeTypeGenerator {
// potentially a subtype of a checked function. Currently we eagerly
// generate a function type index or signature for all callable classes.
jsAst.Expression functionTypeIndex;
bool isDeferred = false;
if (!type.containsTypeVariables) {
// TODO(sigmund): use output unit of [method] when the classes mentioned
// in [type] aren't in the main output unit. (Issue #31032)
@ -156,6 +157,12 @@ class RuntimeTypeGenerator {
if (_outputUnitVisitor.isTypeContainedIn(type, mainOutputUnit)) {
functionTypeIndex =
emitterTask.metadataCollector.reifyType(type, mainOutputUnit);
} else if (!storeFunctionTypeInMetadata) {
// TODO(johnniwinther): Support sharing deferred signatures with the
// full emitter.
isDeferred = true;
functionTypeIndex = emitterTask.metadataCollector
.reifyType(type, _outputUnitData.outputUnitForMember(method));
}
}
if (storeFunctionTypeInMetadata && functionTypeIndex != null) {
@ -168,7 +175,18 @@ class RuntimeTypeGenerator {
// The signature function isn't live.
return;
}
encoding = functionTypeIndex ?? encoding;
if (functionTypeIndex != null) {
if (isDeferred) {
// The function type index must be offset by the number of types
// already loaded.
encoding = new jsAst.Binary(
'+',
new jsAst.VariableUse(_namer.typesOffsetName),
functionTypeIndex);
} else {
encoding = functionTypeIndex;
}
}
} else if (encoding == null) {
// Generate the signature on the fly. This is only supported for
// Dart 1.

View file

@ -264,11 +264,12 @@ function setOrUpdateLeafTags(newTags) {
// Updates the types embedded global.
function updateTypes(newTypes) {
var types = #embeddedTypes;
// This relies on the fact that types are added *after* the tear-offs have
// been installed. The tear-off function uses the types-length to figure
// out at which offset its types are located. If the types were added earlier
// the offset would be wrong.
var length = types.length;
// The tear-off function uses another 'typesOffset' value cached in
// [initializeDeferredHunk] so [updateTypes] can be called either before of
// after the tearoffs have been installed.
types.push.apply(types, newTypes);
return length;
}
// Updates the given holder with the properties of the [newHolder].
@ -401,7 +402,9 @@ const String directAccessTestExpression = r'''
/// However, at specific moments they need to contribute their data.
/// For example, once the holders have been created, they are included into
/// the main holders.
const String deferredBoilerplate = '''
///
/// This template is used for Dart 1.
const String deferredBoilerplateDart1 = '''
function(inherit, mixin, lazy, makeConstList, convertToFastObject,
installTearOff, setFunctionNamesIfNecessary, updateHolder, updateTypes,
setOrUpdateInterceptorsByTag, setOrUpdateLeafTags,
@ -443,6 +446,59 @@ updateTypes(#types);
}''';
/// Soft-deferred fragments are built similarly to the main fragment.
/// Deferred fragments (aka 'hunks') are built similarly to the main fragment.
///
/// However, at specific moments they need to contribute their data.
/// For example, once the holders have been created, they are included into
/// the main holders.
///
/// This template is used for Dart 2.
const String deferredBoilerplateDart2 = '''
function(inherit, mixin, lazy, makeConstList, convertToFastObject,
installTearOff, setFunctionNamesIfNecessary, updateHolder, updateTypes,
setOrUpdateInterceptorsByTag, setOrUpdateLeafTags,
#embeddedGlobalsObject, holdersList, #staticState) {
// Builds the holders. They only contain the data for new holders.
#holders;
// If the name is not set on the functions, do it now.
setFunctionNamesIfNecessary(#deferredHoldersList);
// Updates the holders of the main-fragment. Uses the provided holdersList to
// access the main holders.
// The local holders are replaced by the combined holders. This is necessary
// for the inheritance setup below.
#updateHolders;
// Sets the prototypes of the new classes.
#prototypes;
// Add signature function types and compute the types offset in `init.types`.
// These can only refer to regular classes and in Dart 2 only closures have
// function types so the `typesOffset` has been safely computed before it's
// referred in the signatures of the `closures` below.
var #typesOffset = updateTypes(#types);
#closures;
// Sets aliases of methods (on the prototypes of classes).
#aliases;
// Installs the tear-offs of functions.
#tearOffs;
// Builds the inheritance structure.
#inheritance;
// Instantiates all constants of this deferred fragment.
// Note that the constant-holder has been updated earlier and storing the
// constant values in the constant-holder makes them available globally.
#constants;
// Initializes the static non-final fields (with their constant values).
#staticNonFinalFields;
// Creates lazy getters for statics that must run initializers on first access.
#lazyStatics;
// Native-support uses setOrUpdateInterceptorsByTag and setOrUpdateLeafTags.
#nativeSupport;
}''';
///
/// However, they don't contribute anything to global namespace, but just
/// initialize existing classes. For example, they update the inheritance
@ -589,27 +645,51 @@ class FragmentEmitter {
'#holder = updateHolder(holdersList[#index], #holder)',
{'index': js.number(i), 'holder': new js.VariableUse(holder.name)}));
}
// TODO(floitsch): don't just reference 'init'.
return js.js(deferredBoilerplate, {
'embeddedGlobalsObject': new js.Parameter('init'),
'staticState': new js.Parameter(namer.staticStateHolder),
'holders': emitHolders(holders, fragment),
'deferredHoldersList': new js.ArrayInitializer(nonStaticStateHolders
.map((holder) => js.js("#", holder.name))
.toList(growable: false)),
'updateHolders': new js.Block(updateHolderAssignments),
'prototypes': emitPrototypes(fragment),
'inheritance': emitInheritance(fragment),
'aliases': emitInstanceMethodAliases(fragment),
'tearOffs': emitInstallTearOffs(fragment),
'constants': emitConstants(fragment),
'staticNonFinalFields': emitStaticNonFinalFields(fragment),
'lazyStatics': emitLazilyInitializedStatics(fragment),
'types': deferredTypes,
// TODO(floitsch): only call emitNativeSupport if we need native.
'nativeSupport': emitNativeSupport(fragment),
});
if (compiler.options.strongMode) {
// TODO(floitsch): don't just reference 'init'.
return js.js(deferredBoilerplateDart2, {
'embeddedGlobalsObject': new js.Parameter('init'),
'staticState': new js.Parameter(namer.staticStateHolder),
'holders': emitHolders(holders, fragment),
'deferredHoldersList': new js.ArrayInitializer(nonStaticStateHolders
.map((holder) => js.js("#", holder.name))
.toList(growable: false)),
'updateHolders': new js.Block(updateHolderAssignments),
'prototypes': emitPrototypes(fragment, includeClosures: false),
'closures': emitPrototypes(fragment, includeClosures: true),
'inheritance': emitInheritance(fragment),
'aliases': emitInstanceMethodAliases(fragment),
'tearOffs': emitInstallTearOffs(fragment),
'constants': emitConstants(fragment),
'staticNonFinalFields': emitStaticNonFinalFields(fragment),
'lazyStatics': emitLazilyInitializedStatics(fragment),
'types': deferredTypes,
// TODO(floitsch): only call emitNativeSupport if we need native.
'nativeSupport': emitNativeSupport(fragment),
'typesOffset': namer.typesOffsetName,
});
} else {
// TODO(floitsch): don't just reference 'init'.
return js.js(deferredBoilerplateDart1, {
'embeddedGlobalsObject': new js.Parameter('init'),
'staticState': new js.Parameter(namer.staticStateHolder),
'holders': emitHolders(holders, fragment),
'deferredHoldersList': new js.ArrayInitializer(nonStaticStateHolders
.map((holder) => js.js("#", holder.name))
.toList(growable: false)),
'updateHolders': new js.Block(updateHolderAssignments),
'prototypes': emitPrototypes(fragment),
'inheritance': emitInheritance(fragment),
'aliases': emitInstanceMethodAliases(fragment),
'tearOffs': emitInstallTearOffs(fragment),
'constants': emitConstants(fragment),
'staticNonFinalFields': emitStaticNonFinalFields(fragment),
'lazyStatics': emitLazilyInitializedStatics(fragment),
'types': deferredTypes,
// TODO(floitsch): only call emitNativeSupport if we need native.
'nativeSupport': emitNativeSupport(fragment),
});
}
}
/// Emits all holders, except for the static-state holder.
@ -766,11 +846,25 @@ class FragmentEmitter {
///
/// This section updates the prototype-property of all constructors in the
/// global holders.
js.Statement emitPrototypes(Fragment fragment, {softDeferred = false}) {
///
/// [softDeferred] determine whether prototypes for soft deferred classes are
/// generated.
///
/// If [includeClosures] is `true` only prototypes for closure classes are
/// generated, if [includeClosures] is `false` only prototypes for non-closure
/// classes are generated. Otherwise prototypes for all classes are generated.
js.Statement emitPrototypes(Fragment fragment,
{bool softDeferred = false, bool includeClosures}) {
List<js.Statement> assignments = fragment.libraries
.expand((Library library) => library.classes)
.where((Class cls) => cls.isSoftDeferred == softDeferred)
.map((Class cls) {
.where((Class cls) {
if (includeClosures != null) {
if (cls.element.isClosure != includeClosures) {
return false;
}
}
return cls.isSoftDeferred == softDeferred;
}).map((Class cls) {
var proto = js.js.statement(
'#.prototype = #;', [classReference(cls), emitPrototype(cls)]);
ClassEntity element = cls.element;

View file

@ -14,9 +14,11 @@ main() async {
Expect.isFalse(lib1.method1() is String Function(String));
Expect.isTrue(lib1.method5 is Object Function(Null, String, int));
Expect.isFalse(lib1.method5 is Object Function(Null, int, String));
Expect.isTrue(lib1.test5(lib1.method5));
await lib2.loadLibrary();
Expect.isFalse(lib2.method2() is int Function(int));
Expect.isTrue(lib2.method2() is String Function(String));
Expect.isFalse(lib2.method6 is Object Function(Null, String, int));
Expect.isTrue(lib2.method6 is Object Function(Null, int, String));
Expect.isTrue(lib2.test6(lib2.method6));
}

View file

@ -14,9 +14,11 @@ main() async {
Expect.isTrue(lib2.method2() is String Function(String));
Expect.isFalse(lib2.method6 is Object Function(Null, String, int));
Expect.isTrue(lib2.method6 is Object Function(Null, int, String));
Expect.isTrue(lib2.test6(lib2.method6));
await lib1.loadLibrary();
Expect.isTrue(lib1.method1() is int Function(int));
Expect.isFalse(lib1.method1() is String Function(String));
Expect.isTrue(lib1.method5 is Object Function(Null, String, int));
Expect.isFalse(lib1.method5 is Object Function(Null, int, String));
Expect.isTrue(lib1.test5(lib1.method5));
}

View file

@ -15,3 +15,5 @@ method3() {
test3(o) => o is Class1 Function(Class1);
method5(Class1 c, String s, int i) {}
test5(o) => o is Function(Class1, String, int);

View file

@ -15,3 +15,5 @@ method4() {
test4(o) => o is Class2 Function(Class2, Class2);
method6(Class2 c, int i, String s) {}
test6(o) => o is Function(Class2, int, String);