blob: aecbea054017f9bbc9ce211ea84e9ff9efe9ef49 [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 js_backend;
/**
* A function element that represents a closure call. The signature is copied
* from the given element.
*/
class ClosureInvocationElement extends FunctionElement {
ClosureInvocationElement(SourceString name,
FunctionElement other)
: super.from(name, other, other.enclosingElement),
methodElement = other;
isInstanceMember() => true;
Element getOutermostEnclosingMemberOrTopLevel() => methodElement;
/**
* The [member] this invocation refers to.
*/
Element methodElement;
}
/**
* 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 {
bool needsInheritFunction = false;
bool needsDefineClass = false;
bool needsClosureClass = false;
bool needsLazyInitializer = false;
final Namer namer;
ConstantEmitter constantEmitter;
NativeEmitter nativeEmitter;
CodeBuffer boundClosureBuffer;
CodeBuffer mainBuffer;
/** Shorter access to [isolatePropertiesName]. Both here in the code, as
well as in the generated code. */
String isolateProperties;
String classesCollector;
final Map<int, String> boundClosureCache;
Set<ClassElement> checkedClasses;
final bool generateSourceMap;
CodeEmitterTask(Compiler compiler, Namer namer, this.generateSourceMap)
: boundClosureBuffer = new CodeBuffer(),
mainBuffer = new CodeBuffer(),
this.namer = namer,
boundClosureCache = new Map<int, String>(),
constantEmitter = new ConstantEmitter(compiler, namer),
super(compiler) {
nativeEmitter = new NativeEmitter(this);
}
void computeRequiredTypeChecks() {
assert(checkedClasses == null);
checkedClasses = new Set<ClassElement>();
compiler.codegenWorld.isChecks.forEach((DartType t) {
if (t is InterfaceType) checkedClasses.add(t.element);
});
}
void writeConstantToBuffer(Constant value, CodeBuffer buffer,
{emitCanonicalVersion: true}) {
if (emitCanonicalVersion) {
constantEmitter.emitCanonicalVersionOfConstant(value, buffer);
} else {
constantEmitter.emitJavaScriptCodeForConstant(value, buffer);
}
}
String get name => 'CodeEmitter';
String get defineClassName
=> '${namer.ISOLATE}.\$defineClass';
String get finishClassesName
=> '${namer.ISOLATE}.\$finishClasses';
String get finishIsolateConstructorName
=> '${namer.ISOLATE}.\$finishIsolateConstructor';
String get pendingClassesName
=> '${namer.ISOLATE}.\$pendingClasses';
String get isolatePropertiesName
=> '${namer.ISOLATE}.${namer.ISOLATE_PROPERTIES}';
String get supportsProtoName
=> 'supportsProto';
String get lazyInitializerName
=> '${namer.ISOLATE}.\$lazy';
final String GETTER_SUFFIX = "?";
final String SETTER_SUFFIX = "!";
final String GETTER_SETTER_SUFFIX = "=";
String get generateGetterSetterFunction {
return """
function(field, prototype) {
var len = field.length;
var lastChar = field[len - 1];
var needsGetter = lastChar == '$GETTER_SUFFIX' || lastChar == '$GETTER_SETTER_SUFFIX';
var needsSetter = lastChar == '$SETTER_SUFFIX' || lastChar == '$GETTER_SETTER_SUFFIX';
if (needsGetter || needsSetter) field = field.substring(0, len - 1);
if (needsGetter) {
var getterString = "return this." + field + ";";
"""
/* The supportsProtoCheck below depends on the getter/setter convention.
When changing here, update the protoCheck too. */
"""
prototype["get\$" + field] = new Function(getterString);
}
if (needsSetter) {
var setterString = "this." + field + " = v;";
prototype["set\$" + field] = new Function("v", setterString);
}
return field;
}""";
}
String get defineClassFunction {
// First the class name, then the super class name, followed by the fields
// (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", "B", ["x", "y"], {
// foo$1: function(y) {
// print(this.x + y);
// },
// bar$2: function(t, v) {
// this.x = t - v;
// },
// });
return """
function(cls, fields, prototype) {
var generateGetterSetter = $generateGetterSetterFunction;
var constructor;
if (typeof fields == 'function') {
constructor = fields;
} else {
var str = "function " + cls + "(";
var body = "";
for (var i = 0; i < fields.length; i++) {
if (i != 0) str += ", ";
var field = fields[i];
field = generateGetterSetter(field, prototype);
str += field;
body += "this." + field + " = " + field + ";\\n";
}
str += ") {" + body + "}\\n";
str += "return " + cls + ";";
constructor = new Function(str)();
}
constructor.prototype = prototype;
return constructor;
}""";
}
/** Needs defineClass to be defined. */
String get protoSupportCheck {
// On Firefox and Webkit browsers we can manipulate the __proto__
// directly. Opera claims to have __proto__ support, but it is buggy.
// So we have to do more checks.
// Opera bug was filed as DSK-370158, and fixed as CORE-47615
// (http://my.opera.com/desktopteam/blog/2012/07/20/more-12-01-fixes).
// If the browser does not support __proto__ we need to instantiate an
// object with the correct (internal) prototype set up correctly, and then
// copy the members.
return '''
var $supportsProtoName = false;
var tmp = $defineClassName('c', ['f?'], {}).prototype;
if (tmp.__proto__) {
tmp.__proto__ = {};
if (typeof tmp.get\$f !== "undefined") $supportsProtoName = true;
}
''';
}
String get finishClassesFunction {
// 'defineClass' does not require the classes to be constructed in order.
// Classes are initially just stored in the 'pendingClasses' field.
// 'finishClasses' takes all pending classes 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.
return '''
function(collectedClasses) {
var hasOwnProperty = Object.prototype.hasOwnProperty;
for (var cls in collectedClasses) {
if (hasOwnProperty.call(collectedClasses, cls)) {
var desc = collectedClasses[cls];
$isolatePropertiesName[cls] = $defineClassName(cls, desc[''], desc);
if (desc['super'] !== "") $pendingClassesName[cls] = desc['super'];
}
}
var pendingClasses = $pendingClassesName;
'''/* FinishClasses can be called multiple times. This means that we need to
clear the pendingClasses property. */'''
$pendingClassesName = {};
var finishedClasses = {};
function finishClass(cls) {
'''/* Opera does not support 'getOwnPropertyNames'. Therefore we use
hasOwnProperty instead. */'''
var hasOwnProperty = Object.prototype.hasOwnProperty;
if (hasOwnProperty.call(finishedClasses, cls)) return;
finishedClasses[cls] = true;
var superclass = pendingClasses[cls];
'''/* The superclass is only false (empty string) for Dart's Object class. */'''
if (!superclass) return;
finishClass(superclass);
var constructor = $isolatePropertiesName[cls];
var superConstructor = $isolatePropertiesName[superclass];
var prototype = constructor.prototype;
if ($supportsProtoName) {
prototype.__proto__ = superConstructor.prototype;
prototype.constructor = constructor;
} else {
function tmp() {};
tmp.prototype = superConstructor.prototype;
var newPrototype = new tmp();
constructor.prototype = newPrototype;
newPrototype.constructor = constructor;
for (var member in prototype) {
if (member == '' || member == 'super') continue;
if (hasOwnProperty.call(prototype, member)) {
newPrototype[member] = prototype[member];
}
}
}
}
for (var cls in pendingClasses) finishClass(cls);
}''';
}
String get finishIsolateConstructorFunction {
String isolate = namer.ISOLATE;
// We replace the old Isolate function with a new one that initializes
// all its field with the initial (and often final) value of all globals.
// This has two advantages:
// 1. the properties are in the object itself (thus avoiding to go through
// the prototype when looking up globals.
// 2. a new isolate goes through a (usually well optimized) constructor
// function of the form: "function() { this.x = ...; this.y = ...; }".
//
// Example: If [isolateProperties] is an object containing: x = 3 and
// A = function A() { /* constructor of class A. */ }, then we generate:
// str = "{
// var isolateProperties = Isolate.$isolateProperties;
// this.x = isolateProperties.x;
// this.A = isolateProperties.A;
// }";
// which is then dynamically evaluated:
// var newIsolate = new Function(str);
//
// We also copy over old values like the prototype, and the
// isolateProperties themselves.
return """function(oldIsolate) {
var isolateProperties = oldIsolate.${namer.ISOLATE_PROPERTIES};
var isolatePrototype = oldIsolate.prototype;
var str = "{\\n";
str += "var properties = $isolate.${namer.ISOLATE_PROPERTIES};\\n";
for (var staticName in isolateProperties) {
if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) {
str += "this." + staticName + "= properties." + staticName + ";\\n";
}
}
str += "}\\n";
var newIsolate = new Function(str);
newIsolate.prototype = isolatePrototype;
isolatePrototype.constructor = newIsolate;
newIsolate.${namer.ISOLATE_PROPERTIES} = isolateProperties;
return newIsolate;
}""";
}
String get lazyInitializerFunction {
String isolate = namer.CURRENT_ISOLATE;
JavaScriptBackend backend = compiler.backend;
String cyclicThrow = namer.isolateAccess(backend.cyclicThrowHelper);
return """
function(prototype, staticName, fieldName, getterName, lazyValue) {
var sentinelUndefined = {};
var sentinelInProgress = {};
prototype[fieldName] = sentinelUndefined;
var getter = new Function("{ return $isolate." + fieldName + ";}");
prototype[getterName] = function() {
var result = $isolate[fieldName];
try {
if (result === sentinelUndefined) {
$isolate[fieldName] = sentinelInProgress;
try {
result = $isolate[fieldName] = lazyValue();
} catch (e) {
if ($isolate[fieldName] === sentinelInProgress) {
$isolate[fieldName] = null;
}
throw e;
}
} else if (result === sentinelInProgress) {
$cyclicThrow(staticName);
}
return result;
} finally {
$isolate[getterName] = getter;
}
};
}""";
}
void addDefineClassAndFinishClassFunctionsIfNecessary(CodeBuffer buffer) {
if (needsDefineClass) {
buffer.add("$defineClassName = $defineClassFunction;\n");
buffer.add(protoSupportCheck);
buffer.add("$pendingClassesName = {};\n");
buffer.add("$finishClassesName = $finishClassesFunction;\n");
}
}
void addLazyInitializerFunctionIfNecessary(CodeBuffer buffer) {
if (needsLazyInitializer) {
buffer.add("$lazyInitializerName = $lazyInitializerFunction;\n");
}
}
void emitFinishIsolateConstructor(CodeBuffer buffer) {
String name = finishIsolateConstructorName;
String value = finishIsolateConstructorFunction;
buffer.add("$name = $value;\n");
}
void emitFinishIsolateConstructorInvocation(CodeBuffer buffer) {
String isolate = namer.ISOLATE;
buffer.add("$isolate = $finishIsolateConstructorName($isolate);\n");
}
/**
* Generate stubs to handle invocation of methods with optional
* arguments.
*
* A method like [: foo([x]) :] may be invoked by the following
* calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this
* function for detailed examples.
*/
void addParameterStub(FunctionElement member,
Selector selector,
DefineMemberFunction defineInstanceMember,
Set<String> alreadyGenerated) {
FunctionSignature parameters = member.computeSignature(compiler);
int positionalArgumentCount = selector.positionalArgumentCount;
if (positionalArgumentCount == parameters.parameterCount) {
assert(selector.namedArgumentCount == 0);
return;
}
if (parameters.optionalParametersAreNamed
&& selector.namedArgumentCount == parameters.optionalParameterCount) {
// If the selector has the same number of named arguments as
// the element, we don't need to add a stub. The call site will
// hit the method directly.
return;
}
ConstantHandler handler = compiler.constantHandler;
List<SourceString> names = selector.getOrderedNamedArguments();
String invocationName =
namer.instanceMethodInvocationName(member.getLibrary(), member.name,
selector);
if (alreadyGenerated.contains(invocationName)) return;
alreadyGenerated.add(invocationName);
CodeBuffer buffer = new CodeBuffer();
buffer.add('function(');
// The parameters that this stub takes.
List<String> parametersBuffer = new List<String>(selector.argumentCount);
// The arguments that will be passed to the real method.
List<String> argumentsBuffer = new List<String>(parameters.parameterCount);
int count = 0;
int indexOfLastOptionalArgumentInParameters = positionalArgumentCount - 1;
TreeElements elements =
compiler.enqueuer.resolution.getCachedElements(member);
parameters.orderedForEachParameter((Element element) {
String jsName = JsNames.getValid(element.name.slowToString());
if (count < positionalArgumentCount) {
parametersBuffer[count] = jsName;
argumentsBuffer[count] = jsName;
} else {
int index = names.indexOf(element.name);
if (index != -1) {
indexOfLastOptionalArgumentInParameters = count;
// The order of the named arguments is not the same as the
// one in the real method (which is in Dart source order).
argumentsBuffer[count] = jsName;
parametersBuffer[selector.positionalArgumentCount + index] = jsName;
// Note that [elements] may be null for a synthetized [member].
} else if (elements != null && elements.isParameterChecked(element)) {
CodeBuffer argumentBuffer = new CodeBuffer();
writeConstantToBuffer(SentinelConstant.SENTINEL, argumentBuffer);
argumentsBuffer[count] = argumentBuffer.toString();
} else {
Constant value = handler.initialVariableValues[element];
if (value == null) {
argumentsBuffer[count] = NullConstant.JsNull;
} else {
if (!value.isNull()) {
// If the value is the null constant, we should not pass it
// down to the native method.
indexOfLastOptionalArgumentInParameters = count;
}
CodeBuffer argumentBuffer = new CodeBuffer();
writeConstantToBuffer(value, argumentBuffer);
argumentsBuffer[count] = argumentBuffer.toString();
}
}
}
count++;
});
String parametersString = Strings.join(parametersBuffer, ",");
buffer.add('$parametersString) {\n');
if (member.isNative()) {
nativeEmitter.generateParameterStub(
member, invocationName, parametersString, argumentsBuffer,
indexOfLastOptionalArgumentInParameters, buffer);
} else {
String arguments = Strings.join(argumentsBuffer, ",");
buffer.add(' return this.${namer.getName(member)}($arguments)');
}
buffer.add('\n}');
defineInstanceMember(invocationName, buffer);
}
void addParameterStubs(FunctionElement member,
DefineMemberFunction defineInstanceMember) {
// We fill the lists depending on the selector. For example,
// take method foo:
// foo(a, b, {c, d});
//
// We may have multiple ways of calling foo:
// (1) foo(1, 2);
// (2) foo(1, 2, c: 3);
// (3) foo(1, 2, d: 4);
// (4) foo(1, 2, c: 3, d: 4);
// (5) foo(1, 2, d: 4, c: 3);
//
// What we generate at the call sites are:
// (1) foo$2(1, 2);
// (2) foo$3$c(1, 2, 3);
// (3) foo$3$d(1, 2, 4);
// (4) foo$4$c$d(1, 2, 3, 4);
// (5) foo$4$c$d(1, 2, 3, 4);
//
// The stubs we generate are (expressed in Dart):
// (1) foo$2(a, b) => foo$4$c$d(a, b, null, null)
// (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null);
// (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d);
// (4) No stub generated, call is direct.
// (5) No stub generated, call is direct.
// Keep a cache of which stubs have already been generated, to
// avoid duplicates. Note that even if selectors are
// canonicalized, we would still need this cache: a typed selector
// on A and a typed selector on B could yield the same stub.
Set<String> generatedStubNames = new Set<String>();
if (compiler.enabledFunctionApply
&& member.name == Namer.CLOSURE_INVOCATION_NAME) {
// If [Function.apply] is called, we pessimistically compile all
// possible stubs for this closure.
FunctionSignature signature = member.computeSignature(compiler);
Set<Selector> selectors = signature.optionalParametersAreNamed
? computeNamedSelectors(signature, member)
: computeOptionalSelectors(signature, member);
for (Selector selector in selectors) {
addParameterStub(
member, selector, defineInstanceMember, generatedStubNames);
}
} else {
Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
if (selectors == null) return;
for (Selector selector in selectors) {
if (!selector.applies(member, compiler)) continue;
addParameterStub(
member, selector, defineInstanceMember, generatedStubNames);
}
}
}
/**
* Compute the set of possible selectors in the presence of named
* parameters.
*/
Set<Selector> computeNamedSelectors(FunctionSignature signature,
FunctionElement element) {
Set<Selector> selectors = new Set<Selector>();
// Add the selector that does not have any optional argument.
selectors.add(new Selector(SelectorKind.CALL,
element.name,
element.getLibrary(),
signature.requiredParameterCount,
<SourceString>[]));
// For each optional parameter, we iterator over the set of
// already computed selectors and create new selectors with that
// parameter now being passed.
signature.forEachOptionalParameter((Element element) {
Set<Selector> newSet = new Set<Selector>();
selectors.forEach((Selector other) {
List<SourceString> namedArguments = [element.name];
namedArguments.addAll(other.namedArguments);
newSet.add(new Selector(other.kind,
other.name,
other.library,
other.argumentCount + 1,
namedArguments));
});
selectors.addAll(newSet);
});
return selectors;
}
/**
* Compute the set of possible selectors in the presence of optional
* non-named parameters.
*/
Set<Selector> computeOptionalSelectors(FunctionSignature signature,
FunctionElement element) {
Set<Selector> selectors = new Set<Selector>();
// Add the selector that does not have any optional argument.
selectors.add(new Selector(SelectorKind.CALL,
element.name,
element.getLibrary(),
signature.requiredParameterCount,
<SourceString>[]));
// For each optional parameter, we increment the number of passed
// argument.
for (int i = 1; i <= signature.optionalParameterCount; i++) {
selectors.add(new Selector(SelectorKind.CALL,
element.name,
element.getLibrary(),
signature.requiredParameterCount + i,
<SourceString>[]));
}
return selectors;
}
bool instanceFieldNeedsGetter(Element member) {
assert(member.isField());
return compiler.codegenWorld.hasInvokedGetter(member, compiler);
}
bool instanceFieldNeedsSetter(Element member) {
assert(member.isField());
return (!member.modifiers.isFinalOrConst())
&& compiler.codegenWorld.hasInvokedSetter(member, compiler);
}
String compiledFieldName(Element member) {
assert(member.isField());
return member.isNative()
? member.name.slowToString()
: namer.getName(member);
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [member] must be a declaration element.
*/
void addInstanceMember(Element member,
DefineMemberFunction defineInstanceMember) {
assert(invariant(member, member.isDeclaration));
// TODO(floitsch): we don't need to deal with members of
// uninstantiated classes, that have been overwritten by subclasses.
if (member.isFunction()
|| member.isGenerativeConstructorBody()
|| member.isAccessor()) {
if (member.isAbstract(compiler)) return;
CodeBuffer codeBuffer = compiler.codegenWorld.generatedCode[member];
if (codeBuffer == null) return;
defineInstanceMember(namer.getName(member), codeBuffer);
codeBuffer = compiler.codegenWorld.generatedBailoutCode[member];
if (codeBuffer != null) {
defineInstanceMember(namer.getBailoutName(member), codeBuffer);
}
FunctionElement function = member;
FunctionSignature parameters = function.computeSignature(compiler);
if (!parameters.optionalParameters.isEmpty) {
addParameterStubs(member, defineInstanceMember);
}
} else if (!member.isField()) {
compiler.internalError('unexpected kind: "${member.kind}"',
element: member);
}
emitExtraAccessors(member, defineInstanceMember);
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [classElement] must be a declaration element.
*/
void emitInstanceMembers(ClassElement classElement,
CodeBuffer buffer,
bool needsLeadingComma) {
assert(invariant(classElement, classElement.isDeclaration));
bool needsComma = needsLeadingComma;
void defineInstanceMember(String name, CodeBuffer memberBuffer) {
if (needsComma) buffer.add(',');
needsComma = true;
buffer.add('\n');
buffer.add(' $name: ');
buffer.add(memberBuffer);
}
classElement.implementation.forEachMember(
(ClassElement enclosing, Element member) {
assert(invariant(classElement, member.isDeclaration));
if (member.isInstanceMember()) {
addInstanceMember(member, defineInstanceMember);
}
},
includeBackendMembers: true);
generateIsTestsOn(classElement, (ClassElement other) {
String code;
if (other.isObject(compiler)) return;
if (nativeEmitter.requiresNativeIsCheck(other)) {
code = 'function() { return true; }';
} else {
code = 'true';
}
CodeBuffer typeTestBuffer = new CodeBuffer();
typeTestBuffer.add(code);
defineInstanceMember(namer.operatorIs(other), typeTestBuffer);
});
if (identical(classElement, compiler.objectClass)
&& compiler.enabledNoSuchMethod) {
// Emit the noSuchMethod handlers on the Object prototype now,
// so that the code in the dynamicFunction helper can find
// them. Note that this helper is invoked before analyzing the
// full JS script.
if (!nativeEmitter.handleNoSuchMethod) {
emitNoSuchMethodHandlers(defineInstanceMember);
}
}
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [classElement] must be a declaration element.
*/
void visitClassFields(ClassElement classElement,
void addField(Element member,
String name,
bool needsGetter,
bool needsSetter,
bool needsCheckedSetter)) {
assert(invariant(classElement, classElement.isDeclaration));
// If the class is never instantiated we still need to set it up for
// inheritance purposes, but we can simplify its JavaScript constructor.
bool isInstantiated =
compiler.codegenWorld.instantiatedClasses.contains(classElement);
void visitField(ClassElement enclosingClass, Element member) {
assert(!member.isNative());
assert(invariant(classElement, member.isDeclaration));
LibraryElement library = member.getLibrary();
SourceString name = member.name;
bool isPrivate = name.isPrivate();
// See if we can dynamically create getters and setters.
// We can only generate getters and setters for [classElement] since
// the fields of super classes could be overwritten with getters or
// setters.
bool needsGetter = false;
bool needsSetter = false;
// We need to name shadowed fields differently, so they don't clash with
// the non-shadowed field.
bool isShadowed = false;
if (identical(enclosingClass, classElement)) {
needsGetter = instanceFieldNeedsGetter(member);
needsSetter = instanceFieldNeedsSetter(member);
} else {
isShadowed = classElement.isShadowedByField(member);
}
if ((isInstantiated && !enclosingClass.isNative())
|| needsGetter
|| needsSetter) {
String fieldName = isShadowed
? namer.shadowedFieldName(member)
: namer.getName(member);
bool needsCheckedSetter = false;
if (needsSetter && compiler.enableTypeAssertions
&& canGenerateCheckedSetter(member)) {
needsCheckedSetter = true;
needsSetter = false;
}
// Getters and setters with suffixes will be generated dynamically.
addField(member,
fieldName,
needsGetter,
needsSetter,
needsCheckedSetter);
}
}
// If a class is not instantiated then we add the field just so we can
// generate the field getter/setter dynamically. Since this is only
// allowed on fields that are in [classElement] we don't need to visit
// superclasses for non-instantiated classes.
classElement.implementation.forEachInstanceField(
visitField,
includeBackendMembers: true,
includeSuperMembers: isInstantiated && !classElement.isNative());
}
void generateGetter(Element member, String fieldName, CodeBuffer buffer) {
String getterName = namer.getterName(member.getLibrary(), member.name);
buffer.add("$getterName: function() { return this.$fieldName; }");
}
void generateSetter(Element member, String fieldName, CodeBuffer buffer) {
String setterName = namer.setterName(member.getLibrary(), member.name);
buffer.add("$setterName: function(v) { this.$fieldName = v; }");
}
bool canGenerateCheckedSetter(Element member) {
DartType type = member.computeType(compiler);
if (type.element.isTypeVariable()
|| type.element == compiler.dynamicClass
|| type.element == compiler.objectClass) {
// TODO(ngeoffray): Support type checks on type parameters.
return false;
}
return true;
}
void generateCheckedSetter(Element member,
String fieldName,
CodeBuffer buffer) {
assert(canGenerateCheckedSetter(member));
DartType type = member.computeType(compiler);
SourceString helper = compiler.backend.getCheckedModeHelper(type);
FunctionElement helperElement = compiler.findHelper(helper);
String helperName = namer.isolateAccess(helperElement);
String additionalArgument = '';
if (helperElement.computeSignature(compiler).parameterCount != 1) {
additionalArgument = ", '${namer.operatorIs(type.element)}'";
}
String setterName = namer.setterName(member.getLibrary(), member.name);
buffer.add("$setterName: function(v) { "
"this.$fieldName = $helperName(v$additionalArgument); }");
}
void emitClassConstructor(ClassElement classElement, CodeBuffer buffer) {
/* Do nothing. */
}
void emitClassFields(ClassElement classElement, CodeBuffer buffer) {
buffer.add('"": [');
bool isFirstField = true;
visitClassFields(classElement, (Element member,
String name,
bool needsGetter,
bool needsSetter,
bool needsCheckedSetter) {
if (isFirstField) {
isFirstField = false;
} else {
buffer.add(", ");
}
buffer.add('"$name');
if (needsGetter && needsSetter) {
buffer.add(GETTER_SETTER_SUFFIX);
} else if (needsGetter) {
buffer.add(GETTER_SUFFIX);
} else if (needsSetter) {
buffer.add(SETTER_SUFFIX);
}
buffer.add('"');
});
buffer.add(']');
}
/** Each getter/setter must be prefixed with a ",\n ". */
void emitClassGettersSetters(ClassElement classElement, CodeBuffer buffer,
{bool omitLeadingComma: false}) {
visitClassFields(classElement, (Element member,
String name,
bool needsGetter,
bool needsSetter,
bool needsCheckedSetter) {
if (needsCheckedSetter) {
assert(!needsSetter);
if (!omitLeadingComma) {
buffer.add(",\n ");
} else {
omitLeadingComma = false;
}
generateCheckedSetter(member, name, buffer);
}
});
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [classElement] must be a declaration element.
*/
void generateClass(ClassElement classElement, CodeBuffer buffer) {
assert(invariant(classElement, classElement.isDeclaration));
if (classElement.isNative()) {
nativeEmitter.generateNativeClass(classElement);
return;
} else {
// TODO(ngeoffray): Instead of switching between buffer, we
// should create code sections, and decide where to emit them at
// the end.
buffer = mainBuffer;
}
needsDefineClass = true;
String className = namer.getName(classElement);
ClassElement superclass = classElement.superclass;
String superName = "";
if (superclass != null) {
superName = namer.getName(superclass);
}
buffer.add('$classesCollector.$className = {');
emitClassConstructor(classElement, buffer);
emitClassFields(classElement, buffer);
// TODO(floitsch): the emitInstanceMember should simply always emit a ',\n'.
// That does currently not work because the native classes have a different
// syntax.
buffer.add(',\n "super": "$superName"');
emitClassGettersSetters(classElement, buffer);
emitInstanceMembers(classElement, buffer, true);
buffer.add('\n};\n\n');
}
/**
* Generate "is tests" for [cls]: itself, and the "is tests" for the
* classes it implements. We don't need to add the "is tests" of the
* super class because they will be inherited at runtime.
*/
void generateIsTestsOn(ClassElement cls,
void emitIsTest(ClassElement element)) {
if (checkedClasses.contains(cls)) {
emitIsTest(cls);
}
Set<Element> generated = new Set<Element>();
for (DartType interfaceType in cls.interfaces) {
generateInterfacesIsTests(interfaceType.element, emitIsTest, generated);
}
}
/**
* Generate "is tests" where [cls] is being implemented.
*/
void generateInterfacesIsTests(ClassElement cls,
void emitIsTest(ClassElement element),
Set<Element> alreadyGenerated) {
void tryEmitTest(ClassElement cls) {
if (!alreadyGenerated.contains(cls) && checkedClasses.contains(cls)) {
alreadyGenerated.add(cls);
emitIsTest(cls);
}
};
tryEmitTest(cls);
for (DartType interfaceType in cls.interfaces) {
Element element = interfaceType.element;
tryEmitTest(element);
generateInterfacesIsTests(element, emitIsTest, alreadyGenerated);
}
// We need to also emit "is checks" for the superclass and its supertypes.
ClassElement superclass = cls.superclass;
if (superclass != null) {
tryEmitTest(superclass);
generateInterfacesIsTests(superclass, emitIsTest, alreadyGenerated);
}
}
void emitClasses(CodeBuffer buffer) {
// Compute the required type checks to know which classes need a
// 'is$' method.
computeRequiredTypeChecks();
Set<ClassElement> instantiatedClasses =
compiler.codegenWorld.instantiatedClasses;
Set<ClassElement> neededClasses =
new Set<ClassElement>.from(instantiatedClasses);
for (ClassElement element in instantiatedClasses) {
for (ClassElement superclass = element.superclass;
superclass != null;
superclass = superclass.superclass) {
if (neededClasses.contains(superclass)) break;
neededClasses.add(superclass);
}
}
List<ClassElement> sortedClasses =
new List<ClassElement>.from(neededClasses);
sortedClasses.sort((ClassElement class1, ClassElement class2) {
// We sort by the ids of the classes. There is no guarantee that these
// ids are meaningful (or even deterministic), but in the current
// implementation they are increasing within a source file.
return class1.id - class2.id;
});
// 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) {
SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD;
Selector noSuchMethodSelector = new Selector.noSuchMethod();
for (ClassElement element in sortedClasses) {
if (!element.isNative()) continue;
Element member = element.lookupLocalMember(noSuchMethodName);
if (member == null) continue;
if (noSuchMethodSelector.applies(member, compiler)) {
nativeEmitter.handleNoSuchMethod = true;
break;
}
}
}
for (ClassElement element in sortedClasses) {
generateClass(element, buffer);
}
// The closure class could have become necessary because of the generation
// of stubs.
ClassElement closureClass = compiler.closureClass;
if (needsClosureClass && !instantiatedClasses.contains(closureClass)) {
generateClass(closureClass, buffer);
}
}
void emitFinishClassesInvocationIfNecessary(CodeBuffer buffer) {
if (needsDefineClass) {
buffer.add("$finishClassesName($classesCollector);\n");
// Reset the map.
buffer.add("$classesCollector = {};\n");
}
}
void emitStaticFunctionWithNamer(CodeBuffer buffer,
Element element,
CodeBuffer functionBuffer,
String functionNamer(Element element)) {
String functionName = functionNamer(element);
buffer.add('$isolateProperties.$functionName = ');
buffer.add(functionBuffer);
buffer.add(';\n\n');
}
void emitStaticFunctionsWithNamer(CodeBuffer buffer,
Map<Element, CodeBuffer> generatedCode,
String functionNamer(Element element)) {
generatedCode.forEach((Element element, CodeBuffer functionBuffer) {
if (!element.isInstanceMember() && !element.isField()) {
emitStaticFunctionWithNamer(
buffer, element, functionBuffer,functionNamer);
}
});
}
void emitStaticFunctions(CodeBuffer buffer) {
emitStaticFunctionsWithNamer(buffer,
compiler.codegenWorld.generatedCode,
namer.getName);
emitStaticFunctionsWithNamer(buffer,
compiler.codegenWorld.generatedBailoutCode,
namer.getBailoutName);
}
void emitStaticFunctionGetters(CodeBuffer buffer) {
Set<FunctionElement> functionsNeedingGetter =
compiler.codegenWorld.staticFunctionsNeedingGetter;
for (FunctionElement element in functionsNeedingGetter) {
// The static function does not have the correct name. Since
// [addParameterStubs] use the name to create its stubs we simply
// create a fake element with the correct name.
// Note: the callElement will not have any enclosingElement.
FunctionElement callElement =
new ClosureInvocationElement(Namer.CLOSURE_INVOCATION_NAME, element);
String staticName = namer.getName(element);
String invocationName = namer.instanceMethodName(callElement);
String fieldAccess = '$isolateProperties.$staticName';
buffer.add("$fieldAccess.$invocationName = $fieldAccess;\n");
addParameterStubs(callElement, (String name, CodeBuffer value) {
buffer.add('$fieldAccess.$name = $value;\n');
});
// If a static function is used as a closure we need to add its name
// in case it is used in spawnFunction.
String fieldName = namer.STATIC_CLOSURE_NAME_NAME;
buffer.add('$fieldAccess.$fieldName = "$staticName";\n');
}
}
void emitBoundClosureClassHeader(String mangledName,
String superName,
CodeBuffer buffer) {
buffer.add("""
$classesCollector.$mangledName = {'':
['self', 'target'],
'super': '$superName',
""");
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [member] must be a declaration element.
*/
void emitDynamicFunctionGetter(FunctionElement member,
DefineMemberFunction defineInstanceMember) {
assert(invariant(member, member.isDeclaration));
// For every method that has the same name as a property-get we create a
// getter that returns a bound closure. Say we have a class 'A' with method
// 'foo' and somewhere in the code there is a dynamic property get of
// 'foo'. Then we generate the following code (in pseudo Dart/JavaScript):
//
// class A {
// foo(x, y, z) { ... } // Original function.
// get foo { return new BoundClosure499(this, "foo"); }
// }
// class BoundClosure499 extends Closure {
// var self;
// BoundClosure499(this.self, this.name);
// $call3(x, y, z) { return self[name](x, y, z); }
// }
// TODO(floitsch): share the closure classes with other classes
// if they share methods with the same signature. Currently we do this only
// if there are no optional parameters. Closures with optional parameters
// are more difficult to canonicalize because they would need to have the
// same default values.
bool hasOptionalParameters = member.optionalParameterCount(compiler) != 0;
int parameterCount = member.parameterCount(compiler);
String closureClass =
hasOptionalParameters ? null : boundClosureCache[parameterCount];
if (closureClass == null) {
// Either the class was not cached yet, or there are optional parameters.
// Create a new closure class.
SourceString name = const SourceString("BoundClosure");
ClassElement closureClassElement = new ClosureClassElement(
name, compiler, member, member.getCompilationUnit());
String mangledName = namer.getName(closureClassElement);
String superName = namer.getName(closureClassElement.superclass);
needsClosureClass = true;
// Define the constructor with a name so that Object.toString can
// find the class name of the closure class.
emitBoundClosureClassHeader(mangledName, superName, boundClosureBuffer);
// Now add the methods on the closure class. The instance method does not
// have the correct name. Since [addParameterStubs] use the name to create
// its stubs we simply create a fake element with the correct name.
// Note: the callElement will not have any enclosingElement.
FunctionElement callElement =
new ClosureInvocationElement(Namer.CLOSURE_INVOCATION_NAME, member);
String invocationName = namer.instanceMethodName(callElement);
List<String> arguments = new List<String>(parameterCount);
for (int i = 0; i < parameterCount; i++) {
arguments[i] = "p$i";
}
String joinedArgs = Strings.join(arguments, ", ");
boundClosureBuffer.add(
"$invocationName: function($joinedArgs) {");
boundClosureBuffer.add(" return this.self[this.target]($joinedArgs);");
boundClosureBuffer.add(" }");
addParameterStubs(callElement, (String stubName, CodeBuffer memberValue) {
boundClosureBuffer.add(',\n $stubName: $memberValue');
});
boundClosureBuffer.add("\n};\n");
closureClass = namer.isolateAccess(closureClassElement);
// Cache it.
if (!hasOptionalParameters) {
boundClosureCache[parameterCount] = closureClass;
}
}
// And finally the getter.
String getterName = namer.getterName(member.getLibrary(), member.name);
String targetName = namer.instanceMethodName(member);
CodeBuffer getterBuffer = new CodeBuffer();
getterBuffer.add(
"function() { return new $closureClass(this, '$targetName'); }");
defineInstanceMember(getterName, getterBuffer);
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [member] must be a declaration element.
*/
void emitCallStubForGetter(Element member,
Set<Selector> selectors,
DefineMemberFunction defineInstanceMember) {
assert(invariant(member, member.isDeclaration));
LibraryElement memberLibrary = member.getLibrary();
String getter;
if (member.isGetter()) {
getter = "this.${namer.getterName(member.getLibrary(), member.name)}()";
} else {
String name = namer.instanceFieldName(memberLibrary, member.name);
getter = "this.$name";
}
for (Selector selector in selectors) {
if (selector.applies(member, compiler)) {
String invocationName =
namer.instanceMethodInvocationName(memberLibrary, member.name,
selector);
SourceString callName = Namer.CLOSURE_INVOCATION_NAME;
String closureCallName =
namer.instanceMethodInvocationName(memberLibrary, callName,
selector);
List<String> arguments = <String>[];
for (int i = 0; i < selector.argumentCount; i++) {
arguments.add("arg$i");
}
String joined = Strings.join(arguments, ", ");
CodeBuffer getterBuffer = new CodeBuffer();
getterBuffer.add(
"function($joined) { return $getter.$closureCallName($joined); }");
defineInstanceMember(invocationName, getterBuffer);
}
}
}
void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) {
ConstantHandler handler = compiler.constantHandler;
List<VariableElement> staticNonFinalFields =
handler.getStaticNonFinalFieldsForEmission();
for (Element element in staticNonFinalFields) {
buffer.add('$isolateProperties.${namer.getName(element)} = ');
compiler.withCurrentElement(element, () {
Constant initialValue = handler.getInitialValueFor(element);
writeConstantToBuffer(initialValue, buffer);
});
buffer.add(';\n');
}
}
void emitLazilyInitializedStaticFields(CodeBuffer buffer) {
ConstantHandler handler = compiler.constantHandler;
List<VariableElement> lazyFields =
handler.getLazilyInitializedFieldsForEmission();
if (!lazyFields.isEmpty) {
needsLazyInitializer = true;
for (VariableElement element in lazyFields) {
assert(compiler.codegenWorld.generatedBailoutCode[element] == null);
StringBuffer code = compiler.codegenWorld.generatedCode[element];
assert(code != null);
// 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.
buffer.add("$lazyInitializerName(");
buffer.add(isolateProperties);
buffer.add(", '");
buffer.add(element.name.slowToString());
buffer.add("', '");
buffer.add(namer.getName(element));
buffer.add("', '");
buffer.add(namer.getLazyInitializerName(element));
buffer.add("', ");
buffer.add(code);
buffer.add(");\n");
}
}
}
void emitCompileTimeConstants(CodeBuffer buffer) {
ConstantHandler handler = compiler.constantHandler;
List<Constant> constants = handler.getConstantsForEmission();
bool addedMakeConstantList = false;
for (Constant constant in constants) {
// No need to emit functions. We already did that.
if (constant.isFunction()) continue;
// Numbers, strings and booleans are currently always inlined.
if (constant.isPrimitive()) continue;
String name = namer.constantName(constant);
// 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 (name == null) continue;
if (!addedMakeConstantList && constant.isList()) {
addedMakeConstantList = true;
emitMakeConstantList(buffer);
}
buffer.add('$isolateProperties.$name = ');
writeConstantToBuffer(constant, buffer, emitCanonicalVersion: false);
buffer.add(';\n');
}
}
void emitMakeConstantList(CodeBuffer buffer) {
buffer.add(namer.ISOLATE);
buffer.add(r'''.makeConstantList = function(list) {
list.immutable$list = true;
list.fixed$length = true;
return list;
};
''');
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [member] must be a declaration element.
*/
void emitExtraAccessors(Element member,
DefineMemberFunction defineInstanceMember) {
assert(invariant(member, member.isDeclaration));
if (member.isGetter() || member.isField()) {
Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
if (selectors != null && !selectors.isEmpty) {
emitCallStubForGetter(member, selectors, defineInstanceMember);
}
} else if (member.isFunction()) {
if (compiler.codegenWorld.hasInvokedGetter(member, compiler)) {
emitDynamicFunctionGetter(member, defineInstanceMember);
}
}
}
void emitNoSuchMethodHandlers(DefineMemberFunction defineInstanceMember) {
// Do not generate no such method handlers if there is no class.
if (compiler.codegenWorld.instantiatedClasses.isEmpty) return;
String noSuchMethodName = namer.publicInstanceMethodNameByArity(
Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT);
// Keep track of the JavaScript names we've already added so we
// do not introduce duplicates (bad for code size).
Set<String> addedJsNames = new Set<String>();
// Keep track of the noSuchMethod holders for each possible
// receiver type.
Map<ClassElement, Set<ClassElement>> noSuchMethodHolders =
new Map<ClassElement, Set<ClassElement>>();
Set<ClassElement> noSuchMethodHoldersFor(DartType type) {
ClassElement element = type.element;
Set<ClassElement> result = noSuchMethodHolders[element];
if (result == null) {
// For now, we check the entire world to see if an object of
// the given type may have a user-defined noSuchMethod
// implementation. We could do better by only looking at
// instantiated (or otherwise needed) classes.
result = compiler.world.findNoSuchMethodHolders(type);
noSuchMethodHolders[element] = result;
}
return result;
}
CodeBuffer generateMethod(String methodName, Selector selector) {
// Values match JSInvocationMirror in js-helper library.
const int METHOD = 0;
const int GETTER = 1;
const int SETTER = 2;
int type = METHOD;
if (selector.isGetter()) {
type = GETTER;
} else if (selector.isSetter()) {
type = SETTER;
}
CodeBuffer args = new CodeBuffer();
for (int i = 0; i < selector.argumentCount; i++) {
if (i != 0) args.add(', ');
args.add('\$$i');
}
CodeBuffer argNames = new CodeBuffer();
List<SourceString> names = selector.getOrderedNamedArguments();
for (int i = 0; i < names.length; i++) {
if (i != 0) argNames.add(', ');
argNames.add('"');
argNames.add(names[i].slowToString());
argNames.add('"');
}
String internalName = namer.instanceMethodInvocationName(
selector.library, new SourceString(methodName), selector);
CodeBuffer buffer = new CodeBuffer();
buffer.add('function($args) {\n');
buffer.add(' return this.$noSuchMethodName('
'\$.createInvocationMirror("$methodName", "$internalName",'
' $type, [$args], [$argNames]));\n');
buffer.add(' }');
return buffer;
}
void addNoSuchMethodHandlers(SourceString ignore, Set<Selector> selectors) {
// Cache the object class and type.
ClassElement objectClass = compiler.objectClass;
DartType objectType = objectClass.computeType(compiler);
for (Selector selector in selectors) {
// Introduce a helper function that determines if the given
// class has a member that matches the current name and
// selector (grabbed from the scope).
bool hasMatchingMember(ClassElement holder) {
Element element = holder.lookupMember(selector.name);
if (element == null) return false;
// TODO(kasperl): Consider folding this logic into the
// Selector.applies() method.
if (element is AbstractFieldElement) {
AbstractFieldElement field = element;
if (identical(selector.kind, SelectorKind.GETTER)) {
return field.getter != null;
} else if (identical(selector.kind, SelectorKind.SETTER)) {
return field.setter != null;
} else {
return false;
}
}
return selector.applies(element, compiler);
}
// If the selector is typed, we check to see if that type may
// have a user-defined noSuchMethod implementation. If not, we
// skip the selector altogether.
DartType receiverType = objectType;
ClassElement receiverClass = objectClass;
if (selector is TypedSelector) {
TypedSelector typedSelector = selector;
receiverType = typedSelector.receiverType;
receiverClass = receiverType.element;
}
// If the receiver class is guaranteed to have a member that
// matches what we're looking for, there's no need to
// introduce a noSuchMethod handler. It will never be called.
//
// As an example, consider this class hierarchy:
//
// A <-- noSuchMethod
// / \
// C B <-- foo
//
// If we know we're calling foo on an object of type B we
// don't have to worry about the noSuchMethod method in A
// because objects of type B implement foo. On the other hand,
// if we end up calling foo on something of type C we have to
// add a handler for it.
if (hasMatchingMember(receiverClass)) continue;
// If the holders of all user-defined noSuchMethod
// implementations that might be applicable to the receiver
// type have a matching member for the current name and
// selector, we avoid introducing a noSuchMethod handler.
//
// As an example, consider this class hierarchy:
//
// A <-- foo
// / \
// noSuchMethod --> B C <-- bar
// | |
// C D <-- noSuchMethod
//
// When calling foo on an object of type A, we know that the
// implementations of noSuchMethod are in the classes B and D
// that also (indirectly) implement foo, so we do not need a
// handler for it.
//
// If we're calling bar on an object of type D, we don't need
// the handler either because all objects of type D implement
// bar through inheritance.
//
// If we're calling bar on an object of type A we do need the
// handler because we may have to call B.noSuchMethod since B
// does not implement bar.
Set<ClassElement> holders = noSuchMethodHoldersFor(receiverType);
if (holders.every(hasMatchingMember)) continue;
String jsName = null;
String methodName = null;
String nameString = selector.name.slowToString();
if (selector.isGetter()) {
jsName = namer.getterName(selector.library, selector.name);
methodName = 'get:$nameString';
} else if (selector.isSetter()) {
jsName = namer.setterName(selector.library, selector.name);
methodName = 'set:$nameString';
} else if (selector.isCall()) {
jsName = namer.instanceMethodInvocationName(
selector.library, selector.name, selector);
methodName = nameString;
} else {
// We simply ignore selectors that do not need
// noSuchMethod handlers.
continue;
}
if (!addedJsNames.contains(jsName)) {
CodeBuffer jsCode = generateMethod(methodName, selector);
defineInstanceMember(jsName, jsCode);
addedJsNames.add(jsName);
}
}
}
compiler.codegenWorld.invokedNames.forEach(addNoSuchMethodHandlers);
compiler.codegenWorld.invokedGetters.forEach(addNoSuchMethodHandlers);
compiler.codegenWorld.invokedSetters.forEach(addNoSuchMethodHandlers);
}
String buildIsolateSetup(CodeBuffer buffer,
Element appMain,
Element isolateMain) {
String mainAccess = "${namer.isolateAccess(appMain)}";
String currentIsolate = "${namer.CURRENT_ISOLATE}";
String mainEnsureGetter = '';
// Since we pass the closurized version of the main method to
// the isolate method, we must make sure that it exists.
if (!compiler.codegenWorld.staticFunctionsNeedingGetter.contains(appMain)) {
Selector selector = new Selector.callClosure(0);
String invocationName = "${namer.closureInvocationName(selector)}";
mainEnsureGetter = "$mainAccess.$invocationName = $mainAccess";
}
// TODO(ngeoffray): These globals are currently required by the isolate
// library. They should be removed.
buffer.add("""
var \$globalThis = $currentIsolate;
var \$globalState;
var \$globals;
var \$isWorker = false;
var \$supportsWorkers = false;
var \$thisScriptUrl;
function \$static_init(){};
function \$initGlobals(context) {
context.isolateStatics = new ${namer.ISOLATE}();
}
function \$setGlobals(context) {
$currentIsolate = context.isolateStatics;
\$globalThis = $currentIsolate;
}
$mainEnsureGetter
""");
return "${namer.isolateAccess(isolateMain)}($mainAccess)";
}
emitMain(CodeBuffer buffer) {
if (compiler.isMockCompilation) return;
Element main = compiler.mainApp.find(Compiler.MAIN);
String mainCall = null;
if (compiler.isolateLibrary != null) {
Element isolateMain =
compiler.isolateLibrary.find(Compiler.START_ROOT_ISOLATE);
mainCall = buildIsolateSetup(buffer, main, isolateMain);
} else {
mainCall = '${namer.isolateAccess(main)}()';
}
buffer.add("""
//
// BEGIN invoke [main].
//
if (typeof document != 'undefined' && document.readyState != 'complete') {
document.addEventListener('readystatechange', function () {
if (document.readyState == 'complete') {
if (typeof dartMainRunner == 'function') {
dartMainRunner(function() { ${mainCall}; });
} else {
${mainCall};
}
}
}, false);
} else {
if (typeof dartMainRunner == 'function') {
dartMainRunner(function() { ${mainCall}; });
} else {
${mainCall};
}
}
//
// END invoke [main].
//
""");
}
String assembleProgram() {
measure(() {
mainBuffer.add(HOOKS_API_USAGE);
mainBuffer.add('function ${namer.ISOLATE}() {}\n');
mainBuffer.add('init();\n\n');
// Shorten the code by using "$$" as temporary.
classesCollector = r"$$";
mainBuffer.add('var $classesCollector = {};\n');
// Shorten the code by using [namer.CURRENT_ISOLATE] as temporary.
isolateProperties = namer.CURRENT_ISOLATE;
mainBuffer.add('var $isolateProperties = $isolatePropertiesName;\n');
emitClasses(mainBuffer);
mainBuffer.add(boundClosureBuffer);
// Clear the buffer, so that we can reuse it for the native classes.
boundClosureBuffer.clear();
emitStaticFunctions(mainBuffer);
emitStaticFunctionGetters(mainBuffer);
// We need to finish the classes before we construct compile time
// constants.
emitFinishClassesInvocationIfNecessary(mainBuffer);
emitCompileTimeConstants(mainBuffer);
// Static field initializations require the classes and compile-time
// constants to be set up.
emitStaticNonFinalFieldInitializations(mainBuffer);
emitLazilyInitializedStaticFields(mainBuffer);
isolateProperties = isolatePropertiesName;
// The following code should not use the short-hand for the
// initialStatics.
mainBuffer.add('var ${namer.CURRENT_ISOLATE} = null;\n');
mainBuffer.add(boundClosureBuffer);
emitFinishClassesInvocationIfNecessary(mainBuffer);
// After this assignment we will produce invalid JavaScript code if we use
// the classesCollector variable.
classesCollector = 'classesCollector should not be used from now on';
emitFinishIsolateConstructorInvocation(mainBuffer);
mainBuffer.add(
'var ${namer.CURRENT_ISOLATE} = new ${namer.ISOLATE}();\n');
nativeEmitter.assembleCode(mainBuffer);
emitMain(mainBuffer);
mainBuffer.add('function init() {\n');
mainBuffer.add('$isolateProperties = {};\n');
addDefineClassAndFinishClassFunctionsIfNecessary(mainBuffer);
addLazyInitializerFunctionIfNecessary(mainBuffer);
emitFinishIsolateConstructor(mainBuffer);
mainBuffer.add('}\n');
compiler.assembledCode = mainBuffer.toString();
if (generateSourceMap) {
SourceFile compiledFile = new SourceFile(null, compiler.assembledCode);
String sourceMap = buildSourceMap(mainBuffer, compiledFile);
// TODO(podivilov): We should find a better way to return source maps to
// compiler. Using diagnostic handler for that purpose is a temporary
// hack.
compiler.reportDiagnostic(
null, sourceMap, new api.Diagnostic(-1, 'source map'));
}
});
return compiler.assembledCode;
}
String buildSourceMap(CodeBuffer buffer, SourceFile compiledFile) {
SourceMapBuilder sourceMapBuilder = new SourceMapBuilder();
buffer.forEachSourceLocation(sourceMapBuilder.addMapping);
return sourceMapBuilder.build(compiledFile);
}
}
typedef void DefineMemberFunction(String invocationName, CodeBuffer definition);
const String HOOKS_API_USAGE = """
// Generated by dart2js, the Dart to JavaScript compiler.
// The code supports the following hooks:
// dartPrint(message) - if this function is defined it is called
// instead of the Dart [print] method.
// dartMainRunner(main) - if this function is defined, the Dart [main]
// method will not be invoked directly.
// Instead, a closure that will invoke [main] is
// passed to [dartMainRunner].
""";