| OLD | NEW |
| (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); | |
| OLD | NEW |