| 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 part of js_backend; | |
| 6 | |
| 7 class NativeEmitter { | |
| 8 | |
| 9 final Map<Element, ClassBuilder> cachedBuilders; | |
| 10 | |
| 11 final CodeEmitterTask emitterTask; | |
| 12 CodeBuffer nativeBuffer; | |
| 13 | |
| 14 // Native classes found in the application. | |
| 15 Set<ClassElement> nativeClasses = new Set<ClassElement>(); | |
| 16 | |
| 17 // Caches the native subtypes of a native class. | |
| 18 Map<ClassElement, List<ClassElement>> subtypes; | |
| 19 | |
| 20 // Caches the direct native subtypes of a native class. | |
| 21 Map<ClassElement, List<ClassElement>> directSubtypes; | |
| 22 | |
| 23 // Caches the methods that have a native body. | |
| 24 Set<FunctionElement> nativeMethods; | |
| 25 | |
| 26 // Do we need the native emitter to take care of handling | |
| 27 // noSuchMethod for us? This flag is set to true in the emitter if | |
| 28 // it finds any native class that needs noSuchMethod handling. | |
| 29 bool handleNoSuchMethod = false; | |
| 30 | |
| 31 NativeEmitter(CodeEmitterTask emitterTask) | |
| 32 : this.emitterTask = emitterTask, | |
| 33 subtypes = new Map<ClassElement, List<ClassElement>>(), | |
| 34 directSubtypes = new Map<ClassElement, List<ClassElement>>(), | |
| 35 nativeMethods = new Set<FunctionElement>(), | |
| 36 nativeBuffer = new CodeBuffer(), | |
| 37 cachedBuilders = emitterTask.compiler.cacheStrategy.newMap(); | |
| 38 | |
| 39 Compiler get compiler => emitterTask.compiler; | |
| 40 JavaScriptBackend get backend => compiler.backend; | |
| 41 | |
| 42 jsAst.Expression get defPropFunction { | |
| 43 Element element = backend.findHelper('defineProperty'); | |
| 44 return backend.namer.elementAccess(element); | |
| 45 } | |
| 46 | |
| 47 /** | |
| 48 * Writes the class definitions for the interceptors to [mainBuffer]. | |
| 49 * Writes code to associate dispatch tags with interceptors to [nativeBuffer]. | |
| 50 * | |
| 51 * The interceptors are filtered to avoid emitting trivial interceptors. For | |
| 52 * example, if the program contains no code that can distinguish between the | |
| 53 * numerous subclasses of `Element` then we can pretend that `Element` is a | |
| 54 * leaf class, and all instances of subclasses of `Element` are instances of | |
| 55 * `Element`. | |
| 56 * | |
| 57 * There is also a performance benefit (in addition to the obvious code size | |
| 58 * benefit), due to how [getNativeInterceptor] works. Finding the interceptor | |
| 59 * of a leaf class in the hierarchy is more efficient that a non-leaf, so it | |
| 60 * improves performance when more classes can be treated as leaves. | |
| 61 * | |
| 62 * [classes] contains native classes, mixin applications, and user subclasses | |
| 63 * of native classes. ONLY the native classes are generated here. [classes] | |
| 64 * is sorted in desired output order. | |
| 65 * | |
| 66 * [additionalProperties] is used to collect properties that are pushed up | |
| 67 * from the above optimizations onto a non-native class, e.g, `Interceptor`. | |
| 68 */ | |
| 69 void generateNativeClasses( | |
| 70 List<ClassElement> classes, | |
| 71 CodeBuffer mainBuffer, | |
| 72 Map<ClassElement, Map<String, jsAst.Expression>> additionalProperties) { | |
| 73 // Compute a pre-order traversal of the subclass forest. We actually want a | |
| 74 // post-order traversal but it is easier to compute the pre-order and use it | |
| 75 // in reverse. | |
| 76 | |
| 77 List<ClassElement> preOrder = <ClassElement>[]; | |
| 78 Set<ClassElement> seen = new Set<ClassElement>(); | |
| 79 seen..add(compiler.objectClass) | |
| 80 ..add(backend.jsInterceptorClass); | |
| 81 void walk(ClassElement element) { | |
| 82 if (seen.contains(element)) return; | |
| 83 seen.add(element); | |
| 84 walk(element.superclass); | |
| 85 preOrder.add(element); | |
| 86 } | |
| 87 classes.forEach(walk); | |
| 88 | |
| 89 // Generate code for each native class into [ClassBuilder]s. | |
| 90 | |
| 91 Map<ClassElement, ClassBuilder> builders = | |
| 92 new Map<ClassElement, ClassBuilder>(); | |
| 93 for (ClassElement classElement in classes) { | |
| 94 if (classElement.isNative) { | |
| 95 ClassBuilder builder = generateNativeClass(classElement); | |
| 96 builders[classElement] = builder; | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 // Find which classes are needed and which are non-leaf classes. Any class | |
| 101 // that is not needed can be treated as a leaf class equivalent to some | |
| 102 // needed class. | |
| 103 | |
| 104 Set<ClassElement> neededClasses = new Set<ClassElement>(); | |
| 105 Set<ClassElement> nonleafClasses = new Set<ClassElement>(); | |
| 106 | |
| 107 Map<ClassElement, List<ClassElement>> extensionPoints = | |
| 108 computeExtensionPoints(preOrder); | |
| 109 | |
| 110 neededClasses.add(compiler.objectClass); | |
| 111 | |
| 112 Set<ClassElement> neededByConstant = | |
| 113 emitterTask.interceptorsReferencedFromConstants(); | |
| 114 Set<ClassElement> modifiedClasses = | |
| 115 emitterTask.typeTestEmitter.classesModifiedByEmitRuntimeTypeSupport(); | |
| 116 | |
| 117 for (ClassElement classElement in preOrder.reversed) { | |
| 118 // Post-order traversal ensures we visit the subclasses before their | |
| 119 // superclass. This makes it easy to tell if a class is needed because a | |
| 120 // subclass is needed. | |
| 121 ClassBuilder builder = builders[classElement]; | |
| 122 bool needed = false; | |
| 123 if (builder == null) { | |
| 124 // Mixin applications (native+mixin) are non-native, so [classElement] | |
| 125 // has already been emitted as a regular class. Mark [classElement] as | |
| 126 // 'needed' to ensure the native superclass is needed. | |
| 127 needed = true; | |
| 128 } else if (!builder.isTrivial) { | |
| 129 needed = true; | |
| 130 } else if (neededByConstant.contains(classElement)) { | |
| 131 needed = true; | |
| 132 } else if (modifiedClasses.contains(classElement)) { | |
| 133 // TODO(9556): Remove this test when [emitRuntimeTypeSupport] no longer | |
| 134 // adds information to a class prototype or constructor. | |
| 135 needed = true; | |
| 136 } else if (extensionPoints.containsKey(classElement)) { | |
| 137 needed = true; | |
| 138 } | |
| 139 if (classElement.isNative && | |
| 140 native.nativeTagsForcedNonLeaf(classElement)) { | |
| 141 needed = true; | |
| 142 nonleafClasses.add(classElement); | |
| 143 } | |
| 144 | |
| 145 if (needed || neededClasses.contains(classElement)) { | |
| 146 neededClasses.add(classElement); | |
| 147 neededClasses.add(classElement.superclass); | |
| 148 nonleafClasses.add(classElement.superclass); | |
| 149 } | |
| 150 } | |
| 151 | |
| 152 // Collect all the tags that map to each native class. | |
| 153 | |
| 154 Map<ClassElement, Set<String>> leafTags = | |
| 155 new Map<ClassElement, Set<String>>(); | |
| 156 Map<ClassElement, Set<String>> nonleafTags = | |
| 157 new Map<ClassElement, Set<String>>(); | |
| 158 | |
| 159 for (ClassElement classElement in classes) { | |
| 160 if (!classElement.isNative) continue; | |
| 161 List<String> nativeTags = native.nativeTagsOfClass(classElement); | |
| 162 | |
| 163 if (nonleafClasses.contains(classElement) || | |
| 164 extensionPoints.containsKey(classElement)) { | |
| 165 nonleafTags | |
| 166 .putIfAbsent(classElement, () => new Set<String>()) | |
| 167 .addAll(nativeTags); | |
| 168 } else { | |
| 169 ClassElement sufficingInterceptor = classElement; | |
| 170 while (!neededClasses.contains(sufficingInterceptor)) { | |
| 171 sufficingInterceptor = sufficingInterceptor.superclass; | |
| 172 } | |
| 173 if (sufficingInterceptor == compiler.objectClass) { | |
| 174 sufficingInterceptor = backend.jsInterceptorClass; | |
| 175 } | |
| 176 leafTags | |
| 177 .putIfAbsent(sufficingInterceptor, () => new Set<String>()) | |
| 178 .addAll(nativeTags); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 // Add properties containing the information needed to construct maps used | |
| 183 // by getNativeInterceptor and custom elements. | |
| 184 if (compiler.enqueuer.codegen.nativeEnqueuer | |
| 185 .hasInstantiatedNativeClasses()) { | |
| 186 void generateClassInfo(ClassElement classElement) { | |
| 187 // Property has the form: | |
| 188 // | |
| 189 // "%": "leafTag1|leafTag2|...;nonleafTag1|...;Class1|Class2|...", | |
| 190 // | |
| 191 // If there is no data following a semicolon, the semicolon can be | |
| 192 // omitted. | |
| 193 | |
| 194 String formatTags(Iterable<String> tags) { | |
| 195 if (tags == null) return ''; | |
| 196 return (tags.toList()..sort()).join('|'); | |
| 197 } | |
| 198 | |
| 199 List<ClassElement> extensions = extensionPoints[classElement]; | |
| 200 | |
| 201 String leafStr = formatTags(leafTags[classElement]); | |
| 202 String nonleafStr = formatTags(nonleafTags[classElement]); | |
| 203 | |
| 204 StringBuffer sb = new StringBuffer(leafStr); | |
| 205 if (nonleafStr != '') { | |
| 206 sb..write(';')..write(nonleafStr); | |
| 207 } | |
| 208 if (extensions != null) { | |
| 209 sb..write(';') | |
| 210 ..writeAll(extensions.map(backend.namer.getNameOfClass), '|'); | |
| 211 } | |
| 212 String encoding = sb.toString(); | |
| 213 | |
| 214 ClassBuilder builder = builders[classElement]; | |
| 215 if (builder == null) { | |
| 216 // No builder because this is an intermediate mixin application or | |
| 217 // Interceptor - these are not direct native classes. | |
| 218 if (encoding != '') { | |
| 219 Map<String, jsAst.Expression> properties = | |
| 220 additionalProperties.putIfAbsent(classElement, | |
| 221 () => new LinkedHashMap<String, jsAst.Expression>()); | |
| 222 properties[backend.namer.nativeSpecProperty] = js.string(encoding); | |
| 223 } | |
| 224 } else { | |
| 225 builder.addProperty( | |
| 226 backend.namer.nativeSpecProperty, js.string(encoding)); | |
| 227 } | |
| 228 } | |
| 229 generateClassInfo(backend.jsInterceptorClass); | |
| 230 for (ClassElement classElement in classes) { | |
| 231 generateClassInfo(classElement); | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 // Emit the native class interceptors that were actually used. | |
| 236 for (ClassElement classElement in classes) { | |
| 237 if (!classElement.isNative) continue; | |
| 238 if (neededClasses.contains(classElement)) { | |
| 239 // Define interceptor class for [classElement]. | |
| 240 emitterTask.oldEmitter.classEmitter.emitClassBuilderWithReflectionData( | |
| 241 backend.namer.getNameOfClass(classElement), | |
| 242 classElement, builders[classElement], | |
| 243 emitterTask.oldEmitter.getElementDescriptor(classElement)); | |
| 244 emitterTask.oldEmitter.needsDefineClass = true; | |
| 245 } | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 /** | |
| 250 * Computes the native classes that are extended (subclassed) by non-native | |
| 251 * classes and the set non-mative classes that extend them. (A List is used | |
| 252 * instead of a Set for out stability). | |
| 253 */ | |
| 254 Map<ClassElement, List<ClassElement>> computeExtensionPoints( | |
| 255 List<ClassElement> classes) { | |
| 256 ClassElement nativeSuperclassOf(ClassElement element) { | |
| 257 if (element == null) return null; | |
| 258 if (element.isNative) return element; | |
| 259 return nativeSuperclassOf(element.superclass); | |
| 260 } | |
| 261 | |
| 262 ClassElement nativeAncestorOf(ClassElement element) { | |
| 263 return nativeSuperclassOf(element.superclass); | |
| 264 } | |
| 265 | |
| 266 Map<ClassElement, List<ClassElement>> map = | |
| 267 new Map<ClassElement, List<ClassElement>>(); | |
| 268 | |
| 269 for (ClassElement classElement in classes) { | |
| 270 if (classElement.isNative) continue; | |
| 271 ClassElement nativeAncestor = nativeAncestorOf(classElement); | |
| 272 if (nativeAncestor != null) { | |
| 273 map | |
| 274 .putIfAbsent(nativeAncestor, () => <ClassElement>[]) | |
| 275 .add(classElement); | |
| 276 } | |
| 277 } | |
| 278 return map; | |
| 279 } | |
| 280 | |
| 281 ClassBuilder generateNativeClass(ClassElement classElement) { | |
| 282 ClassBuilder builder; | |
| 283 if (compiler.hasIncrementalSupport) { | |
| 284 builder = cachedBuilders[classElement]; | |
| 285 if (builder != null) return builder; | |
| 286 builder = new ClassBuilder(classElement, backend.namer); | |
| 287 cachedBuilders[classElement] = builder; | |
| 288 } else { | |
| 289 builder = new ClassBuilder(classElement, backend.namer); | |
| 290 } | |
| 291 | |
| 292 // TODO(sra): Issue #13731- this is commented out as part of custom element | |
| 293 // constructor work. | |
| 294 //assert(!classElement.hasBackendMembers); | |
| 295 nativeClasses.add(classElement); | |
| 296 | |
| 297 ClassElement superclass = classElement.superclass; | |
| 298 assert(superclass != null); | |
| 299 // Fix superclass. TODO(sra): make native classes inherit from Interceptor. | |
| 300 assert(superclass != compiler.objectClass); | |
| 301 if (superclass == compiler.objectClass) { | |
| 302 superclass = backend.jsInterceptorClass; | |
| 303 } | |
| 304 | |
| 305 String superName = backend.namer.getNameOfClass(superclass); | |
| 306 | |
| 307 emitterTask.oldEmitter.classEmitter.emitClassConstructor( | |
| 308 classElement, builder); | |
| 309 bool hasFields = emitterTask.oldEmitter.classEmitter.emitFields( | |
| 310 classElement, builder, superName, classIsNative: true); | |
| 311 int propertyCount = builder.properties.length; | |
| 312 emitterTask.oldEmitter.classEmitter.emitClassGettersSetters( | |
| 313 classElement, builder); | |
| 314 emitterTask.oldEmitter.classEmitter.emitInstanceMembers( | |
| 315 classElement, builder); | |
| 316 emitterTask.typeTestEmitter.emitIsTests(classElement, builder); | |
| 317 | |
| 318 if (!hasFields && | |
| 319 builder.properties.length == propertyCount && | |
| 320 superclass is! MixinApplicationElement) { | |
| 321 builder.isTrivial = true; | |
| 322 } | |
| 323 | |
| 324 return builder; | |
| 325 } | |
| 326 | |
| 327 void finishGenerateNativeClasses() { | |
| 328 // TODO(sra): Put specialized version of getNativeMethods on | |
| 329 // `Object.prototype` to avoid checking in `getInterceptor` and | |
| 330 // specializations. | |
| 331 } | |
| 332 | |
| 333 void potentiallyConvertDartClosuresToJs( | |
| 334 List<jsAst.Statement> statements, | |
| 335 FunctionElement member, | |
| 336 List<jsAst.Parameter> stubParameters) { | |
| 337 FunctionSignature parameters = member.functionSignature; | |
| 338 Element converter = backend.findHelper('convertDartClosureToJS'); | |
| 339 jsAst.Expression closureConverter = backend.namer.elementAccess(converter); | |
| 340 parameters.forEachParameter((ParameterElement parameter) { | |
| 341 String name = parameter.name; | |
| 342 // If [name] is not in [stubParameters], then the parameter is an optional | |
| 343 // parameter that was not provided for this stub. | |
| 344 for (jsAst.Parameter stubParameter in stubParameters) { | |
| 345 if (stubParameter.name == name) { | |
| 346 DartType type = parameter.type.unalias(compiler); | |
| 347 if (type is FunctionType) { | |
| 348 // The parameter type is a function type either directly or through | |
| 349 // typedef(s). | |
| 350 FunctionType functionType = type; | |
| 351 int arity = functionType.computeArity(); | |
| 352 statements.add( | |
| 353 js.statement('# = #(#, $arity)', | |
| 354 [name, closureConverter, name])); | |
| 355 break; | |
| 356 } | |
| 357 } | |
| 358 } | |
| 359 }); | |
| 360 } | |
| 361 | |
| 362 List<jsAst.Statement> generateParameterStubStatements( | |
| 363 FunctionElement member, | |
| 364 bool isInterceptedMethod, | |
| 365 String invocationName, | |
| 366 List<jsAst.Parameter> stubParameters, | |
| 367 List<jsAst.Expression> argumentsBuffer, | |
| 368 int indexOfLastOptionalArgumentInParameters) { | |
| 369 // The target JS function may check arguments.length so we need to | |
| 370 // make sure not to pass any unspecified optional arguments to it. | |
| 371 // For example, for the following Dart method: | |
| 372 // foo([x, y, z]); | |
| 373 // The call: | |
| 374 // foo(y: 1) | |
| 375 // must be turned into a JS call to: | |
| 376 // foo(null, y). | |
| 377 | |
| 378 ClassElement classElement = member.enclosingClass; | |
| 379 | |
| 380 List<jsAst.Statement> statements = <jsAst.Statement>[]; | |
| 381 potentiallyConvertDartClosuresToJs(statements, member, stubParameters); | |
| 382 | |
| 383 String target; | |
| 384 jsAst.Expression receiver; | |
| 385 List<jsAst.Expression> arguments; | |
| 386 | |
| 387 assert(invariant(member, nativeMethods.contains(member))); | |
| 388 // When calling a JS method, we call it with the native name, and only the | |
| 389 // arguments up until the last one provided. | |
| 390 target = member.fixedBackendName; | |
| 391 | |
| 392 if (isInterceptedMethod) { | |
| 393 receiver = argumentsBuffer[0]; | |
| 394 arguments = argumentsBuffer.sublist(1, | |
| 395 indexOfLastOptionalArgumentInParameters + 1); | |
| 396 } else { | |
| 397 receiver = js('this'); | |
| 398 arguments = argumentsBuffer.sublist(0, | |
| 399 indexOfLastOptionalArgumentInParameters + 1); | |
| 400 } | |
| 401 statements.add( | |
| 402 js.statement('return #.#(#)', [receiver, target, arguments])); | |
| 403 | |
| 404 return statements; | |
| 405 } | |
| 406 | |
| 407 bool isSupertypeOfNativeClass(Element element) { | |
| 408 if (element.isTypeVariable) { | |
| 409 compiler.internalError(element, "Is check for type variable."); | |
| 410 return false; | |
| 411 } | |
| 412 if (element.computeType(compiler).unalias(compiler) is FunctionType) { | |
| 413 // The element type is a function type either directly or through | |
| 414 // typedef(s). | |
| 415 return false; | |
| 416 } | |
| 417 | |
| 418 if (!element.isClass) { | |
| 419 compiler.internalError(element, "Is check does not handle element."); | |
| 420 return false; | |
| 421 } | |
| 422 | |
| 423 if (backend.classesMixedIntoInterceptedClasses.contains(element)) { | |
| 424 return true; | |
| 425 } | |
| 426 | |
| 427 return subtypes[element] != null; | |
| 428 } | |
| 429 | |
| 430 bool requiresNativeIsCheck(Element element) { | |
| 431 // TODO(sra): Remove this function. It determines if a native type may | |
| 432 // satisfy a check against [element], in which case an interceptor must be | |
| 433 // used. We should also use an interceptor if the check can't be satisfied | |
| 434 // by a native class in case we get a native instance that tries to spoof | |
| 435 // the type info. i.e the criteria for whether or not to use an interceptor | |
| 436 // is whether the receiver can be native, not the type of the test. | |
| 437 if (element == null || !element.isClass) return false; | |
| 438 ClassElement cls = element; | |
| 439 if (Elements.isNativeOrExtendsNative(cls)) return true; | |
| 440 return isSupertypeOfNativeClass(element); | |
| 441 } | |
| 442 | |
| 443 void assembleCode(CodeBuffer targetBuffer) { | |
| 444 List<jsAst.Property> objectProperties = <jsAst.Property>[]; | |
| 445 | |
| 446 jsAst.Property addProperty(String name, jsAst.Expression value) { | |
| 447 jsAst.Property prop = new jsAst.Property(js.string(name), value); | |
| 448 objectProperties.add(prop); | |
| 449 return prop; | |
| 450 } | |
| 451 | |
| 452 if (!nativeClasses.isEmpty) { | |
| 453 // If the native emitter has been asked to take care of the | |
| 454 // noSuchMethod handlers, we do that now. | |
| 455 if (handleNoSuchMethod) { | |
| 456 emitterTask.oldEmitter.nsmEmitter.emitNoSuchMethodHandlers(addProperty); | |
| 457 } | |
| 458 } | |
| 459 | |
| 460 // If we have any properties to add to Object.prototype, we run | |
| 461 // through them and add them using defineProperty. | |
| 462 if (!objectProperties.isEmpty) { | |
| 463 jsAst.Expression init = js(r''' | |
| 464 (function(table) { | |
| 465 for(var key in table) | |
| 466 #(Object.prototype, key, table[key]); | |
| 467 })(#)''', | |
| 468 [ defPropFunction, | |
| 469 new jsAst.ObjectInitializer(objectProperties)]); | |
| 470 | |
| 471 if (emitterTask.compiler.enableMinification) targetBuffer.add(';'); | |
| 472 targetBuffer.add(jsAst.prettyPrint( | |
| 473 new jsAst.ExpressionStatement(init), compiler)); | |
| 474 targetBuffer.add('\n'); | |
| 475 } | |
| 476 | |
| 477 targetBuffer.add(nativeBuffer); | |
| 478 targetBuffer.add('\n'); | |
| 479 } | |
| 480 } | |
| OLD | NEW |