| 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 |