blob: 390ecd684ea42eb5ace5a8127e5759c023f5294b [file] [log] [blame]
// 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');
}
}