OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2015, 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 library dart2js.cps_ir.mutable_ssa; | |
6 | |
7 import 'cps_ir_nodes.dart'; | |
8 import 'optimizers.dart'; | |
9 | |
10 /// Determines which mutable variables should be rewritten to phi assignments | |
11 /// in this pass. | |
12 /// | |
13 /// We do not rewrite variables that have an assignment inside a try block that | |
14 /// does not contain its declaration. | |
15 class MutableVariablePreanalysis extends RecursiveVisitor { | |
16 // Number of try blocks enclosing the current position. | |
17 int currentDepth = 0; | |
18 | |
19 /// Number of try blocks enclosing the declaration of a given mutable | |
20 /// variable. | |
21 /// | |
22 /// All mutable variables seen will be present in the map after the analysis. | |
23 Map<MutableVariable, int> variableDepth = <MutableVariable, int>{}; | |
24 | |
25 /// Variables with an assignment inside a try block that does not contain | |
26 /// its declaration. | |
27 Set<MutableVariable> hasAssignmentInTry = new Set<MutableVariable>(); | |
28 | |
29 void visitLetHandler(LetHandler node) { | |
30 ++currentDepth; | |
31 visit(node.body); | |
32 --currentDepth; | |
33 visit(node.handler); | |
34 } | |
35 | |
36 void processLetMutable(LetMutable node) { | |
37 variableDepth[node.variable] = currentDepth; | |
38 } | |
39 | |
40 void processSetMutableVariable(SetMutableVariable node) { | |
41 MutableVariable variable = node.variable.definition; | |
42 if (currentDepth > variableDepth[variable]) { | |
43 hasAssignmentInTry.add(variable); | |
44 } | |
45 } | |
46 | |
47 /// True if there are no mutable variables or they are all assigned inside | |
48 /// a try block. In this case, there is nothing to do and the pass should | |
49 /// be skipped. | |
50 bool get allMutablesAreAssignedInTryBlocks { | |
51 return hasAssignmentInTry.length == variableDepth.length; | |
52 } | |
53 } | |
54 | |
55 /// Replaces mutable variables with continuation parameters, effectively | |
56 /// bringing them into SSA form. | |
57 /// | |
58 /// This pass is intended to clean up mutable variables that were introduced | |
59 /// by an optimization in the type propagation pass. | |
60 /// | |
61 /// This implementation potentially creates a lot of redundant and dead phi | |
62 /// parameters. These will be cleaned up by redundant phi elimination and | |
63 /// shrinking reductions. | |
64 /// | |
65 /// Discussion: | |
66 /// For methods with a lot of mutable variables, creating all the spurious | |
67 /// parameters might be too expensive. If this is the case, we should | |
68 /// improve this pass to avoid most spurious parameters in practice. | |
69 class MutableVariableEliminator implements Pass { | |
70 String get passName => 'Mutable variable elimination'; | |
71 | |
72 /// Mutable variables currently in scope, in order of declaration. | |
73 /// This list determines the order of the corresponding phi parameters. | |
74 final List<MutableVariable> mutableVariables = <MutableVariable>[]; | |
75 | |
76 /// Number of phi parameters added to the given continuation. | |
77 final Map<Continuation, int> continuationPhiCount = <Continuation, int>{}; | |
78 | |
79 /// Stack of yet unprocessed continuations interleaved with the | |
80 /// mutable variables currently in scope. | |
81 /// | |
82 /// Continuations are processed when taken off the stack and mutable | |
83 /// variables fall out of scope (i.e. removed from [mutableVariables]) when | |
84 /// taken off the stack. | |
85 final List<StackItem> stack = <StackItem>[]; | |
86 | |
87 MutableVariablePreanalysis analysis; | |
88 | |
89 void rewrite(FunctionDefinition node) { | |
90 analysis = new MutableVariablePreanalysis()..visit(node); | |
91 if (analysis.allMutablesAreAssignedInTryBlocks) { | |
92 // Skip the pass if there is nothing to do. | |
93 return; | |
94 } | |
95 processBlock(node.body, <MutableVariable, Primitive>{}); | |
96 while (stack.isNotEmpty) { | |
97 StackItem item = stack.removeLast(); | |
98 if (item is ContinuationItem) { | |
99 processBlock(item.continuation.body, item.environment); | |
100 } else { | |
101 assert(item is VariableItem); | |
102 mutableVariables.removeLast(); | |
103 } | |
104 } | |
105 } | |
106 | |
107 bool shouldRewrite(MutableVariable variable) { | |
108 return !analysis.hasAssignmentInTry.contains(variable); | |
109 } | |
110 | |
111 bool isJoinContinuation(Continuation cont) { | |
112 return !cont.hasExactlyOneUse || | |
113 cont.firstRef.parent is InvokeContinuation; | |
114 } | |
115 | |
116 void removeNode(InteriorNode node) { | |
117 InteriorNode parent = node.parent; | |
118 parent.body = node.body; | |
119 node.body.parent = parent; | |
120 } | |
121 | |
122 /// If some useful source information is attached to exactly one of the | |
123 /// two definitions, the information is copied onto the other. | |
124 void merge(MutableVariable variable, Primitive value) { | |
karlklose
2015/07/14 08:39:44
Maybe call this 'mergeHints.'
asgerf
2015/07/14 09:00:22
Done.
| |
125 if (variable.hint == null) { | |
126 variable.hint = value.hint; | |
127 } else if (value.hint == null) { | |
128 value.hint = variable.hint; | |
129 } | |
130 } | |
131 | |
132 /// Processes a basic block, replacing mutable variable uses with direct | |
133 /// references to their values. | |
134 /// | |
135 /// [environment] is the current value of each mutable variable. The map | |
136 /// will be mutated during the processing. | |
137 /// | |
138 /// Continuations to be processed are put on the stack for later processing. | |
139 void processBlock(Expression node, | |
140 Map<MutableVariable, Primitive> environment) { | |
141 while (node is! TailExpression) { | |
karlklose
2015/07/14 08:39:44
Consider using a for-loop instead to make the trav
asgerf
2015/07/14 09:00:22
Nice idea! Done.
| |
142 if (node is LetMutable && shouldRewrite(node.variable)) { | |
143 // Put the new mutable variable on the stack while processing the body. | |
karlklose
2015/07/14 08:39:44
Merge comment with comment below.
asgerf
2015/07/14 09:00:22
Done.
| |
144 mutableVariables.add(node.variable); | |
145 | |
146 // Pop it off again when done with the body. | |
147 stack.add(new VariableItem()); | |
148 | |
149 // Put the initial value into the environment. | |
150 Primitive value = node.value.definition; | |
151 environment[node.variable] = value; | |
152 | |
153 // Preserve variable names. | |
154 merge(node.variable, value); | |
155 | |
156 // Remove the mutable variable binding. | |
157 node.value.unlink(); | |
158 removeNode(node); | |
159 } else if (node is SetMutableVariable && | |
160 shouldRewrite(node.variable.definition)) { | |
161 // As above, update the environment, preserve variables and remove | |
162 // the mutable variable assignment. | |
163 MutableVariable variable = node.variable.definition; | |
164 environment[variable] = node.value.definition; | |
165 merge(variable, node.value.definition); | |
166 node.value.unlink(); | |
167 removeNode(node); | |
168 } else if (node is LetPrim && node.primitive is GetMutableVariable) { | |
169 GetMutableVariable getter = node.primitive; | |
170 MutableVariable variable = getter.variable.definition; | |
171 if (shouldRewrite(variable)) { | |
172 // Replace with the reaching definition from the environment. | |
173 Primitive value = environment[variable]; | |
174 value.substituteFor(getter); | |
175 merge(variable, value); | |
176 removeNode(node); | |
177 } | |
178 } else if (node is LetCont) { | |
179 // Create phi parameters for each join continuation bound here, and put | |
180 // them on the stack for later processing. | |
181 // Note that non-join continuations are handled at the use-site. | |
182 for (Continuation cont in node.continuations) { | |
183 if (!isJoinContinuation(cont)) continue; | |
184 // Create a phi parameter for every mutable variable in scope. | |
185 // At the same time, build the environment to use for processing | |
186 // the continuation (mapping mutables to phi parameters). | |
187 continuationPhiCount[cont] = mutableVariables.length; | |
188 Map<MutableVariable, Primitive> environment = | |
189 <MutableVariable, Primitive>{}; | |
190 for (MutableVariable variable in mutableVariables) { | |
191 Parameter phi = new Parameter(variable.hint); | |
192 cont.parameters.add(phi); | |
193 phi.parent = cont; | |
194 environment[variable] = phi; | |
195 } | |
196 stack.add(new ContinuationItem(cont, environment)); | |
197 } | |
198 } else if (node is LetHandler) { | |
199 // Process the catch block later and continue into the try block. | |
200 // We can use the same environment object for the try and catch blocks. | |
201 // The current environment bindings cannot change inside the try block | |
202 // because we exclude all variables assigned inside a try block. | |
203 // The environment might be extended with more bindings before we | |
204 // analyze the catch block, but that's ok. | |
205 stack.add(new ContinuationItem(node.handler, environment)); | |
206 } | |
207 | |
208 node = node.next; | |
209 } | |
210 | |
211 // Analyze the terminal node. | |
212 if (node is InvokeContinuation) { | |
213 Continuation cont = node.continuation.definition; | |
214 if (cont.isReturnContinuation) return; | |
215 // This is a call to a join continuation. Add arguments for the phi | |
216 // parameters that were added to this continuation. | |
217 int phiCount = continuationPhiCount[cont]; | |
218 for (int i = 0; i < phiCount; ++i) { | |
219 Primitive value = environment[mutableVariables[i]]; | |
220 Reference<Primitive> arg = new Reference<Primitive>(value); | |
221 node.arguments.add(arg); | |
222 arg.parent = node; | |
223 } | |
224 } else if (node is Branch) { | |
225 // Enqueue both branches with the current environment. | |
226 // Clone the environments once so the processing of one branch does not | |
227 // mutate the environment needed to process the other branch. | |
228 stack.add(new ContinuationItem( | |
229 node.trueContinuation.definition, | |
230 new Map<MutableVariable, Primitive>.from(environment))); | |
231 stack.add(new ContinuationItem( | |
232 node.falseContinuation.definition, | |
233 environment)); | |
234 } else { | |
235 assert(node is Throw || node is Unreachable); | |
236 } | |
237 } | |
238 } | |
239 | |
240 abstract class StackItem {} | |
241 | |
242 /// Represents a mutable variable that is in scope. | |
243 /// | |
244 /// The top-level mutable variable falls out of scope when this item is | |
245 /// taken off the stack. | |
246 class VariableItem extends StackItem {} | |
247 | |
248 /// Represents a yet unprocessed continuation together with the | |
249 /// environment in which to process it. | |
250 class ContinuationItem extends StackItem { | |
251 final Continuation continuation; | |
252 final Map<MutableVariable, Primitive> environment; | |
253 | |
254 ContinuationItem(this.continuation, this.environment); | |
255 } | |
OLD | NEW |