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 |