Commit 90a24303 authored by Georg Neis's avatar Georg Neis Committed by V8 LUCI CQ

Optimize JSCallWithArrayLike with diamond speculation when probable arguments...

Optimize JSCallWithArrayLike with diamond speculation when probable arguments list is empty literal array

The JSCallWithArraylike can be replaced with a JSCall if its probable arguments list is empty literal array. This replacement will introduce a deoptimization check to make sure the length of arguments list is 0 at runtime.

This CL change this optimization to a diamond speculation which may help avoid deoptimization once and keep the fast path. This change may benefit a following usecase,

function calcMax(testArray) {
     Array.max = function(array) {
         return Math.max.apply(Math, array);
     };

     var result = [];
     for (var i = 0; i < testArray.length - 3; i++) {
         var positiveNumbers = [];
         for (var j = 0; j < 3; j++) {
             if (testArray[i + j] > 0) {
                 positiveNumbers.push(testArray[i + j]);
             }
         }
         result.push(Array.max(positiveNumbers));
     }
     return result;
 }

 testArray = [-1, 2, 3, -4, -5, -6, -7, -8, -9, 10];

 for (var i = 0; i < 1000000; i++) {
     calcMax(testArray);
 }

Bug: v8:9974
Change-Id: I595627e2fd937527350c8f8652d701c791b41dd3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2967757
Commit-Queue: Georg Neis <neis@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75636}
parent f35048cf
......@@ -601,6 +601,12 @@ TNode<Boolean> JSGraphAssembler::ReferenceEqual(TNode<Object> lhs,
graph()->NewNode(simplified()->ReferenceEqual(), lhs, rhs));
}
TNode<Boolean> JSGraphAssembler::NumberEqual(TNode<Number> lhs,
TNode<Number> rhs) {
return AddNode<Boolean>(
graph()->NewNode(simplified()->NumberEqual(), lhs, rhs));
}
TNode<Number> JSGraphAssembler::NumberMin(TNode<Number> lhs,
TNode<Number> rhs) {
return AddNode<Number>(graph()->NewNode(simplified()->NumberMin(), lhs, rhs));
......
......@@ -909,6 +909,7 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler {
TNode<Number> PlainPrimitiveToNumber(TNode<Object> value);
TNode<Number> NumberMin(TNode<Number> lhs, TNode<Number> rhs);
TNode<Number> NumberMax(TNode<Number> lhs, TNode<Number> rhs);
TNode<Boolean> NumberEqual(TNode<Number> lhs, TNode<Number> rhs);
TNode<Boolean> NumberLessThan(TNode<Number> lhs, TNode<Number> rhs);
TNode<Boolean> NumberLessThanOrEqual(TNode<Number> lhs, TNode<Number> rhs);
TNode<Number> NumberAdd(TNode<Number> lhs, TNode<Number> rhs);
......
This diff is collapsed.
......@@ -265,6 +265,9 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
CompilationDependencies* const dependencies_;
std::set<Node*> waitlist_;
// For preventing infinite recursion via ReduceJSCallWithArrayLikeOrSpread.
std::unordered_set<Node*> generated_calls_with_array_like_or_spread_;
bool has_wasm_calls_ = false;
};
......
......@@ -1375,10 +1375,20 @@ class JSCallOrConstructNode : public JSNodeWrapperBase {
};
template <int kOpcode>
bool IsExpectedOpcode(int opcode) {
return opcode == kOpcode;
}
template <int kOpcode1, int kOpcode2, int... kOpcodes>
bool IsExpectedOpcode(int opcode) {
return opcode == kOpcode1 || IsExpectedOpcode<kOpcode2, kOpcodes...>(opcode);
}
template <int... kOpcodes>
class JSCallNodeBase final : public JSCallOrConstructNode {
public:
explicit constexpr JSCallNodeBase(Node* node) : JSCallOrConstructNode(node) {
DCHECK_EQ(kOpcode, node->opcode());
DCHECK(IsExpectedOpcode<kOpcodes...>(node->opcode()));
}
const CallParameters& Parameters() const {
......@@ -1405,6 +1415,8 @@ class JSCallNodeBase final : public JSCallOrConstructNode {
using JSCallNode = JSCallNodeBase<IrOpcode::kJSCall>;
using JSCallWithSpreadNode = JSCallNodeBase<IrOpcode::kJSCallWithSpread>;
using JSCallWithArrayLikeNode = JSCallNodeBase<IrOpcode::kJSCallWithArrayLike>;
using JSCallWithArrayLikeOrSpreadNode =
JSCallNodeBase<IrOpcode::kJSCallWithArrayLike, IrOpcode::kJSCallWithSpread>;
#if V8_ENABLE_WEBASSEMBLY
class JSWasmCallNode final : public JSCallOrConstructNode {
......
......@@ -178,29 +178,27 @@
got_interpreted = %IsBeingInterpreted();
return 42 + arguments.length;
}
var len = 2;
function foo() {
let v = [];
v.push(42);
let result = fortytwo.apply(null, v);
let args = []
for (var i = 0; i < len; i++) { args.push(1); }
let result = fortytwo.apply(null, args);
return result;
}
%PrepareFunctionForOptimization(fortytwo);
%PrepareFunctionForOptimization(foo);
assertEquals(43, foo(1));
assertEquals(44, foo());
%OptimizeFunctionOnNextCall(foo);
assertTrue(got_interpreted);
assertEquals(43, foo(1));
assertEquals(44, foo());
assertTrue(got_interpreted);
assertUnoptimized(foo);
assertOptimized(foo);
// Call again, verifies that it stays optimized, but the call is not inlined.
%PrepareFunctionForOptimization(foo);
assertEquals(43, foo(1));
%OptimizeFunctionOnNextCall(foo);
assertTrue(got_interpreted);
assertEquals(43, foo(1));
assertTrue(got_interpreted);
len = 0;
assertEquals(42, foo());
assertFalse(got_interpreted);
assertOptimized(foo);
})();
......@@ -226,6 +224,60 @@
assertOptimized(foo);
})();
// Test Reflect.apply() with empty array.
(function () {
"use strict";
var got_interpreted = true;
function fortytwo() {
got_interpreted = %IsBeingInterpreted();
return 42;
}
function foo() {
return Reflect.apply(fortytwo, null, []);
}
%PrepareFunctionForOptimization(fortytwo);
%PrepareFunctionForOptimization(foo);
assertEquals(42, foo());
%OptimizeFunctionOnNextCall(foo);
assertTrue(got_interpreted);
assertEquals(42, foo());
assertFalse(got_interpreted);
assertOptimized(foo);
})();
// Test Reflect.apply() with empty array that changes size.
(function () {
"use strict";
var got_interpreted = true;
function fortytwo() {
got_interpreted = %IsBeingInterpreted();
return 42 + arguments.length;
}
var len = 2;
function foo() {
let args = []
for (var i = 0; i < len; i++) { args.push(1); }
let result = Reflect.apply(fortytwo, null, args);
return result;
}
%PrepareFunctionForOptimization(fortytwo);
%PrepareFunctionForOptimization(foo);
assertEquals(44, foo());
%OptimizeFunctionOnNextCall(foo);
assertTrue(got_interpreted);
assertEquals(44, foo());
assertTrue(got_interpreted);
assertOptimized(foo);
len = 0;
assertEquals(42, foo());
assertFalse(got_interpreted);
assertOptimized(foo);
})();
// Test JSCallReducer::ReduceJSCallWithSpread.
(function () {
"use strict";
......@@ -272,6 +324,37 @@
assertOptimized(foo);
})();
// Test spread call with empty array that changes size.
(function () {
"use strict";
var max_got_interpreted = true;
function max() {
max_got_interpreted = %IsBeingInterpreted();
return Math.max(...arguments);
}
var len = 2;
function foo(x, y, z) {
let args = [];
for (var i = 0; i < len; i++) { args.push(4 + i); }
return max(x, y, z, ...args);
}
%PrepareFunctionForOptimization(max);
%PrepareFunctionForOptimization(foo);
assertEquals(5, foo(1, 2, 3));
%OptimizeFunctionOnNextCall(foo);
assertTrue(max_got_interpreted);
assertEquals(5, foo(1, 2, 3));
assertTrue(max_got_interpreted);
assertOptimized(foo);
len = 0;
assertEquals(3, foo(1, 2, 3));
assertFalse(max_got_interpreted);
assertOptimized(foo);
})();
// Test spread call with more args.
(function () {
"use strict";
......@@ -296,6 +379,82 @@
assertOptimized(foo);
})();
// Test on speculative optimization of introduced JSCall (array_size != 0).
(function () {
"use strict";
var F = Math.max;
var len;
function foo(x, y, z) {
var args = [z];
for (var i = 0; i < len; i++) { args.push(0); }
return F(x, y, ...args);
}
function foo1(x, y, z) {
var args = [z];
for (var i = 0; i < len; i++) { args.push(0); }
return F(x, y, ...args);
}
// Optimize JSCallWithSpread and Math.max
len = 0;
%PrepareFunctionForOptimization(foo);
assertEquals(3, foo(1, 2, 3));
%OptimizeFunctionOnNextCall(foo);
assertEquals(3, foo(1, 2, 3));
assertOptimized(foo);
// Deoptimize when input of Math.max is not number
foo('a', 'b', 3);
assertUnoptimized(foo);
// Optimize JSCallWithSpread and Math.max
len = 2;
%PrepareFunctionForOptimization(foo1);
assertEquals(3, foo1(1, 2, 3));
%OptimizeFunctionOnNextCall(foo1);
assertEquals(3, foo1(1, 2, 3));
//Deoptimize when array length changes
assertUnoptimized(foo1);
})();
// Test on speculative optimization of introduced JSCall (array_size == 0).
(function () {
"use strict";
var F = Math.max;
var len;
function foo(x, y, z) {
var args = [];
for (var i = 0; i < len; i++) { args.push(z); }
return F(x, y, ...args);
}
function foo1(x, y, z) {
var args = [];
for (var i = 0; i < len; i++) { args.push(z); }
return F(x, y, ...args);
}
// Optimize JSCallWithSpread and Math.max
len = 0;
%PrepareFunctionForOptimization(foo);
assertEquals(2, foo(1, 2, 3));
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo(1, 2, 3));
assertOptimized(foo);
// Deoptimzie when input of Math.max is not number
foo('a', 'b', 3);
assertUnoptimized(foo);
// Optimize JSCallWithSpread and Math.max
len = 2;
%PrepareFunctionForOptimization(foo1);
assertEquals(3, foo1(1, 2, 3));
%OptimizeFunctionOnNextCall(foo1);
assertEquals(3, foo1(1, 2, 3));
assertOptimized(foo1);
// No Deoptimization when array length changes
foo(1, 2, 3);
assertUnoptimized(foo);
})();
// Test apply on JSCreateClosure.
(function () {
"use strict";
......
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