blob: dd2d29390f529dcf3bdaf78c304fe10420138d57 [file] [log] [blame]
// Copyright (c) 2012, 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;
/**
* Generates the code for all used classes in the program. Static fields (even
* in classes) are ignored, since they can be treated as non-class elements.
*
* The code for the containing (used) methods must exist in the [:universe:].
*/
class CodeEmitterTask extends CompilerTask {
final ContainerBuilder containerBuilder = new ContainerBuilder();
final ClassEmitter classEmitter = new ClassEmitter();
final NsmEmitter nsmEmitter = new NsmEmitter();
final TypeTestEmitter typeTestEmitter = new TypeTestEmitter();
final InterceptorEmitter interceptorEmitter = new InterceptorEmitter();
final MetadataEmitter metadataEmitter = new MetadataEmitter();
bool needsDefineClass = false;
bool needsMixinSupport = false;
bool needsLazyInitializer = false;
final Namer namer;
ConstantEmitter constantEmitter;
NativeEmitter nativeEmitter;
CodeBuffer mainBuffer;
final CodeBuffer deferredLibraries = new 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;
final Set<ClassElement> neededClasses = new Set<ClassElement>();
final List<ClassElement> regularClasses = <ClassElement>[];
final List<ClassElement> deferredClasses = <ClassElement>[];
final List<ClassElement> nativeClasses = <ClassElement>[];
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>>();
// TODO(ngeoffray): remove this field.
Set<ClassElement> instantiatedClasses;
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";
/**
* 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.
*/
List<jsAst.Node> precompiledFunction = <jsAst.Node>[];
List<jsAst.Expression> precompiledConstructorNames = <jsAst.Expression>[];
// True if Isolate.makeConstantList is needed.
bool hasMakeConstantList = false;
/**
* For classes and libraries, record code for static/top-level members.
* Later, this code is emitted when the class or library is emitted.
* See [bufferForElement].
*/
// TODO(ahe): Generate statics with their class, and store only libraries in
// this map.
final Map<Element, List<CodeBuffer>> elementBuffers =
new Map<Element, List<CodeBuffer>>();
final bool generateSourceMap;
CodeEmitterTask(Compiler compiler, Namer namer, this.generateSourceMap)
: mainBuffer = new CodeBuffer(),
this.namer = namer,
constantEmitter = new ConstantEmitter(compiler, namer),
super(compiler) {
nativeEmitter = new NativeEmitter(this);
containerBuilder.task = this;
classEmitter.task = this;
nsmEmitter.task = this;
typeTestEmitter.task = this;
interceptorEmitter.task = this;
metadataEmitter.task = this;
}
void addComment(String comment, CodeBuffer buffer) {
buffer.write(jsAst.prettyPrint(js.comment(comment), compiler));
}
jsAst.Expression constantReference(Constant value) {
return constantEmitter.reference(value);
}
jsAst.Expression constantInitializerExpression(Constant value) {
return constantEmitter.initializationExpression(value);
}
String get name => 'CodeEmitter';
String get currentGenerateAccessorName
=> '${namer.CURRENT_ISOLATE}.\$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';
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;
// function generateAccessor(field, prototype, cls) {
jsAst.Fun fun = js.fun(['field', 'accessors', 'cls'], [
js('var len = field.length'),
js('var code = field.charCodeAt(len - 1)'),
js('var reflectable = false'),
js.if_('code == $REFLECTION_MARKER', [
js('len--'),
js('code = field.charCodeAt(len - 1)'),
js('field = field.substring(0, len)'),
js('reflectable = true')
]),
js('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 (needsAccessor) {
js.if_('code', [
js('var getterCode = code & 3'),
js('var setterCode = code >> 2'),
js('var accessorName = field = field.substring(0, len - 1)'),
js('var divider = field.indexOf(":")'),
js.if_('divider > 0', [ // Colon never in first position.
js('accessorName = field.substring(0, divider)'),
js('field = field.substring(divider + 1)')
]),
// if (needsGetter) {
js.if_('getterCode', [
js('var args = (getterCode & 2) ? "$receiverParamName" : ""'),
js('var receiver = (getterCode & 1) ? "this" : "$receiverParamName"'),
js('var body = "return " + receiver + "." + field'),
js('var property ='
' cls + ".prototype.${namer.getterPrefix}" + accessorName + "="'),
js('var fn = "function(" + args + "){" + body + "}"'),
js.if_(
'reflectable',
js('accessors.push(property + "\$reflectable(" + fn + ");\\n")'),
js('accessors.push(property + fn + ";\\n")')),
]),
// if (needsSetter) {
js.if_('setterCode', [
js('var args = (setterCode & 2)'
' ? "$receiverParamName,${_}$valueParamName"'
' : "$valueParamName"'),
js('var receiver = (setterCode & 1) ? "this" : "$receiverParamName"'),
js('var body = receiver + "." + field + "$_=$_$valueParamName"'),
js('var property ='
' cls + ".prototype.${namer.setterPrefix}" + accessorName + "="'),
js('var fn = "function(" + args + "){" + body + "}"'),
js.if_(
'reflectable',
js('accessors.push(property + "\$reflectable(" + fn + ");\\n")'),
js('accessors.push(property + fn + ";\\n")')),
]),
]),
// return field;
js.return_('field')
]);
return new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration('generateAccessor'),
fun);
}
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.fun(['name', 'cls', 'fields'], [
js('var accessors = []'),
js('var str = "function " + cls + "("'),
js('var body = ""'),
js.for_('var i = 0', 'i < fields.length', 'i++', [
js.if_('i != 0', js('str += ", "')),
js('var field = generateAccessor(fields[i], accessors, cls)'),
js('var parameter = "parameter_" + field'),
js('str += parameter'),
js('body += ("this." + field + " = " + parameter + ";\\n")')
]),
js('str += ") {\\n" + body + "}\\n"'),
js('str += cls + ".builtin\$cls=\\"" + name + "\\";\\n"'),
js('str += "\$desc=\$collectedClasses." + cls + ";\\n"'),
js('str += "if(\$desc instanceof Array) \$desc = \$desc[1];\\n"'),
js('str += cls + ".prototype = \$desc;\\n"'),
js.if_(
'typeof defineClass.name != "string"',
[js('str += cls + ".name=\\"" + cls + "\\";\\n"')]),
js('str += accessors.join("")'),
js.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. */
List buildInheritFrom() {
return [
js('var inheritFrom = #',
js.fun([], [
new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration('tmp'), js.fun([], [])),
js('var hasOwnProperty = Object.prototype.hasOwnProperty'),
js.return_(js.fun(['constructor', 'superConstructor'], [
js('tmp.prototype = superConstructor.prototype'),
js('var object = new tmp()'),
js('var properties = constructor.prototype'),
js.forIn('member', 'properties',
js.if_('hasOwnProperty.call(properties, member)',
js('object[member] = properties[member]'))),
js('object.constructor = constructor'),
js('constructor.prototype = object'),
js.return_('object')
]))])())];
}
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;
List<jsAst.Node> statements = [
js('var pendingClasses = {}'),
js.if_('!init.allClasses', js('init.allClasses = {}')),
js('var allClasses = init.allClasses'),
optional(
DEBUG_FAST_OBJECTS,
js('print("Number of classes: "'
r' + Object.getOwnPropertyNames($$).length)')),
js('var hasOwnProperty = Object.prototype.hasOwnProperty'),
js.if_('typeof dart_precompiled == "function"',
[js('var constructors = dart_precompiled(collectedClasses)')],
[js('var combinedConstructorFunction = "function \$reflectable(fn){'
'fn.$reflectableField=1;return fn};\\n"+ "var \$desc;\\n"'),
js('var constructorsList = []')]),
js.forIn('cls', 'collectedClasses', [
js.if_('hasOwnProperty.call(collectedClasses, cls)', [
js('var desc = collectedClasses[cls]'),
js.if_('desc instanceof Array', js('desc = desc[1]')),
/* The 'fields' are either a constructor function or a
* string encoding fields, constructor and superclass. Get
* the superclass and the fields in the format
* '[name/]Super;field1,field2'
* from the null-string 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'.
*/
js('var classData = desc[""], supr, name = cls, fields = classData'),
optional(
backend.hasRetainedMetadata,
js.if_('typeof classData == "object" && '
'classData instanceof Array',
[js('classData = fields = classData[0]')])),
js.if_('typeof classData == "string"', [
js('var split = classData.split("/")'),
js.if_('split.length == 2', [
js('name = split[0]'),
js('fields = split[1]')
])
]),
js('var s = fields.split(";")'),
js('fields = s[1] == "" ? [] : s[1].split(",")'),
js('supr = s[0]'),
optional(needsMixinSupport, js.if_('supr && supr.indexOf("+") > 0', [
js('s = supr.split("+")'),
js('supr = s[0]'),
js('var mixin = collectedClasses[s[1]]'),
js.if_('mixin instanceof Array', js('mixin = mixin[1]')),
js.forIn('d', 'mixin', [
js.if_('hasOwnProperty.call(mixin, d)'
'&& !hasOwnProperty.call(desc, d)',
js('desc[d] = mixin[d]'))
]),
])),
js.if_('typeof dart_precompiled != "function"',
[js('combinedConstructorFunction +='
' defineClass(name, cls, fields)'),
js('constructorsList.push(cls)')]),
js.if_('supr', js('pendingClasses[cls] = supr'))
])
]),
js.if_('typeof dart_precompiled != "function"',
[js('combinedConstructorFunction +='
' "return [\\n " + constructorsList.join(",\\n ") + "\\n]"'),
js('var constructors ='
' new Function("\$collectedClasses", combinedConstructorFunction)'
'(collectedClasses)'),
js('combinedConstructorFunction = null')]),
js.for_('var i = 0', 'i < constructors.length', 'i++', [
js('var constructor = constructors[i]'),
js('var cls = constructor.name'),
js('var desc = collectedClasses[cls]'),
js('var globalObject = isolateProperties'),
js.if_('desc instanceof Array', [
js('globalObject = desc[0] || isolateProperties'),
js('desc = desc[1]')
]),
optional(backend.isTreeShakingDisabled,
js('constructor["${namer.metadataField}"] = desc')),
js('allClasses[cls] = constructor'),
js('globalObject[cls] = constructor'),
]),
js('constructors = null'),
js('var finishedClasses = {}'),
js('init.interceptorsByTag = Object.create(null)'),
js('init.leafTags = {}'),
buildFinishClass(),
];
nsmEmitter.addTrivialNsmHandlers(statements);
statements.add(
js.forIn('cls', 'pendingClasses', js('finishClass(cls)'))
);
// function(collectedClasses,
// isolateProperties,
// existingIsolateProperties)
return js.fun(['collectedClasses', 'isolateProperties',
'existingIsolateProperties'], statements);
}
jsAst.Node optional(bool condition, jsAst.Node node) {
return condition ? node : new jsAst.EmptyStatement();
}
jsAst.FunctionDeclaration buildFinishClass() {
String specProperty = '"${namer.nativeSpecProperty}"'; // "%"
// function finishClass(cls) {
jsAst.Fun fun = js.fun(['cls'], [
// TODO(8540): Remove this work around.
/* Opera does not support 'getOwnPropertyNames'. Therefore we use
hasOwnProperty instead. */
js('var hasOwnProperty = Object.prototype.hasOwnProperty'),
// if (hasOwnProperty.call(finishedClasses, cls)) return;
js.if_('hasOwnProperty.call(finishedClasses, cls)',
js.return_()),
js('finishedClasses[cls] = true'),
js('var superclass = pendingClasses[cls]'),
// The superclass is only false (empty string) for Dart's 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.
js.if_('!superclass || typeof superclass != "string"', js.return_()),
js('finishClass(superclass)'),
js('var constructor = allClasses[cls]'),
js('var superConstructor = allClasses[superclass]'),
js.if_(js('!superConstructor'),
js('superConstructor ='
'existingIsolateProperties[superclass]')),
js('var prototype = inheritFrom(constructor, superConstructor)'),
optional(!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.
js.if_('hasOwnProperty.call(prototype, $specProperty)', [
js('var nativeSpec = prototype[$specProperty].split(";")'),
js.if_('nativeSpec[0]', [
js('var tags = nativeSpec[0].split("|")'),
js.for_('var i = 0', 'i < tags.length', 'i++', [
js('init.interceptorsByTag[tags[i]] = constructor'),
js('init.leafTags[tags[i]] = true')])]),
js.if_('nativeSpec[1]', [
js('tags = nativeSpec[1].split("|")'),
optional(true, // User subclassing of native classes?
js.if_('nativeSpec[2]', [
js('var subclasses = nativeSpec[2].split("|")'),
js.for_('var i = 0', 'i < subclasses.length', 'i++', [
js('var subclass = allClasses[subclasses[i]]'),
js('subclass.\$nativeSuperclassTag = '
'tags[0]')])])),
js.for_('i = 0', 'i < tags.length', 'i++', [
js('init.interceptorsByTag[tags[i]] = constructor'),
js('init.leafTags[tags[i]] = false')])])]))
]);
return new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration('finishClass'),
fun);
}
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.fun('oldIsolate', [
js('var isolateProperties = oldIsolate.${namer.isolatePropertiesName}'),
new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration('Isolate'),
js.fun([], [
js('var hasOwnProperty = Object.prototype.hasOwnProperty'),
js.forIn('staticName', 'isolateProperties',
js.if_('hasOwnProperty.call(isolateProperties, staticName)',
js('this[staticName] = isolateProperties[staticName]'))),
// 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.
new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration('ForceEfficientMap'),
js.fun([], [])),
js('ForceEfficientMap.prototype = this'),
js('new ForceEfficientMap()')])),
js('Isolate.prototype = oldIsolate.prototype'),
js('Isolate.prototype.constructor = Isolate'),
js('Isolate.${namer.isolatePropertiesName} = isolateProperties'),
optional(needsDefineClass,
js('Isolate.$finishClassesProperty ='
' oldIsolate.$finishClassesProperty')),
optional(hasMakeConstantList,
js('Isolate.makeConstantList = oldIsolate.makeConstantList')),
js.return_('Isolate')]);
}
jsAst.Fun get lazyInitializerFunction {
// function(prototype, staticName, fieldName, getterName, lazyValue) {
var parameters = <String>['prototype', 'staticName', 'fieldName',
'getterName', 'lazyValue'];
return js.fun(parameters, addLazyInitializerLogic());
}
List addLazyInitializerLogic() {
String isolate = namer.CURRENT_ISOLATE;
String cyclicThrow = namer.isolateAccess(backend.getCyclicThrowHelper());
var lazies = [];
if (backend.rememberLazies) {
lazies = [
js.if_('!init.lazies', js('init.lazies = {}')),
js('init.lazies[fieldName] = getterName')];
}
return lazies..addAll([
js('var sentinelUndefined = {}'),
js('var sentinelInProgress = {}'),
js('prototype[fieldName] = sentinelUndefined'),
// prototype[getterName] = function()
js('prototype[getterName] = #', js.fun([], [
js('var result = $isolate[fieldName]'),
// try
js.try_([
js.if_('result === sentinelUndefined', [
js('$isolate[fieldName] = sentinelInProgress'),
// try
js.try_([
js('result = $isolate[fieldName] = lazyValue()'),
], finallyPart: [
// Use try-finally, not try-catch/throw as it destroys the
// stack trace.
// if (result === sentinelUndefined)
js.if_('result === sentinelUndefined', [
// if ($isolate[fieldName] === sentinelInProgress)
js.if_('$isolate[fieldName] === sentinelInProgress', [
js('$isolate[fieldName] = null'),
])
])
])
], /* else */ [
js.if_('result === sentinelInProgress',
js('$cyclicThrow(staticName)')
)
]),
// return result;
js.return_('result')
], finallyPart: [
js('$isolate[getterName] = #',
js.fun([], [js.return_('this[fieldName]')]))
])
]))
]);
}
List buildDefineClassAndFinishClassFunctionsIfNecessary() {
if (!needsDefineClass) return [];
return defineClassFunction
..addAll(buildInheritFrom())
..addAll([
js('$finishClassesName = #', finishClassesFunction)
]);
}
List buildLazyInitializerFunctionIfNecessary() {
if (!needsLazyInitializer) return [];
return [js('$lazyInitializerName = #', lazyInitializerFunction)];
}
List buildFinishIsolateConstructor() {
return [
js('$finishIsolateConstructorName = #', finishIsolateConstructorFunction)
];
}
void emitFinishIsolateConstructorInvocation(CodeBuffer buffer) {
String isolate = namer.isolateName;
buffer.write("$isolate = $finishIsolateConstructorName($isolate)$N");
}
/// 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)) {
if (name == '' && elementOrSelector is Element) {
// Make sure to retain names of unnamed constructors.
if (!backend.isNeededForReflection(elementOrSelector)) return null;
} else {
return null;
}
}
// 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);
}
String getReflectionNameInternal(elementOrSelector, String mangledName) {
String name = 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[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 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);
}
requiredParameterCount = function.requiredParameterCount(compiler);
optionalParameterCount = function.optionalParameterCount(compiler);
FunctionSignature signature = function.computeSignature(compiler);
if (signature.optionalParametersAreNamed) {
var names = [];
for (Element e in signature.optionalParameters) {
names.add(e.name);
}
Selector selector = new Selector.call(
function.name,
function.getLibrary(),
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;
}
throw compiler.internalErrorOnElement(
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 buildPrecompiledFunction() {
// TODO(ahe): Compute a hash code.
String name = 'dart_precompiled';
precompiledFunction.add(
js.return_(
new jsAst.ArrayInitializer.from(precompiledConstructorNames)));
precompiledFunction.insert(0, js(r'var $desc'));
return new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration(name),
js.fun([r'$collectedClasses'], precompiledFunction));
}
void generateClass(ClassElement classElement, CodeBuffer buffer) {
classEmitter.generateClass(
classElement, buffer, additionalProperties[classElement]);
}
int _selectorRank(Selector selector) {
int arity = selector.argumentCount * 3;
if (selector.isGetter()) return arity + 2;
if (selector.isSetter()) return arity + 1;
return arity;
}
int _compareSelectorNames(Selector selector1, Selector selector2) {
String name1 = selector1.name.toString();
String name2 = selector2.name.toString();
if (name1 != name2) return Comparable.compare(name1, name2);
return _selectorRank(selector1) - _selectorRank(selector2);
}
/**
* Return a function that returns true if its argument is a class
* that needs to be emitted.
*/
Function computeClassFilter() {
if (backend.isTreeShakingDisabled) return (ClassElement cls) => true;
Set<ClassElement> unneededClasses = new Set<ClassElement>();
// The [Bool] class is not marked as abstract, but has a factory
// constructor that always throws. We never need to emit it.
unneededClasses.add(compiler.boolClass);
// Go over specialized interceptors and then constants to know which
// interceptors are needed.
Set<ClassElement> needed = new Set<ClassElement>();
backend.specializedGetInterceptors.forEach(
(_, Iterable<ClassElement> elements) {
needed.addAll(elements);
}
);
// Add interceptors referenced by constants.
needed.addAll(interceptorEmitter.interceptorsReferencedFromConstants());
// Add unneeded interceptors to the [unneededClasses] set.
for (ClassElement interceptor in backend.interceptedClasses) {
if (!needed.contains(interceptor)
&& interceptor != compiler.objectClass) {
unneededClasses.add(interceptor);
}
}
return (ClassElement cls) => !unneededClasses.contains(cls);
}
void emitFinishClassesInvocationIfNecessary(CodeBuffer buffer) {
if (needsDefineClass) {
buffer.write('$finishClassesName($classesCollector,'
'$_$isolateProperties,'
'${_}null)$N');
// Reset the map.
buffer.write("$classesCollector$_=${_}null$N$n");
}
}
void emitStaticFunctions(CodeBuffer eagerBuffer) {
bool isStaticFunction(Element element) =>
!element.isInstanceMember() && !element.isField();
Iterable<Element> elements =
backend.generatedCode.keys.where(isStaticFunction);
for (Element element in Elements.sortedByPosition(elements)) {
ClassBuilder builder = new ClassBuilder();
containerBuilder.addMember(element, builder);
builder.writeOn_DO_NOT_USE(
bufferForElement(element, eagerBuffer), compiler, ',$n$n');
}
}
void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) {
ConstantHandler handler = compiler.constantHandler;
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, () {
Constant initialValue = handler.getInitialValueFor(element);
jsAst.Expression init =
js('$isolateProperties.${namer.getNameOfGlobalField(element)} = #',
constantEmitter.referenceInInitializationContext(initialValue));
buffer.write(jsAst.prettyPrint(init, compiler));
buffer.write('$N');
});
}
}
void emitLazilyInitializedStaticFields(CodeBuffer buffer) {
ConstantHandler handler = compiler.constantHandler;
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.
List<jsAst.Expression> arguments = <jsAst.Expression>[];
arguments.add(js(isolateProperties));
arguments.add(js.string(element.name));
arguments.add(js.string(namer.getNameX(element)));
arguments.add(js.string(namer.getLazyInitializerName(element)));
arguments.add(code);
jsAst.Expression getter = buildLazyInitializedGetter(element);
if (getter != null) {
arguments.add(getter);
}
jsAst.Expression init = js(lazyInitializerName)(arguments);
buffer.write(jsAst.prettyPrint(init, compiler));
buffer.write("$N");
}
}
}
jsAst.Expression buildLazyInitializedGetter(VariableElement element) {
// Nothing to do, the 'lazy' function will create the getter.
return null;
}
void emitCompileTimeConstants(CodeBuffer eagerBuffer) {
ConstantHandler handler = compiler.constantHandler;
List<Constant> constants = handler.getConstantsForEmission(
compareConstants);
for (Constant constant in constants) {
if (isConstantInlinedOrAlreadyEmitted(constant)) continue;
String name = namer.constantName(constant);
if (constant.isList()) emitMakeConstantListIfNotEmitted(eagerBuffer);
CodeBuffer buffer = bufferForConstant(constant, eagerBuffer);
jsAst.Expression init = js(
'${namer.globalObjectForConstant(constant)}.$name = #',
constantInitializerExpression(constant));
buffer.write(jsAst.prettyPrint(init, compiler));
buffer.write('$N');
}
}
bool isConstantInlinedOrAlreadyEmitted(Constant constant) {
if (constant.isFunction()) return true; // Already emitted.
if (constant.isPrimitive()) 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(Constant a, Constant 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;
// 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 emitMakeConstantListIfNotEmitted(CodeBuffer buffer) {
if (hasMakeConstantList) return;
hasMakeConstantList = true;
buffer
..write(namer.isolateName)
..write(r'''.makeConstantList = function(list) {
list.immutable$list = true;
list.fixed$length = true;
return list;
};
''');
}
String buildIsolateSetup(CodeBuffer buffer,
Element appMain,
Element isolateMain) {
String 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 "${namer.isolateAccess(isolateMain)}($mainAccess)";
}
jsAst.Expression generateDispatchPropertyInitialization() {
return js('!#', js.fun([], [
js('var objectProto = Object.prototype'),
js.for_('var i = 0', null, 'i++', [
js('var property = "${generateDispatchPropertyName(0)}"'),
js.if_('i > 0', js('property = rootProperty + "_" + i')),
js.if_('!(property in objectProto)',
js.return_(
js('init.dispatchPropertyName = property')))])])());
}
String generateDispatchPropertyName(int seed) {
// TODO(sra): MD5 of contributing source code or URIs?
return '___dart_dispatch_record_ZxYxX_${seed}_';
}
emitMain(CodeBuffer buffer) {
if (compiler.isMockCompilation) return;
Element main = compiler.mainFunction;
String mainCall = null;
if (compiler.hasIsolateSupport()) {
Element isolateMain =
compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE);
mainCall = buildIsolateSetup(buffer, main, isolateMain);
} else {
mainCall = '${namer.isolateAccess(main)}()';
}
if (backend.needToInitializeDispatchProperty) {
buffer.write(
jsAst.prettyPrint(generateDispatchPropertyInitialization(),
compiler));
buffer.write(N);
}
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.
buffer.write('''
;(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) {
init.currentScript = currentScript;
if (typeof dartMainRunner === "function") {
dartMainRunner(function() { ${mainCall}; });
} else {
${mainCall};
}
})$N''');
addComment('END invoke [main].', buffer);
}
/**
* Compute all the classes that must be emitted.
*/
void computeNeededClasses() {
instantiatedClasses =
compiler.codegenWorld.instantiatedClasses.where(computeClassFilter())
.toSet();
void addClassWithSuperclasses(ClassElement cls) {
neededClasses.add(cls);
for (ClassElement superclass = cls.superclass;
superclass != null;
superclass = superclass.superclass) {
neededClasses.add(superclass);
}
}
void addClassesWithSuperclasses(Iterable<ClassElement> classes) {
for (ClassElement cls in classes) {
addClassWithSuperclasses(cls);
}
}
// 1. We need to generate all classes that are instantiated.
addClassesWithSuperclasses(instantiatedClasses);
// 2. Add all classes used as mixins.
Set<ClassElement> mixinClasses = neededClasses
.where((ClassElement element) => element.isMixinApplication)
.map(computeMixinClass)
.toSet();
neededClasses.addAll(mixinClasses);
// 3. If we need noSuchMethod support, we run through all needed
// classes to figure out if we need the support on any native
// class. If so, we let the native emitter deal with it.
if (compiler.enabledNoSuchMethod) {
String noSuchMethodName = Compiler.NO_SUCH_METHOD;
Selector noSuchMethodSelector = compiler.noSuchMethodSelector;
for (ClassElement element in neededClasses) {
if (!element.isNative()) continue;
Element member = element.lookupLocalMember(noSuchMethodName);
if (member == null) continue;
if (noSuchMethodSelector.applies(member, compiler)) {
nativeEmitter.handleNoSuchMethod = true;
break;
}
}
}
// 4. Find all classes needed for rti.
// It is important that this is the penultimate step, at this point,
// neededClasses must only contain classes that have been resolved and
// codegen'd. The rtiNeededClasses may contain additional classes, but
// these are thought to not have been instantiated, so we neeed to be able
// to identify them later and make sure we only emit "empty shells" without
// fields, etc.
typeTestEmitter.computeRtiNeededClasses();
typeTestEmitter.rtiNeededClasses.removeAll(neededClasses);
// rtiNeededClasses now contains only the "empty shells".
neededClasses.addAll(typeTestEmitter.rtiNeededClasses);
// 5. Finally, sort the classes.
List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses);
for (ClassElement element in sortedClasses) {
if (typeTestEmitter.rtiNeededClasses.contains(element)) {
regularClasses.add(element);
} else if (Elements.isNativeOrExtendsNative(element)) {
// For now, native classes and related classes cannot be deferred.
nativeClasses.add(element);
if (!element.isNative()) {
assert(invariant(element, !isDeferred(element)));
regularClasses.add(element);
}
} else if (isDeferred(element)) {
deferredClasses.add(element);
} else {
regularClasses.add(element);
}
}
}
void emitInitFunction(CodeBuffer buffer) {
jsAst.Fun fun = js.fun([], [
js('$isolateProperties = {}'),
]
..addAll(buildDefineClassAndFinishClassFunctionsIfNecessary())
..addAll(buildLazyInitializerFunctionIfNecessary())
..addAll(buildFinishIsolateConstructor())
);
jsAst.FunctionDeclaration decl = new jsAst.FunctionDeclaration(
new jsAst.VariableDeclaration('init'), fun);
buffer.write(jsAst.prettyPrint(decl, compiler).getText());
if (compiler.enableMinification) buffer.write('\n');
}
void emitConvertToFastObjectFunction() {
// Create an instance that uses 'properties' as prototype. This should make
// 'properties' a fast object.
mainBuffer.add(r'''function convertToFastObject(properties) {
function MyClass() {};
MyClass.prototype = properties;
new MyClass();
''');
if (DEBUG_FAST_OBJECTS) {
ClassElement primitives =
compiler.findHelper('Primitives');
FunctionElement printHelper =
compiler.lookupElementIn(
primitives, 'printString');
String printHelperName = namer.isolateAccess(printHelper);
mainBuffer.add('''
// The following only works on V8 when run with option "--allow-natives-syntax".
if (typeof $printHelperName === "function") {
$printHelperName("Size of global object: "
+ String(Object.getOwnPropertyNames(properties).length)
+ ", fast properties " + %HasFastProperties(properties));
}
''');
}
mainBuffer.add(r'''
return properties;
}
''');
}
String assembleProgram() {
measure(() {
// Compute the required type checks to know which classes need a
// 'is$' method.
typeTestEmitter.computeRequiredTypeChecks();
computeNeededClasses();
mainBuffer.add(buildGeneratedBy());
addComment(HOOKS_API_USAGE, mainBuffer);
if (!areAnyElementsDeferred) {
mainBuffer.add('(function(${namer.CURRENT_ISOLATE})$_{$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}$_=$_{}$N')
..write('delete ${globalObject}.x$N');
}
mainBuffer.add('function ${namer.isolateName}()$_{}\n');
mainBuffer.add('init()$N$n');
// Shorten the code by using [namer.CURRENT_ISOLATE] as temporary.
isolateProperties = namer.CURRENT_ISOLATE;
mainBuffer.add(
'$isolateProperties$_=$_$isolatePropertiesName$N');
emitStaticFunctions(mainBuffer);
if (!regularClasses.isEmpty ||
!deferredClasses.isEmpty ||
!nativeClasses.isEmpty ||
!compiler.codegenWorld.staticFunctionsNeedingGetter.isEmpty) {
// Shorten the code by using "$$" as temporary.
classesCollector = r"$$";
mainBuffer.add('var $classesCollector$_=$_{}$N$n');
}
// Emit native classes on [nativeBuffer].
// Might create methodClosures.
final CodeBuffer nativeBuffer = new CodeBuffer();
if (!nativeClasses.isEmpty) {
addComment('Native classes', nativeBuffer);
addComment('Native classes', mainBuffer);
nativeEmitter.generateNativeClasses(nativeClasses, mainBuffer,
additionalProperties);
}
// As a side-effect, emitting classes will produce "bound closures" in
// [methodClosures]. The bound closures are JS AST nodes that add
// properties to $$ [classesCollector]. The bound closures are not
// emitted until we have emitted all other classes (native or not).
// Might create methodClosures.
if (!regularClasses.isEmpty) {
for (ClassElement element in regularClasses) {
generateClass(element, bufferForElement(element, mainBuffer));
}
}
nativeEmitter.finishGenerateNativeClasses();
nativeEmitter.assembleCode(nativeBuffer);
// Might create methodClosures.
if (!deferredClasses.isEmpty) {
for (ClassElement element in deferredClasses) {
generateClass(element, bufferForElement(element, mainBuffer));
}
}
containerBuilder.emitStaticFunctionClosures();
addComment('Method closures', mainBuffer);
// Now that we have emitted all classes, we know all the method
// closures that will be needed.
containerBuilder.methodClosures.forEach((String code, Element closure) {
// TODO(ahe): Some of these can be deferred.
String mangledName = namer.getNameOfClass(closure);
mainBuffer.add('$classesCollector.$mangledName$_=$_'
'[${namer.globalObjectFor(closure)},$_$code]');
mainBuffer.add("$N$n");
});
// After this assignment we will produce invalid JavaScript code if we use
// the classesCollector variable.
classesCollector = 'classesCollector should not be used from now on';
if (!elementBuffers.isEmpty) {
var oldClassesCollector = classesCollector;
classesCollector = r"$$";
if (compiler.enableMinification) {
mainBuffer.write(';');
}
for (Element element in elementBuffers.keys) {
// TODO(ahe): Should iterate over all libraries. Otherwise, we will
// not see libraries that only have fields.
if (element.isLibrary()) {
LibraryElement library = element;
ClassBuilder builder = new ClassBuilder();
if (classEmitter.emitFields(
library, builder, null, emitStatics: true)) {
List<CodeBuffer> buffers = elementBuffers[library];
var buffer = buffers[0];
if (buffer == null) {
buffers[0] = buffer = new CodeBuffer();
}
for (jsAst.Property property in builder.properties) {
if (!buffer.isEmpty) buffer.write(',$n');
buffer.addBuffer(jsAst.prettyPrint(property, compiler));
}
}
}
}
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));
}
var map = new jsAst.ObjectInitializer(properties);
mainBuffer.write(
jsAst.prettyPrint(
js('init.mangledNames = #', map).toStatement(), compiler));
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));
}
var map = new jsAst.ObjectInitializer(properties);
mainBuffer.write(
jsAst.prettyPrint(
js('init.mangledGlobalNames = #', map).toStatement(),
compiler));
if (compiler.enableMinification) {
mainBuffer.write(';');
}
}
mainBuffer
..write(getReflectionDataParser(classesCollector, namer))
..write('([$n');
List<Element> sortedElements =
Elements.sortedByPosition(elementBuffers.keys);
bool hasPendingStatics = false;
for (Element element in sortedElements) {
if (!element.isLibrary()) {
for (CodeBuffer b in elementBuffers[element]) {
if (b != null) {
hasPendingStatics = true;
compiler.reportInfo(
element, MessageKind.GENERIC, {'text': 'Pending statics.'});
print(b.getText());
}
}
continue;
}
LibraryElement library = element;
List<CodeBuffer> buffers = elementBuffers[library];
var buffer = buffers[0];
var uri = library.canonicalUri;
if (uri.scheme == 'file' && compiler.sourceMapUri != null) {
// TODO(ahe): It is a hack to use compiler.sourceMapUri
// here. It should be relative to the main JavaScript
// output file.
uri = relativize(
compiler.sourceMapUri, library.canonicalUri, false);
}
if (buffer != null) {
var metadata = metadataEmitter.buildMetadataFunction(library);
mainBuffer
..write('["${library.getLibraryName()}",$_')
..write('"${uri}",$_')
..write(metadata == null
? "" : jsAst.prettyPrint(metadata, compiler))
..write(',$_')
..write(namer.globalObjectFor(library))
..write(',$_')
..write('{$n')
..addBuffer(buffer)
..write('}');
if (library == compiler.mainApp) {
mainBuffer.write(',${n}1');
}
mainBuffer.write('],$n');
}
buffer = buffers[1];
if (buffer != null) {
deferredLibraries
..write('["${library.getLibraryName()}",$_')
..write('"${uri}",$_')
..write('[],$_')
..write(namer.globalObjectFor(library))
..write(',$_')
..write('{$n')
..addBuffer(buffer)
..write('}],$n');
}
elementBuffers[library] = const [];
}
if (hasPendingStatics) {
compiler.internalError('Pending statics (see above).');
}
mainBuffer.write('])$N');
emitFinishClassesInvocationIfNecessary(mainBuffer);
classesCollector = oldClassesCollector;
}
containerBuilder.emitStaticFunctionGetters(mainBuffer);
typeTestEmitter.emitRuntimeTypeSupport(mainBuffer);
interceptorEmitter.emitGetInterceptorMethods(mainBuffer);
// Constants in checked mode call into RTI code to set type information
// which may need getInterceptor methods, so we have to make sure that
// [emitGetInterceptorMethods] has been called.
emitCompileTimeConstants(mainBuffer);
// Static field initializations require the classes and compile-time
// constants to be set up.
emitStaticNonFinalFieldInitializations(mainBuffer);
interceptorEmitter.emitOneShotInterceptors(mainBuffer);
interceptorEmitter.emitInterceptedNames(mainBuffer);
interceptorEmitter.emitMapTypeToInterceptor(mainBuffer);
emitLazilyInitializedStaticFields(mainBuffer);
mainBuffer.add(nativeBuffer);
metadataEmitter.emitMetadata(mainBuffer);
isolateProperties = isolatePropertiesName;
// The following code should not use the short-hand for the
// initialStatics.
mainBuffer.add('${namer.CURRENT_ISOLATE}$_=${_}null$N');
emitFinishIsolateConstructorInvocation(mainBuffer);
mainBuffer.add(
'${namer.CURRENT_ISOLATE}$_=${_}new ${namer.isolateName}()$N');
emitConvertToFastObjectFunction();
for (String globalObject in Namer.reservedGlobalObjectNames) {
mainBuffer.add('$globalObject = convertToFastObject($globalObject)$N');
}
if (DEBUG_FAST_OBJECTS) {
ClassElement primitives =
compiler.findHelper('Primitives');
FunctionElement printHelper =
compiler.lookupElementIn(
primitives, 'printString');
String printHelperName = namer.isolateAccess(printHelper);
mainBuffer.add('''
// The following only works on V8 when run with option "--allow-natives-syntax".
if (typeof $printHelperName === "function") {
$printHelperName("Size of global helper object: "
+ String(Object.getOwnPropertyNames(H).length)
+ ", fast properties " + %HasFastProperties(H));
$printHelperName("Size of global platform object: "
+ String(Object.getOwnPropertyNames(P).length)
+ ", fast properties " + %HasFastProperties(P));
$printHelperName("Size of global dart:html object: "
+ String(Object.getOwnPropertyNames(W).length)
+ ", fast properties " + %HasFastProperties(W));
$printHelperName("Size of isolate properties object: "
+ String(Object.getOwnPropertyNames(\$).length)
+ ", fast properties " + %HasFastProperties(\$));
$printHelperName("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++) {
$printHelperName("\$." + names[i]);
}
}
''');
for (String object in Namer.userGlobalObjects) {
mainBuffer.add('''
if (typeof $printHelperName === "function") {
$printHelperName("Size of $object: "
+ String(Object.getOwnPropertyNames($object).length)
+ ", fast properties " + %HasFastProperties($object));
}
''');
}
}
emitMain(mainBuffer);
jsAst.FunctionDeclaration precompiledFunctionAst =
buildPrecompiledFunction();
emitInitFunction(mainBuffer);
if (!areAnyElementsDeferred) {
mainBuffer.add('})()\n');
} else {
mainBuffer.add('\n');
}
compiler.assembledCode = mainBuffer.getText();
outputSourceMap(compiler.assembledCode, '');
mainBuffer.write(
jsAst.prettyPrint(
precompiledFunctionAst, compiler,
allowVariableMinification: false).getText());
compiler.outputProvider('', 'precompiled.js')
..add(mainBuffer.getText())
..close();
emitDeferredCode();
});
return compiler.assembledCode;
}
CodeBuffer bufferForElement(Element element, CodeBuffer eagerBuffer) {
Element owner = element.getLibrary();
if (!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.getEnclosingClassOrCompilationUnit().declaration;
if (compiler.codegenWorld.instantiatedClasses.contains(cls)
&& !cls.isNative()) {
owner = cls;
}
}
if (owner == null) {
compiler.internalErrorOnElement(element, 'Owner is null');
}
List<CodeBuffer> buffers = elementBuffers.putIfAbsent(
owner, () => <CodeBuffer>[null, null]);
bool deferred = isDeferred(element);
int index = deferred ? 1 : 0;
CodeBuffer buffer = buffers[index];
if (buffer == null) {
buffer = buffers[index] = new CodeBuffer();
}
return buffer;
}
/**
* Returns the appropriate buffer for [constant]. If [constant] is
* itself an instance of a deferred type (or built from constants
* that are instances of deferred types) attempting to use
* [constant] before the deferred type has been loaded will not
* work, and [constant] itself must be deferred.
*/
CodeBuffer bufferForConstant(Constant constant, CodeBuffer eagerBuffer) {
var queue = new Queue()..add(constant);
while (!queue.isEmpty) {
constant = queue.removeFirst();
if (isDeferred(constant.computeType(compiler).element)) {
return deferredConstants;
}
queue.addAll(constant.getDependencies());
}
return eagerBuffer;
}
void emitDeferredCode() {
if (deferredLibraries.isEmpty && deferredConstants.isEmpty) return;
var oldClassesCollector = classesCollector;
classesCollector = r"$$";
// It does not make sense to defer constants if there are no
// deferred elements.
assert(!deferredLibraries.isEmpty);
var buffer = new CodeBuffer()
..write(buildGeneratedBy())
..write('var old${namer.CURRENT_ISOLATE}$_='
'$_${namer.CURRENT_ISOLATE}$N'
// 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.
'${namer.CURRENT_ISOLATE}$_='
'$_${namer.isolateName}.prototype$N$n'
// The classesCollector object ($$).
'$classesCollector$_=$_{};$n')
..write(getReflectionDataParser(classesCollector, namer))
..write('([$n')
..addBuffer(deferredLibraries)
..write('])$N');
if (!deferredClasses.isEmpty) {
buffer.write(
'$finishClassesName($classesCollector,$_${namer.CURRENT_ISOLATE},'
'$_$isolatePropertiesName)$N');
}
buffer.write(
// Reset the classesCollector ($$).
'$classesCollector$_=${_}null$N$n'
'${namer.CURRENT_ISOLATE}$_=${_}old${namer.CURRENT_ISOLATE}$N');
classesCollector = oldClassesCollector;
if (!deferredConstants.isEmpty) {
buffer.addBuffer(deferredConstants);
}
String code = buffer.getText();
compiler.outputProvider('part', 'js')
..add(code)
..close();
outputSourceMap(compiler.assembledCode, 'part');
}
String buildGeneratedBy() {
var suffix = '';
if (compiler.hasBuildId) suffix = ' version: ${compiler.buildId}';
return '// Generated by dart2js, the Dart to JavaScript compiler$suffix.\n';
}
String buildSourceMap(CodeBuffer buffer, SourceFile compiledFile) {
SourceMapBuilder sourceMapBuilder =
new SourceMapBuilder(compiler.sourceMapUri);
buffer.forEachSourceLocation(sourceMapBuilder.addMapping);
return sourceMapBuilder.build(compiledFile);
}
void outputSourceMap(String code, String name) {
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, compiler.assembledCode);
String sourceMap = buildSourceMap(mainBuffer, compiledFile);
compiler.outputProvider(name, 'js.map')
..add(sourceMap)
..close();
}
bool isDeferred(Element element) {
return compiler.deferredLoadTask.isDeferred(element);
}
bool get areAnyElementsDeferred {
return compiler.deferredLoadTask.areAnyElementsDeferred;
}
}