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 |