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 library dart2js.js_emitter.interceptor_stub_generator; | 5 library dart2js.js_emitter.interceptor_stub_generator; |
6 | 6 |
7 import '../compiler.dart' show Compiler; | 7 import '../compiler.dart' show Compiler; |
8 import '../constants/values.dart'; | 8 import '../constants/values.dart'; |
9 import '../elements/entities.dart'; | 9 import '../elements/entities.dart'; |
10 import '../elements/types.dart' show InterfaceType; | 10 import '../elements/types.dart' show InterfaceType; |
11 import '../js/js.dart' as jsAst; | 11 import '../js/js.dart' as jsAst; |
12 import '../js/js.dart' show js; | 12 import '../js/js.dart' show js; |
13 import '../js_backend/backend_helpers.dart' show BackendHelpers; | |
14 import '../js_backend/js_backend.dart' | 13 import '../js_backend/js_backend.dart' |
15 show | 14 show |
16 CustomElementsCodegenAnalysis, | 15 CustomElementsCodegenAnalysis, |
17 JavaScriptBackend, | 16 JavaScriptBackend, |
18 JavaScriptConstantCompiler, | 17 JavaScriptConstantCompiler, |
19 Namer; | 18 Namer; |
20 import '../universe/selector.dart' show Selector; | 19 import '../universe/selector.dart' show Selector; |
21 import '../world.dart' show ClosedWorld; | 20 import '../world.dart' show ClosedWorld; |
22 | 21 |
23 import 'code_emitter_task.dart' show Emitter; | 22 import 'code_emitter_task.dart' show Emitter; |
24 | 23 |
25 class InterceptorStubGenerator { | 24 class InterceptorStubGenerator { |
26 final Compiler compiler; | 25 final Compiler compiler; |
27 final Namer namer; | 26 final Namer namer; |
28 final JavaScriptBackend backend; | 27 final JavaScriptBackend backend; |
29 final ClosedWorld closedWorld; | 28 final ClosedWorld closedWorld; |
30 | 29 |
31 InterceptorStubGenerator( | 30 InterceptorStubGenerator( |
32 this.compiler, this.namer, this.backend, this.closedWorld); | 31 this.compiler, this.namer, this.backend, this.closedWorld); |
33 | 32 |
34 Emitter get emitter => backend.emitter.emitter; | 33 Emitter get emitter => backend.emitter.emitter; |
35 | 34 |
36 BackendHelpers get helpers => backend.helpers; | |
37 | |
38 jsAst.Expression generateGetInterceptorMethod(Set<ClassEntity> classes) { | 35 jsAst.Expression generateGetInterceptorMethod(Set<ClassEntity> classes) { |
39 jsAst.Expression interceptorFor(ClassEntity cls) { | 36 jsAst.Expression interceptorFor(ClassEntity cls) { |
40 return backend.emitter.interceptorPrototypeAccess(cls); | 37 return backend.emitter.interceptorPrototypeAccess(cls); |
41 } | 38 } |
42 | 39 |
43 /** | 40 /** |
44 * Build a JavaScrit AST node for doing a type check on | 41 * Build a JavaScrit AST node for doing a type check on |
45 * [cls]. [cls] must be a non-native interceptor class. | 42 * [cls]. [cls] must be a non-native interceptor class. |
46 */ | 43 */ |
47 jsAst.Statement buildInterceptorCheck(ClassEntity cls) { | 44 jsAst.Statement buildInterceptorCheck(ClassEntity cls) { |
48 jsAst.Expression condition; | 45 jsAst.Expression condition; |
49 assert(backend.interceptorData.isInterceptedClass(cls)); | 46 assert(backend.interceptorData.isInterceptedClass(cls)); |
50 if (cls == helpers.jsBoolClass) { | 47 if (cls == compiler.commonElements.jsBoolClass) { |
51 condition = js('(typeof receiver) == "boolean"'); | 48 condition = js('(typeof receiver) == "boolean"'); |
52 } else if (cls == helpers.jsIntClass || | 49 } else if (cls == compiler.commonElements.jsIntClass || |
53 cls == helpers.jsDoubleClass || | 50 cls == compiler.commonElements.jsDoubleClass || |
54 cls == helpers.jsNumberClass) { | 51 cls == compiler.commonElements.jsNumberClass) { |
55 throw 'internal error'; | 52 throw 'internal error'; |
56 } else if (cls == helpers.jsArrayClass || | 53 } else if (cls == compiler.commonElements.jsArrayClass || |
57 cls == helpers.jsMutableArrayClass || | 54 cls == compiler.commonElements.jsMutableArrayClass || |
58 cls == helpers.jsFixedArrayClass || | 55 cls == compiler.commonElements.jsFixedArrayClass || |
59 cls == helpers.jsExtendableArrayClass) { | 56 cls == compiler.commonElements.jsExtendableArrayClass) { |
60 condition = js('receiver.constructor == Array'); | 57 condition = js('receiver.constructor == Array'); |
61 } else if (cls == helpers.jsStringClass) { | 58 } else if (cls == compiler.commonElements.jsStringClass) { |
62 condition = js('(typeof receiver) == "string"'); | 59 condition = js('(typeof receiver) == "string"'); |
63 } else if (cls == helpers.jsNullClass) { | 60 } else if (cls == compiler.commonElements.jsNullClass) { |
64 condition = js('receiver == null'); | 61 condition = js('receiver == null'); |
65 } else { | 62 } else { |
66 throw 'internal error'; | 63 throw 'internal error'; |
67 } | 64 } |
68 return js.statement('if (#) return #', [condition, interceptorFor(cls)]); | 65 return js.statement('if (#) return #', [condition, interceptorFor(cls)]); |
69 } | 66 } |
70 | 67 |
71 bool hasArray = false; | 68 bool hasArray = false; |
72 bool hasBool = false; | 69 bool hasBool = false; |
73 bool hasDouble = false; | 70 bool hasDouble = false; |
74 bool hasInt = false; | 71 bool hasInt = false; |
75 bool hasNull = false; | 72 bool hasNull = false; |
76 bool hasNumber = false; | 73 bool hasNumber = false; |
77 bool hasString = false; | 74 bool hasString = false; |
78 bool hasNative = false; | 75 bool hasNative = false; |
79 bool anyNativeClasses = | 76 bool anyNativeClasses = |
80 backend.nativeCodegenEnqueuer.hasInstantiatedNativeClasses; | 77 backend.nativeCodegenEnqueuer.hasInstantiatedNativeClasses; |
81 | 78 |
82 for (ClassEntity cls in classes) { | 79 for (ClassEntity cls in classes) { |
83 if (cls == helpers.jsArrayClass || | 80 if (cls == compiler.commonElements.jsArrayClass || |
84 cls == helpers.jsMutableArrayClass || | 81 cls == compiler.commonElements.jsMutableArrayClass || |
85 cls == helpers.jsFixedArrayClass || | 82 cls == compiler.commonElements.jsFixedArrayClass || |
86 cls == helpers.jsExtendableArrayClass) | 83 cls == compiler.commonElements.jsExtendableArrayClass) |
87 hasArray = true; | 84 hasArray = true; |
88 else if (cls == helpers.jsBoolClass) | 85 else if (cls == compiler.commonElements.jsBoolClass) |
89 hasBool = true; | 86 hasBool = true; |
90 else if (cls == helpers.jsDoubleClass) | 87 else if (cls == compiler.commonElements.jsDoubleClass) |
91 hasDouble = true; | 88 hasDouble = true; |
92 else if (cls == helpers.jsIntClass) | 89 else if (cls == compiler.commonElements.jsIntClass) |
93 hasInt = true; | 90 hasInt = true; |
94 else if (cls == helpers.jsNullClass) | 91 else if (cls == compiler.commonElements.jsNullClass) |
95 hasNull = true; | 92 hasNull = true; |
96 else if (cls == helpers.jsNumberClass) | 93 else if (cls == compiler.commonElements.jsNumberClass) |
97 hasNumber = true; | 94 hasNumber = true; |
98 else if (cls == helpers.jsStringClass) | 95 else if (cls == compiler.commonElements.jsStringClass) |
99 hasString = true; | 96 hasString = true; |
100 else { | 97 else { |
101 // The set of classes includes classes mixed-in to interceptor classes | 98 // The set of classes includes classes mixed-in to interceptor classes |
102 // and user extensions of native classes. | 99 // and user extensions of native classes. |
103 // | 100 // |
104 // The set of classes also includes the 'primitive' interceptor | 101 // The set of classes also includes the 'primitive' interceptor |
105 // PlainJavaScriptObject even when it has not been resolved, since it is | 102 // PlainJavaScriptObject even when it has not been resolved, since it is |
106 // only resolved through the reference in getNativeInterceptor when | 103 // only resolved through the reference in getNativeInterceptor when |
107 // getNativeInterceptor is marked as used. Guard against probing | 104 // getNativeInterceptor is marked as used. Guard against probing |
108 // unresolved PlainJavaScriptObject by testing for anyNativeClasses. | 105 // unresolved PlainJavaScriptObject by testing for anyNativeClasses. |
(...skipping 15 matching lines...) Expand all Loading... |
124 | 121 |
125 List<jsAst.Statement> statements = <jsAst.Statement>[]; | 122 List<jsAst.Statement> statements = <jsAst.Statement>[]; |
126 | 123 |
127 if (hasNumber) { | 124 if (hasNumber) { |
128 jsAst.Statement whenNumber; | 125 jsAst.Statement whenNumber; |
129 | 126 |
130 /// Note: there are two number classes in play: Dart's [num], | 127 /// Note: there are two number classes in play: Dart's [num], |
131 /// and JavaScript's Number (typeof receiver == 'number'). This | 128 /// and JavaScript's Number (typeof receiver == 'number'). This |
132 /// is the fallback used when we have determined that receiver | 129 /// is the fallback used when we have determined that receiver |
133 /// is a JavaScript Number. | 130 /// is a JavaScript Number. |
134 jsAst.Expression interceptorForNumber = interceptorFor( | 131 jsAst.Expression interceptorForNumber = interceptorFor(hasDouble |
135 hasDouble ? helpers.jsDoubleClass : helpers.jsNumberClass); | 132 ? compiler.commonElements.jsDoubleClass |
| 133 : compiler.commonElements.jsNumberClass); |
136 | 134 |
137 if (hasInt) { | 135 if (hasInt) { |
138 whenNumber = js.statement( | 136 whenNumber = js.statement( |
139 '''{ | 137 '''{ |
140 if (Math.floor(receiver) == receiver) return #; | 138 if (Math.floor(receiver) == receiver) return #; |
141 return #; | 139 return #; |
142 }''', | 140 }''', |
143 [interceptorFor(helpers.jsIntClass), interceptorForNumber]); | 141 [ |
| 142 interceptorFor(compiler.commonElements.jsIntClass), |
| 143 interceptorForNumber |
| 144 ]); |
144 } else { | 145 } else { |
145 whenNumber = js.statement('return #', interceptorForNumber); | 146 whenNumber = js.statement('return #', interceptorForNumber); |
146 } | 147 } |
147 statements | 148 statements |
148 .add(js.statement('if (typeof receiver == "number") #;', whenNumber)); | 149 .add(js.statement('if (typeof receiver == "number") #;', whenNumber)); |
149 } | 150 } |
150 | 151 |
151 if (hasString) { | 152 if (hasString) { |
152 statements.add(buildInterceptorCheck(helpers.jsStringClass)); | 153 statements |
| 154 .add(buildInterceptorCheck(compiler.commonElements.jsStringClass)); |
153 } | 155 } |
154 if (hasNull) { | 156 if (hasNull) { |
155 statements.add(buildInterceptorCheck(helpers.jsNullClass)); | 157 statements |
| 158 .add(buildInterceptorCheck(compiler.commonElements.jsNullClass)); |
156 } else { | 159 } else { |
157 // Returning "undefined" or "null" here will provoke a JavaScript | 160 // Returning "undefined" or "null" here will provoke a JavaScript |
158 // TypeError which is later identified as a null-error by | 161 // TypeError which is later identified as a null-error by |
159 // [unwrapException] in js_helper.dart. | 162 // [unwrapException] in js_helper.dart. |
160 statements.add(js.statement('if (receiver == null) return receiver')); | 163 statements.add(js.statement('if (receiver == null) return receiver')); |
161 } | 164 } |
162 if (hasBool) { | 165 if (hasBool) { |
163 statements.add(buildInterceptorCheck(helpers.jsBoolClass)); | 166 statements |
| 167 .add(buildInterceptorCheck(compiler.commonElements.jsBoolClass)); |
164 } | 168 } |
165 // TODO(ahe): It might be faster to check for Array before | 169 // TODO(ahe): It might be faster to check for Array before |
166 // function and bool. | 170 // function and bool. |
167 if (hasArray) { | 171 if (hasArray) { |
168 statements.add(buildInterceptorCheck(helpers.jsArrayClass)); | 172 statements |
| 173 .add(buildInterceptorCheck(compiler.commonElements.jsArrayClass)); |
169 } | 174 } |
170 | 175 |
171 if (hasNative) { | 176 if (hasNative) { |
172 statements.add(js.statement( | 177 statements.add(js.statement( |
173 r'''{ | 178 r'''{ |
174 if (typeof receiver != "object") { | 179 if (typeof receiver != "object") { |
175 if (typeof receiver == "function" ) return #; | 180 if (typeof receiver == "function" ) return #; |
176 return receiver; | 181 return receiver; |
177 } | 182 } |
178 if (receiver instanceof #) return receiver; | 183 if (receiver instanceof #) return receiver; |
179 return #(receiver); | 184 return #(receiver); |
180 }''', | 185 }''', |
181 [ | 186 [ |
182 interceptorFor(helpers.jsJavaScriptFunctionClass), | 187 interceptorFor(compiler.commonElements.jsJavaScriptFunctionClass), |
183 backend.emitter | 188 backend.emitter |
184 .constructorAccess(compiler.commonElements.objectClass), | 189 .constructorAccess(compiler.commonElements.objectClass), |
185 backend.emitter | 190 backend.emitter.staticFunctionAccess( |
186 .staticFunctionAccess(helpers.getNativeInterceptorMethod) | 191 compiler.commonElements.getNativeInterceptorMethod) |
187 ])); | 192 ])); |
188 } else { | 193 } else { |
189 ClassEntity jsUnknown = helpers.jsUnknownJavaScriptObjectClass; | 194 ClassEntity jsUnknown = |
| 195 compiler.commonElements.jsUnknownJavaScriptObjectClass; |
190 if (compiler.codegenWorldBuilder.directlyInstantiatedClasses | 196 if (compiler.codegenWorldBuilder.directlyInstantiatedClasses |
191 .contains(jsUnknown)) { | 197 .contains(jsUnknown)) { |
192 statements.add(js.statement('if (!(receiver instanceof #)) return #;', [ | 198 statements.add(js.statement('if (!(receiver instanceof #)) return #;', [ |
193 backend.emitter | 199 backend.emitter |
194 .constructorAccess(compiler.commonElements.objectClass), | 200 .constructorAccess(compiler.commonElements.objectClass), |
195 interceptorFor(jsUnknown) | 201 interceptorFor(jsUnknown) |
196 ])); | 202 ])); |
197 } | 203 } |
198 | 204 |
199 statements.add(js.statement('return receiver')); | 205 statements.add(js.statement('return receiver')); |
200 } | 206 } |
201 | 207 |
202 return js('''function(receiver) { #; }''', new jsAst.Block(statements)); | 208 return js('''function(receiver) { #; }''', new jsAst.Block(statements)); |
203 } | 209 } |
204 | 210 |
205 // Returns a statement that takes care of performance critical | 211 // Returns a statement that takes care of performance critical |
206 // common case for a one-shot interceptor, or null if there is no | 212 // common case for a one-shot interceptor, or null if there is no |
207 // fast path. | 213 // fast path. |
208 jsAst.Statement _fastPathForOneShotInterceptor( | 214 jsAst.Statement _fastPathForOneShotInterceptor( |
209 Selector selector, Set<ClassEntity> classes) { | 215 Selector selector, Set<ClassEntity> classes) { |
210 if (selector.isOperator) { | 216 if (selector.isOperator) { |
211 String name = selector.name; | 217 String name = selector.name; |
212 if (name == '==') { | 218 if (name == '==') { |
213 return js.statement('''{ | 219 return js.statement('''{ |
214 if (receiver == null) return a0 == null; | 220 if (receiver == null) return a0 == null; |
215 if (typeof receiver != "object") | 221 if (typeof receiver != "object") |
216 return a0 != null && receiver === a0; | 222 return a0 != null && receiver === a0; |
217 }'''); | 223 }'''); |
218 } | 224 } |
219 if (!classes.contains(helpers.jsIntClass) && | 225 if (!classes.contains(compiler.commonElements.jsIntClass) && |
220 !classes.contains(helpers.jsNumberClass) && | 226 !classes.contains(compiler.commonElements.jsNumberClass) && |
221 !classes.contains(helpers.jsDoubleClass)) { | 227 !classes.contains(compiler.commonElements.jsDoubleClass)) { |
222 return null; | 228 return null; |
223 } | 229 } |
224 if (selector.argumentCount == 1) { | 230 if (selector.argumentCount == 1) { |
225 // The following operators do not map to a JavaScript operator. | 231 // The following operators do not map to a JavaScript operator. |
226 if (name == '~/' || name == '<<' || name == '%' || name == '>>') { | 232 if (name == '~/' || name == '<<' || name == '%' || name == '>>') { |
227 return null; | 233 return null; |
228 } | 234 } |
229 jsAst.Expression result = js('receiver $name a0'); | 235 jsAst.Expression result = js('receiver $name a0'); |
230 if (name == '&' || name == '|' || name == '^') { | 236 if (name == '&' || name == '|' || name == '^') { |
231 result = js('# >>> 0', result); | 237 result = js('# >>> 0', result); |
(...skipping 26 matching lines...) Expand all Loading... |
258 // | 264 // |
259 // For an index set operation, this code generates: | 265 // For an index set operation, this code generates: |
260 // | 266 // |
261 // if (typeof a0 === "number") { | 267 // if (typeof a0 === "number") { |
262 // if (receiver.constructor == Array && !receiver.immutable$list) { | 268 // if (receiver.constructor == Array && !receiver.immutable$list) { |
263 // if (a0 >>> 0 === a0 && a0 < receiver.length) { | 269 // if (a0 >>> 0 === a0 && a0 < receiver.length) { |
264 // return receiver[a0] = a1; | 270 // return receiver[a0] = a1; |
265 // } | 271 // } |
266 // } | 272 // } |
267 // } | 273 // } |
268 bool containsArray = classes.contains(helpers.jsArrayClass); | 274 bool containsArray = |
269 bool containsString = classes.contains(helpers.jsStringClass); | 275 classes.contains(compiler.commonElements.jsArrayClass); |
270 bool containsJsIndexable = | 276 bool containsString = |
271 closedWorld.isImplemented(helpers.jsIndexingBehaviorInterface) && | 277 classes.contains(compiler.commonElements.jsStringClass); |
272 classes.any((cls) { | 278 bool containsJsIndexable = closedWorld.isImplemented( |
273 return closedWorld.isSubtypeOf( | 279 compiler.commonElements.jsIndexingBehaviorInterface) && |
274 cls, helpers.jsIndexingBehaviorInterface); | 280 classes.any((cls) { |
275 }); | 281 return closedWorld.isSubtypeOf( |
| 282 cls, compiler.commonElements.jsIndexingBehaviorInterface); |
| 283 }); |
276 // The index set operator requires a check on its set value in | 284 // The index set operator requires a check on its set value in |
277 // checked mode, so we don't optimize the interceptor if the | 285 // checked mode, so we don't optimize the interceptor if the |
278 // compiler has type assertions enabled. | 286 // compiler has type assertions enabled. |
279 if (selector.isIndexSet && | 287 if (selector.isIndexSet && |
280 (compiler.options.enableTypeAssertions || !containsArray)) { | 288 (compiler.options.enableTypeAssertions || !containsArray)) { |
281 return null; | 289 return null; |
282 } | 290 } |
283 if (!containsArray && !containsString) { | 291 if (!containsArray && !containsString) { |
284 return null; | 292 return null; |
285 } | 293 } |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
348 | 356 |
349 if (selector.isSetter) { | 357 if (selector.isSetter) { |
350 parameterNames.add('value'); | 358 parameterNames.add('value'); |
351 } else { | 359 } else { |
352 for (int i = 0; i < selector.argumentCount; i++) { | 360 for (int i = 0; i < selector.argumentCount; i++) { |
353 parameterNames.add('a$i'); | 361 parameterNames.add('a$i'); |
354 } | 362 } |
355 } | 363 } |
356 | 364 |
357 jsAst.Name invocationName = backend.namer.invocationName(selector); | 365 jsAst.Name invocationName = backend.namer.invocationName(selector); |
358 String globalObject = | 366 String globalObject = namer |
359 namer.globalObjectForLibrary(helpers.interceptorsLibrary); | 367 .globalObjectForLibrary(compiler.commonElements.interceptorsLibrary); |
360 | 368 |
361 jsAst.Statement optimizedPath = | 369 jsAst.Statement optimizedPath = |
362 _fastPathForOneShotInterceptor(selector, classes); | 370 _fastPathForOneShotInterceptor(selector, classes); |
363 if (optimizedPath == null) optimizedPath = js.statement(';'); | 371 if (optimizedPath == null) optimizedPath = js.statement(';'); |
364 | 372 |
365 return js('function(#) { #; return #.#(receiver).#(#) }', [ | 373 return js('function(#) { #; return #.#(receiver).#(#) }', [ |
366 parameterNames, | 374 parameterNames, |
367 optimizedPath, | 375 optimizedPath, |
368 globalObject, | 376 globalObject, |
369 getInterceptorName, | 377 getInterceptorName, |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
414 } | 422 } |
415 | 423 |
416 var map = new jsAst.ObjectInitializer(properties); | 424 var map = new jsAst.ObjectInitializer(properties); |
417 elements.add(map); | 425 elements.add(map); |
418 } | 426 } |
419 } | 427 } |
420 | 428 |
421 return new jsAst.ArrayInitializer(elements); | 429 return new jsAst.ArrayInitializer(elements); |
422 } | 430 } |
423 } | 431 } |
OLD | NEW |