Index: dart/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
diff --git a/dart/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart b/dart/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
index 21df4ae5faf6eae8831df67b122ad7704097d686..f508bfd47aaa92d68fda48c032ae367070e2f03d 100644 |
--- a/dart/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
+++ b/dart/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
@@ -90,7 +90,6 @@ typedef void SubstitutionEmitter(Element element, {bool emitNull}); |
* The code for the containing (used) methods must exist in the [:universe:]. |
*/ |
class CodeEmitterTask extends CompilerTask { |
- bool needsInheritFunction = false; |
bool needsDefineClass = false; |
bool needsMixinSupport = false; |
bool needsLazyInitializer = false; |
@@ -168,6 +167,22 @@ class CodeEmitterTask extends CompilerTask { |
new Set<FunctionType>(); |
/** |
+ * 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]. |
@@ -263,8 +278,6 @@ class CodeEmitterTask extends CompilerTask { |
=> '${namer.isolateName}.\$finishIsolateConstructor'; |
String get isolatePropertiesName |
=> '${namer.isolateName}.${namer.isolatePropertiesName}'; |
- String get supportsProtoName |
- => 'supportsProto'; |
String get lazyInitializerName |
=> '${namer.isolateName}.\$lazy'; |
@@ -427,27 +440,24 @@ class CodeEmitterTask extends CompilerTask { |
} |
/** Needs defineClass to be defined. */ |
- List buildProtoSupportCheck() { |
- // 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. |
- // TODO(8541): Remove this work around. |
- |
+ List buildInheritFrom() { |
return [ |
- js('var $supportsProtoName = false'), |
- // js('var tmp = defineClass("c", "c", ["f<"], {}).prototype'), |
- // |
- // js.if_(js('tmp.__proto__'), [ |
- // js('tmp.__proto__ = {}'), |
- // js.if_(js(r'typeof tmp.get$f != "undefined"'), |
- // js('$supportsProtoName = true')) |
- // ]) |
- ]; |
+ 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') |
+ ]))])())]; |
} |
static const MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING = 4; |
@@ -702,17 +712,19 @@ class CodeEmitterTask extends CompilerTask { |
js.if_('!init.allClasses', js('init.allClasses = {}')), |
js('var allClasses = init.allClasses'), |
- js('var hasOwnProperty = Object.prototype.hasOwnProperty'), |
- js('var combinedConstructorFunction = ' |
- '"function \$reflectable(fn){fn.$reflectableField=1;return fn};\\n"' |
- '+ "var \$desc;\\n"'), |
- js('var constructorsList = []'), |
- |
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]'), |
@@ -742,14 +754,9 @@ class CodeEmitterTask extends CompilerTask { |
]) |
]), |
- js.if_('typeof fields == "string"', [ |
- js('var s = fields.split(";")'), |
- js('fields = s[1] == "" ? [] : s[1].split(",")'), |
- js('supr = s[0]'), |
- ], /* else */ [ |
- js('supr = desc.super'), |
- js.if_(r'!!desc.$name', js(r'name = desc.$name')) |
- ]), |
+ 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("+")'), |
@@ -763,17 +770,20 @@ class CodeEmitterTask extends CompilerTask { |
]), |
])), |
- js('combinedConstructorFunction += defineClass(name, cls, fields)'), |
- js('constructorsList.push(cls)'), |
+ js.if_('typeof dart_precompiled != "function"', |
+ [js('combinedConstructorFunction +=' |
+ ' defineClass(name, cls, fields)'), |
+ js('constructorsList.push(cls)')]), |
js.if_('supr', js('pendingClasses[cls] = supr')) |
]) |
]), |
- js('combinedConstructorFunction +=' |
- ' "return [\\n " + constructorsList.join(",\\n ") + "\\n]"'), |
- js('var constructors =' |
- ' new Function("\$collectedClasses", combinedConstructorFunction)' |
- '(collectedClasses)'), |
- js('combinedConstructorFunction = null'), |
+ 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'), |
@@ -841,38 +851,7 @@ class CodeEmitterTask extends CompilerTask { |
js('superConstructor =' |
'existingIsolateProperties[superclass]')), |
- js('var prototype = constructor.prototype'), |
- |
- // if ($supportsProtoName) { |
- js.if_(supportsProtoName, [ |
- js('prototype.__proto__ = superConstructor.prototype'), |
- js('prototype.constructor = constructor'), |
- |
- ], /* else */ [ |
- // function tmp() {}; |
- new jsAst.FunctionDeclaration( |
- new jsAst.VariableDeclaration('tmp'), |
- js.fun([], [])), |
- |
- js('tmp.prototype = superConstructor.prototype'), |
- js('var newPrototype = new tmp()'), |
- |
- js('constructor.prototype = newPrototype'), |
- js('newPrototype.constructor = constructor'), |
- |
- // for (var member in prototype) { |
- js.forIn('member', 'prototype', [ |
- /* Short version of: if (member == '') */ |
- // if (!member) continue; |
- js.if_('!member', new jsAst.Continue(null)), |
- |
- // if (hasOwnProperty.call(prototype, member)) { |
- js.if_('hasOwnProperty.call(prototype, member)', [ |
- js('newPrototype[member] = prototype[member]') |
- ]) |
- ]) |
- |
- ]) |
+ js('prototype = inheritFrom(constructor, superConstructor)'), |
]); |
return new jsAst.FunctionDeclaration( |
@@ -880,7 +859,7 @@ class CodeEmitterTask extends CompilerTask { |
fun); |
} |
- jsAst.Fun get finishIsolateConstructorFunction { |
+ jsAst.Fun get finishIsolateConstructorFunction_NO_CSP { |
ngeoffray
2013/09/24 08:15:29
Is that dead code?
ahe
2013/09/24 14:05:09
Yes, removed in https://codereview.chromium.org/24
|
String isolate = namer.isolateName; |
// 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. |
@@ -944,14 +923,45 @@ class CodeEmitterTask extends CompilerTask { |
])); |
} |
+ 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, [ |
- js('var getter = new Function("{ return this." + fieldName + ";}")'), |
- ]..addAll(addLazyInitializerLogic()) |
- ); |
+ return js.fun(parameters, addLazyInitializerLogic()); |
} |
List addLazyInitializerLogic() { |
@@ -1004,7 +1014,8 @@ class CodeEmitterTask extends CompilerTask { |
js.return_('result') |
], finallyPart: [ |
- js('$isolate[getterName] = getter') |
+ js('$isolate[getterName] = #', |
+ js.fun([], [js.return_('this[fieldName]')])) |
]) |
])) |
]); |
@@ -1013,7 +1024,7 @@ class CodeEmitterTask extends CompilerTask { |
List buildDefineClassAndFinishClassFunctionsIfNecessary() { |
if (!needsDefineClass) return []; |
return defineClassFunction |
- ..addAll(buildProtoSupportCheck()) |
+ ..addAll(buildInheritFrom()) |
..addAll([ |
js('$finishClassesName = #', finishClassesFunction) |
]); |
@@ -1709,29 +1720,34 @@ class CodeEmitterTask extends CompilerTask { |
void generateGetter(Element member, String fieldName, String accessorName, |
ClassBuilder builder) { |
String getterName = namer.getterNameFromAccessorName(accessorName); |
- String receiver = backend.isInterceptorClass(member.getEnclosingClass()) |
- ? 'receiver' : 'this'; |
- List<String> args = backend.isInterceptedMethod(member) |
- ? ['receiver'] |
- : []; |
- builder.addProperty(getterName, |
- js.fun(args, js.return_(js('$receiver.$fieldName')))); |
- generateReflectionDataForFieldGetterOrSetter( |
- member, getterName, builder, isGetter: true); |
+ ClassElement cls = member.getEnclosingClass(); |
+ String className = namer.getNameOfClass(cls); |
+ String receiver = backend.isInterceptorClass(cls) ? 'receiver' : 'this'; |
+ List<String> args = backend.isInterceptedMethod(member) ? ['receiver'] : []; |
+ precompiledFunction.add( |
+ js('$className.prototype.$getterName = #', |
+ js.fun(args, js.return_(js('$receiver.$fieldName'))))); |
+ if (backend.isNeededForReflection(member)) { |
+ precompiledFunction.add( |
+ js('$className.prototype.$getterName.${namer.reflectableField} = 1')); |
+ } |
} |
void generateSetter(Element member, String fieldName, String accessorName, |
ClassBuilder builder) { |
String setterName = namer.setterNameFromAccessorName(accessorName); |
- String receiver = backend.isInterceptorClass(member.getEnclosingClass()) |
- ? 'receiver' : 'this'; |
- List<String> args = backend.isInterceptedMethod(member) |
- ? ['receiver', 'v'] |
- : ['v']; |
- builder.addProperty(setterName, |
- js.fun(args, js('$receiver.$fieldName = v'))); |
- generateReflectionDataForFieldGetterOrSetter( |
- member, setterName, builder, isGetter: false); |
+ ClassElement cls = member.getEnclosingClass(); |
+ String className = namer.getNameOfClass(cls); |
+ String receiver = backend.isInterceptorClass(cls) ? 'receiver' : 'this'; |
+ List<String> args = |
+ backend.isInterceptedMethod(member) ? ['receiver', 'v'] : ['v']; |
+ precompiledFunction.add( |
+ js('$className.prototype.$setterName = #', |
+ js.fun(args, js.return_(js('$receiver.$fieldName = v'))))); |
+ if (backend.isNeededForReflection(member)) { |
+ precompiledFunction.add( |
+ js('$className.prototype.$setterName.${namer.reflectableField} = 1')); |
+ } |
} |
bool canGenerateCheckedSetter(VariableElement field) { |
@@ -1781,8 +1797,52 @@ class CodeEmitterTask extends CompilerTask { |
member, setterName, builder, isGetter: false); |
} |
- void emitClassConstructor(ClassElement classElement, ClassBuilder builder) { |
- /* Do nothing. */ |
+ void emitClassConstructor(ClassElement classElement, |
+ ClassBuilder builder, |
+ String runtimeName) { |
+ List<String> fields = <String>[]; |
+ if (!classElement.isNative()) { |
+ visitFields(classElement, false, |
+ (Element member, |
+ String name, |
+ String accessorName, |
+ bool needsGetter, |
+ bool needsSetter, |
+ bool needsCheckedSetter) { |
+ fields.add(name); |
+ }); |
+ } |
+ String constructorName = namer.getNameOfClass(classElement); |
+ precompiledFunction.add(new jsAst.FunctionDeclaration( |
+ new jsAst.VariableDeclaration(constructorName), |
+ js.fun(fields, fields.map( |
+ (name) => js('this.$name = $name')).toList()))); |
+ if (runtimeName == null) { |
+ runtimeName = constructorName; |
+ } |
+ precompiledFunction.addAll([ |
+ js('$constructorName.builtin\$cls = "$runtimeName"'), |
+ js.if_('!"name" in $constructorName', |
+ js('$constructorName.name = "$constructorName"')), |
+ js('\$desc=\$collectedClasses.$constructorName'), |
+ js.if_('\$desc instanceof Array', js('\$desc = \$desc[1]')), |
+ js('$constructorName.prototype = \$desc'), |
+ ]); |
+ |
+ precompiledConstructorNames.add(js(constructorName)); |
+ } |
+ |
+ 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 emitSuper(String superName, ClassBuilder builder) { |
@@ -1966,13 +2026,11 @@ class CodeEmitterTask extends CompilerTask { |
assert(!needsSetter); |
generateCheckedSetter(member, name, accessorName, builder); |
} |
- if (!getterAndSetterCanBeImplementedByFieldSpec) { |
- if (needsGetter) { |
- generateGetter(member, name, accessorName, builder); |
- } |
- if (needsSetter) { |
- generateSetter(member, name, accessorName, builder); |
- } |
+ if (needsGetter) { |
+ generateGetter(member, name, accessorName, builder); |
+ } |
+ if (needsSetter) { |
+ generateSetter(member, name, accessorName, builder); |
} |
}); |
}); |
@@ -2007,7 +2065,7 @@ class CodeEmitterTask extends CompilerTask { |
} |
ClassBuilder builder = new ClassBuilder(); |
- emitClassConstructor(classElement, builder); |
+ emitClassConstructor(classElement, builder, runtimeName); |
emitSuper(superName, builder); |
emitRuntimeName(runtimeName, builder); |
emitFields(classElement, builder, superName, onlyForRti: onlyForRti); |
@@ -2075,8 +2133,6 @@ class CodeEmitterTask extends CompilerTask { |
} |
} |
- bool get getterAndSetterCanBeImplementedByFieldSpec => true; |
- |
/// If this is true then we can generate the noSuchMethod handlers at startup |
/// time, instead of them being emitted as part of the Object class. |
bool get generateTrivialNsmHandlers => true; |
@@ -2493,6 +2549,21 @@ class CodeEmitterTask extends CompilerTask { |
ClassBuilder builder) { |
builder.addProperty('', |
js.string("$superName;${fieldNames.join(',')}")); |
+ |
+ List<String> fields = fieldNames; |
+ String constructorName = mangledName; |
+ precompiledFunction.add(new jsAst.FunctionDeclaration( |
+ new jsAst.VariableDeclaration(constructorName), |
+ js.fun(fields, fields.map( |
+ (name) => js('this.$name = $name')).toList()))); |
+ precompiledFunction.addAll([ |
+ js('$constructorName.builtin\$cls = "$constructorName"'), |
+ js('\$desc=\$collectedClasses.$constructorName'), |
+ js.if_('\$desc instanceof Array', js('\$desc = \$desc[1]')), |
+ js('$constructorName.prototype = \$desc'), |
+ ]); |
+ |
+ precompiledConstructorNames.add(js(constructorName)); |
} |
/** |
@@ -2792,14 +2863,10 @@ class CodeEmitterTask extends CompilerTask { |
ConstantHandler handler = compiler.constantHandler; |
List<Constant> constants = handler.getConstantsForEmission( |
compareConstants); |
- bool addedMakeConstantList = false; |
for (Constant constant in constants) { |
if (isConstantInlinedOrAlreadyEmitted(constant)) continue; |
String name = namer.constantName(constant); |
- if (!addedMakeConstantList && constant.isList()) { |
- addedMakeConstantList = true; |
- emitMakeConstantList(eagerBuffer); |
- } |
+ if (constant.isList()) emitMakeConstantListIfNotEmitted(eagerBuffer); |
CodeBuffer buffer = bufferForConstant(constant, eagerBuffer); |
jsAst.Expression init = js( |
'${namer.globalObjectForConstant(constant)}.$name = #', |
@@ -2834,9 +2901,12 @@ class CodeEmitterTask extends CompilerTask { |
return namer.constantName(a).compareTo(namer.constantName(b)); |
} |
- void emitMakeConstantList(CodeBuffer buffer) { |
- buffer.write(namer.isolateName); |
- buffer.write(r'''.makeConstantList = function(list) { |
+ 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; |
@@ -3757,7 +3827,7 @@ class CodeEmitterTask extends CompilerTask { |
buffer.write('];$n'); |
} |
- void emitConvertToFastObjectFunction() { |
+ void emitConvertToFastObjectFunction_NO_CSP() { |
ngeoffray
2013/09/24 08:15:29
Ditto
|
mainBuffer.add(r''' |
function convertToFastObject(properties) { |
function makeConstructor() { |
@@ -3777,6 +3847,37 @@ function convertToFastObject(properties) { |
'''); |
} |
+ 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(const SourceString('Primitives')); |
+ FunctionElement printHelper = |
+ compiler.lookupElementIn( |
+ primitives, const SourceString('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 |
@@ -4074,6 +4175,8 @@ if (typeof $printHelperName === "function") { |
} |
emitMain(mainBuffer); |
+ jsAst.FunctionDeclaration precompiledFunctionAst = |
+ buildPrecompiledFunction(); |
emitInitFunction(mainBuffer); |
if (!areAnyElementsDeferred) { |
mainBuffer.add('})()$n'); |
@@ -4081,6 +4184,13 @@ if (typeof $printHelperName === "function") { |
compiler.assembledCode = mainBuffer.getText(); |
outputSourceMap(compiler.assembledCode, ''); |
+ mainBuffer.write( |
+ jsAst.prettyPrint(precompiledFunctionAst, compiler).getText()); |
+ |
+ compiler.outputProvider('precompiled', 'js') |
+ ..add(mainBuffer.getText()) |
+ ..close(); |
+ |
emitDeferredCode(); |
}); |