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:
parent
f35048cf99
commit
90a2430388
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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";
|
||||
|
Loading…
Reference in New Issue
Block a user