[compiler] Generalize CallWithArrayLike optimization
CallWithArrayLike was optimized in TF only for 'arguments' in inlined functions. Here we add logic to optimize also in non inlined functions, enabling the rewriting of Function.prototype.apply(f, [1, 2, 3]) as f(1, 2, 3). Bug: v8:9974 Change-Id: Icc9ccfc2276f75d06755176b55e7a02ddfdb04ed Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2805623 Commit-Queue: Paolo Severini <paolosev@microsoft.com> Reviewed-by: Georg Neis <neis@chromium.org> Cr-Commit-Position: refs/heads/master@{#74723}
This commit is contained in:
parent
d33777fab9
commit
fed41a9235
@ -2672,6 +2672,7 @@ void JSHeapBroker::InitializeAndStartSerializing() {
|
||||
mode_ = kSerializing;
|
||||
|
||||
// Throw away the dummy data that we created while disabled.
|
||||
feedback_.clear();
|
||||
refs_->Clear();
|
||||
refs_ =
|
||||
zone()->New<RefsMap>(kInitialRefsBucketCount, AddressMatcher(), zone());
|
||||
|
@ -870,6 +870,7 @@ class ScopeInfoRef : public HeapObjectRef {
|
||||
|
||||
#define BROKER_SFI_FIELDS(V) \
|
||||
V(int, internal_formal_parameter_count) \
|
||||
V(bool, has_simple_parameters) \
|
||||
V(bool, has_duplicate_parameters) \
|
||||
V(int, function_map_index) \
|
||||
V(FunctionKind, kind) \
|
||||
|
@ -3804,21 +3804,103 @@ bool IsCallWithArrayLikeOrSpread(Node* node) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
Node* node, int arraylike_or_spread_index, CallFrequency const& frequency,
|
||||
FeedbackSource const& feedback, SpeculationMode speculation_mode,
|
||||
CallFeedbackRelation feedback_relation) {
|
||||
DCHECK(IsCallOrConstructWithArrayLike(node) ||
|
||||
IsCallOrConstructWithSpread(node));
|
||||
DCHECK_IMPLIES(speculation_mode == SpeculationMode::kAllowSpeculation,
|
||||
feedback.IsValid());
|
||||
void JSCallReducer::CheckIfConstructor(Node* construct) {
|
||||
JSConstructNode n(construct);
|
||||
Node* new_target = n.new_target();
|
||||
Control control = n.control();
|
||||
|
||||
Node* arguments_list =
|
||||
NodeProperties::GetValueInput(node, arraylike_or_spread_index);
|
||||
if (arguments_list->opcode() != IrOpcode::kJSCreateArguments) {
|
||||
return NoChange();
|
||||
Node* check =
|
||||
graph()->NewNode(simplified()->ObjectIsConstructor(), new_target);
|
||||
Node* check_branch =
|
||||
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
||||
Node* check_fail = graph()->NewNode(common()->IfFalse(), check_branch);
|
||||
Node* check_throw = check_fail = graph()->NewNode(
|
||||
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
|
||||
jsgraph()->Constant(static_cast<int>(MessageTemplate::kNotConstructor)),
|
||||
new_target, n.context(), n.frame_state(), n.effect(), check_fail);
|
||||
control = graph()->NewNode(common()->IfTrue(), check_branch);
|
||||
NodeProperties::ReplaceControlInput(construct, control);
|
||||
|
||||
// Rewire potential exception edges.
|
||||
Node* on_exception = nullptr;
|
||||
if (NodeProperties::IsExceptionalCall(construct, &on_exception)) {
|
||||
// Create appropriate {IfException} and {IfSuccess} nodes.
|
||||
Node* if_exception =
|
||||
graph()->NewNode(common()->IfException(), check_throw, check_fail);
|
||||
check_fail = graph()->NewNode(common()->IfSuccess(), check_fail);
|
||||
|
||||
// Join the exception edges.
|
||||
Node* merge =
|
||||
graph()->NewNode(common()->Merge(2), if_exception, on_exception);
|
||||
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception,
|
||||
on_exception, merge);
|
||||
Node* phi =
|
||||
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
||||
if_exception, on_exception, merge);
|
||||
ReplaceWithValue(on_exception, phi, ephi, merge);
|
||||
merge->ReplaceInput(1, on_exception);
|
||||
ephi->ReplaceInput(1, on_exception);
|
||||
phi->ReplaceInput(1, on_exception);
|
||||
}
|
||||
|
||||
// The above %ThrowTypeError runtime call is an unconditional throw,
|
||||
// making it impossible to return a successful completion in this case. We
|
||||
// simply connect the successful completion to the graph end.
|
||||
Node* throw_node =
|
||||
graph()->NewNode(common()->Throw(), check_throw, check_fail);
|
||||
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool ShouldUseCallICFeedback(Node* node) {
|
||||
HeapObjectMatcher m(node);
|
||||
if (m.HasResolvedValue() || m.IsCheckClosure() || m.IsJSCreateClosure()) {
|
||||
// Don't use CallIC feedback when we know the function
|
||||
// being called, i.e. either know the closure itself or
|
||||
// at least the SharedFunctionInfo.
|
||||
return false;
|
||||
} else if (m.IsPhi()) {
|
||||
// Protect against endless loops here.
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
if (control->opcode() == IrOpcode::kLoop ||
|
||||
control->opcode() == IrOpcode::kDead)
|
||||
return false;
|
||||
// Check if {node} is a Phi of nodes which shouldn't
|
||||
// use CallIC feedback (not looking through loops).
|
||||
int const value_input_count = m.node()->op()->ValueInputCount();
|
||||
for (int n = 0; n < value_input_count; ++n) {
|
||||
if (ShouldUseCallICFeedback(node->InputAt(n))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Node* JSCallReducer::CheckArrayLength(Node* array, ElementsKind elements_kind,
|
||||
uint32_t array_length,
|
||||
const FeedbackSource& feedback_source,
|
||||
Effect effect, Control control) {
|
||||
Node* length = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSArrayLength(elements_kind)),
|
||||
array, effect, control);
|
||||
Node* check = graph()->NewNode(simplified()->NumberEqual(), length,
|
||||
jsgraph()->Constant(array_length));
|
||||
return graph()->NewNode(
|
||||
simplified()->CheckIf(DeoptimizeReason::kArrayLengthChanged,
|
||||
feedback_source),
|
||||
check, effect, control);
|
||||
}
|
||||
|
||||
Reduction
|
||||
JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpreadOfCreateArguments(
|
||||
Node* node, Node* arguments_list, int arraylike_or_spread_index,
|
||||
CallFrequency const& frequency, FeedbackSource const& feedback,
|
||||
SpeculationMode speculation_mode, CallFeedbackRelation feedback_relation) {
|
||||
DCHECK_EQ(arguments_list->opcode(), IrOpcode::kJSCreateArguments);
|
||||
|
||||
// Check if {node} is the only value user of {arguments_list} (except for
|
||||
// value uses in frame states). If not, we give up for now.
|
||||
for (Edge edge : arguments_list->use_edges()) {
|
||||
@ -3984,88 +4066,155 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
node, javascript()->Construct(JSConstructNode::ArityForArgc(argc),
|
||||
frequency, feedback));
|
||||
|
||||
JSConstructNode n(node);
|
||||
Node* new_target = n.new_target();
|
||||
FrameState frame_state = n.frame_state();
|
||||
Node* context = n.context();
|
||||
Effect effect = n.effect();
|
||||
Control control = n.control();
|
||||
|
||||
// Check whether the given new target value is a constructor function. The
|
||||
// replacement {JSConstruct} operator only checks the passed target value
|
||||
// but relies on the new target value to be implicitly valid.
|
||||
Node* check =
|
||||
graph()->NewNode(simplified()->ObjectIsConstructor(), new_target);
|
||||
Node* check_branch =
|
||||
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
||||
Node* check_fail = graph()->NewNode(common()->IfFalse(), check_branch);
|
||||
Node* check_throw = check_fail = graph()->NewNode(
|
||||
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
|
||||
jsgraph()->Constant(static_cast<int>(MessageTemplate::kNotConstructor)),
|
||||
new_target, context, frame_state, effect, check_fail);
|
||||
control = graph()->NewNode(common()->IfTrue(), check_branch);
|
||||
NodeProperties::ReplaceControlInput(node, control);
|
||||
|
||||
// Rewire potential exception edges.
|
||||
Node* on_exception = nullptr;
|
||||
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
||||
// Create appropriate {IfException} and {IfSuccess} nodes.
|
||||
Node* if_exception =
|
||||
graph()->NewNode(common()->IfException(), check_throw, check_fail);
|
||||
check_fail = graph()->NewNode(common()->IfSuccess(), check_fail);
|
||||
|
||||
// Join the exception edges.
|
||||
Node* merge =
|
||||
graph()->NewNode(common()->Merge(2), if_exception, on_exception);
|
||||
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception,
|
||||
on_exception, merge);
|
||||
Node* phi =
|
||||
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
||||
if_exception, on_exception, merge);
|
||||
ReplaceWithValue(on_exception, phi, ephi, merge);
|
||||
merge->ReplaceInput(1, on_exception);
|
||||
ephi->ReplaceInput(1, on_exception);
|
||||
phi->ReplaceInput(1, on_exception);
|
||||
}
|
||||
|
||||
// The above %ThrowTypeError runtime call is an unconditional throw,
|
||||
// making it impossible to return a successful completion in this case. We
|
||||
// simply connect the successful completion to the graph end.
|
||||
Node* throw_node =
|
||||
graph()->NewNode(common()->Throw(), check_throw, check_fail);
|
||||
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
|
||||
|
||||
CheckIfConstructor(node);
|
||||
return Changed(node).FollowedBy(ReduceJSConstruct(node));
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
Node* node, int argument_count, int arraylike_or_spread_index,
|
||||
CallFrequency const& frequency, FeedbackSource const& feedback_source,
|
||||
SpeculationMode speculation_mode, CallFeedbackRelation feedback_relation,
|
||||
Node* target, Effect effect, Control control) {
|
||||
DCHECK(IsCallOrConstructWithArrayLike(node) ||
|
||||
IsCallOrConstructWithSpread(node));
|
||||
DCHECK_IMPLIES(speculation_mode == SpeculationMode::kAllowSpeculation,
|
||||
feedback_source.IsValid());
|
||||
|
||||
bool ShouldUseCallICFeedback(Node* node) {
|
||||
HeapObjectMatcher m(node);
|
||||
if (m.HasResolvedValue() || m.IsCheckClosure() || m.IsJSCreateClosure()) {
|
||||
// Don't use CallIC feedback when we know the function
|
||||
// being called, i.e. either know the closure itself or
|
||||
// at least the SharedFunctionInfo.
|
||||
return false;
|
||||
} else if (m.IsPhi()) {
|
||||
// Protect against endless loops here.
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
if (control->opcode() == IrOpcode::kLoop ||
|
||||
control->opcode() == IrOpcode::kDead)
|
||||
return false;
|
||||
// Check if {node} is a Phi of nodes which shouldn't
|
||||
// use CallIC feedback (not looking through loops).
|
||||
int const value_input_count = m.node()->op()->ValueInputCount();
|
||||
for (int n = 0; n < value_input_count; ++n) {
|
||||
if (ShouldUseCallICFeedback(node->InputAt(n))) return true;
|
||||
}
|
||||
return false;
|
||||
Node* arguments_list =
|
||||
NodeProperties::GetValueInput(node, arraylike_or_spread_index);
|
||||
|
||||
if (arguments_list->opcode() == IrOpcode::kJSCreateArguments) {
|
||||
return ReduceCallOrConstructWithArrayLikeOrSpreadOfCreateArguments(
|
||||
node, arguments_list, arraylike_or_spread_index, frequency,
|
||||
feedback_source, speculation_mode, feedback_relation);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
if (!FLAG_turbo_optimize_apply) return NoChange();
|
||||
|
||||
// Optimization of construct nodes not supported yet.
|
||||
if (!IsCallWithArrayLikeOrSpread(node)) return NoChange();
|
||||
|
||||
// Avoid deoptimization loops.
|
||||
if (speculation_mode != SpeculationMode::kAllowSpeculation) return NoChange();
|
||||
|
||||
// Only optimize with array literals.
|
||||
if (arguments_list->opcode() != IrOpcode::kJSCreateLiteralArray &&
|
||||
arguments_list->opcode() != IrOpcode::kJSCreateEmptyLiteralArray) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
base::Optional<JSArrayRef> boilerplate_array =
|
||||
site.boilerplate()->AsJSArray();
|
||||
int const array_length = boilerplate_array->GetBoilerplateLength().AsSmi();
|
||||
|
||||
// We'll replace the arguments_list input with {array_length} element loads.
|
||||
new_argument_count = argument_count - 1 + array_length;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Determine the array's map.
|
||||
MapRef array_map = boilerplate_array->map();
|
||||
if (!array_map.supports_fast_array_iteration()) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
// Check and depend on NoElementsProtector.
|
||||
if (!dependencies()->DependOnNoElementsProtector()) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
|
||||
// Speculate on that array's length being equal to the dynamic length of
|
||||
// arguments_list; generate a deopt check.
|
||||
ElementsKind elements_kind = boilerplate_array->GetElementsKind();
|
||||
effect = CheckArrayLength(arguments_list, elements_kind, array_length,
|
||||
feedback_source, 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
NodeProperties::ChangeOp(
|
||||
node, javascript()->Call(
|
||||
JSCallNode::ArityForArgc(new_argument_count), frequency,
|
||||
feedback_source, ConvertReceiverMode::kNullOrUndefined,
|
||||
speculation_mode, CallFeedbackRelation::kUnrelated));
|
||||
NodeProperties::ReplaceEffectInput(node, effect);
|
||||
return Changed(node).FollowedBy(ReduceJSCall(node));
|
||||
}
|
||||
|
||||
bool JSCallReducer::IsBuiltinOrApiFunction(JSFunctionRef function) const {
|
||||
if (!function.serialized()) return false;
|
||||
@ -4658,8 +4807,9 @@ Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) {
|
||||
CallParameters const& p = n.Parameters();
|
||||
DCHECK_EQ(p.arity_without_implicit_args(), 1); // The arraylike object.
|
||||
return ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
node, n.LastArgumentIndex(), p.frequency(), p.feedback(),
|
||||
p.speculation_mode(), p.feedback_relation());
|
||||
node, n.ArgumentCount(), n.LastArgumentIndex(), p.frequency(),
|
||||
p.feedback(), p.speculation_mode(), p.feedback_relation(), n.target(),
|
||||
n.effect(), n.control());
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) {
|
||||
@ -4667,8 +4817,9 @@ Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) {
|
||||
CallParameters const& p = n.Parameters();
|
||||
DCHECK_GE(p.arity_without_implicit_args(), 1); // At least the spread.
|
||||
return ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
node, n.LastArgumentIndex(), p.frequency(), p.feedback(),
|
||||
p.speculation_mode(), p.feedback_relation());
|
||||
node, n.ArgumentCount(), n.LastArgumentIndex(), p.frequency(),
|
||||
p.feedback(), p.speculation_mode(), p.feedback_relation(), n.target(),
|
||||
n.effect(), n.control());
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
|
||||
@ -5088,8 +5239,9 @@ Reduction JSCallReducer::ReduceJSConstructWithArrayLike(Node* node) {
|
||||
const int arraylike_index = n.LastArgumentIndex();
|
||||
DCHECK_EQ(n.ArgumentCount(), 1); // The arraylike object.
|
||||
return ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
node, arraylike_index, p.frequency(), p.feedback(),
|
||||
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget);
|
||||
node, n.ArgumentCount(), arraylike_index, p.frequency(), p.feedback(),
|
||||
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget,
|
||||
n.target(), n.effect(), n.control());
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) {
|
||||
@ -5098,8 +5250,9 @@ Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) {
|
||||
const int spread_index = n.LastArgumentIndex();
|
||||
DCHECK_GE(n.ArgumentCount(), 1); // At least the spread.
|
||||
return ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
node, spread_index, p.frequency(), p.feedback(),
|
||||
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget);
|
||||
node, n.ArgumentCount(), spread_index, p.frequency(), p.feedback(),
|
||||
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget,
|
||||
n.target(), n.effect(), n.control());
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceReturnReceiver(Node* node) {
|
||||
|
@ -123,10 +123,15 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
|
||||
Reduction ReduceFastArrayIteratorNext(InstanceType type, Node* node,
|
||||
IterationKind kind);
|
||||
|
||||
Reduction ReduceCallOrConstructWithArrayLikeOrSpreadOfCreateArguments(
|
||||
Node* node, Node* arguments_list, int arraylike_or_spread_index,
|
||||
CallFrequency const& frequency, FeedbackSource const& feedback,
|
||||
SpeculationMode speculation_mode, CallFeedbackRelation feedback_relation);
|
||||
Reduction ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
Node* node, int arraylike_or_spread_index, CallFrequency const& frequency,
|
||||
FeedbackSource const& feedback, SpeculationMode speculation_mode,
|
||||
CallFeedbackRelation feedback_relation);
|
||||
Node* node, int argument_count, int arraylike_or_spread_index,
|
||||
CallFrequency const& frequency, FeedbackSource const& feedback_source,
|
||||
SpeculationMode speculation_mode, CallFeedbackRelation feedback_relation,
|
||||
Node* target, Effect effect, Control control);
|
||||
Reduction ReduceJSConstruct(Node* node);
|
||||
Reduction ReduceJSConstructWithArrayLike(Node* node);
|
||||
Reduction ReduceJSConstructWithSpread(Node* node);
|
||||
@ -231,6 +236,15 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
|
||||
|
||||
bool IsBuiltinOrApiFunction(JSFunctionRef target_ref) const;
|
||||
|
||||
// Check whether an array has the expected length. Returns the new effect.
|
||||
Node* CheckArrayLength(Node* array, ElementsKind elements_kind,
|
||||
uint32_t array_length,
|
||||
const FeedbackSource& feedback_source, Effect effect,
|
||||
Control control);
|
||||
|
||||
// Check whether the given new target value is a constructor function.
|
||||
void CheckIfConstructor(Node* call);
|
||||
|
||||
Graph* graph() const;
|
||||
JSGraph* jsgraph() const { return jsgraph_; }
|
||||
JSHeapBroker* broker() const { return broker_; }
|
||||
|
@ -2505,6 +2505,28 @@ void SerializerForBackgroundCompilation::ProcessBuiltinCall(
|
||||
}
|
||||
break;
|
||||
case Builtins::kReflectApply:
|
||||
if (arguments.size() >= 2) {
|
||||
// Drop hints for all arguments except the user-given receiver.
|
||||
Hints const new_receiver =
|
||||
arguments.size() >= 3
|
||||
? arguments[2]
|
||||
: Hints::SingleConstant(
|
||||
broker()->isolate()->factory()->undefined_value(),
|
||||
zone());
|
||||
HintsVector new_arguments({new_receiver}, zone());
|
||||
for (auto constant : arguments[1].constants()) {
|
||||
ProcessCalleeForCallOrConstruct(
|
||||
constant, base::nullopt, new_arguments, speculation_mode,
|
||||
kMissingArgumentsAreUnknown, result_hints);
|
||||
}
|
||||
for (auto const& virtual_closure : arguments[1].virtual_closures()) {
|
||||
ProcessCalleeForCallOrConstruct(
|
||||
Callee(virtual_closure), base::nullopt, new_arguments,
|
||||
speculation_mode, kMissingArgumentsAreUnknown, result_hints);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Builtins::kReflectConstruct:
|
||||
if (arguments.size() >= 2) {
|
||||
for (auto constant : arguments[1].constants()) {
|
||||
|
@ -69,7 +69,8 @@ namespace internal {
|
||||
V(WrongHandler, "wrong handler") \
|
||||
V(WrongName, "wrong name") \
|
||||
V(WrongValue, "wrong value") \
|
||||
V(NoInitialElement, "no initial element")
|
||||
V(NoInitialElement, "no initial element") \
|
||||
V(ArrayLengthChanged, "the array length changed")
|
||||
|
||||
enum class DeoptimizeReason : uint8_t {
|
||||
#define DEOPTIMIZE_REASON(Name, message) k##Name,
|
||||
|
@ -837,6 +837,9 @@ DEFINE_BOOL(turbo_dynamic_map_checks, true,
|
||||
DEFINE_BOOL(turbo_compress_translation_arrays, false,
|
||||
"compress translation arrays (experimental)")
|
||||
DEFINE_BOOL(turbo_inline_js_wasm_calls, false, "inline JS->Wasm calls")
|
||||
|
||||
DEFINE_BOOL(turbo_optimize_apply, false, "optimize Function.prototype.apply")
|
||||
|
||||
DEFINE_BOOL(turbo_collect_feedback_in_generic_lowering, true,
|
||||
"enable experimental feedback collection in generic lowering.")
|
||||
DEFINE_BOOL(isolate_script_cache_ageing, true,
|
||||
|
@ -93,6 +93,7 @@ v8_source_set("cctest_sources") {
|
||||
"compiler/serializer-tester.h",
|
||||
"compiler/test-basic-block-profiler.cc",
|
||||
"compiler/test-branch-combine.cc",
|
||||
"compiler/test-calls-with-arraylike-or-spread.cc",
|
||||
"compiler/test-code-assembler.cc",
|
||||
"compiler/test-code-generator.cc",
|
||||
"compiler/test-concurrent-shared-function-info.cc",
|
||||
|
@ -217,7 +217,8 @@
|
||||
'test-streaming-compilation/AsyncTestDeserializationFails': [SKIP],
|
||||
'test-streaming-compilation/AsyncTestDeserializationBypassesCompilation': [SKIP],
|
||||
|
||||
# %ObserveNode tests relies on TurboFan.
|
||||
# %ObserveNode tests rely on TurboFan.
|
||||
'test-calls-with-arraylike-or-spread/*': [SKIP],
|
||||
'test-sloppy-equality/*' : [SKIP],
|
||||
'test-js-to-wasm/*': [SKIP],
|
||||
'test-verify-type/*': [SKIP],
|
||||
@ -697,6 +698,7 @@
|
||||
'serializer-tester/SerializeConstructWithSpread': [SKIP],
|
||||
'serializer-tester/SerializeInlinedClosure': [SKIP],
|
||||
'serializer-tester/SerializeInlinedFunction': [SKIP],
|
||||
'test-calls-with-arraylike-or-spread/*': [SKIP],
|
||||
'test-cpu-profiler/TickLinesOptimized': [SKIP],
|
||||
'test-heap/TestOptimizeAfterBytecodeFlushingCandidate': [SKIP],
|
||||
'test-js-to-wasm/*': [SKIP],
|
||||
@ -744,6 +746,7 @@
|
||||
'test-cpu-profiler/DetailedSourcePositionAPI_Inlining': [SKIP],
|
||||
'serializer-tester/BoundFunctionArguments': [SKIP],
|
||||
'serializer-tester/BoundFunctionTarget': [SKIP],
|
||||
'test-calls-with-arraylike-or-spread/*': [SKIP],
|
||||
'test-js-to-wasm/*': [SKIP],
|
||||
}], # variant == turboprop or variant == turboprop_as_toptier
|
||||
|
||||
@ -1232,6 +1235,14 @@
|
||||
# SFI deduplication tests check compilation state, which always_sparkplug
|
||||
# can break.
|
||||
'test-web-snapshots/SFIDeduplication*': [SKIP],
|
||||
|
||||
# %ObserveNode tests rely on TurboFan.
|
||||
'test-calls-with-arraylike-or-spread/*': [SKIP],
|
||||
}],
|
||||
|
||||
################################################################################
|
||||
['variant == no_lfa', {
|
||||
'test-calls-with-arraylike-or-spread/*': [SKIP],
|
||||
}],
|
||||
|
||||
]
|
||||
|
132
test/cctest/compiler/test-calls-with-arraylike-or-spread.cc
Normal file
132
test/cctest/compiler/test-calls-with-arraylike-or-spread.cc
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
#include "src/flags/flags.h"
|
||||
#include "test/cctest/compiler/node-observer-tester.h"
|
||||
#include "test/cctest/test-api.h"
|
||||
#include "test/common/wasm/flag-utils.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace compiler {
|
||||
|
||||
void CompileRunWithNodeObserver(const std::string& js_code,
|
||||
int32_t expected_result,
|
||||
IrOpcode::Value initial_call_opcode,
|
||||
IrOpcode::Value updated_call_opcode1,
|
||||
IrOpcode::Value updated_call_opcode2) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
// Note: Make sure to not capture stack locations (e.g. `this`) here since
|
||||
// these lambdas are executed on another thread.
|
||||
ModificationObserver apply_call_observer(
|
||||
[initial_call_opcode](const Node* node) {
|
||||
CHECK_EQ(initial_call_opcode, node->opcode());
|
||||
},
|
||||
[updated_call_opcode1, updated_call_opcode2](
|
||||
const Node* node,
|
||||
const ObservableNodeState& old_state) -> NodeObserver::Observation {
|
||||
if (updated_call_opcode1 == node->opcode()) {
|
||||
return NodeObserver::Observation::kContinue;
|
||||
} else {
|
||||
CHECK(updated_call_opcode2 == node->opcode());
|
||||
return NodeObserver::Observation::kStop;
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
ObserveNodeScope scope(reinterpret_cast<i::Isolate*>(isolate),
|
||||
&apply_call_observer);
|
||||
|
||||
FlagScope<bool> allow_natives_syntax(&i::FLAG_allow_natives_syntax, true);
|
||||
FlagScope<bool> optimize_apply(&i::FLAG_turbo_optimize_apply, true);
|
||||
|
||||
v8::Local<v8::Value> result_value = CompileRun(js_code.c_str());
|
||||
|
||||
CHECK(result_value->IsNumber());
|
||||
int32_t result =
|
||||
ConvertJSValue<int32_t>::Get(result_value, env.local()).ToChecked();
|
||||
CHECK_EQ(result, expected_result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReduceJSCallWithArrayLike) {
|
||||
CompileRunWithNodeObserver(
|
||||
"function sum_js3(a, b, c) { return a + b + c; }"
|
||||
"function foo(x, y, z) {"
|
||||
" return %ObserveNode(sum_js3.apply(null, [x, y, z]));"
|
||||
"}"
|
||||
"%PrepareFunctionForOptimization(sum_js3);"
|
||||
"%PrepareFunctionForOptimization(foo);"
|
||||
"foo(41, 42, 43);"
|
||||
"%OptimizeFunctionOnNextCall(foo);"
|
||||
"foo(41, 42, 43);",
|
||||
126, IrOpcode::kJSCall,
|
||||
IrOpcode::kJSCall, // not JSCallWithArrayLike
|
||||
IrOpcode::kPhi); // JSCall => Phi when the call is inlined.
|
||||
}
|
||||
|
||||
TEST(ReduceJSCallWithSpread) {
|
||||
CompileRunWithNodeObserver(
|
||||
"function sum_js3(a, b, c) { return a + b + c; }"
|
||||
"function foo(x, y, z) {"
|
||||
" const numbers = [x, y, z];"
|
||||
" return %ObserveNode(sum_js3(...numbers));"
|
||||
"}"
|
||||
"%PrepareFunctionForOptimization(sum_js3);"
|
||||
"%PrepareFunctionForOptimization(foo);"
|
||||
"foo(41, 42, 43);"
|
||||
"%OptimizeFunctionOnNextCall(foo);"
|
||||
"foo(41, 42, 43)",
|
||||
126, IrOpcode::kJSCallWithSpread,
|
||||
IrOpcode::kJSCall, // not JSCallWithSpread
|
||||
IrOpcode::kPhi);
|
||||
}
|
||||
|
||||
TEST(ReduceJSCreateClosure) {
|
||||
CompileRunWithNodeObserver(
|
||||
"function foo_closure() {"
|
||||
" return function(a, b, c) {"
|
||||
" return a + b + c;"
|
||||
" }"
|
||||
"}"
|
||||
"const _foo_closure = foo_closure();"
|
||||
"%PrepareFunctionForOptimization(_foo_closure);"
|
||||
"function foo(x, y, z) {"
|
||||
" return %ObserveNode(foo_closure().apply(null, [x, y, z]));"
|
||||
"}"
|
||||
"%PrepareFunctionForOptimization(foo_closure);"
|
||||
"%PrepareFunctionForOptimization(foo);"
|
||||
"foo(41, 42, 43);"
|
||||
"%OptimizeFunctionOnNextCall(foo_closure);"
|
||||
"%OptimizeFunctionOnNextCall(foo);"
|
||||
"foo(41, 42, 43)",
|
||||
126, IrOpcode::kJSCall,
|
||||
IrOpcode::kJSCall, // not JSCallWithArrayLike
|
||||
IrOpcode::kPhi);
|
||||
}
|
||||
|
||||
TEST(ReduceJSCreateBoundFunction) {
|
||||
CompileRunWithNodeObserver(
|
||||
"function sum_js3(a, b, c) {"
|
||||
" return this.x + a + b + c;"
|
||||
"}"
|
||||
"function foo(x, y ,z) {"
|
||||
" return %ObserveNode(sum_js3.bind({x : 42}).apply(null, [ x, y, z ]));"
|
||||
"}"
|
||||
"%PrepareFunctionForOptimization(sum_js3);"
|
||||
"%PrepareFunctionForOptimization(foo);"
|
||||
"foo(41, 42, 43);"
|
||||
"%OptimizeFunctionOnNextCall(foo);"
|
||||
"foo(41, 42, 43)",
|
||||
168, IrOpcode::kJSCall,
|
||||
IrOpcode::kJSCall, // not JSCallWithArrayLike
|
||||
IrOpcode::kPhi);
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
41
test/mjsunit/compiler/call-with-arraylike-or-spread-2.js
Normal file
41
test/mjsunit/compiler/call-with-arraylike-or-spread-2.js
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2021 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 --turbo-optimize-apply --opt
|
||||
|
||||
// These tests do not work well if this script is run more than once (e.g.
|
||||
// --stress-opt); after a few runs the whole function is immediately compiled
|
||||
// and assertions would fail. We prevent re-runs.
|
||||
// Flags: --nostress-opt --no-always-opt
|
||||
|
||||
// Tests for optimization of CallWithSpread and CallWithArrayLike.
|
||||
// This test is in a separate file because it invalidates protectors.
|
||||
|
||||
// Test with holey array with default values.
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Setting value to be retrieved in place of hole.
|
||||
Array.prototype[1] = 'x';
|
||||
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y) {
|
||||
return sum_js3.apply(null, [x, , y]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('AxB', foo('A', 'B'));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
|
||||
// The protector should be invalidated, which prevents inlining.
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals('AxB', foo('A', 'B'));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
48
test/mjsunit/compiler/call-with-arraylike-or-spread-3.js
Normal file
48
test/mjsunit/compiler/call-with-arraylike-or-spread-3.js
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2021 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 --turbo-optimize-apply --opt
|
||||
|
||||
// These tests do not work well if this script is run more than once (e.g.
|
||||
// --stress-opt); after a few runs the whole function is immediately compiled
|
||||
// and assertions would fail. We prevent re-runs.
|
||||
// Flags: --nostress-opt --no-always-opt
|
||||
|
||||
// Tests for optimization of CallWithSpread and CallWithArrayLike.
|
||||
// This test is in a separate file because it invalidates protectors.
|
||||
|
||||
// Test with array prototype modified after compilation.
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y) {
|
||||
return sum_js3.apply(null, [x, , y]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('AundefinedB', foo('A', 'B'));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals('AundefinedB', foo('A', 'B'));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
|
||||
// Modify the array prototype, define a default value for element [1].
|
||||
Array.prototype[1] = 'x';
|
||||
assertUnoptimized(foo);
|
||||
|
||||
// Now the call will not be inlined.
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals('AxB', foo('A', 'B'));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
628
test/mjsunit/compiler/call-with-arraylike-or-spread.js
Normal file
628
test/mjsunit/compiler/call-with-arraylike-or-spread.js
Normal file
@ -0,0 +1,628 @@
|
||||
// Copyright 2021 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 --turbo-optimize-apply --opt
|
||||
|
||||
// These tests do not work well if this script is run more than once (e.g.
|
||||
// --stress-opt); after a few runs the whole function is immediately compiled
|
||||
// and assertions would fail. We prevent re-runs.
|
||||
// Flags: --nostress-opt --no-always-opt
|
||||
|
||||
// Tests for optimization of CallWithSpread and CallWithArrayLike.
|
||||
|
||||
// Test JSCallReducer::ReduceJSCallWithArrayLike.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
return sum_js3.apply(null, [x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test with holey array.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y) {
|
||||
return sum_js3.apply(null, [x,,y]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('AundefinedB', foo('A', 'B'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertEquals('AundefinedB', foo('A', 'B'));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test with holey-double array.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + (b ? b : .0) + c;
|
||||
}
|
||||
function foo(x, y) {
|
||||
return sum_js3.apply(null, [x,,y]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals(42.17, foo(16.11, 26.06));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
|
||||
// This is expected to deoptimize
|
||||
assertEquals(42.17, foo(16.11, 26.06));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertUnoptimized(foo);
|
||||
|
||||
// Optimize again
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals(42.17, foo(16.11, 26.06));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
|
||||
// This this it should stay optimized, but with the call not inlined.
|
||||
assertEquals(42.17, foo(16.11, 26.06));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test deopt when array size changes.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
let a = [x, y, z];
|
||||
a.push('*');
|
||||
return sum_js3.apply(null, a);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
// Here array size changes.
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
// Here it should deoptimize.
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertUnoptimized(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
// Now speculation mode prevents the optimization.
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test deopt when array map changes.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y, z, str) {
|
||||
let v = [x, y, z];
|
||||
if (str) {
|
||||
v[0] = str;
|
||||
}
|
||||
return sum_js3.apply(null, v);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals(78, foo(26, 6, 46, null));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
|
||||
// Compile function foo; inlines 'sum_js3' into 'foo'.
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(78, foo(26, 6, 46, null));
|
||||
assertOptimized(foo);
|
||||
|
||||
if (i < 3) {
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
} else {
|
||||
// i: 3: Speculation mode prevents optimization of sum_js3.apply() call.
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
}
|
||||
|
||||
// This should deoptimize:
|
||||
// i: 0: Deopt soft: insufficient type feedback for generic keyed access.
|
||||
// i: 1,2: Deopt eager: wrong map.
|
||||
// i: 3: Won't deopt anymore.
|
||||
assertEquals("v8646", foo(26, 6, 46, "v8"));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
|
||||
if (i < 3) {
|
||||
assertUnoptimized(foo);
|
||||
} else {
|
||||
assertOptimized(foo);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Test with FixedDoubleArray.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
return sum_js3.apply(null, [x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals(53.2, foo(11.03, 16.11, 26.06));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertEquals(53.2, foo(11.03, 16.11, 26.06));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test with empty array.
|
||||
(function () {
|
||||
"use strict";
|
||||
var got_interpreted = true;
|
||||
function fortytwo() {
|
||||
got_interpreted = %IsBeingInterpreted();
|
||||
return 42;
|
||||
}
|
||||
function foo() {
|
||||
return fortytwo.apply(null, []);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(fortytwo);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals(42, foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(got_interpreted);
|
||||
assertEquals(42, foo());
|
||||
assertFalse(got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test with empty array that changes size.
|
||||
(function () {
|
||||
"use strict";
|
||||
var got_interpreted = true;
|
||||
function fortytwo() {
|
||||
got_interpreted = %IsBeingInterpreted();
|
||||
return 42 + arguments.length;
|
||||
}
|
||||
function foo() {
|
||||
let v = [];
|
||||
v.push(42);
|
||||
let result = fortytwo.apply(null, v);
|
||||
return result;
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(fortytwo);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals(43, foo(1));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(got_interpreted);
|
||||
assertEquals(43, foo(1));
|
||||
assertTrue(got_interpreted);
|
||||
assertUnoptimized(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);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test Reflect.apply().
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y ,z) {
|
||||
return Reflect.apply(sum_js3, null, [x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test JSCallReducer::ReduceJSCallWithSpread.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
const numbers = [x, y, z];
|
||||
return sum_js3(...numbers);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test spread call with empty array.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
const args = [];
|
||||
return sum_js3(x, y, z, ...args);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test spread call with more args.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js_got_interpreted = true;
|
||||
function sum_js(a, b, c, d, e, f) {
|
||||
assertEquals(6, arguments.length);
|
||||
sum_js_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c + d + e + f;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
const numbers = [z, y, x];
|
||||
return sum_js(x, y, z, ...numbers);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abccba', foo('a', 'b', 'c'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js_got_interpreted);
|
||||
assertEquals('abccba', foo('a', 'b', 'c'));
|
||||
assertFalse(sum_js_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test apply on JSCreateClosure.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_got_interpreted = true;
|
||||
function foo_closure() {
|
||||
return function(a, b, c) {
|
||||
sum_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
}
|
||||
const _foo_closure = foo_closure();
|
||||
%PrepareFunctionForOptimization(_foo_closure);
|
||||
|
||||
function foo(x, y, z) {
|
||||
return foo_closure().apply(null, [x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(foo_closure);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
%OptimizeFunctionOnNextCall(foo_closure);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_got_interpreted);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertFalse(sum_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test apply with JSBoundFunction
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_got_interpreted = true;
|
||||
function sum_js(a, b, c, d, e) {
|
||||
sum_got_interpreted = %IsBeingInterpreted();
|
||||
return this.x + a + b + c + d + e;
|
||||
}
|
||||
const f = sum_js.bind({ x: 26 }, 11, 3);
|
||||
function foo(a, b, c) {
|
||||
return f.apply(null, [a, b, c]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals(166, foo(40, 42, 44));
|
||||
assertTrue(sum_got_interpreted);
|
||||
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(166, foo(40, 42, 44));
|
||||
assertFalse(sum_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test apply with nested bindings
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_got_interpreted = true;
|
||||
function sum_js(a, b, c, d, e) {
|
||||
sum_got_interpreted = %IsBeingInterpreted();
|
||||
return this.x + a + b + c + d + e;
|
||||
}
|
||||
const f = sum_js.bind({ x: 26 }, 11).bind({ y: 4 }, 3);
|
||||
function foo(x, y, z) {
|
||||
return f.apply(null, [x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals(166, foo(40, 42, 44));
|
||||
assertTrue(sum_got_interpreted);
|
||||
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(166, foo(40, 42, 44));
|
||||
assertFalse(sum_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test apply on bound function (JSCreateBoundFunction).
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_got_interpreted = %IsBeingInterpreted();
|
||||
return this.x + a + b + c;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
return sum_js3.bind({ x: 42 }).apply(null, [x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('42abc', foo('a', 'b', 'c'));
|
||||
assertTrue(sum_got_interpreted);
|
||||
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals('42abc', foo('a', 'b', 'c'));
|
||||
assertFalse(sum_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test apply on bound function (JSCreateBoundFunction) with args.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_got_interpreted = true;
|
||||
function sum_js(a, b, c, d, e) {
|
||||
sum_got_interpreted = %IsBeingInterpreted();
|
||||
return this.x + a + b + c + d + e;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
return sum_js.bind({ x: 3 }, 11, 31).apply(null, [x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('45abc', foo('a', 'b', 'c'));
|
||||
assertTrue(sum_got_interpreted);
|
||||
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals('45abc', foo('a', 'b', 'c'));
|
||||
assertFalse(sum_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test call with array-like under-application.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js_got_interpreted = true;
|
||||
function sum_js(a, b, c) {
|
||||
sum_js_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c + arguments.length;
|
||||
}
|
||||
function foo(x, y) {
|
||||
return sum_js.apply(null, [x, y]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('ABundefined2', foo('A', 'B'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js_got_interpreted);
|
||||
assertEquals('ABundefined2', foo('A', 'B'));
|
||||
assertFalse(sum_js_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test call with array-like over-application.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js_got_interpreted = true;
|
||||
function sum_js(a, b, c) {
|
||||
sum_js_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c + arguments.length;
|
||||
}
|
||||
function foo(v, w, x, y, z) {
|
||||
return sum_js.apply(null, [v, w, x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abc5', foo('a', 'b', 'c', 'd', 'e'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js_got_interpreted);
|
||||
assertEquals('abc5', foo('a', 'b', 'c', 'd', 'e'));
|
||||
assertFalse(sum_js_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test call with spread under-application.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js_got_interpreted = true;
|
||||
function sum_js(a, b, c) {
|
||||
sum_js_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c + arguments.length;
|
||||
}
|
||||
function foo(x, y) {
|
||||
const numbers = [x, y];
|
||||
return sum_js(...numbers);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('ABundefined2', foo('A', 'B'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js_got_interpreted);
|
||||
assertEquals('ABundefined2', foo('A', 'B'));
|
||||
assertFalse(sum_js_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test call with spread over-application.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c + arguments.length;
|
||||
}
|
||||
function foo(v, w, x, y, z) {
|
||||
const numbers = [v, w, x, y, z];
|
||||
return sum_js3(...numbers);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abc5', foo('a', 'b', 'c', 'd', 'e'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertEquals('abc5', foo('a', 'b', 'c', 'd', 'e'));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test calling function that has rest parameters.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js_got_interpreted = true;
|
||||
function sum_js(a, b, ...moreArgs) {
|
||||
sum_js_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + moreArgs[0] + moreArgs[1] + moreArgs[2];
|
||||
}
|
||||
function foo(v, w, x, y, z) {
|
||||
return sum_js.apply(null, [v, w, x, y, z]);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abcde', foo('a', 'b', 'c', 'd', 'e'));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(sum_js_got_interpreted);
|
||||
assertEquals('abcde', foo('a', 'b', 'c', 'd', 'e'));
|
||||
assertFalse(sum_js_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test with 'arguments'.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo() {
|
||||
return sum_js3.apply(null, arguments);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
|
||||
// The call is not inlined with CreateArguments.
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals('abc', foo('a', 'b', 'c'));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
assertOptimized(foo);
|
||||
})();
|
||||
|
||||
// Test with inlined calls.
|
||||
(function () {
|
||||
"use strict";
|
||||
var sum_js3_got_interpreted = true;
|
||||
function sum_js3(a, b, c) {
|
||||
sum_js3_got_interpreted = %IsBeingInterpreted();
|
||||
return a + b + c;
|
||||
}
|
||||
function foo(x, y, z) {
|
||||
return sum_js3.apply(null, [x, y, z]);
|
||||
}
|
||||
function bar(a, b, c) {
|
||||
return foo(c, b, a);
|
||||
}
|
||||
|
||||
%PrepareFunctionForOptimization(sum_js3);
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
%PrepareFunctionForOptimization(bar);
|
||||
assertEquals('cba', bar('a', 'b', 'c'));
|
||||
assertTrue(sum_js3_got_interpreted);
|
||||
|
||||
// Optimization also works if the call is in an inlined function.
|
||||
%OptimizeFunctionOnNextCall(bar);
|
||||
assertEquals('cba', bar('a', 'b', 'c'));
|
||||
assertFalse(sum_js3_got_interpreted);
|
||||
assertOptimized(bar);
|
||||
})();
|
@ -372,6 +372,7 @@
|
||||
'regress/regress-996234': [SKIP],
|
||||
|
||||
# These tests rely on TurboFan being enabled.
|
||||
'compiler/call-with-arraylike-or-spread*': [SKIP],
|
||||
'compiler/fast-api-calls': [SKIP],
|
||||
'compiler/fast-api-interface-types': [SKIP],
|
||||
'compiler/regress-crbug-1201011': [SKIP],
|
||||
@ -1280,6 +1281,7 @@
|
||||
'compiler/serializer-transition-propagation': [SKIP],
|
||||
|
||||
# Some tests rely on inlining.
|
||||
'compiler/call-with-arraylike-or-spread*': [SKIP],
|
||||
'compiler/inlined-call-polymorphic': [SKIP],
|
||||
'compiler/opt-higher-order-functions': [SKIP],
|
||||
'regress/regress-1049982-1': [SKIP],
|
||||
|
Loading…
Reference in New Issue
Block a user