Index: lib/src/compiler/code_generator.dart |
diff --git a/lib/src/compiler/code_generator.dart b/lib/src/compiler/code_generator.dart |
index bfc7dc95e960e65c47c14f4b018e088ee168da51..c5acb9e5c89524a5ba3d8d07edbe16f0588d1c83 100644 |
--- a/lib/src/compiler/code_generator.dart |
+++ b/lib/src/compiler/code_generator.dart |
@@ -3,6 +3,7 @@ |
// BSD-style license that can be found in the LICENSE file. |
import 'dart:collection' show HashMap, HashSet; |
+import 'dart:math' show min, max; |
import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
import 'package:analyzer/dart/ast/ast.dart'; |
@@ -2852,11 +2853,27 @@ class CodeGenerator extends GeneralizingAstVisitor |
return bitwise('# ^ #'); |
case TokenType.GT_GT: |
- // TODO(sra): Detect when JS shift does the right thing. |
+ int shiftCount = _asIntInRange(right, 0, 31); |
+ if (_is31BitUnsigned(left) && shiftCount != null) { |
+ return binary('# >> #'); |
+ } |
+ if (_isDefinitelyNonNegative(left) && shiftCount != null) { |
+ return binary('# >>> #'); |
+ } |
+ // TODO(sra): If the context selects out only bits that can't be |
+ // affected by the sign position we can use any JavaScript shift. |
+ // E.g. `(x >> 6) & 3`. |
return _emitSend(left, op.lexeme, [right]); |
case TokenType.LT_LT: |
- // TODO(sra): Detect when JS shift does the right thing. |
+ if (_is31BitUnsigned(node)) { |
+ // Result is 31 bit unsigned which implies the shift count was small |
+ // enough not to pollute the sign bit. |
+ return binary('# << #'); |
+ } |
+ if (_asIntInRange(right, 0, 31) != null) { |
+ return _coerceBitOperationResultToUnsigned(node, binary('# << #')); |
+ } |
return _emitSend(left, op.lexeme, [right]); |
default: |
@@ -2874,9 +2891,119 @@ class CodeGenerator extends GeneralizingAstVisitor |
/// signed results. |
JS.Expression _coerceBitOperationResultToUnsigned( |
Expression node, JS.Expression operation) { |
+ // Don't coerce if the parent will coerce. |
+ AstNode parent = _parentOperation(node); |
+ if (_nodeIsBitwiseOperation(parent)) return operation; |
+ |
+ // Don't do a no-op coerce if the most significant bit is zero. |
+ if (_is31BitUnsigned(node)) return operation; |
+ |
+ // TODO(sra): If the consumer of the expression is '==' or '!=' to a |
+ // constant that fits in 31 bits, adding a coercion does not change the |
+ // result of the comparision, e.g. `a & ~b == 0`. |
return js.call('# >>> 0', operation); |
} |
+ AstNode _parentOperation(AstNode node) { |
+ node = node.parent; |
+ while (node is ParenthesizedExpression) node = node.parent; |
+ return node; |
+ } |
+ |
+ bool _nodeIsBitwiseOperation(AstNode node) { |
+ if (node is BinaryExpression) { |
+ switch (node.operator.type) { |
+ case TokenType.AMPERSAND: |
+ case TokenType.BAR: |
+ case TokenType.CARET: |
+ return true; |
+ } |
+ return false; |
+ } |
+ if (node is PrefixExpression) { |
+ return node.operator.type == TokenType.TILDE; |
+ } |
+ return false; |
+ } |
+ |
+ Expression _skipParentheses(Expression expr) { |
+ while (expr is ParenthesizedExpression) { |
+ ParenthesizedExpression parenExpr = expr; |
+ expr = parenExpr.expression; |
+ } |
+ return expr; |
+ } |
+ |
+ int _asIntInRange(Expression expr, int low, int high) { |
+ expr = _skipParentheses(expr); |
+ if (expr is IntegerLiteral) { |
+ if (expr.value >= low && expr.value <= high) return expr.value; |
+ } |
+ return null; |
+ } |
+ |
+ bool _isDefinitelyNonNegative(Expression expr) { |
+ expr = _skipParentheses(expr); |
+ if (expr is IntegerLiteral) { |
+ return expr.value >= 0; |
+ } |
+ if (_nodeIsBitwiseOperation(expr)) return true; |
+ // TODO(sra): Lengths of known list types etc. |
+ return false; |
+ } |
+ |
+ /// Determines if the result of evaluating [expr] will be an non-negative |
+ /// value that fits in 31 bits. |
+ bool _is31BitUnsigned(Expression expr) { |
+ const int MAX = 32; // Includes larger and negative values. |
+ /// Determines how many bits are required to hold result of evaluation |
+ /// [expr]. [depth] is used to bound exploration of huge expressions. |
+ int bitWidth(Expression expr, int depth) { |
+ if (expr is IntegerLiteral) { |
+ return expr.value >= 0 ? expr.value.bitLength : MAX; |
+ } |
+ if (++depth > 5) return MAX; |
+ if (expr is BinaryExpression) { |
+ var left = _skipParentheses(expr.leftOperand); |
+ var right = _skipParentheses(expr.rightOperand); |
+ switch (expr.operator.type) { |
+ case TokenType.AMPERSAND: |
+ return min(bitWidth(left, depth), bitWidth(right, depth)); |
+ |
+ case TokenType.BAR: |
+ case TokenType.CARET: |
+ return max(bitWidth(left, depth), bitWidth(right, depth)); |
+ |
+ case TokenType.GT_GT: |
+ int shiftValue = _asIntInRange(right, 0, 31); |
+ if (shiftValue != null) { |
+ int leftWidth = bitWidth(left, depth); |
+ return leftWidth == MAX ? MAX : max(0, leftWidth - shiftValue); |
+ } |
+ return MAX; |
+ |
+ case TokenType.LT_LT: |
+ int leftWidth = bitWidth(left, depth); |
+ int shiftValue = _asIntInRange(right, 0, 31); |
+ if (shiftValue != null) { |
+ return min(MAX, leftWidth + shiftValue); |
+ } |
+ int rightWidth = bitWidth(right, depth); |
+ if (rightWidth <= 5) { |
+ // e.g. `1 << (x & 7)` has a rightWidth of 3, so shifts by up to |
+ // (1 << 3) - 1 == 7 bits. |
+ return min(MAX, leftWidth + ((1 << rightWidth) - 1)); |
+ } |
+ return MAX; |
+ default: |
+ return MAX; |
+ } |
+ } |
+ return MAX; |
+ } |
+ return bitWidth(expr, 0) < 32; |
+ } |
+ |
/// If the type [t] is [int] or [double], or a type parameter |
/// bounded by [int], [double] or [num] returns [num]. |
/// Otherwise returns [t]. |