| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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.logical_rewriter; | 5 library tree_ir.optimization.logical_rewriter; |
| 6 | 6 |
| 7 import '../tree_ir_nodes.dart'; | 7 import '../tree_ir_nodes.dart'; |
| 8 import 'optimization.dart' show Pass; | 8 import 'optimization.dart' show Pass; |
| 9 import '../../constants/values.dart' as values; | 9 import '../../constants/values.dart' as values; |
| 10 | 10 |
| 11 // TODO(asgerf): Update this class to use JS semantics for && and ||. | |
| 12 | |
| 13 /// Rewrites logical expressions to be more compact in the Tree IR. | 11 /// Rewrites logical expressions to be more compact in the Tree IR. |
| 14 /// | 12 /// |
| 15 /// In this class an expression is said to occur in "boolean context" if | 13 /// In this class an expression is said to occur in "boolean context" if |
| 16 /// its result is immediately applied to boolean conversion. | 14 /// its result is immediately applied to boolean conversion. |
| 17 /// | 15 /// |
| 18 /// IF STATEMENTS: | 16 /// IF STATEMENTS: |
| 19 /// | 17 /// |
| 20 /// We apply the following two rules to [If] statements (see [visitIf]). | 18 /// We apply the following two rules to [If] statements (see [visitIf]). |
| 21 /// | 19 /// |
| 22 /// if (E) {} else S ==> if (!E) S else {} (else can be omitted) | 20 /// if (E) {} else S ==> if (!E) S else {} (else can be omitted) |
| (...skipping 19 matching lines...) Expand all Loading... |
| 42 /// | 40 /// |
| 43 /// if (x ? y : false) S1 else S2 | 41 /// if (x ? y : false) S1 else S2 |
| 44 /// ==> | 42 /// ==> |
| 45 /// if (x && y) S1 else S2 | 43 /// if (x && y) S1 else S2 |
| 46 /// | 44 /// |
| 47 /// Conditionals are tricky to rewrite when they occur out of boolean context. | 45 /// Conditionals are tricky to rewrite when they occur out of boolean context. |
| 48 /// Here we must apply more conservative rules, such as: | 46 /// Here we must apply more conservative rules, such as: |
| 49 /// | 47 /// |
| 50 /// x ? true : false ==> !!x | 48 /// x ? true : false ==> !!x |
| 51 /// | 49 /// |
| 52 /// If an operand is known to be a boolean, we can introduce a logical operator: | 50 /// If the possible falsy values of the condition are known, we can sometimes |
| 51 /// introduce a logical operator: |
| 53 /// | 52 /// |
| 54 /// x ? y : false ==> x && y (if y is known to be a boolean) | 53 /// !x ? y : false ==> !x && y |
| 55 /// | |
| 56 /// The following sequence of rewrites demonstrates the merit of these rules: | |
| 57 /// | |
| 58 /// x ? (y ? true : false) : false | |
| 59 /// x ? !!y : false (double negation introduced by [toBoolean]) | |
| 60 /// x && !!y (!!y validated by [isBooleanValued]) | |
| 61 /// x && y (double negation removed by [putInBooleanContext]) | |
| 62 /// | 54 /// |
| 63 class LogicalRewriter extends RecursiveTransformer | 55 class LogicalRewriter extends RecursiveTransformer |
| 64 implements Pass { | 56 implements Pass { |
| 65 String get passName => 'Logical rewriter'; | 57 String get passName => 'Logical rewriter'; |
| 66 | 58 |
| 67 @override | 59 @override |
| 68 void rewrite(FunctionDefinition node) { | 60 void rewrite(FunctionDefinition node) { |
| 69 node.body = visitStatement(node.body); | 61 node.body = visitStatement(node.body); |
| 70 } | 62 } |
| 71 | 63 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 87 node.next = visitStatement(node.next); | 79 node.next = visitStatement(node.next); |
| 88 return node; | 80 return node; |
| 89 } | 81 } |
| 90 | 82 |
| 91 bool isFallthroughBreak(Statement node) { | 83 bool isFallthroughBreak(Statement node) { |
| 92 return node is Break && node.target.binding.next == fallthrough; | 84 return node is Break && node.target.binding.next == fallthrough; |
| 93 } | 85 } |
| 94 | 86 |
| 95 Statement visitIf(If node) { | 87 Statement visitIf(If node) { |
| 96 // If one of the branches is empty (i.e. just a fallthrough), then that | 88 // If one of the branches is empty (i.e. just a fallthrough), then that |
| 97 // branch should preferrably be the 'else' so we won't have to print it. | 89 // branch should preferably be the 'else' so we won't have to print it. |
| 98 // In other words, we wish to perform this rewrite: | 90 // In other words, we wish to perform this rewrite: |
| 99 // if (E) {} else {S} | 91 // if (E) {} else {S} |
| 100 // ==> | 92 // ==> |
| 101 // if (!E) {S} | 93 // if (!E) {S} |
| 102 // In the tree language, empty statements do not exist yet, so we must check | 94 // In the tree language, empty statements do not exist yet, so we must check |
| 103 // if one branch contains a break that can be eliminated by fallthrough. | 95 // if one branch contains a break that can be eliminated by fallthrough. |
| 104 | 96 |
| 105 // Swap branches if then is a fallthrough break. | 97 // Swap branches if then is a fallthrough break. |
| 106 if (isFallthroughBreak(node.thenStatement)) { | 98 if (isFallthroughBreak(node.thenStatement)) { |
| 107 node.condition = new Not(node.condition); | 99 node.condition = new Not(node.condition); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 136 node.condition = makeCondition(node.condition, true, liftNots: false); | 128 node.condition = makeCondition(node.condition, true, liftNots: false); |
| 137 node.body = visitStatement(node.body); | 129 node.body = visitStatement(node.body); |
| 138 node.next = visitStatement(node.next); | 130 node.next = visitStatement(node.next); |
| 139 return node; | 131 return node; |
| 140 } | 132 } |
| 141 | 133 |
| 142 Expression visitNot(Not node) { | 134 Expression visitNot(Not node) { |
| 143 return toBoolean(makeCondition(node.operand, false, liftNots: false)); | 135 return toBoolean(makeCondition(node.operand, false, liftNots: false)); |
| 144 } | 136 } |
| 145 | 137 |
| 138 /// True if the only possible falsy return value of [condition] is [value]. |
| 139 /// |
| 140 /// If [value] is `null` or a truthy value, false is returned. This is to make |
| 141 /// pattern matching more convenient. |
| 142 bool matchesFalsyValue(Expression condition, values.ConstantValue value) { |
| 143 if (value == null) return false; |
| 144 // TODO(asgerf): Here we could really use some more type information, |
| 145 // this is just the best we can do at the moment. |
| 146 return isBooleanValued(condition) && value.isFalse; |
| 147 } |
| 148 |
| 149 /// True if the only possible truthy return value of [condition] is [value]. |
| 150 /// |
| 151 /// If [value] is `null` or a falsy value, false is returned. This is to make |
| 152 /// pattern matching more convenient. |
| 153 bool matchesTruthyValue(Expression condition, values.ConstantValue value) { |
| 154 if (value == null) return false; |
| 155 // TODO(asgerf): Again, more type information could really beef this up. |
| 156 return isBooleanValued(condition) && value.isTrue; |
| 157 } |
| 158 |
| 159 values.ConstantValue getConstant(Expression exp) { |
| 160 return exp is Constant ? exp.value : null; |
| 161 } |
| 162 |
| 146 Expression visitConditional(Conditional node) { | 163 Expression visitConditional(Conditional node) { |
| 147 // node.condition will be visited after the then and else parts, because its | 164 // node.condition will be visited after the then and else parts, because its |
| 148 // polarity depends on what rewrite we use. | 165 // polarity depends on what rewrite we use. |
| 149 node.thenExpression = visitExpression(node.thenExpression); | 166 node.thenExpression = visitExpression(node.thenExpression); |
| 150 node.elseExpression = visitExpression(node.elseExpression); | 167 node.elseExpression = visitExpression(node.elseExpression); |
| 151 | 168 |
| 152 // In the following, we must take care not to eliminate or introduce a | 169 // In the following, we must take care not to eliminate or introduce a |
| 153 // boolean conversion. | 170 // boolean conversion. |
| 154 | 171 |
| 155 // x ? true : false --> !!x | 172 // x ? true : false --> !!x |
| 156 if (isTrue(node.thenExpression) && isFalse(node.elseExpression)) { | 173 if (isTrue(node.thenExpression) && isFalse(node.elseExpression)) { |
| 157 return toBoolean(makeCondition(node.condition, true, liftNots: false)); | 174 return toBoolean(makeCondition(node.condition, true, liftNots: false)); |
| 158 } | 175 } |
| 159 // x ? false : true --> !x | 176 // x ? false : true --> !x |
| 160 if (isFalse(node.thenExpression) && isTrue(node.elseExpression)) { | 177 if (isFalse(node.thenExpression) && isTrue(node.elseExpression)) { |
| 161 return toBoolean(makeCondition(node.condition, false, liftNots: false)); | 178 return toBoolean(makeCondition(node.condition, false, liftNots: false)); |
| 162 } | 179 } |
| 163 | 180 |
| 164 // x ? y : false ==> x && y (if y is known to be a boolean) | 181 // x ? y : false ==> x && y (if x is truthy or false) |
| 165 if (isBooleanValued(node.thenExpression) && isFalse(node.elseExpression)) { | 182 // x ? y : null ==> x && y (if x is truthy or null) |
| 183 // x ? y : 0 ==> x && y (if x is truthy or zero) (and so on...) |
| 184 if (matchesFalsyValue(node.condition, getConstant(node.elseExpression))) { |
| 166 return new LogicalOperator.and( | 185 return new LogicalOperator.and( |
| 167 makeCondition(node.condition, true, liftNots:false), | 186 visitExpression(node.condition), |
| 168 putInBooleanContext(node.thenExpression)); | 187 node.thenExpression); |
| 169 } | 188 } |
| 170 // x ? y : true ==> !x || y (if y is known to be a boolean) | 189 // x ? true : y ==> x || y (if x is falsy or true) |
| 171 if (isBooleanValued(node.thenExpression) && isTrue(node.elseExpression)) { | 190 // x ? 1 : y ==> x || y (if x is falsy or one) (and so on...) |
| 191 if (matchesTruthyValue(node.condition, getConstant(node.thenExpression))) { |
| 172 return new LogicalOperator.or( | 192 return new LogicalOperator.or( |
| 173 makeCondition(node.condition, false, liftNots: false), | 193 visitExpression(node.condition), |
| 174 putInBooleanContext(node.thenExpression)); | 194 node.elseExpression); |
| 175 } | 195 } |
| 176 // x ? true : y ==> x || y (if y if known to be boolean) | 196 // x ? y : true ==> !x || y |
| 177 if (isBooleanValued(node.elseExpression) && isTrue(node.thenExpression)) { | 197 if (isTrue(node.elseExpression)) { |
| 178 return new LogicalOperator.or( | 198 return new LogicalOperator.or( |
| 179 makeCondition(node.condition, true, liftNots: false), | 199 toBoolean(makeCondition(node.condition, false, liftNots: false)), |
| 180 putInBooleanContext(node.elseExpression)); | 200 node.thenExpression); |
| 181 } | 201 } |
| 182 // x ? false : y ==> !x && y (if y is known to be a boolean) | 202 // x ? false : y ==> !x && y |
| 183 if (isBooleanValued(node.elseExpression) && isFalse(node.thenExpression)) { | 203 if (isFalse(node.thenExpression)) { |
| 184 return new LogicalOperator.and( | 204 return new LogicalOperator.and( |
| 185 makeCondition(node.condition, false, liftNots: false), | 205 toBoolean(makeCondition(node.condition, false, liftNots: false)), |
| 186 putInBooleanContext(node.elseExpression)); | 206 node.elseExpression); |
| 187 } | 207 } |
| 188 | 208 |
| 189 node.condition = makeCondition(node.condition, true); | 209 node.condition = makeCondition(node.condition, true); |
| 190 | 210 |
| 191 // !x ? y : z ==> x ? z : y | 211 // !x ? y : z ==> x ? z : y |
| 192 if (node.condition is Not) { | 212 if (node.condition is Not) { |
| 193 node.condition = (node.condition as Not).operand; | 213 node.condition = (node.condition as Not).operand; |
| 194 Expression tmp = node.thenExpression; | 214 Expression tmp = node.thenExpression; |
| 195 node.thenExpression = node.elseExpression; | 215 node.thenExpression = node.elseExpression; |
| 196 node.elseExpression = tmp; | 216 node.elseExpression = tmp; |
| 197 } | 217 } |
| 198 | 218 |
| 199 return node; | 219 return node; |
| 200 } | 220 } |
| 201 | 221 |
| 202 Expression visitLogicalOperator(LogicalOperator node) { | 222 Expression visitLogicalOperator(LogicalOperator node) { |
| 203 node.left = makeCondition(node.left, true); | 223 node.left = visitExpression(node.left); |
| 204 node.right = makeCondition(node.right, true); | 224 node.right = visitExpression(node.right); |
| 205 return node; | 225 return node; |
| 206 } | 226 } |
| 207 | 227 |
| 208 /// True if the given expression is known to evaluate to a boolean. | 228 /// True if the given expression is known to evaluate to a boolean. |
| 209 /// This will not recursively traverse [Conditional] expressions, but if | 229 /// This will not recursively traverse [Conditional] expressions, but if |
| 210 /// applied to the result of [visitExpression] conditionals will have been | 230 /// applied to the result of [visitExpression] conditionals will have been |
| 211 /// rewritten anyway. | 231 /// rewritten anyway. |
| 212 bool isBooleanValued(Expression e) { | 232 bool isBooleanValued(Expression e) { |
| 213 return isTrue(e) || | 233 return isTrue(e) || |
| 214 isFalse(e) || | 234 isFalse(e) || |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 246 case BuiltinOperator.NumLe: | 266 case BuiltinOperator.NumLe: |
| 247 case BuiltinOperator.NumGt: | 267 case BuiltinOperator.NumGt: |
| 248 case BuiltinOperator.NumGe: | 268 case BuiltinOperator.NumGe: |
| 249 return null; | 269 return null; |
| 250 | 270 |
| 251 default: | 271 default: |
| 252 return null; | 272 return null; |
| 253 } | 273 } |
| 254 } | 274 } |
| 255 | 275 |
| 256 /// Rewrite an expression that was originally processed in a non-boolean | |
| 257 /// context. | |
| 258 Expression putInBooleanContext(Expression e) { | |
| 259 if (e is Not && e.operand is Not) { | |
| 260 return (e.operand as Not).operand; | |
| 261 } else { | |
| 262 return e; | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 /// Forces a boolean conversion of the given expression. | 276 /// Forces a boolean conversion of the given expression. |
| 267 Expression toBoolean(Expression e) { | 277 Expression toBoolean(Expression e) { |
| 268 if (isBooleanValued(e)) | 278 if (isBooleanValued(e)) |
| 269 return e; | 279 return e; |
| 270 else | 280 else |
| 271 return new Not(new Not(e)); | 281 return new Not(new Not(e)); |
| 272 } | 282 } |
| 273 | 283 |
| 274 /// Creates an equivalent boolean expression. The expression must occur in a | 284 /// Creates an equivalent boolean expression. The expression must occur in a |
| 275 /// context where its result is immediately subject to boolean conversion. | 285 /// context where its result is immediately subject to boolean conversion. |
| 276 /// If [polarity] if false, the negated condition will be created instead. | 286 /// If [polarity] if false, the negated condition will be created instead. |
| 277 /// If [liftNots] is true (default) then Not expressions will be lifted toward | 287 /// If [liftNots] is true (default) then Not expressions will be lifted toward |
| 278 /// the root the condition so they can be eliminated by the caller. | 288 /// the root of the condition so they can be eliminated by the caller. |
| 279 Expression makeCondition(Expression e, bool polarity, {bool liftNots:true}) { | 289 Expression makeCondition(Expression e, bool polarity, {bool liftNots:true}) { |
| 280 if (e is Not) { | 290 if (e is Not) { |
| 281 // !!E ==> E | 291 // !!E ==> E |
| 282 return makeCondition(e.operand, !polarity, liftNots: liftNots); | 292 return makeCondition(e.operand, !polarity, liftNots: liftNots); |
| 283 } | 293 } |
| 284 if (e is LogicalOperator) { | 294 if (e is LogicalOperator) { |
| 285 // If polarity=false, then apply the rewrite !(x && y) ==> !x || !y | 295 // If polarity=false, then apply the rewrite !(x && y) ==> !x || !y |
| 286 e.left = makeCondition(e.left, polarity); | 296 e.left = makeCondition(e.left, polarity); |
| 287 e.right = makeCondition(e.right, polarity); | 297 e.right = makeCondition(e.right, polarity); |
| 288 if (!polarity) { | 298 if (!polarity) { |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 393 | 403 |
| 394 Expression makeOr(Expression e1, Expression e2, {bool liftNots: true}) { | 404 Expression makeOr(Expression e1, Expression e2, {bool liftNots: true}) { |
| 395 if (e1 is Not && e2 is Not && liftNots) { | 405 if (e1 is Not && e2 is Not && liftNots) { |
| 396 return new Not(new LogicalOperator.and(e1.operand, e2.operand)); | 406 return new Not(new LogicalOperator.and(e1.operand, e2.operand)); |
| 397 } else { | 407 } else { |
| 398 return new LogicalOperator.or(e1, e2); | 408 return new LogicalOperator.or(e1, e2); |
| 399 } | 409 } |
| 400 } | 410 } |
| 401 } | 411 } |
| 402 | 412 |
| OLD | NEW |