Commit 519c82ce authored by Fanchen Kong's avatar Fanchen Kong Committed by V8 LUCI CQ

Collect receiver to feedback for prototype.apply

When a function is invoked by prototype.apply, it may undergo following transformation in the JSCallReducer:
	receiver.apply(this, args) ->
	this.receiver(...args) Since the new target (also the receiver of apply()) is not collected to the feedback slot, further speculative optimization on the new target is not available if the new target
is not a heapconstant.

With this CL, the receiver will be collected to the feedback instead of the target if the target is a prototype.apply. It may improve the performance of the following usecase by ~80%.

function reduceArray(func, arr, r) {
    for (var i = 0, len = arr.length; i < len; i++) {
            r = func.apply(null, r, arr[i]);
    }
    return r;
}

var a = 0; for (var i = 0; i < 10000000; i++) {
    a += reduceArray(Math.imul, [5,6,2,3,7,6,8,3,7,9,2,5,], 1);
}
console.log(a);

This CL also improves the runTime score of JetStream2/richards-wasm by ~45% in default, ~60% with --turbo-inline-js-wasm-calls.

Change-Id: I542eb8d3fcb592f4e0993af93ba1af70e89c3982
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2639813
Commit-Queue: Fanchen Kong <fanchen.kong@intel.com>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74413}
parent b271648e
......@@ -262,6 +262,8 @@ extern enum UpdateFeedbackMode { kOptionalFeedback, kGuaranteedFeedback }
extern operator '==' macro UpdateFeedbackModeEqual(
constexpr UpdateFeedbackMode, constexpr UpdateFeedbackMode): constexpr bool;
extern enum CallFeedbackContent extends int32 { kTarget, kReceiver }
extern enum UnicodeEncoding { UTF16, UTF32 }
// Promise constants
......@@ -961,6 +963,8 @@ extern operator '|' macro ConstexprWord32Or(
constexpr int32, constexpr int32): constexpr int32;
extern operator '^' macro Word32Xor(int32, int32): int32;
extern operator '^' macro Word32Xor(uint32, uint32): uint32;
extern operator '<<' macro ConstexprWord32Shl(
constexpr uint32, constexpr int32): uint32;
extern operator '==' macro Word64Equal(int64, int64): bool;
extern operator '==' macro Word64Equal(uint64, uint64): bool;
......@@ -1296,6 +1300,9 @@ macro GetFastAliasedArgumentsMap(implicit context: Context)(): Map {
macro GetWeakCellMap(implicit context: Context)(): Map {
return %GetClassMapConstant<WeakCell>();
}
macro GetPrototypeApplyFunction(implicit context: Context)(): JSFunction {
return *NativeContextSlot(ContextSlot::FUNCTION_PROTOTYPE_APPLY_INDEX);
}
// Call(Context, Target, Receiver, ...Args)
// TODO(joshualitt): Assuming the context parameter is for throwing when Target
......
......@@ -71,7 +71,8 @@ TF_BUILTIN(Call_ReceiverIsNullOrUndefined_Baseline,
auto context = LoadContextFromBaseline();
auto feedback_vector = LoadFeedbackVectorFromBaseline();
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
auto receiver = UndefinedConstant();
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
TailCallBuiltin(Builtins::kCall_ReceiverIsNullOrUndefined, context, target,
argc);
}
......@@ -83,7 +84,9 @@ TF_BUILTIN(Call_ReceiverIsNotNullOrUndefined_Baseline,
auto context = LoadContextFromBaseline();
auto feedback_vector = LoadFeedbackVectorFromBaseline();
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
CodeStubArguments args(this, argc);
auto receiver = args.GetReceiver();
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
TailCallBuiltin(Builtins::kCall_ReceiverIsNotNullOrUndefined, context, target,
argc);
}
......@@ -94,7 +97,9 @@ TF_BUILTIN(Call_ReceiverIsAny_Baseline, CallOrConstructBuiltinsAssembler) {
auto context = LoadContextFromBaseline();
auto feedback_vector = LoadFeedbackVectorFromBaseline();
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
CodeStubArguments args(this, argc);
auto receiver = args.GetReceiver();
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
TailCallBuiltin(Builtins::kCall_ReceiverIsAny, context, target, argc);
}
......@@ -105,7 +110,8 @@ TF_BUILTIN(Call_ReceiverIsNullOrUndefined_WithFeedback,
auto context = Parameter<Context>(Descriptor::kContext);
auto feedback_vector = Parameter<FeedbackVector>(Descriptor::kFeedbackVector);
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
auto receiver = Parameter<Object>(Descriptor::kReceiver);
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
TailCallBuiltin(Builtins::kCall_ReceiverIsNullOrUndefined, context, target,
argc);
}
......@@ -117,7 +123,8 @@ TF_BUILTIN(Call_ReceiverIsNotNullOrUndefined_WithFeedback,
auto context = Parameter<Context>(Descriptor::kContext);
auto feedback_vector = Parameter<FeedbackVector>(Descriptor::kFeedbackVector);
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
auto receiver = Parameter<Object>(Descriptor::kReceiver);
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
TailCallBuiltin(Builtins::kCall_ReceiverIsNotNullOrUndefined, context, target,
argc);
}
......@@ -128,7 +135,8 @@ TF_BUILTIN(Call_ReceiverIsAny_WithFeedback, CallOrConstructBuiltinsAssembler) {
auto context = Parameter<Context>(Descriptor::kContext);
auto feedback_vector = Parameter<FeedbackVector>(Descriptor::kFeedbackVector);
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
auto receiver = Parameter<Object>(Descriptor::kReceiver);
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
TailCallBuiltin(Builtins::kCall_ReceiverIsAny, context, target, argc);
}
......@@ -464,7 +472,8 @@ TF_BUILTIN(CallWithArrayLike_WithFeedback, CallOrConstructBuiltinsAssembler) {
auto context = Parameter<Context>(Descriptor::kContext);
auto feedback_vector = Parameter<FeedbackVector>(Descriptor::kFeedbackVector);
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
auto receiver = Parameter<Object>(Descriptor::kReceiver);
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
CallOrConstructWithArrayLike(target, new_target, arguments_list, context);
}
......@@ -485,7 +494,9 @@ TF_BUILTIN(CallWithSpread_Baseline, CallOrConstructBuiltinsAssembler) {
auto context = LoadContextFromBaseline();
auto feedback_vector = LoadFeedbackVectorFromBaseline();
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
CodeStubArguments args(this, args_count);
auto receiver = args.GetReceiver();
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
CallOrConstructWithSpread(target, new_target, spread, args_count, context);
}
......@@ -497,7 +508,8 @@ TF_BUILTIN(CallWithSpread_WithFeedback, CallOrConstructBuiltinsAssembler) {
auto context = Parameter<Context>(Descriptor::kContext);
auto feedback_vector = Parameter<FeedbackVector>(Descriptor::kFeedbackVector);
auto slot = UncheckedParameter<UintPtrT>(Descriptor::kSlot);
CollectCallFeedback(target, context, feedback_vector, slot);
auto receiver = Parameter<Object>(Descriptor::kReceiver);
CollectCallFeedback(target, receiver, context, feedback_vector, slot);
CallOrConstructWithSpread(target, new_target, spread, args_count, context);
}
......
......@@ -6,6 +6,10 @@ namespace ic {
namespace callable {
extern macro IncrementCallCount(FeedbackVector, uintptr): void;
const kCallFeedbackContentFieldMask: constexpr int32
generates 'FeedbackNexus::CallFeedbackContentField::kMask';
const kCallFeedbackContentFieldShift: constexpr uint32
generates 'FeedbackNexus::CallFeedbackContentField::kShift';
macro IsMonomorphic(feedback: MaybeObject, target: JSAny): bool {
return IsWeakReferenceToObject(feedback, target);
......@@ -50,8 +54,42 @@ macro TransitionToMegamorphic(implicit context: Context)(
ReportFeedbackUpdate(feedbackVector, slotId, 'Call:TransitionMegamorphic');
}
macro TaggedEqualPrototypeApplyFunction(implicit context: Context)(
target: JSAny): bool {
return TaggedEqual(target, GetPrototypeApplyFunction());
}
macro FeedbackValueIsReceiver(implicit context: Context)(
feedbackVector: FeedbackVector, slotId: uintptr): bool {
const callCount: intptr = SmiUntag(Cast<Smi>(LoadFeedbackVectorSlot(
feedbackVector, slotId, kTaggedSize)) otherwise return false);
return (callCount & IntPtrConstant(kCallFeedbackContentFieldMask)) !=
IntPtrConstant(0);
}
macro SetCallFeedbackContent(implicit context: Context)(
feedbackVector: FeedbackVector, slotId: uintptr,
callFeedbackContent: constexpr CallFeedbackContent): void {
// Load the call count field from the feecback vector.
const callCount: intptr = SmiUntag(Cast<Smi>(LoadFeedbackVectorSlot(
feedbackVector, slotId, kTaggedSize)) otherwise return );
// The second lowest bits of the call count are used to state whether the
// feedback collected is a target or a receiver. Change that bit based on the
// callFeedbackContent input.
const callFeedbackContentFieldMask: intptr =
~IntPtrConstant(kCallFeedbackContentFieldMask);
const newCount: intptr = (callCount & callFeedbackContentFieldMask) |
Convert<intptr>(Signed(
%RawConstexprCast<constexpr uint32>(callFeedbackContent)
<< kCallFeedbackContentFieldShift));
StoreFeedbackVectorSlot(
feedbackVector, slotId, SmiTag(newCount), SKIP_WRITE_BARRIER,
kTaggedSize);
ReportFeedbackUpdate(feedbackVector, slotId, 'Call:SetCallFeedbackContent');
}
macro CollectCallFeedback(
maybeTarget: JSAny, context: Context,
maybeTarget: JSAny, maybeReceiver: JSAny, context: Context,
maybeFeedbackVector: Undefined|FeedbackVector, slotId: uintptr): void {
// TODO(v8:9891): Remove this assert once all callers are ported to Torque.
// This assert ensures correctness of maybeFeedbackVector's type which can
......@@ -70,6 +108,22 @@ macro CollectCallFeedback(
if (IsMegamorphic(feedback)) return;
if (IsUninitialized(feedback)) goto TryInitializeAsMonomorphic;
if (FeedbackValueIsReceiver(feedbackVector, slotId) &&
TaggedEqualPrototypeApplyFunction(maybeTarget)) {
// If the Receiver is recorded and the target is Function.prototype.apply,
// check whether we can stay monomorphic based on the receiver.
if (IsMonomorphic(feedback, maybeReceiver)) {
return;
} else {
// If not, reinitialize the feedback with target.
SetCallFeedbackContent(
feedbackVector, slotId, CallFeedbackContent::kTarget);
TryInitializeAsMonomorphic(maybeTarget, feedbackVector, slotId)
otherwise TransitionToMegamorphic;
return;
}
}
// If cleared, we have a new chance to become monomorphic.
const feedbackValue: HeapObject =
MaybeObjectToStrong(feedback) otherwise TryInitializeAsMonomorphic;
......@@ -93,7 +147,15 @@ macro CollectCallFeedback(
StoreWeakReferenceInFeedbackVector(feedbackVector, slotId, feedbackCell);
ReportFeedbackUpdate(feedbackVector, slotId, 'Call:FeedbackVectorCell');
} label TryInitializeAsMonomorphic {
TryInitializeAsMonomorphic(maybeTarget, feedbackVector, slotId)
let recordedFunction = maybeTarget;
if (TaggedEqualPrototypeApplyFunction(maybeTarget)) {
recordedFunction = maybeReceiver;
SetCallFeedbackContent(
feedbackVector, slotId, CallFeedbackContent::kReceiver);
} else {
assert(!FeedbackValueIsReceiver(feedbackVector, slotId));
}
TryInitializeAsMonomorphic(recordedFunction, feedbackVector, slotId)
otherwise TransitionToMegamorphic;
} label TransitionToMegamorphic {
TransitionToMegamorphic(feedbackVector, slotId);
......
......@@ -8,10 +8,10 @@ namespace ic {
@export
macro CollectCallFeedback(
maybeTarget: JSAny, context: Context,
maybeTarget: JSAny, maybeReceiver: JSAny, context: Context,
maybeFeedbackVector: Undefined|FeedbackVector, slotId: uintptr): void {
callable::CollectCallFeedback(
maybeTarget, context, maybeFeedbackVector, slotId);
maybeTarget, maybeReceiver, context, maybeFeedbackVector, slotId);
}
@export
......@@ -51,10 +51,15 @@ macro IsUninitialized(feedback: MaybeObject): bool {
}
extern macro LoadFeedbackVectorSlot(FeedbackVector, uintptr): MaybeObject;
extern macro LoadFeedbackVectorSlot(
FeedbackVector, uintptr, constexpr int32): MaybeObject;
extern operator '[]' macro LoadFeedbackVectorSlot(
FeedbackVector, intptr): MaybeObject;
extern macro StoreFeedbackVectorSlot(
FeedbackVector, uintptr, MaybeObject): void;
extern macro StoreFeedbackVectorSlot(
FeedbackVector, uintptr, MaybeObject, constexpr WriteBarrierMode,
constexpr int32): void;
extern macro StoreWeakReferenceInFeedbackVector(
FeedbackVector, uintptr, HeapObject): MaybeObject;
extern macro ReportFeedbackUpdate(FeedbackVector, uintptr, constexpr string);
......
......@@ -102,7 +102,8 @@ transitioning builtin CallIteratorWithFeedback(
feedback: Undefined|FeedbackVector): JSAny {
// TODO(v8:10047): Use TaggedIndex here once TurboFan supports it.
const callSlotUnTagged: uintptr = Unsigned(SmiUntag(callSlot));
ic::CollectCallFeedback(iteratorMethod, context, feedback, callSlotUnTagged);
ic::CollectCallFeedback(
iteratorMethod, receiver, context, feedback, callSlotUnTagged);
const iteratorCallable: Callable = Cast<Callable>(iteratorMethod)
otherwise ThrowCalledNonCallable(iteratorMethod);
return Call(context, iteratorCallable, receiver);
......
......@@ -3630,6 +3630,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
}
int32_t ConstexprWord32Or(int32_t a, int32_t b) { return a | b; }
uint32_t ConstexprWord32Shl(uint32_t a, int32_t b) { return a << b; }
bool ConstexprUintPtrLessThan(uintptr_t a, uintptr_t b) { return a < b; }
......
......@@ -1176,12 +1176,13 @@ class CallWithSpread_WithFeedbackDescriptor
CallWithSpread_WithFeedbackDescriptor> {
public:
DEFINE_PARAMETERS_VARARGS(kTarget, kArgumentsCount, kSpread, kSlot,
kFeedbackVector)
kFeedbackVector, kReceiver)
DEFINE_PARAMETER_TYPES(MachineType::AnyTagged(), // kTarget
MachineType::Int32(), // kArgumentsCount
MachineType::AnyTagged(), // kSpread
MachineType::UintPtr(), // kSlot
MachineType::AnyTagged()) // kFeedbackVector
MachineType::AnyTagged(), // kFeedbackVector
MachineType::AnyTagged()) // kReceiver
DECLARE_DESCRIPTOR(CallWithSpread_WithFeedbackDescriptor)
};
......@@ -1200,11 +1201,12 @@ class CallWithArrayLike_WithFeedbackDescriptor
: public StaticCallInterfaceDescriptor<
CallWithArrayLike_WithFeedbackDescriptor> {
public:
DEFINE_PARAMETERS(kTarget, kArgumentsList, kSlot, kFeedbackVector)
DEFINE_PARAMETERS(kTarget, kArgumentsList, kSlot, kFeedbackVector, kReceiver)
DEFINE_PARAMETER_TYPES(MachineType::AnyTagged(), // kTarget
MachineType::AnyTagged(), // kArgumentsList
MachineType::UintPtr(), // kSlot
MachineType::AnyTagged()) // kFeedbackVector
MachineType::AnyTagged(), // kFeedbackVector
MachineType::AnyTagged()) // kReceiver
DECLARE_DESCRIPTOR(CallWithArrayLike_WithFeedbackDescriptor)
};
......@@ -1854,11 +1856,12 @@ class CallTrampoline_WithFeedbackDescriptor
CallTrampoline_WithFeedbackDescriptor> {
public:
DEFINE_PARAMETERS_VARARGS(kFunction, kActualArgumentsCount, kSlot,
kFeedbackVector)
kFeedbackVector, kReceiver)
DEFINE_PARAMETER_TYPES(MachineType::AnyTagged(), // kFunction
MachineType::Int32(), // kActualArgumentsCount
MachineType::UintPtr(), // kSlot
MachineType::AnyTagged()) // kFeedbackVector
MachineType::AnyTagged(), // kFeedbackVector
MachineType::AnyTagged()) // kReceiver
DECLARE_DESCRIPTOR(CallTrampoline_WithFeedbackDescriptor)
};
......
......@@ -1622,6 +1622,7 @@ inline std::ostream& operator<<(std::ostream& os,
}
enum class SpeculationMode { kAllowSpeculation, kDisallowSpeculation };
enum class CallFeedbackContent { kTarget, kReceiver };
inline std::ostream& operator<<(std::ostream& os,
SpeculationMode speculation_mode) {
......
......@@ -347,6 +347,10 @@ class BytecodeGraphBuilder {
// feedback. Returns kDisallowSpeculation if feedback is insufficient.
SpeculationMode GetSpeculationMode(int slot_id) const;
// Helper function to determine the call feedback relation from the recorded
// type feedback. Returns kUnrelated if feedback is insufficient.
CallFeedbackRelation ComputeCallFeedbackRelation(int slot_id) const;
// Helpers for building the implicit FunctionEntry and IterationBody
// StackChecks.
void BuildFunctionEntryStackCheck();
......@@ -2529,9 +2533,11 @@ void BytecodeGraphBuilder::BuildCall(ConvertReceiverMode receiver_mode,
FeedbackSource feedback = CreateFeedbackSource(slot_id);
CallFrequency frequency = ComputeCallFrequency(slot_id);
SpeculationMode speculation_mode = GetSpeculationMode(slot_id);
CallFeedbackRelation call_feedback_relation =
ComputeCallFeedbackRelation(slot_id);
const Operator* op =
javascript()->Call(arg_count, frequency, feedback, receiver_mode,
speculation_mode, CallFeedbackRelation::kRelated);
speculation_mode, call_feedback_relation);
DCHECK(IrOpcode::IsFeedbackCollectingOpcode(op->opcode()));
JSTypeHintLowering::LoweringResult lowering = TryBuildSimplifiedCall(
......@@ -3109,6 +3115,19 @@ SpeculationMode BytecodeGraphBuilder::GetSpeculationMode(int slot_id) const {
: feedback.AsCall().speculation_mode();
}
CallFeedbackRelation BytecodeGraphBuilder::ComputeCallFeedbackRelation(
int slot_id) const {
FeedbackSlot slot = FeedbackVector::ToSlot(slot_id);
FeedbackSource source(feedback_vector(), slot);
ProcessedFeedback const& feedback = broker()->GetFeedbackForCall(source);
if (feedback.IsInsufficient()) return CallFeedbackRelation::kUnrelated;
CallFeedbackContent call_feedback_content =
feedback.AsCall().call_feedback_content();
return call_feedback_content == CallFeedbackContent::kTarget
? CallFeedbackRelation::kTarget
: CallFeedbackRelation::kReceiver;
}
void BytecodeGraphBuilder::VisitBitwiseNot() {
FeedbackSource feedback = CreateFeedbackSource(
bytecode_iterator().GetSlotOperand(kUnaryOperationHintIndex));
......
......@@ -988,6 +988,11 @@ class V8_EXPORT_PRIVATE CodeAssembler {
static_cast<TNode<Word32T>>(right)));
}
TNode<IntPtrT> WordOr(TNode<IntPtrT> left, TNode<IntPtrT> right) {
return Signed(WordOr(static_cast<TNode<WordT>>(left),
static_cast<TNode<WordT>>(right)));
}
TNode<Int32T> Word32Or(TNode<Int32T> left, TNode<Int32T> right) {
return Signed(Word32Or(static_cast<TNode<Word32T>>(left),
static_cast<TNode<Word32T>>(right)));
......@@ -1005,6 +1010,9 @@ class V8_EXPORT_PRIVATE CodeAssembler {
TNode<BoolT> Word64Equal(TNode<Word64T> left, TNode<Word64T> right);
TNode<BoolT> Word64NotEqual(TNode<Word64T> left, TNode<Word64T> right);
TNode<IntPtrT> WordNot(TNode<IntPtrT> a) {
return Signed(WordNot(static_cast<TNode<WordT>>(a)));
}
TNode<BoolT> Word32Or(TNode<BoolT> left, TNode<BoolT> right) {
return UncheckedCast<BoolT>(Word32Or(static_cast<TNode<Word32T>>(left),
static_cast<TNode<Word32T>>(right)));
......
......@@ -49,17 +49,24 @@ inline size_t hash_value(StackCheckKind kind) {
return static_cast<size_t>(kind);
}
// The CallFeedbackRelation states whether the target feedback stored with a
// JSCall is related to the call. If, during lowering, a JSCall (e.g. of a
// higher order function) is replaced by a JSCall with another target, the
// feedback has to be kept but is now unrelated.
enum class CallFeedbackRelation { kRelated, kUnrelated };
// The CallFeedbackRelation provides the meaning of the call feedback for a
// TurboFan JSCall operator
// - kReceiver: The call target was Function.prototype.apply and its receiver
// was recorded as the feedback value.
// - kTarget: The call target was recorded as the feedback value.
// - kUnrelated: The feedback is no longer related to the call. If, during
// lowering, a JSCall (e.g. of a higher order function) is replaced by a
// JSCall with another target, the feedback has to be kept but is now
// unrelated.
enum class CallFeedbackRelation { kReceiver, kTarget, kUnrelated };
inline std::ostream& operator<<(std::ostream& os,
CallFeedbackRelation call_feedback_relation) {
switch (call_feedback_relation) {
case CallFeedbackRelation::kRelated:
return os << "CallFeedbackRelation::kRelated";
case CallFeedbackRelation::kReceiver:
return os << "CallFeedbackRelation::kReceiver";
case CallFeedbackRelation::kTarget:
return os << "CallFeedbackRelation::kTarget";
case CallFeedbackRelation::kUnrelated:
return os << "CallFeedbackRelation::kUnrelated";
}
......
......@@ -467,6 +467,7 @@ class ContextRef : public HeapObjectRef {
#define BROKER_COMPULSORY_NATIVE_CONTEXT_FIELDS(V) \
V(JSFunction, array_function) \
V(JSFunction, function_prototype_apply) \
V(JSFunction, boolean_function) \
V(JSFunction, bigint_function) \
V(JSFunction, number_function) \
......
......@@ -2448,6 +2448,10 @@ Reduction JSCallReducer::ReduceObjectConstructor(Node* node) {
Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
CallFeedbackRelation new_feedback_relation =
p.feedback_relation() == CallFeedbackRelation::kReceiver
? CallFeedbackRelation::kTarget
: CallFeedbackRelation::kUnrelated;
int arity = p.arity_without_implicit_args();
ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny;
if (arity == 0) {
......@@ -2480,9 +2484,9 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
// Morph the {node} to a {JSCallWithArrayLike}.
NodeProperties::ChangeOp(
node, javascript()->CallWithArrayLike(
p.frequency(), p.feedback(), p.speculation_mode(),
CallFeedbackRelation::kUnrelated));
node, javascript()->CallWithArrayLike(p.frequency(), p.feedback(),
p.speculation_mode(),
new_feedback_relation));
return Changed(node).FollowedBy(ReduceJSCallWithArrayLike(node));
} else {
// Check whether {arguments_list} is null.
......@@ -2510,7 +2514,7 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
Node* value0 = effect0 = control0 = graph()->NewNode(
javascript()->CallWithArrayLike(p.frequency(), p.feedback(),
p.speculation_mode(),
CallFeedbackRelation::kUnrelated),
new_feedback_relation),
target, this_argument, arguments_list, n.feedback_vector(), context,
frame_state, effect0, control0);
......@@ -2560,7 +2564,7 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
NodeProperties::ChangeOp(
node, javascript()->Call(JSCallNode::ArityForArgc(arity), p.frequency(),
p.feedback(), convert_mode, p.speculation_mode(),
CallFeedbackRelation::kUnrelated));
new_feedback_relation));
// Try to further reduce the JSCall {node}.
return Changed(node).FollowedBy(ReduceJSCall(node));
}
......@@ -3966,10 +3970,9 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
if (IsCallWithArrayLikeOrSpread(node)) {
NodeProperties::ChangeOp(
node,
javascript()->Call(JSCallNode::ArityForArgc(argc), frequency, feedback,
ConvertReceiverMode::kAny, speculation_mode,
CallFeedbackRelation::kUnrelated));
node, javascript()->Call(JSCallNode::ArityForArgc(argc), frequency,
feedback, ConvertReceiverMode::kAny,
speculation_mode, feedback_relation));
return Changed(node).FollowedBy(ReduceJSCall(node));
} else {
NodeProperties::ChangeOp(
......@@ -4201,7 +4204,7 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
}
if (!ShouldUseCallICFeedback(target) ||
p.feedback_relation() != CallFeedbackRelation::kRelated ||
p.feedback_relation() == CallFeedbackRelation::kUnrelated ||
!p.feedback().IsValid()) {
return NoChange();
}
......@@ -4213,7 +4216,14 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
node, DeoptimizeReason::kInsufficientTypeFeedbackForCall);
}
base::Optional<HeapObjectRef> feedback_target = feedback.AsCall().target();
base::Optional<HeapObjectRef> feedback_target;
if (p.feedback_relation() == CallFeedbackRelation::kTarget) {
feedback_target = feedback.AsCall().target();
} else {
DCHECK_EQ(p.feedback_relation(), CallFeedbackRelation::kReceiver);
feedback_target = native_context().function_prototype_apply();
}
if (feedback_target.has_value() && feedback_target->map().is_callable()) {
Node* target_function = jsgraph()->Constant(*feedback_target);
......@@ -5074,7 +5084,7 @@ Reduction JSCallReducer::ReduceJSConstructWithArrayLike(Node* node) {
DCHECK_EQ(n.ArgumentCount(), 1); // The arraylike object.
return ReduceCallOrConstructWithArrayLikeOrSpread(
node, arraylike_index, p.frequency(), p.feedback(),
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kRelated);
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget);
}
Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) {
......@@ -5084,7 +5094,7 @@ Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) {
DCHECK_GE(n.ArgumentCount(), 1); // At least the spread.
return ReduceCallOrConstructWithArrayLikeOrSpread(
node, spread_index, p.frequency(), p.feedback(),
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kRelated);
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget);
}
Reduction JSCallReducer::ReduceReturnReceiver(Node* node) {
......
......@@ -828,7 +828,9 @@ ProcessedFeedback const& JSHeapBroker::ReadFeedbackForCall(
}
float frequency = nexus.ComputeCallFrequency();
SpeculationMode mode = nexus.GetSpeculationMode();
return *zone()->New<CallFeedback>(target_ref, frequency, mode, nexus.kind());
CallFeedbackContent content = nexus.GetCallFeedbackContent();
return *zone()->New<CallFeedback>(target_ref, frequency, mode, content,
nexus.kind());
}
BinaryOperationHint JSHeapBroker::GetFeedbackForBinaryOperation(
......
......@@ -1585,7 +1585,7 @@ Reduction JSNativeContextSpecialization::ReduceJSGetIterator(Node* node) {
const Operator* call_op = javascript()->Call(
JSCallNode::ArityForArgc(0), CallFrequency(), p.callFeedback(),
ConvertReceiverMode::kNotNullOrUndefined, mode,
CallFeedbackRelation::kRelated);
CallFeedbackRelation::kTarget);
Node* call_property =
graph()->NewNode(call_op, load_property, receiver, n.feedback_vector(),
context, frame_state, effect, control);
......
......@@ -290,9 +290,9 @@ class CallParameters final {
}
using ArityField = base::BitField<size_t, 0, 27>;
using CallFeedbackRelationField = base::BitField<CallFeedbackRelation, 27, 1>;
using SpeculationModeField = base::BitField<SpeculationMode, 28, 1>;
using ConvertReceiverModeField = base::BitField<ConvertReceiverMode, 29, 2>;
using CallFeedbackRelationField = base::BitField<CallFeedbackRelation, 27, 2>;
using SpeculationModeField = base::BitField<SpeculationMode, 29, 1>;
using ConvertReceiverModeField = base::BitField<ConvertReceiverMode, 30, 2>;
uint32_t const bit_field_;
CallFrequency const frequency_;
......@@ -951,12 +951,12 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
CallFrequency const& frequency,
const FeedbackSource& feedback = FeedbackSource{},
SpeculationMode speculation_mode = SpeculationMode::kDisallowSpeculation,
CallFeedbackRelation feedback_relation = CallFeedbackRelation::kRelated);
CallFeedbackRelation feedback_relation = CallFeedbackRelation::kTarget);
const Operator* CallWithSpread(
uint32_t arity, CallFrequency const& frequency = CallFrequency(),
FeedbackSource const& feedback = FeedbackSource(),
SpeculationMode speculation_mode = SpeculationMode::kDisallowSpeculation,
CallFeedbackRelation feedback_relation = CallFeedbackRelation::kRelated);
CallFeedbackRelation feedback_relation = CallFeedbackRelation::kTarget);
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);
......
......@@ -197,20 +197,24 @@ class MinimorphicLoadPropertyAccessFeedback : public ProcessedFeedback {
class CallFeedback : public ProcessedFeedback {
public:
CallFeedback(base::Optional<HeapObjectRef> target, float frequency,
SpeculationMode mode, FeedbackSlotKind slot_kind)
SpeculationMode mode, CallFeedbackContent call_feedback_content,
FeedbackSlotKind slot_kind)
: ProcessedFeedback(kCall, slot_kind),
target_(target),
frequency_(frequency),
mode_(mode) {}
mode_(mode),
content_(call_feedback_content) {}
base::Optional<HeapObjectRef> target() const { return target_; }
float frequency() const { return frequency_; }
SpeculationMode speculation_mode() const { return mode_; }
CallFeedbackContent call_feedback_content() const { return content_; }
private:
base::Optional<HeapObjectRef> const target_;
float const frequency_;
SpeculationMode const mode_;
CallFeedbackContent const content_;
};
template <class T, ProcessedFeedback::Kind K>
......
......@@ -1648,8 +1648,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
// Setup the methods on the %FunctionPrototype%.
JSObject::AddProperty(isolate_, prototype, factory->constructor_string(),
function_fun, DONT_ENUM);
Handle<JSFunction> function_prototype_apply =
SimpleInstallFunction(isolate_, prototype, "apply",
Builtins::kFunctionPrototypeApply, 2, false);
native_context()->set_function_prototype_apply(*function_prototype_apply);
SimpleInstallFunction(isolate_, prototype, "bind",
Builtins::kFastFunctionPrototypeBind, 1, false);
SimpleInstallFunction(isolate_, prototype, "call",
......
......@@ -803,7 +803,9 @@ void InterpreterAssembler::CallJSWithSpreadAndDispatch(
TNode<UintPtrT> slot_id, TNode<HeapObject> maybe_feedback_vector) {
DCHECK(Bytecodes::MakesCallAlongCriticalPath(bytecode_));
DCHECK_EQ(Bytecodes::GetReceiverMode(bytecode_), ConvertReceiverMode::kAny);
CollectCallFeedback(function, context, maybe_feedback_vector, slot_id);
TNode<Object> receiver = LoadRegisterAtOperandIndex(1);
CollectCallFeedback(function, receiver, context, maybe_feedback_vector,
slot_id);
Comment("call using CallWithSpread builtin");
Callable callable = CodeFactory::InterpreterPushArgsThenCall(
isolate(), ConvertReceiverMode::kAny,
......
......@@ -1365,13 +1365,18 @@ class InterpreterJSCallAssembler : public InterpreterAssembler {
// Generates code to perform a JS call that collects type feedback.
void JSCall(ConvertReceiverMode receiver_mode) {
TNode<Object> function = LoadRegisterAtOperandIndex(0);
TNode<Object> receiver =
receiver_mode == ConvertReceiverMode::kNullOrUndefined
? UndefinedConstant()
: LoadRegisterAtOperandIndex(1);
RegListNodePair args = GetRegisterListAtOperandIndex(1);
TNode<UintPtrT> slot_id = BytecodeOperandIdx(3);
TNode<HeapObject> maybe_feedback_vector = LoadFeedbackVector();
TNode<Context> context = GetContext();
// Collect the {function} feedback.
CollectCallFeedback(function, context, maybe_feedback_vector, slot_id);
CollectCallFeedback(function, receiver, context, maybe_feedback_vector,
slot_id);
// Call the function and dispatch to the next handler.
CallJSAndDispatch(function, context, args, receiver_mode);
......@@ -1399,12 +1404,17 @@ class InterpreterJSCallAssembler : public InterpreterAssembler {
kFirstArgumentOperandIndex + kReceiverAndArgOperandCount;
TNode<Object> function = LoadRegisterAtOperandIndex(0);
TNode<Object> receiver =
receiver_mode == ConvertReceiverMode::kNullOrUndefined
? UndefinedConstant()
: LoadRegisterAtOperandIndex(1);
TNode<UintPtrT> slot_id = BytecodeOperandIdx(kSlotOperandIndex);
TNode<HeapObject> maybe_feedback_vector = LoadFeedbackVector();
TNode<Context> context = GetContext();
// Collect the {function} feedback.
CollectCallFeedback(function, context, maybe_feedback_vector, slot_id);
CollectCallFeedback(function, receiver, context, maybe_feedback_vector,
slot_id);
switch (kReceiverAndArgOperandCount) {
case 0:
......
......@@ -50,7 +50,8 @@ enum ContextLookupFlags {
V(MATH_POW_INDEX, JSFunction, math_pow) \
V(PROMISE_INTERNAL_CONSTRUCTOR_INDEX, JSFunction, \
promise_internal_constructor) \
V(PROMISE_THEN_INDEX, JSFunction, promise_then)
V(PROMISE_THEN_INDEX, JSFunction, promise_then) \
V(FUNCTION_PROTOTYPE_APPLY_INDEX, JSFunction, function_prototype_apply)
#define NATIVE_CONTEXT_FIELDS(V) \
V(GLOBAL_PROXY_INDEX, JSGlobalProxy, global_proxy_object) \
......
......@@ -118,6 +118,7 @@ extern enum ContextSlot extends intptr constexpr 'Context::Field' {
SLOPPY_ARGUMENTS_MAP_INDEX: Slot<NativeContext, Map>,
FAST_ALIASED_ARGUMENTS_MAP_INDEX: Slot<NativeContext, Map>,
FUNCTION_CONTEXT_MAP_INDEX: Slot<NativeContext, Map>,
FUNCTION_PROTOTYPE_APPLY_INDEX: Slot<NativeContext, JSFunction>,
PROMISE_FUNCTION_INDEX: Slot<NativeContext, JSFunction>,
PROMISE_THEN_INDEX: Slot<NativeContext, JSFunction>,
......
......@@ -1009,6 +1009,15 @@ SpeculationMode FeedbackNexus::GetSpeculationMode() {
return SpeculationModeField::decode(value);
}
CallFeedbackContent FeedbackNexus::GetCallFeedbackContent() {
DCHECK(IsCallICKind(kind()));
Object call_count = GetFeedbackExtra()->cast<Object>();
CHECK(call_count.IsSmi());
uint32_t value = static_cast<uint32_t>(Smi::ToInt(call_count));
return CallFeedbackContentField::decode(value);
}
float FeedbackNexus::ComputeCallFrequency() {
DCHECK(IsCallICKind(kind()));
......
......@@ -795,13 +795,15 @@ class V8_EXPORT_PRIVATE FeedbackNexus final {
int GetCallCount();
void SetSpeculationMode(SpeculationMode mode);
SpeculationMode GetSpeculationMode();
CallFeedbackContent GetCallFeedbackContent();
// Compute the call frequency based on the call count and the invocation
// count (taken from the type feedback vector).
float ComputeCallFrequency();
using SpeculationModeField = base::BitField<SpeculationMode, 0, 1>;
using CallCountField = base::BitField<uint32_t, 1, 31>;
using CallFeedbackContentField = base::BitField<CallFeedbackContent, 1, 1>;
using CallCountField = base::BitField<uint32_t, 2, 30>;
// For InstanceOf ICs.
MaybeHandle<JSObject> GetConstructorFeedback() const;
......
......@@ -184,6 +184,49 @@ TEST(VectorCallICStates) {
CHECK_EQ(GENERIC, nexus.ic_state());
}
// Test the Call IC states transfer with Function.prototype.apply
TEST(VectorCallICStateApply) {
if (!i::FLAG_use_ic) return;
if (i::FLAG_always_opt) return;
FLAG_allow_natives_syntax = true;
CcTest::InitializeVM();
LocalContext context;
v8::HandleScope scope(context->GetIsolate());
Isolate* isolate = CcTest::i_isolate();
// Make sure function f has a call that uses a type feedback slot.
CompileRun(
"var F;"
"%EnsureFeedbackVectorForFunction(foo);"
"function foo() { return F.apply(null, arguments); }"
"F = Math.min;"
"foo();");
Handle<JSFunction> foo = GetFunction("foo");
Handle<JSFunction> F = GetFunction("F");
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(foo->feedback_vector(), isolate);
FeedbackSlot slot(4);
FeedbackNexus nexus(feedback_vector, slot);
CHECK_EQ(MONOMORPHIC, nexus.ic_state());
CHECK_EQ(CallFeedbackContent::kReceiver, nexus.GetCallFeedbackContent());
HeapObject heap_object;
CHECK(nexus.GetFeedback()->GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(*F, heap_object);
CompileRun(
"F = Math.max;"
"foo();");
CHECK_EQ(MONOMORPHIC, nexus.ic_state());
CHECK_EQ(CallFeedbackContent::kTarget, nexus.GetCallFeedbackContent());
CHECK(nexus.GetFeedback()->GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(*isolate->function_prototype_apply(), heap_object);
CompileRun(
"F.apply = (function () { return; });"
"foo();");
CHECK_EQ(GENERIC, nexus.ic_state());
}
TEST(VectorCallFeedback) {
if (!i::FLAG_use_ic) return;
if (i::FLAG_always_opt) return;
......
......@@ -28,6 +28,51 @@ assertOptimized(TestFunctionPrototypeApply);
TestFunctionPrototypeApply("abc");
assertUnoptimized(TestFunctionPrototypeApply);
// Testing: FunctionPrototypeApply with non-HeapConstant Receiver
var MathMin = (function() { return Math.min.apply(null, arguments); })
function TestFunctionPrototypeApplyReceiver(func, x, y) {
return func(x, y);
}
%PrepareFunctionForOptimization(MathMin);
%PrepareFunctionForOptimization(TestFunctionPrototypeApplyReceiver);
assertEquals(-13, TestFunctionPrototypeApplyReceiver(MathMin, -13, 42));
assertEquals(-4, TestFunctionPrototypeApplyReceiver(MathMin, 3, -4));
%OptimizeFunctionOnNextCall(TestFunctionPrototypeApplyReceiver);
assertEquals(7, TestFunctionPrototypeApplyReceiver(MathMin, 7, 9));
assertOptimized(TestFunctionPrototypeApplyReceiver);
TestFunctionPrototypeApplyReceiver(MathMin, "abc");
assertUnoptimized(TestFunctionPrototypeApplyReceiver);
// Testing: FunctionPrototypeApply with non-HeapConstant Receiver won't cause
// deopt loop
(function() {
var F;
function foo() {
return F.apply(null, arguments);
}
function test(x, y) {
return foo(x, y);
}
F = Math.min;
%PrepareFunctionForOptimization(foo);
%PrepareFunctionForOptimization(test);
assertEquals(-13, test(-13, 42));
%OptimizeFunctionOnNextCall(test);
assertEquals(-13, test(-13, 42));
assertOptimized(test);
%PrepareFunctionForOptimization(test);
F = Math.max;
assertEquals(42, test(-13, 42));
assertUnoptimized(test);
%OptimizeFunctionOnNextCall(test);
assertEquals(42, test(-13, 42));
F = Math.min;
assertEquals(-13, test(-13, 42));
assertOptimized(test);
})();
// Testing: FunctionPrototypeCall
function TestFunctionPrototypeCall(x) {
......
......@@ -117,7 +117,7 @@ class JSCallReducerTest : public TypedGraphTest {
return javascript()->Call(JSCallNode::ArityForArgc(arity), CallFrequency(),
feedback, ConvertReceiverMode::kAny,
SpeculationMode::kAllowSpeculation,
CallFeedbackRelation::kRelated);
CallFeedbackRelation::kTarget);
}
private:
......
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