diff --git a/src/compiler/operation-typer.cc b/src/compiler/operation-typer.cc index fa0eb60401..317bbe1888 100644 --- a/src/compiler/operation-typer.cc +++ b/src/compiler/operation-typer.cc @@ -221,31 +221,36 @@ Type OperationTyper::SubtractRanger(double lhs_min, double lhs_max, // [m, +inf] - [-inf, n] = [-inf, +inf] \/ NaN } -Type OperationTyper::MultiplyRanger(Type lhs, Type rhs) { +Type OperationTyper::MultiplyRanger(double lhs_min, double lhs_max, + double rhs_min, double rhs_max) { double results[4]; - double lmin = lhs.AsRange()->Min(); - double lmax = lhs.AsRange()->Max(); - double rmin = rhs.AsRange()->Min(); - double rmax = rhs.AsRange()->Max(); - results[0] = lmin * rmin; - results[1] = lmin * rmax; - results[2] = lmax * rmin; - results[3] = lmax * rmax; - // If the result may be nan, we give up on calculating a precise type, because - // the discontinuity makes it too complicated. Note that even if none of the - // "results" above is nan, the actual result may still be, so we have to do a - // different check: - bool maybe_nan = (lhs.Maybe(cache_.kSingletonZero) && - (rmin == -V8_INFINITY || rmax == +V8_INFINITY)) || - (rhs.Maybe(cache_.kSingletonZero) && - (lmin == -V8_INFINITY || lmax == +V8_INFINITY)); - if (maybe_nan) return cache_.kIntegerOrMinusZeroOrNaN; // Giving up. - bool maybe_minuszero = (lhs.Maybe(cache_.kSingletonZero) && rmin < 0) || - (rhs.Maybe(cache_.kSingletonZero) && lmin < 0); - Type range = - Type::Range(array_min(results, 4), array_max(results, 4), zone()); - return maybe_minuszero ? Type::Union(range, Type::MinusZero(), zone()) - : range; + results[0] = lhs_min * rhs_min; + results[1] = lhs_min * rhs_max; + results[2] = lhs_max * rhs_min; + results[3] = lhs_max * rhs_max; + // If the result may be nan, we give up on calculating a precise type, + // because the discontinuity makes it too complicated. Note that even if + // none of the "results" above is nan, the actual result may still be, so we + // have to do a different check: + for (int i = 0; i < 4; ++i) { + if (std::isnan(results[i])) { + return cache_.kIntegerOrMinusZeroOrNaN; + } + } + double min = array_min(results, 4); + double max = array_max(results, 4); + Type type = Type::Range(min, max, zone()); + if (min <= 0.0 && 0.0 <= max && (lhs_min < 0.0 || rhs_min < 0.0)) { + type = Type::Union(type, Type::MinusZero(), zone()); + } + // 0 * V8_INFINITY is NaN, regardless of sign + if (((lhs_min == -V8_INFINITY || lhs_max == V8_INFINITY) && + (rhs_min <= 0.0 && 0.0 <= rhs_max)) || + ((rhs_min == -V8_INFINITY || rhs_max == V8_INFINITY) && + (lhs_min <= 0.0 && 0.0 <= lhs_max))) { + type = Type::Union(type, Type::NaN(), zone()); + } + return type; } Type OperationTyper::ConvertReceiver(Type type) { @@ -685,14 +690,44 @@ Type OperationTyper::NumberMultiply(Type lhs, Type rhs) { DCHECK(rhs.Is(Type::Number())); if (lhs.IsNone() || rhs.IsNone()) return Type::None(); - - lhs = Rangify(lhs); - rhs = Rangify(rhs); if (lhs.Is(Type::NaN()) || rhs.Is(Type::NaN())) return Type::NaN(); - if (lhs.IsRange() && rhs.IsRange()) { - return MultiplyRanger(lhs, rhs); + + // Multiplication propagates NaN: + // NaN * x = NaN (regardless of sign of x) + // 0 * Infinity = NaN (regardless of signs) + bool maybe_nan = lhs.Maybe(Type::NaN()) || rhs.Maybe(Type::NaN()) || + (lhs.Maybe(cache_.kZeroish) && + (rhs.Min() == -V8_INFINITY || rhs.Max() == V8_INFINITY)) || + (rhs.Maybe(cache_.kZeroish) && + (lhs.Min() == -V8_INFINITY || lhs.Max() == V8_INFINITY)); + lhs = Type::Intersect(lhs, Type::OrderedNumber(), zone()); + DCHECK(!lhs.IsNone()); + rhs = Type::Intersect(rhs, Type::OrderedNumber(), zone()); + DCHECK(!rhs.IsNone()); + + // Try to rule out -0. + bool maybe_minuszero = lhs.Maybe(Type::MinusZero()) || + rhs.Maybe(Type::MinusZero()) || + (lhs.Maybe(cache_.kZeroish) && rhs.Min() < 0.0) || + (rhs.Maybe(cache_.kZeroish) && lhs.Min() < 0.0); + if (lhs.Maybe(Type::MinusZero())) { + lhs = Type::Union(lhs, cache_.kSingletonZero, zone()); + lhs = Type::Intersect(lhs, Type::PlainNumber(), zone()); } - return Type::Number(); + if (rhs.Maybe(Type::MinusZero())) { + rhs = Type::Union(rhs, cache_.kSingletonZero, zone()); + rhs = Type::Intersect(rhs, Type::PlainNumber(), zone()); + } + + // Compute the effective type, utilizing range information if possible. + Type type = (lhs.Is(cache_.kInteger) && rhs.Is(cache_.kInteger)) + ? MultiplyRanger(lhs.Min(), lhs.Max(), rhs.Min(), rhs.Max()) + : Type::OrderedNumber(); + + // Take into account the -0 and NaN information computed earlier. + if (maybe_minuszero) type = Type::Union(type, Type::MinusZero(), zone()); + if (maybe_nan) type = Type::Union(type, Type::NaN(), zone()); + return type; } Type OperationTyper::NumberDivide(Type lhs, Type rhs) { diff --git a/src/compiler/operation-typer.h b/src/compiler/operation-typer.h index 4e74bd64d7..633e1ce125 100644 --- a/src/compiler/operation-typer.h +++ b/src/compiler/operation-typer.h @@ -87,7 +87,8 @@ class V8_EXPORT_PRIVATE OperationTyper { double rhs_max); Type SubtractRanger(double lhs_min, double lhs_max, double rhs_min, double rhs_max); - Type MultiplyRanger(Type lhs, Type rhs); + Type MultiplyRanger(double lhs_min, double lhs_max, double rhs_min, + double rhs_max); Zone* zone() const { return zone_; } diff --git a/test/mjsunit/compiler/number-multiply.js b/test/mjsunit/compiler/number-multiply.js new file mode 100644 index 0000000000..5b644974ec --- /dev/null +++ b/test/mjsunit/compiler/number-multiply.js @@ -0,0 +1,59 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --opt + +// Test the extreme case where -0 is produced by rounding errors. +(function() { + function bar(x) { + return 1e-308 * x; + } + bar(1); + + function foo() { + return Object.is(-0, bar(-1e-308)); + } + + assertTrue(foo()); + assertTrue(foo()); + %OptimizeFunctionOnNextCall(foo); + assertTrue(foo()); +})(); + +// Test that multiplication of integer by 0 produces the correct results. +(function() { + function foo(x) { + return 0 * Math.round(x); + } + + assertEquals(0, foo(0.1)); + assertEquals(-0, foo(-0.1)); + assertEquals(NaN, foo(NaN)); + assertEquals(NaN, foo(Infinity)); + assertEquals(NaN, foo(-Infinity)); + %OptimizeFunctionOnNextCall(foo); + assertEquals(0, foo(0.1)); + assertEquals(-0, foo(-0.1)); + assertEquals(NaN, foo(NaN)); + assertEquals(NaN, foo(Infinity)); + assertEquals(NaN, foo(-Infinity)); +})(); + +// Test that multiplication properly preserves -0 and NaN, and doesn't +// cut it short incorrectly. +(function() { + function foo(x, y) { + x = Math.sign(x); + y = Math.sign(y); + return Math.min(x * y, 0); + } + + assertEquals(0, foo(1, 0)); + assertEquals(-0, foo(1, -0)); + assertEquals(NaN, foo(NaN, -0)); + %OptimizeFunctionOnNextCall(foo); + assertEquals(0, foo(1, 0)); + assertEquals(-0, foo(1, -0)); + assertEquals(NaN, foo(NaN, -0)); +})();