| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of dart2js.js_emitter.startup_emitter.model_emitter; |
| |
| /// The fast startup emitter's goal is to minimize the amount of work that the |
| /// JavaScript engine has to do before it can start running user code. |
| /// |
| /// Whenever possible, the emitter uses object literals instead of updating |
| /// objects. |
| /// |
| /// Example: |
| /// |
| /// // Holders are initialized directly with the classes and static |
| /// // functions. |
| /// var A = { Point: function Point(x, y) { this.x = x; this.y = y }, |
| /// someStaticFunction: function someStaticFunction() { ... } }; |
| /// |
| /// // Class-behavior is emitted in a prototype object that is directly |
| /// // assigned: |
| /// A.Point.prototype = { distanceTo: function(other) { ... } }; |
| /// |
| /// // Inheritance is achieved by updating the prototype objects (hidden in |
| /// // a helper function): |
| /// A.Point.prototype.__proto__ = H.Object.prototype; |
| /// |
| /// The emitter doesn't try to be clever and emits everything beforehand. This |
| /// increases the output size, but improves performance. |
| /// |
| // The code relies on the fact that all Dart code is inside holders. As such |
| // we can use "global" names however we want. As long as we don't shadow |
| // JavaScript variables (like `Array`) we are free to chose whatever variable |
| // names we want. Furthermore, the minifier reduces the names of the variables. |
| const String mainBoilerplate = ''' |
| { |
| // Declare deferred-initializer global, which is used to keep track of the |
| // loaded fragments. |
| #deferredInitializer; |
| |
| (function() { |
| // Copies the own properties from [from] to [to]. |
| function copyProperties(from, to) { |
| var keys = Object.keys(from); |
| for (var i = 0; i < keys.length; i++) { |
| to[keys[i]] = from[keys[i]]; |
| } |
| } |
| |
| // Makes [cls] inherit from [sup]. |
| // On Chrome, Firefox and recent IEs this happens by updating the internal |
| // proto-property of the classes 'prototype' field. |
| // Older IEs use `Object.create` and copy over the properties. |
| function inherit(cls, sup) { |
| // TODO(floitsch): IE doesn't support changing the __proto__ property. There, |
| // we need to copy the properties instead. |
| cls.#typeNameProperty = cls.name; // Needed for RTI. |
| cls.prototype.constructor = cls; |
| cls.prototype[#operatorIsPrefix + cls.name] = cls; |
| cls.prototype.__proto__ = sup.prototype; |
| } |
| |
| // Mixes in the properties of [mixin] into [cls]. |
| function mixin(cls, mixin) { |
| copyProperties(mixin.prototype, cls.prototype); |
| } |
| |
| // Creates a lazy field. |
| // |
| // A lazy field has a storage entry, [name], which holds the value, and a |
| // getter ([getterName]) to access the field. If the field wasn't set before |
| // the first access, it is initialized with the [initializer]. |
| function lazy(holder, name, getterName, initializer) { |
| holder[name] = null; |
| holder[getterName] = function() { |
| holder[getterName] = function() { #cyclicThrow(name) }; |
| var result; |
| var sentinelInProgress = initializer; |
| try { |
| result = holder[name] = sentinelInProgress; |
| result = holder[name] = initializer(); |
| } finally { |
| // Use try-finally, not try-catch/throw as it destroys the stack |
| // trace. |
| if (result === sentinelInProgress) { |
| // The lazy static (holder[name]) might have been set to a different |
| // value. According to spec we still have to reset it to null, if |
| // the initialization failed. |
| holder[name] = null; |
| } |
| // TODO(floitsch): for performance reasons the function should probably |
| // be unique for each static. |
| holder[getterName] = function() { return this[name]; }; |
| } |
| return result; |
| }; |
| } |
| |
| // Given a list, marks it as constant. |
| // |
| // The runtime ensures that const-lists cannot be modified. |
| function makeConstList(list) { |
| // By assigning a function to the properties they become part of the |
| // hidden class. The actual values of the fields don't matter, since we |
| // only check if they exist. |
| list.immutable\$list = Array; |
| list.fixed\$length = Array; |
| return list; |
| } |
| |
| // TODO(floitsch): provide code for tear-offs. |
| |
| // Instead of setting the interceptor tags directly we use this update |
| // function. This makes it easier for deferred fragments to contribute to the |
| // embedded global. |
| function setOrUpdateInterceptorsByTag(newTags) { |
| var tags = #embeddedInterceptorTags; |
| if (!tags) { |
| #embeddedInterceptorTags = newTags; |
| return; |
| } |
| copyProperties(newTags, tags); |
| } |
| |
| // Instead of setting the leaf tags directly we use this update |
| // function. This makes it easier for deferred fragments to contribute to the |
| // embedded global. |
| function setOrUpdateLeafTags(newTags) { |
| var tags = #embeddedLeafTags; |
| if (!tags) { |
| #embeddedLeafTags = newTags; |
| return; |
| } |
| copyProperties(newTags, tags); |
| } |
| |
| // Updates the types embedded global. |
| function updateTypes(newTypes) { |
| var types = #embeddedTypes; |
| types.push.apply(types, newTypes); |
| } |
| |
| // Updates the given holder with the properties of the [newHolder]. |
| // This function is used when a deferred fragment is initialized. |
| function updateHolder(holder, newHolder) { |
| // TODO(floitsch): updating the prototype (instead of copying) is |
| // *horribly* inefficient in Firefox. There we should just copy the |
| // properties. |
| var oldPrototype = holder.__proto__; |
| newHolder.__proto__ = oldPrototype; |
| holder.__proto__ = newHolder; |
| return holder; |
| } |
| |
| // 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. |
| function initializeDeferredHunk(hunk) { |
| // TODO(floitsch): extend natives. |
| hunk(derive, mixin, lazy, makeConstList, installTearOff, |
| updateHolder, updateTypes, updateInterceptorsByTag, updateLeafTags, |
| #embeddedGlobalsObject, #holdersList, #currentIsolate); |
| } |
| |
| // Creates the holders. |
| #holders; |
| // Sets the prototypes of classes. |
| #prototypes; |
| // Sets aliases of methods (on the prototypes of classes). |
| #aliases; |
| // Installs the tear-offs of functions. |
| #tearOffs; |
| // Builds the inheritance structure. |
| #inheritance; |
| |
| // Emits the embedded globals. |
| #embeddedGlobals; |
| |
| // Sets up the native support. |
| // Native-support uses setOrUpdateInterceptorsByTag and setOrUpdateLeafTags. |
| #nativeSupport; |
| |
| // Instantiates all constants. |
| #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; |
| |
| // Invokes main (making sure that it records the 'current-script' value). |
| #invokeMain; |
| })(); |
| }'''; |
| |
| /// 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. |
| const String deferredBoilerplate = ''' |
| { |
| #deferredInitializers.current = |
| function(derive, mixin, lazy, makeConstList, installTearOff, |
| updateHolder, updateTypes, |
| setOrUpdateInterceptorsByTag, setOrUpdateLeafTags, |
| #embeddedGlobalsObject, holdersList, #currentIsolate) { |
| |
| // Builds the holders. They only contain the data for new holders. |
| #holders; |
| // 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; |
| // Sets aliases of methods (on the prototypes of classes). |
| #aliases; |
| // Installs the tear-offs of functions. |
| #tearOffs; |
| // Builds the inheritance structure. |
| #inheritance; |
| |
| updateTypes(#types); |
| |
| // Native-support uses setOrUpdateInterceptorsByTag and setOrUpdateLeafTags. |
| #nativeSupport; |
| |
| // 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; |
| }; |
| // TODO(floitsch): this last line should be outside the AST, since it |
| // requires to know the hash of the part of the code above this comment. |
| #deferredInitializers[#hash] = #deferredInitializers.current; |
| }'''; |
| |
| /** |
| * This class builds a JavaScript tree for a given fragment. |
| * |
| * A fragment is generally written into a separate file so that it can be |
| * loaded dynamically when a deferred library is loaded. |
| * |
| * This class is stateless and can be reused for different fragments. |
| */ |
| class FragmentEmitter { |
| final Compiler compiler; |
| final Namer namer; |
| final JavaScriptBackend backend; |
| final ConstantEmitter constantEmitter; |
| final ModelEmitter modelEmitter; |
| |
| FragmentEmitter(this.compiler, this.namer, this.backend, this.constantEmitter, |
| this.modelEmitter); |
| |
| js.Expression generateEmbeddedGlobalAccess(String global) => |
| modelEmitter.generateEmbeddedGlobalAccess(global); |
| |
| js.Expression generateConstantReference(ConstantValue value) => |
| modelEmitter.generateConstantReference(value); |
| |
| js.Statement emitMainFragment(Program program) { |
| MainFragment fragment = program.fragments.first; |
| throw new UnimplementedError('emitMain'); |
| } |
| |
| js.Statement emitDeferredFragment(DeferredFragment fragment, |
| js.Expression deferredTypes, |
| List<Holder> holders) { |
| throw new UnimplementedError('emitDeferred'); |
| } |
| } |