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 class NativeEmitter { | |
6 | |
7 Compiler compiler; | |
8 StringBuffer buffer; | |
9 | |
10 // Classes that participate in dynamic dispatch. These are the | |
11 // classes that contain used members. | |
12 Set<ClassElement> classesWithDynamicDispatch; | |
13 | |
14 // Native classes found in the application. | |
15 Set<ClassElement> nativeClasses; | |
16 | |
17 // Caches the direct native subtypes of a native class. | |
18 Map<ClassElement, List<ClassElement>> subtypes; | |
19 | |
20 // Caches the native methods that are overridden by a native class. | |
21 // Note that the method that overrides does not have to be native: | |
22 // it's the overridden method that must make sure it will dispatch | |
23 // to its subclass if it sees an instance whose class is a subclass. | |
24 Set<FunctionElement> overriddenMethods; | |
25 | |
26 NativeEmitter(this.compiler) | |
27 : classesWithDynamicDispatch = new Set<ClassElement>(), | |
28 nativeClasses = new Set<ClassElement>(), | |
29 subtypes = new Map<ClassElement, List<ClassElement>>(), | |
30 overriddenMethods = new Set<FunctionElement>(), | |
31 buffer = new StringBuffer(); | |
32 | |
33 String get dynamicName() { | |
34 Element element = compiler.findHelper( | |
35 const SourceString('dynamicFunction')); | |
36 return compiler.namer.isolateAccess(element); | |
37 } | |
38 | |
39 String get dynamicSetMetadataName() { | |
40 Element element = compiler.findHelper( | |
41 const SourceString('dynamicSetMetadata')); | |
42 return compiler.namer.isolateAccess(element); | |
43 } | |
44 | |
45 String get typeNameOfName() { | |
46 Element element = compiler.findHelper( | |
47 const SourceString('getTypeNameOf')); | |
48 return compiler.namer.isolateAccess(element); | |
49 } | |
50 | |
51 String get defPropName() { | |
52 Element element = compiler.findHelper( | |
53 const SourceString('defineProperty')); | |
54 return compiler.namer.isolateAccess(element); | |
55 } | |
56 | |
57 String get toStringHelperName() { | |
58 Element element = compiler.findHelper( | |
59 const SourceString('toStringForNativeObject')); | |
60 return compiler.namer.isolateAccess(element); | |
61 } | |
62 | |
63 void generateNativeLiteral(ClassElement classElement) { | |
64 String quotedNative = classElement.nativeName.slowToString(); | |
65 String nativeCode = quotedNative.substring(2, quotedNative.length - 1); | |
66 String className = compiler.namer.getName(classElement); | |
67 buffer.add(className); | |
68 buffer.add(' = '); | |
69 buffer.add(nativeCode); | |
70 buffer.add(';\n'); | |
71 | |
72 String attachTo(name) => "$className.$name"; | |
73 | |
74 for (Element member in classElement.members) { | |
75 if (member.isInstanceMember()) { | |
76 compiler.emitter.addInstanceMember( | |
77 member, attachTo, buffer, isNative: true); | |
78 } | |
79 } | |
80 } | |
81 | |
82 bool isNativeLiteral(String quotedName) { | |
83 return quotedName[1] === '='; | |
84 } | |
85 | |
86 bool isNativeGlobal(String quotedName) { | |
87 return quotedName[1] === '@'; | |
88 } | |
89 | |
90 String toNativeName(ClassElement cls) { | |
91 String quotedName = cls.nativeName.slowToString(); | |
92 if (isNativeGlobal(quotedName)) { | |
93 // Global object, just be like the other types for now. | |
94 return quotedName.substring(3, quotedName.length - 1); | |
95 } else { | |
96 return quotedName.substring(2, quotedName.length - 1); | |
97 } | |
98 } | |
99 | |
100 void generateNativeClass(ClassElement classElement) { | |
101 nativeClasses.add(classElement); | |
102 | |
103 assert(classElement.backendMembers.isEmpty()); | |
104 String quotedName = classElement.nativeName.slowToString(); | |
105 if (isNativeLiteral(quotedName)) { | |
106 generateNativeLiteral(classElement); | |
107 // The native literal kind needs to be dealt with specially when | |
108 // generating code for it. | |
109 return; | |
110 } | |
111 | |
112 String nativeName = toNativeName(classElement); | |
113 bool hasUsedSelectors = false; | |
114 | |
115 String attachTo(String name) { | |
116 hasUsedSelectors = true; | |
117 return "$dynamicName('$name').$nativeName"; | |
118 } | |
119 | |
120 for (Element member in classElement.members) { | |
121 if (member.isInstanceMember()) { | |
122 compiler.emitter.addInstanceMember( | |
123 member, attachTo, buffer, isNative: true); | |
124 } | |
125 } | |
126 | |
127 compiler.emitter.generateTypeTests(classElement, (Element other) { | |
128 assert(requiresNativeIsCheck(other)); | |
129 buffer.add('${attachTo(compiler.namer.operatorIs(other))} = '); | |
130 buffer.add('function() { return true; };\n'); | |
131 }); | |
132 | |
133 if (hasUsedSelectors) classesWithDynamicDispatch.add(classElement); | |
134 } | |
135 | |
136 List<ClassElement> getDirectSubclasses(ClassElement cls) { | |
137 List<ClassElement> result = subtypes[cls]; | |
138 if (result === null) result = const<ClassElement>[]; | |
139 return result; | |
140 } | |
141 | |
142 void emitParameterStub(Element member, | |
143 String invocationName, | |
144 String stubParameters, | |
145 List<String> argumentsBuffer, | |
146 int indexOfLastOptionalArgumentInParameters) { | |
147 // The target JS function may check arguments.length so we need to | |
148 // make sure not to pass any unspecified optional arguments to it. | |
149 // For example, for the following Dart method: | |
150 // foo([x, y, z]); | |
151 // The call: | |
152 // foo(y: 1) | |
153 // must be turned into a JS call to: | |
154 // foo(null, y). | |
155 | |
156 List<String> nativeArgumentsBuffer = argumentsBuffer.getRange( | |
157 0, indexOfLastOptionalArgumentInParameters + 1); | |
158 | |
159 ClassElement classElement = member.enclosingElement; | |
160 String nativeName = classElement.nativeName.slowToString(); | |
161 String nativeArguments = Strings.join(nativeArgumentsBuffer, ","); | |
162 | |
163 if (isNativeLiteral(nativeName) || !overriddenMethods.contains(member)) { | |
164 // Call the method directly. | |
165 buffer.add(' return this.${member.name.slowToString()}'); | |
166 buffer.add('($nativeArguments)'); | |
167 return; | |
168 } | |
169 | |
170 // If the method is overridden, we must check if the prototype of | |
171 // 'this' has the method available. Otherwise, we may end up | |
172 // calling the method from the super class. If the method is not | |
173 // available, we make a direct call to | |
174 // Object.prototype.$invocationName. This method will patch the | |
175 // prototype of 'this' to the real method. | |
176 | |
177 buffer.add(' if (Object.getPrototypeOf(this).hasOwnProperty('); | |
178 buffer.add("'$invocationName')) {\n"); | |
179 buffer.add(' return this.${member.name.slowToString()}'); | |
180 buffer.add('($nativeArguments)'); | |
181 buffer.add('\n }\n'); | |
182 buffer.add(' return Object.prototype.$invocationName.call(this'); | |
183 buffer.add(stubParameters == '' ? '' : ', $stubParameters'); | |
184 buffer.add(');'); | |
185 } | |
186 | |
187 void emitDynamicDispatchMetadata() { | |
188 if (classesWithDynamicDispatch.isEmpty()) return; | |
189 buffer.add('// ${classesWithDynamicDispatch.length} dynamic classes.\n'); | |
190 | |
191 // Build a pre-order traversal over all the classes and their subclasses. | |
192 Set<ClassElement> seen = new Set<ClassElement>(); | |
193 List<ClassElement> classes = <ClassElement>[]; | |
194 void visit(ClassElement cls) { | |
195 if (seen.contains(cls)) return; | |
196 seen.add(cls); | |
197 for (final ClassElement subclass in getDirectSubclasses(cls)) { | |
198 visit(subclass); | |
199 } | |
200 classes.add(cls); | |
201 } | |
202 for (final ClassElement cls in classesWithDynamicDispatch) { | |
203 visit(cls); | |
204 } | |
205 | |
206 Collection<ClassElement> dispatchClasses = classes.filter( | |
207 (cls) => !getDirectSubclasses(cls).isEmpty() && | |
208 classesWithDynamicDispatch.contains(cls)); | |
209 | |
210 buffer.add('// ${classes.length} classes\n'); | |
211 Collection<ClassElement> classesThatHaveSubclasses = classes.filter( | |
212 (ClassElement t) => !getDirectSubclasses(t).isEmpty()); | |
213 buffer.add('// ${classesThatHaveSubclasses.length} !leaf\n'); | |
214 | |
215 // Generate code that builds the map from cls tags used in dynamic dispatch | |
216 // to the set of cls tags of classes that extend (TODO: or implement) those | |
217 // classes. The set is represented as a string of tags joined with '|'. | |
218 // This is easily split into an array of tags, or converted into a regexp. | |
219 // | |
220 // To reduce the size of the sets, subsets are CSE-ed out into variables. | |
221 // The sets could be much smaller if we could make assumptions about the | |
222 // cls tags of other classes (which are constructor names or part of the | |
223 // result of Object.protocls.toString). For example, if objects that are | |
224 // Dart objects could be easily excluded, then we might be able to simplify | |
225 // the test, replacing dozens of HTMLxxxElement classes with the regexp | |
226 // /HTML.*Element/. | |
227 | |
228 // Temporary variables for common substrings. | |
229 List<String> varNames = <String>[]; | |
230 // var -> expression | |
231 Map<String, String> varDefns = <String>{}; | |
232 // tag -> expression (a string or a variable) | |
233 Map<ClassElement, String> tagDefns = new Map<ClassElement, String>(); | |
234 | |
235 String makeExpression(ClassElement cls) { | |
236 // Expression fragments for this set of cls keys. | |
237 List<String> expressions = <String>[]; | |
238 // TODO: Remove if cls is abstract. | |
239 List<String> subtags = [toNativeName(cls)]; | |
240 void walk(ClassElement cls) { | |
241 for (final ClassElement subclass in getDirectSubclasses(cls)) { | |
242 ClassElement tag = subclass; | |
243 String existing = tagDefns[tag]; | |
244 if (existing == null) { | |
245 subtags.add(toNativeName(tag)); | |
246 walk(subclass); | |
247 } else { | |
248 if (varDefns.containsKey(existing)) { | |
249 expressions.add(existing); | |
250 } else { | |
251 String varName = 'v${varNames.length}/*${tag}*/'; | |
252 varNames.add(varName); | |
253 varDefns[varName] = existing; | |
254 tagDefns[tag] = varName; | |
255 expressions.add(varName); | |
256 } | |
257 } | |
258 } | |
259 } | |
260 walk(cls); | |
261 String constantPart = "'${Strings.join(subtags, '|')}'"; | |
262 if (constantPart != "''") expressions.add(constantPart); | |
263 String expression; | |
264 if (expressions.length == 1) { | |
265 expression = expressions[0]; | |
266 } else { | |
267 expression = "[${Strings.join(expressions, ',')}].join('|')"; | |
268 } | |
269 return expression; | |
270 } | |
271 | |
272 for (final ClassElement cls in dispatchClasses) { | |
273 tagDefns[cls] = makeExpression(cls); | |
274 } | |
275 | |
276 // Write out a thunk that builds the metadata. | |
277 | |
278 if (!tagDefns.isEmpty()) { | |
279 buffer.add('(function(){\n'); | |
280 | |
281 for (final String varName in varNames) { | |
282 buffer.add(' var ${varName} = ${varDefns[varName]};\n'); | |
283 } | |
284 | |
285 buffer.add(' var table = [\n'); | |
286 buffer.add( | |
287 ' // [dynamic-dispatch-tag, ' | |
288 'tags of classes implementing dynamic-dispatch-tag]'); | |
289 bool needsComma = false; | |
290 List<String> entries = <String>[]; | |
291 for (final ClassElement cls in dispatchClasses) { | |
292 String clsName = toNativeName(cls); | |
293 entries.add("\n ['$clsName', ${tagDefns[cls]}]"); | |
294 } | |
295 buffer.add(Strings.join(entries, ',')); | |
296 buffer.add('];\n'); | |
297 buffer.add('$dynamicSetMetadataName(table);\n'); | |
298 | |
299 buffer.add('})();\n'); | |
300 } | |
301 } | |
302 | |
303 bool isSupertypeOfNativeClass(Element element) { | |
304 if (element.isTypeVariable()) { | |
305 compiler.cancel("Is check for type variable", element: work.element); | |
306 return false; | |
307 } | |
308 if (element.computeType(compiler) is FunctionType) return false; | |
309 | |
310 if (!element.isClass()) { | |
311 compiler.cancel("Is check does not handle element", element: element); | |
312 return false; | |
313 } | |
314 | |
315 return subtypes[element] !== null; | |
316 } | |
317 | |
318 bool requiresNativeIsCheck(Element element) { | |
319 if (!element.isClass()) return false; | |
320 ClassElement cls = element; | |
321 if (cls.isNative()) return true; | |
322 return isSupertypeOfNativeClass(element); | |
323 } | |
324 | |
325 void emitIsChecks(StringBuffer buffer) { | |
326 for (Element type in compiler.universe.isChecks) { | |
327 if (!requiresNativeIsCheck(type)) continue; | |
328 String name = compiler.namer.operatorIs(type); | |
329 buffer.add("$defPropName(Object.prototype, '$name', "); | |
330 buffer.add('function() { return false; });\n'); | |
331 } | |
332 } | |
333 | |
334 void assembleCode(StringBuffer other) { | |
335 if (nativeClasses.isEmpty()) return; | |
336 | |
337 // Because of native classes, we have to generate some is checks | |
338 // by calling a method, instead of accessing a property. So we | |
339 // attach to the JS Object prototype these methods that return | |
340 // false, and will be overridden by subclasses when they have to | |
341 // return true. | |
342 StringBuffer objectProperties = new StringBuffer(); | |
343 emitIsChecks(objectProperties); | |
344 | |
345 // In order to have the toString method on every native class, | |
346 // we must patch the JS Object prototype with a helper method. | |
347 String toStringName = compiler.namer.instanceMethodName( | |
348 null, const SourceString('toString'), 0); | |
349 objectProperties.add("$defPropName(Object.prototype, '$toStringName', "); | |
350 objectProperties.add( | |
351 'function() { return $toStringHelperName(this); });\n'); | |
352 | |
353 // Finally, emit the code in the main buffer. | |
354 other.add('(function() {\n$objectProperties$buffer\n})();\n'); | |
355 } | |
356 } | |
OLD | NEW |