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 |