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 |