[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:
Paolo Severini 2021-05-21 01:54:53 -07:00 committed by V8 LUCI CQ
parent d33777fab9
commit fed41a9235
14 changed files with 1155 additions and 97 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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],
}],
]

View 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

View 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);
})();

View 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);
})();

View 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);
})();

View File

@ -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],