| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of ssa; | |
| 6 | |
| 7 /** | |
| 8 * This phase simplifies interceptors in multiple ways: | |
| 9 * | |
| 10 * 1) If the interceptor is for an object whose type is known, it | |
| 11 * tries to use a constant interceptor instead. | |
| 12 * | |
| 13 * 2) It specializes interceptors based on the selectors it is being | |
| 14 * called with. | |
| 15 * | |
| 16 * 3) If we know the object is not intercepted, we just use it | |
| 17 * instead. | |
| 18 * | |
| 19 * 4) It replaces all interceptors that are used only once with | |
| 20 * one-shot interceptors. It saves code size and makes the receiver of | |
| 21 * an intercepted call a candidate for being generated at use site. | |
| 22 * | |
| 23 */ | |
| 24 class SsaSimplifyInterceptors extends HBaseVisitor | |
| 25 implements OptimizationPhase { | |
| 26 final String name = "SsaSimplifyInterceptors"; | |
| 27 final ConstantSystem constantSystem; | |
| 28 final Compiler compiler; | |
| 29 final CodegenWorkItem work; | |
| 30 HGraph graph; | |
| 31 | |
| 32 SsaSimplifyInterceptors(this.compiler, this.constantSystem, this.work); | |
| 33 | |
| 34 void visitGraph(HGraph graph) { | |
| 35 this.graph = graph; | |
| 36 visitDominatorTree(graph); | |
| 37 } | |
| 38 | |
| 39 void visitBasicBlock(HBasicBlock node) { | |
| 40 currentBlock = node; | |
| 41 | |
| 42 HInstruction instruction = node.first; | |
| 43 while (instruction != null) { | |
| 44 bool shouldRemove = instruction.accept(this); | |
| 45 HInstruction next = instruction.next; | |
| 46 if (shouldRemove) { | |
| 47 instruction.block.remove(instruction); | |
| 48 } | |
| 49 instruction = next; | |
| 50 } | |
| 51 } | |
| 52 | |
| 53 bool visitInstruction(HInstruction instruction) => false; | |
| 54 | |
| 55 bool visitInvoke(HInvoke invoke) { | |
| 56 if (!invoke.isInterceptedCall) return false; | |
| 57 var interceptor = invoke.inputs[0]; | |
| 58 if (interceptor is! HInterceptor) return false; | |
| 59 HInstruction constant = tryComputeConstantInterceptor( | |
| 60 invoke.inputs[1], interceptor.interceptedClasses); | |
| 61 if (constant != null) { | |
| 62 invoke.changeUse(interceptor, constant); | |
| 63 } | |
| 64 return false; | |
| 65 } | |
| 66 | |
| 67 bool canUseSelfForInterceptor(HInstruction receiver, | |
| 68 Set<ClassElement> interceptedClasses) { | |
| 69 JavaScriptBackend backend = compiler.backend; | |
| 70 ClassWorld classWorld = compiler.world; | |
| 71 | |
| 72 if (receiver.canBePrimitive(compiler)) { | |
| 73 // Primitives always need interceptors. | |
| 74 return false; | |
| 75 } | |
| 76 if (receiver.canBeNull() && | |
| 77 interceptedClasses.contains(backend.jsNullClass)) { | |
| 78 // Need the JSNull interceptor. | |
| 79 return false; | |
| 80 } | |
| 81 | |
| 82 // All intercepted classes extend `Interceptor`, so if the receiver can't be | |
| 83 // a class extending `Interceptor` then it can be called directly. | |
| 84 return new TypeMask.nonNullSubclass(backend.jsInterceptorClass, classWorld) | |
| 85 .intersection(receiver.instructionType, classWorld) | |
| 86 .isEmpty; | |
| 87 } | |
| 88 | |
| 89 HInstruction tryComputeConstantInterceptor( | |
| 90 HInstruction input, | |
| 91 Set<ClassElement> interceptedClasses) { | |
| 92 if (input == graph.explicitReceiverParameter) { | |
| 93 // If `explicitReceiverParameter` is set it means the current method is an | |
| 94 // interceptor method, and `this` is the interceptor. The caller just did | |
| 95 // `getInterceptor(foo).currentMethod(foo)` to enter the current method. | |
| 96 return graph.thisInstruction; | |
| 97 } | |
| 98 | |
| 99 ClassElement constantInterceptor; | |
| 100 ClassWorld classWorld = compiler.world; | |
| 101 JavaScriptBackend backend = compiler.backend; | |
| 102 if (input.canBeNull()) { | |
| 103 if (input.isNull()) { | |
| 104 constantInterceptor = backend.jsNullClass; | |
| 105 } | |
| 106 } else if (input.isInteger(compiler)) { | |
| 107 constantInterceptor = backend.jsIntClass; | |
| 108 } else if (input.isDouble(compiler)) { | |
| 109 constantInterceptor = backend.jsDoubleClass; | |
| 110 } else if (input.isBoolean(compiler)) { | |
| 111 constantInterceptor = backend.jsBoolClass; | |
| 112 } else if (input.isString(compiler)) { | |
| 113 constantInterceptor = backend.jsStringClass; | |
| 114 } else if (input.isArray(compiler)) { | |
| 115 constantInterceptor = backend.jsArrayClass; | |
| 116 } else if (input.isNumber(compiler) && | |
| 117 !interceptedClasses.contains(backend.jsIntClass) && | |
| 118 !interceptedClasses.contains(backend.jsDoubleClass)) { | |
| 119 // If the method being intercepted is not defined in [int] or [double] we | |
| 120 // can safely use the number interceptor. This is because none of the | |
| 121 // [int] or [double] methods are called from a method defined on [num]. | |
| 122 constantInterceptor = backend.jsNumberClass; | |
| 123 } else { | |
| 124 // Try to find constant interceptor for a native class. If the receiver | |
| 125 // is constrained to a leaf native class, we can use the class's | |
| 126 // interceptor directly. | |
| 127 | |
| 128 // TODO(sra): Key DOM classes like Node, Element and Event are not leaf | |
| 129 // classes. When the receiver type is not a leaf class, we might still be | |
| 130 // able to use the receiver class as a constant interceptor. It is | |
| 131 // usually the case that methods defined on a non-leaf class don't test | |
| 132 // for a subclass or call methods defined on a subclass. Provided the | |
| 133 // code is completely insensitive to the specific instance subclasses, we | |
| 134 // can use the non-leaf class directly. | |
| 135 ClassElement element = input.instructionType.singleClass(classWorld); | |
| 136 if (element != null && element.isNative) { | |
| 137 constantInterceptor = element; | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 if (constantInterceptor == null) return null; | |
| 142 | |
| 143 // If we just happen to be in an instance method of the constant | |
| 144 // interceptor, `this` is a shorter alias. | |
| 145 if (constantInterceptor == work.element.enclosingClass && | |
| 146 graph.thisInstruction != null) { | |
| 147 return graph.thisInstruction; | |
| 148 } | |
| 149 | |
| 150 ConstantValue constant = | |
| 151 new InterceptorConstantValue(constantInterceptor.thisType); | |
| 152 return graph.addConstant(constant, compiler); | |
| 153 } | |
| 154 | |
| 155 HInstruction findDominator(Iterable<HInstruction> instructions) { | |
| 156 HInstruction result; | |
| 157 L1: for (HInstruction candidate in instructions) { | |
| 158 for (HInstruction current in instructions) { | |
| 159 if (current != candidate && !candidate.dominates(current)) continue L1; | |
| 160 } | |
| 161 result = candidate; | |
| 162 break; | |
| 163 } | |
| 164 return result; | |
| 165 } | |
| 166 | |
| 167 bool visitInterceptor(HInterceptor node) { | |
| 168 if (node.isConstant()) return false; | |
| 169 | |
| 170 // Specialize the interceptor with set of classes it intercepts, considering | |
| 171 // all uses. (The specialized interceptor has a shorter dispatch chain). | |
| 172 // This operation applies only where the interceptor is used to dispatch a | |
| 173 // method. Other uses, e.g. as an ordinary argument or a HIs check use the | |
| 174 // most general interceptor. | |
| 175 // | |
| 176 // TODO(sra): Take into account the receiver type at each call. e.g: | |
| 177 // | |
| 178 // (a) => a.length + a.hashCode | |
| 179 // | |
| 180 // Currently we use the most general interceptor since all intercepted types | |
| 181 // implement `hashCode`. But in this example, `a.hashCode` is only reached | |
| 182 // if `a.length` succeeds, which is indicated by the hashCode receiver being | |
| 183 // a HTypeKnown instruction. | |
| 184 | |
| 185 int useCount(HInstruction user, HInstruction used) => | |
| 186 user.inputs.where((input) => input == used).length; | |
| 187 | |
| 188 Set<ClassElement> interceptedClasses; | |
| 189 JavaScriptBackend backend = compiler.backend; | |
| 190 HInstruction dominator = findDominator(node.usedBy); | |
| 191 // If there is a call that dominates all other uses, we can use just the | |
| 192 // selector of that instruction. | |
| 193 if (dominator is HInvokeDynamic && | |
| 194 dominator.isCallOnInterceptor(compiler) && | |
| 195 node == dominator.receiver && | |
| 196 useCount(dominator, node) == 1) { | |
| 197 interceptedClasses = | |
| 198 backend.getInterceptedClassesOn(dominator.selector.name); | |
| 199 | |
| 200 // If we found that we need number, we must still go through all | |
| 201 // uses to check if they require int, or double. | |
| 202 if (interceptedClasses.contains(backend.jsNumberClass) && | |
| 203 !(interceptedClasses.contains(backend.jsDoubleClass) || | |
| 204 interceptedClasses.contains(backend.jsIntClass))) { | |
| 205 for (HInstruction user in node.usedBy) { | |
| 206 if (user is! HInvoke) continue; | |
| 207 Set<ClassElement> intercepted = | |
| 208 backend.getInterceptedClassesOn(user.selector.name); | |
| 209 if (intercepted.contains(backend.jsIntClass)) { | |
| 210 interceptedClasses.add(backend.jsIntClass); | |
| 211 } | |
| 212 if (intercepted.contains(backend.jsDoubleClass)) { | |
| 213 interceptedClasses.add(backend.jsDoubleClass); | |
| 214 } | |
| 215 } | |
| 216 } | |
| 217 } else { | |
| 218 interceptedClasses = new Set<ClassElement>(); | |
| 219 for (HInstruction user in node.usedBy) { | |
| 220 if (user is HInvokeDynamic && | |
| 221 user.isCallOnInterceptor(compiler) && | |
| 222 node == user.receiver && | |
| 223 useCount(user, node) == 1) { | |
| 224 interceptedClasses.addAll( | |
| 225 backend.getInterceptedClassesOn(user.selector.name)); | |
| 226 } else if (user is HInvokeSuper && | |
| 227 user.isCallOnInterceptor(compiler) && | |
| 228 node == user.receiver && | |
| 229 useCount(user, node) == 1) { | |
| 230 interceptedClasses.addAll( | |
| 231 backend.getInterceptedClassesOn(user.selector.name)); | |
| 232 } else { | |
| 233 // Use a most general interceptor for other instructions, example, | |
| 234 // is-checks and escaping interceptors. | |
| 235 interceptedClasses.addAll(backend.interceptedClasses); | |
| 236 break; | |
| 237 } | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 HInstruction receiver = node.receiver; | |
| 242 | |
| 243 if (canUseSelfForInterceptor(receiver, interceptedClasses)) { | |
| 244 return rewriteToUseSelfAsInterceptor(node, receiver); | |
| 245 } | |
| 246 | |
| 247 // Try computing a constant interceptor. | |
| 248 HInstruction constantInterceptor = | |
| 249 tryComputeConstantInterceptor(receiver, interceptedClasses); | |
| 250 if (constantInterceptor != null) { | |
| 251 node.block.rewrite(node, constantInterceptor); | |
| 252 return false; | |
| 253 } | |
| 254 | |
| 255 node.interceptedClasses = interceptedClasses; | |
| 256 | |
| 257 // Try creating a one-shot interceptor. | |
| 258 if (node.usedBy.length != 1) return false; | |
| 259 if (node.usedBy[0] is !HInvokeDynamic) return false; | |
| 260 | |
| 261 HInvokeDynamic user = node.usedBy[0]; | |
| 262 | |
| 263 // If [node] was loop hoisted, we keep the interceptor. | |
| 264 if (!user.hasSameLoopHeaderAs(node)) return false; | |
| 265 | |
| 266 // Replace the user with a [HOneShotInterceptor]. | |
| 267 HConstant nullConstant = graph.addConstantNull(compiler); | |
| 268 List<HInstruction> inputs = new List<HInstruction>.from(user.inputs); | |
| 269 inputs[0] = nullConstant; | |
| 270 HOneShotInterceptor interceptor = new HOneShotInterceptor( | |
| 271 user.selector, inputs, user.instructionType, node.interceptedClasses); | |
| 272 interceptor.sourcePosition = user.sourcePosition; | |
| 273 interceptor.sourceElement = user.sourceElement; | |
| 274 | |
| 275 HBasicBlock block = user.block; | |
| 276 block.addAfter(user, interceptor); | |
| 277 block.rewrite(user, interceptor); | |
| 278 block.remove(user); | |
| 279 return true; | |
| 280 } | |
| 281 | |
| 282 bool rewriteToUseSelfAsInterceptor(HInterceptor node, HInstruction receiver) { | |
| 283 for (HInstruction user in node.usedBy.toList()) { | |
| 284 if (user is HIs) { | |
| 285 user.changeUse(node, receiver); | |
| 286 } else { | |
| 287 // Use the potentially self-argument as new receiver. Note that the | |
| 288 // self-argument could potentially have a tighter type than the | |
| 289 // receiver which was the input to the interceptor. | |
| 290 assert(user.inputs[0] == node); | |
| 291 assert(receiver.nonCheck() == user.inputs[1].nonCheck()); | |
| 292 user.changeUse(node, user.inputs[1]); | |
| 293 } | |
| 294 } | |
| 295 return false; | |
| 296 } | |
| 297 | |
| 298 bool visitOneShotInterceptor(HOneShotInterceptor node) { | |
| 299 HInstruction constant = tryComputeConstantInterceptor( | |
| 300 node.inputs[1], node.interceptedClasses); | |
| 301 | |
| 302 if (constant == null) return false; | |
| 303 | |
| 304 Selector selector = node.selector; | |
| 305 HInstruction instruction; | |
| 306 if (selector.isGetter) { | |
| 307 instruction = new HInvokeDynamicGetter( | |
| 308 selector, | |
| 309 node.element, | |
| 310 <HInstruction>[constant, node.inputs[1]], | |
| 311 node.instructionType); | |
| 312 } else if (selector.isSetter) { | |
| 313 instruction = new HInvokeDynamicSetter( | |
| 314 selector, | |
| 315 node.element, | |
| 316 <HInstruction>[constant, node.inputs[1], node.inputs[2]], | |
| 317 node.instructionType); | |
| 318 } else { | |
| 319 List<HInstruction> inputs = new List<HInstruction>.from(node.inputs); | |
| 320 inputs[0] = constant; | |
| 321 instruction = new HInvokeDynamicMethod( | |
| 322 selector, inputs, node.instructionType, true); | |
| 323 } | |
| 324 | |
| 325 HBasicBlock block = node.block; | |
| 326 block.addAfter(node, instruction); | |
| 327 block.rewrite(node, instruction); | |
| 328 return true; | |
| 329 } | |
| 330 } | |
| OLD | NEW |