| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library dart2js.new_js_emitter.model_emitter; | |
| 6 | |
| 7 import '../../dart2jslib.dart' show Compiler; | |
| 8 import '../../js/js.dart' as js; | |
| 9 import '../../js_backend/js_backend.dart' show | |
| 10 JavaScriptBackend, | |
| 11 Namer, | |
| 12 ConstantEmitter; | |
| 13 | |
| 14 import 'package:_internal/compiler/js_lib/shared/embedded_names.dart' show | |
| 15 DEFERRED_LIBRARY_URIS, | |
| 16 DEFERRED_LIBRARY_HASHES, | |
| 17 INITIALIZE_LOADED_HUNK, | |
| 18 IS_HUNK_LOADED; | |
| 19 | |
| 20 import '../model.dart'; | |
| 21 | |
| 22 class ModelEmitter { | |
| 23 final Compiler compiler; | |
| 24 final Namer namer; | |
| 25 final ConstantEmitter constantEmitter; | |
| 26 | |
| 27 JavaScriptBackend get backend => compiler.backend; | |
| 28 | |
| 29 /// For deferred loading we communicate the initializers via this global var. | |
| 30 static const String deferredInitializersGlobal = | |
| 31 r"$__dart_deferred_initializers__"; | |
| 32 | |
| 33 static const String deferredExtension = ".part.js"; | |
| 34 | |
| 35 ModelEmitter(Compiler compiler, Namer namer) | |
| 36 : this.compiler = compiler, | |
| 37 this.namer = namer, | |
| 38 constantEmitter = | |
| 39 new ConstantEmitter(compiler, namer, makeConstantListTemplate); | |
| 40 | |
| 41 void emitProgram(Program program) { | |
| 42 List<Output> outputs = program.outputs; | |
| 43 MainOutput mainUnit = outputs.first; | |
| 44 js.Statement mainAst = emitMainUnit(program); | |
| 45 String mainCode = js.prettyPrint(mainAst, compiler).getText(); | |
| 46 compiler.outputProvider(mainUnit.outputFileName, 'js') | |
| 47 ..add(buildGeneratedBy(compiler)) | |
| 48 ..add(mainCode) | |
| 49 ..close(); | |
| 50 compiler.assembledCode = mainCode; | |
| 51 | |
| 52 outputs.skip(1).forEach((DeferredOutput deferredUnit) { | |
| 53 js.Expression ast = emitDeferredUnit(deferredUnit, mainUnit.holders); | |
| 54 String code = js.prettyPrint(ast, compiler).getText(); | |
| 55 compiler.outputProvider(deferredUnit.outputFileName, deferredExtension) | |
| 56 ..add(code) | |
| 57 ..close(); | |
| 58 }); | |
| 59 } | |
| 60 | |
| 61 js.LiteralString unparse(Compiler compiler, js.Expression value) { | |
| 62 String text = js.prettyPrint(value, compiler).getText(); | |
| 63 if (value is js.Fun) text = '($text)'; | |
| 64 return js.js.escapedString(text); | |
| 65 } | |
| 66 | |
| 67 String buildGeneratedBy(compiler) { | |
| 68 var suffix = ''; | |
| 69 if (compiler.hasBuildId) suffix = ' version: ${compiler.buildId}'; | |
| 70 return '// Generated by dart2js, the Dart to JavaScript compiler$suffix.\n'; | |
| 71 } | |
| 72 | |
| 73 js.Statement emitMainUnit(Program program) { | |
| 74 MainOutput unit = program.outputs.first; | |
| 75 List<js.Expression> elements = unit.libraries.map(emitLibrary).toList(); | |
| 76 elements.add( | |
| 77 emitLazilyInitializedStatics(unit.staticLazilyInitializedFields)); | |
| 78 js.Expression code = new js.ArrayInitializer.from(elements); | |
| 79 return js.js.statement( | |
| 80 boilerplate, | |
| 81 [emitDeferredInitializerGlobal(program.loadMap), | |
| 82 emitHolders(unit.holders), | |
| 83 namer.elementAccess(backend.getCyclicThrowHelper()), | |
| 84 program.outputContainsConstantList, | |
| 85 emitEmbeddedGlobals(program.loadMap), | |
| 86 emitConstants(unit.constants), | |
| 87 emitStaticNonFinalFields(unit.staticNonFinalFields), | |
| 88 emitEagerClassInitializations(unit.libraries), | |
| 89 unit.main, | |
| 90 code]); | |
| 91 } | |
| 92 | |
| 93 js.Block emitHolders(List<Holder> holders) { | |
| 94 // The top-level variables for holders must *not* be renamed by the | |
| 95 // JavaScript pretty printer because a lot of code already uses the | |
| 96 // non-renamed names. The generated code looks like this: | |
| 97 // | |
| 98 // var H = {}, ..., G = {}; | |
| 99 // var holders = [ H, ..., G ]; | |
| 100 // | |
| 101 // and it is inserted at the top of the top-level function expression | |
| 102 // that covers the entire program. | |
| 103 | |
| 104 List<js.Statement> statements = [ | |
| 105 new js.ExpressionStatement( | |
| 106 new js.VariableDeclarationList(holders.map((e) => | |
| 107 new js.VariableInitialization( | |
| 108 new js.VariableDeclaration(e.name, allowRename: false), | |
| 109 new js.ObjectInitializer(const []))).toList())), | |
| 110 js.js.statement('var holders = #', new js.ArrayInitializer.from( | |
| 111 holders.map((e) => new js.VariableUse(e.name)))) | |
| 112 ]; | |
| 113 return new js.Block(statements); | |
| 114 } | |
| 115 | |
| 116 static js.Template get makeConstantListTemplate { | |
| 117 // TODO(floitsch): remove hard-coded name. | |
| 118 // TODO(floitsch): there is no harm in caching the template. | |
| 119 return js.js.uncachedExpressionTemplate('makeConstList(#)'); | |
| 120 } | |
| 121 | |
| 122 js.Block emitEmbeddedGlobals(Map<String, List<Output>> loadMap) { | |
| 123 List<js.Property> globals = <js.Property>[]; | |
| 124 | |
| 125 if (loadMap.isNotEmpty) { | |
| 126 globals.addAll(emitLoadUrisAndHashes(loadMap)); | |
| 127 globals.add(emitIsHunkLoadedFunction()); | |
| 128 globals.add(emitInitializeLoadedHunk()); | |
| 129 } | |
| 130 | |
| 131 if (globals.isEmpty) return new js.Block.empty(); | |
| 132 | |
| 133 js.ObjectInitializer globalsObject = new js.ObjectInitializer(globals); | |
| 134 | |
| 135 List<js.Statement> statements = | |
| 136 [new js.ExpressionStatement( | |
| 137 new js.VariableDeclarationList( | |
| 138 [new js.VariableInitialization( | |
| 139 new js.VariableDeclaration(r"init", allowRename: false), | |
| 140 globalsObject)]))]; | |
| 141 return new js.Block(statements); | |
| 142 } | |
| 143 | |
| 144 List<js.Property> emitLoadUrisAndHashes(Map<String, List<Output>> loadMap) { | |
| 145 js.ArrayInitializer outputUris(List<Output> outputs) { | |
| 146 return js.stringArray(outputs.map((DeferredOutput output) => | |
| 147 "${output.outputFileName}$deferredExtension")); | |
| 148 } | |
| 149 js.ArrayInitializer outputHashes(List<Output> outputs) { | |
| 150 // TODO(floitsch): the hash must depend on the generated code. | |
| 151 return js.numArray( | |
| 152 outputs.map((DeferredOutput output) => output.hashCode)); | |
| 153 } | |
| 154 | |
| 155 List<js.Property> uris = new List<js.Property>(loadMap.length); | |
| 156 List<js.Property> hashes = new List<js.Property>(loadMap.length); | |
| 157 int count = 0; | |
| 158 loadMap.forEach((String loadId, List<Output> outputList) { | |
| 159 uris[count] = new js.Property(js.string(loadId), outputUris(outputList)); | |
| 160 hashes[count] = | |
| 161 new js.Property(js.string(loadId), outputHashes(outputList)); | |
| 162 count++; | |
| 163 }); | |
| 164 | |
| 165 return <js.Property>[ | |
| 166 new js.Property(js.string(DEFERRED_LIBRARY_URIS), | |
| 167 new js.ObjectInitializer(uris)), | |
| 168 new js.Property(js.string(DEFERRED_LIBRARY_HASHES), | |
| 169 new js.ObjectInitializer(hashes)) | |
| 170 ]; | |
| 171 } | |
| 172 | |
| 173 js.Statement emitDeferredInitializerGlobal(Map loadMap) { | |
| 174 if (loadMap.isEmpty) return new js.Block.empty(); | |
| 175 | |
| 176 return js.js.statement(""" | |
| 177 if (typeof($deferredInitializersGlobal) === 'undefined') | |
| 178 var $deferredInitializersGlobal = Object.create(null);"""); | |
| 179 } | |
| 180 | |
| 181 js.Property emitIsHunkLoadedFunction() { | |
| 182 js.Expression function = | |
| 183 js.js("function(hash) { return !!$deferredInitializersGlobal[hash]; }"); | |
| 184 return new js.Property(js.string(IS_HUNK_LOADED), function); | |
| 185 } | |
| 186 | |
| 187 js.Property emitInitializeLoadedHunk() { | |
| 188 js.Expression function = | |
| 189 js.js("function(hash) { eval($deferredInitializersGlobal[hash]); }"); | |
| 190 return new js.Property(js.string(INITIALIZE_LOADED_HUNK), function); | |
| 191 } | |
| 192 | |
| 193 js.Expression emitDeferredUnit(DeferredOutput unit, List<Holder> holders) { | |
| 194 // TODO(floitsch): initialize eager classes. | |
| 195 // TODO(floitsch): the hash must depend on the output. | |
| 196 int hash = this.hashCode; | |
| 197 if (unit.constants.isNotEmpty) { | |
| 198 throw new UnimplementedError("constants in deferred units"); | |
| 199 } | |
| 200 js.ArrayInitializer content = | |
| 201 new js.ArrayInitializer.from(unit.libraries.map(emitLibrary)); | |
| 202 return js.js("$deferredInitializersGlobal[$hash] = #", content); | |
| 203 } | |
| 204 | |
| 205 js.Block emitConstants(List<Constant> constants) { | |
| 206 Iterable<js.Statement> statements = constants.map((Constant constant) { | |
| 207 js.Expression code = | |
| 208 constantEmitter.initializationExpression(constant.value); | |
| 209 return js.js.statement("#.# = #;", | |
| 210 [constant.holder.name, constant.name, code]); | |
| 211 }); | |
| 212 return new js.Block(statements.toList()); | |
| 213 } | |
| 214 | |
| 215 js.Block emitStaticNonFinalFields(List<StaticField> fields) { | |
| 216 Iterable<js.Statement> statements = fields.map((StaticField field) { | |
| 217 return js.js.statement("#.# = #;", | |
| 218 [field.holder.name, field.name, field.code]); | |
| 219 }); | |
| 220 return new js.Block(statements.toList()); | |
| 221 } | |
| 222 | |
| 223 js.Expression emitLazilyInitializedStatics(List<StaticField> fields) { | |
| 224 Iterable fieldDescriptors = fields.expand((field) => | |
| 225 [ js.string(field.name), | |
| 226 js.string("${namer.getterPrefix}${field.name}"), | |
| 227 js.number(field.holder.index), | |
| 228 emitLazyInitializer(field) ]); | |
| 229 return new js.ArrayInitializer.from(fieldDescriptors); | |
| 230 } | |
| 231 | |
| 232 js.Block emitEagerClassInitializations(List<Library> libraries) { | |
| 233 js.Statement createInstantiation(Class cls) { | |
| 234 return js.js.statement('new #.#()', [cls.holder.name, cls.name]); | |
| 235 } | |
| 236 | |
| 237 List<js.Statement> instantiations = | |
| 238 libraries.expand((Library library) => library.classes) | |
| 239 .where((Class cls) => cls.isEager) | |
| 240 .map(createInstantiation) | |
| 241 .toList(growable: false); | |
| 242 return new js.Block(instantiations); | |
| 243 } | |
| 244 | |
| 245 js.Expression emitLibrary(Library library) { | |
| 246 Iterable staticDescriptors = library.statics.expand((e) => | |
| 247 [ js.string(e.name), js.number(e.holder.index), emitStaticMethod(e) ]); | |
| 248 Iterable classDescriptors = library.classes.expand((e) => | |
| 249 [ js.string(e.name), js.number(e.holder.index), emitClass(e) ]); | |
| 250 | |
| 251 js.Expression staticArray = new js.ArrayInitializer.from(staticDescriptors); | |
| 252 js.Expression classArray = new js.ArrayInitializer.from(classDescriptors); | |
| 253 | |
| 254 return new js.ArrayInitializer.from([staticArray, classArray]); | |
| 255 } | |
| 256 | |
| 257 js.Expression _generateConstructor(Class cls) { | |
| 258 List<String> allFieldNames = <String>[]; | |
| 259 Class currentClass = cls; | |
| 260 while (currentClass != null) { | |
| 261 allFieldNames.addAll( | |
| 262 currentClass.fields.map((InstanceField field) => field.name)); | |
| 263 currentClass = currentClass.superclass; | |
| 264 } | |
| 265 String name = cls.name; | |
| 266 String parameters = allFieldNames.join(', '); | |
| 267 String assignments = allFieldNames | |
| 268 .map((String field) => "this.$field = $field;\n") | |
| 269 .join(); | |
| 270 String code = 'function $name($parameters) { $assignments }'; | |
| 271 js.Template template = js.js.uncachedExpressionTemplate(code); | |
| 272 return template.instantiate(const []); | |
| 273 } | |
| 274 | |
| 275 Method _generateGetter(InstanceField field) { | |
| 276 String getterTemplateFor(int flags) { | |
| 277 switch (flags) { | |
| 278 case 1: return "function() { return this[#]; }"; | |
| 279 case 2: return "function(receiver) { return receiver[#]; }"; | |
| 280 case 3: return "function(receiver) { return this[#]; }"; | |
| 281 } | |
| 282 return null; | |
| 283 } | |
| 284 | |
| 285 js.Expression fieldName = js.string(field.name); | |
| 286 js.Expression code = js.js(getterTemplateFor(field.getterFlags), fieldName); | |
| 287 String getterName = "${namer.getterPrefix}${field.name}"; | |
| 288 return new Method(getterName, code); | |
| 289 } | |
| 290 | |
| 291 Method _generateSetter(InstanceField field) { | |
| 292 String setterTemplateFor(int flags) { | |
| 293 switch (flags) { | |
| 294 case 1: return "function(val) { return this[#] = val; }"; | |
| 295 case 2: return "function(receiver, val) { return receiver[#] = val; }"; | |
| 296 case 3: return "function(receiver, val) { return this[#] = val; }"; | |
| 297 } | |
| 298 return null; | |
| 299 } | |
| 300 js.Expression fieldName = js.string(field.name); | |
| 301 js.Expression code = js.js(setterTemplateFor(field.setterFlags), fieldName); | |
| 302 String setterName = "${namer.setterPrefix}${field.name}"; | |
| 303 return new Method(setterName, code); | |
| 304 } | |
| 305 | |
| 306 Iterable<Method> _generateGettersSetters(Class cls) { | |
| 307 Iterable<Method> getters = cls.fields | |
| 308 .where((InstanceField field) => field.needsGetter) | |
| 309 .map(_generateGetter); | |
| 310 | |
| 311 Iterable<Method> setters = cls.fields | |
| 312 .where((InstanceField field) => field.needsSetter) | |
| 313 .map(_generateSetter); | |
| 314 | |
| 315 return [getters, setters].expand((x) => x); | |
| 316 } | |
| 317 | |
| 318 js.Expression emitClass(Class cls) { | |
| 319 List elements = [ js.string(cls.superclassName), | |
| 320 js.number(cls.superclassHolderIndex), | |
| 321 _generateConstructor(cls) ]; | |
| 322 Iterable<Method> methods = cls.methods; | |
| 323 Iterable<Method> gettersSetters = _generateGettersSetters(cls); | |
| 324 Iterable<Method> allMethods = [methods, gettersSetters].expand((x) => x); | |
| 325 elements.addAll(allMethods.expand((e) => [ js.string(e.name), e.code ])); | |
| 326 return unparse(compiler, new js.ArrayInitializer.from(elements)); | |
| 327 } | |
| 328 | |
| 329 js.Expression emitLazyInitializer(StaticField field) { | |
| 330 assert(field.isLazy); | |
| 331 return unparse(compiler, field.code); | |
| 332 } | |
| 333 | |
| 334 js.Expression emitStaticMethod(StaticMethod method) { | |
| 335 return unparse(compiler, method.code); | |
| 336 } | |
| 337 } | |
| 338 | |
| 339 final String boilerplate = r""" | |
| 340 { | |
| 341 // Declare deferred-initializer global. | |
| 342 #; | |
| 343 | |
| 344 !function(start, program) { | |
| 345 | |
| 346 // Initialize holder objects. | |
| 347 #; | |
| 348 | |
| 349 function setupProgram() { | |
| 350 for (var i = 0; i < program.length - 1; i++) { | |
| 351 setupLibrary(program[i]); | |
| 352 } | |
| 353 setupLazyStatics(program[i]); | |
| 354 } | |
| 355 | |
| 356 function setupLibrary(library) { | |
| 357 var statics = library[0]; | |
| 358 for (var i = 0; i < statics.length; i += 3) { | |
| 359 var holderIndex = statics[i + 1]; | |
| 360 setupStatic(statics[i], holders[holderIndex], statics[i + 2]); | |
| 361 } | |
| 362 | |
| 363 var classes = library[1]; | |
| 364 for (var i = 0; i < classes.length; i += 3) { | |
| 365 var holderIndex = classes[i + 1]; | |
| 366 setupClass(classes[i], holders[holderIndex], classes[i + 2]); | |
| 367 } | |
| 368 } | |
| 369 | |
| 370 function setupLazyStatics(statics) { | |
| 371 for (var i = 0; i < statics.length; i += 4) { | |
| 372 var name = statics[i]; | |
| 373 var getterName = statics[i + 1]; | |
| 374 var holderIndex = statics[i + 2]; | |
| 375 var initializer = statics[i + 3]; | |
| 376 setupLazyStatic(name, getterName, holders[holderIndex], initializer); | |
| 377 } | |
| 378 } | |
| 379 | |
| 380 function setupStatic(name, holder, descriptor) { | |
| 381 holder[name] = function() { | |
| 382 var method = compile(name, descriptor); | |
| 383 holder[name] = method; | |
| 384 return method.apply(this, arguments); | |
| 385 }; | |
| 386 } | |
| 387 | |
| 388 function setupLazyStatic(name, getterName, holder, descriptor) { | |
| 389 holder[name] = null; | |
| 390 holder[getterName] = function() { | |
| 391 var initializer = compile(name, descriptor); | |
| 392 holder[getterName] = function() { #(name) }; // cyclicThrowHelper | |
| 393 var result; | |
| 394 var sentinelInProgress = descriptor; | |
| 395 try { | |
| 396 result = holder[name] = sentinelInProgress; | |
| 397 result = holder[name] = initializer(); | |
| 398 } finally { | |
| 399 // Use try-finally, not try-catch/throw as it destroys the stack trace. | |
| 400 if (result === sentinelInProgress) { | |
| 401 // The lazy static (holder[name]) might have been set to a different | |
| 402 // value. According to spec we still have to reset it to null, if the | |
| 403 // initialization failed. | |
| 404 holder[name] = null; | |
| 405 } | |
| 406 holder[getterName] = function() { return this[name]; }; | |
| 407 } | |
| 408 return result; | |
| 409 }; | |
| 410 } | |
| 411 | |
| 412 function setupClass(name, holder, descriptor) { | |
| 413 var resolve = function() { | |
| 414 var constructor = compileConstructor(name, descriptor); | |
| 415 holder[name] = constructor; | |
| 416 return constructor; | |
| 417 }; | |
| 418 | |
| 419 var patch = function() { | |
| 420 var constructor = resolve(); | |
| 421 var object = new constructor(); | |
| 422 constructor.apply(object, arguments); | |
| 423 return object; | |
| 424 }; | |
| 425 | |
| 426 // We store the resolve function on the patch function to make it possible | |
| 427 // to resolve superclass references without constructing instances. The | |
| 428 // resolve property also serves as a marker that indicates whether or not | |
| 429 // a class has been resolved yet. | |
| 430 patch.resolve = resolve; | |
| 431 holder[name] = patch; | |
| 432 } | |
| 433 | |
| 434 function compileConstructor(name, descriptor) { | |
| 435 descriptor = compile(name, descriptor); | |
| 436 var prototype = determinePrototype(descriptor); | |
| 437 var constructor = descriptor[2]; | |
| 438 for (var i = 3; i < descriptor.length; i += 2) { | |
| 439 prototype[descriptor[i]] = descriptor[i + 1]; | |
| 440 } | |
| 441 constructor.prototype = prototype; | |
| 442 return constructor; | |
| 443 } | |
| 444 | |
| 445 function determinePrototype(descriptor) { | |
| 446 var superclassName = descriptor[0]; | |
| 447 if (!superclassName) return { }; | |
| 448 | |
| 449 // Look up the superclass constructor function in the right holder. | |
| 450 var holderIndex = descriptor[1]; | |
| 451 var superclass = holders[holderIndex][superclassName]; | |
| 452 if (superclass.resolve) superclass = superclass.resolve(); | |
| 453 | |
| 454 // Create a new prototype object chained to the superclass prototype. | |
| 455 var intermediate = function() { }; | |
| 456 intermediate.prototype = superclass.prototype; | |
| 457 return new intermediate(); | |
| 458 } | |
| 459 | |
| 460 function compile(__name__, __s__) { | |
| 461 'use strict'; | |
| 462 // TODO(floitsch): evaluate the performance impact of the string | |
| 463 // concatenations. | |
| 464 return eval(__s__ + "\n//# sourceURL=" + __name__ + ".js"); | |
| 465 } | |
| 466 | |
| 467 if (#) { // outputContainsConstantList | |
| 468 function makeConstList(list) { | |
| 469 // By assigning a function to the properties they become part of the | |
| 470 // hidden class. The actual values of the fields don't matter, since we | |
| 471 // only check if they exist. | |
| 472 list.immutable$list = Array; | |
| 473 list.fixed$length = Array; | |
| 474 return list; | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 setupProgram(); | |
| 479 | |
| 480 // Initialize globals. | |
| 481 #; | |
| 482 | |
| 483 // Initialize constants. | |
| 484 #; | |
| 485 | |
| 486 // Initialize static non-final fields. | |
| 487 #; | |
| 488 | |
| 489 // Initialize eager classes. | |
| 490 #; | |
| 491 | |
| 492 var end = Date.now(); | |
| 493 print('Setup: ' + (end - start) + ' ms.'); | |
| 494 | |
| 495 if (true) #(); // Start main. | |
| 496 | |
| 497 }(Date.now(), #) | |
| 498 }"""; | |
| OLD | NEW |