[maglev] Support call speculation disabling

Add a FeedbackSource to DeoptInfo which allows the caller to specify
that this deopt is part of call speculation, and that call speculation
should be disabled for this call when the speculation fails. This is a
mechanism to prevent deopt loops, also used by TurboFan.

Bug: v8:7700
Change-Id: I59b5db3956e074ec808b218c00ae85796455742e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4030438
Reviewed-by: Victor Gomes <victorgomes@chromium.org>
Auto-Submit: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84332}
This commit is contained in:
Leszek Swirski 2022-11-17 15:31:18 +01:00 committed by V8 LUCI CQ
parent 1f26a28f0e
commit 00db0fff8c
5 changed files with 99 additions and 47 deletions

View File

@ -1075,6 +1075,11 @@ class MaglevTranslationArrayBuilder {
deopt_info->set_translation_index(
translation_array_builder_->BeginTranslation(frame_count, jsframe_count,
update_feedback_count));
if (deopt_info->feedback_to_update().IsValid()) {
translation_array_builder_->AddUpdateFeedback(
GetDeoptLiteral(*deopt_info->feedback_to_update().vector),
deopt_info->feedback_to_update().index());
}
const InputLocation* current_input_location = deopt_info->input_locations();
BuildDeoptFrame(deopt_info->top_frame(), current_input_location);

View File

@ -183,6 +183,25 @@ class CallArguments {
Mode mode_;
};
class MaglevGraphBuilder::CallSpeculationScope {
public:
CallSpeculationScope(MaglevGraphBuilder* builder,
compiler::FeedbackSource feedback_source)
: builder_(builder) {
DCHECK(!builder_->current_speculation_feedback_.IsValid());
DCHECK_EQ(
FeedbackNexus(feedback_source.vector, feedback_source.slot).kind(),
FeedbackSlotKind::kCall);
builder_->current_speculation_feedback_ = feedback_source;
}
~CallSpeculationScope() {
builder_->current_speculation_feedback_ = compiler::FeedbackSource();
}
private:
MaglevGraphBuilder* builder_;
};
MaglevGraphBuilder::MaglevGraphBuilder(LocalIsolate* local_isolate,
MaglevCompilationUnit* compilation_unit,
Graph* graph, MaglevGraphBuilder* parent)
@ -2836,26 +2855,12 @@ ValueNode* MaglevGraphBuilder::TryBuildInlinedCall(
ValueNode* MaglevGraphBuilder::TryReduceStringFromCharCode(
compiler::JSFunctionRef target, CallArguments& args) {
if (args.count() != 1) return nullptr;
ValueNode* arg = args[0];
// Bail out of the inlining if the known type is not Number. This prevents
// GetTruncatedInt32FromNumber from deoptimizing.
// TODO(leszeks): Add support for call feedback speculation.
if (!CheckType(arg, NodeType::kNumber)) {
return nullptr;
}
return AddNewNode<BuiltinStringFromCharCode>(
{GetTruncatedInt32FromNumber(arg)});
{GetTruncatedInt32FromNumber(args[0])});
}
ValueNode* MaglevGraphBuilder::TryReduceStringPrototypeCharCodeAt(
compiler::JSFunctionRef target, CallArguments& args) {
// Bail out of the inlining if the known type is not Number or String. This
// prevents GetInt32ElementIndex from deoptimizing.
// TODO(leszeks): Add support for call feedback speculation.
if (args.count() != 0 && !CheckType(args[0], NodeType::kNumber) &&
!CheckType(args[0], NodeType::kString)) {
return nullptr;
}
ValueNode* receiver = GetTaggedOrUndefined(args.receiver());
ValueNode* index;
if (args.count() == 0) {
@ -2975,13 +2980,20 @@ ValueNode* MaglevGraphBuilder::TryReduceFunctionPrototypeCall(
return BuildGenericCall(receiver, context, Call::TargetType::kAny, args);
}
ValueNode* MaglevGraphBuilder::TryReduceBuiltin(compiler::JSFunctionRef target,
CallArguments& args) {
ValueNode* MaglevGraphBuilder::TryReduceBuiltin(
compiler::JSFunctionRef target, CallArguments& args,
const compiler::FeedbackSource& feedback_source,
SpeculationMode speculation_mode) {
if (args.mode() != CallArguments::kDefault) {
// TODO(victorgomes): Maybe inline the spread stub? Or call known function
// directly if arguments list is an array.
return nullptr;
}
if (speculation_mode == SpeculationMode::kDisallowSpeculation) {
// TODO(leszeks): Some builtins might be inlinable without speculation.
return nullptr;
}
CallSpeculationScope speculate(this, feedback_source);
if (!target.shared().HasBuiltinId()) return nullptr;
switch (target.shared().builtin_id()) {
#define CASE(Name) \
@ -3092,8 +3104,10 @@ bool MaglevGraphBuilder::BuildCheckValue(ValueNode* node,
return true;
}
ValueNode* MaglevGraphBuilder::ReduceCall(compiler::ObjectRef object,
CallArguments& args) {
ValueNode* MaglevGraphBuilder::ReduceCall(
compiler::ObjectRef object, CallArguments& args,
const compiler::FeedbackSource& feedback_source,
SpeculationMode speculation_mode) {
if (!object.IsJSFunction()) {
return BuildGenericCall(GetConstant(object), GetContext(),
Call::TargetType::kAny, args);
@ -3108,7 +3122,8 @@ ValueNode* MaglevGraphBuilder::ReduceCall(compiler::ObjectRef object,
}
DCHECK(target.object()->IsCallable());
if (ValueNode* result = TryReduceBuiltin(target, args)) {
if (ValueNode* result =
TryReduceBuiltin(target, args, feedback_source, speculation_mode)) {
return result;
}
if (ValueNode* result = TryBuildCallKnownJSFunction(target, args)) {
@ -3120,15 +3135,17 @@ ValueNode* MaglevGraphBuilder::ReduceCall(compiler::ObjectRef object,
}
ValueNode* MaglevGraphBuilder::ReduceCallForTarget(
ValueNode* target_node, compiler::JSFunctionRef target,
CallArguments& args) {
ValueNode* target_node, compiler::JSFunctionRef target, CallArguments& args,
const compiler::FeedbackSource& feedback_source,
SpeculationMode speculation_mode) {
if (!BuildCheckValue(target_node, target)) return nullptr;
return ReduceCall(target, args);
return ReduceCall(target, args, feedback_source, speculation_mode);
}
ValueNode* MaglevGraphBuilder::ReduceFunctionPrototypeApplyCallWithReceiver(
ValueNode* target_node, compiler::JSFunctionRef receiver,
CallArguments& args) {
CallArguments& args, const compiler::FeedbackSource& feedback_source,
SpeculationMode speculation_mode) {
compiler::NativeContextRef native_context = broker()->target_native_context();
if (!BuildCheckValue(target_node,
native_context.function_prototype_apply())) {
@ -3142,12 +3159,12 @@ ValueNode* MaglevGraphBuilder::ReduceFunctionPrototypeApplyCallWithReceiver(
if (args.count() == 0) {
// No need for spread.
CallArguments empty_args(ConvertReceiverMode::kNullOrUndefined);
call = ReduceCall(receiver, empty_args);
call = ReduceCall(receiver, empty_args, feedback_source, speculation_mode);
} else if (args.count() == 1 || IsNullValue(args[1]) ||
IsUndefinedValue(args[1])) {
// No need for spread. We have only the new receiver.
CallArguments new_args(ConvertReceiverMode::kAny, {args[0]});
call = ReduceCall(receiver, new_args);
call = ReduceCall(receiver, new_args, feedback_source, speculation_mode);
} else {
// FunctionPrototypeApply only consider two arguments: the new receiver and
// an array-like arguments_list. All others shall be ignored.
@ -3159,10 +3176,11 @@ ValueNode* MaglevGraphBuilder::ReduceFunctionPrototypeApplyCallWithReceiver(
// constant and unfold the arguments.
CallArguments new_args(ConvertReceiverMode::kAny, {args[0], args[1]},
CallArguments::kWithArrayLike);
call = ReduceCall(receiver, new_args);
call = ReduceCall(receiver, new_args, feedback_source, speculation_mode);
} else {
args.Truncate(2);
call = ReduceCall(native_context.function_prototype_apply(), args);
call = ReduceCall(native_context.function_prototype_apply(), args,
feedback_source, speculation_mode);
}
}
return call;
@ -3185,13 +3203,15 @@ void MaglevGraphBuilder::BuildCall(ValueNode* target_node, CallArguments& args,
compiler::JSFunctionRef function = call_feedback.target()->AsJSFunction();
ValueNode* call;
if (content == CallFeedbackContent::kTarget) {
call = ReduceCallForTarget(target_node, function, args);
call = ReduceCallForTarget(target_node, function, args, feedback_source,
call_feedback.speculation_mode());
} else {
DCHECK_EQ(content, CallFeedbackContent::kReceiver);
// We only collect receiver feedback for FunctionPrototypeApply.
// See CollectCallFeedback in ic-callable.tq
call = ReduceFunctionPrototypeApplyCallWithReceiver(target_node, function,
args);
call = ReduceFunctionPrototypeApplyCallWithReceiver(
target_node, function, args, feedback_source,
call_feedback.speculation_mode());
}
// If {call} is null, we hit an unconditional deopt.
if (!call) return;
@ -3785,7 +3805,8 @@ bool MaglevGraphBuilder::TryBuildFastInstanceOf(
BuiltinContinuationDeoptFrame(
Builtin::kToBooleanLazyDeoptContinuation, {}, GetContext(),
zone()->New<InterpretedDeoptFrame>(
call->lazy_deopt_info()->top_frame().as_interpreted())));
call->lazy_deopt_info()->top_frame().as_interpreted())),
call->lazy_deopt_info()->feedback_to_update());
}
// TODO(v8:7700): Do we need to call ToBoolean here? If we have reduce the

View File

@ -102,6 +102,8 @@ class MaglevGraphBuilder {
Graph* graph() const { return graph_; }
private:
class CallSpeculationScope;
bool CheckType(ValueNode* node, NodeType type);
NodeInfo* CreateInfoIfNot(ValueNode* node, NodeType type);
bool EnsureType(ValueNode* node, NodeType type, NodeType* old = nullptr);
@ -418,9 +420,11 @@ class MaglevGraphBuilder {
NodeT* CreateNewNodeHelper(Args&&... args) {
if constexpr (NodeT::kProperties.can_eager_deopt()) {
return NodeBase::New<NodeT>(zone(), GetLatestCheckpointedFrame(),
current_speculation_feedback_,
std::forward<Args>(args)...);
} else if constexpr (NodeT::kProperties.can_lazy_deopt()) {
return NodeBase::New<NodeT>(zone(), GetDeoptFrameForLazyDeopt(),
current_speculation_feedback_,
std::forward<Args>(args)...);
} else {
return NodeBase::New<NodeT>(zone(), std::forward<Args>(args)...);
@ -1150,7 +1154,9 @@ class MaglevGraphBuilder {
CallNode* AddNewCallNode(const CallArguments& args, Args&&... extra_args);
ValueNode* TryReduceBuiltin(compiler::JSFunctionRef builtin_target,
CallArguments& args);
CallArguments& args,
const compiler::FeedbackSource& feedback_source,
SpeculationMode speculation_mode);
ValueNode* TryBuildCallKnownJSFunction(compiler::JSFunctionRef function,
CallArguments& args);
ValueNode* TryBuildInlinedCall(compiler::JSFunctionRef function,
@ -1160,13 +1166,19 @@ class MaglevGraphBuilder {
const CallArguments& args,
const compiler::FeedbackSource& feedback_source =
compiler::FeedbackSource());
ValueNode* ReduceCall(compiler::ObjectRef target, CallArguments& args);
ValueNode* ReduceCallForTarget(ValueNode* target_node,
compiler::JSFunctionRef target,
CallArguments& args);
ValueNode* ReduceCall(
compiler::ObjectRef target, CallArguments& args,
const compiler::FeedbackSource& feedback_source =
compiler::FeedbackSource(),
SpeculationMode speculation_mode = SpeculationMode::kDisallowSpeculation);
ValueNode* ReduceCallForTarget(
ValueNode* target_node, compiler::JSFunctionRef target,
CallArguments& args, const compiler::FeedbackSource& feedback_source,
SpeculationMode speculation_mode);
ValueNode* ReduceFunctionPrototypeApplyCallWithReceiver(
ValueNode* target_node, compiler::JSFunctionRef receiver,
CallArguments& args);
CallArguments& args, const compiler::FeedbackSource& feedback_source,
SpeculationMode speculation_mode);
void BuildCall(ValueNode* target_node, CallArguments& args,
compiler::FeedbackSource& feedback_source);
void BuildCallFromRegisterList(ConvertReceiverMode receiver_mode);
@ -1427,6 +1439,7 @@ class MaglevGraphBuilder {
MergePointInterpreterFrameState** merge_states_;
InterpreterFrameState current_interpreter_frame_;
compiler::FeedbackSource current_speculation_feedback_;
struct HandlerTableEntry {
int end;

View File

@ -16,6 +16,7 @@
#include "src/codegen/x64/register-x64.h"
#include "src/common/globals.h"
#include "src/compiler/backend/instruction.h"
#include "src/compiler/feedback-source.h"
#include "src/compiler/js-heap-broker.h"
#include "src/deoptimizer/deoptimize-reason.h"
#include "src/ic/handler-configuration.h"
@ -259,8 +260,10 @@ size_t GetInputLocationsArraySize(const DeoptFrame& top_frame) {
}
} // namespace
DeoptInfo::DeoptInfo(Zone* zone, DeoptFrame top_frame)
DeoptInfo::DeoptInfo(Zone* zone, DeoptFrame top_frame,
compiler::FeedbackSource feedback_to_update)
: top_frame_(top_frame),
feedback_to_update_(feedback_to_update),
input_locations_(zone->NewArray<InputLocation>(
GetInputLocationsArraySize(top_frame))) {
// Initialise InputLocations so that they correctly don't have a next use id.

View File

@ -807,10 +807,14 @@ DeoptFrame::as_builtin_continuation() const {
class DeoptInfo {
protected:
DeoptInfo(Zone* zone, DeoptFrame top_frame);
DeoptInfo(Zone* zone, DeoptFrame top_frame,
compiler::FeedbackSource feedback_to_update);
public:
const DeoptFrame& top_frame() const { return top_frame_; }
const compiler::FeedbackSource& feedback_to_update() {
return feedback_to_update_;
}
InputLocation* input_locations() const { return input_locations_; }
Label* deopt_entry_label() { return &deopt_entry_label_; }
@ -820,6 +824,7 @@ class DeoptInfo {
private:
const DeoptFrame top_frame_;
const compiler::FeedbackSource feedback_to_update_;
InputLocation* const input_locations_;
Label deopt_entry_label_;
int translation_index_ = -1;
@ -833,8 +838,9 @@ struct RegisterSnapshot {
class EagerDeoptInfo : public DeoptInfo {
public:
EagerDeoptInfo(Zone* zone, DeoptFrame&& top_frame)
: DeoptInfo(zone, std::move(top_frame)) {}
EagerDeoptInfo(Zone* zone, DeoptFrame&& top_frame,
compiler::FeedbackSource feedback_to_update)
: DeoptInfo(zone, std::move(top_frame), feedback_to_update) {}
DeoptimizeReason reason() const { return reason_; }
void set_reason(DeoptimizeReason reason) { reason_ = reason; }
@ -845,8 +851,9 @@ class EagerDeoptInfo : public DeoptInfo {
class LazyDeoptInfo : public DeoptInfo {
public:
LazyDeoptInfo(Zone* zone, DeoptFrame&& top_frame)
: DeoptInfo(zone, std::move(top_frame)) {}
LazyDeoptInfo(Zone* zone, DeoptFrame&& top_frame,
compiler::FeedbackSource feedback_to_update)
: DeoptInfo(zone, std::move(top_frame), feedback_to_update) {}
interpreter::Register result_location() const { return result_location_; }
int result_size() const { return result_size_; }
@ -965,14 +972,17 @@ class NodeBase : public ZoneObject {
}
template <class Derived, typename... Args>
static Derived* New(Zone* zone, DeoptFrame&& deopt_frame, Args&&... args) {
static Derived* New(Zone* zone, DeoptFrame&& deopt_frame,
compiler::FeedbackSource feedback_to_update,
Args&&... args) {
Derived* node = New<Derived>(zone, std::forward<Args>(args)...);
if constexpr (Derived::kProperties.can_eager_deopt()) {
new (node->eager_deopt_info())
EagerDeoptInfo(zone, std::move(deopt_frame));
EagerDeoptInfo(zone, std::move(deopt_frame), feedback_to_update);
} else {
static_assert(Derived::kProperties.can_lazy_deopt());
new (node->lazy_deopt_info()) LazyDeoptInfo(zone, std::move(deopt_frame));
new (node->lazy_deopt_info())
LazyDeoptInfo(zone, std::move(deopt_frame), feedback_to_update);
}
return node;
}