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, ...@@ -601,6 +601,12 @@ TNode<Boolean> JSGraphAssembler::ReferenceEqual(TNode<Object> lhs,
graph()->NewNode(simplified()->ReferenceEqual(), lhs, rhs)); 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> JSGraphAssembler::NumberMin(TNode<Number> lhs,
TNode<Number> rhs) { TNode<Number> rhs) {
return AddNode<Number>(graph()->NewNode(simplified()->NumberMin(), lhs, rhs)); return AddNode<Number>(graph()->NewNode(simplified()->NumberMin(), lhs, rhs));
......
...@@ -909,6 +909,7 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler { ...@@ -909,6 +909,7 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler {
TNode<Number> PlainPrimitiveToNumber(TNode<Object> value); TNode<Number> PlainPrimitiveToNumber(TNode<Object> value);
TNode<Number> NumberMin(TNode<Number> lhs, TNode<Number> rhs); TNode<Number> NumberMin(TNode<Number> lhs, TNode<Number> rhs);
TNode<Number> NumberMax(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> NumberLessThan(TNode<Number> lhs, TNode<Number> rhs);
TNode<Boolean> NumberLessThanOrEqual(TNode<Number> lhs, TNode<Number> rhs); TNode<Boolean> NumberLessThanOrEqual(TNode<Number> lhs, TNode<Number> rhs);
TNode<Number> NumberAdd(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 { ...@@ -265,6 +265,9 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
CompilationDependencies* const dependencies_; CompilationDependencies* const dependencies_;
std::set<Node*> waitlist_; 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; bool has_wasm_calls_ = false;
}; };
......
...@@ -1375,10 +1375,20 @@ class JSCallOrConstructNode : public JSNodeWrapperBase { ...@@ -1375,10 +1375,20 @@ class JSCallOrConstructNode : public JSNodeWrapperBase {
}; };
template <int kOpcode> 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 { class JSCallNodeBase final : public JSCallOrConstructNode {
public: public:
explicit constexpr JSCallNodeBase(Node* node) : JSCallOrConstructNode(node) { explicit constexpr JSCallNodeBase(Node* node) : JSCallOrConstructNode(node) {
DCHECK_EQ(kOpcode, node->opcode()); DCHECK(IsExpectedOpcode<kOpcodes...>(node->opcode()));
} }
const CallParameters& Parameters() const { const CallParameters& Parameters() const {
...@@ -1405,6 +1415,8 @@ class JSCallNodeBase final : public JSCallOrConstructNode { ...@@ -1405,6 +1415,8 @@ class JSCallNodeBase final : public JSCallOrConstructNode {
using JSCallNode = JSCallNodeBase<IrOpcode::kJSCall>; using JSCallNode = JSCallNodeBase<IrOpcode::kJSCall>;
using JSCallWithSpreadNode = JSCallNodeBase<IrOpcode::kJSCallWithSpread>; using JSCallWithSpreadNode = JSCallNodeBase<IrOpcode::kJSCallWithSpread>;
using JSCallWithArrayLikeNode = JSCallNodeBase<IrOpcode::kJSCallWithArrayLike>; using JSCallWithArrayLikeNode = JSCallNodeBase<IrOpcode::kJSCallWithArrayLike>;
using JSCallWithArrayLikeOrSpreadNode =
JSCallNodeBase<IrOpcode::kJSCallWithArrayLike, IrOpcode::kJSCallWithSpread>;
#if V8_ENABLE_WEBASSEMBLY #if V8_ENABLE_WEBASSEMBLY
class JSWasmCallNode final : public JSCallOrConstructNode { class JSWasmCallNode final : public JSCallOrConstructNode {
......
...@@ -178,29 +178,27 @@ ...@@ -178,29 +178,27 @@
got_interpreted = %IsBeingInterpreted(); got_interpreted = %IsBeingInterpreted();
return 42 + arguments.length; return 42 + arguments.length;
} }
var len = 2;
function foo() { function foo() {
let v = []; let args = []
v.push(42); for (var i = 0; i < len; i++) { args.push(1); }
let result = fortytwo.apply(null, v); let result = fortytwo.apply(null, args);
return result; return result;
} }
%PrepareFunctionForOptimization(fortytwo); %PrepareFunctionForOptimization(fortytwo);
%PrepareFunctionForOptimization(foo); %PrepareFunctionForOptimization(foo);
assertEquals(43, foo(1)); assertEquals(44, foo());
%OptimizeFunctionOnNextCall(foo); %OptimizeFunctionOnNextCall(foo);
assertTrue(got_interpreted); assertTrue(got_interpreted);
assertEquals(43, foo(1)); assertEquals(44, foo());
assertTrue(got_interpreted); assertTrue(got_interpreted);
assertUnoptimized(foo); assertOptimized(foo);
// Call again, verifies that it stays optimized, but the call is not inlined. len = 0;
%PrepareFunctionForOptimization(foo); assertEquals(42, foo());
assertEquals(43, foo(1)); assertFalse(got_interpreted);
%OptimizeFunctionOnNextCall(foo);
assertTrue(got_interpreted);
assertEquals(43, foo(1));
assertTrue(got_interpreted);
assertOptimized(foo); assertOptimized(foo);
})(); })();
...@@ -226,6 +224,60 @@ ...@@ -226,6 +224,60 @@
assertOptimized(foo); 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. // Test JSCallReducer::ReduceJSCallWithSpread.
(function () { (function () {
"use strict"; "use strict";
...@@ -272,6 +324,37 @@ ...@@ -272,6 +324,37 @@
assertOptimized(foo); 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. // Test spread call with more args.
(function () { (function () {
"use strict"; "use strict";
...@@ -296,6 +379,82 @@ ...@@ -296,6 +379,82 @@
assertOptimized(foo); 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. // Test apply on JSCreateClosure.
(function () { (function () {
"use strict"; "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