| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 ssa; | 5 part of ssa; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * This phase simplifies interceptors in multiple ways: | 8 * This phase simplifies interceptors in multiple ways: |
| 9 * | 9 * |
| 10 * 1) If the interceptor is for an object whose type is known, it | 10 * 1) If the interceptor is for an object whose type is known, it |
| (...skipping 16 matching lines...) Expand all Loading... |
| 27 class SsaSimplifyInterceptors extends HBaseVisitor | 27 class SsaSimplifyInterceptors extends HBaseVisitor |
| 28 implements OptimizationPhase { | 28 implements OptimizationPhase { |
| 29 final String name = "SsaSimplifyInterceptors"; | 29 final String name = "SsaSimplifyInterceptors"; |
| 30 final ConstantSystem constantSystem; | 30 final ConstantSystem constantSystem; |
| 31 final Compiler compiler; | 31 final Compiler compiler; |
| 32 final CodegenWorkItem work; | 32 final CodegenWorkItem work; |
| 33 HGraph graph; | 33 HGraph graph; |
| 34 | 34 |
| 35 SsaSimplifyInterceptors(this.compiler, this.constantSystem, this.work); | 35 SsaSimplifyInterceptors(this.compiler, this.constantSystem, this.work); |
| 36 | 36 |
| 37 JavaScriptBackend get backend => compiler.backend; |
| 38 |
| 39 BackendHelpers get helpers => backend.helpers; |
| 40 |
| 41 ClassWorld get classWorld => compiler.world; |
| 42 |
| 37 void visitGraph(HGraph graph) { | 43 void visitGraph(HGraph graph) { |
| 38 this.graph = graph; | 44 this.graph = graph; |
| 39 visitDominatorTree(graph); | 45 visitDominatorTree(graph); |
| 40 } | 46 } |
| 41 | 47 |
| 42 void visitBasicBlock(HBasicBlock node) { | 48 void visitBasicBlock(HBasicBlock node) { |
| 43 currentBlock = node; | 49 currentBlock = node; |
| 44 | 50 |
| 45 HInstruction instruction = node.first; | 51 HInstruction instruction = node.first; |
| 46 while (instruction != null) { | 52 while (instruction != null) { |
| (...skipping 26 matching lines...) Expand all Loading... |
| 73 HInstruction constant = tryComputeConstantInterceptor( | 79 HInstruction constant = tryComputeConstantInterceptor( |
| 74 invoke.inputs[1], interceptor.interceptedClasses); | 80 invoke.inputs[1], interceptor.interceptedClasses); |
| 75 if (constant != null) { | 81 if (constant != null) { |
| 76 invoke.changeUse(interceptor, constant); | 82 invoke.changeUse(interceptor, constant); |
| 77 } | 83 } |
| 78 return false; | 84 return false; |
| 79 } | 85 } |
| 80 | 86 |
| 81 bool canUseSelfForInterceptor(HInstruction receiver, | 87 bool canUseSelfForInterceptor(HInstruction receiver, |
| 82 Set<ClassElement> interceptedClasses) { | 88 Set<ClassElement> interceptedClasses) { |
| 83 JavaScriptBackend backend = compiler.backend; | |
| 84 ClassWorld classWorld = compiler.world; | |
| 85 | 89 |
| 86 if (receiver.canBePrimitive(compiler)) { | 90 if (receiver.canBePrimitive(compiler)) { |
| 87 // Primitives always need interceptors. | 91 // Primitives always need interceptors. |
| 88 return false; | 92 return false; |
| 89 } | 93 } |
| 90 if (receiver.canBeNull() && | 94 if (receiver.canBeNull() && |
| 91 interceptedClasses.contains(backend.jsNullClass)) { | 95 interceptedClasses.contains(helpers.jsNullClass)) { |
| 92 // Need the JSNull interceptor. | 96 // Need the JSNull interceptor. |
| 93 return false; | 97 return false; |
| 94 } | 98 } |
| 95 | 99 |
| 96 // All intercepted classes extend `Interceptor`, so if the receiver can't be | 100 // All intercepted classes extend `Interceptor`, so if the receiver can't be |
| 97 // a class extending `Interceptor` then it can be called directly. | 101 // a class extending `Interceptor` then it can be called directly. |
| 98 return new TypeMask.nonNullSubclass(backend.jsInterceptorClass, classWorld) | 102 return new TypeMask.nonNullSubclass(helpers.jsInterceptorClass, classWorld) |
| 99 .intersection(receiver.instructionType, classWorld) | 103 .intersection(receiver.instructionType, classWorld) |
| 100 .isEmpty; | 104 .isEmpty; |
| 101 } | 105 } |
| 102 | 106 |
| 103 HInstruction tryComputeConstantInterceptor( | 107 HInstruction tryComputeConstantInterceptor( |
| 104 HInstruction input, | 108 HInstruction input, |
| 105 Set<ClassElement> interceptedClasses) { | 109 Set<ClassElement> interceptedClasses) { |
| 106 if (input == graph.explicitReceiverParameter) { | 110 if (input == graph.explicitReceiverParameter) { |
| 107 // If `explicitReceiverParameter` is set it means the current method is an | 111 // If `explicitReceiverParameter` is set it means the current method is an |
| 108 // interceptor method, and `this` is the interceptor. The caller just did | 112 // interceptor method, and `this` is the interceptor. The caller just did |
| (...skipping 15 matching lines...) Expand all Loading... |
| 124 | 128 |
| 125 ConstantValue constant = | 129 ConstantValue constant = |
| 126 new InterceptorConstantValue(constantInterceptor.thisType); | 130 new InterceptorConstantValue(constantInterceptor.thisType); |
| 127 return graph.addConstant(constant, compiler); | 131 return graph.addConstant(constant, compiler); |
| 128 } | 132 } |
| 129 | 133 |
| 130 ClassElement tryComputeConstantInterceptorFromType( | 134 ClassElement tryComputeConstantInterceptorFromType( |
| 131 TypeMask type, | 135 TypeMask type, |
| 132 Set<ClassElement> interceptedClasses) { | 136 Set<ClassElement> interceptedClasses) { |
| 133 | 137 |
| 134 ClassWorld classWorld = compiler.world; | |
| 135 JavaScriptBackend backend = compiler.backend; | |
| 136 if (type.isNullable) { | 138 if (type.isNullable) { |
| 137 if (type.isEmpty) { | 139 if (type.isEmpty) { |
| 138 return backend.jsNullClass; | 140 return helpers.jsNullClass; |
| 139 } | 141 } |
| 140 } else if (type.containsOnlyInt(classWorld)) { | 142 } else if (type.containsOnlyInt(classWorld)) { |
| 141 return backend.jsIntClass; | 143 return helpers.jsIntClass; |
| 142 } else if (type.containsOnlyDouble(classWorld)) { | 144 } else if (type.containsOnlyDouble(classWorld)) { |
| 143 return backend.jsDoubleClass; | 145 return helpers.jsDoubleClass; |
| 144 } else if (type.containsOnlyBool(classWorld)) { | 146 } else if (type.containsOnlyBool(classWorld)) { |
| 145 return backend.jsBoolClass; | 147 return helpers.jsBoolClass; |
| 146 } else if (type.containsOnlyString(classWorld)) { | 148 } else if (type.containsOnlyString(classWorld)) { |
| 147 return backend.jsStringClass; | 149 return helpers.jsStringClass; |
| 148 } else if (type.satisfies(backend.jsArrayClass, classWorld)) { | 150 } else if (type.satisfies(helpers.jsArrayClass, classWorld)) { |
| 149 return backend.jsArrayClass; | 151 return helpers.jsArrayClass; |
| 150 } else if (type.containsOnlyNum(classWorld) && | 152 } else if (type.containsOnlyNum(classWorld) && |
| 151 !interceptedClasses.contains(backend.jsIntClass) && | 153 !interceptedClasses.contains(helpers.jsIntClass) && |
| 152 !interceptedClasses.contains(backend.jsDoubleClass)) { | 154 !interceptedClasses.contains(helpers.jsDoubleClass)) { |
| 153 // If the method being intercepted is not defined in [int] or [double] we | 155 // If the method being intercepted is not defined in [int] or [double] we |
| 154 // can safely use the number interceptor. This is because none of the | 156 // can safely use the number interceptor. This is because none of the |
| 155 // [int] or [double] methods are called from a method defined on [num]. | 157 // [int] or [double] methods are called from a method defined on [num]. |
| 156 return backend.jsNumberClass; | 158 return helpers.jsNumberClass; |
| 157 } else { | 159 } else { |
| 158 // Try to find constant interceptor for a native class. If the receiver | 160 // Try to find constant interceptor for a native class. If the receiver |
| 159 // is constrained to a leaf native class, we can use the class's | 161 // is constrained to a leaf native class, we can use the class's |
| 160 // interceptor directly. | 162 // interceptor directly. |
| 161 | 163 |
| 162 // TODO(sra): Key DOM classes like Node, Element and Event are not leaf | 164 // TODO(sra): Key DOM classes like Node, Element and Event are not leaf |
| 163 // classes. When the receiver type is not a leaf class, we might still be | 165 // classes. When the receiver type is not a leaf class, we might still be |
| 164 // able to use the receiver class as a constant interceptor. It is | 166 // able to use the receiver class as a constant interceptor. It is |
| 165 // usually the case that methods defined on a non-leaf class don't test | 167 // usually the case that methods defined on a non-leaf class don't test |
| 166 // for a subclass or call methods defined on a subclass. Provided the | 168 // for a subclass or call methods defined on a subclass. Provided the |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 202 // | 204 // |
| 203 // Currently we use the most general interceptor since all intercepted types | 205 // Currently we use the most general interceptor since all intercepted types |
| 204 // implement `hashCode`. But in this example, `a.hashCode` is only reached | 206 // implement `hashCode`. But in this example, `a.hashCode` is only reached |
| 205 // if `a.length` succeeds, which is indicated by the hashCode receiver being | 207 // if `a.length` succeeds, which is indicated by the hashCode receiver being |
| 206 // a HTypeKnown instruction. | 208 // a HTypeKnown instruction. |
| 207 | 209 |
| 208 int useCount(HInstruction user, HInstruction used) => | 210 int useCount(HInstruction user, HInstruction used) => |
| 209 user.inputs.where((input) => input == used).length; | 211 user.inputs.where((input) => input == used).length; |
| 210 | 212 |
| 211 Set<ClassElement> interceptedClasses; | 213 Set<ClassElement> interceptedClasses; |
| 212 JavaScriptBackend backend = compiler.backend; | |
| 213 HInstruction dominator = findDominator(node.usedBy); | 214 HInstruction dominator = findDominator(node.usedBy); |
| 214 // If there is a call that dominates all other uses, we can use just the | 215 // If there is a call that dominates all other uses, we can use just the |
| 215 // selector of that instruction. | 216 // selector of that instruction. |
| 216 if (dominator is HInvokeDynamic && | 217 if (dominator is HInvokeDynamic && |
| 217 dominator.isCallOnInterceptor(compiler) && | 218 dominator.isCallOnInterceptor(compiler) && |
| 218 node == dominator.receiver && | 219 node == dominator.receiver && |
| 219 useCount(dominator, node) == 1) { | 220 useCount(dominator, node) == 1) { |
| 220 interceptedClasses = | 221 interceptedClasses = |
| 221 backend.getInterceptedClassesOn(dominator.selector.name); | 222 backend.getInterceptedClassesOn(dominator.selector.name); |
| 222 | 223 |
| 223 // If we found that we need number, we must still go through all | 224 // If we found that we need number, we must still go through all |
| 224 // uses to check if they require int, or double. | 225 // uses to check if they require int, or double. |
| 225 if (interceptedClasses.contains(backend.jsNumberClass) && | 226 if (interceptedClasses.contains(helpers.jsNumberClass) && |
| 226 !(interceptedClasses.contains(backend.jsDoubleClass) || | 227 !(interceptedClasses.contains(helpers.jsDoubleClass) || |
| 227 interceptedClasses.contains(backend.jsIntClass))) { | 228 interceptedClasses.contains(helpers.jsIntClass))) { |
| 228 for (HInstruction user in node.usedBy) { | 229 for (HInstruction user in node.usedBy) { |
| 229 if (user is! HInvoke) continue; | 230 if (user is! HInvoke) continue; |
| 230 Set<ClassElement> intercepted = | 231 Set<ClassElement> intercepted = |
| 231 backend.getInterceptedClassesOn(user.selector.name); | 232 backend.getInterceptedClassesOn(user.selector.name); |
| 232 if (intercepted.contains(backend.jsIntClass)) { | 233 if (intercepted.contains(helpers.jsIntClass)) { |
| 233 interceptedClasses.add(backend.jsIntClass); | 234 interceptedClasses.add(helpers.jsIntClass); |
| 234 } | 235 } |
| 235 if (intercepted.contains(backend.jsDoubleClass)) { | 236 if (intercepted.contains(helpers.jsDoubleClass)) { |
| 236 interceptedClasses.add(backend.jsDoubleClass); | 237 interceptedClasses.add(helpers.jsDoubleClass); |
| 237 } | 238 } |
| 238 } | 239 } |
| 239 } | 240 } |
| 240 } else { | 241 } else { |
| 241 interceptedClasses = new Set<ClassElement>(); | 242 interceptedClasses = new Set<ClassElement>(); |
| 242 for (HInstruction user in node.usedBy) { | 243 for (HInstruction user in node.usedBy) { |
| 243 if (user is HInvokeDynamic && | 244 if (user is HInvokeDynamic && |
| 244 user.isCallOnInterceptor(compiler) && | 245 user.isCallOnInterceptor(compiler) && |
| 245 node == user.receiver && | 246 node == user.receiver && |
| 246 useCount(user, node) == 1) { | 247 useCount(user, node) == 1) { |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 284 node.block.rewrite(node, constantInterceptor); | 285 node.block.rewrite(node, constantInterceptor); |
| 285 return false; | 286 return false; |
| 286 } | 287 } |
| 287 | 288 |
| 288 // Do we have an 'almost constant' interceptor? The receiver could be | 289 // Do we have an 'almost constant' interceptor? The receiver could be |
| 289 // `null` but not any other JavaScript falsy value, `null` values cause | 290 // `null` but not any other JavaScript falsy value, `null` values cause |
| 290 // `NoSuchMethodError`s, and if the receiver was not null we would have a | 291 // `NoSuchMethodError`s, and if the receiver was not null we would have a |
| 291 // constant interceptor `C`. Then we can use `(receiver && C)` for the | 292 // constant interceptor `C`. Then we can use `(receiver && C)` for the |
| 292 // interceptor. | 293 // interceptor. |
| 293 if (receiver.canBeNull() && !node.isConditionalConstantInterceptor) { | 294 if (receiver.canBeNull() && !node.isConditionalConstantInterceptor) { |
| 294 if (!interceptedClasses.contains(backend.jsNullClass)) { | 295 if (!interceptedClasses.contains(helpers.jsNullClass)) { |
| 295 // Can use `(receiver && C)` only if receiver is either null or truthy. | 296 // Can use `(receiver && C)` only if receiver is either null or truthy. |
| 296 if (!(receiver.canBePrimitiveNumber(compiler) || | 297 if (!(receiver.canBePrimitiveNumber(compiler) || |
| 297 receiver.canBePrimitiveBoolean(compiler) || | 298 receiver.canBePrimitiveBoolean(compiler) || |
| 298 receiver.canBePrimitiveString(compiler))) { | 299 receiver.canBePrimitiveString(compiler))) { |
| 299 ClassElement interceptorClass = tryComputeConstantInterceptorFromType( | 300 ClassElement interceptorClass = tryComputeConstantInterceptorFromType( |
| 300 receiver.instructionType.nonNullable(), interceptedClasses); | 301 receiver.instructionType.nonNullable(), interceptedClasses); |
| 301 if (interceptorClass != null) { | 302 if (interceptorClass != null) { |
| 302 HInstruction constantInstruction = | 303 HInstruction constantInstruction = |
| 303 graph.addConstant( | 304 graph.addConstant( |
| 304 new InterceptorConstantValue(interceptorClass.thisType), | 305 new InterceptorConstantValue(interceptorClass.thisType), |
| (...skipping 19 matching lines...) Expand all Loading... |
| 324 block.addAfter(user, replacement); | 325 block.addAfter(user, replacement); |
| 325 block.rewrite(user, replacement); | 326 block.rewrite(user, replacement); |
| 326 block.remove(user); | 327 block.remove(user); |
| 327 return false; | 328 return false; |
| 328 } | 329 } |
| 329 | 330 |
| 330 if (user is HIs) { | 331 if (user is HIs) { |
| 331 // See if we can rewrite the is-check to use 'instanceof', i.e. rewrite | 332 // See if we can rewrite the is-check to use 'instanceof', i.e. rewrite |
| 332 // "getInterceptor(x).$isT" to "x instanceof T". | 333 // "getInterceptor(x).$isT" to "x instanceof T". |
| 333 if (node == user.interceptor) { | 334 if (node == user.interceptor) { |
| 334 JavaScriptBackend backend = compiler.backend; | |
| 335 if (backend.mayGenerateInstanceofCheck(user.typeExpression)) { | 335 if (backend.mayGenerateInstanceofCheck(user.typeExpression)) { |
| 336 HInstruction instanceofCheck = new HIs.instanceOf( | 336 HInstruction instanceofCheck = new HIs.instanceOf( |
| 337 user.typeExpression, user.expression, user.instructionType); | 337 user.typeExpression, user.expression, user.instructionType); |
| 338 instanceofCheck.sourceInformation = user.sourceInformation; | 338 instanceofCheck.sourceInformation = user.sourceInformation; |
| 339 instanceofCheck.sourceElement = user.sourceElement; | 339 instanceofCheck.sourceElement = user.sourceElement; |
| 340 return replaceUserWith(instanceofCheck); | 340 return replaceUserWith(instanceofCheck); |
| 341 } | 341 } |
| 342 } | 342 } |
| 343 } else if (user is HInvokeDynamic) { | 343 } else if (user is HInvokeDynamic) { |
| 344 if (node == user.inputs[0]) { | 344 if (node == user.inputs[0]) { |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 403 instruction = new HInvokeDynamicMethod( | 403 instruction = new HInvokeDynamicMethod( |
| 404 selector, mask, inputs, node.instructionType, true); | 404 selector, mask, inputs, node.instructionType, true); |
| 405 } | 405 } |
| 406 | 406 |
| 407 HBasicBlock block = node.block; | 407 HBasicBlock block = node.block; |
| 408 block.addAfter(node, instruction); | 408 block.addAfter(node, instruction); |
| 409 block.rewrite(node, instruction); | 409 block.rewrite(node, instruction); |
| 410 return true; | 410 return true; |
| 411 } | 411 } |
| 412 } | 412 } |
| OLD | NEW |