OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, 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 dart2js.js_emitter; | |
6 | |
7 class InterceptorStubGenerator { | |
8 final Compiler compiler; | |
9 final Namer namer; | |
10 final JavaScriptBackend backend; | |
11 | |
12 InterceptorStubGenerator(this.compiler, this.namer, this.backend); | |
13 | |
14 jsAst.Expression generateGetInterceptorMethod(Set<ClassElement> classes) { | |
15 jsAst.Expression interceptorFor(ClassElement cls) { | |
16 return js('#.prototype', namer.elementAccess(cls)); | |
17 } | |
18 | |
19 /** | |
20 * Build a JavaScrit AST node for doing a type check on | |
21 * [cls]. [cls] must be a non-native interceptor class. | |
22 */ | |
23 jsAst.Statement buildInterceptorCheck(ClassElement cls) { | |
24 jsAst.Expression condition; | |
25 assert(backend.isInterceptorClass(cls)); | |
26 if (cls == backend.jsBoolClass) { | |
27 condition = js('(typeof receiver) == "boolean"'); | |
28 } else if (cls == backend.jsIntClass || | |
29 cls == backend.jsDoubleClass || | |
30 cls == backend.jsNumberClass) { | |
31 throw 'internal error'; | |
32 } else if (cls == backend.jsArrayClass || | |
33 cls == backend.jsMutableArrayClass || | |
34 cls == backend.jsFixedArrayClass || | |
35 cls == backend.jsExtendableArrayClass) { | |
36 condition = js('receiver.constructor == Array'); | |
37 } else if (cls == backend.jsStringClass) { | |
38 condition = js('(typeof receiver) == "string"'); | |
39 } else if (cls == backend.jsNullClass) { | |
40 condition = js('receiver == null'); | |
41 } else { | |
42 throw 'internal error'; | |
43 } | |
44 return js.statement('if (#) return #', [condition, interceptorFor(cls)]); | |
45 } | |
46 | |
47 bool hasArray = false; | |
48 bool hasBool = false; | |
49 bool hasDouble = false; | |
50 bool hasInt = false; | |
51 bool hasNull = false; | |
52 bool hasNumber = false; | |
53 bool hasString = false; | |
54 bool hasNative = false; | |
55 bool anyNativeClasses = compiler.enqueuer.codegen.nativeEnqueuer | |
56 .hasInstantiatedNativeClasses(); | |
57 | |
58 for (ClassElement cls in classes) { | |
59 if (cls == backend.jsArrayClass || | |
60 cls == backend.jsMutableArrayClass || | |
61 cls == backend.jsFixedArrayClass || | |
62 cls == backend.jsExtendableArrayClass) hasArray = true; | |
63 else if (cls == backend.jsBoolClass) hasBool = true; | |
64 else if (cls == backend.jsDoubleClass) hasDouble = true; | |
65 else if (cls == backend.jsIntClass) hasInt = true; | |
66 else if (cls == backend.jsNullClass) hasNull = true; | |
67 else if (cls == backend.jsNumberClass) hasNumber = true; | |
68 else if (cls == backend.jsStringClass) hasString = true; | |
69 else { | |
70 // The set of classes includes classes mixed-in to interceptor classes | |
71 // and user extensions of native classes. | |
72 // | |
73 // The set of classes also includes the 'primitive' interceptor | |
74 // PlainJavaScriptObject even when it has not been resolved, since it is | |
75 // only resolved through the reference in getNativeInterceptor when | |
76 // getNativeInterceptor is marked as used. Guard against probing | |
77 // unresolved PlainJavaScriptObject by testing for anyNativeClasses. | |
78 | |
79 if (anyNativeClasses) { | |
80 if (Elements.isNativeOrExtendsNative(cls)) hasNative = true; | |
81 } | |
82 } | |
83 } | |
84 if (hasDouble) { | |
85 hasNumber = true; | |
86 } | |
87 if (hasInt) hasNumber = true; | |
88 | |
89 if (classes.containsAll(backend.interceptedClasses)) { | |
90 // I.e. this is the general interceptor. | |
91 hasNative = anyNativeClasses; | |
92 } | |
93 | |
94 List<jsAst.Statement> statements = <jsAst.Statement>[]; | |
95 | |
96 if (hasNumber) { | |
97 jsAst.Statement whenNumber; | |
98 | |
99 /// Note: there are two number classes in play: Dart's [num], | |
100 /// and JavaScript's Number (typeof receiver == 'number'). This | |
101 /// is the fallback used when we have determined that receiver | |
102 /// is a JavaScript Number. | |
103 jsAst.Expression interceptorForNumber = interceptorFor( | |
104 hasDouble ? backend.jsDoubleClass : backend.jsNumberClass); | |
105 | |
106 if (hasInt) { | |
107 whenNumber = js.statement('''{ | |
108 if (Math.floor(receiver) == receiver) return #; | |
109 return #; | |
110 }''', [interceptorFor(backend.jsIntClass), interceptorForNumber]); | |
111 } else { | |
112 whenNumber = js.statement('return #', interceptorForNumber); | |
113 } | |
114 statements.add( | |
115 js.statement('if (typeof receiver == "number") #;', whenNumber)); | |
116 } | |
117 | |
118 if (hasString) { | |
119 statements.add(buildInterceptorCheck(backend.jsStringClass)); | |
120 } | |
121 if (hasNull) { | |
122 statements.add(buildInterceptorCheck(backend.jsNullClass)); | |
123 } else { | |
124 // Returning "undefined" or "null" here will provoke a JavaScript | |
125 // TypeError which is later identified as a null-error by | |
126 // [unwrapException] in js_helper.dart. | |
127 statements.add( | |
128 js.statement('if (receiver == null) return receiver')); | |
129 } | |
130 if (hasBool) { | |
131 statements.add(buildInterceptorCheck(backend.jsBoolClass)); | |
132 } | |
133 // TODO(ahe): It might be faster to check for Array before | |
134 // function and bool. | |
135 if (hasArray) { | |
136 statements.add(buildInterceptorCheck(backend.jsArrayClass)); | |
137 } | |
138 | |
139 if (hasNative) { | |
140 statements.add(js.statement(r'''{ | |
141 if (typeof receiver != "object") return receiver; | |
142 if (receiver instanceof #) return receiver; | |
143 return #(receiver); | |
144 }''', [ | |
145 namer.elementAccess(compiler.objectClass), | |
146 namer.elementAccess(backend.getNativeInterceptorMethod)])); | |
147 | |
148 } else { | |
149 ClassElement jsUnknown = backend.jsUnknownJavaScriptObjectClass; | |
150 if (compiler.codegenWorld | |
151 .directlyInstantiatedClasses.contains(jsUnknown)) { | |
152 statements.add( | |
153 js.statement('if (!(receiver instanceof #)) return #;', | |
154 [namer.elementAccess(compiler.objectClass), | |
155 interceptorFor(jsUnknown)])); | |
156 } | |
157 | |
158 statements.add(js.statement('return receiver')); | |
159 } | |
160 | |
161 return js('''function(receiver) { #; }''', new jsAst.Block(statements)); | |
162 } | |
163 | |
164 // Returns a statement that takes care of performance critical | |
165 // common case for a one-shot interceptor, or null if there is no | |
166 // fast path. | |
167 jsAst.Statement _fastPathForOneShotInterceptor(Selector selector, | |
168 Set<ClassElement> classes) { | |
169 | |
170 if (selector.isOperator) { | |
171 String name = selector.name; | |
172 if (name == '==') { | |
173 return js.statement('''{ | |
174 if (receiver == null) return a0 == null; | |
175 if (typeof receiver != "object") | |
176 return a0 != null && receiver === a0; | |
177 }'''); | |
178 } | |
179 if (!classes.contains(backend.jsIntClass) | |
180 && !classes.contains(backend.jsNumberClass) | |
181 && !classes.contains(backend.jsDoubleClass)) { | |
182 return null; | |
183 } | |
184 if (selector.argumentCount == 1) { | |
185 // The following operators do not map to a JavaScript operator. | |
186 if (name == '~/' || name == '<<' || name == '%' || name == '>>') { | |
187 return null; | |
188 } | |
189 jsAst.Expression result = js('receiver $name a0'); | |
190 if (name == '&' || name == '|' || name == '^') { | |
191 result = js('# >>> 0', result); | |
192 } | |
193 return js.statement( | |
194 'if (typeof receiver == "number" && typeof a0 == "number")' | |
195 ' return #;', | |
196 result); | |
197 } else if (name == 'unary-') { | |
198 return js.statement( | |
199 'if (typeof receiver == "number") return -receiver'); | |
200 } else { | |
201 assert(name == '~'); | |
202 return js.statement(''' | |
203 if (typeof receiver == "number" && Math.floor(receiver) == receiver) | |
204 return (~receiver) >>> 0; | |
205 '''); | |
206 } | |
207 } else if (selector.isIndex || selector.isIndexSet) { | |
208 // For an index operation, this code generates: | |
209 // | |
210 // if (receiver.constructor == Array || typeof receiver == "string") { | |
211 // if (a0 >>> 0 === a0 && a0 < receiver.length) { | |
212 // return receiver[a0]; | |
213 // } | |
214 // } | |
215 // | |
216 // For an index set operation, this code generates: | |
217 // | |
218 // if (receiver.constructor == Array && !receiver.immutable$list) { | |
219 // if (a0 >>> 0 === a0 && a0 < receiver.length) { | |
220 // return receiver[a0] = a1; | |
221 // } | |
222 // } | |
223 bool containsArray = classes.contains(backend.jsArrayClass); | |
224 bool containsString = classes.contains(backend.jsStringClass); | |
225 bool containsJsIndexable = | |
226 backend.jsIndexingBehaviorInterface.isResolved && classes.any((cls) { | |
227 return compiler.world.isSubtypeOf(cls, | |
228 backend.jsIndexingBehaviorInterface); | |
229 }); | |
230 // The index set operator requires a check on its set value in | |
231 // checked mode, so we don't optimize the interceptor if the | |
232 // compiler has type assertions enabled. | |
233 if (selector.isIndexSet | |
234 && (compiler.enableTypeAssertions || !containsArray)) { | |
235 return null; | |
236 } | |
237 if (!containsArray && !containsString) { | |
238 return null; | |
239 } | |
240 jsAst.Expression arrayCheck = js('receiver.constructor == Array'); | |
241 jsAst.Expression indexableCheck = | |
242 backend.generateIsJsIndexableCall(js('receiver'), js('receiver')); | |
243 | |
244 jsAst.Expression orExp(left, right) { | |
245 return left == null ? right : js('# || #', [left, right]); | |
246 } | |
247 | |
248 if (selector.isIndex) { | |
249 jsAst.Expression typeCheck; | |
250 if (containsArray) { | |
251 typeCheck = arrayCheck; | |
252 } | |
253 | |
254 if (containsString) { | |
255 typeCheck = orExp(typeCheck, js('typeof receiver == "string"')); | |
256 } | |
257 | |
258 if (containsJsIndexable) { | |
259 typeCheck = orExp(typeCheck, indexableCheck); | |
260 } | |
261 | |
262 return js.statement(''' | |
263 if (#) | |
264 if ((a0 >>> 0) === a0 && a0 < receiver.length) | |
265 return receiver[a0]; | |
266 ''', typeCheck); | |
267 } else { | |
268 jsAst.Expression typeCheck; | |
269 if (containsArray) { | |
270 typeCheck = arrayCheck; | |
271 } | |
272 | |
273 if (containsJsIndexable) { | |
274 typeCheck = orExp(typeCheck, indexableCheck); | |
275 } | |
276 | |
277 return js.statement(r''' | |
278 if (# && !receiver.immutable$list && | |
279 (a0 >>> 0) === a0 && a0 < receiver.length) | |
280 return receiver[a0] = a1; | |
281 ''', typeCheck); | |
282 } | |
283 } | |
284 return null; | |
285 } | |
286 | |
287 jsAst.Expression generateOneShotInterceptor(String name) { | |
288 Selector selector = backend.oneShotInterceptors[name]; | |
289 Set<ClassElement> classes = | |
290 backend.getInterceptedClassesOn(selector.name); | |
291 String getInterceptorName = | |
292 namer.getInterceptorName(backend.getInterceptorMethod, classes); | |
293 | |
294 List<String> parameterNames = <String>[]; | |
295 parameterNames.add('receiver'); | |
296 | |
297 if (selector.isSetter) { | |
298 parameterNames.add('value'); | |
299 } else { | |
300 for (int i = 0; i < selector.argumentCount; i++) { | |
301 parameterNames.add('a$i'); | |
302 } | |
303 } | |
304 | |
305 String invocationName = backend.namer.invocationName(selector); | |
306 String globalObject = namer.globalObjectFor(backend.interceptorsLibrary); | |
307 | |
308 jsAst.Statement optimizedPath = | |
309 _fastPathForOneShotInterceptor(selector, classes); | |
310 if (optimizedPath == null) optimizedPath = js.statement(';'); | |
311 | |
312 return js( | |
313 'function(#) { #; return #.#(receiver).#(#) }', | |
314 [parameterNames, | |
315 optimizedPath, | |
316 globalObject, getInterceptorName, invocationName, parameterNames]); | |
317 } | |
318 } | |
OLD | NEW |