mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
dart2js: Constant fold num.round()
Also num.round() is pure R=sigmund@google.com Committed:3d28d08413
Review URL: https://codereview.chromium.org/2400853003 . Reverted:2eadef53b7
This commit is contained in:
parent
2eadef53b7
commit
c91cbe1fc5
5 changed files with 251 additions and 18 deletions
|
@ -348,20 +348,13 @@ class IfNullOperation implements BinaryOperation {
|
|||
apply(left, right) => left ?? right;
|
||||
}
|
||||
|
||||
abstract class CodeUnitAtOperation implements BinaryOperation {
|
||||
final String name = 'charCodeAt';
|
||||
class CodeUnitAtOperation implements BinaryOperation {
|
||||
String get name => 'charCodeAt';
|
||||
const CodeUnitAtOperation();
|
||||
ConstantValue fold(ConstantValue left, ConstantValue right) => null;
|
||||
apply(left, right) => left.codeUnitAt(right);
|
||||
}
|
||||
|
||||
class CodeUnitAtConstantOperation extends CodeUnitAtOperation {
|
||||
const CodeUnitAtConstantOperation();
|
||||
ConstantValue fold(ConstantValue left, ConstantValue right) {
|
||||
// 'a'.codeUnitAt(0) is not a constant expression.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class CodeUnitAtRuntimeOperation extends CodeUnitAtOperation {
|
||||
const CodeUnitAtRuntimeOperation();
|
||||
IntConstantValue fold(ConstantValue left, ConstantValue right) {
|
||||
|
@ -379,6 +372,14 @@ class CodeUnitAtRuntimeOperation extends CodeUnitAtOperation {
|
|||
}
|
||||
}
|
||||
|
||||
class UnfoldedUnaryOperation implements UnaryOperation {
|
||||
final String name;
|
||||
const UnfoldedUnaryOperation(this.name);
|
||||
ConstantValue fold(ConstantValue constant) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A constant system implementing the Dart semantics. This system relies on
|
||||
* the underlying runtime-system. That is, if dart2js is run in an environment
|
||||
|
@ -409,7 +410,8 @@ class DartConstantSystem extends ConstantSystem {
|
|||
final shiftRight = const ShiftRightOperation();
|
||||
final subtract = const SubtractOperation();
|
||||
final truncatingDivide = const TruncatingDivideOperation();
|
||||
final codeUnitAt = const CodeUnitAtConstantOperation();
|
||||
final codeUnitAt = const CodeUnitAtOperation();
|
||||
final round = const UnfoldedUnaryOperation('round');
|
||||
|
||||
const DartConstantSystem();
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ abstract class ConstantSystem {
|
|||
BinaryOperation get truncatingDivide;
|
||||
|
||||
BinaryOperation get codeUnitAt;
|
||||
UnaryOperation get round;
|
||||
|
||||
const ConstantSystem();
|
||||
|
||||
|
|
|
@ -165,12 +165,50 @@ class JavaScriptIdentityOperation implements BinaryOperation {
|
|||
apply(left, right) => identical(left, right);
|
||||
}
|
||||
|
||||
class JavaScriptRoundOperation implements UnaryOperation {
|
||||
const JavaScriptRoundOperation();
|
||||
String get name => DART_CONSTANT_SYSTEM.round.name;
|
||||
ConstantValue fold(ConstantValue constant) {
|
||||
// Be careful to round() only values that do not throw on either the host or
|
||||
// target platform.
|
||||
ConstantValue tryToRound(num value) {
|
||||
// Due to differences between browsers, only 'round' easy cases. Avoid
|
||||
// cases where nudging the value up or down changes the answer.
|
||||
// 13 digits is safely within the ~15 digit precision of doubles.
|
||||
const severalULP = 0.0000000000001;
|
||||
// Use 'roundToDouble()' to avoid exceptions on rounding the nudged value.
|
||||
double rounded = value.roundToDouble();
|
||||
double rounded1 = (value * (1.0 + severalULP)).roundToDouble();
|
||||
double rounded2 = (value * (1.0 - severalULP)).roundToDouble();
|
||||
if (rounded != rounded1 || rounded != rounded2) return null;
|
||||
return JAVA_SCRIPT_CONSTANT_SYSTEM
|
||||
.convertToJavaScriptConstant(new IntConstantValue(value.round()));
|
||||
}
|
||||
|
||||
if (constant.isInt) {
|
||||
IntConstantValue intConstant = constant;
|
||||
int value = intConstant.primitiveValue;
|
||||
if (value >= -double.MAX_FINITE && value <= double.MAX_FINITE) {
|
||||
return tryToRound(value);
|
||||
}
|
||||
}
|
||||
if (constant.isDouble) {
|
||||
DoubleConstantValue doubleConstant = constant;
|
||||
double value = doubleConstant.primitiveValue;
|
||||
// NaN and infinities will throw.
|
||||
if (value.isNaN) return null;
|
||||
if (value.isInfinite) return null;
|
||||
return tryToRound(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant system following the semantics for Dart code that has been
|
||||
* compiled to JavaScript.
|
||||
*/
|
||||
class JavaScriptConstantSystem extends ConstantSystem {
|
||||
final int BITS31 = 0x8FFFFFFF;
|
||||
final int BITS32 = 0xFFFFFFFF;
|
||||
|
||||
final add = const JavaScriptAddOperation();
|
||||
|
@ -203,6 +241,7 @@ class JavaScriptConstantSystem extends ConstantSystem {
|
|||
final truncatingDivide = const JavaScriptBinaryArithmeticOperation(
|
||||
const TruncatingDivideOperation());
|
||||
final codeUnitAt = const CodeUnitAtRuntimeOperation();
|
||||
final round = const JavaScriptRoundOperation();
|
||||
|
||||
const JavaScriptConstantSystem();
|
||||
|
||||
|
|
|
@ -34,6 +34,12 @@ class InvokeDynamicSpecializer {
|
|||
return null;
|
||||
}
|
||||
|
||||
void clearAllSideEffects(HInstruction instruction) {
|
||||
instruction.sideEffects.clearAllSideEffects();
|
||||
instruction.sideEffects.clearAllDependencies();
|
||||
instruction.setUseGvn();
|
||||
}
|
||||
|
||||
Operation operation(ConstantSystem constantSystem) => null;
|
||||
|
||||
static InvokeDynamicSpecializer lookupSpecializer(Selector selector) {
|
||||
|
@ -85,6 +91,11 @@ class InvokeDynamicSpecializer {
|
|||
return const CodeUnitAtSpecializer();
|
||||
}
|
||||
}
|
||||
if (selector.argumentCount == 0 && selector.namedArguments.length == 0) {
|
||||
if (selector.name == 'round') {
|
||||
return const RoundSpecializer();
|
||||
}
|
||||
}
|
||||
}
|
||||
return const InvokeDynamicSpecializer();
|
||||
}
|
||||
|
@ -219,12 +230,6 @@ abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer {
|
|||
return null;
|
||||
}
|
||||
|
||||
void clearAllSideEffects(HInstruction instruction) {
|
||||
instruction.sideEffects.clearAllSideEffects();
|
||||
instruction.sideEffects.clearAllDependencies();
|
||||
instruction.setUseGvn();
|
||||
}
|
||||
|
||||
bool inputsArePositiveIntegers(HInstruction instruction, Compiler compiler) {
|
||||
HInstruction left = instruction.inputs[1];
|
||||
HInstruction right = instruction.inputs[2];
|
||||
|
@ -759,3 +764,22 @@ class CodeUnitAtSpecializer extends InvokeDynamicSpecializer {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class RoundSpecializer extends InvokeDynamicSpecializer {
|
||||
const RoundSpecializer();
|
||||
|
||||
UnaryOperation operation(ConstantSystem constantSystem) {
|
||||
return constantSystem.round;
|
||||
}
|
||||
|
||||
HInstruction tryConvertToBuiltin(
|
||||
HInvokeDynamic instruction, Compiler compiler) {
|
||||
HInstruction receiver = instruction.getDartReceiver(compiler);
|
||||
if (receiver.isNumberOrNull(compiler)) {
|
||||
// Even if there is no builtin equivalent instruction, we know the
|
||||
// instruction does not have any side effect, and that it can be GVN'ed.
|
||||
clearAllSideEffects(instruction);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
167
tests/compiler/dart2js_extra/round_constant_folding_test.dart
Normal file
167
tests/compiler/dart2js_extra/round_constant_folding_test.dart
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
const double PD1 = 0.0;
|
||||
const double PD2 = double.MIN_POSITIVE;
|
||||
const double PD3 = 2.0 * double.MIN_POSITIVE;
|
||||
const double PD4 = 1.18e-38;
|
||||
const double PD5 = 1.18e-38 * 2;
|
||||
const double PD6 = 0.49999999999999994;
|
||||
const double PD7 = 0.5;
|
||||
const double PD8 = 0.9999999999999999;
|
||||
const double PD9 = 1.0;
|
||||
const double PD10 = 1.000000000000001;
|
||||
const double PD11 = double.MAX_FINITE;
|
||||
|
||||
const double ND1 = -PD1;
|
||||
const double ND2 = -PD2;
|
||||
const double ND3 = -PD3;
|
||||
const double ND4 = -PD4;
|
||||
const double ND5 = -PD5;
|
||||
const double ND6 = -PD6;
|
||||
const double ND7 = -PD7;
|
||||
const double ND8 = -PD8;
|
||||
const double ND9 = -PD9;
|
||||
const double ND10 = -PD10;
|
||||
const double ND11 = -PD11;
|
||||
|
||||
const X1 = double.INFINITY;
|
||||
const X2 = double.NEGATIVE_INFINITY;
|
||||
const X3 = double.NAN;
|
||||
|
||||
// The following numbers are on the border of 52 bits.
|
||||
// For example: 4503599627370499 + 0.5 => 4503599627370500.
|
||||
const PQ1 = 4503599627370496.0;
|
||||
const PQ2 = 4503599627370497.0;
|
||||
const PQ3 = 4503599627370498.0;
|
||||
const PQ4 = 4503599627370499.0;
|
||||
const PQ5 = 9007199254740991.0;
|
||||
const PQ6 = 9007199254740992.0;
|
||||
|
||||
const NQ1 = -PQ1;
|
||||
const NQ2 = -PQ2;
|
||||
const NQ3 = -PQ3;
|
||||
const NQ4 = -PQ4;
|
||||
const NQ5 = -PQ5;
|
||||
const NQ6 = -PQ6;
|
||||
|
||||
const int PI1 = 0;
|
||||
const int PI2 = 1;
|
||||
const int PI3 = 0x1234;
|
||||
const int PI4 = 0x12345678;
|
||||
const int PI5 = 0x123456789AB;
|
||||
const int PI6 = 0x123456789ABCDEF;
|
||||
const int PI7 = 0x123456789ABCDEF0123456789ABCDEF0123456789ABCDEF;
|
||||
|
||||
const int NI1 = 0-PI1;
|
||||
const int NI2 = -PI2;
|
||||
const int NI3 = -PI3;
|
||||
const int NI4 = -PI4;
|
||||
const int NI5 = -PI5;
|
||||
const int NI6 = -PI6;
|
||||
const int NI7 = -PI7;
|
||||
|
||||
|
||||
/// Ensures that the behaviour of `action()` is the same as `value.round()`.
|
||||
@NoInline() // To ensure 'value.round()' has a non-constant receiver.
|
||||
check(value, action) {
|
||||
var result1, result2;
|
||||
try {
|
||||
result1 = value.round();
|
||||
} catch (e) {
|
||||
result1 = e;
|
||||
}
|
||||
|
||||
try {
|
||||
result2 = action();
|
||||
} catch (e) {
|
||||
result2 = e;
|
||||
}
|
||||
|
||||
Expect.equals(result1.runtimeType, result2.runtimeType);
|
||||
if (result1 is num) {
|
||||
Expect.equals(result1, result2);
|
||||
Expect.equals(result1 is int, result2 is int);
|
||||
} else {
|
||||
Expect.equals(result1.runtimeType, result2.runtimeType);
|
||||
Expect.isTrue(result1 is Error);
|
||||
}
|
||||
}
|
||||
|
||||
@NoInline()
|
||||
void unusedCall(num x) {
|
||||
x.round(); // This call should not be removed since it might throw.
|
||||
}
|
||||
|
||||
main() {
|
||||
check(PD1, () => PD1.round());
|
||||
check(PD2, () => PD2.round());
|
||||
check(PD3, () => PD3.round());
|
||||
check(PD4, () => PD4.round());
|
||||
check(PD5, () => PD5.round());
|
||||
check(PD6, () => PD6.round());
|
||||
check(PD7, () => PD7.round());
|
||||
check(PD8, () => PD8.round());
|
||||
check(PD9, () => PD9.round());
|
||||
check(PD10, () => PD10.round());
|
||||
check(PD11, () => PD11.round());
|
||||
|
||||
check(ND1, () => ND1.round());
|
||||
check(ND2, () => ND2.round());
|
||||
check(ND3, () => ND3.round());
|
||||
check(ND4, () => ND4.round());
|
||||
check(ND5, () => ND5.round());
|
||||
check(ND6, () => ND6.round());
|
||||
check(ND7, () => ND7.round());
|
||||
check(ND8, () => ND8.round());
|
||||
check(ND9, () => ND9.round());
|
||||
check(ND10, () => ND10.round());
|
||||
check(ND11, () => ND11.round());
|
||||
|
||||
check(X1, () => X1.round());
|
||||
check(X2, () => X2.round());
|
||||
check(X3, () => X3.round());
|
||||
|
||||
check(PQ1, () => PQ1.round());
|
||||
check(PQ2, () => PQ2.round());
|
||||
check(PQ3, () => PQ3.round());
|
||||
check(PQ4, () => PQ4.round());
|
||||
check(PQ5, () => PQ5.round());
|
||||
check(PQ6, () => PQ6.round());
|
||||
|
||||
check(NQ1, () => NQ1.round());
|
||||
check(NQ2, () => NQ2.round());
|
||||
check(NQ3, () => NQ3.round());
|
||||
check(NQ4, () => NQ4.round());
|
||||
check(NQ5, () => NQ5.round());
|
||||
check(NQ6, () => NQ6.round());
|
||||
|
||||
check(PI1, () => PI1.round());
|
||||
check(PI2, () => PI2.round());
|
||||
check(PI3, () => PI3.round());
|
||||
check(PI4, () => PI4.round());
|
||||
check(PI5, () => PI5.round());
|
||||
check(PI6, () => PI6.round());
|
||||
check(PI7, () => PI7.round());
|
||||
|
||||
check(NI1, () => NI1.round());
|
||||
check(NI2, () => NI2.round());
|
||||
check(NI3, () => NI3.round());
|
||||
check(NI4, () => NI4.round());
|
||||
check(NI5, () => NI5.round());
|
||||
check(NI6, () => NI6.round());
|
||||
check(NI7, () => NI7.round());
|
||||
|
||||
// Check that the operation is not removed if it can throw, even if the result
|
||||
// is unused.
|
||||
Expect.throws(() { X1.round(); });
|
||||
Expect.throws(() { X2.round(); });
|
||||
Expect.throws(() { X3.round(); });
|
||||
unusedCall(0);
|
||||
Expect.throws(() { unusedCall(X1); });
|
||||
Expect.throws(() { unusedCall(X2); });
|
||||
Expect.throws(() { unusedCall(X3); });
|
||||
}
|
Loading…
Reference in a new issue