Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(266)

Side by Side Diff: lib/compiler/implementation/emitter.dart

Issue 10854066: Start moving the JavaScript backend related code into a separate library (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Add missing import Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012, 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 /**
6 * A function element that represents a closure call. The signature is copied
7 * from the given element.
8 */
9 class ClosureInvocationElement extends FunctionElement {
10 ClosureInvocationElement(SourceString name,
11 FunctionElement other)
12 : super.from(name, other, other.enclosingElement);
13
14 isInstanceMember() => true;
15 }
16
17 /**
18 * Generates the code for all used classes in the program. Static fields (even
19 * in classes) are ignored, since they can be treated as non-class elements.
20 *
21 * The code for the containing (used) methods must exist in the [:universe:].
22 */
23 class CodeEmitterTask extends CompilerTask {
24 bool needsInheritFunction = false;
25 bool needsDefineClass = false;
26 bool needsClosureClass = false;
27 final Namer namer;
28 NativeEmitter nativeEmitter;
29 CodeBuffer boundClosureBuffer;
30 CodeBuffer mainBuffer;
31 /** Shorter access to [isolatePropertiesName]. Both here in the code, as
32 well as in the generated code. */
33 String isolateProperties;
34 String classesCollector;
35 final Map<int, String> boundClosureCache;
36
37 final bool generateSourceMap;
38 final SourceMapBuilder sourceMapBuilder;
39
40 CodeEmitterTask(Compiler compiler, [bool generateSourceMap = false])
41 : namer = compiler.namer,
42 boundClosureBuffer = new CodeBuffer(),
43 mainBuffer = new CodeBuffer(),
44 boundClosureCache = new Map<int, String>(),
45 generateSourceMap = generateSourceMap,
46 sourceMapBuilder = new SourceMapBuilder(),
47 super(compiler) {
48 nativeEmitter = new NativeEmitter(this);
49 }
50
51 String get name() => 'CodeEmitter';
52
53 String get defineClassName()
54 => '${namer.ISOLATE}.\$defineClass';
55 String get finishClassesName()
56 => '${namer.ISOLATE}.\$finishClasses';
57 String get finishIsolateConstructorName()
58 => '${namer.ISOLATE}.\$finishIsolateConstructor';
59 String get pendingClassesName()
60 => '${namer.ISOLATE}.\$pendingClasses';
61 String get isolatePropertiesName()
62 => '${namer.ISOLATE}.${namer.ISOLATE_PROPERTIES}';
63 String get supportsProtoName()
64 => 'supportsProto';
65
66 final String GETTER_SUFFIX = "?";
67 final String SETTER_SUFFIX = "!";
68 final String GETTER_SETTER_SUFFIX = "=";
69
70 String get generateGetterSetterFunction() {
71 return """
72 function(field, prototype) {
73 var len = field.length;
74 var lastChar = field[len - 1];
75 var needsGetter = lastChar == '$GETTER_SUFFIX' || lastChar == '$GETTER_SETTER_ SUFFIX';
76 var needsSetter = lastChar == '$SETTER_SUFFIX' || lastChar == '$GETTER_SETTER_ SUFFIX';
77 if (needsGetter || needsSetter) field = field.substring(0, len - 1);
78 if (needsGetter) {
79 var getterString = "return this." + field + ";";
80 """ /* The supportsProtoCheck below depends on the getter/setter convention.
81 When changing here, update the protoCheck too. */ """
82 prototype["get\$" + field] = new Function(getterString);
83 }
84 if (needsSetter) {
85 var setterString = "this." + field + " = v;";
86 prototype["set\$" + field] = new Function("v", setterString);
87 }
88 return field;
89 }""";
90 }
91
92 String get defineClassFunction() {
93 // First the class name, then the super class name, followed by the fields
94 // (in an array) and the members (inside an Object literal).
95 // The caller can also pass in the constructor as a function if needed.
96 //
97 // Example:
98 // defineClass("A", "B", ["x", "y"], {
99 // foo$1: function(y) {
100 // print(this.x + y);
101 // },
102 // bar$2: function(t, v) {
103 // this.x = t - v;
104 // },
105 // });
106 return """
107 function(cls, fields, prototype) {
108 var generateGetterSetter = $generateGetterSetterFunction;
109 var constructor;
110 if (typeof fields == 'function') {
111 constructor = fields;
112 } else {
113 var str = "function " + cls + "(";
114 var body = "";
115 for (var i = 0; i < fields.length; i++) {
116 if (i != 0) str += ", ";
117 var field = fields[i];
118 field = generateGetterSetter(field, prototype);
119 str += field;
120 body += "this." + field + " = " + field + ";\\n";
121 }
122 str += ") {" + body + "}\\n";
123 str += "return " + cls + ";";
124 constructor = new Function(str)();
125 }
126 constructor.prototype = prototype;
127 return constructor;
128 }""";
129 }
130
131 /** Needs defineClass to be defined. */
132 String get protoSupportCheck() {
133 // On Firefox and Webkit browsers we can manipulate the __proto__
134 // directly. Opera claims to have __proto__ support, but it is buggy.
135 // So we have to do more checks.
136 // If the browser does not support __proto__ we need to instantiate an
137 // object with the correct (internal) prototype set up correctly, and then
138 // copy the members.
139
140 return '''
141 var $supportsProtoName = false;
142 var tmp = $defineClassName('c', ['f?'], {}).prototype;
143 if (tmp.__proto__) {
144 tmp.__proto__ = {};
145 if (typeof tmp.get\$f !== "undefined") $supportsProtoName = true;
146 }
147 ''';
148 }
149
150 String get finishClassesFunction() {
151 // 'defineClass' does not require the classes to be constructed in order.
152 // Classes are initially just stored in the 'pendingClasses' field.
153 // 'finishClasses' takes all pending classes and sets up the prototype.
154 // Once set up, the constructors prototype field satisfy:
155 // - it contains all (local) members.
156 // - its internal prototype (__proto__) points to the superclass'
157 // prototype field.
158 // - the prototype's constructor field points to the JavaScript
159 // constructor.
160 // For engines where we have access to the '__proto__' we can manipulate
161 // the object literal directly. For other engines we have to create a new
162 // object and copy over the members.
163 return '''
164 function(collectedClasses) {
165 for (var cls in collectedClasses) {
166 if (Object.prototype.hasOwnProperty.call(collectedClasses, cls)) {
167 var desc = collectedClasses[cls];
168 $isolatePropertiesName[cls] = $defineClassName(cls, desc[''], desc);
169 if (desc['super'] !== "") $pendingClassesName[cls] = desc['super'];
170 }
171 }
172 var pendingClasses = $pendingClassesName;
173 '''/* FinishClasses can be called multiple times. This means that we need to
174 clear the pendingClasses property. */'''
175 $pendingClassesName = {};
176 var finishedClasses = {};
177 function finishClass(cls) {
178 if (finishedClasses[cls]) return;
179 finishedClasses[cls] = true;
180 var superclass = pendingClasses[cls];
181 '''/* The superclass is only false (empty string) for Dart's Object class. */'''
182 if (!superclass) return;
183 finishClass(superclass);
184 var constructor = $isolatePropertiesName[cls];
185 var superConstructor = $isolatePropertiesName[superclass];
186 var prototype = constructor.prototype;
187 if ($supportsProtoName) {
188 prototype.__proto__ = superConstructor.prototype;
189 prototype.constructor = constructor;
190 } else {
191 function tmp() {};
192 tmp.prototype = superConstructor.prototype;
193 var newPrototype = new tmp();
194 constructor.prototype = newPrototype;
195 newPrototype.constructor = constructor;
196 '''/* Opera does not support 'getOwnPropertyNames'. Therefore we use
197 hosOwnProperty instead. */'''
198 var hasOwnProperty = Object.prototype.hasOwnProperty;
199 for (var member in prototype) {
200 if (member == '' || member == 'super') continue;
201 if (hasOwnProperty.call(prototype, member)) {
202 newPrototype[member] = prototype[member];
203 }
204 }
205 }
206 }
207 for (var cls in pendingClasses) finishClass(cls);
208 }''';
209 }
210
211 String get finishIsolateConstructorFunction() {
212 String isolate = namer.ISOLATE;
213 // We replace the old Isolate function with a new one that initializes
214 // all its field with the initial (and often final) value of all globals.
215 // This has two advantages:
216 // 1. the properties are in the object itself (thus avoiding to go through
217 // the prototype when looking up globals.
218 // 2. a new isolate goes through a (usually well optimized) constructor
219 // function of the form: "function() { this.x = ...; this.y = ...; }".
220 //
221 // Example: If [isolateProperties] is an object containing: x = 3 and
222 // A = function A() { /* constructor of class A. */ }, then we generate:
223 // str = "{
224 // var isolateProperties = Isolate.$isolateProperties;
225 // this.x = isolateProperties.x;
226 // this.A = isolateProperties.A;
227 // }";
228 // which is then dynamically evaluated:
229 // var newIsolate = new Function(str);
230 //
231 // We also copy over old values like the prototype, and the
232 // isolateProperties themselves.
233 return """function(oldIsolate) {
234 var isolateProperties = oldIsolate.${namer.ISOLATE_PROPERTIES};
235 var isolatePrototype = oldIsolate.prototype;
236 var str = "{\\n";
237 str += "var properties = $isolate.${namer.ISOLATE_PROPERTIES};\\n";
238 for (var staticName in isolateProperties) {
239 if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) {
240 str += "this." + staticName + "= properties." + staticName + ";\\n";
241 }
242 }
243 str += "}\\n";
244 var newIsolate = new Function(str);
245 newIsolate.prototype = isolatePrototype;
246 isolatePrototype.constructor = newIsolate;
247 newIsolate.${namer.ISOLATE_PROPERTIES} = isolateProperties;
248 return newIsolate;
249 }""";
250 }
251
252 void addDefineClassAndFinishClassFunctionsIfNecessary(CodeBuffer buffer) {
253 if (needsDefineClass) {
254 String isolate = namer.ISOLATE;
255 buffer.add("$defineClassName = $defineClassFunction;\n");
256 buffer.add(protoSupportCheck);
257 buffer.add("$pendingClassesName = {};\n");
258 buffer.add("$finishClassesName = $finishClassesFunction;\n");
259 }
260 }
261
262 void emitFinishIsolateConstructor(CodeBuffer buffer) {
263 String name = finishIsolateConstructorName;
264 String value = finishIsolateConstructorFunction;
265 buffer.add("$name = $value;\n");
266 }
267
268 void emitFinishIsolateConstructorInvocation(CodeBuffer buffer) {
269 String isolate = namer.ISOLATE;
270 buffer.add("$isolate = $finishIsolateConstructorName($isolate);\n");
271 }
272
273 void addParameterStub(FunctionElement member,
274 Selector selector,
275 DefineMemberFunction defineInstanceMember) {
276 FunctionSignature parameters = member.computeSignature(compiler);
277 int positionalArgumentCount = selector.positionalArgumentCount;
278 if (positionalArgumentCount == parameters.parameterCount) {
279 assert(selector.namedArgumentCount == 0);
280 return;
281 }
282 ConstantHandler handler = compiler.constantHandler;
283 List<SourceString> names = selector.getOrderedNamedArguments();
284
285 String invocationName =
286 namer.instanceMethodInvocationName(member.getLibrary(), member.name,
287 selector);
288 CodeBuffer buffer = new CodeBuffer();
289 buffer.add('function(');
290
291 // The parameters that this stub takes.
292 List<String> parametersBuffer = new List<String>(selector.argumentCount);
293 // The arguments that will be passed to the real method.
294 List<String> argumentsBuffer = new List<String>(parameters.parameterCount);
295
296 // We fill the lists depending on the selector. For example,
297 // take method foo:
298 // foo(a, b, [c, d]);
299 //
300 // We may have multiple ways of calling foo:
301 // (1) foo(1, 2, 3, 4)
302 // (2) foo(1, 2);
303 // (3) foo(1, 2, 3);
304 // (4) foo(1, 2, c: 3);
305 // (5) foo(1, 2, d: 4);
306 // (6) foo(1, 2, c: 3, d: 4);
307 // (7) foo(1, 2, d: 4, c: 3);
308 //
309 // What we generate at the call sites are:
310 // (1) foo$4(1, 2, 3, 4)
311 // (2) foo$2(1, 2);
312 // (3) foo$3(1, 2, 3);
313 // (4) foo$3$c(1, 2, 3);
314 // (5) foo$3$d(1, 2, 4);
315 // (6) foo$4$c$d(1, 2, 3, 4);
316 // (7) foo$4$c$d(1, 2, 3, 4);
317 //
318 // The stubs we generate are (expressed in Dart):
319 // (1) No stub generated, call is direct.
320 // (2) foo$2(a, b) => foo$4(a, b, null, null)
321 // (3) foo$3(a, b, c) => foo$4(a, b, c, null)
322 // (4) foo$3$c(a, b, c) => foo$4(a, b, c, null);
323 // (5) foo$3$d(a, b, d) => foo$4(a, b, null, d);
324 // (6) foo$4$c$d(a, b, c, d) => foo$4(a, b, c, d);
325 // (7) Same as (5).
326 //
327 // We need to generate a stub for (5) because the order of the
328 // stub arguments and the real method may be different.
329
330 int count = 0;
331 int indexOfLastOptionalArgumentInParameters = positionalArgumentCount - 1;
332 parameters.forEachParameter((Element element) {
333 String jsName = JsNames.getValid(element.name.slowToString());
334 if (count < positionalArgumentCount) {
335 parametersBuffer[count] = jsName;
336 argumentsBuffer[count] = jsName;
337 } else {
338 int index = names.indexOf(element.name);
339 if (index != -1) {
340 indexOfLastOptionalArgumentInParameters = count;
341 // The order of the named arguments is not the same as the
342 // one in the real method (which is in Dart source order).
343 argumentsBuffer[count] = jsName;
344 parametersBuffer[selector.positionalArgumentCount + index] = jsName;
345 } else {
346 Constant value = handler.initialVariableValues[element];
347 if (value == null) {
348 argumentsBuffer[count] = NullConstant.JsNull;
349 } else {
350 if (!value.isNull()) {
351 // If the value is the null constant, we should not pass it
352 // down to the native method.
353 indexOfLastOptionalArgumentInParameters = count;
354 }
355 CodeBuffer argumentBuffer = new CodeBuffer();
356 handler.writeConstant(argumentBuffer, value);
357 argumentsBuffer[count] = argumentBuffer.toString();
358 }
359 }
360 }
361 count++;
362 });
363 String parametersString = Strings.join(parametersBuffer, ",");
364 buffer.add('$parametersString) {\n');
365
366 if (member.isNative()) {
367 nativeEmitter.generateParameterStub(
368 member, invocationName, parametersString, argumentsBuffer,
369 indexOfLastOptionalArgumentInParameters, buffer);
370 } else {
371 String arguments = Strings.join(argumentsBuffer, ",");
372 buffer.add(' return this.${namer.getName(member)}($arguments)');
373 }
374 buffer.add('\n}');
375 defineInstanceMember(invocationName, buffer);
376 }
377
378 void addParameterStubs(FunctionElement member,
379 DefineMemberFunction defineInstanceMember) {
380 Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
381 if (selectors == null) return;
382 for (Selector selector in selectors) {
383 if (!selector.applies(member, compiler)) continue;
384 addParameterStub(member, selector, defineInstanceMember);
385 }
386 }
387
388 bool instanceFieldNeedsGetter(Element member) {
389 assert(member.kind === ElementKind.FIELD);
390 return compiler.codegenWorld.hasInvokedGetter(member, compiler);
391 }
392
393 bool instanceFieldNeedsSetter(Element member) {
394 assert(member.kind === ElementKind.FIELD);
395 return (member.modifiers === null || !member.modifiers.isFinal())
396 && compiler.codegenWorld.hasInvokedSetter(member, compiler);
397 }
398
399 String compiledFieldName(Element member) {
400 assert(member.kind === ElementKind.FIELD);
401 return member.isNative()
402 ? member.name.slowToString()
403 : namer.getName(member);
404 }
405
406 void addInstanceMember(Element member,
407 DefineMemberFunction defineInstanceMember) {
408 // TODO(floitsch): we don't need to deal with members of
409 // uninstantiated classes, that have been overwritten by subclasses.
410
411 if (member.kind === ElementKind.FUNCTION
412 || member.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY
413 || member.kind === ElementKind.GETTER
414 || member.kind === ElementKind.SETTER) {
415 if (member.modifiers !== null && member.modifiers.isAbstract()) return;
416 CodeBuffer codeBuffer = compiler.codegenWorld.generatedCode[member];
417 if (codeBuffer == null) return;
418 defineInstanceMember(namer.getName(member), codeBuffer);
419 codeBuffer = compiler.codegenWorld.generatedBailoutCode[member];
420 if (codeBuffer !== null) {
421 defineInstanceMember(compiler.namer.getBailoutName(member), codeBuffer);
422 }
423 FunctionElement function = member;
424 FunctionSignature parameters = function.computeSignature(compiler);
425 if (!parameters.optionalParameters.isEmpty()) {
426 addParameterStubs(member, defineInstanceMember);
427 }
428 } else if (member.kind !== ElementKind.FIELD) {
429 compiler.internalError('unexpected kind: "${member.kind}"',
430 element: member);
431 }
432 emitExtraAccessors(member, defineInstanceMember);
433 }
434
435 Set<Element> emitClassFields(ClassElement classElement, CodeBuffer buffer) {
436 // If the class is never instantiated we still need to set it up for
437 // inheritance purposes, but we can simplify its JavaScript constructor.
438 bool isInstantiated =
439 compiler.codegenWorld.instantiatedClasses.contains(classElement);
440
441 bool isFirstField = true;
442 void addField(ClassElement enclosingClass, Element member) {
443 assert(!member.isNative());
444
445 LibraryElement library = member.getLibrary();
446 SourceString name = member.name;
447 bool isPrivate = name.isPrivate();
448 // See if we can dynamically create getters and setters.
449 // We can only generate getters and setters for [classElement] since
450 // the fields of super classes could be overwritten with getters or
451 // setters.
452 bool needsDynamicGetter = false;
453 bool needsDynamicSetter = false;
454 // We need to name shadowed fields differently, so they don't clash with
455 // the non-shadowed field.
456 bool isShadowed = false;
457 if (enclosingClass === classElement) {
458 needsDynamicGetter = instanceFieldNeedsGetter(member);
459 needsDynamicSetter = instanceFieldNeedsSetter(member);
460 } else {
461 isShadowed = classElement.isShadowedByField(member);
462 }
463
464 if ((isInstantiated && !enclosingClass.isNative())
465 || needsDynamicGetter
466 || needsDynamicSetter) {
467 if (isFirstField) {
468 isFirstField = false;
469 } else {
470 buffer.add(", ");
471 }
472 String fieldName = isShadowed
473 ? namer.shadowedFieldName(member)
474 : namer.instanceFieldName(library, name);
475 // Getters and setters with suffixes will be generated dynamically.
476 buffer.add('"$fieldName');
477 if (needsDynamicGetter || needsDynamicSetter) {
478 if (needsDynamicGetter && needsDynamicSetter) {
479 buffer.add(GETTER_SETTER_SUFFIX);
480 } else if (needsDynamicGetter) {
481 buffer.add(GETTER_SUFFIX);
482 } else {
483 buffer.add(SETTER_SUFFIX);
484 }
485 }
486 buffer.add('"');
487 }
488 }
489
490 // If a class is not instantiated then we add the field just so we can
491 // generate the field getter/setter dynamically. Since this is only
492 // allowed on fields that are in [classElement] we don't need to visit
493 // superclasses for non-instantiated classes.
494 classElement.forEachInstanceField(
495 addField,
496 includeBackendMembers: true,
497 includeSuperMembers: isInstantiated && !classElement.isNative());
498 }
499
500 void emitInstanceMembers(ClassElement classElement,
501 CodeBuffer buffer,
502 bool needsLeadingComma) {
503 bool needsComma = needsLeadingComma;
504 void defineInstanceMember(String name, CodeBuffer memberBuffer) {
505 if (needsComma) buffer.add(',');
506 needsComma = true;
507 buffer.add('\n');
508 buffer.add(' $name: ');
509 addMappings(memberBuffer, buffer.length);
510 buffer.add(memberBuffer);
511 }
512
513 classElement.forEachMember(includeBackendMembers: true,
514 f: (ClassElement enclosing, Element member) {
515 if (member.isInstanceMember()) {
516 addInstanceMember(member, defineInstanceMember);
517 }
518 });
519
520 generateTypeTests(classElement, (Element other) {
521 String code;
522 if (nativeEmitter.requiresNativeIsCheck(other)) {
523 code = 'function() { return true; }';
524 } else {
525 code = 'true';
526 }
527 CodeBuffer typeTestBuffer = new CodeBuffer();
528 typeTestBuffer.add(code);
529 defineInstanceMember(namer.operatorIs(other), typeTestBuffer);
530 });
531
532 if (classElement === compiler.objectClass && compiler.enabledNoSuchMethod) {
533 // Emit the noSuchMethod handlers on the Object prototype now,
534 // so that the code in the dynamicFunction helper can find
535 // them. Note that this helper is invoked before analyzing the
536 // full JS script.
537 if (!nativeEmitter.handleNoSuchMethod) {
538 emitNoSuchMethodHandlers(defineInstanceMember);
539 }
540 }
541 }
542
543 void generateClass(ClassElement classElement, CodeBuffer buffer) {
544 if (classElement.isNative()) {
545 nativeEmitter.generateNativeClass(classElement);
546 return;
547 } else {
548 // TODO(ngeoffray): Instead of switching between buffer, we
549 // should create code sections, and decide where to emit them at
550 // the end.
551 buffer = mainBuffer;
552 }
553
554 needsDefineClass = true;
555 String className = namer.getName(classElement);
556 ClassElement superclass = classElement.superclass;
557 String superName = "";
558 if (superclass !== null) {
559 superName = namer.getName(superclass);
560 }
561 String constructorName = namer.safeName(classElement.name.slowToString());
562
563 buffer.add('$classesCollector.$className = {"":\n');
564 buffer.add(' [');
565 emitClassFields(classElement, buffer);
566 buffer.add('],\n');
567 // TODO(floitsch): the emitInstanceMember should simply always emit a ',\n'.
568 // That does currently not work because the native classes have a different
569 // syntax.
570 buffer.add(' super: "$superName"');
571 emitInstanceMembers(classElement, buffer, true);
572 buffer.add('\n};\n\n');
573 }
574
575 void generateTypeTests(ClassElement cls,
576 void generateTypeTest(ClassElement element)) {
577 if (compiler.codegenWorld.isChecks.contains(cls)) {
578 generateTypeTest(cls);
579 }
580 generateInterfacesIsTests(cls, generateTypeTest, new Set<Element>());
581 }
582
583 void generateInterfacesIsTests(ClassElement cls,
584 void generateTypeTest(ClassElement element),
585 Set<Element> alreadyGenerated) {
586 for (Type interfaceType in cls.interfaces) {
587 Element element = interfaceType.element;
588 if (!alreadyGenerated.contains(element) &&
589 compiler.codegenWorld.isChecks.contains(element)) {
590 alreadyGenerated.add(element);
591 generateTypeTest(element);
592 }
593 generateInterfacesIsTests(element, generateTypeTest, alreadyGenerated);
594 }
595 }
596
597 void emitClasses(CodeBuffer buffer) {
598 Set<ClassElement> instantiatedClasses =
599 compiler.codegenWorld.instantiatedClasses;
600 Set<ClassElement> neededClasses =
601 new Set<ClassElement>.from(instantiatedClasses);
602 for (ClassElement element in instantiatedClasses) {
603 for (ClassElement superclass = element.superclass;
604 superclass !== null;
605 superclass = superclass.superclass) {
606 if (neededClasses.contains(superclass)) break;
607 neededClasses.add(superclass);
608 }
609 }
610 List<ClassElement> sortedClasses =
611 new List<ClassElement>.from(neededClasses);
612 sortedClasses.sort((ClassElement class1, ClassElement class2) {
613 // We sort by the ids of the classes. There is no guarantee that these
614 // ids are meaningful (or even deterministic), but in the current
615 // implementation they are increasing within a source file.
616 return class1.id - class2.id;
617 });
618
619 // If we need noSuchMethod support, we run through all needed
620 // classes to figure out if we need the support on any native
621 // class. If so, we let the native emitter deal with it.
622 if (compiler.enabledNoSuchMethod) {
623 SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD;
624 for (ClassElement element in sortedClasses) {
625 if (!element.isNative()) continue;
626 Element member = element.lookupLocalMember(noSuchMethodName);
627 if (member === null) continue;
628 if (Selector.INVOCATION_2.applies(member, compiler)) {
629 nativeEmitter.handleNoSuchMethod = true;
630 break;
631 }
632 }
633 }
634
635 for (ClassElement element in sortedClasses) {
636 generateClass(element, buffer);
637 }
638
639 // The closure class could have become necessary because of the generation
640 // of stubs.
641 ClassElement closureClass = compiler.closureClass;
642 if (needsClosureClass && !instantiatedClasses.contains(closureClass)) {
643 generateClass(closureClass, buffer);
644 }
645 }
646
647 void emitFinishClassesInvocationIfNecessary(CodeBuffer buffer) {
648 if (needsDefineClass) {
649 buffer.add("$finishClassesName($classesCollector);\n");
650 // Reset the map.
651 buffer.add("$classesCollector = {};\n");
652 }
653 }
654
655 void emitStaticFunctionsWithNamer(CodeBuffer buffer,
656 Map<Element, CodeBuffer> generatedCode,
657 String functionNamer(Element element)) {
658 generatedCode.forEach((Element element, CodeBuffer functionBuffer) {
659 if (!element.isInstanceMember()) {
660 String functionName = functionNamer(element);
661 buffer.add('$isolateProperties.$functionName = ');
662 addMappings(functionBuffer, buffer.length);
663 buffer.add(functionBuffer);
664 buffer.add(';\n\n');
665 }
666 });
667 }
668
669 void emitStaticFunctions(CodeBuffer buffer) {
670 emitStaticFunctionsWithNamer(buffer,
671 compiler.codegenWorld.generatedCode,
672 namer.getName);
673 emitStaticFunctionsWithNamer(buffer,
674 compiler.codegenWorld.generatedBailoutCode,
675 namer.getBailoutName);
676 }
677
678 void emitStaticFunctionGetters(CodeBuffer buffer) {
679 Set<FunctionElement> functionsNeedingGetter =
680 compiler.codegenWorld.staticFunctionsNeedingGetter;
681 for (FunctionElement element in functionsNeedingGetter) {
682 // The static function does not have the correct name. Since
683 // [addParameterStubs] use the name to create its stubs we simply
684 // create a fake element with the correct name.
685 // Note: the callElement will not have any enclosingElement.
686 FunctionElement callElement =
687 new ClosureInvocationElement(namer.CLOSURE_INVOCATION_NAME, element);
688 String staticName = namer.getName(element);
689 int parameterCount = element.parameterCount(compiler);
690 String invocationName =
691 namer.instanceMethodName(element.getLibrary(), callElement.name,
692 parameterCount);
693 String fieldAccess = '$isolateProperties.$staticName';
694 buffer.add("$fieldAccess.$invocationName = $fieldAccess;\n");
695 addParameterStubs(callElement, (String name, CodeBuffer value) {
696 buffer.add('$fieldAccess.$name = $value;\n');
697 });
698 // If a static function is used as a closure we need to add its name
699 // in case it is used in spawnFunction.
700 String fieldName = namer.STATIC_CLOSURE_NAME_NAME;
701 buffer.add('$fieldAccess.$fieldName = "$staticName";\n');
702 }
703 }
704
705 void emitDynamicFunctionGetter(FunctionElement member,
706 DefineMemberFunction defineInstanceMember) {
707 // For every method that has the same name as a property-get we create a
708 // getter that returns a bound closure. Say we have a class 'A' with method
709 // 'foo' and somewhere in the code there is a dynamic property get of
710 // 'foo'. Then we generate the following code (in pseudo Dart/JavaScript):
711 //
712 // class A {
713 // foo(x, y, z) { ... } // Original function.
714 // get foo() { return new BoundClosure499(this, "foo"); }
715 // }
716 // class BoundClosure499 extends Closure {
717 // var self;
718 // BoundClosure499(this.self, this.name);
719 // $call3(x, y, z) { return self[name](x, y, z); }
720 // }
721
722 // TODO(floitsch): share the closure classes with other classes
723 // if they share methods with the same signature. Currently we do this only
724 // if there are no optional parameters. Closures with optional parameters
725 // are more difficult to canonicalize because they would need to have the
726 // same default values.
727
728 bool hasOptionalParameters = member.optionalParameterCount(compiler) != 0;
729 int parameterCount = member.parameterCount(compiler);
730
731 String closureClass =
732 hasOptionalParameters ? null : boundClosureCache[parameterCount];
733 if (closureClass === null) {
734 // Either the class was not cached yet, or there are optional parameters.
735 // Create a new closure class.
736 SourceString name = const SourceString("BoundClosure");
737 ClassElement closureClassElement =
738 new ClosureClassElement(name, compiler, member.getCompilationUnit());
739 String mangledName = namer.getName(closureClassElement);
740 String superName = namer.getName(closureClassElement.superclass);
741 needsClosureClass = true;
742
743 // Define the constructor with a name so that Object.toString can
744 // find the class name of the closure class.
745 boundClosureBuffer.add("""
746 $classesCollector.$mangledName = {'':
747 ['self', 'target'],
748 'super': '$superName',
749 """);
750 // Now add the methods on the closure class. The instance method does not
751 // have the correct name. Since [addParameterStubs] use the name to create
752 // its stubs we simply create a fake element with the correct name.
753 // Note: the callElement will not have any enclosingElement.
754 FunctionElement callElement =
755 new ClosureInvocationElement(namer.CLOSURE_INVOCATION_NAME, member);
756
757 String invocationName =
758 namer.instanceMethodName(member.getLibrary(),
759 callElement.name, parameterCount);
760 List<String> arguments = new List<String>(parameterCount);
761 for (int i = 0; i < parameterCount; i++) {
762 arguments[i] = "p$i";
763 }
764 String joinedArgs = Strings.join(arguments, ", ");
765 boundClosureBuffer.add(
766 "$invocationName: function($joinedArgs) {");
767 boundClosureBuffer.add(" return this.self[this.target]($joinedArgs);");
768 boundClosureBuffer.add(" }");
769 addParameterStubs(callElement, (String stubName, CodeBuffer memberValue) {
770 boundClosureBuffer.add(',\n $stubName: $memberValue');
771 });
772 boundClosureBuffer.add("\n};\n");
773
774 closureClass = namer.isolateAccess(closureClassElement);
775
776 // Cache it.
777 if (!hasOptionalParameters) {
778 boundClosureCache[parameterCount] = closureClass;
779 }
780 }
781
782 // And finally the getter.
783 String getterName = namer.getterName(member.getLibrary(), member.name);
784 String targetName = namer.instanceMethodName(member.getLibrary(),
785 member.name, parameterCount);
786 CodeBuffer getterBuffer = new CodeBuffer();
787 getterBuffer.add(
788 "function() { return new $closureClass(this, '$targetName'); }");
789 defineInstanceMember(getterName, getterBuffer);
790 }
791
792 void emitCallStubForGetter(Element member,
793 Set<Selector> selectors,
794 DefineMemberFunction defineInstanceMember) {
795 String getter;
796 if (member.kind == ElementKind.GETTER) {
797 getter = "this.${namer.getterName(member.getLibrary(), member.name)}()";
798 } else {
799 String name = namer.instanceFieldName(member.getLibrary(), member.name);
800 getter = "this.$name";
801 }
802 for (Selector selector in selectors) {
803 if (selector.applies(member, compiler)) {
804 String invocationName =
805 namer.instanceMethodInvocationName(member.getLibrary(), member.name,
806 selector);
807 SourceString callName = namer.CLOSURE_INVOCATION_NAME;
808 String closureCallName =
809 namer.instanceMethodInvocationName(member.getLibrary(), callName,
810 selector);
811 List<String> arguments = <String>[];
812 for (int i = 0; i < selector.argumentCount; i++) {
813 arguments.add("arg$i");
814 }
815 String joined = Strings.join(arguments, ", ");
816 CodeBuffer getterBuffer = new CodeBuffer();
817 getterBuffer.add(
818 "function($joined) { return $getter.$closureCallName($joined); }");
819 defineInstanceMember(invocationName, getterBuffer);
820 }
821 }
822 }
823
824 void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) {
825 ConstantHandler handler = compiler.constantHandler;
826 List<VariableElement> staticNonFinalFields =
827 handler.getStaticNonFinalFieldsForEmission();
828 for (Element element in staticNonFinalFields) {
829 buffer.add('$isolateProperties.${namer.getName(element)} = ');
830 compiler.withCurrentElement(element, () {
831 handler.writeJsCodeForVariable(buffer, element);
832 });
833 buffer.add(';\n');
834 }
835 }
836
837 void emitCompileTimeConstants(CodeBuffer buffer) {
838 ConstantHandler handler = compiler.constantHandler;
839 List<Constant> constants = handler.getConstantsForEmission();
840 bool addedMakeConstantList = false;
841 for (Constant constant in constants) {
842 String name = handler.getNameForConstant(constant);
843 // The name is null when the constant is already a JS constant.
844 // TODO(floitsch): every constant should be registered, so that we can
845 // share the ones that take up too much space (like some strings).
846 if (name === null) continue;
847 if (!addedMakeConstantList && constant.isList()) {
848 addedMakeConstantList = true;
849 emitMakeConstantList(buffer);
850 }
851 buffer.add('$isolateProperties.$name = ');
852 handler.writeJsCode(buffer, constant);
853 buffer.add(';\n');
854 }
855 }
856
857 void emitMakeConstantList(CodeBuffer buffer) {
858 buffer.add(namer.ISOLATE);
859 buffer.add(@'''.makeConstantList = function(list) {
860 list.immutable$list = true;
861 list.fixed$length = true;
862 return list;
863 };
864 ''');
865 }
866
867 void emitExtraAccessors(Element member,
868 DefineMemberFunction defineInstanceMember) {
869 if (member.kind == ElementKind.GETTER || member.kind == ElementKind.FIELD) {
870 Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name];
871 if (selectors !== null && !selectors.isEmpty()) {
872 emitCallStubForGetter(member, selectors, defineInstanceMember);
873 }
874 } else if (member.kind == ElementKind.FUNCTION) {
875 if (compiler.codegenWorld.hasInvokedGetter(member, compiler)) {
876 emitDynamicFunctionGetter(member, defineInstanceMember);
877 }
878 }
879 }
880
881 void emitNoSuchMethodHandlers(DefineMemberFunction defineInstanceMember) {
882 // Do not generate no such method handlers if there is no class.
883 if (compiler.codegenWorld.instantiatedClasses.isEmpty()) return;
884
885 String noSuchMethodName =
886 namer.instanceMethodName(null, Compiler.NO_SUCH_METHOD, 2);
887
888 // Keep track of the JavaScript names we've already added so we
889 // do not introduce duplicates (bad for code size).
890 Set<String> addedJsNames = new Set<String>();
891
892 // Keep track of the noSuchMethod holders for each possible
893 // receiver type.
894 Map<ClassElement, Set<ClassElement>> noSuchMethodHolders =
895 new Map<ClassElement, Set<ClassElement>>();
896 Set<ClassElement> noSuchMethodHoldersFor(Type type) {
897 ClassElement element = type.element;
898 Set<ClassElement> result = noSuchMethodHolders[element];
899 if (result === null) {
900 // For now, we check the entire world to see if an object of
901 // the given type may have a user-defined noSuchMethod
902 // implementation. We could do better by only looking at
903 // instantiated (or otherwise needed) classes.
904 result = compiler.world.findNoSuchMethodHolders(type);
905 noSuchMethodHolders[element] = result;
906 }
907 return result;
908 }
909
910 CodeBuffer generateMethod(String methodName, Selector selector) {
911 CodeBuffer args = new CodeBuffer();
912 for (int i = 0; i < selector.argumentCount; i++) {
913 if (i != 0) args.add(', ');
914 args.add('\$$i');
915 }
916 CodeBuffer buffer = new CodeBuffer();
917 buffer.add('function($args) {\n');
918 buffer.add(' return this.$noSuchMethodName("$methodName", [$args]);\n');
919 buffer.add(' }');
920 return buffer;
921 }
922
923 void addNoSuchMethodHandlers(SourceString name, Set<Selector> selectors) {
924 // TODO(kasperl): We should really teach private selectors about
925 // which libraries they are used from. That way, we wouldn't
926 // have to conservatively generate versions for all libraries
927 // the name is used from.
928 String nameString = name.slowToString();
929 Collection<LibraryElement> libraries = name.isPrivate()
930 ? namer.usedPrivateNames[nameString]
931 : const [ null ];
932
933 // Cache the object class and type.
934 ClassElement objectClass = compiler.objectClass;
935 Type objectType = objectClass.computeType(compiler);
936
937 for (Selector selector in selectors) {
938 // Introduce a helper function that determines if the given
939 // class has a member that matches the current name and
940 // selector (grabbed from the scope).
941 bool hasMatchingMember(ClassElement holder) {
942 Element element = holder.lookupMember(name);
943 if (element === null) return false;
944
945 // TODO(kasperl): Consider folding this logic into the
946 // Selector.applies() method.
947 if (element is AbstractFieldElement) {
948 AbstractFieldElement field = element;
949 if (selector.kind === SelectorKind.GETTER) {
950 return field.getter !== null;
951 } else if (selector.kind === SelectorKind.SETTER) {
952 return field.setter !== null;
953 } else {
954 return false;
955 }
956 }
957 return selector.applies(element, compiler);
958 }
959
960 // If the selector is typed, we check to see if that type may
961 // have a user-defined noSuchMethod implementation. If not, we
962 // skip the selector altogether.
963 Type receiverType = objectType;
964 ClassElement receiverClass = objectClass;
965 if (selector is TypedSelector) {
966 receiverType = (selector as TypedSelector).receiverType;
967 receiverClass = receiverType.element;
968 }
969
970 // If the receiver class is guaranteed to have a member that
971 // matches what we're looking for, there's no need to
972 // introduce a noSuchMethod handler. It will never be called.
973 //
974 // As an example, consider this class hierarchy:
975 //
976 // A <-- noSuchMethod
977 // / \
978 // C B <-- foo
979 //
980 // If we know we're calling foo on an object of type B we
981 // don't have to worry about the noSuchMethod method in A
982 // because objects of type B implement foo. On the other hand,
983 // if we end up calling foo on something of type C we have to
984 // add a handler for it.
985 if (hasMatchingMember(receiverClass)) continue;
986
987 // If the holders of all user-defined noSuchMethod
988 // implementations that might be applicable to the receiver
989 // type have a matching member for the current name and
990 // selector, we avoid introducing a noSuchMethod handler.
991 //
992 // As an example, consider this class hierarchy:
993 //
994 // A <-- foo
995 // / \
996 // noSuchMethod --> B C <-- bar
997 // | |
998 // C D <-- noSuchMethod
999 //
1000 // When calling foo on an object of type A, we know that the
1001 // implementations of noSuchMethod are in the classes B and D
1002 // that also (indirectly) implement foo, so we do not need a
1003 // handler for it.
1004 //
1005 // If we're calling bar on an object of type D, we don't need
1006 // the handler either because all objects of type D implement
1007 // bar through inheritance.
1008 //
1009 // If we're calling bar on an object of type A we do need the
1010 // handler because we may have to call B.noSuchMethod since B
1011 // does not implement bar.
1012 Set<ClassElement> holders = noSuchMethodHoldersFor(receiverType);
1013 if (holders.every(hasMatchingMember)) continue;
1014
1015 for (LibraryElement lib in libraries) {
1016 String jsName = null;
1017 String methodName = null;
1018 if (selector.kind === SelectorKind.GETTER) {
1019 jsName = namer.getterName(lib, name);
1020 methodName = 'get:$nameString';
1021 } else if (selector.kind === SelectorKind.SETTER) {
1022 jsName = namer.setterName(lib, name);
1023 methodName = 'set:$nameString';
1024 } else if (selector.kind === SelectorKind.INVOCATION) {
1025 jsName = namer.instanceMethodInvocationName(lib, name, selector);
1026 methodName = nameString;
1027 } else {
1028 // We simply ignore selectors that do not need
1029 // noSuchMethod handlers.
1030 continue;
1031 }
1032 if (!addedJsNames.contains(jsName)) {
1033 CodeBuffer jsCode = generateMethod(methodName, selector);
1034 defineInstanceMember(jsName, jsCode);
1035 addedJsNames.add(jsName);
1036 }
1037 }
1038 }
1039 }
1040
1041 compiler.codegenWorld.invokedNames.forEach(addNoSuchMethodHandlers);
1042 compiler.codegenWorld.invokedGetters.forEach(addNoSuchMethodHandlers);
1043 compiler.codegenWorld.invokedSetters.forEach(addNoSuchMethodHandlers);
1044 }
1045
1046 String buildIsolateSetup(CodeBuffer buffer,
1047 Element appMain,
1048 Element isolateMain) {
1049 String mainAccess = "${namer.isolateAccess(appMain)}";
1050 String currentIsolate = "${namer.CURRENT_ISOLATE}";
1051 String mainEnsureGetter = '';
1052 // Since we pass the closurized version of the main method to
1053 // the isolate method, we must make sure that it exists.
1054 if (!compiler.codegenWorld.staticFunctionsNeedingGetter.contains(appMain)) {
1055 String invocationName =
1056 "${namer.closureInvocationName(Selector.INVOCATION_0)}";
1057 mainEnsureGetter = "$mainAccess.$invocationName = $mainAccess";
1058 }
1059
1060 // TODO(ngeoffray): These globals are currently required by the isolate
1061 // library, but since leg already generates code on an Isolate object, they
1062 // are not really needed. We should remove them once Leg replaces Frog.
1063 buffer.add("""
1064 var \$globalThis = $currentIsolate;
1065 var \$globalState;
1066 var \$globals;
1067 var \$isWorker;
1068 var \$supportsWorkers;
1069 var \$thisScriptUrl;
1070 function \$static_init(){};
1071
1072 function \$initGlobals(context) {
1073 context.isolateStatics = new ${namer.ISOLATE}();
1074 }
1075 function \$setGlobals(context) {
1076 $currentIsolate = context.isolateStatics;
1077 \$globalThis = $currentIsolate;
1078 }
1079 $mainEnsureGetter
1080 """);
1081 return "${namer.isolateAccess(isolateMain)}($mainAccess)";
1082 }
1083
1084 emitMain(CodeBuffer buffer) {
1085 if (compiler.isMockCompilation) return;
1086 Element main = compiler.mainApp.find(Compiler.MAIN);
1087 String mainCall = null;
1088 if (compiler.isolateLibrary != null) {
1089 Element isolateMain =
1090 compiler.isolateLibrary.find(Compiler.START_ROOT_ISOLATE);
1091 mainCall = buildIsolateSetup(buffer, main, isolateMain);
1092 } else {
1093 mainCall = '${namer.isolateAccess(main)}()';
1094 }
1095 buffer.add("""
1096 if (typeof document != 'undefined' && document.readyState != 'complete') {
1097 document.addEventListener('readystatechange', function () {
1098 if (document.readyState == 'complete') {
1099 ${mainCall};
1100 }
1101 }, false);
1102 } else {
1103 ${mainCall};
1104 }
1105 """);
1106 }
1107
1108 String assembleProgram() {
1109 measure(() {
1110 mainBuffer.add('function ${namer.ISOLATE}() {}\n');
1111 mainBuffer.add('init();\n\n');
1112 // Shorten the code by using "$$" as temporary.
1113 classesCollector = @"$$";
1114 mainBuffer.add('var $classesCollector = {};\n');
1115 // Shorten the code by using [namer.CURRENT_ISOLATE] as temporary.
1116 isolateProperties = namer.CURRENT_ISOLATE;
1117 mainBuffer.add('var $isolateProperties = $isolatePropertiesName;\n');
1118 emitClasses(mainBuffer);
1119 mainBuffer.add(boundClosureBuffer);
1120 // Clear the buffer, so that we can reuse it for the native classes.
1121 boundClosureBuffer.clear();
1122 emitStaticFunctions(mainBuffer);
1123 emitStaticFunctionGetters(mainBuffer);
1124 // We need to finish the classes before we construct compile time
1125 // constants.
1126 emitFinishClassesInvocationIfNecessary(mainBuffer);
1127 emitCompileTimeConstants(mainBuffer);
1128 // Static field initializations require the classes and compile-time
1129 // constants to be set up.
1130 emitStaticNonFinalFieldInitializations(mainBuffer);
1131
1132 isolateProperties = isolatePropertiesName;
1133 // The following code should not use the short-hand for the
1134 // initialStatics.
1135 mainBuffer.add('var ${namer.CURRENT_ISOLATE} = null;\n');
1136 mainBuffer.add(boundClosureBuffer);
1137 emitFinishClassesInvocationIfNecessary(mainBuffer);
1138 // After this assignment we will produce invalid JavaScript code if we use
1139 // the classesCollector variable.
1140 classesCollector = 'classesCollector should not be used from now on';
1141
1142 emitFinishIsolateConstructorInvocation(mainBuffer);
1143 mainBuffer.add(
1144 'var ${namer.CURRENT_ISOLATE} = new ${namer.ISOLATE}();\n');
1145
1146 nativeEmitter.assembleCode(mainBuffer);
1147 emitMain(mainBuffer);
1148 mainBuffer.add('function init() {\n');
1149 mainBuffer.add('$isolateProperties = {};\n');
1150 addDefineClassAndFinishClassFunctionsIfNecessary(mainBuffer);
1151 emitFinishIsolateConstructor(mainBuffer);
1152 mainBuffer.add('}\n');
1153 compiler.assembledCode = mainBuffer.toString();
1154
1155 if (generateSourceMap) {
1156 SourceFile compiledFile = new SourceFile(null, compiler.assembledCode);
1157 String sourceMap = sourceMapBuilder.build(compiledFile);
1158 // TODO(podivilov): We should find a better way to return source maps to
1159 // compiler. Using diagnostic handler for that purpose is a temporary
1160 // hack.
1161 compiler.reportDiagnostic(
1162 null, sourceMap, new api.Diagnostic(-1, 'source map'));
1163 }
1164 });
1165 return compiler.assembledCode;
1166 }
1167
1168 void addMappings(CodeBuffer buffer, int bufferOffset) {
1169 buffer.forEachSourceLocation((Element element, Token token, int offset) {
1170 SourceFile sourceFile = element.getCompilationUnit().script.file;
1171 String sourceName = null;
1172 if (token.kind === IDENTIFIER_TOKEN) {
1173 sourceName = token.slowToString();
1174 }
1175 int totalOffset = bufferOffset + offset;
1176 sourceMapBuilder.addMapping(
1177 sourceFile, token.charOffset, sourceName, totalOffset);
1178 });
1179 }
1180 }
1181
1182 typedef void DefineMemberFunction(String invocationName, CodeBuffer definition);
OLDNEW
« no previous file with comments | « lib/compiler/implementation/compiler.dart ('k') | lib/compiler/implementation/js_backend/backend.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698