Commit 8ead5698 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Unify handling of zeros.

Following up on the earlier work regarding redundant Smi checks in
https://chromium-review.googlesource.com/c/v8/v8/+/1246181, it was
noticed that the handling of the 0 and -0 and how some operations
identify these is not really consistent, but was still rather ad-hoc.
This change tries to unify the handling a bit by making sure that all
number comparisons generally pass truncations that identify zeros, since
for the number comparisons in JavaScript there's no difference between
0 and -0. In the same spirit NumberAbs and NumberToBoolean should also
pass these truncations, since they also don't care about the differences
between 0 and -0.

Adjust NumberCeil, NumberFloor, NumberTrunc, NumberMin and NumberMax
to pass along any incoming kIdentifiesZeros truncation, since these
operations also don't really care whether the inputs can be -0 if the
use nodes don't care.

Also utilize the kIdentifiesZeros truncation for NumberModulus with
Signed32 inputs, because it's kind of common to do something like
`x % 2 === 0`, where it doesn't really matter whether `x % 2` would
eventually produce a negative zero (since that would still be considered
true for the sake of the comparison).

This also adds a whole lot of tests to ensure that not only are these
optimizations correct, but also that we do indeed perform them.

Drive-by-fix: The `NumberAbs(x)` would incorrectly lower to just `x` for
PositiveIntegerOrMinusZeroOrNaN inputs, which was obviously wrong in
case of -0. This was fixed as well, and an appropriate test was added.

The reason for the unification is that with the introduction of Word64
for CheckBounds (which is necessary to support large TypedArrays and
DataViews) we can no longer safely pass Word32 truncations for the
interesting cases, since the index might be outside the Signed32 or
Unsigned32 ranges, but we still identify 0 and -0 for the sake of the
bounds check, and so it's important that this is handled consistently
to not regress performance on TypedArrays and DataViews accesses.

