| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of dart2js.js_emitter; | 5 part of dart2js.js_emitter; |
| 6 | 6 |
| 7 class NativeEmitter { | 7 class NativeEmitter { |
| 8 | 8 |
| 9 final Map<Element, ClassBuilder> cachedBuilders; | 9 final Map<Element, ClassBuilder> cachedBuilders; |
| 10 | 10 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 Set<FunctionElement> nativeMethods; | 23 Set<FunctionElement> nativeMethods; |
| 24 | 24 |
| 25 NativeEmitter(CodeEmitterTask emitterTask) | 25 NativeEmitter(CodeEmitterTask emitterTask) |
| 26 : this.emitterTask = emitterTask, | 26 : this.emitterTask = emitterTask, |
| 27 subtypes = new Map<ClassElement, List<ClassElement>>(), | 27 subtypes = new Map<ClassElement, List<ClassElement>>(), |
| 28 directSubtypes = new Map<ClassElement, List<ClassElement>>(), | 28 directSubtypes = new Map<ClassElement, List<ClassElement>>(), |
| 29 nativeMethods = new Set<FunctionElement>(), | 29 nativeMethods = new Set<FunctionElement>(), |
| 30 cachedBuilders = emitterTask.compiler.cacheStrategy.newMap(); | 30 cachedBuilders = emitterTask.compiler.cacheStrategy.newMap(); |
| 31 | 31 |
| 32 Compiler get compiler => emitterTask.compiler; | 32 Compiler get compiler => emitterTask.compiler; |
| 33 |
| 33 JavaScriptBackend get backend => compiler.backend; | 34 JavaScriptBackend get backend => compiler.backend; |
| 34 | 35 |
| 35 jsAst.Expression get defPropFunction { | 36 jsAst.Expression get defPropFunction { |
| 36 Element element = backend.findHelper('defineProperty'); | 37 Element element = backend.findHelper('defineProperty'); |
| 37 return emitterTask.staticFunctionAccess(element); | 38 return emitterTask.staticFunctionAccess(element); |
| 38 } | 39 } |
| 39 | 40 |
| 40 /** | 41 /** |
| 41 * Prepares native classes for emission. Returns the unneeded classes. | 42 * Prepares native classes for emission. Returns the unneeded classes. |
| 42 * | 43 * |
| 43 * Removes trivial classes (that can be represented by a super type) and | 44 * Removes trivial classes (that can be represented by a super type) and |
| 44 * generates properties that have to be added to classes (native or not). | 45 * generates properties that have to be added to classes (native or not). |
| 45 * | 46 * |
| 46 * Updates the `nativeInfo` field of the given classes. This data | 47 * Updates the `nativeLeafTags`, `nativeNonLeafTags` and `nativeExtensions` |
| 47 * must be emitted with the corresponding classes. | 48 * fields of the given classes. This data must be emitted with the |
| 49 * corresponding classes. |
| 48 * | 50 * |
| 49 * The interceptors are filtered to avoid emitting trivial interceptors. For | 51 * The interceptors are filtered to avoid emitting trivial interceptors. For |
| 50 * example, if the program contains no code that can distinguish between the | 52 * example, if the program contains no code that can distinguish between the |
| 51 * numerous subclasses of `Element` then we can pretend that `Element` is a | 53 * numerous subclasses of `Element` then we can pretend that `Element` is a |
| 52 * leaf class, and all instances of subclasses of `Element` are instances of | 54 * leaf class, and all instances of subclasses of `Element` are instances of |
| 53 * `Element`. | 55 * `Element`. |
| 54 * | 56 * |
| 55 * There is also a performance benefit (in addition to the obvious code size | 57 * There is also a performance benefit (in addition to the obvious code size |
| 56 * benefit), due to how [getNativeInterceptor] works. Finding the interceptor | 58 * benefit), due to how [getNativeInterceptor] works. Finding the interceptor |
| 57 * of a leaf class in the hierarchy is more efficient that a non-leaf, so it | 59 * of a leaf class in the hierarchy is more efficient that a non-leaf, so it |
| 58 * improves performance when more classes can be treated as leaves. | 60 * improves performance when more classes can be treated as leaves. |
| 59 * | 61 * |
| 60 * [classes] contains native classes, mixin applications, and user subclasses | 62 * [classes] contains native classes, mixin applications, and user subclasses |
| 61 * of native classes. | 63 * of native classes. |
| 62 */ | 64 */ |
| 63 Set<Class> prepareNativeClasses(List<Class> classes) { | 65 Set<Class> prepareNativeClasses(List<Class> classes) { |
| 64 assert(classes.every((Class cls) => cls != null)); | 66 assert(classes.every((Class cls) => cls != null)); |
| 65 | 67 |
| 66 hasNativeClasses = classes.isNotEmpty; | 68 hasNativeClasses = classes.isNotEmpty; |
| 67 | 69 |
| 68 // Compute a pre-order traversal of the subclass forest. We actually want a | 70 // Compute a pre-order traversal of the subclass forest. We actually want a |
| 69 // post-order traversal but it is easier to compute the pre-order and use it | 71 // post-order traversal but it is easier to compute the pre-order and use it |
| 70 // in reverse. | 72 // in reverse. |
| 71 List<Class> preOrder = <Class>[]; | 73 List<Class> preOrder = <Class>[]; |
| 72 Set<Class> seen = new Set<Class>(); | 74 Set<Class> seen = new Set<Class>(); |
| 73 | 75 |
| 74 Class objectClass = null; | 76 Class objectClass = null; |
| 75 Class jsInterceptorClass = null; | 77 Class jsInterceptorClass = null; |
| 78 |
| 76 void walk(Class cls) { | 79 void walk(Class cls) { |
| 77 if (cls.element == compiler.objectClass) { | 80 if (cls.element == compiler.objectClass) { |
| 78 objectClass = cls; | 81 objectClass = cls; |
| 79 return; | 82 return; |
| 80 } | 83 } |
| 81 if (cls.element == backend.jsInterceptorClass) { | 84 if (cls.element == backend.jsInterceptorClass) { |
| 82 jsInterceptorClass = cls; | 85 jsInterceptorClass = cls; |
| 83 return; | 86 return; |
| 84 } | 87 } |
| 85 if (seen.contains(cls)) return; | 88 if (seen.contains(cls)) return; |
| 86 seen.add(cls); | 89 seen.add(cls); |
| 87 walk(cls.superclass); | 90 walk(cls.superclass); |
| 88 preOrder.add(cls); | 91 preOrder.add(cls); |
| 89 } | 92 } |
| 90 classes.forEach(walk); | 93 classes.forEach(walk); |
| 91 | 94 |
| 92 // Find which classes are needed and which are non-leaf classes. Any class | 95 // Find which classes are needed and which are non-leaf classes. Any class |
| 93 // that is not needed can be treated as a leaf class equivalent to some | 96 // that is not needed can be treated as a leaf class equivalent to some |
| 94 // needed class. | 97 // needed class. |
| 95 | 98 |
| 96 Set<Class> neededClasses = new Set<Class>(); | 99 Set<Class> neededClasses = new Set<Class>(); |
| 97 Set<Class> nonleafClasses = new Set<Class>(); | 100 Set<Class> nonLeafClasses = new Set<Class>(); |
| 98 | 101 |
| 99 Map<Class, List<Class>> extensionPoints = computeExtensionPoints(preOrder); | 102 Map<Class, List<Class>> extensionPoints = computeExtensionPoints(preOrder); |
| 100 | 103 |
| 101 neededClasses.add(objectClass); | 104 neededClasses.add(objectClass); |
| 102 | 105 |
| 103 Set<ClassElement> neededByConstant = emitterTask | 106 Set<ClassElement> neededByConstant = emitterTask |
| 104 .computeInterceptorsReferencedFromConstants(); | 107 .computeInterceptorsReferencedFromConstants(); |
| 105 Set<ClassElement> modifiedClasses = emitterTask.typeTestRegistry | 108 Set<ClassElement> modifiedClasses = emitterTask.typeTestRegistry |
| 106 .computeClassesModifiedByEmitRuntimeTypeSupport(); | 109 .computeClassesModifiedByEmitRuntimeTypeSupport(); |
| 107 | 110 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 123 } else if (modifiedClasses.contains(classElement)) { | 126 } else if (modifiedClasses.contains(classElement)) { |
| 124 // TODO(9556): Remove this test when [emitRuntimeTypeSupport] no longer | 127 // TODO(9556): Remove this test when [emitRuntimeTypeSupport] no longer |
| 125 // adds information to a class prototype or constructor. | 128 // adds information to a class prototype or constructor. |
| 126 needed = true; | 129 needed = true; |
| 127 } else if (extensionPoints.containsKey(cls)) { | 130 } else if (extensionPoints.containsKey(cls)) { |
| 128 needed = true; | 131 needed = true; |
| 129 } | 132 } |
| 130 if (cls.isNative && | 133 if (cls.isNative && |
| 131 native.nativeTagsForcedNonLeaf(classElement)) { | 134 native.nativeTagsForcedNonLeaf(classElement)) { |
| 132 needed = true; | 135 needed = true; |
| 133 nonleafClasses.add(cls); | 136 nonLeafClasses.add(cls); |
| 134 } | 137 } |
| 135 | 138 |
| 136 if (needed || neededClasses.contains(cls)) { | 139 if (needed || neededClasses.contains(cls)) { |
| 137 neededClasses.add(cls); | 140 neededClasses.add(cls); |
| 138 neededClasses.add(cls.superclass); | 141 neededClasses.add(cls.superclass); |
| 139 nonleafClasses.add(cls.superclass); | 142 nonLeafClasses.add(cls.superclass); |
| 140 } | 143 } |
| 141 } | 144 } |
| 142 | 145 |
| 143 // Collect all the tags that map to each native class. | 146 // Collect all the tags that map to each native class. |
| 144 | 147 |
| 145 Map<Class, Set<String>> leafTags = new Map<Class, Set<String>>(); | 148 Map<Class, Set<String>> leafTags = new Map<Class, Set<String>>(); |
| 146 Map<Class, Set<String>> nonleafTags = new Map<Class, Set<String>>(); | 149 Map<Class, Set<String>> nonleafTags = new Map<Class, Set<String>>(); |
| 147 | 150 |
| 148 for (Class cls in classes) { | 151 for (Class cls in classes) { |
| 149 if (!cls.isNative) continue; | 152 if (!cls.isNative) continue; |
| 150 List<String> nativeTags = native.nativeTagsOfClass(cls.element); | 153 List<String> nativeTags = native.nativeTagsOfClass(cls.element); |
| 151 | 154 |
| 152 if (nonleafClasses.contains(cls) || | 155 if (nonLeafClasses.contains(cls) || |
| 153 extensionPoints.containsKey(cls)) { | 156 extensionPoints.containsKey(cls)) { |
| 154 nonleafTags | 157 nonleafTags |
| 155 .putIfAbsent(cls, () => new Set<String>()) | 158 .putIfAbsent(cls, () => new Set<String>()) |
| 156 .addAll(nativeTags); | 159 .addAll(nativeTags); |
| 157 } else { | 160 } else { |
| 158 Class sufficingInterceptor = cls; | 161 Class sufficingInterceptor = cls; |
| 159 while (!neededClasses.contains(sufficingInterceptor)) { | 162 while (!neededClasses.contains(sufficingInterceptor)) { |
| 160 sufficingInterceptor = sufficingInterceptor.superclass; | 163 sufficingInterceptor = sufficingInterceptor.superclass; |
| 161 } | 164 } |
| 162 if (sufficingInterceptor == objectClass) { | 165 if (sufficingInterceptor == objectClass) { |
| 163 sufficingInterceptor = jsInterceptorClass; | 166 sufficingInterceptor = jsInterceptorClass; |
| 164 } | 167 } |
| 165 leafTags | 168 leafTags |
| 166 .putIfAbsent(sufficingInterceptor, () => new Set<String>()) | 169 .putIfAbsent(sufficingInterceptor, () => new Set<String>()) |
| 167 .addAll(nativeTags); | 170 .addAll(nativeTags); |
| 168 } | 171 } |
| 169 } | 172 } |
| 170 | 173 |
| 174 void fillNativeInfo(Class cls) { |
| 175 assert(cls.nativeLeafTags == null && |
| 176 cls.nativeNonLeafTags == null && |
| 177 cls.nativeExtensions == null); |
| 178 if (leafTags[cls] != null) { |
| 179 cls.nativeLeafTags = leafTags[cls].toList(growable: false); |
| 180 } |
| 181 if (nonleafTags[cls] != null) { |
| 182 cls.nativeNonLeafTags = nonleafTags[cls].toList(growable: false); |
| 183 } |
| 184 cls.nativeExtensions = extensionPoints[cls]; |
| 185 } |
| 171 // Add properties containing the information needed to construct maps used | 186 // Add properties containing the information needed to construct maps used |
| 172 // by getNativeInterceptor and custom elements. | 187 // by getNativeInterceptor and custom elements. |
| 173 if (compiler.enqueuer.codegen.nativeEnqueuer | 188 if (compiler.enqueuer.codegen.nativeEnqueuer |
| 174 .hasInstantiatedNativeClasses()) { | 189 .hasInstantiatedNativeClasses()) { |
| 175 void generateClassInfo(Class cls) { | 190 fillNativeInfo(jsInterceptorClass); |
| 176 // Property has the form: | |
| 177 // | |
| 178 // "%": "leafTag1|leafTag2|...;nonleafTag1|...;Class1|Class2|...", | |
| 179 // | |
| 180 // If there is no data following a semicolon, the semicolon can be | |
| 181 // omitted. | |
| 182 | |
| 183 String formatTags(Iterable<String> tags) { | |
| 184 if (tags == null) return ''; | |
| 185 return (tags.toList()..sort()).join('|'); | |
| 186 } | |
| 187 | |
| 188 List<Class> extensions = extensionPoints[cls]; | |
| 189 | |
| 190 String leafStr = formatTags(leafTags[cls]); | |
| 191 String nonleafStr = formatTags(nonleafTags[cls]); | |
| 192 | |
| 193 StringBuffer sb = new StringBuffer(leafStr); | |
| 194 if (nonleafStr != '') { | |
| 195 sb..write(';')..write(nonleafStr); | |
| 196 } | |
| 197 | |
| 198 String encoding = sb.toString(); | |
| 199 | |
| 200 if (cls.isNative || encoding != '' || extensions != null) { | |
| 201 List<jsAst.Literal> parts = <jsAst.Literal>[js.stringPart(encoding)]; | |
| 202 if (extensions != null) { | |
| 203 parts..add(js.stringPart(';')) | |
| 204 ..addAll( | |
| 205 js.joinLiterals(extensions.map((Class cls) => cls.name), | |
| 206 js.stringPart('|'))); | |
| 207 } | |
| 208 assert(cls.nativeInfo == null); | |
| 209 cls.nativeInfo = js.concatenateStrings(parts, addQuotes: true); | |
| 210 } | |
| 211 } | |
| 212 generateClassInfo(jsInterceptorClass); | |
| 213 for (Class cls in classes) { | 191 for (Class cls in classes) { |
| 214 if (!cls.isNative || neededClasses.contains(cls)) { | 192 if (!cls.isNative || neededClasses.contains(cls)) { |
| 215 generateClassInfo(cls); | 193 fillNativeInfo(cls); |
| 216 } | 194 } |
| 217 } | 195 } |
| 218 } | 196 } |
| 219 | 197 |
| 220 // TODO(sra): Issue #13731- this is commented out as part of custom | 198 // TODO(sra): Issue #13731- this is commented out as part of custom |
| 221 // element constructor work. | 199 // element constructor work. |
| 222 // (floitsch: was run on every native class.) | 200 // (floitsch: was run on every native class.) |
| 223 //assert(!classElement.hasBackendMembers); | 201 //assert(!classElement.hasBackendMembers); |
| 224 | 202 |
| 225 return classes | 203 return classes |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 360 // satisfy a check against [element], in which case an interceptor must be | 338 // satisfy a check against [element], in which case an interceptor must be |
| 361 // used. We should also use an interceptor if the check can't be satisfied | 339 // used. We should also use an interceptor if the check can't be satisfied |
| 362 // by a native class in case we get a native instance that tries to spoof | 340 // by a native class in case we get a native instance that tries to spoof |
| 363 // the type info. i.e the criteria for whether or not to use an interceptor | 341 // the type info. i.e the criteria for whether or not to use an interceptor |
| 364 // is whether the receiver can be native, not the type of the test. | 342 // is whether the receiver can be native, not the type of the test. |
| 365 if (element == null || !element.isClass) return false; | 343 if (element == null || !element.isClass) return false; |
| 366 ClassElement cls = element; | 344 ClassElement cls = element; |
| 367 if (Elements.isNativeOrExtendsNative(cls)) return true; | 345 if (Elements.isNativeOrExtendsNative(cls)) return true; |
| 368 return isSupertypeOfNativeClass(element); | 346 return isSupertypeOfNativeClass(element); |
| 369 } | 347 } |
| 370 | 348 } |
| 371 /// Returns a JavaScript template that fills the embedded globals referenced | |
| 372 /// by [interceptorsByTagAccess] and [leafTagsAccess]. | |
| 373 /// | |
| 374 /// This code must be invoked for every class that has a native info before | |
| 375 /// the program starts. | |
| 376 /// | |
| 377 /// The [infoAccess] parameter must evaluate to an expression that contains | |
| 378 /// the info (as a JavaScript string). | |
| 379 /// | |
| 380 /// The [constructorAccess] parameter must evaluate to an expression that | |
| 381 /// contains the constructor of the class. The constructor's prototype must | |
| 382 /// be set up. | |
| 383 /// | |
| 384 /// The [subclassReadGenerator] function must evaluate to a JS expression | |
| 385 /// that returns a reference to the constructor (with evaluated prototype) | |
| 386 /// of the given JS expression. | |
| 387 /// | |
| 388 /// The [interceptorsByTagAccess] must point to the embedded global | |
| 389 /// [embeddedNames.INTERCEPTORS_BY_TAG] and must be initialized with an empty | |
| 390 /// JS Object (used as a map). | |
| 391 /// | |
| 392 /// Similarly, the [leafTagsAccess] must point to the embedded global | |
| 393 /// [embeddedNames.LEAF_TAGS] and must be initialized with an empty JS Object | |
| 394 /// (used as a map). | |
| 395 /// | |
| 396 /// Both variables are passed in (instead of creating the access here) to | |
| 397 /// make sure the caller is aware of these globals. | |
| 398 jsAst.Statement buildNativeInfoHandler( | |
| 399 jsAst.Expression infoAccess, | |
| 400 jsAst.Expression constructorAccess, | |
| 401 jsAst.Expression subclassReadGenerator(jsAst.Expression subclass), | |
| 402 jsAst.Expression interceptorsByTagAccess, | |
| 403 jsAst.Expression leafTagsAccess) { | |
| 404 jsAst.Expression subclassRead = | |
| 405 subclassReadGenerator(js('subclasses[i]', [])); | |
| 406 return js.statement(''' | |
| 407 // The native info looks like this: | |
| 408 // | |
| 409 // HtmlElement: { | |
| 410 // "%": "HTMLDivElement|HTMLAnchorElement;HTMLElement;FancyButton" | |
| 411 // | |
| 412 // The first two semicolon-separated parts contain dispatch tags, the | |
| 413 // third contains the JavaScript names for classes. | |
| 414 // | |
| 415 // The tags indicate that JavaScript objects with the dispatch tags | |
| 416 // (usually constructor names) HTMLDivElement, HTMLAnchorElement and | |
| 417 // HTMLElement all map to the Dart native class named HtmlElement. | |
| 418 // The first set is for effective leaf nodes in the hierarchy, the | |
| 419 // second set is non-leaf nodes. | |
| 420 // | |
| 421 // The third part contains the JavaScript names of Dart classes that | |
| 422 // extend the native class. Here, FancyButton extends HtmlElement, so | |
| 423 // the runtime needs to know that window.HTMLElement.prototype is the | |
| 424 // prototype that needs to be extended in creating the custom element. | |
| 425 // | |
| 426 // The information is used to build tables referenced by | |
| 427 // getNativeInterceptor and custom element support. | |
| 428 { | |
| 429 var nativeSpec = #info.split(";"); | |
| 430 if (nativeSpec[0]) { | |
| 431 var tags = nativeSpec[0].split("|"); | |
| 432 for (var i = 0; i < tags.length; i++) { | |
| 433 #interceptorsByTagAccess[tags[i]] = #constructor; | |
| 434 #leafTagsAccess[tags[i]] = true; | |
| 435 } | |
| 436 } | |
| 437 if (nativeSpec[1]) { | |
| 438 tags = nativeSpec[1].split("|"); | |
| 439 if (#allowNativesSubclassing) { | |
| 440 if (nativeSpec[2]) { | |
| 441 var subclasses = nativeSpec[2].split("|"); | |
| 442 for (var i = 0; i < subclasses.length; i++) { | |
| 443 var subclass = #subclassRead; | |
| 444 subclass.#nativeSuperclassTagName = tags[0]; | |
| 445 } | |
| 446 } | |
| 447 for (i = 0; i < tags.length; i++) { | |
| 448 #interceptorsByTagAccess[tags[i]] = #constructor; | |
| 449 #leafTagsAccess[tags[i]] = false; | |
| 450 } | |
| 451 } | |
| 452 } | |
| 453 } | |
| 454 ''', {'info': infoAccess, | |
| 455 'constructor': constructorAccess, | |
| 456 'subclassRead': subclassRead, | |
| 457 'interceptorsByTagAccess': interceptorsByTagAccess, | |
| 458 'leafTagsAccess': leafTagsAccess, | |
| 459 'nativeSuperclassTagName': embeddedNames.NATIVE_SUPERCLASS_TAG_NAME, | |
| 460 'allowNativesSubclassing': true}); | |
| 461 } | |
| 462 } | |
| OLD | NEW |