blob: b99843cd258e790c13a7f21e91da6c0bb3ce35c8 [file] [log] [blame]
// Copyright (c) 2014, 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;
class OldEmitter implements Emitter {
final Compiler compiler;
final CodeEmitterTask task;
final ContainerBuilder containerBuilder = new ContainerBuilder();
final ClassEmitter classEmitter = new ClassEmitter();
final NsmEmitter nsmEmitter = new NsmEmitter();
TypeTestEmitter get typeTestEmitter => task.typeTestEmitter;
final InterceptorEmitter interceptorEmitter = new InterceptorEmitter();
final MetadataEmitter metadataEmitter = new MetadataEmitter();
final Set<ConstantValue> cachedEmittedConstants;
final CodeBuffer cachedEmittedConstantsBuffer = new CodeBuffer();
final Map<Element, ClassBuilder> cachedClassBuilders;
final Set<Element> cachedElements;
bool needsDefineClass = false;
bool needsMixinSupport = false;
bool needsLazyInitializer = false;
final Namer namer;
ConstantEmitter constantEmitter;
NativeEmitter get nativeEmitter => task.nativeEmitter;
// The full code that is written to each hunk part-file.
Map<OutputUnit, CodeBuffer> outputBuffers = new Map<OutputUnit, CodeBuffer>();
final CodeBuffer deferredConstants = new CodeBuffer();
/** Shorter access to [isolatePropertiesName]. Both here in the code, as
well as in the generated code. */
String isolateProperties;
String classesCollector;
Set<ClassElement> get neededClasses => task.neededClasses;
Map<OutputUnit, List<ClassElement>> get outputClassLists
=> task.outputClassLists;
Map<OutputUnit, List<ConstantValue>> get outputConstantLists
=> task.outputConstantLists;
List<ClassElement> get nativeClasses => task.nativeClasses;
final Map<String, String> mangledFieldNames = <String, String>{};
final Map<String, String> mangledGlobalFieldNames = <String, String>{};
final Set<String> recordedMangledNames = new Set<String>();
final Map<ClassElement, Map<String, jsAst.Expression>> additionalProperties =
new Map<ClassElement, Map<String, jsAst.Expression>>();
List<TypedefElement> get typedefsNeededForReflection =>
task.typedefsNeededForReflection;
JavaScriptBackend get backend => compiler.backend;
TypeVariableHandler get typeVariableHandler => backend.typeVariableHandler;
String get _ => space;
String get space => compiler.enableMinification ? "" : " ";
String get n => compiler.enableMinification ? "" : "\n";
String get N => compiler.enableMinification ? "\n" : ";\n";
CodeBuffer getBuffer(OutputUnit outputUnit) {
return outputBuffers.putIfAbsent(outputUnit, () => new CodeBuffer());
}
CodeBuffer get mainBuffer {
return getBuffer(compiler.deferredLoadTask.mainOutputUnit);
}
/**
* List of expressions and statements that will be included in the
* precompiled function.
*
* To save space, dart2js normally generates constructors and accessors
* dynamically. This doesn't work in CSP mode, and may impact startup time
* negatively. So dart2js will emit these functions to a separate file that
* can be optionally included to support CSP mode or for faster startup.
*/
Map<OutputUnit, List<jsAst.Node>> _cspPrecompiledFunctions =
new Map<OutputUnit, List<jsAst.Node>>();
Map<OutputUnit, List<jsAst.Expression>> _cspPrecompiledConstructorNames =
new Map<OutputUnit, List<jsAst.Expression>>();
/**
* Accumulate properties for classes and libraries, describing their
* static/top-level members.
* Later, these members are emitted when the class or library is emitted.
*
* See [getElementDescriptor].
*/
// TODO(ahe): Generate statics with their class, and store only libraries in
// this map.
final Map<Element, ClassBuilder> elementDescriptors =
new Map<Element, ClassBuilder>();
final bool generateSourceMap;
OldEmitter(Compiler compiler, Namer namer, this.generateSourceMap, this.task)
: this.compiler = compiler,
this.namer = namer,
cachedEmittedConstants = compiler.cacheStrategy.newSet(),
cachedClassBuilders = compiler.cacheStrategy.newMap(),
cachedElements = compiler.cacheStrategy.newSet() {
constantEmitter =
new ConstantEmitter(compiler, namer, makeConstantListTemplate);
containerBuilder.emitter = this;
classEmitter.emitter = this;
nsmEmitter.emitter = this;
interceptorEmitter.emitter = this;
metadataEmitter.emitter = this;
}
List<jsAst.Node> cspPrecompiledFunctionFor(OutputUnit outputUnit) {
return _cspPrecompiledFunctions.putIfAbsent(
outputUnit,
() => new List<jsAst.Node>());
}
List<jsAst.Expression> cspPrecompiledConstructorNamesFor(
OutputUnit outputUnit) {
return _cspPrecompiledConstructorNames.putIfAbsent(
outputUnit,
() => new List<jsAst.Expression>());
}
/// Erases the precompiled information for csp mode for all output units.
/// Used by the incremental compiler.
void clearCspPrecompiledNodes() {
_cspPrecompiledFunctions.clear();
_cspPrecompiledConstructorNames.clear();
}
void addComment(String comment, CodeBuffer buffer) {
buffer.write(jsAst.prettyPrint(js.comment(comment), compiler));
}
jsAst.Expression constantReference(ConstantValue value) {
return constantEmitter.reference(value);
}
jsAst.Expression constantInitializerExpression(ConstantValue value) {
return constantEmitter.initializationExpression(value);
}
String get name => 'CodeEmitter';
String get currentGenerateAccessorName
=> '${namer.currentIsolate}.\$generateAccessor';
String get generateAccessorHolder
=> '$isolatePropertiesName.\$generateAccessor';
String get finishClassesProperty
=> r'$finishClasses';
String get finishClassesName
=> '${namer.isolateName}.$finishClassesProperty';
String get finishIsolateConstructorName
=> '${namer.isolateName}.\$finishIsolateConstructor';
String get isolatePropertiesName
=> '${namer.isolateName}.${namer.isolatePropertiesName}';
String get lazyInitializerName
=> '${namer.isolateName}.\$lazy';
String get initName => 'init';
String get makeConstListProperty
=> namer.getMappedInstanceName('makeConstantList');
/// For deferred loading we communicate the initializers via this global var.
final String deferredInitializers = r"$dart_deferred_initializers";
/// All the global state can be passed around with this variable.
String get globalsHolder => namer.getMappedGlobalName("globalsHolder");
jsAst.Expression generateEmbeddedGlobalAccess(String global) {
return js(generateEmbeddedGlobalAccessString(global));
}
String generateEmbeddedGlobalAccessString(String global) {
// TODO(floitsch): don't use 'init' as global embedder storage.
return '$initName.$global';
}
jsAst.FunctionDeclaration get generateAccessorFunction {
const RANGE1_SIZE = RANGE1_LAST - RANGE1_FIRST + 1;
const RANGE2_SIZE = RANGE2_LAST - RANGE2_FIRST + 1;
const RANGE1_ADJUST = - (FIRST_FIELD_CODE - RANGE1_FIRST);
const RANGE2_ADJUST = - (FIRST_FIELD_CODE + RANGE1_SIZE - RANGE2_FIRST);
const RANGE3_ADJUST =
- (FIRST_FIELD_CODE + RANGE1_SIZE + RANGE2_SIZE - RANGE3_FIRST);
String receiverParamName = compiler.enableMinification ? "r" : "receiver";
String valueParamName = compiler.enableMinification ? "v" : "value";
String reflectableField = namer.reflectableField;
return js.statement('''
function generateAccessor(fieldDescriptor, accessors, cls) {
var fieldInformation = fieldDescriptor.split("-");
var field = fieldInformation[0];
var len = field.length;
var code = field.charCodeAt(len - 1);
var reflectable;
if (fieldInformation.length > 1) reflectable = true;
else reflectable = false;
code = ((code >= $RANGE1_FIRST) && (code <= $RANGE1_LAST))
? code - $RANGE1_ADJUST
: ((code >= $RANGE2_FIRST) && (code <= $RANGE2_LAST))
? code - $RANGE2_ADJUST
: ((code >= $RANGE3_FIRST) && (code <= $RANGE3_LAST))
? code - $RANGE3_ADJUST
: $NO_FIELD_CODE;
if (code) { // needsAccessor
var getterCode = code & 3;
var setterCode = code >> 2;
var accessorName = field = field.substring(0, len - 1);
var divider = field.indexOf(":");
if (divider > 0) { // Colon never in first position.
accessorName = field.substring(0, divider);
field = field.substring(divider + 1);
}
if (getterCode) { // needsGetter
var args = (getterCode & 2) ? "$receiverParamName" : "";
var receiver = (getterCode & 1) ? "this" : "$receiverParamName";
var body = "return " + receiver + "." + field;
var property =
cls + ".prototype.${namer.getterPrefix}" + accessorName + "=";
var fn = "function(" + args + "){" + body + "}";
if (reflectable)
accessors.push(property + "\$reflectable(" + fn + ");\\n");
else
accessors.push(property + fn + ";\\n");
}
if (setterCode) { // needsSetter
var args = (setterCode & 2)
? "$receiverParamName,${_}$valueParamName"
: "$valueParamName";
var receiver = (setterCode & 1) ? "this" : "$receiverParamName";
var body = receiver + "." + field + "$_=$_$valueParamName";
var property =
cls + ".prototype.${namer.setterPrefix}" + accessorName + "=";
var fn = "function(" + args + "){" + body + "}";
if (reflectable)
accessors.push(property + "\$reflectable(" + fn + ");\\n");
else
accessors.push(property + fn + ";\\n");
}
}
return field;
}''');
}
List get defineClassFunction {
// First the class name, then the field names in an array and the members
// (inside an Object literal).
// The caller can also pass in the constructor as a function if needed.
//
// Example:
// defineClass("A", ["x", "y"], {
// foo$1: function(y) {
// print(this.x + y);
// },
// bar$2: function(t, v) {
// this.x = t - v;
// },
// });
var defineClass = js('''function(name, cls, fields) {
var accessors = [];
var str = "function " + cls + "(";
var body = "";
for (var i = 0; i < fields.length; i++) {
if(i != 0) str += ", ";
var field = generateAccessor(fields[i], accessors, cls);
var parameter = "parameter_" + field;
str += parameter;
body += ("this." + field + " = " + parameter + ";\\n");
}
str += ") {\\n" + body + "}\\n";
str += cls + ".builtin\$cls=\\"" + name + "\\";\\n";
str += "\$desc=\$collectedClasses." + cls + ";\\n";
str += "if(\$desc instanceof Array) \$desc = \$desc[1];\\n";
str += cls + ".prototype = \$desc;\\n";
if (typeof defineClass.name != "string") {
str += cls + ".name=\\"" + cls + "\\";\\n";
}
str += accessors.join("");
return str;
}''');
// Declare a function called "generateAccessor". This is used in
// defineClassFunction (it's a local declaration in init()).
return [
generateAccessorFunction,
js('$generateAccessorHolder = generateAccessor'),
new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration('defineClass'), defineClass) ];
}
/** Needs defineClass to be defined. */
jsAst.Expression buildInheritFrom() {
return js(r'''
var inheritFrom = function() {
function tmp() {}
function tmp() {}
var hasOwnProperty = Object.prototype.hasOwnProperty;
return function (constructor, superConstructor) {
tmp.prototype = superConstructor.prototype;
var object = new tmp();
var properties = constructor.prototype;
for (var member in properties) {
if (hasOwnProperty.call(properties, member)) {
object[member] = properties[member];
}
}
object.constructor = constructor;
constructor.prototype = object;
return object;
};
}()
''');
}
/// Code that needs to be run before first invocation of
/// [finishClassesFunction], but should only be run once.
jsAst.Expression get initFinishClasses {
jsAst.Expression allClassesAccess =
generateEmbeddedGlobalAccess(embeddedNames.ALL_CLASSES);
jsAst.Expression interceptorsByTagAccess =
generateEmbeddedGlobalAccess(embeddedNames.INTERCEPTORS_BY_TAG);
jsAst.Expression leafTagsAccess =
generateEmbeddedGlobalAccess(embeddedNames.LEAF_TAGS);
jsAst.Expression finishedClassesAccess =
generateEmbeddedGlobalAccess(embeddedNames.FINISHED_CLASSES);
return js('''
(function(){
# = Object.create(null); // embedded allClasses.
# = Object.create(null); // embedded interceptorsByTag.
# = Object.create(null); // embedded leafTags.
# = Object.create(null); // embedded finishedClasses
})()
''', [allClassesAccess,
interceptorsByTagAccess,
leafTagsAccess,
finishedClassesAccess]);
}
jsAst.Fun get finishClassesFunction {
// Class descriptions are collected in a JS object.
// 'finishClasses' takes all collected descriptions and sets up
// the prototype.
// Once set up, the constructors prototype field satisfy:
// - it contains all (local) members.
// - its internal prototype (__proto__) points to the superclass'
// prototype field.
// - the prototype's constructor field points to the JavaScript
// constructor.
// For engines where we have access to the '__proto__' we can manipulate
// the object literal directly. For other engines we have to create a new
// object and copy over the members.
String reflectableField = namer.reflectableField;
jsAst.Expression allClassesAccess =
generateEmbeddedGlobalAccess(embeddedNames.ALL_CLASSES);
jsAst.Expression metadataAccess =
generateEmbeddedGlobalAccess(embeddedNames.METADATA);
return js('''
function(collectedClasses, isolateProperties, existingIsolateProperties) {
var pendingClasses = Object.create(null);
var allClasses = #allClasses;
var constructors;
if (#debugFastObjects)
print("Number of classes: " +
Object.getOwnPropertyNames(\$\$).length);
var hasOwnProperty = Object.prototype.hasOwnProperty;
if (typeof dart_precompiled == "function") {
constructors = dart_precompiled(collectedClasses);
} else {
var combinedConstructorFunction =
"function \$reflectable(fn){fn.$reflectableField=1;return fn};\\n"+
"var \$desc;\\n";
var constructorsList = [];
}
for (var cls in collectedClasses) {
var desc = collectedClasses[cls];
if (desc instanceof Array) desc = desc[1];
/* The 'fields' are either a constructor function or a
* string encoding fields, constructor and superclass. Gets the
* superclass and fields in the format
* '[name/]Super;field1,field2'
* from the CLASS_DESCRIPTOR_PROPERTY property on the descriptor.
* The 'name/' is optional and contains the name that should be used
* when printing the runtime type string. It is used, for example,
* to print the runtime type JSInt as 'int'.
*/
var classData = desc["${namer.classDescriptorProperty}"],
supr, name = cls, fields = classData;
if (#hasRetainedMetadata)
if (typeof classData == "object" &&
classData instanceof Array) {
classData = fields = classData[0];
}
if (typeof classData == "string") {
var split = classData.split("/");
if (split.length == 2) {
name = split[0];
fields = split[1];
}
}
var s = fields.split(";");
fields = s[1] == "" ? [] : s[1].split(",");
supr = s[0];
split = supr.split(":");
if (split.length == 2) {
supr = split[0];
var functionSignature = split[1];
if (functionSignature)
desc.\$signature = (function(s) {
return function(){ return #metadata[s]; };
})(functionSignature);
}
if (#needsMixinSupport)
if (supr && supr.indexOf("+") > 0) {
s = supr.split("+");
supr = s[0];
var mixin = collectedClasses[s[1]];
if (mixin instanceof Array) mixin = mixin[1];
for (var d in mixin) {
if (hasOwnProperty.call(mixin, d) &&
!hasOwnProperty.call(desc, d))
desc[d] = mixin[d];
}
}
if (typeof dart_precompiled != "function") {
combinedConstructorFunction += defineClass(name, cls, fields);
constructorsList.push(cls);
}
if (supr) pendingClasses[cls] = supr;
}
if (typeof dart_precompiled != "function") {
combinedConstructorFunction +=
"return [\\n " + constructorsList.join(",\\n ") + "\\n]";
var constructors =
new Function("\$collectedClasses", combinedConstructorFunction)
(collectedClasses);
combinedConstructorFunction = null;
}
for (var i = 0; i < constructors.length; i++) {
var constructor = constructors[i];
var cls = constructor.name;
var desc = collectedClasses[cls];
var globalObject = isolateProperties;
if (desc instanceof Array) {
globalObject = desc[0] || isolateProperties;
desc = desc[1];
}
if (#isTreeShakingDisabled)
constructor["${namer.metadataField}"] = desc;
allClasses[cls] = constructor;
globalObject[cls] = constructor;
}
constructors = null;
#finishClassFunction;
#trivialNsmHandlers;
for (var cls in pendingClasses) finishClass(cls);
}''', { 'allClasses': allClassesAccess,
'debugFastObjects': DEBUG_FAST_OBJECTS,
'hasRetainedMetadata': backend.hasRetainedMetadata,
'metadata': metadataAccess,
'needsMixinSupport': needsMixinSupport,
'isTreeShakingDisabled': backend.isTreeShakingDisabled,
'finishClassFunction': buildFinishClass(),
'trivialNsmHandlers': nsmEmitter.buildTrivialNsmHandlers()});
}
jsAst.Node optional(bool condition, jsAst.Node node) {
return condition ? node : new jsAst.EmptyStatement();
}
jsAst.Statement buildFinishClass() {
String specProperty = '"${namer.nativeSpecProperty}"'; // "%"
jsAst.Expression finishedClassesAccess =
generateEmbeddedGlobalAccess(embeddedNames.FINISHED_CLASSES);
jsAst.Expression interceptorsByTagAccess =
generateEmbeddedGlobalAccess(embeddedNames.INTERCEPTORS_BY_TAG);
jsAst.Expression leafTagsAccess =
generateEmbeddedGlobalAccess(embeddedNames.LEAF_TAGS);
return js.statement('''
{
var finishedClasses = #; // finishedClassesAccess.
function finishClass(cls) {
if (finishedClasses[cls]) return;
finishedClasses[cls] = true;
var superclass = pendingClasses[cls];
// The superclass is only false (empty string) for the Dart Object
// class. The minifier together with noSuchMethod can put methods on
// the Object.prototype object, and they show through here, so we check
// that we have a string.
if (!superclass || typeof superclass != "string") return;
finishClass(superclass);
var constructor = allClasses[cls];
var superConstructor = allClasses[superclass];
if (!superConstructor)
superConstructor = existingIsolateProperties[superclass];
var prototype = inheritFrom(constructor, superConstructor);
if (#) { // !nativeClasses.isEmpty,
// The property looks like this:
//
// HtmlElement: {
// "%": "HTMLDivElement|HTMLAnchorElement;HTMLElement;FancyButton"
//
// The first two semicolon-separated parts contain dispatch tags, the
// third contains the JavaScript names for classes.
//
// The tags indicate that JavaScript objects with the dispatch tags
// (usually constructor names) HTMLDivElement, HTMLAnchorElement and
// HTMLElement all map to the Dart native class named HtmlElement.
// The first set is for effective leaf nodes in the hierarchy, the
// second set is non-leaf nodes.
//
// The third part contains the JavaScript names of Dart classes that
// extend the native class. Here, FancyButton extends HtmlElement, so
// the runtime needs to know that window.HTMLElement.prototype is the
// prototype that needs to be extended in creating the custom element.
//
// The information is used to build tables referenced by
// getNativeInterceptor and custom element support.
if (Object.prototype.hasOwnProperty.call(prototype, $specProperty)) {
var nativeSpec = prototype[$specProperty].split(";");
if (nativeSpec[0]) {
var tags = nativeSpec[0].split("|");
for (var i = 0; i < tags.length; i++) {
#[tags[i]] = constructor; // embedded interceptorsByTag.
#[tags[i]] = true; // embedded leafTags.
}
}
if (nativeSpec[1]) {
tags = nativeSpec[1].split("|");
if (#) { // User subclassing of native classes?
if (nativeSpec[2]) {
var subclasses = nativeSpec[2].split("|");
for (var i = 0; i < subclasses.length; i++) {
var subclass = allClasses[subclasses[i]];
subclass.\$nativeSuperclassTag = tags[0];
}
}
for (i = 0; i < tags.length; i++) {
#[tags[i]] = constructor; // embedded interceptorsByTag.
#[tags[i]] = false; // embedded leafTags.
}
}
}
}
}
}
}''', [finishedClassesAccess,
!nativeClasses.isEmpty,
interceptorsByTagAccess,
leafTagsAccess,
true,
interceptorsByTagAccess,
leafTagsAccess]);
}
jsAst.Fun get finishIsolateConstructorFunction {
// We replace the old Isolate function with a new one that initializes
// all its fields with the initial (and often final) value of all globals.
//
// We also copy over old values like the prototype, and the
// isolateProperties themselves.
return js('''
function (oldIsolate) {
var isolateProperties = oldIsolate.#; // isolatePropertiesName
function Isolate() {
var hasOwnProperty = Object.prototype.hasOwnProperty;
for (var staticName in isolateProperties)
if (hasOwnProperty.call(isolateProperties, staticName))
this[staticName] = isolateProperties[staticName];
// Reset lazy initializers to null.
// When forcing the object to fast mode (below) v8 will consider
// functions as part the object's map. Since we will change them
// (after the first call to the getter), we would have a map
// transition.
var lazies = init.lazies;
for (var lazyInit in lazies) {
this[lazies[lazyInit]] = null;
}
// Use the newly created object as prototype. In Chrome,
// this creates a hidden class for the object and makes
// sure it is fast to access.
function ForceEfficientMap() {}
ForceEfficientMap.prototype = this;
new ForceEfficientMap();
// Now, after being a fast map we can set the lazies again.
for (var lazyInit in lazies) {
var lazyInitName = lazies[lazyInit];
this[lazyInitName] = isolateProperties[lazyInitName];
}
}
Isolate.prototype = oldIsolate.prototype;
Isolate.prototype.constructor = Isolate;
Isolate.# = isolateProperties; // isolatePropertiesName
if (#) // needsDefineClass.
Isolate.# = oldIsolate.#; // finishClassesProperty * 2
if (#) // outputContainsConstantList
Isolate.# = oldIsolate.#; // makeConstListProperty * 2
return Isolate;
}''',
[namer.isolatePropertiesName, namer.isolatePropertiesName,
needsDefineClass, finishClassesProperty, finishClassesProperty,
task.outputContainsConstantList,
makeConstListProperty, makeConstListProperty ]);
}
jsAst.Fun get lazyInitializerFunction {
String isolate = namer.currentIsolate;
jsAst.Expression cyclicThrow =
namer.elementAccess(backend.getCyclicThrowHelper());
jsAst.Expression laziesAccess =
generateEmbeddedGlobalAccess(embeddedNames.LAZIES);
return js('''
function (prototype, staticName, fieldName, getterName, lazyValue) {
if (!#) # = Object.create(null);
#[fieldName] = getterName;
var sentinelUndefined = {};
var sentinelInProgress = {};
prototype[fieldName] = sentinelUndefined;
prototype[getterName] = function () {
var result = $isolate[fieldName];
try {
if (result === sentinelUndefined) {
$isolate[fieldName] = sentinelInProgress;
try {
result = $isolate[fieldName] = lazyValue();
} finally {
// Use try-finally, not try-catch/throw as it destroys the
// stack trace.
if (result === sentinelUndefined)
$isolate[fieldName] = null;
}
} else {
if (result === sentinelInProgress)
#(staticName);
}
return result;
} finally {
$isolate[getterName] = function() { return this[fieldName]; };
}
}
}
''', [laziesAccess, laziesAccess,
laziesAccess,
cyclicThrow]);
}
List buildDefineClassAndFinishClassFunctionsIfNecessary() {
if (!needsDefineClass) return [];
return defineClassFunction
..add(buildInheritFrom())
..add(js('$finishClassesName = #', finishClassesFunction))
..add(initFinishClasses);
}
List buildLazyInitializerFunctionIfNecessary() {
if (!needsLazyInitializer) return [];
return [js('# = #', [js(lazyInitializerName), lazyInitializerFunction])];
}
List buildFinishIsolateConstructor() {
return [
js('$finishIsolateConstructorName = #', finishIsolateConstructorFunction)
];
}
void emitFinishIsolateConstructorInvocation(CodeBuffer buffer) {
String isolate = namer.isolateName;
buffer.write("$isolate = $finishIsolateConstructorName($isolate)$N");
}
/// In minified mode we want to keep the name for the most common core types.
bool _isNativeTypeNeedingReflectionName(Element element) {
if (!element.isClass) return false;
return (element == compiler.intClass ||
element == compiler.doubleClass ||
element == compiler.numClass ||
element == compiler.stringClass ||
element == compiler.boolClass ||
element == compiler.nullClass ||
element == compiler.listClass);
}
/// Returns the "reflection name" of an [Element] or [Selector].
/// The reflection name of a getter 'foo' is 'foo'.
/// The reflection name of a setter 'foo' is 'foo='.
/// The reflection name of a method 'foo' is 'foo:N:M:O', where N is the
/// number of required arguments, M is the number of optional arguments, and
/// O is the named arguments.
/// The reflection name of a constructor is similar to a regular method but
/// starts with 'new '.
/// The reflection name of class 'C' is 'C'.
/// An anonymous mixin application has no reflection name.
/// This is used by js_mirrors.dart.
String getReflectionName(elementOrSelector, String mangledName) {
String name = elementOrSelector.name;
if (backend.shouldRetainName(name) ||
elementOrSelector is Element &&
// Make sure to retain names of unnamed constructors, and
// for common native types.
((name == '' &&
backend.isAccessibleByReflection(elementOrSelector)) ||
_isNativeTypeNeedingReflectionName(elementOrSelector))) {
// TODO(ahe): Enable the next line when I can tell the difference between
// an instance method and a global. They may have the same mangled name.
// if (recordedMangledNames.contains(mangledName)) return null;
recordedMangledNames.add(mangledName);
return getReflectionNameInternal(elementOrSelector, mangledName);
}
return null;
}
String getReflectionNameInternal(elementOrSelector, String mangledName) {
String name =
namer.privateName(elementOrSelector.library, elementOrSelector.name);
if (elementOrSelector.isGetter) return name;
if (elementOrSelector.isSetter) {
if (!mangledName.startsWith(namer.setterPrefix)) return '$name=';
String base = mangledName.substring(namer.setterPrefix.length);
String getter = '${namer.getterPrefix}$base';
mangledFieldNames.putIfAbsent(getter, () => name);
assert(mangledFieldNames[getter] == name);
recordedMangledNames.add(getter);
// TODO(karlklose,ahe): we do not actually need to store information
// about the name of this setter in the output, but it is needed for
// marking the function as invokable by reflection.
return '$name=';
}
if (elementOrSelector is Element && elementOrSelector.isClosure) {
// Closures are synthesized and their name might conflict with existing
// globals. Assign an illegal name, and make sure they don't clash
// with each other.
return " $mangledName";
}
if (elementOrSelector is Selector
|| elementOrSelector.isFunction
|| elementOrSelector.isConstructor) {
int requiredParameterCount;
int optionalParameterCount;
String namedArguments = '';
bool isConstructor = false;
if (elementOrSelector is Selector) {
Selector selector = elementOrSelector;
requiredParameterCount = selector.argumentCount;
optionalParameterCount = 0;
namedArguments = namedParametersAsReflectionNames(selector);
} else {
FunctionElement function = elementOrSelector;
if (function.isConstructor) {
isConstructor = true;
name = Elements.reconstructConstructorName(function);
}
FunctionSignature signature = function.functionSignature;
requiredParameterCount = signature.requiredParameterCount;
optionalParameterCount = signature.optionalParameterCount;
if (signature.optionalParametersAreNamed) {
var names = [];
for (Element e in signature.optionalParameters) {
names.add(e.name);
}
Selector selector = new Selector.call(
function.name,
function.library,
requiredParameterCount,
names);
namedArguments = namedParametersAsReflectionNames(selector);
} else {
// Named parameters are handled differently by mirrors. For unnamed
// parameters, they are actually required if invoked
// reflectively. Also, if you have a method c(x) and c([x]) they both
// get the same mangled name, so they must have the same reflection
// name.
requiredParameterCount += optionalParameterCount;
optionalParameterCount = 0;
}
}
String suffix =
// TODO(ahe): We probably don't need optionalParameterCount in the
// reflection name.
'$name:$requiredParameterCount:$optionalParameterCount'
'$namedArguments';
return (isConstructor) ? 'new $suffix' : suffix;
}
Element element = elementOrSelector;
if (element.isGenerativeConstructorBody) {
return null;
} else if (element.isClass) {
ClassElement cls = element;
if (cls.isUnnamedMixinApplication) return null;
return cls.name;
} else if (element.isTypedef) {
return element.name;
}
throw compiler.internalError(element,
'Do not know how to reflect on this $element.');
}
String namedParametersAsReflectionNames(Selector selector) {
if (selector.getOrderedNamedArguments().isEmpty) return '';
String names = selector.getOrderedNamedArguments().join(':');
return ':$names';
}
jsAst.FunctionDeclaration buildCspPrecompiledFunctionFor(
OutputUnit outputUnit) {
// TODO(ahe): Compute a hash code.
return js.statement('''
function dart_precompiled(\$collectedClasses) {
var \$desc;
#;
return #;
}''',
[cspPrecompiledFunctionFor(outputUnit),
new jsAst.ArrayInitializer.from(
cspPrecompiledConstructorNamesFor(outputUnit))]);
}
void generateClass(ClassElement classElement, ClassBuilder properties) {
compiler.withCurrentElement(classElement, () {
if (compiler.hasIncrementalSupport) {
ClassBuilder builder =
cachedClassBuilders.putIfAbsent(classElement, () {
ClassBuilder builder = new ClassBuilder(classElement, namer);
classEmitter.generateClass(
classElement, builder, additionalProperties[classElement]);
return builder;
});
invariant(classElement, builder.fields.isEmpty);
invariant(classElement, builder.superName == null);
invariant(classElement, builder.functionType == null);
invariant(classElement, builder.fieldMetadata == null);
properties.properties.addAll(builder.properties);
} else {
classEmitter.generateClass(
classElement, properties, additionalProperties[classElement]);
}
});
}
void emitFinishClassesInvocationIfNecessary(CodeBuffer buffer) {
if (needsDefineClass) {
buffer.write('$finishClassesName($classesCollector,'
'$_$isolateProperties,'
'${_}null)$N');
// Reset the map.
buffer.write("$classesCollector$_=${_}null$N$n");
}
}
void emitStaticFunctions(List<Element> staticFunctions) {
for (Element element in staticFunctions) {
ClassBuilder builder = new ClassBuilder(element, namer);
containerBuilder.addMember(element, builder);
getElementDescriptor(element).properties.addAll(builder.properties);
}
}
void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) {
JavaScriptConstantCompiler handler = backend.constants;
Iterable<VariableElement> staticNonFinalFields =
handler.getStaticNonFinalFieldsForEmission();
for (Element element in Elements.sortedByPosition(staticNonFinalFields)) {
// [:interceptedNames:] is handled in [emitInterceptedNames].
if (element == backend.interceptedNames) continue;
// `mapTypeToInterceptor` is handled in [emitMapTypeToInterceptor].
if (element == backend.mapTypeToInterceptor) continue;
compiler.withCurrentElement(element, () {
ConstantValue initialValue = handler.getInitialValueFor(element).value;
jsAst.Expression init =
js('$isolateProperties.# = #',
[namer.getNameOfGlobalField(element),
constantEmitter.referenceInInitializationContext(initialValue)]);
buffer.write(jsAst.prettyPrint(init, compiler,
monitor: compiler.dumpInfoTask));
buffer.write('$N');
});
}
}
void emitLazilyInitializedStaticFields(CodeBuffer buffer) {
JavaScriptConstantCompiler handler = backend.constants;
List<VariableElement> lazyFields =
handler.getLazilyInitializedFieldsForEmission();
if (!lazyFields.isEmpty) {
needsLazyInitializer = true;
for (VariableElement element in Elements.sortedByPosition(lazyFields)) {
jsAst.Expression code = backend.generatedCode[element];
// The code is null if we ended up not needing the lazily
// initialized field after all because of constant folding
// before code generation.
if (code == null) continue;
// The code only computes the initial value. We build the lazy-check
// here:
// lazyInitializer(prototype, 'name', fieldName, getterName, initial);
// The name is used for error reporting. The 'initial' must be a
// closure that constructs the initial value.
jsAst.Expression init = js('#(#,#,#,#,#)',
[js(lazyInitializerName),
js(isolateProperties),
js.string(element.name),
js.string(namer.getNameX(element)),
js.string(namer.getLazyInitializerName(element)),
code]);
buffer.write(jsAst.prettyPrint(init, compiler,
monitor: compiler.dumpInfoTask));
buffer.write("$N");
}
}
}
bool isConstantInlinedOrAlreadyEmitted(ConstantValue constant) {
if (constant.isFunction) return true; // Already emitted.
if (constant.isPrimitive) return true; // Inlined.
if (constant.isDummy) return true; // Inlined.
// The name is null when the constant is already a JS constant.
// TODO(floitsch): every constant should be registered, so that we can
// share the ones that take up too much space (like some strings).
if (namer.constantName(constant) == null) return true;
return false;
}
int compareConstants(ConstantValue a, ConstantValue b) {
// Inlined constants don't affect the order and sometimes don't even have
// names.
int cmp1 = isConstantInlinedOrAlreadyEmitted(a) ? 0 : 1;
int cmp2 = isConstantInlinedOrAlreadyEmitted(b) ? 0 : 1;
if (cmp1 + cmp2 < 2) return cmp1 - cmp2;
// Emit constant interceptors first. Constant interceptors for primitives
// might be used by code that builds other constants. See Issue 18173.
if (a.isInterceptor != b.isInterceptor) {
return a.isInterceptor ? -1 : 1;
}
// Sorting by the long name clusters constants with the same constructor
// which compresses a tiny bit better.
int r = namer.constantLongName(a).compareTo(namer.constantLongName(b));
if (r != 0) return r;
// Resolve collisions in the long name by using the constant name (i.e. JS
// name) which is unique.
return namer.constantName(a).compareTo(namer.constantName(b));
}
void emitCompileTimeConstants(CodeBuffer buffer, OutputUnit outputUnit) {
List<ConstantValue> constants = outputConstantLists[outputUnit];
if (constants == null) return;
bool isMainBuffer = buffer == mainBuffer;
if (compiler.hasIncrementalSupport && isMainBuffer) {
buffer = cachedEmittedConstantsBuffer;
}
for (ConstantValue constant in constants) {
if (compiler.hasIncrementalSupport && isMainBuffer) {
if (cachedEmittedConstants.contains(constant)) continue;
cachedEmittedConstants.add(constant);
}
String name = namer.constantName(constant);
jsAst.Expression init = js('#.# = #',
[namer.globalObjectForConstant(constant), name,
constantInitializerExpression(constant)]);
buffer.write(jsAst.prettyPrint(init, compiler,
monitor: compiler.dumpInfoTask));
buffer.write('$N');
}
if (compiler.hasIncrementalSupport && isMainBuffer) {
mainBuffer.add(cachedEmittedConstantsBuffer);
}
}
jsAst.Template get makeConstantListTemplate {
// TODO(floitsch): there is no harm in caching the template.
return jsAst.js.uncachedExpressionTemplate(
'${namer.isolateName}.$makeConstListProperty(#)');
}
void emitMakeConstantList(CodeBuffer buffer) {
buffer.write(
jsAst.prettyPrint(
// Functions are stored in the hidden class and not as properties in
// the object. We never actually look at the value, but only want
// to know if the property exists.
js.statement(r'''#.# = function(list) {
list.immutable$list = Array;
list.fixed$length = Array;
return list;
}''',
[namer.isolateName, makeConstListProperty]),
compiler, monitor: compiler.dumpInfoTask));
buffer.write(N);
}
/// Returns the code equivalent to:
/// `function(args) { $.startRootIsolate(X.main$closure(), args); }`
jsAst.Expression buildIsolateSetupClosure(Element appMain,
Element isolateMain) {
jsAst.Expression mainAccess = namer.isolateStaticClosureAccess(appMain);
// Since we pass the closurized version of the main method to
// the isolate method, we must make sure that it exists.
return js('function(a){ #(#, a); }',
[namer.elementAccess(isolateMain), mainAccess]);
}
/**
* Emits code that sets the `isolateTag embedded global to a unique string.
*/
jsAst.Expression generateIsolateAffinityTagInitialization() {
jsAst.Expression getIsolateTagAccess =
generateEmbeddedGlobalAccess(embeddedNames.GET_ISOLATE_TAG);
jsAst.Expression isolateTagAccess =
generateEmbeddedGlobalAccess(embeddedNames.ISOLATE_TAG);
return js('''
!function() {
// On V8, the 'intern' function converts a string to a symbol, which
// makes property access much faster.
function intern(s) {
var o = {};
o[s] = 1;
return Object.keys(convertToFastObject(o))[0];
}
# = function(name) { // embedded getIsolateTag
return intern("___dart_" + name + #); // embedded isolateTag
};
// To ensure that different programs loaded into the same context (page)
// use distinct dispatch properies, we place an object on `Object` to
// contain the names already in use.
var tableProperty = "___dart_isolate_tags_";
var usedProperties = Object[tableProperty] ||
(Object[tableProperty] = Object.create(null));
var rootProperty = "_${generateIsolateTagRoot()}";
for (var i = 0; ; i++) {
var property = intern(rootProperty + "_" + i + "_");
if (!(property in usedProperties)) {
usedProperties[property] = 1;
# = property; // embedded isolateTag
break;
}
}
}()
''', [getIsolateTagAccess,
isolateTagAccess,
isolateTagAccess]);
}
jsAst.Expression generateDispatchPropertyNameInitialization() {
jsAst.Expression dispatchPropertyNameAccess =
generateEmbeddedGlobalAccess(embeddedNames.DISPATCH_PROPERTY_NAME);
jsAst.Expression getIsolateTagAccess =
generateEmbeddedGlobalAccess(embeddedNames.GET_ISOLATE_TAG);
return js('# = #("dispatch_record")',
[dispatchPropertyNameAccess,
getIsolateTagAccess]);
}
String generateIsolateTagRoot() {
// TODO(sra): MD5 of contributing source code or URIs?
return 'ZxYxX';
}
emitMain(CodeBuffer buffer) {
if (compiler.isMockCompilation) return;
Element main = compiler.mainFunction;
jsAst.Expression mainCallClosure = null;
if (compiler.hasIsolateSupport) {
Element isolateMain =
backend.isolateHelperLibrary.find(JavaScriptBackend.START_ROOT_ISOLATE);
mainCallClosure = buildIsolateSetupClosure(main, isolateMain);
} else if (compiler.hasIncrementalSupport) {
mainCallClosure =
js('function() { return #(); }', namer.elementAccess(main));
} else {
mainCallClosure = namer.elementAccess(main);
}
if (backend.needToInitializeIsolateAffinityTag) {
buffer.write(
jsAst.prettyPrint(generateIsolateAffinityTagInitialization(),
compiler, monitor: compiler.dumpInfoTask));
buffer.write(N);
}
if (backend.needToInitializeDispatchProperty) {
assert(backend.needToInitializeIsolateAffinityTag);
buffer.write(
jsAst.prettyPrint(generateDispatchPropertyNameInitialization(),
compiler, monitor: compiler.dumpInfoTask));
buffer.write(N);
}
jsAst.Expression currentScriptAccess =
generateEmbeddedGlobalAccess(embeddedNames.CURRENT_SCRIPT);
addComment('BEGIN invoke [main].', buffer);
// This code finds the currently executing script by listening to the
// onload event of all script tags and getting the first script which
// finishes. Since onload is called immediately after execution this should
// not substantially change execution order.
jsAst.Statement invokeMain = js.statement('''
(function (callback) {
if (typeof document === "undefined") {
callback(null);
return;
}
if (document.currentScript) {
callback(document.currentScript);
return;
}
var scripts = document.scripts;
function onLoad(event) {
for (var i = 0; i < scripts.length; ++i) {
scripts[i].removeEventListener("load", onLoad, false);
}
callback(event.target);
}
for (var i = 0; i < scripts.length; ++i) {
scripts[i].addEventListener("load", onLoad, false);
}
})(function(currentScript) {
# = currentScript; // embedded currentScript.
if (typeof dartMainRunner === "function") {
dartMainRunner(#, []); // mainCallClosure.
} else {
#([]); // mainCallClosure.
}
})$N''', [currentScriptAccess,
mainCallClosure,
mainCallClosure]);
buffer.write(';');
buffer.write(jsAst.prettyPrint(invokeMain,
compiler, monitor: compiler.dumpInfoTask));
buffer.write(N);
addComment('END invoke [main].', buffer);
}
void emitInitFunction(CodeBuffer buffer) {
jsAst.FunctionDeclaration decl = js.statement('''
function init() {
$isolateProperties = Object.create(null);
#; #; #;
}''', [
buildDefineClassAndFinishClassFunctionsIfNecessary(),
buildLazyInitializerFunctionIfNecessary(),
buildFinishIsolateConstructor()]);
buffer.write(jsAst.prettyPrint(decl,
compiler, monitor: compiler.dumpInfoTask).getText());
if (compiler.enableMinification) buffer.write('\n');
}
void emitConvertToFastObjectFunction() {
List<jsAst.Statement> debugCode = <jsAst.Statement>[];
if (DEBUG_FAST_OBJECTS) {
debugCode.add(js.statement(r'''
// The following only works on V8 when run with option
// "--allow-natives-syntax". We use'new Function' because the
// miniparser does not understand V8 native syntax.
if (typeof print === "function") {
var HasFastProperties =
new Function("a", "return %HasFastProperties(a)");
print("Size of global object: "
+ String(Object.getOwnPropertyNames(properties).length)
+ ", fast properties " + HasFastProperties(properties));
}'''));
}
jsAst.Statement convertToFastObject = js.statement(r'''
function convertToFastObject(properties) {
// Create an instance that uses 'properties' as prototype. This should
// make 'properties' a fast object.
function MyClass() {};
MyClass.prototype = properties;
new MyClass();
#;
return properties;
}''', [debugCode]);
mainBuffer.add(jsAst.prettyPrint(convertToFastObject, compiler));
mainBuffer.add(N);
}
void writeLibraryDescriptors(CodeBuffer buffer, LibraryElement library) {
var uri = "";
if (!compiler.enableMinification || backend.mustPreserveUris) {
uri = library.canonicalUri;
if (uri.scheme == 'file' && compiler.outputUri != null) {
uri = relativize(compiler.outputUri, library.canonicalUri, false);
}
}
ClassBuilder descriptor = elementDescriptors[library];
if (descriptor == null) {
// Nothing of the library was emitted.
// TODO(floitsch): this should not happen. We currently have an example
// with language/prefix6_negative_test.dart where we have an instance
// method without its corresponding class.
return;
}
String libraryName =
(!compiler.enableMinification || backend.mustRetainLibraryNames) ?
library.getLibraryName() :
"";
jsAst.Fun metadata = metadataEmitter.buildMetadataFunction(library);
jsAst.ObjectInitializer initializers = descriptor.toObjectInitializer();
compiler.dumpInfoTask.registerElementAst(library, metadata);
compiler.dumpInfoTask.registerElementAst(library, initializers);
buffer
..write('["$libraryName",$_')
..write('"${uri}",$_')
..write(metadata == null ? "" : jsAst.prettyPrint(metadata,
compiler,
monitor: compiler.dumpInfoTask))
..write(',$_')
..write(namer.globalObjectFor(library))
..write(',$_')
..write(jsAst.prettyPrint(initializers,
compiler,
monitor: compiler.dumpInfoTask))
..write(library == compiler.mainApp ? ',${n}1' : "")
..write('],$n');
}
void emitPrecompiledConstructor(OutputUnit outputUnit,
String constructorName,
jsAst.Expression constructorAst) {
cspPrecompiledFunctionFor(outputUnit).add(
new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration(constructorName), constructorAst));
cspPrecompiledFunctionFor(outputUnit).add(
js.statement(r'''{
#.builtin$cls = #;
if (!"name" in #)
#.name = #;
$desc=$collectedClasses.#;
if ($desc instanceof Array) $desc = $desc[1];
#.prototype = $desc;
}''',
[ constructorName, js.string(constructorName),
constructorName,
constructorName, js.string(constructorName),
constructorName,
constructorName
]));
cspPrecompiledConstructorNamesFor(outputUnit).add(js('#', constructorName));
}
/// Extracts the output name of the compiler's outputUri.
String deferredPartFileName(OutputUnit outputUnit,
{bool addExtension: true}) {
String outPath = compiler.outputUri != null
? compiler.outputUri.path
: "out";
String outName = outPath.substring(outPath.lastIndexOf('/') + 1);
String extension = addExtension ? ".part.js" : "";
if (outputUnit == compiler.deferredLoadTask.mainOutputUnit) {
return "$outName$extension";
} else {
String name = outputUnit.name;
return "${outName}_$name$extension";
}
}
void emitLibraries(Iterable<LibraryElement> libraries) {
if (libraries.isEmpty) return;
// TODO(karlklose): document what kinds of fields this loop adds to the
// library class builder.
for (LibraryElement element in libraries) {
LibraryElement library = element;
ClassBuilder builder = new ClassBuilder(library, namer);
if (classEmitter.emitFields(library, builder, null, emitStatics: true)) {
jsAst.ObjectInitializer initializer = builder.toObjectInitializer();
compiler.dumpInfoTask.registerElementAst(builder.element, initializer);
getElementDescriptor(library).properties.addAll(initializer.properties);
}
}
}
void emitTypedefs() {
OutputUnit mainOutputUnit = compiler.deferredLoadTask.mainOutputUnit;
// Emit all required typedef declarations into the main output unit.
// TODO(karlklose): unify required classes and typedefs to declarations
// and have builders for each kind.
for (TypedefElement typedef in typedefsNeededForReflection) {
OutputUnit mainUnit = compiler.deferredLoadTask.mainOutputUnit;
LibraryElement library = typedef.library;
// TODO(karlklose): add a TypedefBuilder and move this code there.
DartType type = typedef.alias;
int typeIndex = metadataEmitter.reifyType(type);
String typeReference =
encoding.encodeTypedefFieldDescriptor(typeIndex);
jsAst.Property descriptor = new jsAst.Property(
js.string(namer.classDescriptorProperty),
js.string(typeReference));
jsAst.Node declaration = new jsAst.ObjectInitializer([descriptor]);
String mangledName = namer.getNameX(typedef);
String reflectionName = getReflectionName(typedef, mangledName);
getElementDescriptor(library)
..addProperty(mangledName, declaration)
..addProperty("+$reflectionName", js.string(''));
// Also emit a trivial constructor for CSP mode.
String constructorName = mangledName;
jsAst.Expression constructorAst = js('function() {}');
emitPrecompiledConstructor(mainOutputUnit,
constructorName,
constructorAst);
}
}
void emitMangledNames() {
if (!mangledFieldNames.isEmpty) {
var keys = mangledFieldNames.keys.toList();
keys.sort();
var properties = [];
for (String key in keys) {
var value = js.string('${mangledFieldNames[key]}');
properties.add(new jsAst.Property(js.string(key), value));
}
jsAst.Expression mangledNamesAccess =
generateEmbeddedGlobalAccess(embeddedNames.MANGLED_NAMES);
var map = new jsAst.ObjectInitializer(properties);
mainBuffer.write(
jsAst.prettyPrint(
js.statement('# = #', [mangledNamesAccess, map]),
compiler,
monitor: compiler.dumpInfoTask));
if (compiler.enableMinification) {
mainBuffer.write(';');
}
}
if (!mangledGlobalFieldNames.isEmpty) {
var keys = mangledGlobalFieldNames.keys.toList();
keys.sort();
var properties = [];
for (String key in keys) {
var value = js.string('${mangledGlobalFieldNames[key]}');
properties.add(new jsAst.Property(js.string(key), value));
}
jsAst.Expression mangledGlobalNamesAccess =
generateEmbeddedGlobalAccess(embeddedNames.MANGLED_GLOBAL_NAMES);
var map = new jsAst.ObjectInitializer(properties);
mainBuffer.write(
jsAst.prettyPrint(
js.statement('# = #', [mangledGlobalNamesAccess, map]),
compiler,
monitor: compiler.dumpInfoTask));
if (compiler.enableMinification) {
mainBuffer.write(';');
}
}
}
void checkEverythingEmitted(Iterable<Element> elements) {
List<Element> pendingStatics;
if (!compiler.hasIncrementalSupport) {
pendingStatics =
Elements.sortedByPosition(elements.where((e) => !e.isLibrary));
pendingStatics.forEach((element) =>
compiler.reportInfo(
element, MessageKind.GENERIC, {'text': 'Pending statics.'}));
}
if (pendingStatics != null && !pendingStatics.isEmpty) {
compiler.internalError(pendingStatics.first,
'Pending statics (see above).');
}
}
void emitMainOutputUnit(Map<OutputUnit, String> deferredLoadHashes,
CodeBuffer nativeBuffer) {
bool isProgramSplit = compiler.deferredLoadTask.isProgramSplit;
OutputUnit mainOutputUnit = compiler.deferredLoadTask.mainOutputUnit;
mainBuffer.add(buildGeneratedBy());
addComment(HOOKS_API_USAGE, mainBuffer);
if (isProgramSplit) {
/// For deferred loading we communicate the initializers via this global
/// variable. The deferred hunks will add their initialization to this.
/// The semicolon is important in minified mode, without it the
/// following parenthesis looks like a call to the object literal.
mainBuffer..add(
'self.${deferredInitializers} = self.${deferredInitializers} || '
'Object.create(null);$n');
}
// Using a named function here produces easier to read stack traces in
// Chrome/V8.
mainBuffer.add('(function(${namer.currentIsolate})$_{\n');
if (compiler.hasIncrementalSupport) {
mainBuffer.add(
'(this.\$dart_unsafe_eval ='
' this.\$dart_unsafe_eval || Object.create(null))'
'.patch = function(a) { eval(a) }$N');
}
if (isProgramSplit) {
/// We collect all the global state of the, so it can be passed to the
/// initializer of deferred files.
mainBuffer.add('var ${globalsHolder}$_=${_}Object.create(null)$N');
}
mainBuffer.add('function dart()$_{$n'
'${_}${_}this.x$_=${_}0$N'
'${_}${_}delete this.x$N'
'}$n');
for (String globalObject in Namer.reservedGlobalObjectNames) {
// The global objects start as so-called "slow objects". For V8, this
// means that it won't try to make map transitions as we add properties
// to these objects. Later on, we attempt to turn these objects into
// fast objects by calling "convertToFastObject" (see
// [emitConvertToFastObjectFunction]).
mainBuffer.write('var ${globalObject}$_=${_}');
if(isProgramSplit) {
mainBuffer.add('${globalsHolder}.$globalObject$_=${_}');
}
mainBuffer.write('new dart$N');
}
mainBuffer.add('function ${namer.isolateName}()$_{}\n');
if (isProgramSplit) {
mainBuffer
.write('${globalsHolder}.${namer.isolateName}$_=$_'
'${namer.isolateName}$N'
'${globalsHolder}.$initName$_=${_}$initName$N');
}
mainBuffer.add('init()$N$n');
mainBuffer.add('$isolateProperties$_=$_$isolatePropertiesName$N');
emitStaticFunctions(task.outputStaticLists[mainOutputUnit]);
// Only output the classesCollector if we actually have any classes.
if (!(nativeClasses.isEmpty &&
compiler.codegenWorld.staticFunctionsNeedingGetter.isEmpty &&
outputClassLists.values.every((classList) => classList.isEmpty) &&
typedefsNeededForReflection.isEmpty)) {
// Shorten the code by using "$$" as temporary.
classesCollector = r"$$";
mainBuffer.add('var $classesCollector$_=${_}Object.create(null)$N$n');
}
if (!nativeClasses.isEmpty) {
addComment('Native classes', mainBuffer);
}
List<ClassElement> classes = task.outputClassLists[mainOutputUnit];
if (classes != null) {
for (ClassElement element in classes) {
generateClass(element, getElementDescriptor(element));
}
}
if (compiler.enableMinification) {
mainBuffer.write(';');
}
if (elementDescriptors.isNotEmpty) {
Iterable<LibraryElement> libraries =
task.outputLibraryLists[mainOutputUnit];
if (libraries == null) libraries = [];
emitLibraries(libraries);
emitTypedefs();
emitMangledNames();
checkEverythingEmitted(elementDescriptors.keys);
CodeBuffer libraryBuffer = new CodeBuffer();
for (LibraryElement library in Elements.sortedByPosition(libraries)) {
writeLibraryDescriptors(libraryBuffer, library);
elementDescriptors.remove(library);
}
mainBuffer
..write('(')
..write(
jsAst.prettyPrint(
getReflectionDataParser(classesCollector, backend),
compiler))
..write(')')
..write('([$n')
..add(libraryBuffer)
..write('])$N');
emitFinishClassesInvocationIfNecessary(mainBuffer);
}
typeTestEmitter.emitRuntimeTypeSupport(mainBuffer, mainOutputUnit);
interceptorEmitter.emitGetInterceptorMethods(mainBuffer);
interceptorEmitter.emitOneShotInterceptors(mainBuffer);
if (task.outputContainsConstantList) {
emitMakeConstantList(mainBuffer);
}
// Constants in checked mode call into RTI code to set type information
// which may need getInterceptor (and one-shot interceptor) methods, so
// we have to make sure that [emitGetInterceptorMethods] and
// [emitOneShotInterceptors] have been called.
emitCompileTimeConstants(mainBuffer, mainOutputUnit);
emitDeferredBoilerPlate(mainBuffer, deferredLoadHashes);
// Static field initializations require the classes and compile-time
// constants to be set up.
emitStaticNonFinalFieldInitializations(mainBuffer);
interceptorEmitter.emitInterceptedNames(mainBuffer);
interceptorEmitter.emitMapTypeToInterceptor(mainBuffer);
emitLazilyInitializedStaticFields(mainBuffer);
mainBuffer.writeln();
mainBuffer.add(nativeBuffer);
metadataEmitter.emitMetadata(mainBuffer);
isolateProperties = isolatePropertiesName;
// The following code should not use the short-hand for the
// initialStatics.
mainBuffer.add('${namer.currentIsolate}$_=${_}null$N');
emitFinishIsolateConstructorInvocation(mainBuffer);
mainBuffer.add(
'${namer.currentIsolate}$_=${_}new ${namer.isolateName}()$N');
emitConvertToFastObjectFunction();
for (String globalObject in Namer.reservedGlobalObjectNames) {
mainBuffer.add('$globalObject = convertToFastObject($globalObject)$N');
}
if (DEBUG_FAST_OBJECTS) {
mainBuffer.add(r'''
// The following only works on V8 when run with option
// "--allow-natives-syntax". We use'new Function' because the
// miniparser does not understand V8 native syntax.
if (typeof print === "function") {
var HasFastProperties =
new Function("a", "return %HasFastProperties(a)");
print("Size of global helper object: "
+ String(Object.getOwnPropertyNames(H).length)
+ ", fast properties " + HasFastProperties(H));
print("Size of global platform object: "
+ String(Object.getOwnPropertyNames(P).length)
+ ", fast properties " + HasFastProperties(P));
print("Size of global dart:html object: "
+ String(Object.getOwnPropertyNames(W).length)
+ ", fast properties " + HasFastProperties(W));
print("Size of isolate properties object: "
+ String(Object.getOwnPropertyNames($).length)
+ ", fast properties " + HasFastProperties($));
print("Size of constant object: "
+ String(Object.getOwnPropertyNames(C).length)
+ ", fast properties " + HasFastProperties(C));
var names = Object.getOwnPropertyNames($);
for (var i = 0; i < names.length; i++) {
print("$." + names[i]);
}
}
''');
for (String object in Namer.userGlobalObjects) {
mainBuffer.add('''
if (typeof print === "function") {
print("Size of $object: "
+ String(Object.getOwnPropertyNames($object).length)
+ ", fast properties " + HasFastProperties($object));
}
''');
}
}
jsAst.FunctionDeclaration precompiledFunctionAst =
buildCspPrecompiledFunctionFor(mainOutputUnit);
emitInitFunction(mainBuffer);
emitMain(mainBuffer);
mainBuffer.add('})()\n');
if (compiler.useContentSecurityPolicy) {
mainBuffer.write(
jsAst.prettyPrint(
precompiledFunctionAst,
compiler,
monitor: compiler.dumpInfoTask,
allowVariableMinification: false).getText());
}
String assembledCode = mainBuffer.getText();
if (generateSourceMap) {
outputSourceMap(assembledCode, mainBuffer, '',
compiler.sourceMapUri, compiler.outputUri);
mainBuffer.add(
generateSourceMapTag(compiler.sourceMapUri, compiler.outputUri));
assembledCode = mainBuffer.getText();
}
compiler.outputProvider('', 'js')
..add(assembledCode)
..close();
compiler.assembledCode = assembledCode;
}
/// Returns a map from OutputUnit to a hash of its content. The hash uniquely
/// identifies the code of the output-unit. It does not include
/// boilerplate JS code, like the sourcemap directives or the hash
/// itself.
Map<OutputUnit, String> emitDeferredOutputUnits() {
if (!compiler.deferredLoadTask.isProgramSplit) return const {};
Map<OutputUnit, CodeBuffer> outputBuffers =
new Map<OutputUnit, CodeBuffer>();
for (OutputUnit outputUnit in compiler.deferredLoadTask.allOutputUnits) {
if (outputUnit == compiler.deferredLoadTask.mainOutputUnit) continue;
List<Element> functions = task.outputStaticLists[outputUnit];
if (functions != null) {
emitStaticFunctions(functions);
}
List<ClassElement> classes = task.outputClassLists[outputUnit];
if (classes != null) {
for (ClassElement element in classes) {
generateClass(element, getElementDescriptor(element));
}
}
if (elementDescriptors.isNotEmpty) {
Iterable<LibraryElement> libraries =
task.outputLibraryLists[outputUnit];
if (libraries == null) libraries = [];
emitLibraries(libraries);
CodeBuffer buffer = new CodeBuffer();
outputBuffers[outputUnit] = buffer;
for (LibraryElement library in Elements.sortedByPosition(libraries)) {
writeLibraryDescriptors(buffer, library);
elementDescriptors.remove(library);
}
}
}
return emitDeferredCode(outputBuffers);
}
CodeBuffer buildNativesBuffer() {
// Emit native classes on [nativeBuffer].
final CodeBuffer nativeBuffer = new CodeBuffer();
if (nativeClasses.isEmpty) return nativeBuffer;
addComment('Native classes', nativeBuffer);
nativeEmitter.generateNativeClasses(nativeClasses, mainBuffer,
additionalProperties);
nativeEmitter.finishGenerateNativeClasses();
nativeEmitter.assembleCode(nativeBuffer);
return nativeBuffer;
}
void emitProgram(Program program) {
// Shorten the code by using [namer.currentIsolate] as temporary.
isolateProperties = namer.currentIsolate;
classesCollector = r"$$";
// Emit deferred units first, so we have their hashes.
// Map from OutputUnit to a hash of its content. The hash uniquely
// identifies the code of the output-unit. It does not include
// boilerplate JS code, like the sourcemap directives or the hash
// itself.
Map<OutputUnit, String> deferredLoadHashes = emitDeferredOutputUnits();
CodeBuffer nativeBuffer = buildNativesBuffer();
emitMainOutputUnit(deferredLoadHashes, nativeBuffer);
if (backend.requiresPreamble &&
!backend.htmlLibraryIsLoaded) {
compiler.reportHint(NO_LOCATION_SPANNABLE, MessageKind.PREAMBLE);
}
}
String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) {
if (sourceMapUri != null && fileUri != null) {
String sourceMapFileName = relativize(fileUri, sourceMapUri, false);
return '''
//# sourceMappingURL=$sourceMapFileName
''';
}
return '';
}
ClassBuilder getElementDescriptor(Element element) {
Element owner = element.library;
if (!element.isLibrary && !element.isTopLevel && !element.isNative) {
// For static (not top level) elements, record their code in a buffer
// specific to the class. For now, not supported for native classes and
// native elements.
ClassElement cls =
element.enclosingClassOrCompilationUnit.declaration;
if (compiler.codegenWorld.directlyInstantiatedClasses.contains(cls)
&& !cls.isNative) {
owner = cls;
}
}
if (owner == null) {
compiler.internalError(element, 'Owner is null.');
}
return elementDescriptors.putIfAbsent(
owner,
() => new ClassBuilder(owner, namer));
}
/// Emits support-code for deferred loading into [buffer].
void emitDeferredBoilerPlate(CodeBuffer buffer,
Map<OutputUnit, String> deferredLoadHashes) {
// Function for checking if a hunk is loaded given its hash.
buffer.write(jsAst.prettyPrint(
js('# = function(hunkHash) {'
' return !!$deferredInitializers[hunkHash];'
'}', generateEmbeddedGlobalAccess(embeddedNames.IS_HUNK_LOADED)),
compiler, monitor: compiler.dumpInfoTask));
buffer.write('$N');
// Function for initializing a loaded hunk, given its hash.
buffer.write(jsAst.prettyPrint(
js('# = function(hunkHash) {'
' $deferredInitializers[hunkHash]('
'$globalsHolder, ${namer.currentIsolate})'
'}',
generateEmbeddedGlobalAccess(
embeddedNames.INITIALIZE_LOADED_HUNK)),
compiler, monitor: compiler.dumpInfoTask));
buffer.write('$N');
// Write a javascript mapping from Deferred import load ids (derrived
// from the import prefix.) to a list of lists of uris of hunks to load,
// and a corresponding mapping to a list of hashes used by
// INITIALIZE_LOADED_HUNK and IS_HUNK_LOADED.
Map<String, List<String>> deferredLibraryUris =
new Map<String, List<String>>();
Map<String, List<String>> deferredLibraryHashes =
new Map<String, List<String>>();
compiler.deferredLoadTask.hunksToLoad.forEach(
(String loadId, List<OutputUnit>outputUnits) {
List<String> uris = new List<String>();
List<String> hashes = new List<String>();
deferredLibraryHashes[loadId] = new List<String>();
for (OutputUnit outputUnit in outputUnits) {
uris.add(deferredPartFileName(outputUnit));
hashes.add(deferredLoadHashes[outputUnit]);
}
deferredLibraryUris[loadId] = uris;
deferredLibraryHashes[loadId] = hashes;
});
void emitMapping(String name, Map<String, List<String>> mapping) {
List<jsAst.Property> properties = new List<jsAst.Property>();
mapping.forEach((String key, List<String> values) {
properties.add(new jsAst.Property(js.escapedString(key),
new jsAst.ArrayInitializer.from(
values.map(js.escapedString))));
});
jsAst.Node initializer =
new jsAst.ObjectInitializer(properties, isOneLiner: true);
jsAst.Node globalName = generateEmbeddedGlobalAccess(name);
buffer.write(jsAst.prettyPrint(
js("# = #", [globalName, initializer]),
compiler, monitor: compiler.dumpInfoTask));
buffer.write('$N');
}
emitMapping(embeddedNames.DEFERRED_LIBRARY_URIS, deferredLibraryUris);
emitMapping(embeddedNames.DEFERRED_LIBRARY_HASHES,
deferredLibraryHashes);
}
/// Emits code for all output units except the main.
/// Returns a mapping from outputUnit to a hash of the corresponding hunk that
/// can be used for calling the initializer.
Map<OutputUnit, String> emitDeferredCode(
Map<OutputUnit, CodeBuffer> deferredBuffers) {
Map<OutputUnit, String> hunkHashes = new Map<OutputUnit, String>();
for (OutputUnit outputUnit in compiler.deferredLoadTask.allOutputUnits) {
if (outputUnit == compiler.deferredLoadTask.mainOutputUnit) continue;
CodeBuffer libraryDescriptorBuffer = deferredBuffers[outputUnit];
CodeBuffer outputBuffer = new CodeBuffer();
outputBuffer..write(buildGeneratedBy())
..write('${deferredInitializers}.current$_=$_'
'function$_(${globalsHolder}) {$N');
for (String globalObject in Namer.reservedGlobalObjectNames) {
outputBuffer
.write('var $globalObject$_=$_'
'${globalsHolder}.$globalObject$N');
}
outputBuffer
..write('var init$_=$_${globalsHolder}.init$N')
..write('var ${namer.isolateName}$_=$_'
'${globalsHolder}.${namer.isolateName}$N');
if (libraryDescriptorBuffer != null) {
// TODO(ahe): This defines a lot of properties on the
// Isolate.prototype object. We know this will turn it into a
// slow object in V8, so instead we should do something similar
// to Isolate.$finishIsolateConstructor.
outputBuffer
..write('var ${namer.currentIsolate}$_=$_$isolatePropertiesName$N')
// The classesCollector object ($$).
..write('$classesCollector$_=${_}Object.create(null);$n')
..write('(')
..write(
jsAst.prettyPrint(
getReflectionDataParser(classesCollector, backend),
compiler, monitor: compiler.dumpInfoTask))
..write(')')
..write('([$n')
..addBuffer(libraryDescriptorBuffer)
..write('])$N');
if (outputClassLists.containsKey(outputUnit)) {
outputBuffer.write(
'$finishClassesName($classesCollector,$_${namer.currentIsolate},'
'$_$isolatePropertiesName)$N');
}
}
// Set the currentIsolate variable to the current isolate (which is
// provided as second argument).
// We need to do this, because we use the same variable for setting up
// the isolate-properties and for storing the current isolate. During
// the setup (the code above this lines) we must set the variable to
// the isolate-properties.
// After we have done the setup (finishing with `finishClasses`) it must
// point to the current Isolate. Otherwise all methods/functions
// accessing isolate variables will access the wrong object.
outputBuffer.write("${namer.currentIsolate}$_=${_}arguments[1]$N");
typeTestEmitter.emitRuntimeTypeSupport(outputBuffer, outputUnit);
emitCompileTimeConstants(outputBuffer, outputUnit);
outputBuffer.write('}$N');
if (compiler.useContentSecurityPolicy) {
jsAst.FunctionDeclaration precompiledFunctionAst =
buildCspPrecompiledFunctionFor(outputUnit);
outputBuffer.write(
jsAst.prettyPrint(
precompiledFunctionAst, compiler,
monitor: compiler.dumpInfoTask,
allowVariableMinification: false).getText());
}
// Make a unique hash of the code (before the sourcemaps are added)
// This will be used to retrieve the initializing function from the global
// variable.
String hash = hashOfString(outputBuffer.getText());
outputBuffer.add('${deferredInitializers}["$hash"]$_=$_'
'${deferredInitializers}.current$N');
String partPrefix = deferredPartFileName(outputUnit, addExtension: false);
if (generateSourceMap) {
Uri mapUri, partUri;
Uri sourceMapUri = compiler.sourceMapUri;
Uri outputUri = compiler.outputUri;
String partName = "$partPrefix.part";
if (sourceMapUri != null) {
String mapFileName = partName + ".js.map";
List<String> mapSegments = sourceMapUri.pathSegments.toList();
mapSegments[mapSegments.length - 1] = mapFileName;
mapUri = compiler.sourceMapUri.replace(pathSegments: mapSegments);
}
if (outputUri != null) {
String partFileName = partName + ".js";
List<String> partSegments = outputUri.pathSegments.toList();
partSegments[partSegments.length - 1] = partFileName;
partUri = compiler.outputUri.replace(pathSegments: partSegments);
}
outputSourceMap(outputBuffer.getText(), outputBuffer, partName,
mapUri, partUri);
outputBuffer.add(generateSourceMapTag(mapUri, partUri));
}
outputBuffers[outputUnit] = outputBuffer;
compiler.outputProvider(partPrefix, 'part.js')
..add(outputBuffer.getText())
..close();
hunkHashes[outputUnit] = hash;
}
return hunkHashes;
}
String buildGeneratedBy() {
var suffix = '';
if (compiler.hasBuildId) suffix = ' version: ${compiler.buildId}';
return '// Generated by dart2js, the Dart to JavaScript compiler$suffix.\n';
}
void outputSourceMap(String code, CodeBuffer buffer, String name,
[Uri sourceMapUri, Uri fileUri]) {
if (!generateSourceMap) return;
// Create a source file for the compilation output. This allows using
// [:getLine:] to transform offsets to line numbers in [SourceMapBuilder].
SourceFile compiledFile = new StringSourceFile(null, code);
SourceMapBuilder sourceMapBuilder =
new SourceMapBuilder(sourceMapUri, fileUri, compiledFile);
buffer.forEachSourceLocation(sourceMapBuilder.addMapping);
String sourceMap = sourceMapBuilder.build();
compiler.outputProvider(name, 'js.map')
..add(sourceMap)
..close();
}
void invalidateCaches() {
if (!compiler.hasIncrementalSupport) return;
if (cachedElements.isEmpty) return;
for (Element element in compiler.enqueuer.codegen.newlyEnqueuedElements) {
if (element.isInstanceMember) {
cachedClassBuilders.remove(element.enclosingClass);
nativeEmitter.cachedBuilders.remove(element.enclosingClass);
}
}
}
}