Commit 5ee1b7ad authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Add IC support for Call/ConstructWithSpread.

Properly hook up the (existing) IC slots for the CallWithSpread and
ConstructWithSpread bytecodes, and change the interpreter to collect
feedback (call counts and regular target function feedback) for those.
There's no integration with the Array constructor yet, since that
requires some yak shaving to thread through the AllocationSite to the
Array constructor stub. Once we have a solution for that, we can also
remove the current code duplication in the Call/Construct IC logic.

Also properly hook up the newly available feedback in TurboFan. This
will fix not only the missing target feedback, but more importantly
the tear-up decisions for optimization are correct now in the presence
of spread calls, and even more importantly the inlining heurstic has
proper call frequencies for those.

Some follow-up changes will be necessary to make sure we use the
feedback even for corner cases that aren't handled properly yet. Also
we should consider collecting feedback about the map of the spread
at some point to be able to always inline the spread calls.

Bug: v8:6399, v8:6527, v8:6630
Change-Id: I818dbcb411fd3951d8e9d31f5d7e794f8d60fa00
Reviewed-on: https://chromium-review.googlesource.com/582647Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46832}
parent e0e3049e
......@@ -1504,12 +1504,30 @@ void BytecodeGraphBuilder::VisitCallWithSpread() {
Node* callee =
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0));
interpreter::Register receiver = bytecode_iterator().GetRegisterOperand(1);
Node* receiver_node = environment()->LookupRegister(receiver);
size_t reg_count = bytecode_iterator().GetRegisterCountOperand(2);
const Operator* call =
javascript()->CallWithSpread(static_cast<int>(reg_count + 1));
interpreter::Register first_arg = interpreter::Register(receiver.index() + 1);
int arg_count = static_cast<int>(reg_count) - 1;
Node* const* args =
GetCallArgumentsFromRegister(callee, receiver_node, first_arg, arg_count);
// Slot index of 0 is used indicate no feedback slot is available. Assert
// the assumption that slot index 0 is never a valid feedback slot.
int const slot_id = bytecode_iterator().GetIndexOperand(3);
STATIC_ASSERT(FeedbackVector::kReservedIndexCount > 0);
VectorSlotPair feedback = CreateVectorSlotPair(slot_id);
Node* value = ProcessCallArguments(call, callee, receiver, reg_count);
environment()->BindAccumulator(value, Environment::kAttachFrameState);
CallFrequency frequency = ComputeCallFrequency(slot_id);
const Operator* op = javascript()->CallWithSpread(
static_cast<int>(reg_count + 1), frequency, feedback);
Node* node = nullptr;
if (Node* simplified = TryBuildSimplifiedCall(
op, args, static_cast<int>(arg_count), feedback.slot())) {
if (environment() == nullptr) return;
node = simplified;
} else {
node = ProcessCallArguments(op, args, 2 + arg_count);
}
environment()->BindAccumulator(node, Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::VisitCallJSRuntime() {
......@@ -1626,17 +1644,30 @@ void BytecodeGraphBuilder::VisitConstructWithSpread() {
interpreter::Register callee_reg = bytecode_iterator().GetRegisterOperand(0);
interpreter::Register first_reg = bytecode_iterator().GetRegisterOperand(1);
size_t reg_count = bytecode_iterator().GetRegisterCountOperand(2);
// Slot index of 0 is used indicate no feedback slot is available. Assert
// the assumption that slot index 0 is never a valid feedback slot.
STATIC_ASSERT(FeedbackVector::kReservedIndexCount > 0);
int const slot_id = bytecode_iterator().GetIndexOperand(3);
VectorSlotPair feedback = CreateVectorSlotPair(slot_id);
Node* new_target = environment()->LookupAccumulator();
Node* callee = environment()->LookupRegister(callee_reg);
const Operator* op =
javascript()->ConstructWithSpread(static_cast<uint32_t>(reg_count + 2));
CallFrequency frequency = ComputeCallFrequency(slot_id);
const Operator* op = javascript()->ConstructWithSpread(
static_cast<uint32_t>(reg_count + 2), frequency, feedback);
int arg_count = static_cast<int>(reg_count);
Node* const* args = GetConstructArgumentsFromRegister(callee, new_target,
first_reg, arg_count);
Node* value = ProcessConstructArguments(op, args, 2 + arg_count);
environment()->BindAccumulator(value, Environment::kAttachFrameState);
Node* node = nullptr;
if (Node* simplified = TryBuildSimplifiedConstruct(
op, args, static_cast<int>(arg_count), feedback.slot())) {
if (environment() == nullptr) return;
node = simplified;
} else {
node = ProcessConstructArguments(op, args, 2 + arg_count);
}
environment()->BindAccumulator(node, Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::VisitInvokeIntrinsic() {
......
......@@ -934,7 +934,8 @@ bool IsSafeArgumentsElements(Node* node) {
} // namespace
Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
Node* node, int arity, CallFrequency const& frequency) {
Node* node, int arity, CallFrequency const& frequency,
VectorSlotPair const& feedback) {
DCHECK(node->opcode() == IrOpcode::kJSCallWithArrayLike ||
node->opcode() == IrOpcode::kJSCallWithSpread ||
node->opcode() == IrOpcode::kJSConstructWithArrayLike ||
......@@ -989,14 +990,14 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
break;
case IrOpcode::kJSCallWithSpread: {
// Ignore uses as spread input to calls with spread.
SpreadWithArityParameter p = SpreadWithArityParameterOf(user->op());
CallParameters p = CallParametersOf(user->op());
int const arity = static_cast<int>(p.arity() - 1);
if (user->InputAt(arity) == arguments_list) continue;
break;
}
case IrOpcode::kJSConstructWithSpread: {
// Ignore uses as spread input to construct with spread.
SpreadWithArityParameter p = SpreadWithArityParameterOf(user->op());
ConstructParameters p = ConstructParametersOf(user->op());
int const arity = static_cast<int>(p.arity() - 2);
if (user->InputAt(arity) == arguments_list) continue;
break;
......@@ -1089,12 +1090,13 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
if (node->opcode() == IrOpcode::kJSCallWithArrayLike ||
node->opcode() == IrOpcode::kJSCallWithSpread) {
NodeProperties::ChangeOp(node, javascript()->Call(arity + 1, frequency));
NodeProperties::ChangeOp(
node, javascript()->Call(arity + 1, frequency, feedback));
Reduction const reduction = ReduceJSCall(node);
return reduction.Changed() ? reduction : Changed(node);
} else {
NodeProperties::ChangeOp(node,
javascript()->Construct(arity + 2, frequency));
NodeProperties::ChangeOp(
node, javascript()->Construct(arity + 2, frequency, feedback));
Reduction const reduction = ReduceJSConstruct(node);
return reduction.Changed() ? reduction : Changed(node);
}
......@@ -1293,19 +1295,20 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) {
DCHECK_EQ(IrOpcode::kJSCallWithArrayLike, node->opcode());
CallFrequency frequency = CallFrequencyOf(node->op());
return ReduceCallOrConstructWithArrayLikeOrSpread(node, 2, frequency);
VectorSlotPair feedback;
return ReduceCallOrConstructWithArrayLikeOrSpread(node, 2, frequency,
feedback);
}
Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) {
DCHECK_EQ(IrOpcode::kJSCallWithSpread, node->opcode());
SpreadWithArityParameter const& p = SpreadWithArityParameterOf(node->op());
CallParameters const& p = CallParametersOf(node->op());
DCHECK_LE(3u, p.arity());
int arity = static_cast<int>(p.arity() - 1);
// TODO(turbofan): Collect call counts on spread call/construct and thread it
// through here.
CallFrequency frequency;
return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency);
CallFrequency frequency = p.frequency();
VectorSlotPair feedback = p.feedback();
return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency,
feedback);
}
Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
......@@ -1436,19 +1439,20 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
Reduction JSCallReducer::ReduceJSConstructWithArrayLike(Node* node) {
DCHECK_EQ(IrOpcode::kJSConstructWithArrayLike, node->opcode());
CallFrequency frequency = CallFrequencyOf(node->op());
return ReduceCallOrConstructWithArrayLikeOrSpread(node, 1, frequency);
VectorSlotPair feedback;
return ReduceCallOrConstructWithArrayLikeOrSpread(node, 1, frequency,
feedback);
}
Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) {
DCHECK_EQ(IrOpcode::kJSConstructWithSpread, node->opcode());
SpreadWithArityParameter const& p = SpreadWithArityParameterOf(node->op());
ConstructParameters const& p = ConstructParametersOf(node->op());
DCHECK_LE(3u, p.arity());
int arity = static_cast<int>(p.arity() - 2);
// TODO(turbofan): Collect call counts on spread call/construct and thread it
// through here.
CallFrequency frequency;
return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency);
CallFrequency frequency = p.frequency();
VectorSlotPair feedback = p.feedback();
return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency,
feedback);
}
Reduction JSCallReducer::ReduceReturnReceiver(Node* node) {
......
......@@ -24,6 +24,7 @@ class CommonOperatorBuilder;
class JSGraph;
class JSOperatorBuilder;
class SimplifiedOperatorBuilder;
class VectorSlotPair;
// Performs strength reduction on {JSConstruct} and {JSCall} nodes,
// which might allow inlining or other optimizations to be performed afterwards.
......@@ -69,7 +70,8 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
Reduction ReduceArrayMap(Handle<JSFunction> function, Node* node);
Reduction ReduceCallOrConstructWithArrayLikeOrSpread(
Node* node, int arity, CallFrequency const& frequency);
Node* node, int arity, CallFrequency const& frequency,
VectorSlotPair const& feedback);
Reduction ReduceJSConstruct(Node* node);
Reduction ReduceJSConstructWithArrayLike(Node* node);
Reduction ReduceJSConstructWithSpread(Node* node);
......
......@@ -593,7 +593,7 @@ void JSGenericLowering::LowerJSConstructWithArrayLike(Node* node) {
}
void JSGenericLowering::LowerJSConstructWithSpread(Node* node) {
SpreadWithArityParameter const& p = SpreadWithArityParameterOf(node->op());
ConstructParameters const& p = ConstructParametersOf(node->op());
int const arg_count = static_cast<int>(p.arity() - 2);
int const spread_index = arg_count;
int const new_target_index = arg_count + 1;
......@@ -664,7 +664,7 @@ void JSGenericLowering::LowerJSCallWithArrayLike(Node* node) {
}
void JSGenericLowering::LowerJSCallWithSpread(Node* node) {
SpreadWithArityParameter const& p = SpreadWithArityParameterOf(node->op());
CallParameters const& p = CallParametersOf(node->op());
int const arg_count = static_cast<int>(p.arity() - 2);
int const spread_index = static_cast<int>(p.arity() + 1);
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
......
......@@ -94,40 +94,18 @@ std::ostream& operator<<(std::ostream& os, ConstructParameters const& p) {
}
ConstructParameters const& ConstructParametersOf(Operator const* op) {
DCHECK_EQ(IrOpcode::kJSConstruct, op->opcode());
DCHECK(op->opcode() == IrOpcode::kJSConstruct ||
op->opcode() == IrOpcode::kJSConstructWithSpread);
return OpParameter<ConstructParameters>(op);
}
bool operator==(SpreadWithArityParameter const& lhs,
SpreadWithArityParameter const& rhs) {
return lhs.arity() == rhs.arity();
}
bool operator!=(SpreadWithArityParameter const& lhs,
SpreadWithArityParameter const& rhs) {
return !(lhs == rhs);
}
size_t hash_value(SpreadWithArityParameter const& p) {
return base::hash_combine(p.arity());
}
std::ostream& operator<<(std::ostream& os, SpreadWithArityParameter const& p) {
return os << p.arity();
}
SpreadWithArityParameter const& SpreadWithArityParameterOf(Operator const* op) {
DCHECK(op->opcode() == IrOpcode::kJSConstructWithSpread ||
op->opcode() == IrOpcode::kJSCallWithSpread);
return OpParameter<SpreadWithArityParameter>(op);
}
std::ostream& operator<<(std::ostream& os, CallParameters const& p) {
return os << p.arity() << ", " << p.frequency() << ", " << p.convert_mode();
}
const CallParameters& CallParametersOf(const Operator* op) {
DCHECK_EQ(IrOpcode::kJSCall, op->opcode());
DCHECK(op->opcode() == IrOpcode::kJSCall ||
op->opcode() == IrOpcode::kJSCallWithSpread);
return OpParameter<CallParameters>(op);
}
......@@ -768,9 +746,11 @@ const Operator* JSOperatorBuilder::CallWithArrayLike(CallFrequency frequency) {
frequency); // parameter
}
const Operator* JSOperatorBuilder::CallWithSpread(uint32_t arity) {
SpreadWithArityParameter parameters(arity);
return new (zone()) Operator1<SpreadWithArityParameter>( // --
const Operator* JSOperatorBuilder::CallWithSpread(
uint32_t arity, CallFrequency frequency, VectorSlotPair const& feedback) {
CallParameters parameters(arity, frequency, feedback,
ConvertReceiverMode::kAny);
return new (zone()) Operator1<CallParameters>( // --
IrOpcode::kJSCallWithSpread, Operator::kNoProperties, // opcode
"JSCallWithSpread", // name
parameters.arity(), 1, 1, 1, 1, 2, // counts
......@@ -832,9 +812,10 @@ const Operator* JSOperatorBuilder::ConstructWithArrayLike(
frequency); // parameter
}
const Operator* JSOperatorBuilder::ConstructWithSpread(uint32_t arity) {
SpreadWithArityParameter parameters(arity);
return new (zone()) Operator1<SpreadWithArityParameter>( // --
const Operator* JSOperatorBuilder::ConstructWithSpread(
uint32_t arity, CallFrequency frequency, VectorSlotPair const& feedback) {
ConstructParameters parameters(arity, frequency, feedback);
return new (zone()) Operator1<ConstructParameters>( // --
IrOpcode::kJSConstructWithSpread, Operator::kNoProperties, // opcode
"JSConstructWithSpread", // name
parameters.arity(), 1, 1, 1, 1, 2, // counts
......
......@@ -128,7 +128,7 @@ ConstructForwardVarargsParameters const& ConstructForwardVarargsParametersOf(
Operator const*) WARN_UNUSED_RESULT;
// Defines the arity and the feedback for a JavaScript constructor call. This is
// used as a parameter by JSConstruct operators.
// used as a parameter by JSConstruct and JSConstructWithSpread operators.
class ConstructParameters final {
public:
ConstructParameters(uint32_t arity, CallFrequency frequency,
......@@ -154,30 +154,6 @@ std::ostream& operator<<(std::ostream&, ConstructParameters const&);
ConstructParameters const& ConstructParametersOf(Operator const*);
// Defines the arity for JavaScript calls with a spread as the last
// parameter. This is used as a parameter by JSConstructWithSpread and
// JSCallWithSpread operators.
class SpreadWithArityParameter final {
public:
explicit SpreadWithArityParameter(uint32_t arity) : arity_(arity) {}
uint32_t arity() const { return arity_; }
private:
uint32_t const arity_;
};
bool operator==(SpreadWithArityParameter const&,
SpreadWithArityParameter const&);
bool operator!=(SpreadWithArityParameter const&,
SpreadWithArityParameter const&);
size_t hash_value(SpreadWithArityParameter const&);
std::ostream& operator<<(std::ostream&, SpreadWithArityParameter const&);
SpreadWithArityParameter const& SpreadWithArityParameterOf(Operator const*);
// Defines the flags for a JavaScript call forwarding parameters. This
// is used as parameter by JSCallForwardVarargs operators.
class CallForwardVarargsParameters final {
......@@ -213,7 +189,7 @@ CallForwardVarargsParameters const& CallForwardVarargsParametersOf(
Operator const*) WARN_UNUSED_RESULT;
// Defines the arity and the call flags for a JavaScript function call. This is
// used as a parameter by JSCall operators.
// used as a parameter by JSCall and JSCallWithSpread operators.
class CallParameters final {
public:
CallParameters(size_t arity, CallFrequency frequency,
......@@ -672,7 +648,9 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
VectorSlotPair const& feedback = VectorSlotPair(),
ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny);
const Operator* CallWithArrayLike(CallFrequency frequency);
const Operator* CallWithSpread(uint32_t arity);
const Operator* CallWithSpread(
uint32_t arity, CallFrequency frequency = CallFrequency(),
VectorSlotPair const& feedback = VectorSlotPair());
const Operator* CallRuntime(Runtime::FunctionId id);
const Operator* CallRuntime(Runtime::FunctionId id, size_t arity);
const Operator* CallRuntime(const Runtime::Function* function, size_t arity);
......@@ -682,7 +660,9 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
CallFrequency frequency = CallFrequency(),
VectorSlotPair const& feedback = VectorSlotPair());
const Operator* ConstructWithArrayLike(CallFrequency frequency);
const Operator* ConstructWithSpread(uint32_t arity);
const Operator* ConstructWithSpread(
uint32_t arity, CallFrequency frequency = CallFrequency(),
VectorSlotPair const& feedback = VectorSlotPair());
const Operator* ConvertReceiver(ConvertReceiverMode convert_mode);
......
......@@ -261,7 +261,8 @@ Reduction JSTypeHintLowering::ReduceCallOperation(const Operator* op,
int arg_count, Node* effect,
Node* control,
FeedbackSlot slot) const {
DCHECK_EQ(IrOpcode::kJSCall, op->opcode());
DCHECK(op->opcode() == IrOpcode::kJSCall ||
op->opcode() == IrOpcode::kJSCallWithSpread);
DCHECK(!slot.IsInvalid());
CallICNexus nexus(feedback_vector(), slot);
if (Node* node = TryBuildSoftDeopt(
......@@ -275,7 +276,8 @@ Reduction JSTypeHintLowering::ReduceCallOperation(const Operator* op,
Reduction JSTypeHintLowering::ReduceConstructOperation(
const Operator* op, Node* const* args, int arg_count, Node* effect,
Node* control, FeedbackSlot slot) const {
DCHECK_EQ(IrOpcode::kJSConstruct, op->opcode());
DCHECK(op->opcode() == IrOpcode::kJSConstruct ||
op->opcode() == IrOpcode::kJSConstructWithSpread);
DCHECK(!slot.IsInvalid());
CallICNexus nexus(feedback_vector(), slot);
if (Node* node = TryBuildSoftDeopt(
......
......@@ -1320,8 +1320,9 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CallAnyReceiver(Register callable,
}
BytecodeArrayBuilder& BytecodeArrayBuilder::CallWithSpread(Register callable,
RegisterList args) {
OutputCallWithSpread(callable, args, args.register_count());
RegisterList args,
int feedback_slot) {
OutputCallWithSpread(callable, args, args.register_count(), feedback_slot);
return *this;
}
......@@ -1333,8 +1334,9 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::Construct(Register constructor,
}
BytecodeArrayBuilder& BytecodeArrayBuilder::ConstructWithSpread(
Register constructor, RegisterList args) {
OutputConstructWithSpread(constructor, args, args.register_count());
Register constructor, RegisterList args, int feedback_slot_id) {
OutputConstructWithSpread(constructor, args, args.register_count(),
feedback_slot_id);
return *this;
}
......
......@@ -264,7 +264,8 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final
// Call a JS function. The JSFunction or Callable to be called should be in
// |callable|, the receiver in |args[0]| and the arguments in |args[1]|
// onwards. The final argument must be a spread.
BytecodeArrayBuilder& CallWithSpread(Register callable, RegisterList args);
BytecodeArrayBuilder& CallWithSpread(Register callable, RegisterList args,
int feedback_slot);
// Call the Construct operator. The accumulator holds the |new_target|.
// The |constructor| is in a register and arguments are in |args|.
......@@ -275,7 +276,8 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final
// the |new_target|. The |constructor| is in a register and arguments are in
// |args|. The final argument must be a spread.
BytecodeArrayBuilder& ConstructWithSpread(Register constructor,
RegisterList args);
RegisterList args,
int feedback_slot);
// Call the runtime function with |function_id| and arguments |args|.
BytecodeArrayBuilder& CallRuntime(Runtime::FunctionId function_id,
......
......@@ -3216,7 +3216,7 @@ void BytecodeGenerator::VisitCall(Call* expr) {
if (is_spread_call) {
DCHECK(!implicit_undefined_receiver);
builder()->CallWithSpread(callee, args);
builder()->CallWithSpread(callee, args, feedback_slot_index);
} else if (call_type == Call::NAMED_PROPERTY_CALL ||
call_type == Call::KEYED_PROPERTY_CALL) {
DCHECK(!implicit_undefined_receiver);
......@@ -3247,9 +3247,9 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) {
// When a super call contains a spread, a CallSuper AST node is only created
// if there is exactly one spread, and it is the last argument.
int const feedback_slot_index = feedback_index(expr->CallFeedbackICSlot());
if (expr->only_last_arg_is_spread()) {
// TODO(petermarshall): Collect type on the feedback slot.
builder()->ConstructWithSpread(constructor, args_regs);
builder()->ConstructWithSpread(constructor, args_regs, feedback_slot_index);
} else {
// Call construct.
// TODO(turbofan): For now we do gather feedback on super constructor
......@@ -3258,7 +3258,6 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) {
// is not an ideal solution for super constructor calls, but it gets
// the job done for now. In the long run we might want to revisit this
// and come up with a better way.
int const feedback_slot_index = feedback_index(expr->CallFeedbackICSlot());
builder()->Construct(constructor, args_regs, feedback_slot_index);
}
}
......@@ -3273,12 +3272,11 @@ void BytecodeGenerator::VisitCallNew(CallNew* expr) {
builder()->SetExpressionPosition(expr);
builder()->LoadAccumulatorWithRegister(constructor);
int const feedback_slot_index = feedback_index(expr->CallNewFeedbackSlot());
if (expr->only_last_arg_is_spread()) {
// TODO(petermarshall): Collect type on the feedback slot.
builder()->ConstructWithSpread(constructor, args);
builder()->ConstructWithSpread(constructor, args, feedback_slot_index);
} else {
builder()->Construct(constructor, args,
feedback_index(expr->CallNewFeedbackSlot()));
builder()->Construct(constructor, args, feedback_slot_index);
}
}
......
......@@ -178,7 +178,7 @@ namespace interpreter {
V(CallUndefinedReceiver2, AccumulatorUse::kWrite, OperandType::kReg, \
OperandType::kReg, OperandType::kReg, OperandType::kIdx) \
V(CallWithSpread, AccumulatorUse::kWrite, OperandType::kReg, \
OperandType::kRegList, OperandType::kRegCount) \
OperandType::kRegList, OperandType::kRegCount, OperandType::kIdx) \
V(CallRuntime, AccumulatorUse::kWrite, OperandType::kRuntimeId, \
OperandType::kRegList, OperandType::kRegCount) \
V(CallRuntimeForPair, AccumulatorUse::kNone, OperandType::kRuntimeId, \
......@@ -194,7 +194,7 @@ namespace interpreter {
V(Construct, AccumulatorUse::kReadWrite, OperandType::kReg, \
OperandType::kRegList, OperandType::kRegCount, OperandType::kIdx) \
V(ConstructWithSpread, AccumulatorUse::kReadWrite, OperandType::kReg, \
OperandType::kRegList, OperandType::kRegCount) \
OperandType::kRegList, OperandType::kRegCount, OperandType::kIdx) \
\
/* Test Operators */ \
V(TestEqual, AccumulatorUse::kReadWrite, OperandType::kReg, \
......
......@@ -559,6 +559,86 @@ Node* InterpreterAssembler::IncrementCallCount(Node* feedback_vector,
SKIP_WRITE_BARRIER);
}
void InterpreterAssembler::CollectCallFeedback(Node* target, Node* context,
Node* feedback_vector,
Node* slot_id) {
// TODO(bmeurer): Add support for the Array constructor AllocationSite,
// and unify this with the general Call/Construct IC code below.
Label extra_checks(this, Label::kDeferred), done(this);
// Increment the call count.
IncrementCallCount(feedback_vector, slot_id);
// Check if we have monomorphic {target} feedback already.
Node* feedback_element = LoadFixedArrayElement(feedback_vector, slot_id);
Node* feedback_value = LoadWeakCellValueUnchecked(feedback_element);
Branch(WordEqual(target, feedback_value), &done, &extra_checks);
BIND(&extra_checks);
{
Label check_initialized(this), initialize(this), mark_megamorphic(this);
// Check if it is a megamorphic target.
Comment("check if megamorphic");
Node* is_megamorphic =
WordEqual(feedback_element,
HeapConstant(FeedbackVector::MegamorphicSentinel(isolate())));
GotoIf(is_megamorphic, &done);
Comment("check if weak cell");
Node* is_weak_cell = WordEqual(LoadMap(feedback_element),
LoadRoot(Heap::kWeakCellMapRootIndex));
GotoIfNot(is_weak_cell, &check_initialized);
// If the weak cell is cleared, we have a new chance to become monomorphic.
Comment("check if weak cell is cleared");
Node* is_smi = TaggedIsSmi(feedback_value);
Branch(is_smi, &initialize, &mark_megamorphic);
BIND(&check_initialized);
{
// Check if it is uninitialized.
Comment("check if uninitialized");
Node* is_uninitialized = WordEqual(
feedback_element, LoadRoot(Heap::kuninitialized_symbolRootIndex));
Branch(is_uninitialized, &initialize, &mark_megamorphic);
}
BIND(&initialize);
{
// Check if {target} is a JSFunction in the current native context.
Comment("check if function in same native context");
GotoIf(TaggedIsSmi(target), &mark_megamorphic);
// TODO(bmeurer): Add support for arbitrary callables here, and
// check via GetFunctionRealm (see src/objects.cc).
GotoIfNot(IsJSFunction(target), &mark_megamorphic);
Node* target_context =
LoadObjectField(target, JSFunction::kContextOffset);
Node* target_native_context = LoadNativeContext(target_context);
GotoIfNot(WordEqual(LoadNativeContext(context), target_native_context),
&mark_megamorphic);
CreateWeakCellInFeedbackVector(feedback_vector, SmiTag(slot_id), target);
Goto(&done);
}
BIND(&mark_megamorphic);
{
// MegamorphicSentinel is an immortal immovable object so
// write-barrier is not needed.
Comment("transition to megamorphic");
DCHECK(Heap::RootIsImmortalImmovable(Heap::kmegamorphic_symbolRootIndex));
StoreFixedArrayElement(
feedback_vector, slot_id,
HeapConstant(FeedbackVector::MegamorphicSentinel(isolate())),
SKIP_WRITE_BARRIER);
Goto(&done);
}
}
BIND(&done);
}
Node* InterpreterAssembler::CallJSWithFeedback(
compiler::Node* function, compiler::Node* context,
compiler::Node* first_arg, compiler::Node* arg_count,
......@@ -750,9 +830,13 @@ Node* InterpreterAssembler::CallJS(Node* function, Node* context,
}
Node* InterpreterAssembler::CallJSWithSpread(Node* function, Node* context,
Node* first_arg, Node* arg_count) {
Node* first_arg, Node* arg_count,
Node* slot_id,
Node* feedback_vector) {
DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_));
DCHECK_EQ(Bytecodes::GetReceiverMode(bytecode_), ConvertReceiverMode::kAny);
CollectCallFeedback(function, context, feedback_vector, slot_id);
Comment("call using CallWithSpread builtin");
Callable callable = CodeFactory::InterpreterPushArgsThenCall(
isolate(), ConvertReceiverMode::kAny,
InterpreterPushArgsMode::kWithFinalSpread);
......@@ -914,18 +998,16 @@ Node* InterpreterAssembler::Construct(Node* constructor, Node* context,
Node* InterpreterAssembler::ConstructWithSpread(Node* constructor,
Node* context, Node* new_target,
Node* first_arg,
Node* arg_count) {
Node* arg_count, Node* slot_id,
Node* feedback_vector) {
DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_));
Variable return_value(this, MachineRepresentation::kTagged);
Comment("call using ConstructWithSpread");
CollectCallFeedback(constructor, context, feedback_vector, slot_id);
Comment("call using ConstructWithSpread builtin");
Callable callable = CodeFactory::InterpreterPushArgsThenConstruct(
isolate(), InterpreterPushArgsMode::kWithFinalSpread);
Node* code_target = HeapConstant(callable.code());
return_value.Bind(CallStub(callable.descriptor(), code_target, context,
arg_count, new_target, constructor,
UndefinedConstant(), first_arg));
return return_value.value();
return CallStub(callable.descriptor(), code_target, context, arg_count,
new_target, constructor, UndefinedConstant(), first_arg);
}
Node* InterpreterAssembler::CallRuntimeN(Node* function_id, Node* context,
......
......@@ -117,6 +117,12 @@ class V8_EXPORT_PRIVATE InterpreterAssembler : public CodeStubAssembler {
compiler::Node* IncrementCallCount(compiler::Node* feedback_vector,
compiler::Node* slot_id);
// Collect CALL_IC feedback for |target| function in the
// |feedback_vector| at |slot_id|.
void CollectCallFeedback(compiler::Node* target, compiler::Node* context,
compiler::Node* slot_id,
compiler::Node* feedback_vector);
// Call JSFunction or Callable |function| with |arg_count| arguments (not
// including receiver) and the first argument located at |first_arg|. Type
// feedback is collected in the slot at index |slot_id|.
......@@ -145,7 +151,9 @@ class V8_EXPORT_PRIVATE InterpreterAssembler : public CodeStubAssembler {
compiler::Node* CallJSWithSpread(compiler::Node* function,
compiler::Node* context,
compiler::Node* first_arg,
compiler::Node* arg_count);
compiler::Node* arg_count,
compiler::Node* slot_id,
compiler::Node* feedback_vector);
// Call constructor |constructor| with |arg_count| arguments (not
// including receiver) and the first argument located at
......@@ -166,7 +174,9 @@ class V8_EXPORT_PRIVATE InterpreterAssembler : public CodeStubAssembler {
compiler::Node* context,
compiler::Node* new_target,
compiler::Node* first_arg,
compiler::Node* arg_count);
compiler::Node* arg_count,
compiler::Node* slot_id,
compiler::Node* feedback_vector);
// Call runtime function with |arg_count| arguments and the first argument
// located at |first_arg|.
......
......@@ -1912,10 +1912,13 @@ IGNITION_HANDLER(CallWithSpread, InterpreterAssembler) {
Node* receiver_args_count = BytecodeOperandCount(2);
Node* receiver_count = Int32Constant(1);
Node* args_count = Int32Sub(receiver_args_count, receiver_count);
Node* slot_id = BytecodeOperandIdx(3);
Node* feedback_vector = LoadFeedbackVector();
Node* context = GetContext();
// Call into Runtime function CallWithSpread which does everything.
Node* result = CallJSWithSpread(callable, context, receiver_arg, args_count);
Node* result = CallJSWithSpread(callable, context, receiver_arg, args_count,
slot_id, feedback_vector);
SetAccumulator(result);
Dispatch();
}
......@@ -1933,9 +1936,12 @@ IGNITION_HANDLER(ConstructWithSpread, InterpreterAssembler) {
Node* first_arg_reg = BytecodeOperandReg(1);
Node* first_arg = RegisterLocation(first_arg_reg);
Node* args_count = BytecodeOperandCount(2);
Node* slot_id = BytecodeOperandIdx(3);
Node* feedback_vector = LoadFeedbackVector();
Node* context = GetContext();
Node* result = ConstructWithSpread(constructor, context, new_target,
first_arg, args_count);
Node* result =
ConstructWithSpread(constructor, context, new_target, first_arg,
args_count, slot_id, feedback_vector);
SetAccumulator(result);
Dispatch();
}
......
......@@ -11,7 +11,7 @@ snippet: "
"
frame size: 3
parameter count: 1
bytecode array length: 24
bytecode array length: 25
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 34 S> */ B(LdaGlobal), U8(0), U8(5),
......@@ -20,7 +20,7 @@ bytecodes: [
B(Star), R(0),
B(CreateArrayLiteral), U8(2), U8(9), U8(37),
B(Star), R(2),
/* 39 E> */ B(CallWithSpread), R(0), R(1), U8(2),
/* 39 E> */ B(CallWithSpread), R(0), R(1), U8(2), U8(3),
B(LdaUndefined),
/* 58 S> */ B(Return),
]
......@@ -38,7 +38,7 @@ snippet: "
"
frame size: 4
parameter count: 1
bytecode array length: 27
bytecode array length: 28
bytecodes: [
/* 30 E> */ B(StackCheck),
/* 34 S> */ B(LdaGlobal), U8(0), U8(5),
......@@ -49,7 +49,7 @@ bytecodes: [
B(Star), R(2),
B(CreateArrayLiteral), U8(2), U8(9), U8(37),
B(Star), R(3),
/* 39 E> */ B(CallWithSpread), R(0), R(1), U8(3),
/* 39 E> */ B(CallWithSpread), R(0), R(1), U8(3), U8(3),
B(LdaUndefined),
/* 61 S> */ B(Return),
]
......
......@@ -12,7 +12,7 @@ snippet: "
"
frame size: 7
parameter count: 1
bytecode array length: 56
bytecode array length: 57
bytecodes: [
/* 30 E> */ B(StackCheck),
B(CreateClosure), U8(0), U8(3), U8(2),
......@@ -33,7 +33,7 @@ bytecodes: [
/* 89 S> */ B(CreateArrayLiteral), U8(1), U8(6), U8(37),
B(Star), R(3),
B(Ldar), R(1),
/* 89 E> */ B(ConstructWithSpread), R(1), R(3), U8(1),
/* 89 E> */ B(ConstructWithSpread), R(1), R(3), U8(1), U8(4),
B(LdaUndefined),
/* 110 S> */ B(Return),
]
......@@ -51,7 +51,7 @@ snippet: "
"
frame size: 7
parameter count: 1
bytecode array length: 59
bytecode array length: 60
bytecodes: [
/* 30 E> */ B(StackCheck),
B(CreateClosure), U8(0), U8(3), U8(2),
......@@ -74,7 +74,7 @@ bytecodes: [
B(CreateArrayLiteral), U8(1), U8(6), U8(37),
B(Star), R(4),
B(Ldar), R(1),
/* 89 E> */ B(ConstructWithSpread), R(1), R(3), U8(2),
/* 89 E> */ B(ConstructWithSpread), R(1), R(3), U8(2), U8(4),
B(LdaUndefined),
/* 113 S> */ B(Return),
]
......
......@@ -19,7 +19,7 @@ snippet: "
"
frame size: 4
parameter count: 1
bytecode array length: 23
bytecode array length: 24
bytecodes: [
B(CreateRestParameter),
B(Star), R(2),
......@@ -30,7 +30,7 @@ bytecodes: [
/* 93 S> */ B(Ldar), R(1),
B(GetSuperConstructor), R(3),
B(Ldar), R(0),
/* 93 E> */ B(ConstructWithSpread), R(3), R(2), U8(1),
/* 93 E> */ B(ConstructWithSpread), R(3), R(2), U8(1), U8(3),
/* 93 S> */ B(Return),
]
constant pool: [
......@@ -53,7 +53,7 @@ snippet: "
"
frame size: 7
parameter count: 1
bytecode array length: 44
bytecode array length: 45
bytecodes: [
B(CreateRestParameter),
B(Star), R(2),
......@@ -68,7 +68,7 @@ bytecodes: [
B(Star), R(5),
B(Ldar), R(0),
B(Mov), R(2), R(6),
/* 140 E> */ B(ConstructWithSpread), R(4), R(5), U8(2),
/* 140 E> */ B(ConstructWithSpread), R(4), R(5), U8(2), U8(3),
B(Star), R(4),
B(Ldar), R(this),
/* 140 E> */ B(ThrowSuperAlreadyCalledIfNotHole),
......
......@@ -158,7 +158,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.CallRuntime(Runtime::kIsArray, reg)
.CallRuntimeForPair(Runtime::kLoadLookupSlotForCall, reg_list, pair)
.CallJSRuntime(Context::SPREAD_ITERABLE_INDEX, reg_list)
.CallWithSpread(reg, reg_list);
.CallWithSpread(reg, reg_list, 1);
// Emit binary operator invocations.
builder.BinaryOperation(Token::Value::ADD, reg, 1)
......@@ -203,7 +203,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
builder.Delete(reg, LanguageMode::SLOPPY).Delete(reg, LanguageMode::STRICT);
// Emit construct.
builder.Construct(reg, reg_list, 1).ConstructWithSpread(reg, reg_list);
builder.Construct(reg, reg_list, 1).ConstructWithSpread(reg, reg_list, 1);
// Emit test operator invocations.
builder.CompareOperation(Token::Value::EQ, reg, 1)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment