PR-URL: https://github.com/nodejs/node/pull/58070 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
347 lines
11 KiB
JavaScript
347 lines
11 KiB
JavaScript
// Copyright 2024 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 --additive-safe-int-feedback
|
|
// Flags: --turbofan --no-always-turbofan
|
|
|
|
const maxAdditiveSafeInteger = 4503599627370495; // 2^52 - 1
|
|
const minAdditiveSafeInteger = - 4503599627370496; // - 2^52
|
|
|
|
// If one of the inputs is a constant in the additive safe range,
|
|
// we can use AdditiveSafeInteger.
|
|
(function() {
|
|
function foo(a) {
|
|
return a + 1;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertOptimized(foo);
|
|
|
|
// We don't deopt in the maximum value.
|
|
assertEquals(maxAdditiveSafeInteger, foo(maxAdditiveSafeInteger - 1));
|
|
assertOptimized(foo);
|
|
// Nor in the minimum.
|
|
assertEquals(minAdditiveSafeInteger + 1, foo(minAdditiveSafeInteger));
|
|
assertOptimized(foo);
|
|
|
|
// We deopt if we overflow!
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertOptimized(foo);
|
|
|
|
// This time we don't overflow, since we use
|
|
// Float64Add.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles.
|
|
assertEquals(2.5, foo(1.5));
|
|
assertOptimized(foo);
|
|
})();
|
|
|
|
// Deopt if input is a double.
|
|
(function() {
|
|
function foo(a) {
|
|
return a + 1;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertOptimized(foo);
|
|
|
|
// Deopt if input is a double.
|
|
assertEquals(2.5, foo(1.5));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles anymore.
|
|
assertEquals(2.5, foo(1.5));
|
|
assertOptimized(foo);
|
|
})();
|
|
|
|
// If we don't know statically that one of the inputs is
|
|
// in the additive safe range, we fallback to float64 add.
|
|
(function() {
|
|
function foo(a, b) {
|
|
return a + b;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567891, foo(1231234567890, 1));
|
|
assertEquals(1231234567891, foo(1231234567890, 1));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// We don't deopt in overflow.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles.
|
|
assertEquals(2.5, foo(1.5, 1));
|
|
assertOptimized(foo);
|
|
})();
|
|
|
|
// We deopt in minus zero.
|
|
(function() {
|
|
function foo(a) {
|
|
return a + 1;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertOptimized(foo);
|
|
|
|
// TODO(victorgomes): If we know one of the inputs is
|
|
// definitely not minus zero, we could avoid deopting here.
|
|
assertEquals(1, foo(-0));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles.
|
|
assertEquals(2.5, foo(1.5));
|
|
assertOptimized(foo);
|
|
})();
|
|
|
|
// Optimize to AdditiveSafeInteger if one of the inputs is truncated, since it is
|
|
// in the safe integer range.
|
|
(function() {
|
|
function foo(a, b) {
|
|
return a + (b | 0);
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567891, foo(1231234567890, 1));
|
|
assertEquals(1231234567891, foo(1231234567890, 1));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// We don't deopt in the maximum value.
|
|
assertEquals(maxAdditiveSafeInteger, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertOptimized(foo);
|
|
// Nor in the minimum.
|
|
assertEquals(minAdditiveSafeInteger + 1, foo(minAdditiveSafeInteger, 1));
|
|
assertOptimized(foo);
|
|
|
|
// We deopt if we overflow!
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger, 1));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// This time we don't overflow, since we use
|
|
// Float64Add.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles.
|
|
assertEquals(2.5, foo(1.5, 1));
|
|
assertOptimized(foo);
|
|
})();
|
|
|
|
// Optimize when the result is pass to another add, since we know the result of
|
|
// the add is in the safe integer range.
|
|
(function() {
|
|
function foo(a, b) {
|
|
return 1 + a + b;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// We don't deopt in the maximum value.
|
|
assertEquals(maxAdditiveSafeInteger, foo(maxAdditiveSafeInteger - 2, 1));
|
|
assertOptimized(foo);
|
|
// Nor in the minimum.
|
|
assertEquals(minAdditiveSafeInteger + 2, foo(minAdditiveSafeInteger, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Overflow the second add to ensure that we optimize with additive safe int add.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// This time we don't overflow, since we useFloat64Add.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertOptimized(foo);
|
|
|
|
// But we can still deopt the first add.
|
|
assertEquals(maxAdditiveSafeInteger + 2, foo(maxAdditiveSafeInteger, 1));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// This time no deopts.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertEquals(maxAdditiveSafeInteger + 2, foo(maxAdditiveSafeInteger, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles.
|
|
assertEquals(3.5, foo(1.5, 1));
|
|
assertOptimized(foo);
|
|
})();
|
|
|
|
// This also works if the known input is the middle one.
|
|
(function() {
|
|
function foo(a, b) {
|
|
return a + 1 + b;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// We don't deopt in the maximum value.
|
|
assertEquals(maxAdditiveSafeInteger, foo(maxAdditiveSafeInteger - 2, 1));
|
|
assertOptimized(foo);
|
|
// Nor in the minimum.
|
|
assertEquals(minAdditiveSafeInteger + 2, foo(minAdditiveSafeInteger, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Overflow the second add to ensure that we optimize with additive safe int add.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// This time we don't overflow, since we useFloat64Add.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertOptimized(foo);
|
|
|
|
// But we can still deopt the first add.
|
|
assertEquals(maxAdditiveSafeInteger + 2, foo(maxAdditiveSafeInteger, 1));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// This time no deopts.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertEquals(maxAdditiveSafeInteger + 2, foo(maxAdditiveSafeInteger, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles.
|
|
assertEquals(3.5, foo(1.5, 1));
|
|
assertOptimized(foo);
|
|
})();
|
|
|
|
// Since add is not an associative operation in JS, unfortunately, we don't
|
|
// optimize the first add.
|
|
(function() {
|
|
function foo(a, b) {
|
|
return a + b + 1;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Overflow the second add to ensure that we optimize with additive safe int add.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567892, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// This time we don't overflow, since we use Float64Add.
|
|
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger - 1, 1));
|
|
assertOptimized(foo);
|
|
|
|
// And we cannot deopt by overflowing the first one.
|
|
assertEquals(maxAdditiveSafeInteger + 2, foo(maxAdditiveSafeInteger, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles.
|
|
assertEquals(3.5, foo(1.5, 1));
|
|
assertOptimized(foo);
|
|
})();
|
|
|
|
// Don't need to deopt in overflow if we are truncating the result.
|
|
(function() {
|
|
function foo(a) {
|
|
return a + 1 | 0;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(foo);
|
|
assertEquals(1231234567891 | 0, foo(1231234567890, 1));
|
|
assertEquals(1231234567891 | 0, foo(1231234567890, 1));
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891 | 0, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// We don't deopt in the maximum value.
|
|
assertEquals(maxAdditiveSafeInteger | 0, foo(maxAdditiveSafeInteger - 1));
|
|
assertOptimized(foo);
|
|
// Nor in the minimum.
|
|
assertEquals(minAdditiveSafeInteger + 1 | 0, foo(minAdditiveSafeInteger));
|
|
assertOptimized(foo);
|
|
|
|
// We don't deopt in overflow!
|
|
assertEquals(maxAdditiveSafeInteger + 1 | 0, foo(maxAdditiveSafeInteger));
|
|
assertOptimized(foo);
|
|
|
|
// Deopt with doubles.
|
|
assertEquals(2, foo(1.5));
|
|
assertUnoptimized(foo);
|
|
|
|
// Optimize again.
|
|
%OptimizeFunctionOnNextCall(foo);
|
|
assertEquals(1231234567891 | 0, foo(1231234567890, 1));
|
|
assertOptimized(foo);
|
|
|
|
// Don't deopt with doubles anymore.
|
|
assertEquals(2, foo(1.5, 1));
|
|
assertOptimized(foo);
|
|
})();
|