Bug: v8:8015, v8:8178
Change-Id: Ia1d32f1b726754cea1e5793105d9423d84a6393a
Reviewed-on: https://chromium-review.googlesource.com/1246172Reviewed-by: 's avatarSigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56325}
parent 9a537778
......@@ -169,8 +169,10 @@ class UseInfo {
static UseInfo Float32() {
return UseInfo(MachineRepresentation::kFloat32, Truncation::Any());
}
static UseInfo TruncatingFloat64() {
return UseInfo(MachineRepresentation::kFloat64, Truncation::Float64());
static UseInfo TruncatingFloat64(
IdentifyZeros identify_zeros = kDistinguishZeros) {
return UseInfo(MachineRepresentation::kFloat64,
Truncation::Float64(identify_zeros));
}
static UseInfo AnyTagged() {
return UseInfo(MachineRepresentation::kTagged, Truncation::Any());
......@@ -188,9 +190,11 @@ class UseInfo {
TypeCheckKind::kHeapObject);
}
static UseInfo CheckedSignedSmallAsTaggedSigned(
const VectorSlotPair& feedback) {
return UseInfo(MachineRepresentation::kTaggedSigned, Truncation::Any(),
TypeCheckKind::kSignedSmall, feedback);
const VectorSlotPair& feedback,
IdentifyZeros identify_zeros = kDistinguishZeros) {
return UseInfo(MachineRepresentation::kTaggedSigned,
Truncation::Any(identify_zeros), TypeCheckKind::kSignedSmall,
feedback);
}
static UseInfo CheckedSignedSmallAsWord32(IdentifyZeros identify_zeros,
const VectorSlotPair& feedback) {
......@@ -204,17 +208,20 @@ class UseInfo {
Truncation::Any(identify_zeros), TypeCheckKind::kSigned32,
feedback);
}
static UseInfo CheckedNumberAsFloat64(const VectorSlotPair& feedback) {
return UseInfo(MachineRepresentation::kFloat64, Truncation::Any(),
TypeCheckKind::kNumber, feedback);
static UseInfo CheckedNumberAsFloat64(IdentifyZeros identify_zeros,
const VectorSlotPair& feedback) {
return UseInfo(MachineRepresentation::kFloat64,
Truncation::Any(identify_zeros), TypeCheckKind::kNumber,
feedback);
}
static UseInfo CheckedNumberAsWord32(const VectorSlotPair& feedback) {
return UseInfo(MachineRepresentation::kWord32, Truncation::Word32(),
TypeCheckKind::kNumber, feedback);
}
static UseInfo CheckedNumberOrOddballAsFloat64(
const VectorSlotPair& feedback) {
return UseInfo(MachineRepresentation::kFloat64, Truncation::Any(),
IdentifyZeros identify_zeros, const VectorSlotPair& feedback) {
return UseInfo(MachineRepresentation::kFloat64,
Truncation::Any(identify_zeros),
TypeCheckKind::kNumberOrOddball, feedback);
}
static UseInfo CheckedNumberOrOddballAsWord32(
......
This diff is collapsed.
// 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 that NumberAbs correctly deals with PositiveInteger \/ MinusZero
// and turns the -0 into a 0.
(function() {
function foo(x) {
x = Math.floor(x);
x = Math.max(x, -0);
return 1 / Math.abs(x);
}
assertEquals(Infinity, foo(-0));
assertEquals(Infinity, foo(-0));
%OptimizeFunctionOnNextCall(foo);
assertEquals(Infinity, foo(-0));
})();
// Test that NumberAbs properly passes the kIdentifyZeros truncation
// for Signed32 \/ MinusZero inputs.
(function() {
function foo(x) {
return Math.abs(x * -2);
}
assertEquals(2, foo(-1));
assertEquals(4, foo(-2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo(-1));
assertEquals(4, foo(-2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
// Test that NumberAbs properly passes the kIdentifyZeros truncation
// for Unsigned32 \/ MinusZero inputs.
(function() {
function foo(x) {
x = x | 0;
return Math.abs(Math.max(x * -2, 0));
}
assertEquals(2, foo(-1));
assertEquals(4, foo(-2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo(-1));
assertEquals(4, foo(-2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
// Test that NumberAbs properly passes the kIdentifyZeros truncation
// for OrderedNumber inputs.
(function() {
function foo(x) {
x = x | 0;
return Math.abs(Math.min(x * -2, 2 ** 32));
}
assertEquals(2, foo(-1));
assertEquals(4, foo(-2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo(-1));
assertEquals(4, foo(-2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
// 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 that NumberCeil propagates kIdentifyZeros truncations.
(function() {
function foo(x) {
return Math.abs(Math.ceil(x * -2));
}
assertEquals(2, foo(1));
assertEquals(4, foo(2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo(1));
assertEquals(4, foo(2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
// 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 that SpeculativeNumberEqual[SignedSmall] properly passes the
// kIdentifyZeros truncation.
(function() {
function foo(x, y) {
if (x * y === 0) return 0;
return 1;
}
assertEquals(0, foo(0, 1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(0, 1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
assertOptimized(foo);
// Even if x*y produces -0 now, it should stay optimized.
assertEquals(0, foo(-3, 0));
assertEquals(0, foo(0, -3));
assertOptimized(foo);
})();
// Test that SpeculativeNumberEqual[Number] properly passes the
// kIdentifyZeros truncation.
(function() {
// Produce a SpeculativeNumberEqual with Number feedback.
function bar(x, y) { return x === y; }
bar(0.1, 0.5);
bar(-0, 100);
function foo(x, y) {
if (bar(x * y, 0)) return 0;
return 1;
}
assertEquals(0, foo(0, 1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(0, 1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
assertOptimized(foo);
// Even if x*y produces -0 now, it should stay optimized.
assertEquals(0, foo(-3, 0));
assertEquals(0, foo(0, -3));
assertOptimized(foo);
})();
// Test that SpeculativeNumberLessThan[SignedSmall] properly passes the
// kIdentifyZeros truncation.
(function() {
function foo(x, y) {
if (x * y < 0) return 0;
return 1;
}
assertEquals(0, foo(1, -1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(1, -1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
assertOptimized(foo);
// Even if x*y produces -0 now, it should stay optimized.
assertEquals(1, foo(-3, 0));
assertEquals(1, foo(0, -3));
assertOptimized(foo);
})();
// Test that SpeculativeNumberLessThan[Number] properly passes the
// kIdentifyZeros truncation.
(function() {
// Produce a SpeculativeNumberLessThan with Number feedback.
function bar(x, y) { return x < y; }
bar(0.1, 0.5);
bar(-0, 100);
function foo(x, y) {
if (bar(x * y, 0)) return 0;
return 1;
}
assertEquals(0, foo(1, -1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(1, -1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
assertOptimized(foo);
// Even if x*y produces -0 now, it should stay optimized.
assertEquals(1, foo(-3, 0));
assertEquals(1, foo(0, -3));
assertOptimized(foo);
})();
// Test that SpeculativeNumberLessThanOrEqual[SignedSmall] properly passes the
// kIdentifyZeros truncation.
(function() {
function foo(x, y) {
if (x * y <= 0) return 0;
return 1;
}
assertEquals(0, foo(0, 1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(0, 1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
assertOptimized(foo);
// Even if x*y produces -0 now, it should stay optimized.
assertEquals(0, foo(-3, 0));
assertEquals(0, foo(0, -3));
assertOptimized(foo);
})();
// Test that SpeculativeNumberLessThanOrEqual[Number] properly passes the
// kIdentifyZeros truncation.
(function() {
// Produce a SpeculativeNumberLessThanOrEqual with Number feedback.
function bar(x, y) { return x <= y; }
bar(0.1, 0.5);
bar(-0, 100);
function foo(x, y) {
if (bar(x * y, 0)) return 0;
return 1;
}
assertEquals(0, foo(0, 1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(0, 1));
assertEquals(1, foo(1, 1));
assertEquals(1, foo(1, 2));
assertOptimized(foo);
// Even if x*y produces -0 now, it should stay optimized.
assertEquals(0, foo(-3, 0));
assertEquals(0, foo(0, -3));
assertOptimized(foo);
})();
// 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 that NumberFloor propagates kIdentifyZeros truncations.
(function() {
function foo(x) {
return Math.abs(Math.floor(x * -2));
}
assertEquals(2, foo(1));
assertEquals(4, foo(2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo(1));
assertEquals(4, foo(2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
// 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 that NumberMax properly passes the kIdentifyZeros truncation.
(function() {
function foo(x) {
if (Math.max(x * -2, 1) == 1) return 0;
return 1;
}
assertEquals(0, foo(2));
assertEquals(1, foo(-1));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(2));
assertEquals(1, foo(-1));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
// 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 that NumberMin properly passes the kIdentifyZeros truncation.
(function() {
function foo(x) {
if (Math.min(x * -2, -1) == -2) return 0;
return 1;
}
assertEquals(0, foo(1));
assertEquals(1, foo(2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(1));
assertEquals(1, foo(2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(1, foo(0));
assertOptimized(foo);
})();
......@@ -123,3 +123,34 @@
assertEquals(1, foo(4));
assertOptimized(foo);
})();
// Test that NumberModulus works in the case where TurboFan
// can infer that the output is Signed32 \/ MinusZero, and
// there's a truncation on the result that identifies zeros
// (via the SpeculativeNumberEqual).
(function() {
// We need a separately polluted % with NumberOrOddball feedback.
function bar(x) { return x % 2; }
bar(undefined); // The % feedback is now NumberOrOddball.
// Now we just use the gadget above on an `x` that is known
// to be in Signed32 range and compare it to 0, which passes
// a truncation that identifies zeros.
function foo(x) {
if (bar(x | 0) == 0) return 0;
return 1;
}
assertEquals(0, foo(2));
assertEquals(1, foo(1));
%OptimizeFunctionOnNextCall(foo);
assertEquals(0, foo(2));
assertEquals(1, foo(1));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x % 2` would
// produce -0, aka when we pass a negative value for `x`.
assertEquals(0, foo(-2));
assertEquals(1, foo(-1));
assertOptimized(foo);
})();
// 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 that NumberToBoolean properly passes the kIdentifyZeros truncation
// for Signed32 \/ MinusZero inputs.
(function() {
function foo(x) {
if (x * -2) return 1;
return 0;
}
assertEquals(1, foo(1));
assertEquals(1, foo(2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(1, foo(1));
assertEquals(1, foo(2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
// Test that NumberToBoolean properly passes the kIdentifyZeros truncation
// for Unsigned32 \/ MinusZero inputs.
(function() {
function foo(x) {
x = x | 0;
if (Math.max(x * -2, 0)) return 1;
return 0;
}
assertEquals(1, foo(-1));
assertEquals(1, foo(-2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(1, foo(-1));
assertEquals(1, foo(-2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
// 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 that NumberTrunc propagates kIdentifyZeros truncations.
(function() {
function foo(x) {
return Math.abs(Math.trunc(x * -2));
}
assertEquals(2, foo(1));
assertEquals(4, foo(2));
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo(1));
assertEquals(4, foo(2));
assertOptimized(foo);
// Now `foo` should stay optimized even if `x * -2` would produce `-0`.
assertEquals(0, foo(0));
assertOptimized(foo);
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment