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