OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library tree_ir.optimization.pull_into_initializers; | 5 library tree_ir.optimization.pull_into_initializers; |
6 | 6 |
7 import 'optimization.dart' show Pass; | 7 import 'optimization.dart' show Pass; |
8 import '../tree_ir_nodes.dart'; | 8 import '../tree_ir_nodes.dart'; |
9 | 9 |
| 10 /// Where a variable has been assigned. |
| 11 enum AssignArea { |
| 12 /// The variable is only assigned in the initializer block. |
| 13 Initializer, |
| 14 |
| 15 // The variable has at least one assignment outside the initializer block. |
| 16 Anywhere, |
| 17 } |
| 18 |
10 /// Pulls assignment expressions to the top of the function body so they can be | 19 /// Pulls assignment expressions to the top of the function body so they can be |
11 /// translated into declaration-site variable initializaters. | 20 /// translated into declaration-site variable initializaters. |
12 /// | 21 /// |
13 /// This reverts the assignment expression propagation performed by | 22 /// This reverts the assignment expression propagation performed by |
14 /// [StatementRewriter] in cases where it not beneficial. | 23 /// [StatementRewriter] in cases where it not beneficial. |
15 /// | 24 /// |
16 /// EXAMPLE: | 25 /// EXAMPLE: |
17 /// | 26 /// |
18 /// var x = foo(), | 27 /// var x = foo(), |
19 /// y = bar(x); | 28 /// y = bar(x); |
(...skipping 21 matching lines...) Expand all Loading... |
41 /// baz(x, y, y); | 50 /// baz(x, y, y); |
42 /// | 51 /// |
43 /// ==> [StatementRewriter] | 52 /// ==> [StatementRewriter] |
44 /// | 53 /// |
45 /// var y; | 54 /// var y; |
46 /// baz(foo(), y = bar(), y); | 55 /// baz(foo(), y = bar(), y); |
47 /// | 56 /// |
48 /// [PullIntoInitializers] cannot pull `y` into an initializer because | 57 /// [PullIntoInitializers] cannot pull `y` into an initializer because |
49 /// the impure expressions `foo()` and `bar()` would then be swapped. | 58 /// the impure expressions `foo()` and `bar()` would then be swapped. |
50 /// | 59 /// |
51 class PullIntoInitializers extends ExpressionVisitor<Expression> | 60 class PullIntoInitializers extends RecursiveTransformer |
52 implements Pass { | 61 implements Pass { |
53 String get passName => 'Pull into initializers'; | 62 String get passName => 'Pull into initializers'; |
54 | 63 |
55 Set<Variable> assignedVariables = new Set<Variable>(); | 64 /// Denotes where each variable is currently assigned. |
| 65 /// |
| 66 /// Variables without assignments are absent from the map. |
| 67 Map<Variable, AssignArea> assignArea = <Variable, AssignArea>{}; |
56 | 68 |
57 /// The fragment between [first] and [last] holds the statements | 69 /// The fragment between [first] and [last] holds the statements |
58 /// we pulled into the initializer block. | 70 /// we pulled into the initializer block. |
59 /// | 71 /// |
60 /// The *initializer block* is a sequence of [ExpressionStatement]s with | 72 /// The "initializer block" is a sequence of [ExpressionStatement]s with |
61 /// [Assign]s that we create in the beginning of the body, with the intent | 73 /// [Assign]s that we create in the beginning of the body, with the intent |
62 /// that code generation will convert them to variable initializers. | 74 /// that code generation will convert them to variable initializers. |
63 /// | 75 /// |
64 /// The block is empty when both are `null`. | 76 /// The block is empty when both are `null`. |
65 Statement first, last; | 77 Statement first, last; |
66 | 78 |
67 /// True if an impure expression has been returned by visitExpression. | 79 /// The number of impure expressions separating the current program point |
| 80 /// from the initializer block. |
68 /// | 81 /// |
69 /// Expressions cannot be pulled into an initializer if this might reorder | 82 /// A pure expression is an expression that cannot throw, diverge, have side |
70 /// impure expressions. | 83 /// effects, or depend on mutable state. |
71 /// | 84 /// |
72 /// A visit method may not be called while this flag is set, meaning all | 85 /// As a special case, variable uses are also considered pure when their only |
73 /// visitor methods must check the flag between visiting subexpressions. | 86 /// reaching definition is an assignment in the initializer block. |
74 bool seenImpure; | 87 int impureCounter = 0; |
| 88 |
| 89 /// The number of assignments separating the current program point from the |
| 90 /// initializer block. Note that these are also counted as impure expressions. |
| 91 /// |
| 92 /// Assignments are given special treatment because hoisting an assignment |
| 93 /// may change the reaching definitions of a variable use. The analysis may |
| 94 /// already have considered such a use to be pure, and we must then ensure |
| 95 /// that it remains pure. |
| 96 int assignCounter = 0; |
| 97 |
| 98 /// The number of branch points separating the current program point from |
| 99 /// the initializer block. |
| 100 /// |
| 101 /// We do not pull expressions out of branches, not even pure ones, but |
| 102 /// we sometimes want to traverse branches to check if they are pure. |
| 103 int branchCounter = 0; |
75 | 104 |
76 /// Appends a statement to the initializer block. | 105 /// Appends a statement to the initializer block. |
77 void append(Statement node) { | 106 void append(Statement node) { |
78 if (first == null) { | 107 if (first == null) { |
79 first = last = node; | 108 first = last = node; |
80 } else { | 109 } else { |
81 last.next = node; | 110 last.next = node; |
82 last = node; | 111 last = node; |
83 } | 112 } |
84 } | 113 } |
85 | 114 |
86 /// Pulls assignment expressions from [node] into the initializer block | |
87 /// by calling [append]. | |
88 /// | |
89 /// Returns a transformed expression where the pulled assignments are | |
90 /// replaced by variable uses. | |
91 Expression rewriteExpression(Expression node) { | |
92 seenImpure = false; | |
93 return visitExpression(node); | |
94 } | |
95 | |
96 void rewrite(FunctionDefinition node) { | 115 void rewrite(FunctionDefinition node) { |
97 Statement body = node.body; | 116 for (Variable param in node.parameters) { |
98 assignedVariables.addAll(node.parameters); | 117 assignArea[param] = AssignArea.Initializer; |
99 | |
100 // [body] represents the first statement after the initializer block. | |
101 // Repeatedly pull assignment statements into the initializer block. | |
102 while (body is ExpressionStatement) { | |
103 ExpressionStatement stmt = body; | |
104 stmt.expression = rewriteExpression(stmt.expression); | |
105 if (stmt.expression is VariableUse) { | |
106 // The entire expression was pulled into an initializer. | |
107 // This can happen when the expression was an assignment that was | |
108 // pulled into the initializer block and replaced by a variable use. | |
109 // Discard the statement and try to pull in more initializers from | |
110 // the next statement. | |
111 destroyVariableUse(stmt.expression); | |
112 body = stmt.next; | |
113 } else { | |
114 // The whole expression could not be pulled into an initializer, so we | |
115 // have reached the end of the initializer block. | |
116 break; | |
117 } | |
118 } | 118 } |
119 | 119 Statement body = visitStatement(node.body); |
120 // [If] and [Return] statements terminate the initializer block, but the | |
121 // initial expression they contain may be pulled up into an initializer. | |
122 // It's ok to pull an assignment across a label so look for the first | |
123 // non-labeled statement and try to pull its initial subexpression. | |
124 Statement entryNode = unfoldLabeledStatements(body); | |
125 if (entryNode is If) { | |
126 entryNode.condition = rewriteExpression(entryNode.condition); | |
127 } else if (entryNode is Return) { | |
128 entryNode.value = rewriteExpression(entryNode.value); | |
129 } | |
130 | |
131 append(body); | 120 append(body); |
132 assert(first != null); // Because we just appended the body. | 121 assert(first != null); |
133 | |
134 node.body = first; | 122 node.body = first; |
135 } | 123 } |
136 | 124 |
137 void destroyVariableUse(VariableUse node) { | 125 void destroyVariableUse(VariableUse node) { |
138 --node.variable.readCount; | 126 --node.variable.readCount; |
139 } | 127 } |
140 | 128 |
141 Statement unfoldLabeledStatements(Statement node) { | 129 Statement visitExpressionStatement(ExpressionStatement node) { |
142 while (node is LabeledStatement) { | 130 node.expression = visitExpression(node.expression); |
143 node = (node as LabeledStatement).body; | 131 if (node.expression is VariableUse) { |
144 } | 132 // The entire expression was pulled into an initializer. |
| 133 // This can happen when the expression was an assignment that was |
| 134 // pulled into the initializer block and replaced by a variable use. |
| 135 // Discard the statement and try to pull in more initializers from |
| 136 // the next statement. |
| 137 destroyVariableUse(node.expression); |
| 138 return visitStatement(node.next); |
| 139 } |
| 140 node.next = visitStatement(node.next); |
| 141 return node; |
| 142 } |
| 143 |
| 144 Statement visitIf(If node) { |
| 145 node.condition = visitExpression(node.condition); |
| 146 // We could traverse the branches and pull out pure expressions, but |
| 147 // some pure expressions might be too slow for this to pay off. |
| 148 // A CPS transform should decide when things get hoisted out of branches. |
| 149 return node; |
| 150 } |
| 151 |
| 152 Statement visitLabeledStatement(LabeledStatement node) { |
| 153 node.body = visitStatement(node.body); |
| 154 // The 'next' statement might not always get reached, so do not try to |
| 155 // pull expressions up from there. |
| 156 return node; |
| 157 } |
| 158 |
| 159 Statement visitWhileTrue(WhileTrue node) { |
| 160 return node; |
| 161 } |
| 162 |
| 163 Statement visitWhileCondition(WhileCondition node) { |
| 164 return node; |
| 165 } |
| 166 |
| 167 Statement visitTry(Try node) { |
145 return node; | 168 return node; |
146 } | 169 } |
147 | 170 |
148 Expression visitAssign(Assign node) { | 171 Expression visitAssign(Assign node) { |
149 assert(!seenImpure); | 172 bool inImpureContext = impureCounter > 0; |
| 173 bool inBranch = branchCounter > 0; |
| 174 |
| 175 // Remember the number of impure expression seen yet, so we can tell if |
| 176 // there are any impure expressions on the right-hand side. |
| 177 int impureBefore = impureCounter; |
| 178 int assignmentsBefore = assignCounter; |
150 node.value = visitExpression(node.value); | 179 node.value = visitExpression(node.value); |
151 if (!assignedVariables.add(node.variable)) { | 180 bool rightHandSideIsImpure = (impureCounter > impureBefore); |
152 // This is not the first assignment to the variable, so it cannot be | 181 bool rightHandSideHasAssign = (assignCounter > assignmentsBefore); |
153 // pulled into an initializer. | 182 |
154 // We have to leave the assignment here, and assignments are impure. | 183 bool alreadyAssigned = assignArea.containsKey(node.variable); |
155 seenImpure = true; | 184 |
| 185 // An impure right-hand side cannot be pulled out of impure context. |
| 186 // Expressions should not be pulled out of branches. |
| 187 // If this is not the first assignment, it cannot be hoisted. |
| 188 // If the right-hand side contains an unhoistable assignment, this |
| 189 // assignment cannot be hoisted either. |
| 190 if (inImpureContext && rightHandSideIsImpure || |
| 191 inBranch || |
| 192 alreadyAssigned || |
| 193 rightHandSideHasAssign) { |
| 194 assignArea[node.variable] = AssignArea.Anywhere; |
| 195 ++impureCounter; |
| 196 ++assignCounter; |
156 return node; | 197 return node; |
157 } else { | 198 } |
158 // Pull the assignment into an initializer. | 199 |
159 // We will leave behind a variable use, which is pure, so we can | 200 // Pull the assignment into the initializer. Any side-effects in the |
160 // disregard any impure expressions seen in the right-hand side. | 201 // right-hand side will move into the initializer block, so reset the |
161 seenImpure = false; | 202 // impure counter. |
162 append(new ExpressionStatement(node, null)); | 203 assignArea[node.variable] = AssignArea.Initializer; |
163 return new VariableUse(node.variable); | 204 impureCounter = impureBefore; |
164 } | 205 append(new ExpressionStatement(node, null)); |
165 } | 206 return new VariableUse(node.variable); |
166 | 207 } |
167 void rewriteList(List<Expression> list) { | 208 |
168 for (int i = 0; i < list.length; i++) { | 209 Expression visitVariableUse(VariableUse node) { |
169 list[i] = visitExpression(list[i]); | 210 if (assignArea[node.variable] == AssignArea.Anywhere) { |
170 if (seenImpure) return; | 211 // There is a reaching definition outside the initializer block. |
171 } | 212 ++impureCounter; |
172 } | 213 } |
173 | 214 return node; |
174 Expression visitInvokeStatic(InvokeStatic node) { | 215 } |
175 rewriteList(node.arguments); | 216 |
176 seenImpure = true; | 217 void rewriteList(List<Expression> nodes) { |
177 return node; | 218 for (int i = 0; i < nodes.length; ++i) { |
| 219 nodes[i] = visitExpression(nodes[i]); |
| 220 } |
178 } | 221 } |
179 | 222 |
180 Expression visitInvokeMethod(InvokeMethod node) { | 223 Expression visitInvokeMethod(InvokeMethod node) { |
181 node.receiver = visitExpression(node.receiver); | 224 node.receiver = visitExpression(node.receiver); |
182 if (seenImpure) return node; | 225 if (!node.receiverIsNotNull) { |
| 226 // If the receiver is null, the method lookup throws. |
| 227 ++impureCounter; |
| 228 } |
183 rewriteList(node.arguments); | 229 rewriteList(node.arguments); |
184 seenImpure = true; | 230 ++impureCounter; |
| 231 return node; |
| 232 } |
| 233 |
| 234 Expression visitInvokeStatic(InvokeStatic node) { |
| 235 super.visitInvokeStatic(node); |
| 236 ++impureCounter; |
185 return node; | 237 return node; |
186 } | 238 } |
187 | 239 |
188 Expression visitInvokeMethodDirectly(InvokeMethodDirectly node) { | 240 Expression visitInvokeMethodDirectly(InvokeMethodDirectly node) { |
189 node.receiver = visitExpression(node.receiver); | 241 super.visitInvokeMethodDirectly(node); |
190 if (seenImpure) return node; | 242 ++impureCounter; |
191 rewriteList(node.arguments); | |
192 seenImpure = true; | |
193 return node; | 243 return node; |
194 } | 244 } |
195 | 245 |
196 Expression visitInvokeConstructor(InvokeConstructor node) { | 246 Expression visitInvokeConstructor(InvokeConstructor node) { |
197 rewriteList(node.arguments); | 247 super.visitInvokeConstructor(node); |
198 seenImpure = true; | 248 ++impureCounter; |
199 return node; | |
200 } | |
201 | |
202 Expression visitConcatenateStrings(ConcatenateStrings node) { | |
203 rewriteList(node.arguments); | |
204 seenImpure = true; | |
205 return node; | |
206 } | |
207 | |
208 Expression visitTypeExpression(TypeExpression node) { | |
209 rewriteList(node.arguments); | |
210 return node; | 249 return node; |
211 } | 250 } |
212 | 251 |
213 Expression visitConditional(Conditional node) { | 252 Expression visitConditional(Conditional node) { |
214 node.condition = visitExpression(node.condition); | 253 node.condition = visitExpression(node.condition); |
215 if (seenImpure) return node; | 254 // Visit the branches to detect impure subexpressions, but do not pull |
| 255 // expressions out of the branch. |
| 256 ++branchCounter; |
216 node.thenExpression = visitExpression(node.thenExpression); | 257 node.thenExpression = visitExpression(node.thenExpression); |
217 if (seenImpure) return node; | |
218 node.elseExpression = visitExpression(node.elseExpression); | 258 node.elseExpression = visitExpression(node.elseExpression); |
| 259 --branchCounter; |
219 return node; | 260 return node; |
220 } | 261 } |
221 | 262 |
222 Expression visitLogicalOperator(LogicalOperator node) { | 263 Expression visitLogicalOperator(LogicalOperator node) { |
223 node.left = visitExpression(node.left); | 264 node.left = visitExpression(node.left); |
224 if (seenImpure) return node; | 265 ++branchCounter; |
225 node.right = visitExpression(node.right); | 266 node.right = visitExpression(node.right); |
| 267 --branchCounter; |
226 return node; | 268 return node; |
227 } | 269 } |
228 | 270 |
229 Expression visitLiteralList(LiteralList node) { | 271 Expression visitLiteralList(LiteralList node) { |
230 rewriteList(node.values); | 272 super.visitLiteralList(node); |
231 if (node.type != null) seenImpure = true; // Type casts can throw. | 273 if (node.type != null) { |
| 274 ++impureCounter; // Type casts can throw. |
| 275 } |
232 return node; | 276 return node; |
233 } | 277 } |
234 | 278 |
235 Expression visitLiteralMap(LiteralMap node) { | 279 Expression visitLiteralMap(LiteralMap node) { |
236 for (LiteralMapEntry entry in node.entries) { | 280 super.visitLiteralMap(node); |
237 entry.key = visitExpression(entry.key); | 281 if (node.type != null) { |
238 if (seenImpure) return node; | 282 ++impureCounter; // Type casts can throw. |
239 entry.value = visitExpression(entry.value); | 283 } |
240 if (seenImpure) return node; | |
241 } | |
242 if (node.type != null) seenImpure = true; // Type casts can throw. | |
243 return node; | 284 return node; |
244 } | 285 } |
245 | 286 |
246 Expression visitTypeOperator(TypeOperator node) { | 287 Expression visitTypeOperator(TypeOperator node) { |
247 node.value = visitExpression(node.value); | 288 super.visitTypeOperator(node); |
248 if (seenImpure) return node; | 289 if (!node.isTypeTest) { |
249 rewriteList(node.typeArguments); | 290 ++impureCounter; // Type casts can throw. |
250 if (!node.isTypeTest) seenImpure = true; // Type cast can throw. | 291 } |
| 292 return node; |
| 293 } |
| 294 |
| 295 Expression visitGetField(GetField node) { |
| 296 super.visitGetField(node); |
| 297 ++impureCounter; |
| 298 return node; |
| 299 } |
| 300 |
| 301 Expression visitSetField(SetField node) { |
| 302 super.visitSetField(node); |
| 303 ++impureCounter; |
| 304 return node; |
| 305 } |
| 306 |
| 307 Expression visitGetStatic(GetStatic node) { |
| 308 ++impureCounter; |
| 309 return node; |
| 310 } |
| 311 |
| 312 Expression visitSetStatic(SetStatic node) { |
| 313 super.visitSetStatic(node); |
| 314 ++impureCounter; |
251 return node; | 315 return node; |
252 } | 316 } |
253 | 317 |
254 void visitInnerFunction(FunctionDefinition node) { | 318 void visitInnerFunction(FunctionDefinition node) { |
255 new PullIntoInitializers().rewrite(node); | 319 new PullIntoInitializers().rewrite(node); |
256 } | 320 } |
257 | 321 |
258 Expression visitFunctionExpression(FunctionExpression node) { | 322 Expression visitFunctionExpression(FunctionExpression node) { |
259 visitInnerFunction(node.definition); | 323 visitInnerFunction(node.definition); |
260 return node; | 324 return node; |
261 } | 325 } |
262 | 326 |
263 Expression visitGetField(GetField node) { | |
264 node.object = visitExpression(node.object); | |
265 seenImpure = true; | |
266 return node; | |
267 } | |
268 | |
269 Expression visitSetField(SetField node) { | |
270 node.object = visitExpression(node.object); | |
271 if (seenImpure) return node; | |
272 node.value = visitExpression(node.value); | |
273 seenImpure = true; | |
274 return node; | |
275 } | |
276 | |
277 Expression visitGetStatic(GetStatic node) { | |
278 seenImpure = true; | |
279 return node; | |
280 } | |
281 | |
282 Expression visitSetStatic(SetStatic node) { | |
283 node.value = visitExpression(node.value); | |
284 seenImpure = true; | |
285 return node; | |
286 } | |
287 | |
288 Expression visitCreateBox(CreateBox node) { | |
289 return node; | |
290 } | |
291 | |
292 Expression visitCreateInstance(CreateInstance node) { | |
293 rewriteList(node.arguments); | |
294 return node; | |
295 } | |
296 | |
297 Expression visitReifyRuntimeType(ReifyRuntimeType node) { | |
298 node.value = visitExpression(node.value); | |
299 return node; | |
300 } | |
301 | |
302 Expression visitReadTypeVariable(ReadTypeVariable node) { | |
303 node.target = visitExpression(node.target); | |
304 return node; | |
305 } | |
306 | |
307 Expression visitConstant(Constant node) { | |
308 return node; | |
309 } | |
310 | |
311 Expression visitThis(This node) { | |
312 return node; | |
313 } | |
314 | |
315 Expression visitNot(Not node) { | |
316 node.operand = visitExpression(node.operand); | |
317 return node; | |
318 } | |
319 | |
320 Expression visitVariableUse(VariableUse node) { | |
321 return node; | |
322 } | |
323 | |
324 Expression visitCreateInvocationMirror(CreateInvocationMirror node) { | |
325 rewriteList(node.arguments); | |
326 return node; | |
327 } | |
328 | |
329 Expression visitApplyBuiltinOperator(ApplyBuiltinOperator node) { | 327 Expression visitApplyBuiltinOperator(ApplyBuiltinOperator node) { |
330 rewriteList(node.arguments); | 328 rewriteList(node.arguments); |
331 return node; | 329 return node; |
332 } | 330 } |
333 } | 331 } |
OLD | NEW |