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 part of dart2js.js_emitter; | |
6 | |
7 | |
8 class OldEmitter implements Emitter { | |
9 final Compiler compiler; | |
10 final CodeEmitterTask task; | |
11 | |
12 final ContainerBuilder containerBuilder = new ContainerBuilder(); | |
13 final ClassEmitter classEmitter = new ClassEmitter(); | |
14 final NsmEmitter nsmEmitter = new NsmEmitter(); | |
15 final InterceptorEmitter interceptorEmitter = new InterceptorEmitter(); | |
16 | |
17 // TODO(johnniwinther): Wrap these fields in a caching strategy. | |
18 final Set<ConstantValue> cachedEmittedConstants; | |
19 final List<jsAst.Statement> cachedEmittedConstantsAst = <jsAst.Statement>[]; | |
20 final Map<Element, ClassBuilder> cachedClassBuilders; | |
21 final Set<Element> cachedElements; | |
22 | |
23 bool needsClassSupport = false; | |
24 bool needsMixinSupport = false; | |
25 bool needsLazyInitializer = false; | |
26 | |
27 /// True if [ContainerBuilder.addMemberMethodFromInfo] used "structured info", | |
28 /// that is, some function was needed for reflection, had stubs, or had a | |
29 /// super alias. | |
30 bool needsStructuredMemberInfo = false; | |
31 | |
32 final Namer namer; | |
33 ConstantEmitter constantEmitter; | |
34 NativeEmitter get nativeEmitter => task.nativeEmitter; | |
35 TypeTestRegistry get typeTestRegistry => task.typeTestRegistry; | |
36 | |
37 // The full code that is written to each hunk part-file. | |
38 Map<OutputUnit, CodeOutput> outputBuffers = new Map<OutputUnit, CodeOutput>(); | |
39 | |
40 String classesCollector; | |
41 Set<ClassElement> get neededClasses => task.neededClasses; | |
42 Map<OutputUnit, List<ClassElement>> get outputClassLists | |
43 => task.outputClassLists; | |
44 Map<OutputUnit, List<ConstantValue>> get outputConstantLists | |
45 => task.outputConstantLists; | |
46 final Map<jsAst.Name, String> mangledFieldNames = | |
47 new HashMap<jsAst.Name, String>(); | |
48 final Map<jsAst.Name, String> mangledGlobalFieldNames = | |
49 new HashMap<jsAst.Name, String>(); | |
50 final Set<jsAst.Name> recordedMangledNames = new Set<jsAst.Name>(); | |
51 | |
52 List<TypedefElement> get typedefsNeededForReflection => | |
53 task.typedefsNeededForReflection; | |
54 | |
55 JavaScriptBackend get backend => compiler.backend; | |
56 TypeVariableHandler get typeVariableHandler => backend.typeVariableHandler; | |
57 | |
58 String get _ => space; | |
59 String get space => compiler.enableMinification ? "" : " "; | |
60 String get n => compiler.enableMinification ? "" : "\n"; | |
61 String get N => compiler.enableMinification ? "\n" : ";\n"; | |
62 | |
63 /** | |
64 * List of expressions and statements that will be included in the | |
65 * precompiled function. | |
66 * | |
67 * To save space, dart2js normally generates constructors and accessors | |
68 * dynamically. This doesn't work in CSP mode, so dart2js emits them directly | |
69 * when in CSP mode. | |
70 */ | |
71 Map<OutputUnit, List<jsAst.Node>> _cspPrecompiledFunctions = | |
72 new Map<OutputUnit, List<jsAst.Node>>(); | |
73 | |
74 Map<OutputUnit, List<jsAst.Expression>> _cspPrecompiledConstructorNames = | |
75 new Map<OutputUnit, List<jsAst.Expression>>(); | |
76 | |
77 /** | |
78 * Accumulate properties for classes and libraries, describing their | |
79 * static/top-level members. | |
80 * Later, these members are emitted when the class or library is emitted. | |
81 * | |
82 * See [getElementDescriptor]. | |
83 */ | |
84 // TODO(ahe): Generate statics with their class, and store only libraries in | |
85 // this map. | |
86 final Map<Fragment, Map<Element, ClassBuilder>> elementDescriptors = | |
87 new Map<Fragment, Map<Element, ClassBuilder>>(); | |
88 | |
89 final bool generateSourceMap; | |
90 | |
91 OldEmitter(Compiler compiler, Namer namer, this.generateSourceMap, this.task) | |
92 : this.compiler = compiler, | |
93 this.namer = namer, | |
94 cachedEmittedConstants = compiler.cacheStrategy.newSet(), | |
95 cachedClassBuilders = compiler.cacheStrategy.newMap(), | |
96 cachedElements = compiler.cacheStrategy.newSet() { | |
97 constantEmitter = new ConstantEmitter( | |
98 compiler, namer, this.constantReference, constantListGenerator); | |
99 containerBuilder.emitter = this; | |
100 classEmitter.emitter = this; | |
101 nsmEmitter.emitter = this; | |
102 interceptorEmitter.emitter = this; | |
103 } | |
104 | |
105 List<jsAst.Node> cspPrecompiledFunctionFor(OutputUnit outputUnit) { | |
106 return _cspPrecompiledFunctions.putIfAbsent( | |
107 outputUnit, | |
108 () => new List<jsAst.Node>()); | |
109 } | |
110 | |
111 List<jsAst.Expression> cspPrecompiledConstructorNamesFor( | |
112 OutputUnit outputUnit) { | |
113 return _cspPrecompiledConstructorNames.putIfAbsent( | |
114 outputUnit, | |
115 () => new List<jsAst.Expression>()); | |
116 } | |
117 | |
118 /// Erases the precompiled information for csp mode for all output units. | |
119 /// Used by the incremental compiler. | |
120 void clearCspPrecompiledNodes() { | |
121 _cspPrecompiledFunctions.clear(); | |
122 _cspPrecompiledConstructorNames.clear(); | |
123 } | |
124 | |
125 @override | |
126 bool isConstantInlinedOrAlreadyEmitted(ConstantValue constant) { | |
127 if (constant.isFunction) return true; // Already emitted. | |
128 if (constant.isPrimitive) return true; // Inlined. | |
129 if (constant.isDummy) return true; // Inlined. | |
130 // The name is null when the constant is already a JS constant. | |
131 // TODO(floitsch): every constant should be registered, so that we can | |
132 // share the ones that take up too much space (like some strings). | |
133 if (namer.constantName(constant) == null) return true; | |
134 return false; | |
135 } | |
136 | |
137 @override | |
138 int compareConstants(ConstantValue a, ConstantValue b) { | |
139 // Inlined constants don't affect the order and sometimes don't even have | |
140 // names. | |
141 int cmp1 = isConstantInlinedOrAlreadyEmitted(a) ? 0 : 1; | |
142 int cmp2 = isConstantInlinedOrAlreadyEmitted(b) ? 0 : 1; | |
143 if (cmp1 + cmp2 < 2) return cmp1 - cmp2; | |
144 | |
145 // Emit constant interceptors first. Constant interceptors for primitives | |
146 // might be used by code that builds other constants. See Issue 18173. | |
147 if (a.isInterceptor != b.isInterceptor) { | |
148 return a.isInterceptor ? -1 : 1; | |
149 } | |
150 | |
151 // Sorting by the long name clusters constants with the same constructor | |
152 // which compresses a tiny bit better. | |
153 int r = namer.constantLongName(a).compareTo(namer.constantLongName(b)); | |
154 if (r != 0) return r; | |
155 // Resolve collisions in the long name by using the constant name (i.e. JS | |
156 // name) which is unique. | |
157 // TODO(herhut): Find a better way to resolve collisions. | |
158 return namer.constantName(a).hashCode.compareTo( | |
159 namer.constantName(b).hashCode); | |
160 } | |
161 | |
162 @override | |
163 jsAst.Expression constantReference(ConstantValue value) { | |
164 if (value.isFunction) { | |
165 FunctionConstantValue functionConstant = value; | |
166 return isolateStaticClosureAccess(functionConstant.element); | |
167 } | |
168 | |
169 // We are only interested in the "isInlined" part, but it does not hurt to | |
170 // test for the other predicates. | |
171 if (isConstantInlinedOrAlreadyEmitted(value)) { | |
172 return constantEmitter.generate(value); | |
173 } | |
174 return js('#.#', [namer.globalObjectForConstant(value), | |
175 namer.constantName(value)]); | |
176 } | |
177 | |
178 jsAst.Expression constantInitializerExpression(ConstantValue value) { | |
179 return constantEmitter.generate(value); | |
180 } | |
181 | |
182 String get name => 'CodeEmitter'; | |
183 | |
184 String get finishIsolateConstructorName | |
185 => '${namer.isolateName}.\$finishIsolateConstructor'; | |
186 String get isolatePropertiesName | |
187 => '${namer.isolateName}.${namer.isolatePropertiesName}'; | |
188 String get lazyInitializerProperty | |
189 => r'$lazy'; | |
190 String get lazyInitializerName | |
191 => '${namer.isolateName}.${lazyInitializerProperty}'; | |
192 String get initName => 'init'; | |
193 | |
194 jsAst.Name get makeConstListProperty | |
195 => namer.internalGlobal('makeConstantList'); | |
196 | |
197 /// The name of the property that contains all field names. | |
198 /// | |
199 /// This property is added to constructors when isolate support is enabled. | |
200 static const String FIELD_NAMES_PROPERTY_NAME = r"$__fields__"; | |
201 | |
202 /// For deferred loading we communicate the initializers via this global var. | |
203 final String deferredInitializers = r"$dart_deferred_initializers$"; | |
204 | |
205 /// Contains the global state that is needed to initialize and load a | |
206 /// deferred library. | |
207 String get globalsHolder => r"$globals$"; | |
208 | |
209 @override | |
210 jsAst.Expression generateEmbeddedGlobalAccess(String global) { | |
211 return js(generateEmbeddedGlobalAccessString(global)); | |
212 } | |
213 | |
214 String generateEmbeddedGlobalAccessString(String global) { | |
215 // TODO(floitsch): don't use 'init' as global embedder storage. | |
216 return '$initName.$global'; | |
217 } | |
218 | |
219 jsAst.PropertyAccess globalPropertyAccess(Element element) { | |
220 jsAst.Name name = namer.globalPropertyName(element); | |
221 jsAst.PropertyAccess pa = new jsAst.PropertyAccess( | |
222 new jsAst.VariableUse(namer.globalObjectFor(element)), | |
223 name); | |
224 return pa; | |
225 } | |
226 | |
227 @override | |
228 jsAst.Expression isolateLazyInitializerAccess(FieldElement element) { | |
229 return jsAst.js('#.#', [namer.globalObjectFor(element), | |
230 namer.lazyInitializerName(element)]); | |
231 } | |
232 | |
233 @override | |
234 jsAst.Expression isolateStaticClosureAccess(FunctionElement element) { | |
235 return jsAst.js('#.#()', | |
236 [namer.globalObjectFor(element), namer.staticClosureName(element)]); | |
237 } | |
238 | |
239 @override | |
240 jsAst.PropertyAccess staticFieldAccess(FieldElement element) { | |
241 return globalPropertyAccess(element); | |
242 } | |
243 | |
244 @override | |
245 jsAst.PropertyAccess staticFunctionAccess(FunctionElement element) { | |
246 return globalPropertyAccess(element); | |
247 } | |
248 | |
249 @override | |
250 jsAst.PropertyAccess constructorAccess(ClassElement element) { | |
251 return globalPropertyAccess(element); | |
252 } | |
253 | |
254 @override | |
255 jsAst.PropertyAccess prototypeAccess(ClassElement element, | |
256 bool hasBeenInstantiated) { | |
257 return jsAst.js('#.prototype', constructorAccess(element)); | |
258 } | |
259 | |
260 @override | |
261 jsAst.PropertyAccess interceptorClassAccess(ClassElement element) { | |
262 return globalPropertyAccess(element); | |
263 } | |
264 | |
265 @override | |
266 jsAst.PropertyAccess typeAccess(Element element) { | |
267 return globalPropertyAccess(element); | |
268 } | |
269 | |
270 @override | |
271 jsAst.Template templateForBuiltin(JsBuiltin builtin) { | |
272 switch (builtin) { | |
273 case JsBuiltin.dartObjectConstructor: | |
274 return jsAst.js.expressionTemplateYielding( | |
275 typeAccess(compiler.objectClass)); | |
276 | |
277 case JsBuiltin.isCheckPropertyToJsConstructorName: | |
278 int isPrefixLength = namer.operatorIsPrefix.length; | |
279 return jsAst.js.expressionTemplateFor('#.substring($isPrefixLength)'); | |
280 | |
281 case JsBuiltin.isFunctionType: | |
282 return backend.rti.representationGenerator.templateForIsFunctionType; | |
283 | |
284 case JsBuiltin.rawRtiToJsConstructorName: | |
285 return jsAst.js.expressionTemplateFor("#.$typeNameProperty"); | |
286 | |
287 case JsBuiltin.rawRuntimeType: | |
288 return jsAst.js.expressionTemplateFor("#.constructor"); | |
289 | |
290 case JsBuiltin.createFunctionTypeRti: | |
291 return backend.rti.representationGenerator | |
292 .templateForCreateFunctionType; | |
293 | |
294 case JsBuiltin.isSubtype: | |
295 // TODO(floitsch): move this closer to where is-check properties are | |
296 // built. | |
297 String isPrefix = namer.operatorIsPrefix; | |
298 return jsAst.js.expressionTemplateFor( | |
299 "('$isPrefix' + #) in #.prototype"); | |
300 | |
301 case JsBuiltin.isGivenTypeRti: | |
302 return jsAst.js.expressionTemplateFor('#.$typeNameProperty === #'); | |
303 | |
304 case JsBuiltin.getMetadata: | |
305 String metadataAccess = | |
306 generateEmbeddedGlobalAccessString(embeddedNames.METADATA); | |
307 return jsAst.js.expressionTemplateFor("$metadataAccess[#]"); | |
308 | |
309 case JsBuiltin.getType: | |
310 String typesAccess = | |
311 generateEmbeddedGlobalAccessString(embeddedNames.TYPES); | |
312 return jsAst.js.expressionTemplateFor("$typesAccess[#]"); | |
313 | |
314 default: | |
315 compiler.internalError(NO_LOCATION_SPANNABLE, | |
316 "Unhandled Builtin: $builtin"); | |
317 return null; | |
318 } | |
319 } | |
320 | |
321 List<jsAst.Statement> buildTrivialNsmHandlers(){ | |
322 return nsmEmitter.buildTrivialNsmHandlers(); | |
323 } | |
324 | |
325 jsAst.Statement buildNativeInfoHandler( | |
326 jsAst.Expression infoAccess, | |
327 jsAst.Expression constructorAccess, | |
328 jsAst.Expression subclassReadGenerator(jsAst.Expression subclass), | |
329 jsAst.Expression interceptorsByTagAccess, | |
330 jsAst.Expression leafTagsAccess) { | |
331 return NativeGenerator.buildNativeInfoHandler(infoAccess, constructorAccess, | |
332 subclassReadGenerator, | |
333 interceptorsByTagAccess, | |
334 leafTagsAccess); | |
335 } | |
336 | |
337 jsAst.ObjectInitializer generateInterceptedNamesSet() { | |
338 return interceptorEmitter.generateInterceptedNamesSet(); | |
339 } | |
340 | |
341 /// In minified mode we want to keep the name for the most common core types. | |
342 bool _isNativeTypeNeedingReflectionName(Element element) { | |
343 if (!element.isClass) return false; | |
344 return (element == compiler.intClass || | |
345 element == compiler.doubleClass || | |
346 element == compiler.numClass || | |
347 element == compiler.stringClass || | |
348 element == compiler.boolClass || | |
349 element == compiler.nullClass || | |
350 element == compiler.listClass); | |
351 } | |
352 | |
353 /// Returns the "reflection name" of an [Element] or [Selector]. | |
354 /// The reflection name of a getter 'foo' is 'foo'. | |
355 /// The reflection name of a setter 'foo' is 'foo='. | |
356 /// The reflection name of a method 'foo' is 'foo:N:M:O', where N is the | |
357 /// number of required arguments, M is the number of optional arguments, and | |
358 /// O is the named arguments. | |
359 /// The reflection name of a constructor is similar to a regular method but | |
360 /// starts with 'new '. | |
361 /// The reflection name of class 'C' is 'C'. | |
362 /// An anonymous mixin application has no reflection name. | |
363 /// This is used by js_mirrors.dart. | |
364 String getReflectionName(elementOrSelector, jsAst.Name mangledName) { | |
365 String name = elementOrSelector.name; | |
366 if (backend.shouldRetainName(name) || | |
367 elementOrSelector is Element && | |
368 // Make sure to retain names of unnamed constructors, and | |
369 // for common native types. | |
370 ((name == '' && | |
371 backend.isAccessibleByReflection(elementOrSelector)) || | |
372 _isNativeTypeNeedingReflectionName(elementOrSelector))) { | |
373 | |
374 // TODO(ahe): Enable the next line when I can tell the difference between | |
375 // an instance method and a global. They may have the same mangled name. | |
376 // if (recordedMangledNames.contains(mangledName)) return null; | |
377 recordedMangledNames.add(mangledName); | |
378 return getReflectionNameInternal(elementOrSelector, mangledName); | |
379 } | |
380 return null; | |
381 } | |
382 | |
383 String getReflectionNameInternal(elementOrSelector, | |
384 jsAst.Name mangledName) { | |
385 String name = namer.privateName(elementOrSelector.memberName); | |
386 if (elementOrSelector.isGetter) return name; | |
387 if (elementOrSelector.isSetter) { | |
388 if (mangledName is! SetterName) return '$name='; | |
389 SetterName setterName = mangledName; | |
390 jsAst.Name base = setterName.base; | |
391 jsAst.Name getter = namer.deriveGetterName(base); | |
392 mangledFieldNames.putIfAbsent(getter, () => name); | |
393 assert(mangledFieldNames[getter] == name); | |
394 recordedMangledNames.add(getter); | |
395 // TODO(karlklose,ahe): we do not actually need to store information | |
396 // about the name of this setter in the output, but it is needed for | |
397 // marking the function as invokable by reflection. | |
398 return '$name='; | |
399 } | |
400 if (elementOrSelector is Element && elementOrSelector.isClosure) { | |
401 // Closures are synthesized and their name might conflict with existing | |
402 // globals. Assign an illegal name, and make sure they don't clash | |
403 // with each other. | |
404 return " $name"; | |
405 } | |
406 if (elementOrSelector is Selector | |
407 || elementOrSelector.isFunction | |
408 || elementOrSelector.isConstructor) { | |
409 int positionalParameterCount; | |
410 String namedArguments = ''; | |
411 bool isConstructor = false; | |
412 if (elementOrSelector is Selector) { | |
413 CallStructure callStructure = elementOrSelector.callStructure; | |
414 positionalParameterCount = callStructure.positionalArgumentCount; | |
415 namedArguments = namedParametersAsReflectionNames(callStructure); | |
416 } else { | |
417 FunctionElement function = elementOrSelector; | |
418 if (function.isConstructor) { | |
419 isConstructor = true; | |
420 name = Elements.reconstructConstructorName(function); | |
421 } | |
422 FunctionSignature signature = function.functionSignature; | |
423 positionalParameterCount = signature.requiredParameterCount; | |
424 if (signature.optionalParametersAreNamed) { | |
425 var names = []; | |
426 for (Element e in signature.optionalParameters) { | |
427 names.add(e.name); | |
428 } | |
429 CallStructure callStructure = | |
430 new CallStructure(positionalParameterCount, names); | |
431 namedArguments = namedParametersAsReflectionNames(callStructure); | |
432 } else { | |
433 // Named parameters are handled differently by mirrors. For unnamed | |
434 // parameters, they are actually required if invoked | |
435 // reflectively. Also, if you have a method c(x) and c([x]) they both | |
436 // get the same mangled name, so they must have the same reflection | |
437 // name. | |
438 positionalParameterCount += signature.optionalParameterCount; | |
439 } | |
440 } | |
441 String suffix = '$name:$positionalParameterCount$namedArguments'; | |
442 return (isConstructor) ? 'new $suffix' : suffix; | |
443 } | |
444 Element element = elementOrSelector; | |
445 if (element.isGenerativeConstructorBody) { | |
446 return null; | |
447 } else if (element.isClass) { | |
448 ClassElement cls = element; | |
449 if (cls.isUnnamedMixinApplication) return null; | |
450 return cls.name; | |
451 } else if (element.isTypedef) { | |
452 return element.name; | |
453 } | |
454 throw compiler.internalError(element, | |
455 'Do not know how to reflect on this $element.'); | |
456 } | |
457 | |
458 String namedParametersAsReflectionNames(CallStructure structure) { | |
459 if (structure.isUnnamed) return ''; | |
460 String names = structure.getOrderedNamedArguments().join(':'); | |
461 return ':$names'; | |
462 } | |
463 | |
464 jsAst.Statement buildCspPrecompiledFunctionFor( | |
465 OutputUnit outputUnit) { | |
466 if (compiler.useContentSecurityPolicy) { | |
467 // TODO(ahe): Compute a hash code. | |
468 // TODO(sigurdm): Avoid this precompiled function. Generated | |
469 // constructor-functions and getter/setter functions can be stored in the | |
470 // library-description table. Setting properties on these can be moved to | |
471 // finishClasses. | |
472 return js.statement(r""" | |
473 #precompiled = function ($collectedClasses$) { | |
474 #norename; | |
475 var $desc; | |
476 #functions; | |
477 return #result; | |
478 };""", | |
479 {'norename': new jsAst.Comment("// ::norenaming:: "), | |
480 'precompiled': generateEmbeddedGlobalAccess(embeddedNames.PRECOMPILED), | |
481 'functions': cspPrecompiledFunctionFor(outputUnit), | |
482 'result': new jsAst.ArrayInitializer( | |
483 cspPrecompiledConstructorNamesFor(outputUnit))}); | |
484 } else { | |
485 return js.comment("Constructors are generated at runtime."); | |
486 } | |
487 } | |
488 | |
489 void assembleClass(Class cls, ClassBuilder enclosingBuilder, | |
490 Fragment fragment) { | |
491 ClassElement classElement = cls.element; | |
492 compiler.withCurrentElement(classElement, () { | |
493 if (compiler.hasIncrementalSupport) { | |
494 ClassBuilder cachedBuilder = | |
495 cachedClassBuilders.putIfAbsent(classElement, () { | |
496 ClassBuilder builder = | |
497 new ClassBuilder.forClass(classElement, namer); | |
498 classEmitter.emitClass(cls, builder, fragment); | |
499 return builder; | |
500 }); | |
501 invariant(classElement, cachedBuilder.fields.isEmpty); | |
502 invariant(classElement, cachedBuilder.superName == null); | |
503 invariant(classElement, cachedBuilder.functionType == null); | |
504 invariant(classElement, cachedBuilder.fieldMetadata == null); | |
505 enclosingBuilder.properties.addAll(cachedBuilder.properties); | |
506 } else { | |
507 classEmitter.emitClass(cls, enclosingBuilder, fragment); | |
508 } | |
509 }); | |
510 } | |
511 | |
512 void assembleStaticFunctions(Iterable<Method> staticFunctions, | |
513 Fragment fragment) { | |
514 if (staticFunctions == null) return; | |
515 | |
516 for (Method method in staticFunctions) { | |
517 Element element = method.element; | |
518 // We need to filter out null-elements for the interceptors. | |
519 // TODO(floitsch): use the precomputed interceptors here. | |
520 if (element == null) continue; | |
521 ClassBuilder builder = new ClassBuilder.forStatics(element, namer); | |
522 containerBuilder.addMemberMethod(method, builder); | |
523 getElementDescriptor(element, fragment).properties | |
524 .addAll(builder.properties); | |
525 } | |
526 } | |
527 | |
528 jsAst.Statement buildStaticNonFinalFieldInitializations( | |
529 OutputUnit outputUnit) { | |
530 jsAst.Statement buildInitialization(Element element, | |
531 jsAst.Expression initialValue) { | |
532 // Note: `namer.currentIsolate` refers to the isolate properties here. | |
533 return js.statement('${namer.currentIsolate}.# = #', | |
534 [namer.globalPropertyName(element), initialValue]); | |
535 } | |
536 | |
537 bool inMainUnit = (outputUnit == compiler.deferredLoadTask.mainOutputUnit); | |
538 JavaScriptConstantCompiler handler = backend.constants; | |
539 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
540 | |
541 Iterable<Element> fields = task.outputStaticNonFinalFieldLists[outputUnit]; | |
542 // If the outputUnit does not contain any static non-final fields, then | |
543 // [fields] is `null`. | |
544 if (fields != null) { | |
545 for (Element element in fields) { | |
546 compiler.withCurrentElement(element, () { | |
547 ConstantValue constant = handler.getInitialValueFor(element); | |
548 parts.add(buildInitialization(element, constantReference(constant))); | |
549 }); | |
550 } | |
551 } | |
552 | |
553 if (inMainUnit && task.outputStaticNonFinalFieldLists.length > 1) { | |
554 // In the main output-unit we output a stub initializer for deferred | |
555 // variables, so that `isolateProperties` stays a fast object. | |
556 task.outputStaticNonFinalFieldLists.forEach( | |
557 (OutputUnit fieldsOutputUnit, Iterable<VariableElement> fields) { | |
558 if (fieldsOutputUnit == outputUnit) return; // Skip the main unit. | |
559 for (Element element in fields) { | |
560 compiler.withCurrentElement(element, () { | |
561 parts.add(buildInitialization(element, jsAst.number(0))); | |
562 }); | |
563 } | |
564 }); | |
565 } | |
566 | |
567 return new jsAst.Block(parts); | |
568 } | |
569 | |
570 jsAst.Statement buildLazilyInitializedStaticFields() { | |
571 JavaScriptConstantCompiler handler = backend.constants; | |
572 List<VariableElement> lazyFields = | |
573 handler.getLazilyInitializedFieldsForEmission(); | |
574 if (lazyFields.isNotEmpty) { | |
575 needsLazyInitializer = true; | |
576 List<jsAst.Expression> laziesInfo = buildLaziesInfo(lazyFields); | |
577 return js.statement(''' | |
578 (function(lazies) { | |
579 for (var i = 0; i < lazies.length; ) { | |
580 var fieldName = lazies[i++]; | |
581 var getterName = lazies[i++]; | |
582 if (#notMinified) { | |
583 var staticName = lazies[i++]; | |
584 } | |
585 var lazyValue = lazies[i++]; | |
586 | |
587 // We build the lazy-check here: | |
588 // lazyInitializer(fieldName, getterName, lazyValue, staticName); | |
589 // 'staticName' is used for error reporting in non-minified mode. | |
590 // 'lazyValue' must be a closure that constructs the initial value. | |
591 if (#notMinified) { | |
592 #lazy(fieldName, getterName, lazyValue, staticName); | |
593 } else { | |
594 #lazy(fieldName, getterName, lazyValue); | |
595 } | |
596 } | |
597 })(#laziesInfo) | |
598 ''', {'notMinified': !compiler.enableMinification, | |
599 'laziesInfo': new jsAst.ArrayInitializer(laziesInfo), | |
600 'lazy': js(lazyInitializerName)}); | |
601 } else { | |
602 return js.comment("No lazy statics."); | |
603 } | |
604 } | |
605 | |
606 List<jsAst.Expression> buildLaziesInfo(List<VariableElement> lazies) { | |
607 List<jsAst.Expression> laziesInfo = <jsAst.Expression>[]; | |
608 for (VariableElement element in Elements.sortedByPosition(lazies)) { | |
609 jsAst.Expression code = backend.generatedCode[element]; | |
610 // The code is null if we ended up not needing the lazily | |
611 // initialized field after all because of constant folding | |
612 // before code generation. | |
613 if (code == null) continue; | |
614 laziesInfo.add(js.quoteName(namer.globalPropertyName(element))); | |
615 laziesInfo.add(js.quoteName(namer.lazyInitializerName(element))); | |
616 if (!compiler.enableMinification) { | |
617 laziesInfo.add(js.string(element.name)); | |
618 } | |
619 laziesInfo.add(code); | |
620 } | |
621 return laziesInfo; | |
622 } | |
623 | |
624 // TODO(sra): Remove this unused function. | |
625 jsAst.Expression buildLazilyInitializedStaticField( | |
626 VariableElement element, {String isolateProperties}) { | |
627 jsAst.Expression code = backend.generatedCode[element]; | |
628 // The code is null if we ended up not needing the lazily | |
629 // initialized field after all because of constant folding | |
630 // before code generation. | |
631 if (code == null) return null; | |
632 // The code only computes the initial value. We build the lazy-check | |
633 // here: | |
634 // lazyInitializer(fieldName, getterName, initial, name, prototype); | |
635 // The name is used for error reporting. The 'initial' must be a | |
636 // closure that constructs the initial value. | |
637 if (isolateProperties != null) { | |
638 // This is currently only used in incremental compilation to patch | |
639 // in new lazy values. | |
640 return js('#(#,#,#,#,#)', | |
641 [js(lazyInitializerName), | |
642 js.quoteName(namer.globalPropertyName(element)), | |
643 js.quoteName(namer.lazyInitializerName(element)), | |
644 code, | |
645 js.string(element.name), | |
646 isolateProperties]); | |
647 } | |
648 | |
649 if (compiler.enableMinification) { | |
650 return js('#(#,#,#)', | |
651 [js(lazyInitializerName), | |
652 js.quoteName(namer.globalPropertyName(element)), | |
653 js.quoteName(namer.lazyInitializerName(element)), | |
654 code]); | |
655 } else { | |
656 return js('#(#,#,#,#)', | |
657 [js(lazyInitializerName), | |
658 js.quoteName(namer.globalPropertyName(element)), | |
659 js.quoteName(namer.lazyInitializerName(element)), | |
660 code, | |
661 js.string(element.name)]); | |
662 } | |
663 } | |
664 | |
665 jsAst.Statement buildMetadata(Program program, OutputUnit outputUnit) { | |
666 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
667 | |
668 jsAst.Expression types = program.metadataTypesForOutputUnit(outputUnit); | |
669 | |
670 if (outputUnit == compiler.deferredLoadTask.mainOutputUnit) { | |
671 jsAst.Expression metadataAccess = | |
672 generateEmbeddedGlobalAccess(embeddedNames.METADATA); | |
673 jsAst.Expression typesAccess = | |
674 generateEmbeddedGlobalAccess(embeddedNames.TYPES); | |
675 | |
676 parts..add(js.statement('# = #;', [metadataAccess, program.metadata])) | |
677 ..add(js.statement('# = #;', [typesAccess, types])); | |
678 } else if (types != null) { | |
679 parts.add(js.statement('var ${namer.deferredTypesName} = #;', | |
680 types)); | |
681 } | |
682 return new jsAst.Block(parts); | |
683 } | |
684 | |
685 jsAst.Statement buildCompileTimeConstants(List<Constant> constants, | |
686 {bool isMainFragment}) { | |
687 assert(isMainFragment != null); | |
688 | |
689 if (constants.isEmpty) return js.comment("No constants in program."); | |
690 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
691 if (compiler.hasIncrementalSupport && isMainFragment) { | |
692 parts = cachedEmittedConstantsAst; | |
693 } | |
694 for (Constant constant in constants) { | |
695 ConstantValue constantValue = constant.value; | |
696 if (compiler.hasIncrementalSupport && isMainFragment) { | |
697 if (cachedEmittedConstants.contains(constantValue)) continue; | |
698 cachedEmittedConstants.add(constantValue); | |
699 } | |
700 parts.add(buildConstantInitializer(constantValue)); | |
701 } | |
702 | |
703 return new jsAst.Block(parts); | |
704 } | |
705 | |
706 jsAst.Statement buildConstantInitializer(ConstantValue constant) { | |
707 jsAst.Name name = namer.constantName(constant); | |
708 return js.statement('#.# = #', | |
709 [namer.globalObjectForConstant(constant), name, | |
710 constantInitializerExpression(constant)]); | |
711 } | |
712 | |
713 jsAst.Expression constantListGenerator(jsAst.Expression array) { | |
714 // TODO(floitsch): there is no harm in caching the template. | |
715 return js('${namer.isolateName}.#(#)', [makeConstListProperty, array]); | |
716 } | |
717 | |
718 jsAst.Statement buildMakeConstantList() { | |
719 if (task.outputContainsConstantList) { | |
720 return js.statement(r''' | |
721 // Functions are stored in the hidden class and not as properties in | |
722 // the object. We never actually look at the value, but only want | |
723 // to know if the property exists. | |
724 #.# = function (list) { | |
725 list.immutable$list = Array; | |
726 list.fixed$length = Array; | |
727 return list; | |
728 }''', | |
729 [namer.isolateName, makeConstListProperty]); | |
730 } else { | |
731 return js.comment("Output contains no constant list."); | |
732 } | |
733 } | |
734 | |
735 jsAst.Statement buildFunctionThatReturnsNull() { | |
736 return js.statement('#.# = function() {}', | |
737 [namer.isolateName, | |
738 backend.rti.getFunctionThatReturnsNullName]); | |
739 } | |
740 | |
741 jsAst.Expression generateFunctionThatReturnsNull() { | |
742 return js("#.#", [namer.isolateName, | |
743 backend.rti.getFunctionThatReturnsNullName]); | |
744 } | |
745 | |
746 buildMain(jsAst.Statement invokeMain) { | |
747 if (compiler.isMockCompilation) return js.comment("Mock compilation"); | |
748 | |
749 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
750 | |
751 if (NativeGenerator.needsIsolateAffinityTagInitialization(backend)) { | |
752 parts.add( | |
753 NativeGenerator.generateIsolateAffinityTagInitialization( | |
754 backend, | |
755 generateEmbeddedGlobalAccess, | |
756 js("convertToFastObject", []))); | |
757 } | |
758 | |
759 parts..add(js.comment('BEGIN invoke [main].')) | |
760 ..add(invokeMain) | |
761 ..add(js.comment('END invoke [main].')); | |
762 | |
763 return new jsAst.Block(parts); | |
764 } | |
765 | |
766 jsAst.Statement buildInitFunction() { | |
767 jsAst.Expression allClassesAccess = | |
768 generateEmbeddedGlobalAccess(embeddedNames.ALL_CLASSES); | |
769 jsAst.Expression getTypeFromNameAccess = | |
770 generateEmbeddedGlobalAccess(embeddedNames.GET_TYPE_FROM_NAME); | |
771 jsAst.Expression interceptorsByTagAccess = | |
772 generateEmbeddedGlobalAccess(embeddedNames.INTERCEPTORS_BY_TAG); | |
773 jsAst.Expression leafTagsAccess = | |
774 generateEmbeddedGlobalAccess(embeddedNames.LEAF_TAGS); | |
775 jsAst.Expression finishedClassesAccess = | |
776 generateEmbeddedGlobalAccess(embeddedNames.FINISHED_CLASSES); | |
777 jsAst.Expression cyclicThrow = | |
778 staticFunctionAccess(backend.getCyclicThrowHelper()); | |
779 jsAst.Expression laziesAccess = | |
780 generateEmbeddedGlobalAccess(embeddedNames.LAZIES); | |
781 | |
782 return js.statement(''' | |
783 function init() { | |
784 $isolatePropertiesName = Object.create(null); | |
785 #allClasses = map(); | |
786 #getTypeFromName = function(name) {return #allClasses[name];}; | |
787 #interceptorsByTag = map(); | |
788 #leafTags = map(); | |
789 #finishedClasses = map(); | |
790 | |
791 if (#needsLazyInitializer) { | |
792 // [staticName] is only provided in non-minified mode. If missing, we | |
793 // fall back to [fieldName]. Likewise, [prototype] is optional and | |
794 // defaults to the isolateProperties object. | |
795 $lazyInitializerName = function (fieldName, getterName, lazyValue, | |
796 staticName, prototype) { | |
797 if (!#lazies) #lazies = Object.create(null); | |
798 #lazies[fieldName] = getterName; | |
799 | |
800 // 'prototype' will be undefined except if we are doing an update | |
801 // during incremental compilation. In this case we put the lazy | |
802 // field directly on the isolate instead of the isolateProperties. | |
803 prototype = prototype || $isolatePropertiesName; | |
804 var sentinelUndefined = {}; | |
805 var sentinelInProgress = {}; | |
806 prototype[fieldName] = sentinelUndefined; | |
807 | |
808 prototype[getterName] = function () { | |
809 var result = this[fieldName]; | |
810 try { | |
811 if (result === sentinelUndefined) { | |
812 this[fieldName] = sentinelInProgress; | |
813 | |
814 try { | |
815 result = this[fieldName] = lazyValue(); | |
816 } finally { | |
817 // Use try-finally, not try-catch/throw as it destroys the | |
818 // stack trace. | |
819 if (result === sentinelUndefined) | |
820 this[fieldName] = null; | |
821 } | |
822 } else { | |
823 if (result === sentinelInProgress) | |
824 // In minified mode, static name is not provided, so fall | |
825 // back to the minified fieldName. | |
826 #cyclicThrow(staticName || fieldName); | |
827 } | |
828 | |
829 return result; | |
830 } finally { | |
831 this[getterName] = function() { return this[fieldName]; }; | |
832 } | |
833 } | |
834 } | |
835 } | |
836 | |
837 // We replace the old Isolate function with a new one that initializes | |
838 // all its fields with the initial (and often final) value of all | |
839 // globals. | |
840 // | |
841 // We also copy over old values like the prototype, and the | |
842 // isolateProperties themselves. | |
843 $finishIsolateConstructorName = function (oldIsolate) { | |
844 var isolateProperties = oldIsolate.#isolatePropertiesName; | |
845 function Isolate() { | |
846 | |
847 var staticNames = Object.keys(isolateProperties); | |
848 for (var i = 0; i < staticNames.length; i++) { | |
849 var staticName = staticNames[i]; | |
850 this[staticName] = isolateProperties[staticName]; | |
851 } | |
852 | |
853 // Reset lazy initializers to null. | |
854 // When forcing the object to fast mode (below) v8 will consider | |
855 // functions as part the object's map. Since we will change them | |
856 // (after the first call to the getter), we would have a map | |
857 // transition. | |
858 var lazies = init.lazies; | |
859 var lazyInitializers = lazies ? Object.keys(lazies) : []; | |
860 for (var i = 0; i < lazyInitializers.length; i++) { | |
861 this[lazies[lazyInitializers[i]]] = null; | |
862 } | |
863 | |
864 // Use the newly created object as prototype. In Chrome, | |
865 // this creates a hidden class for the object and makes | |
866 // sure it is fast to access. | |
867 function ForceEfficientMap() {} | |
868 ForceEfficientMap.prototype = this; | |
869 new ForceEfficientMap(); | |
870 | |
871 // Now, after being a fast map we can set the lazies again. | |
872 for (var i = 0; i < lazyInitializers.length; i++) { | |
873 var lazyInitName = lazies[lazyInitializers[i]]; | |
874 this[lazyInitName] = isolateProperties[lazyInitName]; | |
875 } | |
876 } | |
877 Isolate.prototype = oldIsolate.prototype; | |
878 Isolate.prototype.constructor = Isolate; | |
879 Isolate.#isolatePropertiesName = isolateProperties; | |
880 if (#outputContainsConstantList) { | |
881 Isolate.#makeConstListProperty = oldIsolate.#makeConstListProperty; | |
882 } | |
883 Isolate.#functionThatReturnsNullProperty = | |
884 oldIsolate.#functionThatReturnsNullProperty; | |
885 if (#hasIncrementalSupport) { | |
886 Isolate.#lazyInitializerProperty = | |
887 oldIsolate.#lazyInitializerProperty; | |
888 } | |
889 return Isolate; | |
890 } | |
891 | |
892 }''', {'allClasses': allClassesAccess, | |
893 'getTypeFromName': getTypeFromNameAccess, | |
894 'interceptorsByTag': interceptorsByTagAccess, | |
895 'leafTags': leafTagsAccess, | |
896 'finishedClasses': finishedClassesAccess, | |
897 'needsLazyInitializer': needsLazyInitializer, | |
898 'lazies': laziesAccess, 'cyclicThrow': cyclicThrow, | |
899 'isolatePropertiesName': namer.isolatePropertiesName, | |
900 'outputContainsConstantList': task.outputContainsConstantList, | |
901 'makeConstListProperty': makeConstListProperty, | |
902 'functionThatReturnsNullProperty': | |
903 backend.rti.getFunctionThatReturnsNullName, | |
904 'hasIncrementalSupport': compiler.hasIncrementalSupport, | |
905 'lazyInitializerProperty': lazyInitializerProperty,}); | |
906 } | |
907 | |
908 jsAst.Statement buildConvertToFastObjectFunction() { | |
909 List<jsAst.Statement> debugCode = <jsAst.Statement>[]; | |
910 if (DEBUG_FAST_OBJECTS) { | |
911 debugCode.add(js.statement(r''' | |
912 // The following only works on V8 when run with option | |
913 // "--allow-natives-syntax". We use'new Function' because the | |
914 // miniparser does not understand V8 native syntax. | |
915 if (typeof print === "function") { | |
916 var HasFastProperties = | |
917 new Function("a", "return %HasFastProperties(a)"); | |
918 print("Size of global object: " | |
919 + String(Object.getOwnPropertyNames(properties).length) | |
920 + ", fast properties " + HasFastProperties(properties)); | |
921 }''')); | |
922 } | |
923 | |
924 return js.statement(r''' | |
925 function convertToFastObject(properties) { | |
926 // Create an instance that uses 'properties' as prototype. This should | |
927 // make 'properties' a fast object. | |
928 function MyClass() {}; | |
929 MyClass.prototype = properties; | |
930 new MyClass(); | |
931 #; | |
932 return properties; | |
933 }''', [debugCode]); | |
934 } | |
935 | |
936 jsAst.Statement buildConvertToSlowObjectFunction() { | |
937 return js.statement(r''' | |
938 function convertToSlowObject(properties) { | |
939 // Add and remove a property to make the object transition into hashmap | |
940 // mode. | |
941 properties.__MAGIC_SLOW_PROPERTY = 1; | |
942 delete properties.__MAGIC_SLOW_PROPERTY; | |
943 return properties; | |
944 }'''); | |
945 } | |
946 | |
947 jsAst.Statement buildSupportsDirectProtoAccess() { | |
948 jsAst.Statement supportsDirectProtoAccess; | |
949 | |
950 if (compiler.hasIncrementalSupport) { | |
951 supportsDirectProtoAccess = js.statement(r''' | |
952 var supportsDirectProtoAccess = false; | |
953 '''); | |
954 } else { | |
955 supportsDirectProtoAccess = js.statement(r''' | |
956 var supportsDirectProtoAccess = (function () { | |
957 var cls = function () {}; | |
958 cls.prototype = {'p': {}}; | |
959 var object = new cls(); | |
960 return object.__proto__ && | |
961 object.__proto__.p === cls.prototype.p; | |
962 })(); | |
963 '''); | |
964 } | |
965 | |
966 return supportsDirectProtoAccess; | |
967 } | |
968 | |
969 jsAst.Expression generateLibraryDescriptor(LibraryElement library, | |
970 Fragment fragment) { | |
971 var uri = ""; | |
972 if (!compiler.enableMinification || backend.mustPreserveUris) { | |
973 uri = library.canonicalUri; | |
974 if (uri.scheme == 'file' && compiler.outputUri != null) { | |
975 uri = relativize(compiler.outputUri, library.canonicalUri, false); | |
976 } | |
977 } | |
978 | |
979 String libraryName = | |
980 (!compiler.enableMinification || backend.mustRetainLibraryNames) ? | |
981 library.getLibraryName() : | |
982 ""; | |
983 | |
984 jsAst.Fun metadata = task.metadataCollector.buildMetadataFunction(library); | |
985 | |
986 ClassBuilder descriptor = elementDescriptors[fragment][library]; | |
987 | |
988 jsAst.ObjectInitializer initializer; | |
989 if (descriptor == null) { | |
990 // Nothing of the library was emitted. | |
991 // TODO(floitsch): this should not happen. We currently have an example | |
992 // with language/prefix6_negative_test.dart where we have an instance | |
993 // method without its corresponding class. | |
994 initializer = new jsAst.ObjectInitializer([]); | |
995 } else { | |
996 initializer = descriptor.toObjectInitializer(); | |
997 } | |
998 | |
999 compiler.dumpInfoTask.registerElementAst(library, metadata); | |
1000 compiler.dumpInfoTask.registerElementAst(library, initializer); | |
1001 | |
1002 List<jsAst.Expression> parts = <jsAst.Expression>[]; | |
1003 parts..add(js.string(libraryName)) | |
1004 ..add(js.string(uri.toString())) | |
1005 ..add(metadata == null ? new jsAst.ArrayHole() : metadata) | |
1006 ..add(js('#', namer.globalObjectFor(library))) | |
1007 ..add(initializer); | |
1008 if (library == compiler.mainApp) { | |
1009 parts.add(js.number(1)); | |
1010 } | |
1011 | |
1012 return new jsAst.ArrayInitializer(parts); | |
1013 } | |
1014 | |
1015 void assemblePrecompiledConstructor(OutputUnit outputUnit, | |
1016 jsAst.Name constructorName, | |
1017 jsAst.Expression constructorAst, | |
1018 List<jsAst.Name> fields) { | |
1019 cspPrecompiledFunctionFor(outputUnit).add( | |
1020 new jsAst.FunctionDeclaration(constructorName, constructorAst)); | |
1021 | |
1022 String fieldNamesProperty = FIELD_NAMES_PROPERTY_NAME; | |
1023 bool hasIsolateSupport = compiler.hasIsolateSupport; | |
1024 jsAst.Node fieldNamesArray; | |
1025 if (hasIsolateSupport) { | |
1026 fieldNamesArray = | |
1027 new jsAst.ArrayInitializer(fields.map(js.quoteName).toList()); | |
1028 } else { | |
1029 fieldNamesArray = new jsAst.LiteralNull(); | |
1030 } | |
1031 | |
1032 cspPrecompiledFunctionFor(outputUnit).add(js.statement(r''' | |
1033 { | |
1034 #constructorName.#typeNameProperty = #constructorNameString; | |
1035 // IE does not have a name property. | |
1036 if (!("name" in #constructorName)) | |
1037 #constructorName.name = #constructorNameString; | |
1038 $desc = $collectedClasses$.#constructorName[1]; | |
1039 #constructorName.prototype = $desc; | |
1040 ''' /* next string is not a raw string */ ''' | |
1041 if (#hasIsolateSupport) { | |
1042 #constructorName.$fieldNamesProperty = #fieldNamesArray; | |
1043 } | |
1044 }''', | |
1045 {"constructorName": constructorName, | |
1046 "typeNameProperty": typeNameProperty, | |
1047 "constructorNameString": js.quoteName(constructorName), | |
1048 "hasIsolateSupport": hasIsolateSupport, | |
1049 "fieldNamesArray": fieldNamesArray})); | |
1050 | |
1051 cspPrecompiledConstructorNamesFor(outputUnit).add(js('#', constructorName)); | |
1052 } | |
1053 | |
1054 void assembleTypedefs(Program program) { | |
1055 Fragment mainFragment = program.mainFragment; | |
1056 OutputUnit mainOutputUnit = mainFragment.outputUnit; | |
1057 | |
1058 // Emit all required typedef declarations into the main output unit. | |
1059 // TODO(karlklose): unify required classes and typedefs to declarations | |
1060 // and have builders for each kind. | |
1061 for (TypedefElement typedef in typedefsNeededForReflection) { | |
1062 LibraryElement library = typedef.library; | |
1063 // TODO(karlklose): add a TypedefBuilder and move this code there. | |
1064 DartType type = typedef.alias; | |
1065 // TODO(zarah): reify type variables once reflection on type arguments of | |
1066 // typedefs is supported. | |
1067 jsAst.Expression typeIndex = | |
1068 task.metadataCollector.reifyType(type, ignoreTypeVariables: true); | |
1069 ClassBuilder builder = new ClassBuilder.forStatics(typedef, namer); | |
1070 builder.addPropertyByName(embeddedNames.TYPEDEF_TYPE_PROPERTY_NAME, | |
1071 typeIndex); | |
1072 builder.addPropertyByName(embeddedNames.TYPEDEF_PREDICATE_PROPERTY_NAME, | |
1073 js.boolean(true)); | |
1074 | |
1075 // We can be pretty sure that the objectClass is initialized, since | |
1076 // typedefs are only emitted with reflection, which requires lots of | |
1077 // classes. | |
1078 assert(compiler.objectClass != null); | |
1079 builder.superName = namer.className(compiler.objectClass); | |
1080 jsAst.Node declaration = builder.toObjectInitializer(); | |
1081 jsAst.Name mangledName = namer.globalPropertyName(typedef); | |
1082 String reflectionName = getReflectionName(typedef, mangledName); | |
1083 getElementDescriptor(library, mainFragment) | |
1084 ..addProperty(mangledName, declaration) | |
1085 ..addPropertyByName("+$reflectionName", js.string('')); | |
1086 // Also emit a trivial constructor for CSP mode. | |
1087 jsAst.Name constructorName = mangledName; | |
1088 jsAst.Expression constructorAst = js('function() {}'); | |
1089 List<jsAst.Name> fieldNames = []; | |
1090 assemblePrecompiledConstructor(mainOutputUnit, | |
1091 constructorName, | |
1092 constructorAst, | |
1093 fieldNames); | |
1094 } | |
1095 } | |
1096 | |
1097 jsAst.Statement buildGlobalObjectSetup(bool isProgramSplit) { | |
1098 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
1099 | |
1100 parts.add(js.comment(""" | |
1101 // The global objects start as so-called "slow objects". For V8, this | |
1102 // means that it won't try to make map transitions as we add properties | |
1103 // to these objects. Later on, we attempt to turn these objects into | |
1104 // fast objects by calling "convertToFastObject" (see | |
1105 // [emitConvertToFastObjectFunction]). | |
1106 """)); | |
1107 | |
1108 for (String globalObject in Namer.reservedGlobalObjectNames) { | |
1109 if (isProgramSplit) { | |
1110 String template = | |
1111 "var #globalObject = #globalsHolder.#globalObject = map();"; | |
1112 parts.add(js.statement(template, {"globalObject": globalObject, | |
1113 "globalsHolder": globalsHolder})); | |
1114 } else { | |
1115 parts.add(js.statement("var #globalObject = map();", | |
1116 {"globalObject": globalObject})); | |
1117 } | |
1118 | |
1119 } | |
1120 | |
1121 return new jsAst.Block(parts); | |
1122 } | |
1123 | |
1124 jsAst.Statement buildConvertGlobalObjectToFastObjects() { | |
1125 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
1126 | |
1127 for (String globalObject in Namer.reservedGlobalObjectNames) { | |
1128 parts.add(js.statement( | |
1129 '#globalObject = convertToFastObject(#globalObject);', | |
1130 {"globalObject": globalObject})); | |
1131 } | |
1132 | |
1133 return new jsAst.Block(parts); | |
1134 } | |
1135 | |
1136 jsAst.Statement buildDebugFastObjectCode() { | |
1137 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
1138 | |
1139 if (DEBUG_FAST_OBJECTS) { | |
1140 parts.add(js.statement(r''' | |
1141 // The following only works on V8 when run with option | |
1142 // "--allow-natives-syntax". We use'new Function' because the | |
1143 // miniparser does not understand V8 native syntax. | |
1144 if (typeof print === "function") { | |
1145 var HasFastProperties = | |
1146 new Function("a", "return %HasFastProperties(a)"); | |
1147 print("Size of global helper object: " | |
1148 + String(Object.getOwnPropertyNames(H).length) | |
1149 + ", fast properties " + HasFastProperties(H)); | |
1150 print("Size of global platform object: " | |
1151 + String(Object.getOwnPropertyNames(P).length) | |
1152 + ", fast properties " + HasFastProperties(P)); | |
1153 print("Size of global dart:html object: " | |
1154 + String(Object.getOwnPropertyNames(W).length) | |
1155 + ", fast properties " + HasFastProperties(W)); | |
1156 print("Size of isolate properties object: " | |
1157 + String(Object.getOwnPropertyNames($).length) | |
1158 + ", fast properties " + HasFastProperties($)); | |
1159 print("Size of constant object: " | |
1160 + String(Object.getOwnPropertyNames(C).length) | |
1161 + ", fast properties " + HasFastProperties(C)); | |
1162 var names = Object.getOwnPropertyNames($); | |
1163 for (var i = 0; i < names.length; i++) { | |
1164 print("$." + names[i]); | |
1165 } | |
1166 } | |
1167 ''')); | |
1168 | |
1169 for (String object in Namer.userGlobalObjects) { | |
1170 parts.add(js.statement(''' | |
1171 if (typeof print === "function") { | |
1172 print("Size of " + #objectString + ": " | |
1173 + String(Object.getOwnPropertyNames(#object).length) | |
1174 + ", fast properties " + HasFastProperties(#object)); | |
1175 } | |
1176 ''', {"object": object, "objectString": js.string(object)})); | |
1177 } | |
1178 } | |
1179 | |
1180 return new jsAst.Block(parts); | |
1181 } | |
1182 | |
1183 jsAst.Statement buildMangledNames() { | |
1184 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
1185 | |
1186 if (!mangledFieldNames.isEmpty) { | |
1187 List<jsAst.Name> keys = mangledFieldNames.keys.toList()..sort(); | |
1188 var properties = []; | |
1189 for (jsAst.Name key in keys) { | |
1190 var value = js.string(mangledFieldNames[key]); | |
1191 properties.add(new jsAst.Property(key, value)); | |
1192 } | |
1193 | |
1194 jsAst.Expression mangledNamesAccess = | |
1195 generateEmbeddedGlobalAccess(embeddedNames.MANGLED_NAMES); | |
1196 var map = new jsAst.ObjectInitializer(properties); | |
1197 parts.add(js.statement('# = #', [mangledNamesAccess, map])); | |
1198 } | |
1199 | |
1200 if (!mangledGlobalFieldNames.isEmpty) { | |
1201 List<jsAst.Name> keys = mangledGlobalFieldNames.keys.toList() | |
1202 ..sort(); | |
1203 List<jsAst.Property> properties = <jsAst.Property>[]; | |
1204 for (jsAst.Name key in keys) { | |
1205 jsAst.Literal value = js.string(mangledGlobalFieldNames[key]); | |
1206 properties.add(new jsAst.Property(js.quoteName(key), value)); | |
1207 } | |
1208 jsAst.Expression mangledGlobalNamesAccess = | |
1209 generateEmbeddedGlobalAccess(embeddedNames.MANGLED_GLOBAL_NAMES); | |
1210 jsAst.ObjectInitializer map = new jsAst.ObjectInitializer(properties); | |
1211 parts.add(js.statement('# = #', [mangledGlobalNamesAccess, map])); | |
1212 } | |
1213 | |
1214 return new jsAst.Block(parts); | |
1215 } | |
1216 | |
1217 void checkEverythingEmitted(Iterable<Element> elements) { | |
1218 List<Element> pendingStatics; | |
1219 if (!compiler.hasIncrementalSupport) { | |
1220 pendingStatics = | |
1221 Elements.sortedByPosition(elements.where((e) => !e.isLibrary)); | |
1222 | |
1223 pendingStatics.forEach((element) => | |
1224 compiler.reportInfo( | |
1225 element, MessageKind.GENERIC, {'text': 'Pending statics.'})); | |
1226 } | |
1227 | |
1228 if (pendingStatics != null && !pendingStatics.isEmpty) { | |
1229 compiler.internalError(pendingStatics.first, | |
1230 'Pending statics (see above).'); | |
1231 } | |
1232 } | |
1233 | |
1234 void assembleLibrary(Library library, Fragment fragment) { | |
1235 LibraryElement libraryElement = library.element; | |
1236 | |
1237 assembleStaticFunctions(library.statics, fragment); | |
1238 | |
1239 ClassBuilder libraryBuilder = | |
1240 getElementDescriptor(libraryElement, fragment); | |
1241 for (Class cls in library.classes) { | |
1242 assembleClass(cls, libraryBuilder, fragment); | |
1243 } | |
1244 | |
1245 classEmitter.emitFields(library, libraryBuilder, emitStatics: true); | |
1246 } | |
1247 | |
1248 void assembleProgram(Program program) { | |
1249 for (Fragment fragment in program.fragments) { | |
1250 for (Library library in fragment.libraries) { | |
1251 assembleLibrary(library, fragment); | |
1252 } | |
1253 } | |
1254 assembleTypedefs(program); | |
1255 } | |
1256 | |
1257 jsAst.Program buildOutputAstForMain(Program program, | |
1258 Map<OutputUnit, _DeferredOutputUnitHash> deferredLoadHashes) { | |
1259 MainFragment mainFragment = program.mainFragment; | |
1260 OutputUnit mainOutputUnit = mainFragment.outputUnit; | |
1261 bool isProgramSplit = program.isSplit; | |
1262 | |
1263 List<jsAst.Statement> statements = <jsAst.Statement>[]; | |
1264 | |
1265 statements..add(buildGeneratedBy()) | |
1266 ..add(js.comment(HOOKS_API_USAGE)); | |
1267 | |
1268 if (isProgramSplit) { | |
1269 /// For deferred loading we communicate the initializers via this global | |
1270 /// variable. The deferred hunks will add their initialization to this. | |
1271 /// The semicolon is important in minified mode, without it the | |
1272 /// following parenthesis looks like a call to the object literal. | |
1273 statements.add( | |
1274 js.statement('self.#deferredInitializers = ' | |
1275 'self.#deferredInitializers || Object.create(null);', | |
1276 {'deferredInitializers': deferredInitializers})); | |
1277 } | |
1278 | |
1279 // Collect the AST for the decriptors | |
1280 Map<Element, ClassBuilder> descriptors = elementDescriptors[mainFragment]; | |
1281 if (descriptors == null) descriptors = const {}; | |
1282 | |
1283 checkEverythingEmitted(descriptors.keys); | |
1284 | |
1285 Iterable<LibraryElement> libraries = | |
1286 task.outputLibraryLists[mainOutputUnit]; | |
1287 if (libraries == null) libraries = <LibraryElement>[]; | |
1288 | |
1289 List<jsAst.Expression> parts = <jsAst.Expression>[]; | |
1290 for (LibraryElement library in Elements.sortedByPosition(libraries)) { | |
1291 parts.add(generateLibraryDescriptor(library, mainFragment)); | |
1292 descriptors.remove(library); | |
1293 } | |
1294 | |
1295 if (descriptors.isNotEmpty) { | |
1296 List<Element> remainingLibraries = descriptors.keys | |
1297 .where((Element e) => e is LibraryElement) | |
1298 .toList(); | |
1299 | |
1300 // The remaining descriptors are only accessible through reflection. | |
1301 // The program builder does not collect libraries that only | |
1302 // contain typedefs that are used for reflection. | |
1303 for (LibraryElement element in remainingLibraries) { | |
1304 assert(element is LibraryElement || compiler.hasIncrementalSupport); | |
1305 if (element is LibraryElement) { | |
1306 parts.add(generateLibraryDescriptor(element, mainFragment)); | |
1307 descriptors.remove(element); | |
1308 } | |
1309 } | |
1310 } | |
1311 jsAst.ArrayInitializer descriptorsAst = new jsAst.ArrayInitializer(parts); | |
1312 | |
1313 // Using a named function here produces easier to read stack traces in | |
1314 // Chrome/V8. | |
1315 statements.add(js.statement(""" | |
1316 (function() { | |
1317 // No renaming in the top-level function to save the locals for the | |
1318 // nested context where they will be used more. We have to put the | |
1319 // comment into a hole as the parser strips out comments right away. | |
1320 #disableVariableRenaming; | |
1321 #supportsDirectProtoAccess; | |
1322 | |
1323 if (#hasIncrementalSupport) { | |
1324 #helper = #helper || Object.create(null); | |
1325 #helper.patch = function(a) { eval(a)}; | |
1326 #helper.schemaChange = #schemaChange; | |
1327 #helper.addMethod = #addMethod; | |
1328 #helper.extractStubs = | |
1329 function(array, name, isStatic, originalDescriptor) { | |
1330 var descriptor = Object.create(null); | |
1331 this.addStubs(descriptor, array, name, isStatic, []); | |
1332 return descriptor; | |
1333 }; | |
1334 } | |
1335 | |
1336 if (#isProgramSplit) { | |
1337 /// We collect all the global state, so it can be passed to the | |
1338 /// initializer of deferred files. | |
1339 var #globalsHolder = Object.create(null) | |
1340 } | |
1341 | |
1342 // [map] returns an object that V8 shouldn't try to optimize with a | |
1343 // hidden class. This prevents a potential performance problem where V8 | |
1344 // tries to build a hidden class for an object used as a hashMap. | |
1345 // It requires fewer characters to declare a variable as a parameter than | |
1346 // with `var`. | |
1347 function map(x) { | |
1348 x = Object.create(null); | |
1349 x.x = 0; | |
1350 delete x.x; | |
1351 return x; | |
1352 } | |
1353 | |
1354 #globalObjectSetup; | |
1355 | |
1356 function #isolateName() {} | |
1357 | |
1358 if (#isProgramSplit) { | |
1359 #globalsHolder.#isolateName = #isolateName; | |
1360 #globalsHolder.#initName = #initName; | |
1361 #globalsHolder.#setupProgramName = #setupProgramName; | |
1362 } | |
1363 | |
1364 init(); | |
1365 | |
1366 #mangledNames; | |
1367 | |
1368 #cspPrecompiledFunctions; | |
1369 | |
1370 #setupProgram; | |
1371 | |
1372 #functionThatReturnsNull; | |
1373 | |
1374 // The argument to reflectionDataParser is assigned to a temporary 'dart' | |
1375 // so that 'dart.' will appear as the prefix to dart methods in stack | |
1376 // traces and profile entries. | |
1377 var dart = #descriptors; | |
1378 | |
1379 #setupProgramName(dart, 0); | |
1380 | |
1381 #getInterceptorMethods; | |
1382 #oneShotInterceptors; | |
1383 | |
1384 #makeConstantList; | |
1385 | |
1386 // We abuse the short name used for the isolate here to store | |
1387 // the isolate properties. This is safe as long as the real isolate | |
1388 // object does not exist yet. | |
1389 var ${namer.currentIsolate} = #isolatePropertiesName; | |
1390 | |
1391 // Constants in checked mode call into RTI code to set type information | |
1392 // which may need getInterceptor (and one-shot interceptor) methods, so | |
1393 // we have to make sure that [emitGetInterceptorMethods] and | |
1394 // [emitOneShotInterceptors] have been called. | |
1395 #compileTimeConstants; | |
1396 | |
1397 // Static field initializations require the classes and compile-time | |
1398 // constants to be set up. | |
1399 #staticNonFinalInitializers; | |
1400 | |
1401 ${namer.currentIsolate} = null; | |
1402 | |
1403 #deferredBoilerPlate; | |
1404 | |
1405 #typeToInterceptorMap; | |
1406 | |
1407 #lazyStaticFields; | |
1408 | |
1409 #isolateName = $finishIsolateConstructorName(#isolateName); | |
1410 | |
1411 ${namer.currentIsolate} = new #isolateName(); | |
1412 | |
1413 #metadata; | |
1414 | |
1415 #convertToFastObject; | |
1416 #convertToSlowObject; | |
1417 | |
1418 #convertGlobalObjectsToFastObjects; | |
1419 #debugFastObjects; | |
1420 | |
1421 #init; | |
1422 | |
1423 #main; | |
1424 })(); | |
1425 """, { | |
1426 "disableVariableRenaming": js.comment("/* ::norenaming:: */"), | |
1427 "hasIncrementalSupport": compiler.hasIncrementalSupport, | |
1428 "helper": js('this.#', [namer.incrementalHelperName]), | |
1429 "schemaChange": buildSchemaChangeFunction(), | |
1430 "addMethod": buildIncrementalAddMethod(), | |
1431 "isProgramSplit": isProgramSplit, | |
1432 "supportsDirectProtoAccess": buildSupportsDirectProtoAccess(), | |
1433 "globalsHolder": globalsHolder, | |
1434 "globalObjectSetup": buildGlobalObjectSetup(isProgramSplit), | |
1435 "isolateName": namer.isolateName, | |
1436 "isolatePropertiesName": js(isolatePropertiesName), | |
1437 "initName": initName, | |
1438 "functionThatReturnsNull": buildFunctionThatReturnsNull(), | |
1439 "mangledNames": buildMangledNames(), | |
1440 "setupProgram": buildSetupProgram(program, compiler, backend, namer, this)
, | |
1441 "setupProgramName": setupProgramName, | |
1442 "descriptors": descriptorsAst, | |
1443 "cspPrecompiledFunctions": buildCspPrecompiledFunctionFor(mainOutputUnit), | |
1444 "getInterceptorMethods": interceptorEmitter.buildGetInterceptorMethods(), | |
1445 "oneShotInterceptors": interceptorEmitter.buildOneShotInterceptors(), | |
1446 "makeConstantList": buildMakeConstantList(), | |
1447 "compileTimeConstants": buildCompileTimeConstants(mainFragment.constants, | |
1448 isMainFragment: true), | |
1449 "deferredBoilerPlate": buildDeferredBoilerPlate(deferredLoadHashes), | |
1450 "staticNonFinalInitializers": buildStaticNonFinalFieldInitializations( | |
1451 mainOutputUnit), | |
1452 "typeToInterceptorMap": | |
1453 interceptorEmitter.buildTypeToInterceptorMap(program), | |
1454 "lazyStaticFields": buildLazilyInitializedStaticFields(), | |
1455 "metadata": buildMetadata(program, mainOutputUnit), | |
1456 "convertToFastObject": buildConvertToFastObjectFunction(), | |
1457 "convertToSlowObject": buildConvertToSlowObjectFunction(), | |
1458 "convertGlobalObjectsToFastObjects": | |
1459 buildConvertGlobalObjectToFastObjects(), | |
1460 "debugFastObjects": buildDebugFastObjectCode(), | |
1461 "init": buildInitFunction(), | |
1462 "main": buildMain(mainFragment.invokeMain) | |
1463 })); | |
1464 | |
1465 return new jsAst.Program(statements); | |
1466 } | |
1467 | |
1468 void emitMainOutputUnit(OutputUnit mainOutputUnit, jsAst.Program program) { | |
1469 LineColumnCollector lineColumnCollector; | |
1470 List<CodeOutputListener> codeOutputListeners; | |
1471 if (generateSourceMap) { | |
1472 lineColumnCollector = new LineColumnCollector(); | |
1473 codeOutputListeners = <CodeOutputListener>[lineColumnCollector]; | |
1474 } | |
1475 | |
1476 CodeOutput mainOutput = | |
1477 new StreamCodeOutput(compiler.outputProvider('', 'js'), | |
1478 codeOutputListeners); | |
1479 outputBuffers[mainOutputUnit] = mainOutput; | |
1480 | |
1481 | |
1482 mainOutput.addBuffer(jsAst.prettyPrint(program, | |
1483 compiler, | |
1484 monitor: compiler.dumpInfoTask)); | |
1485 | |
1486 if (compiler.deferredMapUri != null) { | |
1487 outputDeferredMap(); | |
1488 } | |
1489 | |
1490 if (generateSourceMap) { | |
1491 mainOutput.add( | |
1492 generateSourceMapTag(compiler.sourceMapUri, compiler.outputUri)); | |
1493 } | |
1494 | |
1495 mainOutput.close(); | |
1496 | |
1497 if (generateSourceMap) { | |
1498 outputSourceMap(mainOutput, lineColumnCollector, '', | |
1499 compiler.sourceMapUri, compiler.outputUri); | |
1500 } | |
1501 } | |
1502 | |
1503 /// Used by incremental compilation to patch up the prototype of | |
1504 /// [oldConstructor] for use as prototype of [newConstructor]. | |
1505 jsAst.Fun buildSchemaChangeFunction() { | |
1506 if (!compiler.hasIncrementalSupport) return null; | |
1507 return js(''' | |
1508 function(newConstructor, oldConstructor, superclass) { | |
1509 // Invariant: newConstructor.prototype has no interesting properties besides | |
1510 // generated accessors. These are copied to oldPrototype which will be | |
1511 // updated by other incremental changes. | |
1512 if (superclass != null) { | |
1513 this.inheritFrom(newConstructor, superclass); | |
1514 } | |
1515 var oldPrototype = oldConstructor.prototype; | |
1516 var newPrototype = newConstructor.prototype; | |
1517 var hasOwnProperty = Object.prototype.hasOwnProperty; | |
1518 for (var property in newPrototype) { | |
1519 if (hasOwnProperty.call(newPrototype, property)) { | |
1520 // Copy generated accessors. | |
1521 oldPrototype[property] = newPrototype[property]; | |
1522 } | |
1523 } | |
1524 oldPrototype.__proto__ = newConstructor.prototype.__proto__; | |
1525 oldPrototype.constructor = newConstructor; | |
1526 newConstructor.prototype = oldPrototype; | |
1527 return newConstructor; | |
1528 }'''); | |
1529 } | |
1530 | |
1531 /// Used by incremental compilation to patch up an object ([holder]) with a | |
1532 /// new (or updated) method. [arrayOrFunction] is either the new method, or | |
1533 /// an array containing the method (see | |
1534 /// [ContainerBuilder.addMemberMethodFromInfo]). [name] is the name of the | |
1535 /// new method. [isStatic] tells if method is static (or | |
1536 /// top-level). [globalFunctionsAccess] is a reference to | |
1537 /// [embeddedNames.GLOBAL_FUNCTIONS]. | |
1538 jsAst.Fun buildIncrementalAddMethod() { | |
1539 if (!compiler.hasIncrementalSupport) return null; | |
1540 return js(r""" | |
1541 function(originalDescriptor, name, holder, isStatic, globalFunctionsAccess) { | |
1542 var arrayOrFunction = originalDescriptor[name]; | |
1543 var method; | |
1544 if (arrayOrFunction.constructor === Array) { | |
1545 var existing = holder[name]; | |
1546 var array = arrayOrFunction; | |
1547 | |
1548 // Each method may have a number of stubs associated. For example, if an | |
1549 // instance method supports multiple arguments, a stub for each matching | |
1550 // selector. There is also a getter stub for tear-off getters. For example, | |
1551 // an instance method foo([a]) may have the following stubs: foo$0, foo$1, | |
1552 // and get$foo (here exemplified using unminified names). | |
1553 // [extractStubs] returns a JavaScript object whose own properties | |
1554 // corresponds to the stubs. | |
1555 var descriptor = | |
1556 this.extractStubs(array, name, isStatic, originalDescriptor); | |
1557 method = descriptor[name]; | |
1558 | |
1559 // Iterate through the properties of descriptor and copy the stubs to the | |
1560 // existing holder (for instance methods, a prototype). | |
1561 for (var property in descriptor) { | |
1562 if (!Object.prototype.hasOwnProperty.call(descriptor, property)) continue; | |
1563 var stub = descriptor[property]; | |
1564 var existingStub = holder[property]; | |
1565 if (stub === method || !existingStub || !stub.$getterStub) { | |
1566 // Not replacing an existing getter stub. | |
1567 holder[property] = stub; | |
1568 continue; | |
1569 } | |
1570 if (!stub.$getterStub) { | |
1571 var error = new Error('Unexpected stub.'); | |
1572 error.stub = stub; | |
1573 throw error; | |
1574 } | |
1575 | |
1576 // Existing getter stubs need special treatment as they may already have | |
1577 // been called and produced a closure. | |
1578 this.pendingStubs = this.pendingStubs || []; | |
1579 // It isn't safe to invoke the stub yet. | |
1580 this.pendingStubs.push((function(holder, stub, existingStub, existing, | |
1581 method) { | |
1582 return function() { | |
1583 var receiver = isStatic ? holder : new holder.constructor(); | |
1584 // Invoke the existing stub to obtain the tear-off closure. | |
1585 existingStub = existingStub.call(receiver); | |
1586 // Invoke the new stub to create a tear-off closure we can use as a | |
1587 // prototype. | |
1588 stub = stub.call(receiver); | |
1589 | |
1590 // Copy the properties from the new tear-off's prototype to the | |
1591 // prototype of the existing tear-off. | |
1592 var newProto = stub.constructor.prototype; | |
1593 var existingProto = existingStub.constructor.prototype; | |
1594 for (var stubProperty in newProto) { | |
1595 if (!Object.prototype.hasOwnProperty.call(newProto, stubProperty)) | |
1596 continue; | |
1597 existingProto[stubProperty] = newProto[stubProperty]; | |
1598 } | |
1599 | |
1600 // Update all the existing stub's references to [existing] to | |
1601 // [method]. Instance tear-offs are call-by-name, so this isn't | |
1602 // necessary for those. | |
1603 if (!isStatic) return; | |
1604 for (var reference in existingStub) { | |
1605 if (existingStub[reference] === existing) { | |
1606 existingStub[reference] = method; | |
1607 } | |
1608 } | |
1609 } | |
1610 })(holder, stub, existingStub, existing, method)); | |
1611 } | |
1612 } else { | |
1613 method = arrayOrFunction; | |
1614 holder[name] = method; | |
1615 } | |
1616 if (isStatic) globalFunctionsAccess[name] = method; | |
1617 }"""); | |
1618 } | |
1619 | |
1620 Map<OutputUnit, jsAst.Expression> buildDescriptorsForOutputUnits( | |
1621 Program program) { | |
1622 Map<OutputUnit, jsAst.Expression> outputs = | |
1623 new Map<OutputUnit, jsAst.Expression>(); | |
1624 | |
1625 for (Fragment fragment in program.deferredFragments) { | |
1626 OutputUnit outputUnit = fragment.outputUnit; | |
1627 | |
1628 Map<Element, ClassBuilder> descriptors = elementDescriptors[fragment]; | |
1629 | |
1630 if (descriptors != null && descriptors.isNotEmpty) { | |
1631 Iterable<LibraryElement> libraries = | |
1632 task.outputLibraryLists[outputUnit]; | |
1633 if (libraries == null) libraries = []; | |
1634 | |
1635 // TODO(johnniwinther): Avoid creating [CodeBuffer]s. | |
1636 List<jsAst.Expression> parts = <jsAst.Expression>[]; | |
1637 for (LibraryElement library in Elements.sortedByPosition(libraries)) { | |
1638 parts.add(generateLibraryDescriptor(library, fragment)); | |
1639 descriptors.remove(library); | |
1640 } | |
1641 | |
1642 outputs[outputUnit] = new jsAst.ArrayInitializer(parts); | |
1643 } | |
1644 } | |
1645 | |
1646 return outputs; | |
1647 } | |
1648 | |
1649 void finalizeTokensInAst(jsAst.Program main, | |
1650 Iterable<jsAst.Program> deferredParts) { | |
1651 jsAst.TokenCounter counter = new jsAst.TokenCounter(); | |
1652 counter.countTokens(main); | |
1653 deferredParts.forEach(counter.countTokens); | |
1654 task.metadataCollector.finalizeTokens(); | |
1655 if (backend.namer is jsAst.TokenFinalizer) { | |
1656 var finalizer = backend.namer; | |
1657 finalizer.finalizeTokens(); | |
1658 } | |
1659 } | |
1660 | |
1661 int emitProgram(ProgramBuilder programBuilder) { | |
1662 Program program = programBuilder.buildProgram( | |
1663 storeFunctionTypesInMetadata: true); | |
1664 | |
1665 assembleProgram(program); | |
1666 | |
1667 // Construct the ASTs for all deferred output units. | |
1668 Map<OutputUnit, jsAst.Program> deferredParts = | |
1669 buildOutputAstForDeferredCode(program); | |
1670 | |
1671 Map<OutputUnit, _DeferredOutputUnitHash> deferredHashTokens = | |
1672 new Map<OutputUnit, _DeferredOutputUnitHash>.fromIterables( | |
1673 deferredParts.keys, | |
1674 deferredParts.keys.map((OutputUnit unit) { | |
1675 return new _DeferredOutputUnitHash(unit); | |
1676 }) | |
1677 ); | |
1678 | |
1679 jsAst.Program mainOutput = | |
1680 buildOutputAstForMain(program, deferredHashTokens); | |
1681 | |
1682 finalizeTokensInAst(mainOutput, deferredParts.values); | |
1683 | |
1684 // Emit deferred units first, so we have their hashes. | |
1685 // Map from OutputUnit to a hash of its content. The hash uniquely | |
1686 // identifies the code of the output-unit. It does not include | |
1687 // boilerplate JS code, like the sourcemap directives or the hash | |
1688 // itself. | |
1689 Map<OutputUnit, String> deferredLoadHashes = | |
1690 emitDeferredOutputUnits(deferredParts); | |
1691 | |
1692 deferredHashTokens.forEach((OutputUnit key, _DeferredOutputUnitHash token) { | |
1693 token.setHash(deferredLoadHashes[key]); | |
1694 }); | |
1695 emitMainOutputUnit(program.mainFragment.outputUnit, mainOutput); | |
1696 | |
1697 if (backend.requiresPreamble && | |
1698 !backend.htmlLibraryIsLoaded) { | |
1699 compiler.reportHint(NO_LOCATION_SPANNABLE, MessageKind.PREAMBLE); | |
1700 } | |
1701 // Return the total program size. | |
1702 return outputBuffers.values.fold(0, (a, b) => a + b.length); | |
1703 } | |
1704 | |
1705 String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) { | |
1706 if (sourceMapUri != null && fileUri != null) { | |
1707 String sourceMapFileName = relativize(fileUri, sourceMapUri, false); | |
1708 return ''' | |
1709 | |
1710 //# sourceMappingURL=$sourceMapFileName | |
1711 '''; | |
1712 } | |
1713 return ''; | |
1714 } | |
1715 | |
1716 ClassBuilder getElementDescriptor(Element element, Fragment fragment) { | |
1717 Element owner = element.library; | |
1718 bool isClass = false; | |
1719 if (!element.isLibrary && !element.isTopLevel && !element.isNative) { | |
1720 // For static (not top level) elements, record their code in a buffer | |
1721 // specific to the class. For now, not supported for native classes and | |
1722 // native elements. | |
1723 ClassElement cls = | |
1724 element.enclosingClassOrCompilationUnit.declaration; | |
1725 if (compiler.codegenWorld.directlyInstantiatedClasses.contains(cls) && | |
1726 !cls.isNative && | |
1727 compiler.deferredLoadTask.outputUnitForElement(element) == | |
1728 compiler.deferredLoadTask.outputUnitForElement(cls)) { | |
1729 owner = cls; | |
1730 } | |
1731 } | |
1732 if (owner == null) { | |
1733 compiler.internalError(element, 'Owner is null.'); | |
1734 } | |
1735 return elementDescriptors | |
1736 .putIfAbsent(fragment, () => new Map<Element, ClassBuilder>()) | |
1737 .putIfAbsent(owner, () { | |
1738 return new ClassBuilder(owner, namer, owner.isClass); | |
1739 }); | |
1740 } | |
1741 | |
1742 /// Emits support-code for deferred loading into [output]. | |
1743 jsAst.Statement buildDeferredBoilerPlate( | |
1744 Map<OutputUnit, _DeferredOutputUnitHash> deferredLoadHashes) { | |
1745 List<jsAst.Statement> parts = <jsAst.Statement>[]; | |
1746 | |
1747 parts.add(js.statement(''' | |
1748 { | |
1749 // Function for checking if a hunk is loaded given its hash. | |
1750 #isHunkLoaded = function(hunkHash) { | |
1751 return !!$deferredInitializers[hunkHash]; | |
1752 }; | |
1753 #deferredInitialized = new Object(null); | |
1754 // Function for checking if a hunk is initialized given its hash. | |
1755 #isHunkInitialized = function(hunkHash) { | |
1756 return #deferredInitialized[hunkHash]; | |
1757 }; | |
1758 // Function for initializing a loaded hunk, given its hash. | |
1759 #initializeLoadedHunk = function(hunkHash) { | |
1760 $deferredInitializers[hunkHash]( | |
1761 #globalsHolder, ${namer.currentIsolate}); | |
1762 #deferredInitialized[hunkHash] = true; | |
1763 }; | |
1764 } | |
1765 ''', {"globalsHolder": globalsHolder, | |
1766 "isHunkLoaded": generateEmbeddedGlobalAccess( | |
1767 embeddedNames.IS_HUNK_LOADED), | |
1768 "isHunkInitialized": generateEmbeddedGlobalAccess( | |
1769 embeddedNames.IS_HUNK_INITIALIZED), | |
1770 "initializeLoadedHunk": generateEmbeddedGlobalAccess( | |
1771 embeddedNames.INITIALIZE_LOADED_HUNK), | |
1772 "deferredInitialized": generateEmbeddedGlobalAccess( | |
1773 embeddedNames.DEFERRED_INITIALIZED)})); | |
1774 | |
1775 // Write a javascript mapping from Deferred import load ids (derrived | |
1776 // from the import prefix.) to a list of lists of uris of hunks to load, | |
1777 // and a corresponding mapping to a list of hashes used by | |
1778 // INITIALIZE_LOADED_HUNK and IS_HUNK_LOADED. | |
1779 Map<String, List<jsAst.LiteralString>> deferredLibraryUris = | |
1780 new Map<String, List<jsAst.LiteralString>>(); | |
1781 Map<String, List<_DeferredOutputUnitHash>> deferredLibraryHashes = | |
1782 new Map<String, List<_DeferredOutputUnitHash>>(); | |
1783 compiler.deferredLoadTask.hunksToLoad.forEach( | |
1784 (String loadId, List<OutputUnit>outputUnits) { | |
1785 List<jsAst.LiteralString> uris = new List<jsAst.LiteralString>(); | |
1786 List<_DeferredOutputUnitHash> hashes = | |
1787 new List<_DeferredOutputUnitHash>(); | |
1788 deferredLibraryHashes[loadId] = new List<_DeferredOutputUnitHash>(); | |
1789 for (OutputUnit outputUnit in outputUnits) { | |
1790 uris.add(js.escapedString( | |
1791 backend.deferredPartFileName(outputUnit.name))); | |
1792 hashes.add(deferredLoadHashes[outputUnit]); | |
1793 } | |
1794 | |
1795 deferredLibraryUris[loadId] = uris; | |
1796 deferredLibraryHashes[loadId] = hashes; | |
1797 }); | |
1798 | |
1799 void emitMapping(String name, Map<String, List<jsAst.Expression>> mapping) { | |
1800 List<jsAst.Property> properties = new List<jsAst.Property>(); | |
1801 mapping.forEach((String key, List<jsAst.Expression> values) { | |
1802 properties.add(new jsAst.Property(js.escapedString(key), | |
1803 new jsAst.ArrayInitializer(values))); | |
1804 }); | |
1805 jsAst.Node initializer = | |
1806 new jsAst.ObjectInitializer(properties, isOneLiner: true); | |
1807 | |
1808 jsAst.Node globalName = generateEmbeddedGlobalAccess(name); | |
1809 parts.add(js.statement("# = #", [globalName, initializer])); | |
1810 } | |
1811 | |
1812 emitMapping(embeddedNames.DEFERRED_LIBRARY_URIS, deferredLibraryUris); | |
1813 emitMapping(embeddedNames.DEFERRED_LIBRARY_HASHES, | |
1814 deferredLibraryHashes); | |
1815 | |
1816 return new jsAst.Block(parts); | |
1817 } | |
1818 | |
1819 Map <OutputUnit, jsAst.Program> buildOutputAstForDeferredCode( | |
1820 Program program) { | |
1821 if (!program.isSplit) return const <OutputUnit, jsAst.Program>{}; | |
1822 | |
1823 Map<OutputUnit, jsAst.Program> result = | |
1824 new Map<OutputUnit, jsAst.Program>(); | |
1825 | |
1826 Map<OutputUnit, jsAst.Expression> deferredAsts = | |
1827 buildDescriptorsForOutputUnits(program); | |
1828 | |
1829 for (Fragment fragment in program.deferredFragments) { | |
1830 OutputUnit outputUnit = fragment.outputUnit; | |
1831 jsAst.Expression libraryDescriptor = deferredAsts[outputUnit]; | |
1832 List<jsAst.Statement> body = <jsAst.Statement>[]; | |
1833 | |
1834 // No renaming in the top-level function to save the locals for the | |
1835 // nested context where they will be used more. | |
1836 body.add(js.comment("/* ::norenaming:: ")); | |
1837 | |
1838 for (String globalObject in Namer.reservedGlobalObjectNames) { | |
1839 body.add(js.statement('var #object = #globalsHolder.#object;', | |
1840 {'globalsHolder': globalsHolder, | |
1841 'object': globalObject})); | |
1842 } | |
1843 body..add(js.statement('var init = #globalsHolder.init;', | |
1844 {'globalsHolder': globalsHolder})) | |
1845 ..add(js.statement('var $setupProgramName = ' | |
1846 '#globalsHolder.$setupProgramName;', | |
1847 {'globalsHolder': globalsHolder})) | |
1848 ..add(js.statement('var ${namer.isolateName} = ' | |
1849 '#globalsHolder.${namer.isolateName};', | |
1850 {'globalsHolder': globalsHolder})); | |
1851 String typesAccess = | |
1852 generateEmbeddedGlobalAccessString(embeddedNames.TYPES); | |
1853 if (libraryDescriptor != null) { | |
1854 // The argument to reflectionDataParser is assigned to a temporary | |
1855 // 'dart' so that 'dart.' will appear as the prefix to dart methods | |
1856 // in stack traces and profile entries. | |
1857 body.add(js.statement('var dart = #', libraryDescriptor)); | |
1858 | |
1859 if (compiler.useContentSecurityPolicy) { | |
1860 body.add(buildCspPrecompiledFunctionFor(outputUnit)); | |
1861 } | |
1862 body.add( | |
1863 js.statement('$setupProgramName(dart, ${typesAccess}.length);')); | |
1864 } | |
1865 | |
1866 body..add(buildMetadata(program, outputUnit)) | |
1867 ..add(js.statement('${typesAccess}.push.apply(${typesAccess}, ' | |
1868 '${namer.deferredTypesName});')); | |
1869 | |
1870 // Set the currentIsolate variable to the current isolate (which is | |
1871 // provided as second argument). | |
1872 body.add(js.statement("${namer.currentIsolate} = arguments[1];")); | |
1873 | |
1874 body.add(buildCompileTimeConstants(fragment.constants, | |
1875 isMainFragment: false)); | |
1876 body.add(buildStaticNonFinalFieldInitializations(outputUnit)); | |
1877 | |
1878 List<jsAst.Statement> statements = <jsAst.Statement>[]; | |
1879 | |
1880 statements | |
1881 ..add(buildGeneratedBy()) | |
1882 ..add(js.statement('${deferredInitializers}.current = ' | |
1883 """function (#) { | |
1884 # | |
1885 } | |
1886 """, [globalsHolder, body])); | |
1887 | |
1888 result[outputUnit] = new jsAst.Program(statements); | |
1889 } | |
1890 | |
1891 return result; | |
1892 } | |
1893 | |
1894 /// Returns a map from OutputUnit to a hash of its content. The hash uniquely | |
1895 /// identifies the code of the output-unit. It does not include | |
1896 /// boilerplate JS code, like the sourcemap directives or the hash | |
1897 /// itself. | |
1898 Map<OutputUnit, String> emitDeferredOutputUnits( | |
1899 Map<OutputUnit, jsAst.Program> outputAsts) { | |
1900 | |
1901 Map<OutputUnit, String> hunkHashes = new Map<OutputUnit, String>(); | |
1902 | |
1903 for (OutputUnit outputUnit in outputAsts.keys) { | |
1904 List<CodeOutputListener> outputListeners = <CodeOutputListener>[]; | |
1905 Hasher hasher = new Hasher(); | |
1906 outputListeners.add(hasher); | |
1907 | |
1908 LineColumnCollector lineColumnCollector; | |
1909 if (generateSourceMap) { | |
1910 lineColumnCollector = new LineColumnCollector(); | |
1911 outputListeners.add(lineColumnCollector); | |
1912 } | |
1913 | |
1914 String partPrefix = | |
1915 backend.deferredPartFileName(outputUnit.name, addExtension: false); | |
1916 CodeOutput output = new StreamCodeOutput( | |
1917 compiler.outputProvider(partPrefix, 'part.js'), | |
1918 outputListeners); | |
1919 | |
1920 outputBuffers[outputUnit] = output; | |
1921 | |
1922 output.addBuffer(jsAst.prettyPrint(outputAsts[outputUnit], | |
1923 compiler, | |
1924 monitor: compiler.dumpInfoTask)); | |
1925 | |
1926 // Make a unique hash of the code (before the sourcemaps are added) | |
1927 // This will be used to retrieve the initializing function from the global | |
1928 // variable. | |
1929 String hash = hasher.getHash(); | |
1930 | |
1931 output.add('$N${deferredInitializers}["$hash"]$_=$_' | |
1932 '${deferredInitializers}.current$N'); | |
1933 | |
1934 if (generateSourceMap) { | |
1935 Uri mapUri, partUri; | |
1936 Uri sourceMapUri = compiler.sourceMapUri; | |
1937 Uri outputUri = compiler.outputUri; | |
1938 | |
1939 String partName = "$partPrefix.part"; | |
1940 | |
1941 if (sourceMapUri != null) { | |
1942 String mapFileName = partName + ".js.map"; | |
1943 List<String> mapSegments = sourceMapUri.pathSegments.toList(); | |
1944 mapSegments[mapSegments.length - 1] = mapFileName; | |
1945 mapUri = compiler.sourceMapUri.replace(pathSegments: mapSegments); | |
1946 } | |
1947 | |
1948 if (outputUri != null) { | |
1949 String partFileName = partName + ".js"; | |
1950 List<String> partSegments = outputUri.pathSegments.toList(); | |
1951 partSegments[partSegments.length - 1] = partFileName; | |
1952 partUri = compiler.outputUri.replace(pathSegments: partSegments); | |
1953 } | |
1954 | |
1955 output.add(generateSourceMapTag(mapUri, partUri)); | |
1956 output.close(); | |
1957 outputSourceMap(output, lineColumnCollector, partName, | |
1958 mapUri, partUri); | |
1959 } else { | |
1960 output.close(); | |
1961 } | |
1962 | |
1963 hunkHashes[outputUnit] = hash; | |
1964 } | |
1965 return hunkHashes; | |
1966 } | |
1967 | |
1968 jsAst.Comment buildGeneratedBy() { | |
1969 String suffix = ''; | |
1970 if (compiler.hasBuildId) suffix = ' version: ${compiler.buildId}'; | |
1971 String msg = '// Generated by dart2js, the Dart to JavaScript ' | |
1972 'compiler$suffix.'; | |
1973 return new jsAst.Comment(msg); | |
1974 } | |
1975 | |
1976 void outputSourceMap(CodeOutput output, | |
1977 LineColumnProvider lineColumnProvider, | |
1978 String name, | |
1979 [Uri sourceMapUri, | |
1980 Uri fileUri]) { | |
1981 if (!generateSourceMap) return; | |
1982 // Create a source file for the compilation output. This allows using | |
1983 // [:getLine:] to transform offsets to line numbers in [SourceMapBuilder]. | |
1984 SourceMapBuilder sourceMapBuilder = | |
1985 new SourceMapBuilder(sourceMapUri, fileUri, lineColumnProvider); | |
1986 output.forEachSourceLocation(sourceMapBuilder.addMapping); | |
1987 String sourceMap = sourceMapBuilder.build(); | |
1988 compiler.outputProvider(name, 'js.map') | |
1989 ..add(sourceMap) | |
1990 ..close(); | |
1991 } | |
1992 | |
1993 void outputDeferredMap() { | |
1994 Map<String, dynamic> mapping = new Map<String, dynamic>(); | |
1995 // Json does not support comments, so we embed the explanation in the | |
1996 // data. | |
1997 mapping["_comment"] = "This mapping shows which compiled `.js` files are " | |
1998 "needed for a given deferred library import."; | |
1999 mapping.addAll(compiler.deferredLoadTask.computeDeferredMap()); | |
2000 compiler.outputProvider(compiler.deferredMapUri.path, 'deferred_map') | |
2001 ..add(const JsonEncoder.withIndent(" ").convert(mapping)) | |
2002 ..close(); | |
2003 } | |
2004 | |
2005 void invalidateCaches() { | |
2006 if (!compiler.hasIncrementalSupport) return; | |
2007 if (cachedElements.isEmpty) return; | |
2008 for (Element element in compiler.enqueuer.codegen.newlyEnqueuedElements) { | |
2009 if (element.isInstanceMember) { | |
2010 cachedClassBuilders.remove(element.enclosingClass); | |
2011 | |
2012 nativeEmitter.cachedBuilders.remove(element.enclosingClass); | |
2013 | |
2014 } | |
2015 } | |
2016 } | |
2017 } | |
OLD | NEW |