| Index: pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
|
| 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 dbd8e3121903d65b9e8f428f6927d4ab149784d8..f87a10d45a18b231218841ed1d710b90730bc476 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
|
| @@ -297,7 +297,7 @@ function initializeDeferredHunk(hunk) {
|
| hunk(inherit, mixin, lazy, makeConstList, convertToFastObject, installTearOff,
|
| setFunctionNamesIfNecessary, updateHolder, updateTypes,
|
| setOrUpdateInterceptorsByTag, setOrUpdateLeafTags,
|
| - #embeddedGlobalsObject, #holdersList, #staticState);
|
| + #embeddedGlobalsObject, holders, #staticState);
|
| }
|
|
|
| // Returns the global with the given [name].
|
| @@ -313,11 +313,33 @@ function getGlobalFromName(name) {
|
| }
|
| }
|
|
|
| +if (#hasSoftDeferredClasses) {
|
| + // Loads the soft-deferred classes and initializes them.
|
| + // Updates the prototype of the given object.
|
| + function softDef(o) {
|
| + softDef = function(o) {}; // Replace ourselves.
|
| + #deferredGlobal[#softId](
|
| + holders, #embeddedGlobalsObject, #staticState, inherit, mixin,
|
| + installTearOff);
|
| + if (o != null) {
|
| + // TODO(29574): should we do something different for Firefox?
|
| + // If we recommend that the program triggers the load by itself before
|
| + // classes are needed, then this line should rarely be hit.
|
| + // Also, it is only hit at most once (per soft-deferred chunk).
|
| + o.__proto__ = o.constructor.prototype;
|
| + }
|
| + }
|
| +}
|
| +
|
| +if (#isTrackingAllocations) {
|
| + var allocations = #deferredGlobal['allocations'] = {};
|
| +}
|
| +
|
| // Creates the holders.
|
| #holders;
|
|
|
| // If the name is not set on the functions, do it now.
|
| -setFunctionNamesIfNecessary(#holdersList);
|
| +setFunctionNamesIfNecessary(holders);
|
|
|
| // TODO(floitsch): we should build this object as a literal.
|
| var #staticStateDeclaration = {};
|
| @@ -429,6 +451,28 @@ updateTypes(#types);
|
| #nativeSupport;
|
| }''';
|
|
|
| +/// Soft-deferred fragments are built similarly to the main fragment.
|
| +///
|
| +/// However, they don't contribute anything to global namespace, but just
|
| +/// initialize existing classes. For example, they update the inheritance
|
| +/// hierarchy, and add methods the prototypes.
|
| +const String softDeferredBoilerplate = '''
|
| +#deferredGlobal[#softId] =
|
| + function(holdersList, #embeddedGlobalsObject, #staticState, inherit, mixin,
|
| + installTearOff) {
|
| +
|
| +// Installs the holders as local variables.
|
| +#installHoldersAsLocals;
|
| +// Sets the prototypes of the new classes.
|
| +#prototypes;
|
| +// Sets aliases of methods (on the prototypes of classes).
|
| +#aliases;
|
| +// Installs the tear-offs of functions.
|
| +#tearOffs;
|
| +// Builds the inheritance structure.
|
| +#inheritance;
|
| +}''';
|
| +
|
| /**
|
| * This class builds a JavaScript tree for a given fragment.
|
| *
|
| @@ -464,7 +508,9 @@ class FragmentEmitter {
|
| Iterable<Holder> nonStaticStateHolders =
|
| program.holders.where((Holder holder) => !holder.isStaticStateHolder);
|
|
|
| - return js.js.statement(mainBoilerplate, {
|
| + String softDeferredId = "softDeferred${new Random().nextInt(0x7FFFFFFF)}";
|
| +
|
| + js.Statement mainCode = js.js.statement(mainBoilerplate, {
|
| 'directAccessTestExpression': js.js(directAccessTestExpression),
|
| 'typeNameProperty': js.string(ModelEmitter.typeNameProperty),
|
| 'cyclicThrow': backend.emitter
|
| @@ -477,9 +523,6 @@ class FragmentEmitter {
|
| generateEmbeddedGlobalAccess(INTERCEPTORS_BY_TAG),
|
| 'embeddedLeafTags': generateEmbeddedGlobalAccess(LEAF_TAGS),
|
| 'embeddedGlobalsObject': js.js("init"),
|
| - 'holdersList': new js.ArrayInitializer(nonStaticStateHolders
|
| - .map((holder) => js.js("#", holder.name))
|
| - .toList(growable: false)),
|
| 'staticStateDeclaration': new js.VariableDeclaration(
|
| namer.staticStateHolder,
|
| allowRename: false),
|
| @@ -490,6 +533,10 @@ class FragmentEmitter {
|
| 'stubName': js.string(namer.stubNameField),
|
| 'argumentCount': js.string(namer.requiredParameterField),
|
| 'defaultArgumentValues': js.string(namer.defaultValuesField),
|
| + 'deferredGlobal': ModelEmitter.deferredInitializersGlobal,
|
| + 'hasSoftDeferredClasses': program.hasSoftDeferredClasses,
|
| + 'softId': js.string(softDeferredId),
|
| + 'isTrackingAllocations': compiler.options.experimentalTrackAllocations,
|
| 'prototypes': emitPrototypes(fragment),
|
| 'inheritance': emitInheritance(fragment),
|
| 'aliases': emitInstanceMethodAliases(fragment),
|
| @@ -506,6 +553,36 @@ class FragmentEmitter {
|
| : new js.EmptyStatement(),
|
| 'invokeMain': fragment.invokeMain,
|
| });
|
| + if (program.hasSoftDeferredClasses) {
|
| + return new js.Block([
|
| + js.js.statement(softDeferredBoilerplate, {
|
| + 'deferredGlobal': ModelEmitter.deferredInitializersGlobal,
|
| + 'softId': js.string(softDeferredId),
|
| + // TODO(floitsch): don't just reference 'init'.
|
| + 'embeddedGlobalsObject': new js.Parameter('init'),
|
| + 'staticState': new js.Parameter(namer.staticStateHolder),
|
| + 'installHoldersAsLocals':
|
| + emitInstallHoldersAsLocals(nonStaticStateHolders),
|
| + 'prototypes': emitPrototypes(fragment, softDeferred: true),
|
| + 'aliases': emitInstanceMethodAliases(fragment, softDeferred: true),
|
| + 'tearOffs': emitInstallTearOffs(fragment, softDeferred: true),
|
| + 'inheritance': emitInheritance(fragment, softDeferred: true),
|
| + }),
|
| + mainCode
|
| + ]);
|
| + }
|
| + return mainCode;
|
| + }
|
| +
|
| + js.Statement emitInstallHoldersAsLocals(Iterable<Holder> holders) {
|
| + List<js.Statement> holderInits = <js.Statement>[];
|
| + int counter = 0;
|
| + for (Holder holder in holders) {
|
| + holderInits.add(new js.ExpressionStatement(new js.VariableInitialization(
|
| + new js.VariableDeclaration(holder.name, allowRename: false),
|
| + js.js("holdersList[#]", js.number(counter++)))));
|
| + }
|
| + return new js.Block(holderInits);
|
| }
|
|
|
| js.Expression emitDeferredFragment(DeferredFragment fragment,
|
| @@ -657,6 +734,14 @@ class FragmentEmitter {
|
| var parameters = <js.Name>[];
|
| var thisRef;
|
|
|
| + if (cls.isSoftDeferred) {
|
| + statements.add(js.js.statement('softDef(this)'));
|
| + } else if (compiler.options.experimentalTrackAllocations) {
|
| + String qualifiedName =
|
| + "${cls.element.library.canonicalUri}:${cls.element.name}";
|
| + statements.add(js.js.statement('allocations["$qualifiedName"] = true'));
|
| + }
|
| +
|
| // If there are many references to `this`, cache it in a local.
|
| if (cls.fields.length + (cls.hasRtiField ? 1 : 0) >= 4) {
|
| // TODO(29455): Fix js_ast printer and minifier to avoid conflicts between
|
| @@ -697,9 +782,10 @@ class FragmentEmitter {
|
| ///
|
| /// This section updates the prototype-property of all constructors in the
|
| /// global holders.
|
| - js.Statement emitPrototypes(Fragment fragment) {
|
| + js.Statement emitPrototypes(Fragment fragment, {softDeferred = false}) {
|
| List<js.Statement> assignments = fragment.libraries
|
| .expand((Library library) => library.classes)
|
| + .where((Class cls) => cls.isSoftDeferred == softDeferred)
|
| .map((Class cls) {
|
| var proto = js.js.statement(
|
| '#.prototype = #;', [classReference(cls), emitPrototype(cls)]);
|
| @@ -864,13 +950,14 @@ class FragmentEmitter {
|
| ///
|
| /// In this section prototype chains are updated and mixin functions are
|
| /// copied.
|
| - js.Statement emitInheritance(Fragment fragment) {
|
| + js.Statement emitInheritance(Fragment fragment, {bool softDeferred = false}) {
|
| List<js.Statement> inheritCalls = <js.Statement>[];
|
| List<js.Statement> mixinCalls = <js.Statement>[];
|
|
|
| Set<Class> classesInFragment = new Set<Class>();
|
| for (Library library in fragment.libraries) {
|
| - classesInFragment.addAll(library.classes);
|
| + classesInFragment.addAll(library.classes
|
| + .where(((Class cls) => cls.isSoftDeferred == softDeferred)));
|
| }
|
|
|
| Map<Class, List<Class>> subclasses = <Class, List<Class>>{};
|
| @@ -891,6 +978,7 @@ class FragmentEmitter {
|
|
|
| for (Library library in fragment.libraries) {
|
| for (Class cls in library.classes) {
|
| + if (cls.isSoftDeferred != softDeferred) continue;
|
| collect(cls);
|
|
|
| if (cls.isMixinApplication) {
|
| @@ -937,11 +1025,13 @@ class FragmentEmitter {
|
| ///
|
| /// This step consists of simply copying JavaScript functions to their
|
| /// aliased names so they point to the same function.
|
| - js.Statement emitInstanceMethodAliases(Fragment fragment) {
|
| + js.Statement emitInstanceMethodAliases(Fragment fragment,
|
| + {bool softDeferred = false}) {
|
| List<js.Statement> assignments = <js.Statement>[];
|
|
|
| for (Library library in fragment.libraries) {
|
| for (Class cls in library.classes) {
|
| + if (cls.isSoftDeferred != softDeferred) continue;
|
| for (InstanceMethod method in cls.methods) {
|
| if (method.aliasName != null) {
|
| assignments.add(js.js.statement('#.prototype.# = #.prototype.#', [
|
| @@ -1066,7 +1156,8 @@ class FragmentEmitter {
|
| }
|
|
|
| /// Emits the section that installs tear-off getters.
|
| - js.Statement emitInstallTearOffs(Fragment fragment) {
|
| + js.Statement emitInstallTearOffs(Fragment fragment,
|
| + {bool softDeferred = false}) {
|
| List<js.Statement> inits = <js.Statement>[];
|
| js.Expression temp;
|
|
|
| @@ -1082,6 +1173,7 @@ class FragmentEmitter {
|
| }
|
| }
|
| for (Class cls in library.classes) {
|
| + if (cls.isSoftDeferred != softDeferred) continue;
|
| var methods = cls.methods.where((m) => m.needsTearOff).toList();
|
| js.Expression container = js.js("#.prototype", classReference(cls));
|
| js.Expression reference = container;
|
|
|