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: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75636}
This commit is contained in:
Georg Neis 2021-07-02 16:24:26 +02:00 committed by V8 LUCI CQ
parent f35048cf99
commit 90a2430388
6 changed files with 453 additions and 207 deletions

View File

@ -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));

View File

@ -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);

View File

@ -73,6 +73,8 @@ class JSCallReducerAssembler : public JSGraphAssembler {
outermost_catch_scope_.set_gasm(this);
}
TNode<Object> ReduceJSCallWithArrayLikeOrSpreadOfEmpty(
std::unordered_set<Node*>* generated_calls_with_array_like_or_spread);
TNode<Object> ReduceMathUnary(const Operator* op);
TNode<Object> ReduceMathBinary(const Operator* op);
TNode<String> ReduceStringPrototypeSubstring();
@ -272,6 +274,10 @@ class JSCallReducerAssembler : public JSGraphAssembler {
TNode<Object> JSCallRuntime2(Runtime::FunctionId function_id,
TNode<Object> arg0, TNode<Object> arg1,
FrameState frame_state);
// Emplace a copy of the call node into the graph at current effect/control.
TNode<Object> CopyNode();
// Used in special cases in which we are certain CreateArray does not throw.
TNode<JSArray> CreateArrayNoThrow(TNode<Object> ctor, TNode<Number> size,
FrameState frame_state);
@ -1111,6 +1117,15 @@ TNode<Object> JSCallReducerAssembler::JSCallRuntime2(
});
}
TNode<Object> JSCallReducerAssembler::CopyNode() {
return MayThrow(_ {
Node* copy = graph()->CloneNode(node_ptr());
NodeProperties::ReplaceEffectInput(copy, effect());
NodeProperties::ReplaceControlInput(copy, control());
return AddNode<Object>(copy);
});
}
TNode<JSArray> JSCallReducerAssembler::CreateArrayNoThrow(
TNode<Object> ctor, TNode<Number> size, FrameState frame_state) {
return AddNode<JSArray>(graph()->NewNode(
@ -2461,120 +2476,125 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
? CallFeedbackRelation::kTarget
: CallFeedbackRelation::kUnrelated;
int arity = p.arity_without_implicit_args();
ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny;
if (arity == 0) {
// Neither thisArg nor argArray was provided.
convert_mode = ConvertReceiverMode::kNullOrUndefined;
node->ReplaceInput(n.TargetIndex(), n.receiver());
node->ReplaceInput(n.ReceiverIndex(), jsgraph()->UndefinedConstant());
} else if (arity == 1) {
// The argArray was not provided, just remove the {target}.
node->RemoveInput(n.TargetIndex());
--arity;
} else {
Node* target = n.receiver();
Node* this_argument = n.Argument(0);
Node* arguments_list = n.Argument(1);
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
// If {arguments_list} cannot be null or undefined, we don't need
// to expand this {node} to control-flow.
if (!NodeProperties::CanBeNullOrUndefined(broker(), arguments_list,
effect)) {
// Massage the value inputs appropriately.
node->ReplaceInput(n.TargetIndex(), target);
node->ReplaceInput(n.ReceiverIndex(), this_argument);
node->ReplaceInput(n.ArgumentIndex(0), arguments_list);
while (arity-- > 1) node->RemoveInput(n.ArgumentIndex(1));
// Morph the {node} to a {JSCallWithArrayLike}.
NodeProperties::ChangeOp(
node, javascript()->CallWithArrayLike(p.frequency(), p.feedback(),
p.speculation_mode(),
new_feedback_relation));
return Changed(node).FollowedBy(ReduceJSCallWithArrayLike(node));
if (arity < 2) {
// Degenerate cases.
ConvertReceiverMode convert_mode;
if (arity == 0) {
// Neither thisArg nor argArray was provided.
convert_mode = ConvertReceiverMode::kNullOrUndefined;
node->ReplaceInput(n.TargetIndex(), n.receiver());
node->ReplaceInput(n.ReceiverIndex(), jsgraph()->UndefinedConstant());
} else {
// Check whether {arguments_list} is null.
Node* check_null =
graph()->NewNode(simplified()->ReferenceEqual(), arguments_list,
jsgraph()->NullConstant());
control = graph()->NewNode(common()->Branch(BranchHint::kFalse),
check_null, control);
Node* if_null = graph()->NewNode(common()->IfTrue(), control);
control = graph()->NewNode(common()->IfFalse(), control);
// Check whether {arguments_list} is undefined.
Node* check_undefined =
graph()->NewNode(simplified()->ReferenceEqual(), arguments_list,
jsgraph()->UndefinedConstant());
control = graph()->NewNode(common()->Branch(BranchHint::kFalse),
check_undefined, control);
Node* if_undefined = graph()->NewNode(common()->IfTrue(), control);
control = graph()->NewNode(common()->IfFalse(), control);
// Lower to {JSCallWithArrayLike} if {arguments_list} is neither null
// nor undefined.
Node* effect0 = effect;
Node* control0 = control;
Node* value0 = effect0 = control0 = graph()->NewNode(
javascript()->CallWithArrayLike(p.frequency(), p.feedback(),
p.speculation_mode(),
new_feedback_relation),
target, this_argument, arguments_list, n.feedback_vector(), context,
frame_state, effect0, control0);
// Lower to {JSCall} if {arguments_list} is either null or undefined.
Node* effect1 = effect;
Node* control1 =
graph()->NewNode(common()->Merge(2), if_null, if_undefined);
Node* value1 = effect1 = control1 =
graph()->NewNode(javascript()->Call(JSCallNode::ArityForArgc(0)),
target, this_argument, n.feedback_vector(), context,
frame_state, effect1, control1);
// Rewire potential exception edges.
Node* if_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception0 =
graph()->NewNode(common()->IfException(), control0, effect0);
control0 = graph()->NewNode(common()->IfSuccess(), control0);
Node* if_exception1 =
graph()->NewNode(common()->IfException(), control1, effect1);
control1 = graph()->NewNode(common()->IfSuccess(), control1);
// Join the exception edges.
Node* merge =
graph()->NewNode(common()->Merge(2), if_exception0, if_exception1);
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0,
if_exception1, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_exception0, if_exception1, merge);
ReplaceWithValue(if_exception, phi, ephi, merge);
}
// Join control paths.
control = graph()->NewNode(common()->Merge(2), control0, control1);
effect =
graph()->NewNode(common()->EffectPhi(2), effect0, effect1, control);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
value0, value1, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
DCHECK_EQ(arity, 1);
// The argArray was not provided, just remove the {target}.
convert_mode = ConvertReceiverMode::kAny;
node->RemoveInput(n.TargetIndex());
--arity;
}
// Change {node} to a {JSCall} and try to reduce further.
NodeProperties::ChangeOp(
node, javascript()->Call(JSCallNode::ArityForArgc(arity), p.frequency(),
p.feedback(), convert_mode,
p.speculation_mode(), new_feedback_relation));
return Changed(node).FollowedBy(ReduceJSCall(node));
}
// Change {node} to the new {JSCall} operator.
NodeProperties::ChangeOp(
node, javascript()->Call(JSCallNode::ArityForArgc(arity), p.frequency(),
p.feedback(), convert_mode, p.speculation_mode(),
new_feedback_relation));
// Try to further reduce the JSCall {node}.
return Changed(node).FollowedBy(ReduceJSCall(node));
// Turn the JSCall into a JSCallWithArrayLike.
// If {argArray} can be null or undefined, we have to generate branches since
// JSCallWithArrayLike would throw for null or undefined.
Node* target = n.receiver();
Node* this_argument = n.Argument(0);
Node* arguments_list = n.Argument(1);
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
// If {arguments_list} cannot be null or undefined, we don't need
// to expand this {node} to control-flow.
if (!NodeProperties::CanBeNullOrUndefined(broker(), arguments_list, effect)) {
// Massage the value inputs appropriately.
node->ReplaceInput(n.TargetIndex(), target);
node->ReplaceInput(n.ReceiverIndex(), this_argument);
node->ReplaceInput(n.ArgumentIndex(0), arguments_list);
while (arity-- > 1) node->RemoveInput(n.ArgumentIndex(1));
// Morph the {node} to a {JSCallWithArrayLike}.
NodeProperties::ChangeOp(
node, javascript()->CallWithArrayLike(p.frequency(), p.feedback(),
p.speculation_mode(),
new_feedback_relation));
return Changed(node).FollowedBy(ReduceJSCallWithArrayLike(node));
}
// Check whether {arguments_list} is null.
Node* check_null =
graph()->NewNode(simplified()->ReferenceEqual(), arguments_list,
jsgraph()->NullConstant());
control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check_null,
control);
Node* if_null = graph()->NewNode(common()->IfTrue(), control);
control = graph()->NewNode(common()->IfFalse(), control);
// Check whether {arguments_list} is undefined.
Node* check_undefined =
graph()->NewNode(simplified()->ReferenceEqual(), arguments_list,
jsgraph()->UndefinedConstant());
control = graph()->NewNode(common()->Branch(BranchHint::kFalse),
check_undefined, control);
Node* if_undefined = graph()->NewNode(common()->IfTrue(), control);
control = graph()->NewNode(common()->IfFalse(), control);
// Lower to {JSCallWithArrayLike} if {arguments_list} is neither null
// nor undefined.
Node* effect0 = effect;
Node* control0 = control;
Node* value0 = effect0 = control0 = graph()->NewNode(
javascript()->CallWithArrayLike(p.frequency(), p.feedback(),
p.speculation_mode(),
new_feedback_relation),
target, this_argument, arguments_list, n.feedback_vector(), context,
frame_state, effect0, control0);
// Lower to {JSCall} if {arguments_list} is either null or undefined.
Node* effect1 = effect;
Node* control1 = graph()->NewNode(common()->Merge(2), if_null, if_undefined);
Node* value1 = effect1 = control1 = graph()->NewNode(
javascript()->Call(JSCallNode::ArityForArgc(0)), target, this_argument,
n.feedback_vector(), context, frame_state, effect1, control1);
// Rewire potential exception edges.
Node* if_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception0 =
graph()->NewNode(common()->IfException(), control0, effect0);
control0 = graph()->NewNode(common()->IfSuccess(), control0);
Node* if_exception1 =
graph()->NewNode(common()->IfException(), control1, effect1);
control1 = graph()->NewNode(common()->IfSuccess(), control1);
// Join the exception edges.
Node* merge =
graph()->NewNode(common()->Merge(2), if_exception0, if_exception1);
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0,
if_exception1, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_exception0, if_exception1, merge);
ReplaceWithValue(if_exception, phi, ephi, merge);
}
// Join control paths.
control = graph()->NewNode(common()->Merge(2), control0, control1);
effect = graph()->NewNode(common()->EffectPhi(2), effect0, effect1, control);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), value0,
value1, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES section #sec-function.prototype.bind
@ -4164,104 +4184,103 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
if (!dependencies()->DependOnArrayIteratorProtector()) return NoChange();
}
if (arguments_list->opcode() == IrOpcode::kJSCreateEmptyLiteralArray) {
if (generated_calls_with_array_like_or_spread_.count(node)) {
return NoChange(); // Avoid infinite recursion.
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceJSCallWithArrayLikeOrSpreadOfEmpty(
&generated_calls_with_array_like_or_spread_);
return ReplaceWithSubgraph(&a, subgraph);
}
DCHECK_EQ(arguments_list->opcode(), IrOpcode::kJSCreateLiteralArray);
int new_argument_count;
if (arguments_list->opcode() == IrOpcode::kJSCreateLiteralArray) {
// Find array length and elements' kind from the feedback's allocation
// site's boilerplate JSArray..
JSCreateLiteralOpNode args_node(arguments_list);
CreateLiteralParameters const& args_params = args_node.Parameters();
const FeedbackSource& array_feedback = args_params.feedback();
const ProcessedFeedback& feedback =
broker()->GetFeedbackForArrayOrObjectLiteral(array_feedback);
if (feedback.IsInsufficient()) return NoChange();
AllocationSiteRef site = feedback.AsLiteral().value();
if (!site.IsFastLiteral()) return NoChange();
// Find array length and elements' kind from the feedback's allocation
// site's boilerplate JSArray..
JSCreateLiteralOpNode args_node(arguments_list);
CreateLiteralParameters const& args_params = args_node.Parameters();
const FeedbackSource& array_feedback = args_params.feedback();
const ProcessedFeedback& feedback =
broker()->GetFeedbackForArrayOrObjectLiteral(array_feedback);
if (feedback.IsInsufficient()) return NoChange();
base::Optional<JSArrayRef> boilerplate_array =
site.boilerplate()->AsJSArray();
int const array_length = boilerplate_array->GetBoilerplateLength().AsSmi();
AllocationSiteRef site = feedback.AsLiteral().value();
if (!site.IsFastLiteral()) return NoChange();
// We'll replace the arguments_list input with {array_length} element loads.
new_argument_count = argument_count - 1 + array_length;
base::Optional<JSArrayRef> boilerplate_array =
site.boilerplate()->AsJSArray();
int const array_length = boilerplate_array->GetBoilerplateLength().AsSmi();
// Do not optimize calls with a large number of arguments.
// Arbitrarily sets the limit to 32 arguments.
const int kMaxArityForOptimizedFunctionApply = 32;
if (new_argument_count > kMaxArityForOptimizedFunctionApply) {
return NoChange();
}
// We'll replace the arguments_list input with {array_length} element loads.
new_argument_count = argument_count - 1 + array_length;
// Determine the array's map.
MapRef array_map = boilerplate_array->map();
if (!array_map.supports_fast_array_iteration()) {
return NoChange();
}
// Do not optimize calls with a large number of arguments.
// Arbitrarily sets the limit to 32 arguments.
const int kMaxArityForOptimizedFunctionApply = 32;
if (new_argument_count > kMaxArityForOptimizedFunctionApply) {
return NoChange();
}
// Check and depend on NoElementsProtector.
if (!dependencies()->DependOnNoElementsProtector()) {
return NoChange();
}
// Determine the array's map.
MapRef array_map = boilerplate_array->map();
if (!array_map.supports_fast_array_iteration()) {
return NoChange();
}
// Remove the {arguments_list} node which will be replaced by a sequence of
// LoadElement nodes.
node->RemoveInput(arraylike_or_spread_index);
// Check and depend on NoElementsProtector.
if (!dependencies()->DependOnNoElementsProtector()) {
return NoChange();
}
// Speculate on that array's map is still equal to the dynamic map of
// arguments_list; generate a map check.
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone,
ZoneHandleSet<Map>(array_map.object()),
feedback_source),
arguments_list, effect, control);
// Remove the {arguments_list} node which will be replaced by a sequence of
// LoadElement nodes.
node->RemoveInput(arraylike_or_spread_index);
// Speculate on that array's length being equal to the dynamic length of
// arguments_list; generate a deopt check.
ElementsKind elements_kind = array_map.elements_kind();
effect = CheckArrayLength(arguments_list, elements_kind, array_length,
feedback_source, effect, control);
// Speculate on that array's map is still equal to the dynamic map of
// arguments_list; generate a map check.
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone,
ZoneHandleSet<Map>(array_map.object()),
feedback_source),
arguments_list, effect, control);
// Generate N element loads to replace the {arguments_list} node with a set
// of arguments loaded from it.
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
arguments_list, effect, control);
for (int i = 0; i < array_length; i++) {
// Load the i-th element from the array.
Node* index = jsgraph()->Constant(i);
Node* load = effect = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement(elements_kind)),
elements, index, effect, control);
// Speculate on that array's length being equal to the dynamic length of
// arguments_list; generate a deopt check.
ElementsKind elements_kind = array_map.elements_kind();
effect = CheckArrayLength(arguments_list, elements_kind, array_length,
feedback_source, effect, control);
// In "holey" arrays some arguments might be missing and we pass
// 'undefined' instead.
if (IsHoleyElementsKind(elements_kind)) {
if (elements_kind == HOLEY_DOUBLE_ELEMENTS) {
// May deopt for holey double elements.
load = effect = graph()->NewNode(
simplified()->CheckFloat64Hole(
CheckFloat64HoleMode::kAllowReturnHole, feedback_source),
load, effect, control);
} else {
load = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(),
load);
}
// Generate N element loads to replace the {arguments_list} node with a set
// of arguments loaded from it.
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
arguments_list, effect, control);
for (int i = 0; i < array_length; i++) {
// Load the i-th element from the array.
Node* index = jsgraph()->Constant(i);
Node* load = effect = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement(elements_kind)),
elements, index, effect, control);
// In "holey" arrays some arguments might be missing and we pass
// 'undefined' instead.
if (IsHoleyElementsKind(elements_kind)) {
if (elements_kind == HOLEY_DOUBLE_ELEMENTS) {
// May deopt for holey double elements.
load = effect = graph()->NewNode(
simplified()->CheckFloat64Hole(
CheckFloat64HoleMode::kAllowReturnHole, feedback_source),
load, effect, control);
} else {
load = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(),
load);
}
node->InsertInput(graph()->zone(), arraylike_or_spread_index + i, load);
}
} else {
DCHECK_EQ(arguments_list->opcode(), IrOpcode::kJSCreateEmptyLiteralArray);
// Remove the {arguments_list} node; there are no arguments nodes to add.
new_argument_count = argument_count - 1;
node->RemoveInput(arraylike_or_spread_index);
// Speculate on that array's length being equal to the dynamic length of
// arguments_list; generate a deopting check.
effect = CheckArrayLength(arguments_list, NO_ELEMENTS, 0, feedback_source,
effect, control);
node->InsertInput(graph()->zone(), arraylike_or_spread_index + i, load);
}
NodeProperties::ChangeOp(
@ -4849,6 +4868,52 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
return NoChange();
}
TNode<Object> JSCallReducerAssembler::ReduceJSCallWithArrayLikeOrSpreadOfEmpty(
std::unordered_set<Node*>* generated_calls_with_array_like_or_spread) {
DCHECK_EQ(generated_calls_with_array_like_or_spread->count(node_ptr()), 0);
JSCallWithArrayLikeOrSpreadNode n(node_ptr());
CallParameters const& p = n.Parameters();
TNode<Object> arguments_list = n.LastArgument();
DCHECK_EQ(static_cast<Node*>(arguments_list)->opcode(),
IrOpcode::kJSCreateEmptyLiteralArray);
// Turn the JSCallWithArrayLike or JSCallWithSpread roughly into:
//
// "arguments_list array is still empty?"
// |
// |
// Branch
// / \
// / \
// IfTrue IfFalse
// | |
// | |
// JSCall JSCallWithArrayLike/JSCallWithSpread
// \ /
// \ /
// Merge
TNode<Number> length = TNode<Number>::UncheckedCast(
LoadField(AccessBuilder::ForJSArrayLength(NO_ELEMENTS), arguments_list));
return SelectIf<Object>(NumberEqual(length, ZeroConstant()))
.Then([&]() {
TNode<Object> call = CopyNode();
static_cast<Node*>(call)->RemoveInput(n.LastArgumentIndex());
NodeProperties::ChangeOp(
call, javascript()->Call(p.arity() - 1, p.frequency(), p.feedback(),
p.convert_mode(), p.speculation_mode(),
p.feedback_relation()));
return call;
})
.Else([&]() {
TNode<Object> call = CopyNode();
generated_calls_with_array_like_or_spread->insert(call);
return call;
})
.ExpectFalse()
.Value();
}
Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) {
JSCallWithArrayLikeNode n(node);
CallParameters const& p = n.Parameters();

View File

@ -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;
};

View File

@ -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 {

View File

@ -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";