OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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 InterceptorStubGenerator { | 7 class InterceptorStubGenerator { |
8 final Compiler compiler; | 8 final Compiler compiler; |
9 final Namer namer; | 9 final Namer namer; |
10 final JavaScriptBackend backend; | 10 final JavaScriptBackend backend; |
11 | 11 |
12 InterceptorStubGenerator(this.compiler, this.namer, this.backend); | 12 InterceptorStubGenerator(this.compiler, this.namer, this.backend); |
13 | 13 |
14 Emitter get emitter => backend.emitter.emitter; | 14 Emitter get emitter => backend.emitter.emitter; |
15 | 15 |
| 16 BackendHelpers get helpers => backend.helpers; |
| 17 |
16 jsAst.Expression generateGetInterceptorMethod(Set<ClassElement> classes) { | 18 jsAst.Expression generateGetInterceptorMethod(Set<ClassElement> classes) { |
17 jsAst.Expression interceptorFor(ClassElement cls) { | 19 jsAst.Expression interceptorFor(ClassElement cls) { |
18 return backend.emitter.interceptorPrototypeAccess(cls); | 20 return backend.emitter.interceptorPrototypeAccess(cls); |
19 } | 21 } |
20 | 22 |
21 /** | 23 /** |
22 * Build a JavaScrit AST node for doing a type check on | 24 * Build a JavaScrit AST node for doing a type check on |
23 * [cls]. [cls] must be a non-native interceptor class. | 25 * [cls]. [cls] must be a non-native interceptor class. |
24 */ | 26 */ |
25 jsAst.Statement buildInterceptorCheck(ClassElement cls) { | 27 jsAst.Statement buildInterceptorCheck(ClassElement cls) { |
26 jsAst.Expression condition; | 28 jsAst.Expression condition; |
27 assert(backend.isInterceptorClass(cls)); | 29 assert(backend.isInterceptorClass(cls)); |
28 if (cls == backend.jsBoolClass) { | 30 if (cls == helpers.jsBoolClass) { |
29 condition = js('(typeof receiver) == "boolean"'); | 31 condition = js('(typeof receiver) == "boolean"'); |
30 } else if (cls == backend.jsIntClass || | 32 } else if (cls == helpers.jsIntClass || |
31 cls == backend.jsDoubleClass || | 33 cls == helpers.jsDoubleClass || |
32 cls == backend.jsNumberClass) { | 34 cls == helpers.jsNumberClass) { |
33 throw 'internal error'; | 35 throw 'internal error'; |
34 } else if (cls == backend.jsArrayClass || | 36 } else if (cls == helpers.jsArrayClass || |
35 cls == backend.jsMutableArrayClass || | 37 cls == helpers.jsMutableArrayClass || |
36 cls == backend.jsFixedArrayClass || | 38 cls == helpers.jsFixedArrayClass || |
37 cls == backend.jsExtendableArrayClass) { | 39 cls == helpers.jsExtendableArrayClass) { |
38 condition = js('receiver.constructor == Array'); | 40 condition = js('receiver.constructor == Array'); |
39 } else if (cls == backend.jsStringClass) { | 41 } else if (cls == helpers.jsStringClass) { |
40 condition = js('(typeof receiver) == "string"'); | 42 condition = js('(typeof receiver) == "string"'); |
41 } else if (cls == backend.jsNullClass) { | 43 } else if (cls == helpers.jsNullClass) { |
42 condition = js('receiver == null'); | 44 condition = js('receiver == null'); |
43 } else { | 45 } else { |
44 throw 'internal error'; | 46 throw 'internal error'; |
45 } | 47 } |
46 return js.statement('if (#) return #', [condition, interceptorFor(cls)]); | 48 return js.statement('if (#) return #', [condition, interceptorFor(cls)]); |
47 } | 49 } |
48 | 50 |
49 bool hasArray = false; | 51 bool hasArray = false; |
50 bool hasBool = false; | 52 bool hasBool = false; |
51 bool hasDouble = false; | 53 bool hasDouble = false; |
52 bool hasInt = false; | 54 bool hasInt = false; |
53 bool hasNull = false; | 55 bool hasNull = false; |
54 bool hasNumber = false; | 56 bool hasNumber = false; |
55 bool hasString = false; | 57 bool hasString = false; |
56 bool hasNative = false; | 58 bool hasNative = false; |
57 bool anyNativeClasses = compiler.enqueuer.codegen.nativeEnqueuer | 59 bool anyNativeClasses = compiler.enqueuer.codegen.nativeEnqueuer |
58 .hasInstantiatedNativeClasses(); | 60 .hasInstantiatedNativeClasses(); |
59 | 61 |
60 for (ClassElement cls in classes) { | 62 for (ClassElement cls in classes) { |
61 if (cls == backend.jsArrayClass || | 63 if (cls == helpers.jsArrayClass || |
62 cls == backend.jsMutableArrayClass || | 64 cls == helpers.jsMutableArrayClass || |
63 cls == backend.jsFixedArrayClass || | 65 cls == helpers.jsFixedArrayClass || |
64 cls == backend.jsExtendableArrayClass) hasArray = true; | 66 cls == helpers.jsExtendableArrayClass) hasArray = true; |
65 else if (cls == backend.jsBoolClass) hasBool = true; | 67 else if (cls == helpers.jsBoolClass) hasBool = true; |
66 else if (cls == backend.jsDoubleClass) hasDouble = true; | 68 else if (cls == helpers.jsDoubleClass) hasDouble = true; |
67 else if (cls == backend.jsIntClass) hasInt = true; | 69 else if (cls == helpers.jsIntClass) hasInt = true; |
68 else if (cls == backend.jsNullClass) hasNull = true; | 70 else if (cls == helpers.jsNullClass) hasNull = true; |
69 else if (cls == backend.jsNumberClass) hasNumber = true; | 71 else if (cls == helpers.jsNumberClass) hasNumber = true; |
70 else if (cls == backend.jsStringClass) hasString = true; | 72 else if (cls == helpers.jsStringClass) hasString = true; |
71 else { | 73 else { |
72 // The set of classes includes classes mixed-in to interceptor classes | 74 // The set of classes includes classes mixed-in to interceptor classes |
73 // and user extensions of native classes. | 75 // and user extensions of native classes. |
74 // | 76 // |
75 // The set of classes also includes the 'primitive' interceptor | 77 // The set of classes also includes the 'primitive' interceptor |
76 // PlainJavaScriptObject even when it has not been resolved, since it is | 78 // PlainJavaScriptObject even when it has not been resolved, since it is |
77 // only resolved through the reference in getNativeInterceptor when | 79 // only resolved through the reference in getNativeInterceptor when |
78 // getNativeInterceptor is marked as used. Guard against probing | 80 // getNativeInterceptor is marked as used. Guard against probing |
79 // unresolved PlainJavaScriptObject by testing for anyNativeClasses. | 81 // unresolved PlainJavaScriptObject by testing for anyNativeClasses. |
80 | 82 |
(...skipping 15 matching lines...) Expand all Loading... |
96 List<jsAst.Statement> statements = <jsAst.Statement>[]; | 98 List<jsAst.Statement> statements = <jsAst.Statement>[]; |
97 | 99 |
98 if (hasNumber) { | 100 if (hasNumber) { |
99 jsAst.Statement whenNumber; | 101 jsAst.Statement whenNumber; |
100 | 102 |
101 /// Note: there are two number classes in play: Dart's [num], | 103 /// Note: there are two number classes in play: Dart's [num], |
102 /// and JavaScript's Number (typeof receiver == 'number'). This | 104 /// and JavaScript's Number (typeof receiver == 'number'). This |
103 /// is the fallback used when we have determined that receiver | 105 /// is the fallback used when we have determined that receiver |
104 /// is a JavaScript Number. | 106 /// is a JavaScript Number. |
105 jsAst.Expression interceptorForNumber = interceptorFor( | 107 jsAst.Expression interceptorForNumber = interceptorFor( |
106 hasDouble ? backend.jsDoubleClass : backend.jsNumberClass); | 108 hasDouble ? helpers.jsDoubleClass : helpers.jsNumberClass); |
107 | 109 |
108 if (hasInt) { | 110 if (hasInt) { |
109 whenNumber = js.statement('''{ | 111 whenNumber = js.statement('''{ |
110 if (Math.floor(receiver) == receiver) return #; | 112 if (Math.floor(receiver) == receiver) return #; |
111 return #; | 113 return #; |
112 }''', [interceptorFor(backend.jsIntClass), interceptorForNumber]); | 114 }''', [interceptorFor(helpers.jsIntClass), interceptorForNumber]); |
113 } else { | 115 } else { |
114 whenNumber = js.statement('return #', interceptorForNumber); | 116 whenNumber = js.statement('return #', interceptorForNumber); |
115 } | 117 } |
116 statements.add( | 118 statements.add( |
117 js.statement('if (typeof receiver == "number") #;', whenNumber)); | 119 js.statement('if (typeof receiver == "number") #;', whenNumber)); |
118 } | 120 } |
119 | 121 |
120 if (hasString) { | 122 if (hasString) { |
121 statements.add(buildInterceptorCheck(backend.jsStringClass)); | 123 statements.add(buildInterceptorCheck(helpers.jsStringClass)); |
122 } | 124 } |
123 if (hasNull) { | 125 if (hasNull) { |
124 statements.add(buildInterceptorCheck(backend.jsNullClass)); | 126 statements.add(buildInterceptorCheck(helpers.jsNullClass)); |
125 } else { | 127 } else { |
126 // Returning "undefined" or "null" here will provoke a JavaScript | 128 // Returning "undefined" or "null" here will provoke a JavaScript |
127 // TypeError which is later identified as a null-error by | 129 // TypeError which is later identified as a null-error by |
128 // [unwrapException] in js_helper.dart. | 130 // [unwrapException] in js_helper.dart. |
129 statements.add( | 131 statements.add( |
130 js.statement('if (receiver == null) return receiver')); | 132 js.statement('if (receiver == null) return receiver')); |
131 } | 133 } |
132 if (hasBool) { | 134 if (hasBool) { |
133 statements.add(buildInterceptorCheck(backend.jsBoolClass)); | 135 statements.add(buildInterceptorCheck(helpers.jsBoolClass)); |
134 } | 136 } |
135 // TODO(ahe): It might be faster to check for Array before | 137 // TODO(ahe): It might be faster to check for Array before |
136 // function and bool. | 138 // function and bool. |
137 if (hasArray) { | 139 if (hasArray) { |
138 statements.add(buildInterceptorCheck(backend.jsArrayClass)); | 140 statements.add(buildInterceptorCheck(helpers.jsArrayClass)); |
139 } | 141 } |
140 | 142 |
141 if (hasNative) { | 143 if (hasNative) { |
142 statements.add(js.statement(r'''{ | 144 statements.add(js.statement(r'''{ |
143 if (typeof receiver != "object") { | 145 if (typeof receiver != "object") { |
144 if (typeof receiver == "function" ) return #; | 146 if (typeof receiver == "function" ) return #; |
145 return receiver; | 147 return receiver; |
146 } | 148 } |
147 if (receiver instanceof #) return receiver; | 149 if (receiver instanceof #) return receiver; |
148 return #(receiver); | 150 return #(receiver); |
149 }''', [ | 151 }''', [ |
150 interceptorFor(backend.jsJavaScriptFunctionClass), | 152 interceptorFor(helpers.jsJavaScriptFunctionClass), |
151 backend.emitter.constructorAccess(compiler.coreClasses.objectClass), | 153 backend.emitter.constructorAccess(compiler.coreClasses.objectClass), |
152 backend.emitter | 154 backend.emitter |
153 .staticFunctionAccess(backend.getNativeInterceptorMethod)])); | 155 .staticFunctionAccess(helpers.getNativeInterceptorMethod)])); |
154 | 156 |
155 } else { | 157 } else { |
156 ClassElement jsUnknown = backend.jsUnknownJavaScriptObjectClass; | 158 ClassElement jsUnknown = helpers.jsUnknownJavaScriptObjectClass; |
157 if (compiler.codegenWorld | 159 if (compiler.codegenWorld |
158 .directlyInstantiatedClasses.contains(jsUnknown)) { | 160 .directlyInstantiatedClasses.contains(jsUnknown)) { |
159 statements.add( | 161 statements.add( |
160 js.statement('if (!(receiver instanceof #)) return #;', | 162 js.statement('if (!(receiver instanceof #)) return #;', |
161 [backend.emitter.constructorAccess( | 163 [backend.emitter.constructorAccess( |
162 compiler.coreClasses.objectClass), | 164 compiler.coreClasses.objectClass), |
163 interceptorFor(jsUnknown)])); | 165 interceptorFor(jsUnknown)])); |
164 } | 166 } |
165 | 167 |
166 statements.add(js.statement('return receiver')); | 168 statements.add(js.statement('return receiver')); |
(...skipping 10 matching lines...) Expand all Loading... |
177 | 179 |
178 if (selector.isOperator) { | 180 if (selector.isOperator) { |
179 String name = selector.name; | 181 String name = selector.name; |
180 if (name == '==') { | 182 if (name == '==') { |
181 return js.statement('''{ | 183 return js.statement('''{ |
182 if (receiver == null) return a0 == null; | 184 if (receiver == null) return a0 == null; |
183 if (typeof receiver != "object") | 185 if (typeof receiver != "object") |
184 return a0 != null && receiver === a0; | 186 return a0 != null && receiver === a0; |
185 }'''); | 187 }'''); |
186 } | 188 } |
187 if (!classes.contains(backend.jsIntClass) | 189 if (!classes.contains(helpers.jsIntClass) |
188 && !classes.contains(backend.jsNumberClass) | 190 && !classes.contains(helpers.jsNumberClass) |
189 && !classes.contains(backend.jsDoubleClass)) { | 191 && !classes.contains(helpers.jsDoubleClass)) { |
190 return null; | 192 return null; |
191 } | 193 } |
192 if (selector.argumentCount == 1) { | 194 if (selector.argumentCount == 1) { |
193 // The following operators do not map to a JavaScript operator. | 195 // The following operators do not map to a JavaScript operator. |
194 if (name == '~/' || name == '<<' || name == '%' || name == '>>') { | 196 if (name == '~/' || name == '<<' || name == '%' || name == '>>') { |
195 return null; | 197 return null; |
196 } | 198 } |
197 jsAst.Expression result = js('receiver $name a0'); | 199 jsAst.Expression result = js('receiver $name a0'); |
198 if (name == '&' || name == '|' || name == '^') { | 200 if (name == '&' || name == '|' || name == '^') { |
199 result = js('# >>> 0', result); | 201 result = js('# >>> 0', result); |
(...skipping 21 matching lines...) Expand all Loading... |
221 // } | 223 // } |
222 // } | 224 // } |
223 // | 225 // |
224 // For an index set operation, this code generates: | 226 // For an index set operation, this code generates: |
225 // | 227 // |
226 // if (receiver.constructor == Array && !receiver.immutable$list) { | 228 // if (receiver.constructor == Array && !receiver.immutable$list) { |
227 // if (a0 >>> 0 === a0 && a0 < receiver.length) { | 229 // if (a0 >>> 0 === a0 && a0 < receiver.length) { |
228 // return receiver[a0] = a1; | 230 // return receiver[a0] = a1; |
229 // } | 231 // } |
230 // } | 232 // } |
231 bool containsArray = classes.contains(backend.jsArrayClass); | 233 bool containsArray = classes.contains(helpers.jsArrayClass); |
232 bool containsString = classes.contains(backend.jsStringClass); | 234 bool containsString = classes.contains(helpers.jsStringClass); |
233 bool containsJsIndexable = | 235 bool containsJsIndexable = |
234 backend.jsIndexingBehaviorInterface.isResolved && classes.any((cls) { | 236 helpers.jsIndexingBehaviorInterface.isResolved && classes.any((cls) { |
235 return compiler.world.isSubtypeOf(cls, | 237 return compiler.world.isSubtypeOf(cls, |
236 backend.jsIndexingBehaviorInterface); | 238 helpers.jsIndexingBehaviorInterface); |
237 }); | 239 }); |
238 // The index set operator requires a check on its set value in | 240 // The index set operator requires a check on its set value in |
239 // checked mode, so we don't optimize the interceptor if the | 241 // checked mode, so we don't optimize the interceptor if the |
240 // compiler has type assertions enabled. | 242 // compiler has type assertions enabled. |
241 if (selector.isIndexSet | 243 if (selector.isIndexSet |
242 && (compiler.enableTypeAssertions || !containsArray)) { | 244 && (compiler.enableTypeAssertions || !containsArray)) { |
243 return null; | 245 return null; |
244 } | 246 } |
245 if (!containsArray && !containsString) { | 247 if (!containsArray && !containsString) { |
246 return null; | 248 return null; |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
303 | 305 |
304 if (selector.isSetter) { | 306 if (selector.isSetter) { |
305 parameterNames.add('value'); | 307 parameterNames.add('value'); |
306 } else { | 308 } else { |
307 for (int i = 0; i < selector.argumentCount; i++) { | 309 for (int i = 0; i < selector.argumentCount; i++) { |
308 parameterNames.add('a$i'); | 310 parameterNames.add('a$i'); |
309 } | 311 } |
310 } | 312 } |
311 | 313 |
312 jsAst.Name invocationName = backend.namer.invocationName(selector); | 314 jsAst.Name invocationName = backend.namer.invocationName(selector); |
313 String globalObject = namer.globalObjectFor(backend.interceptorsLibrary); | 315 String globalObject = namer.globalObjectFor(helpers.interceptorsLibrary); |
314 | 316 |
315 jsAst.Statement optimizedPath = | 317 jsAst.Statement optimizedPath = |
316 _fastPathForOneShotInterceptor(selector, classes); | 318 _fastPathForOneShotInterceptor(selector, classes); |
317 if (optimizedPath == null) optimizedPath = js.statement(';'); | 319 if (optimizedPath == null) optimizedPath = js.statement(';'); |
318 | 320 |
319 return js( | 321 return js( |
320 'function(#) { #; return #.#(receiver).#(#) }', | 322 'function(#) { #; return #.#(receiver).#(#) }', |
321 [parameterNames, | 323 [parameterNames, |
322 optimizedPath, | 324 optimizedPath, |
323 globalObject, getInterceptorName, invocationName, parameterNames]); | 325 globalObject, getInterceptorName, invocationName, parameterNames]); |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
368 | 370 |
369 var map = new jsAst.ObjectInitializer(properties); | 371 var map = new jsAst.ObjectInitializer(properties); |
370 elements.add(map); | 372 elements.add(map); |
371 } | 373 } |
372 } | 374 } |
373 } | 375 } |
374 | 376 |
375 return new jsAst.ArrayInitializer(elements); | 377 return new jsAst.ArrayInitializer(elements); |
376 } | 378 } |
377 } | 379 } |
OLD | NEW |