blob: dab95db163d257bc2f804aafbfaad2825b043d31 [file] [log] [blame]
// Copyright 2024 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.
#ifndef V8_MAGLEV_MAGLEV_PRE_REGALLOC_CODEGEN_PROCESSORS_H_
#define V8_MAGLEV_MAGLEV_PRE_REGALLOC_CODEGEN_PROCESSORS_H_
#include "src/codegen/register-configuration.h"
#include "src/maglev/maglev-compilation-info.h"
#include "src/maglev/maglev-graph-processor.h"
#include "src/maglev/maglev-graph.h"
#include "src/maglev/maglev-ir.h"
namespace v8::internal::maglev {
class ValueLocationConstraintProcessor {
public:
void PreProcessGraph(Graph* graph) {}
void PostProcessGraph(Graph* graph) {}
void PostProcessBasicBlock(BasicBlock* block) {}
BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
return BlockProcessResult::kContinue;
}
void PostPhiProcessing() {}
#define DEF_PROCESS_NODE(NAME) \
ProcessResult Process(NAME* node, const ProcessingState& state) { \
node->InitTemporaries(); \
node->SetValueLocationConstraints(); \
return ProcessResult::kContinue; \
}
NODE_BASE_LIST(DEF_PROCESS_NODE)
#undef DEF_PROCESS_NODE
};
class DecompressedUseMarkingProcessor {
public:
void PreProcessGraph(Graph* graph) {}
void PostProcessGraph(Graph* graph) {}
void PostProcessBasicBlock(BasicBlock* block) {}
BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
return BlockProcessResult::kContinue;
}
void PostPhiProcessing() {}
template <typename NodeT>
ProcessResult Process(NodeT* node, const ProcessingState& state) {
#ifdef V8_COMPRESS_POINTERS
node->MarkTaggedInputsAsDecompressing();
#endif
return ProcessResult::kContinue;
}
};
class MaxCallDepthProcessor {
public:
void PreProcessGraph(Graph* graph) {}
void PostProcessGraph(Graph* graph) {
graph->set_max_call_stack_args(max_call_stack_args_);
graph->set_max_deopted_stack_size(max_deopted_stack_size_);
}
void PostProcessBasicBlock(BasicBlock* block) {}
BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
return BlockProcessResult::kContinue;
}
void PostPhiProcessing() {}
template <typename NodeT>
ProcessResult Process(NodeT* node, const ProcessingState& state) {
if constexpr (NodeT::kProperties.is_call() ||
NodeT::kProperties.needs_register_snapshot()) {
int node_stack_args = node->MaxCallStackArgs();
if constexpr (NodeT::kProperties.needs_register_snapshot()) {
// Pessimistically assume that we'll push all registers in deferred
// calls.
node_stack_args +=
kAllocatableGeneralRegisterCount + kAllocatableDoubleRegisterCount;
}
max_call_stack_args_ = std::max(max_call_stack_args_, node_stack_args);
}
if constexpr (NodeT::kProperties.can_eager_deopt()) {
UpdateMaxDeoptedStackSize(node->eager_deopt_info());
}
if constexpr (NodeT::kProperties.can_lazy_deopt()) {
UpdateMaxDeoptedStackSize(node->lazy_deopt_info());
}
return ProcessResult::kContinue;
}
private:
void UpdateMaxDeoptedStackSize(DeoptInfo* deopt_info) {
const DeoptFrame* deopt_frame = &deopt_info->top_frame();
int frame_size = 0;
if (deopt_frame->type() == DeoptFrame::FrameType::kInterpretedFrame) {
if (&deopt_frame->as_interpreted().unit() == last_seen_unit_) return;
last_seen_unit_ = &deopt_frame->as_interpreted().unit();
frame_size = deopt_frame->as_interpreted().unit().max_arguments() *
kSystemPointerSize;
}
do {
frame_size += ConservativeFrameSize(deopt_frame);
deopt_frame = deopt_frame->parent();
} while (deopt_frame != nullptr);
max_deopted_stack_size_ = std::max(frame_size, max_deopted_stack_size_);
}
int ConservativeFrameSize(const DeoptFrame* deopt_frame) {
switch (deopt_frame->type()) {
case DeoptFrame::FrameType::kInterpretedFrame: {
auto info = UnoptimizedFrameInfo::Conservative(
deopt_frame->as_interpreted().unit().parameter_count(),
deopt_frame->as_interpreted().unit().register_count());
return info.frame_size_in_bytes();
}
case DeoptFrame::FrameType::kConstructInvokeStubFrame: {
return FastConstructStubFrameInfo::Conservative().frame_size_in_bytes();
}
case DeoptFrame::FrameType::kInlinedArgumentsFrame: {
return std::max(
0,
static_cast<int>(
deopt_frame->as_inlined_arguments().arguments().size() -
deopt_frame->as_inlined_arguments().unit().parameter_count()) *
kSystemPointerSize);
}
case DeoptFrame::FrameType::kBuiltinContinuationFrame: {
// PC + FP + Closure + Params + Context
const RegisterConfiguration* config = RegisterConfiguration::Default();
auto info = BuiltinContinuationFrameInfo::Conservative(
deopt_frame->as_builtin_continuation().parameters().length(),
Builtins::CallInterfaceDescriptorFor(
deopt_frame->as_builtin_continuation().builtin_id()),
config);
return info.frame_size_in_bytes();
}
}
}
int max_call_stack_args_ = 0;
int max_deopted_stack_size_ = 0;
// Optimize UpdateMaxDeoptedStackSize to not re-calculate if it sees the same
// compilation unit multiple times in a row.
const MaglevCompilationUnit* last_seen_unit_ = nullptr;
};
class LiveRangeAndNextUseProcessor {
public:
explicit LiveRangeAndNextUseProcessor(MaglevCompilationInfo* compilation_info)
: compilation_info_(compilation_info) {}
void PreProcessGraph(Graph* graph) {}
void PostProcessGraph(Graph* graph) { DCHECK(loop_used_nodes_.empty()); }
void PostProcessBasicBlock(BasicBlock* block) {}
BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
if (!block->has_state()) return BlockProcessResult::kContinue;
if (block->state()->is_loop()) {
loop_used_nodes_.push_back(
LoopUsedNodes{{}, kInvalidNodeId, kInvalidNodeId, block});
}
return BlockProcessResult::kContinue;
}
void PostPhiProcessing() {}
template <typename NodeT>
ProcessResult Process(NodeT* node, const ProcessingState& state) {
node->set_id(next_node_id_++);
LoopUsedNodes* loop_used_nodes = GetCurrentLoopUsedNodes();
if (loop_used_nodes && node->properties().is_call() &&
loop_used_nodes->header->has_state()) {
if (loop_used_nodes->first_call == kInvalidNodeId) {
loop_used_nodes->first_call = node->id();
}
loop_used_nodes->last_call = node->id();
}
MarkInputUses(node, state);
return ProcessResult::kContinue;
}
template <typename NodeT>
void MarkInputUses(NodeT* node, const ProcessingState& state) {
LoopUsedNodes* loop_used_nodes = GetCurrentLoopUsedNodes();
// Mark input uses in the same order as inputs are assigned in the register
// allocator (see StraightForwardRegisterAllocator::AssignInputs).
node->ForAllInputsInRegallocAssignmentOrder(
[&](NodeBase::InputAllocationPolicy, Input* input) {
MarkUse(input->node(), node->id(), input, loop_used_nodes);
});
if constexpr (NodeT::kProperties.can_eager_deopt()) {
MarkCheckpointNodes(node, node->eager_deopt_info(), loop_used_nodes,
state);
}
if constexpr (NodeT::kProperties.can_lazy_deopt()) {
MarkCheckpointNodes(node, node->lazy_deopt_info(), loop_used_nodes,
state);
}
}
void MarkInputUses(Phi* node, const ProcessingState& state) {
// Don't mark Phi uses when visiting the node, because of loop phis.
// Instead, they'll be visited while processing Jump/JumpLoop.
}
// Specialize the two unconditional jumps to extend their Phis' inputs' live
// ranges.
void MarkInputUses(JumpLoop* node, const ProcessingState& state) {
int predecessor_id = state.block()->predecessor_id();
BasicBlock* target = node->target();
uint32_t use = node->id();
DCHECK(!loop_used_nodes_.empty());
LoopUsedNodes loop_used_nodes = std::move(loop_used_nodes_.back());
loop_used_nodes_.pop_back();
LoopUsedNodes* outer_loop_used_nodes = GetCurrentLoopUsedNodes();
if (target->has_phi()) {
for (Phi* phi : *target->phis()) {
DCHECK(phi->is_used());
ValueNode* input = phi->input(predecessor_id).node();
MarkUse(input, use, &phi->input(predecessor_id), outer_loop_used_nodes);
}
}
DCHECK_EQ(loop_used_nodes.header, target);
if (!loop_used_nodes.used_nodes.empty()) {
// Try to avoid unnecessary reloads or spills across the back-edge based
// on use positions and calls inside the loop.
ZonePtrList<ValueNode>& reload_hints =
loop_used_nodes.header->reload_hints();
ZonePtrList<ValueNode>& spill_hints =
loop_used_nodes.header->spill_hints();
for (auto p : loop_used_nodes.used_nodes) {
// If the node is used before the first call and after the last call,
// keep it in a register across the back-edge.
if (p.second.first_register_use != kInvalidNodeId &&
(loop_used_nodes.first_call == kInvalidNodeId ||
(p.second.first_register_use <= loop_used_nodes.first_call &&
p.second.last_register_use > loop_used_nodes.last_call))) {
reload_hints.Add(p.first, compilation_info_->zone());
}
// If the node is not used, or used after the first call and before the
// last call, keep it spilled across the back-edge.
if (p.second.first_register_use == kInvalidNodeId ||
(loop_used_nodes.first_call != kInvalidNodeId &&
p.second.first_register_use > loop_used_nodes.first_call &&
p.second.last_register_use <= loop_used_nodes.last_call)) {
spill_hints.Add(p.first, compilation_info_->zone());
}
}
// Uses of nodes in this loop may need to propagate to an outer loop, so
// that they're lifetime is extended there too.
// TODO(leszeks): We only need to extend the lifetime in one outermost
// loop, allow nodes to be "moved" between lifetime extensions.
base::Vector<Input> used_node_inputs =
compilation_info_->zone()->AllocateVector<Input>(
loop_used_nodes.used_nodes.size());
int i = 0;
for (auto& [used_node, info] : loop_used_nodes.used_nodes) {
Input* input = new (&used_node_inputs[i++]) Input(used_node);
MarkUse(used_node, use, input, outer_loop_used_nodes);
}
node->set_used_nodes(used_node_inputs);
}
}
void MarkInputUses(Jump* node, const ProcessingState& state) {
MarkJumpInputUses(node->id(), node->target(), state);
}
void MarkInputUses(CheckpointedJump* node, const ProcessingState& state) {
MarkJumpInputUses(node->id(), node->target(), state);
}
void MarkJumpInputUses(uint32_t use, BasicBlock* target,
const ProcessingState& state) {
int i = state.block()->predecessor_id();
if (!target->has_phi()) return;
LoopUsedNodes* loop_used_nodes = GetCurrentLoopUsedNodes();
Phi::List& phis = *target->phis();
for (auto it = phis.begin(); it != phis.end();) {
Phi* phi = *it;
if (!phi->is_used()) {
// Skip unused phis -- we're processing phis out of order with the dead
// node sweeping processor, so we will still observe unused phis here.
// We can eagerly remove them while we're at it so that the dead node
// sweeping processor doesn't have to revisit them.
it = phis.RemoveAt(it);
} else {
ValueNode* input = phi->input(i).node();
MarkUse(input, use, &phi->input(i), loop_used_nodes);
++it;
}
}
}
private:
struct NodeUse {
// First and last register use inside a loop.
NodeIdT first_register_use;
NodeIdT last_register_use;
};
struct LoopUsedNodes {
std::map<ValueNode*, NodeUse> used_nodes;
NodeIdT first_call;
NodeIdT last_call;
BasicBlock* header;
};
LoopUsedNodes* GetCurrentLoopUsedNodes() {
if (loop_used_nodes_.empty()) return nullptr;
return &loop_used_nodes_.back();
}
void MarkUse(ValueNode* node, uint32_t use_id, InputLocation* input,
LoopUsedNodes* loop_used_nodes) {
DCHECK(!node->Is<Identity>());
node->record_next_use(use_id, input);
// If we are in a loop, loop_used_nodes is non-null. In this case, check if
// the incoming node is from outside the loop, and make sure to extend its
// lifetime to the loop end if yes.
if (loop_used_nodes) {
// If the node's id is smaller than the smallest id inside the loop, then
// it must have been created before the loop. This means that it's alive
// on loop entry, and therefore has to be alive across the loop back edge
// too.
if (node->id() < loop_used_nodes->header->first_id()) {
auto [it, info] = loop_used_nodes->used_nodes.emplace(
node, NodeUse{kInvalidNodeId, kInvalidNodeId});
if (input->operand().IsUnallocated()) {
const auto& operand =
compiler::UnallocatedOperand::cast(input->operand());
if (operand.HasRegisterPolicy() || operand.HasFixedRegisterPolicy() ||
operand.HasFixedFPRegisterPolicy()) {
if (it->second.first_register_use == kInvalidNodeId) {
it->second.first_register_use = use_id;
}
it->second.last_register_use = use_id;
}
}
}
}
}
template <typename DeoptInfoT>
void MarkCheckpointNodes(NodeBase* node, DeoptInfoT* deopt_info,
LoopUsedNodes* loop_used_nodes,
const ProcessingState& state) {
int use_id = node->id();
if (!deopt_info->has_input_locations()) {
size_t count = 0;
deopt_info->ForEachInput([&](ValueNode*) { count++; });
deopt_info->InitializeInputLocations(compilation_info_->zone(), count);
}
InputLocation* input = deopt_info->input_locations();
deopt_info->ForEachInput([&](ValueNode* node) {
MarkUse(node, use_id, input, loop_used_nodes);
input++;
});
}
MaglevCompilationInfo* compilation_info_;
uint32_t next_node_id_ = kFirstValidNodeId;
std::vector<LoopUsedNodes> loop_used_nodes_;
};
} // namespace v8::internal::maglev
#endif // V8_MAGLEV_MAGLEV_PRE_REGALLOC_CODEGEN_PROCESSORS_H_