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 55f9f290c18e6b14e4b93ec3457b5213a293f1f8..977c4841bd5c1dbd24659161dfd68c732a375e76 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,27 @@ 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); |
+ o.__proto__ = o.constructor.prototype; |
sra1
2017/05/02 20:40:13
This will make Firefox angry.
floitsch
2017/05/06 19:25:43
Yes. I added a comment.
|
+ } |
+} |
+ |
+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 +445,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,48 +502,80 @@ class FragmentEmitter { |
Iterable<Holder> nonStaticStateHolders = |
program.holders.where((Holder holder) => !holder.isStaticStateHolder); |
- return js.js.statement(mainBoilerplate, { |
- 'directAccessTestExpression': js.js(directAccessTestExpression), |
- 'typeNameProperty': js.string(ModelEmitter.typeNameProperty), |
- 'cyclicThrow': backend.emitter |
- .staticFunctionAccess(compiler.commonElements.cyclicThrowHelper), |
- 'operatorIsPrefix': js.string(namer.operatorIsPrefix), |
- 'tearOffCode': new js.Block(buildTearOffCode(compiler.options, |
- backend.emitter.emitter, backend.namer, compiler.commonElements)), |
- 'embeddedTypes': generateEmbeddedGlobalAccess(TYPES), |
- 'embeddedInterceptorTags': |
- 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), |
- 'staticState': js.js('#', namer.staticStateHolder), |
- 'constantHolderReference': buildConstantHolderReference(program), |
- 'holders': emitHolders(program.holders, fragment), |
- 'callName': js.string(namer.callNameField), |
- 'stubName': js.string(namer.stubNameField), |
- 'argumentCount': js.string(namer.requiredParameterField), |
- 'defaultArgumentValues': js.string(namer.defaultValuesField), |
- 'prototypes': emitPrototypes(fragment), |
- 'inheritance': emitInheritance(fragment), |
- 'aliases': emitInstanceMethodAliases(fragment), |
- 'tearOffs': emitInstallTearOffs(fragment), |
- 'constants': emitConstants(fragment), |
- 'staticNonFinalFields': emitStaticNonFinalFields(fragment), |
- 'lazyStatics': emitLazilyInitializedStatics(fragment), |
- 'embeddedGlobals': emitEmbeddedGlobals(program, deferredLoadHashes), |
- 'nativeSupport': program.needsNativeSupport |
- ? emitNativeSupport(fragment) |
- : new js.EmptyStatement(), |
- 'jsInteropSupport': backend.jsInteropAnalysis.enabledJsInterop |
- ? backend.jsInteropAnalysis.buildJsInteropBootstrap() |
+ String softDeferredId = "softDeferred${new Random().nextInt(0x7FFFFFFF)}"; |
+ |
+ return new js.Block([ |
+ program.hasSoftDeferredClasses |
+ ? 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), |
+ }) |
: new js.EmptyStatement(), |
- 'invokeMain': fragment.invokeMain, |
- }); |
+ js.js.statement(mainBoilerplate, { |
+ 'directAccessTestExpression': js.js(directAccessTestExpression), |
+ 'typeNameProperty': js.string(ModelEmitter.typeNameProperty), |
+ 'cyclicThrow': backend.emitter |
+ .staticFunctionAccess(compiler.commonElements.cyclicThrowHelper), |
+ 'operatorIsPrefix': js.string(namer.operatorIsPrefix), |
+ 'tearOffCode': new js.Block(buildTearOffCode(compiler.options, |
+ backend.emitter.emitter, backend.namer, compiler.commonElements)), |
+ 'embeddedTypes': generateEmbeddedGlobalAccess(TYPES), |
+ 'embeddedInterceptorTags': |
+ generateEmbeddedGlobalAccess(INTERCEPTORS_BY_TAG), |
+ 'embeddedLeafTags': generateEmbeddedGlobalAccess(LEAF_TAGS), |
+ 'embeddedGlobalsObject': js.js("init"), |
+ 'staticStateDeclaration': new js.VariableDeclaration( |
+ namer.staticStateHolder, |
+ allowRename: false), |
+ 'staticState': js.js('#', namer.staticStateHolder), |
+ 'constantHolderReference': buildConstantHolderReference(program), |
+ 'holders': emitHolders(program.holders, fragment), |
+ 'callName': js.string(namer.callNameField), |
+ '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.trackAllocations, |
+ 'prototypes': emitPrototypes(fragment), |
+ 'inheritance': emitInheritance(fragment), |
+ 'aliases': emitInstanceMethodAliases(fragment), |
+ 'tearOffs': emitInstallTearOffs(fragment), |
+ 'constants': emitConstants(fragment), |
+ 'staticNonFinalFields': emitStaticNonFinalFields(fragment), |
+ 'lazyStatics': emitLazilyInitializedStatics(fragment), |
+ 'embeddedGlobals': emitEmbeddedGlobals(program, deferredLoadHashes), |
+ 'nativeSupport': program.needsNativeSupport |
+ ? emitNativeSupport(fragment) |
+ : new js.EmptyStatement(), |
+ 'jsInteropSupport': backend.jsInteropAnalysis.enabledJsInterop |
+ ? backend.jsInteropAnalysis.buildJsInteropBootstrap() |
+ : new js.EmptyStatement(), |
+ 'invokeMain': fragment.invokeMain, |
+ }), |
+ ]); |
+ } |
+ |
+ 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 +727,14 @@ class FragmentEmitter { |
var parameters = <js.Name>[]; |
var thisRef; |
+ if (cls.isSoftDeferred) { |
+ statements.add(js.js.statement('softDef(this)')); |
+ } else if (compiler.options.trackAllocations) { |
+ 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 +775,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 +943,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 +971,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 +1018,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 +1149,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 +1166,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; |