| 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 // If the interceptor is used by multiple instructions, specialize | |
| 171 // it with a set of classes it intercepts. | |
| 172 Set<ClassElement> interceptedClasses; | |
| 173 JavaScriptBackend backend = compiler.backend; | |
| 174 HInstruction dominator = findDominator(node.usedBy); | |
| 175 // If there is a call that dominates all other uses, we can use just the | |
| 176 // selector of that instruction. | |
| 177 if (dominator is HInvokeDynamic && | |
| 178 dominator.isCallOnInterceptor(compiler) && | |
| 179 node == dominator.receiver) { | |
| 180 interceptedClasses = | |
| 181 backend.getInterceptedClassesOn(dominator.selector.name); | |
| 182 | |
| 183 // If we found that we need number, we must still go through all | |
| 184 // uses to check if they require int, or double. | |
| 185 if (interceptedClasses.contains(backend.jsNumberClass) && | |
| 186 !(interceptedClasses.contains(backend.jsDoubleClass) || | |
| 187 interceptedClasses.contains(backend.jsIntClass))) { | |
| 188 for (HInstruction user in node.usedBy) { | |
| 189 if (user is! HInvoke) continue; | |
| 190 Set<ClassElement> intercepted = | |
| 191 backend.getInterceptedClassesOn(user.selector.name); | |
| 192 if (intercepted.contains(backend.jsIntClass)) { | |
| 193 interceptedClasses.add(backend.jsIntClass); | |
| 194 } | |
| 195 if (intercepted.contains(backend.jsDoubleClass)) { | |
| 196 interceptedClasses.add(backend.jsDoubleClass); | |
| 197 } | |
| 198 } | |
| 199 } | |
| 200 } else { | |
| 201 interceptedClasses = new Set<ClassElement>(); | |
| 202 for (HInstruction user in node.usedBy) { | |
| 203 if (user is HInvokeDynamic && | |
| 204 user.isCallOnInterceptor(compiler) && | |
| 205 node == user.receiver) { | |
| 206 interceptedClasses.addAll( | |
| 207 backend.getInterceptedClassesOn(user.selector.name)); | |
| 208 } else { | |
| 209 // Use a most general interceptor for other instructions, example, | |
| 210 // is-checks and escaping interceptors. | |
| 211 interceptedClasses.addAll(backend.interceptedClasses); | |
| 212 break; | |
| 213 } | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 HInstruction receiver = node.receiver; | |
| 218 if (canUseSelfForInterceptor(receiver, interceptedClasses)) { | |
| 219 return rewriteToUseSelfAsInterceptor(node, receiver); | |
| 220 } | |
| 221 | |
| 222 // Try computing a constant interceptor. | |
| 223 HInstruction constantInterceptor = | |
| 224 tryComputeConstantInterceptor(receiver, interceptedClasses); | |
| 225 if (constantInterceptor != null) { | |
| 226 node.block.rewrite(node, constantInterceptor); | |
| 227 return false; | |
| 228 } | |
| 229 | |
| 230 node.interceptedClasses = interceptedClasses; | |
| 231 | |
| 232 // Try creating a one-shot interceptor. | |
| 233 if (node.usedBy.length != 1) return false; | |
| 234 if (node.usedBy[0] is !HInvokeDynamic) return false; | |
| 235 | |
| 236 HInvokeDynamic user = node.usedBy[0]; | |
| 237 | |
| 238 // If [node] was loop hoisted, we keep the interceptor. | |
| 239 if (!user.hasSameLoopHeaderAs(node)) return false; | |
| 240 | |
| 241 // Replace the user with a [HOneShotInterceptor]. | |
| 242 HConstant nullConstant = graph.addConstantNull(compiler); | |
| 243 List<HInstruction> inputs = new List<HInstruction>.from(user.inputs); | |
| 244 inputs[0] = nullConstant; | |
| 245 HOneShotInterceptor interceptor = new HOneShotInterceptor( | |
| 246 user.selector, inputs, user.instructionType, node.interceptedClasses); | |
| 247 interceptor.sourcePosition = user.sourcePosition; | |
| 248 interceptor.sourceElement = user.sourceElement; | |
| 249 | |
| 250 HBasicBlock block = user.block; | |
| 251 block.addAfter(user, interceptor); | |
| 252 block.rewrite(user, interceptor); | |
| 253 block.remove(user); | |
| 254 return true; | |
| 255 } | |
| 256 | |
| 257 bool rewriteToUseSelfAsInterceptor(HInterceptor node, HInstruction receiver) { | |
| 258 for (HInstruction user in node.usedBy.toList()) { | |
| 259 if (user is HIs) { | |
| 260 user.changeUse(node, receiver); | |
| 261 } else { | |
| 262 // Use the potentially self-argument as new receiver. Note that the | |
| 263 // self-argument could potentially have a tighter type than the | |
| 264 // receiver which was the input to the interceptor. | |
| 265 assert(user.inputs[0] == node); | |
| 266 assert(receiver.nonCheck() == user.inputs[1].nonCheck()); | |
| 267 user.changeUse(node, user.inputs[1]); | |
| 268 } | |
| 269 } | |
| 270 return false; | |
| 271 } | |
| 272 | |
| 273 bool visitOneShotInterceptor(HOneShotInterceptor node) { | |
| 274 HInstruction constant = tryComputeConstantInterceptor( | |
| 275 node.inputs[1], node.interceptedClasses); | |
| 276 | |
| 277 if (constant == null) return false; | |
| 278 | |
| 279 Selector selector = node.selector; | |
| 280 HInstruction instruction; | |
| 281 if (selector.isGetter) { | |
| 282 instruction = new HInvokeDynamicGetter( | |
| 283 selector, | |
| 284 node.element, | |
| 285 <HInstruction>[constant, node.inputs[1]], | |
| 286 node.instructionType); | |
| 287 } else if (selector.isSetter) { | |
| 288 instruction = new HInvokeDynamicSetter( | |
| 289 selector, | |
| 290 node.element, | |
| 291 <HInstruction>[constant, node.inputs[1], node.inputs[2]], | |
| 292 node.instructionType); | |
| 293 } else { | |
| 294 List<HInstruction> inputs = new List<HInstruction>.from(node.inputs); | |
| 295 inputs[0] = constant; | |
| 296 instruction = new HInvokeDynamicMethod( | |
| 297 selector, inputs, node.instructionType, true); | |
| 298 } | |
| 299 | |
| 300 HBasicBlock block = node.block; | |
| 301 block.addAfter(node, instruction); | |
| 302 block.rewrite(node, instruction); | |
| 303 return true; | |
| 304 } | |
| 305 } | |
| OLD | NEW |