| 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 |