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 |