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; |