#include "src/compiler/scheduler.h"
#include <iomanip>
#include <optional>
#include "src/base/iterator.h"
#include "src/builtins/profile-data-reader.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/control-equivalence.h"
#include "src/compiler/node-marker.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/node.h"
#include "src/compiler/turbofan-graph.h"
#include "src/utils/bit-vector.h"
#include "src/zone/zone-containers.h"
namespace v8 {
namespace internal {
namespace compiler {
#define TRACE(...) \
do { \
if (v8_flags.trace_turbo_scheduler) PrintF(__VA_ARGS__); \
} while (false)
Scheduler::Scheduler(Zone* zone, TFGraph* graph, Schedule* schedule,
Flags flags, size_t node_count_hint,
TickCounter* tick_counter,
const ProfileDataFromFile* profile_data)
: zone_(zone),
graph_(graph),
schedule_(schedule),
flags_(flags),
scheduled_nodes_(zone),
schedule_root_nodes_(zone),
schedule_queue_(zone),
node_data_(zone),
tick_counter_(tick_counter),
profile_data_(profile_data),
common_dominator_cache_(zone) {
node_data_.reserve(node_count_hint);
node_data_.resize(graph->NodeCount(), DefaultSchedulerData());
}
Schedule* Scheduler::ComputeSchedule(Zone* zone, TFGraph* graph, Flags flags,
TickCounter* tick_counter,
const ProfileDataFromFile* profile_data) {
Zone* schedule_zone =
(flags & Scheduler::kTempSchedule) ? zone : graph->zone();
float node_hint_multiplier = (flags & Scheduler::kSplitNodes) ? 1.1 : 1;
size_t node_count_hint = node_hint_multiplier * graph->NodeCount();
Schedule* schedule =
schedule_zone->New<Schedule>(schedule_zone, node_count_hint);
Scheduler scheduler(zone, graph, schedule, flags, node_count_hint,
tick_counter, profile_data);
scheduler.BuildCFG();
scheduler.ComputeSpecialRPONumbering();
scheduler.GenerateDominatorTree();
scheduler.PrepareUses();
scheduler.ScheduleEarly();
scheduler.ScheduleLate();
scheduler.SealFinalSchedule();
return schedule;
}
Scheduler::SchedulerData Scheduler::DefaultSchedulerData() {
SchedulerData def = {schedule_->start(), 0, kUnknown};
return def;
}
Scheduler::SchedulerData* Scheduler::GetData(Node* node) {
return &node_data_[node->id()];
}
Scheduler::Placement Scheduler::InitializePlacement(Node* node) {
SchedulerData* data = GetData(node);
if (data->placement_ == kFixed) {
return data->placement_;
}
DCHECK_EQ(kUnknown, data->placement_);
switch (node->opcode()) {
case IrOpcode::kParameter:
case IrOpcode::kOsrValue:
data->placement_ = kFixed;
break;
case IrOpcode::kPhi:
case IrOpcode::kEffectPhi: {
Placement p = GetPlacement(NodeProperties::GetControlInput(node));
data->placement_ = (p == kFixed ? kFixed : kCoupled);
break;
}
default:
data->placement_ = kSchedulable;
break;
}
return data->placement_;
}
Scheduler::Placement Scheduler::GetPlacement(Node* node) {
return GetData(node)->placement_;
}
bool Scheduler::IsLive(Node* node) { return GetPlacement(node) != kUnknown; }
void Scheduler::UpdatePlacement(Node* node, Placement placement) {
SchedulerData* data = GetData(node);
if (data->placement_ == kUnknown) {
DCHECK_EQ(Scheduler::kFixed, placement);
data->placement_ = placement;
return;
}
switch (node->opcode()) {
case IrOpcode::kParameter:
UNREACHABLE();
case IrOpcode::kPhi:
case IrOpcode::kEffectPhi: {
DCHECK_EQ(Scheduler::kCoupled, data->placement_);
DCHECK_EQ(Scheduler::kFixed, placement);
Node* control = NodeProperties::GetControlInput(node);
BasicBlock* block = schedule_->block(control);
schedule_->AddNode(block, node);
break;
}
#define DEFINE_CONTROL_CASE(V) case IrOpcode::k##V:
CONTROL_OP_LIST(DEFINE_CONTROL_CASE)
#undef DEFINE_CONTROL_CASE
{
for (auto use : node->uses()) {
if (GetPlacement(use) == Scheduler::kCoupled) {
DCHECK_EQ(node, NodeProperties::GetControlInput(use));
UpdatePlacement(use, placement);
}
}
break;
}
default:
DCHECK_EQ(Scheduler::kSchedulable, data->placement_);
DCHECK_EQ(Scheduler::kScheduled, placement);
break;
}
std::optional<int> coupled_control_edge = GetCoupledControlEdge(node);
for (Edge const edge : node->input_edges()) {
DCHECK_EQ(node, edge.from());
if (edge.index() != coupled_control_edge) {
DecrementUnscheduledUseCount(edge.to(), node);
}
}
data->placement_ = placement;
}
std::optional<int> Scheduler::GetCoupledControlEdge(Node* node) {
if (GetPlacement(node) == kCoupled) {
return NodeProperties::FirstControlIndex(node);
}
return {};
}
void Scheduler::IncrementUnscheduledUseCount(Node* node, Node* from) {
if (GetPlacement(node) == kFixed) return;
if (GetPlacement(node) == kCoupled) {
node = NodeProperties::GetControlInput(node);
DCHECK_NE(GetPlacement(node), Placement::kFixed);
DCHECK_NE(GetPlacement(node), Placement::kCoupled);
}
++(GetData(node)->unscheduled_count_);
if (v8_flags.trace_turbo_scheduler) {
TRACE(" Use count of #%d:%s (used by #%d:%s)++ = %d\n", node->id(),
node->op()->mnemonic(), from->id(), from->op()->mnemonic(),
GetData(node)->unscheduled_count_);
}
}
void Scheduler::DecrementUnscheduledUseCount(Node* node, Node* from) {
if (GetPlacement(node) == kFixed) return;
if (GetPlacement(node) == kCoupled) {
node = NodeProperties::GetControlInput(node);
DCHECK_NE(GetPlacement(node), Placement::kFixed);
DCHECK_NE(GetPlacement(node), Placement::kCoupled);
}
DCHECK_LT(0, GetData(node)->unscheduled_count_);
--(GetData(node)->unscheduled_count_);
if (v8_flags.trace_turbo_scheduler) {
TRACE(" Use count of #%d:%s (used by #%d:%s)-- = %d\n", node->id(),
node->op()->mnemonic(), from->id(), from->op()->mnemonic(),
GetData(node)->unscheduled_count_);
}
if (GetData(node)->unscheduled_count_ == 0) {
TRACE(" newly eligible #%d:%s\n", node->id(), node->op()->mnemonic());
schedule_queue_.push(node);
}
}
class CFGBuilder : public ZoneObject {
public:
CFGBuilder(Zone* zone, Scheduler* scheduler)
: zone_(zone),
scheduler_(scheduler),
schedule_(scheduler->schedule_),
queued_(scheduler->graph_, 2),
queue_(zone),
control_(zone),
component_entry_(nullptr),
component_start_(nullptr),
component_end_(nullptr) {}
void Run() {
ResetDataStructures();
Queue(scheduler_->graph_->end());
while (!queue_.empty()) {
scheduler_->tick_counter_->TickAndMaybeEnterSafepoint();
Node* node = queue_.front();
queue_.pop();
int max = NodeProperties::PastControlIndex(node);
for (int i = NodeProperties::FirstControlIndex(node); i < max; i++) {
Queue(node->InputAt(i));
}
}
for (NodeVector::iterator i = control_.begin(); i != control_.end(); ++i) {
ConnectBlocks(*i);
}
}
void Run(BasicBlock* block, Node* exit) {
ResetDataStructures();
Queue(exit);
component_entry_ = nullptr;
component_start_ = block;
component_end_ = schedule_->block(exit);
scheduler_->equivalence_->Run(exit);
while (!queue_.empty()) {
scheduler_->tick_counter_->TickAndMaybeEnterSafepoint();
Node* node = queue_.front();
queue_.pop();
if (IsSingleEntrySingleExitRegion(node, exit)) {
TRACE("Found SESE at #%d:%s\n", node->id(), node->op()->mnemonic());
DCHECK(!component_entry_);
component_entry_ = node;
continue;
}
int max = NodeProperties::PastControlIndex(node);
for (int i = NodeProperties::FirstControlIndex(node); i < max; i++) {
Queue(node->InputAt(i));
}
}
DCHECK(component_entry_);
for (NodeVector::iterator i = control_.begin(); i != control_.end(); ++i) {
ConnectBlocks(*i);
}
}
private:
friend class ScheduleLateNodeVisitor;
friend class Scheduler;
void FixNode(BasicBlock* block, Node* node) {
schedule_->AddNode(block, node);
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
}
void Queue(Node* node) {
if (!queued_.Get(node)) {
BuildBlocks(node);
queue_.push(node);
queued_.Set(node, true);
control_.push_back(node);
}
}
void BuildBlocks(Node* node) {
switch (node->opcode()) {
case IrOpcode::kEnd:
FixNode(schedule_->end(), node);
break;
case IrOpcode::kStart:
FixNode(schedule_->start(), node);
break;
case IrOpcode::kLoop:
case IrOpcode::kMerge:
BuildBlockForNode(node);
break;
case IrOpcode::kTerminate: {
Node* loop = NodeProperties::GetControlInput(node);
BasicBlock* block = BuildBlockForNode(loop);
FixNode(block, node);
break;
}
case IrOpcode::kBranch:
case IrOpcode::kSwitch:
BuildBlocksForSuccessors(node);
break;
#define BUILD_BLOCK_JS_CASE(Name, ...) case IrOpcode::k##Name:
JS_OP_LIST(BUILD_BLOCK_JS_CASE)
#undef BUILD_BLOCK_JS_CASE
case IrOpcode::kCall:
case IrOpcode::kFastApiCall:
if (NodeProperties::IsExceptionalCall(node)) {
BuildBlocksForSuccessors(node);
}
break;
default:
break;
}
}
void ConnectBlocks(Node* node) {
switch (node->opcode()) {
case IrOpcode::kLoop:
case IrOpcode::kMerge:
ConnectMerge(node);
break;
case IrOpcode::kBranch:
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
ConnectBranch(node);
break;
case IrOpcode::kSwitch:
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
ConnectSwitch(node);
break;
case IrOpcode::kDeoptimize:
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
ConnectDeoptimize(node);
break;
case IrOpcode::kTailCall:
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
ConnectTailCall(node);
break;
case IrOpcode::kReturn:
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
ConnectReturn(node);
break;
case IrOpcode::kThrow:
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
ConnectThrow(node);
break;
#define CONNECT_BLOCK_JS_CASE(Name, ...) case IrOpcode::k##Name:
JS_OP_LIST(CONNECT_BLOCK_JS_CASE)
#undef CONNECT_BLOCK_JS_CASE
case IrOpcode::kCall:
case IrOpcode::kFastApiCall:
if (NodeProperties::IsExceptionalCall(node)) {
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
ConnectCall(node);
}
break;
default:
break;
}
}
BasicBlock* BuildBlockForNode(Node* node) {
BasicBlock* block = schedule_->block(node);
if (block == nullptr) {
block = schedule_->NewBasicBlock();
TRACE("Create block id:%d for #%d:%s\n", block->id().ToInt(), node->id(),
node->op()->mnemonic());
FixNode(block, node);
}
return block;
}
void BuildBlocksForSuccessors(Node* node) {
size_t const successor_cnt = node->op()->ControlOutputCount();
Node** successors = zone_->AllocateArray<Node*>(successor_cnt);
NodeProperties::CollectControlProjections(node, successors, successor_cnt);
for (size_t index = 0; index < successor_cnt; ++index) {
BuildBlockForNode(successors[index]);
}
}
void CollectSuccessorBlocks(Node* node, BasicBlock** successor_blocks,
size_t successor_cnt) {
Node** successors = reinterpret_cast<Node**>(successor_blocks);
NodeProperties::CollectControlProjections(node, successors, successor_cnt);
for (size_t index = 0; index < successor_cnt; ++index) {
successor_blocks[index] = schedule_->block(successors[index]);
}
}
BasicBlock* FindPredecessorBlock(Node* node) {
BasicBlock* predecessor_block = nullptr;
while (true) {
predecessor_block = schedule_->block(node);
if (predecessor_block != nullptr) break;
node = NodeProperties::GetControlInput(node);
}
return predecessor_block;
}
void ConnectCall(Node* call) {
BasicBlock* successor_blocks[2];
CollectSuccessorBlocks(call, successor_blocks, arraysize(successor_blocks));
successor_blocks[1]->set_deferred(true);
Node* call_control = NodeProperties::GetControlInput(call);
BasicBlock* call_block = FindPredecessorBlock(call_control);
TraceConnect(call, call_block, successor_blocks[0]);
TraceConnect(call, call_block, successor_blocks[1]);
schedule_->AddCall(call_block, call, successor_blocks[0],
successor_blocks[1]);
}
void ConnectBranch(Node* branch) {
BasicBlock* successor_blocks[2];
CollectSuccessorBlocks(branch, successor_blocks,
arraysize(successor_blocks));
BranchHint hint_from_profile = BranchHint::kNone;
if (const ProfileDataFromFile* profile_data = scheduler_->profile_data()) {
hint_from_profile =
profile_data->GetHint(successor_blocks[0]->id().ToSize(),
successor_blocks[1]->id().ToSize());
}
switch (hint_from_profile) {
case BranchHint::kNone:
switch (BranchHintOf(branch->op())) {
case BranchHint::kNone:
break;
case BranchHint::kTrue:
successor_blocks[1]->set_deferred(true);
break;
case BranchHint::kFalse:
successor_blocks[0]->set_deferred(true);
break;
}
break;
case BranchHint::kTrue:
successor_blocks[1]->set_deferred(true);
break;
case BranchHint::kFalse:
successor_blocks[0]->set_deferred(true);
break;
}
if (branch == component_entry_) {
TraceConnect(branch, component_start_, successor_blocks[0]);
TraceConnect(branch, component_start_, successor_blocks[1]);
schedule_->InsertBranch(component_start_, component_end_, branch,
successor_blocks[0], successor_blocks[1]);
} else {
Node* branch_control = NodeProperties::GetControlInput(branch);
BasicBlock* branch_block = FindPredecessorBlock(branch_control);
TraceConnect(branch, branch_block, successor_blocks[0]);
TraceConnect(branch, branch_block, successor_blocks[1]);
schedule_->AddBranch(branch_block, branch, successor_blocks[0],
successor_blocks[1]);
}
}
void ConnectSwitch(Node* sw) {
size_t const successor_count = sw->op()->ControlOutputCount();
BasicBlock** successor_blocks =
zone_->AllocateArray<BasicBlock*>(successor_count);
CollectSuccessorBlocks(sw, successor_blocks, successor_count);
if (sw == component_entry_) {
for (size_t index = 0; index < successor_count; ++index) {
TraceConnect(sw, component_start_, successor_blocks[index]);
}
schedule_->InsertSwitch(component_start_, component_end_, sw,
successor_blocks, successor_count);
} else {
Node* switch_control = NodeProperties::GetControlInput(sw);
BasicBlock* switch_block = FindPredecessorBlock(switch_control);
for (size_t index = 0; index < successor_count; ++index) {
TraceConnect(sw, switch_block, successor_blocks[index]);
}
schedule_->AddSwitch(switch_block, sw, successor_blocks, successor_count);
}
for (size_t index = 0; index < successor_count; ++index) {
if (BranchHintOf(successor_blocks[index]->front()->op()) ==
BranchHint::kFalse) {
successor_blocks[index]->set_deferred(true);
}
}
}
void ConnectMerge(Node* merge) {
if (IsFinalMerge(merge)) return;
BasicBlock* block = schedule_->block(merge);
DCHECK_NOT_NULL(block);
for (Node* const input : merge->inputs()) {
BasicBlock* predecessor_block = FindPredecessorBlock(input);
TraceConnect(merge, predecessor_block, block);
schedule_->AddGoto(predecessor_block, block);
}
}
void ConnectTailCall(Node* call) {
Node* call_control = NodeProperties::GetControlInput(call);
BasicBlock* call_block = FindPredecessorBlock(call_control);
TraceConnect(call, call_block, nullptr);
schedule_->AddTailCall(call_block, call);
}
void ConnectReturn(Node* ret) {
Node* return_control = NodeProperties::GetControlInput(ret);
BasicBlock* return_block = FindPredecessorBlock(return_control);
TraceConnect(ret, return_block, nullptr);
schedule_->AddReturn(return_block, ret);
}
void ConnectDeoptimize(Node* deopt) {
Node* deoptimize_control = NodeProperties::GetControlInput(deopt);
BasicBlock* deoptimize_block = FindPredecessorBlock(deoptimize_control);
TraceConnect(deopt, deoptimize_block, nullptr);
schedule_->AddDeoptimize(deoptimize_block, deopt);
}
void ConnectThrow(Node* thr) {
Node* throw_control = NodeProperties::GetControlInput(thr);
BasicBlock* throw_block = FindPredecessorBlock(throw_control);
TraceConnect(thr, throw_block, nullptr);
schedule_->AddThrow(throw_block, thr);
}
void TraceConnect(Node* node, BasicBlock* block, BasicBlock* succ) {
DCHECK_NOT_NULL(block);
if (succ == nullptr) {
TRACE("Connect #%d:%s, id:%d -> end\n", node->id(),
node->op()->mnemonic(), block->id().ToInt());
} else {
TRACE("Connect #%d:%s, id:%d -> id:%d\n", node->id(),
node->op()->mnemonic(), block->id().ToInt(), succ->id().ToInt());
}
}
bool IsFinalMerge(Node* node) {
return (node->opcode() == IrOpcode::kMerge &&
node == scheduler_->graph_->end()->InputAt(0));
}
bool IsSingleEntrySingleExitRegion(Node* entry, Node* exit) const {
size_t entry_class = scheduler_->equivalence_->ClassOf(entry);
size_t exit_class = scheduler_->equivalence_->ClassOf(exit);
return entry != exit && entry_class == exit_class;
}
void ResetDataStructures() {
control_.clear();
DCHECK(queue_.empty());
DCHECK(control_.empty());
}
Zone* zone_;
Scheduler* scheduler_;
Schedule* schedule_;
NodeMarker<bool> queued_;
ZoneQueue<Node*> queue_;
NodeVector control_;
Node* component_entry_;
BasicBlock* component_start_;
BasicBlock* component_end_;
};
void Scheduler::BuildCFG() {
TRACE("--- CREATING CFG -------------------------------------------\n");
equivalence_ = zone_->New<ControlEquivalence>(zone_, graph_);
control_flow_builder_ = zone_->New<CFGBuilder>(zone_, this);
control_flow_builder_->Run();
scheduled_nodes_.reserve(schedule_->BasicBlockCount() * 1.1);
scheduled_nodes_.resize(schedule_->BasicBlockCount());
}
class SpecialRPONumberer : public ZoneObject {
public:
SpecialRPONumberer(Zone* zone, Schedule* schedule)
: zone_(zone),
schedule_(schedule),
order_(nullptr),
beyond_end_(nullptr),
loops_(zone),
backedges_(zone),
stack_(zone),
previous_block_count_(0),
empty_(0, zone) {}
void ComputeSpecialRPO() {
DCHECK_EQ(0, schedule_->end()->SuccessorCount());
DCHECK(!order_);
ComputeAndInsertSpecialRPO(schedule_->start(), schedule_->end());
}
void UpdateSpecialRPO(BasicBlock* entry, BasicBlock* end) {
DCHECK(order_);
ComputeAndInsertSpecialRPO(entry, end);
}
void SerializeRPOIntoSchedule() {
int32_t number = 0;
for (BasicBlock* b = order_; b != nullptr; b = b->rpo_next()) {
b->set_rpo_number(number++);
schedule_->rpo_order()->push_back(b);
}
BeyondEndSentinel()->set_rpo_number(number);
}
void PrintAndVerifySpecialRPO() {
#if DEBUG
if (v8_flags.trace_turbo_scheduler) PrintRPO();
VerifySpecialRPO();
#endif
}
const ZoneVector<BasicBlock*>& GetOutgoingBlocks(BasicBlock* block) {
if (HasLoopNumber(block)) {
LoopInfo const& loop = loops_[GetLoopNumber(block)];
if (loop.outgoing) return *loop.outgoing;
}
return empty_;
}
bool HasLoopBlocks() const { return !loops_.empty(); }
private:
using Backedge = std::pair<BasicBlock*, size_t>;
static const int kBlockOnStack = -2;
static const int kBlockVisited1 = -3;
static const int kBlockVisited2 = -4;
static const int kBlockUnvisited1 = -1;
static const int kBlockUnvisited2 = kBlockVisited1;
struct SpecialRPOStackFrame {
BasicBlock* block;
size_t index;
};
struct LoopInfo {
BasicBlock* header;
ZoneVector<BasicBlock*>* outgoing;
BitVector* members;
LoopInfo* prev;
BasicBlock* end;
BasicBlock* start;
void AddOutgoing(Zone* zone, BasicBlock* block) {
if (outgoing == nullptr) {
outgoing = zone->New<ZoneVector<BasicBlock*>>(zone);
}
outgoing->push_back(block);
}
};
int Push(int depth, BasicBlock* child, int unvisited) {
if (child->rpo_number() == unvisited) {
stack_[depth].block = child;
stack_[depth].index = 0;
child->set_rpo_number(kBlockOnStack);
return depth + 1;
}
return depth;
}
BasicBlock* PushFront(BasicBlock* head, BasicBlock* block) {
block->set_rpo_next(head);
return block;
}
static int GetLoopNumber(BasicBlock* block) { return block->loop_number(); }
static void SetLoopNumber(BasicBlock* block, int loop_number) {
return block->set_loop_number(loop_number);
}
static bool HasLoopNumber(BasicBlock* block) {
return block->loop_number() >= 0;
}
BasicBlock* BeyondEndSentinel() {
if (beyond_end_ == nullptr) {
BasicBlock::Id id = BasicBlock::Id::FromInt(-1);
beyond_end_ = schedule_->zone()->New<BasicBlock>(schedule_->zone(), id);
}
return beyond_end_;
}
void ComputeAndInsertSpecialRPO(BasicBlock* entry, BasicBlock* end) {
CHECK_EQ(kBlockUnvisited1, schedule_->start()->loop_number());
CHECK_EQ(kBlockUnvisited1, schedule_->start()->rpo_number());
CHECK_EQ(0, static_cast<int>(schedule_->rpo_order()->size()));
BasicBlock* insertion_point = entry->rpo_next();
BasicBlock* order = insertion_point;
DCHECK_LT(previous_block_count_, schedule_->BasicBlockCount());
stack_.resize(schedule_->BasicBlockCount() - previous_block_count_);
previous_block_count_ = schedule_->BasicBlockCount();
int stack_depth = Push(0, entry, kBlockUnvisited1);
int num_loops = static_cast<int>(loops_.size());
while (stack_depth > 0) {
int current = stack_depth - 1;
SpecialRPOStackFrame* frame = &stack_[current];
if (frame->block != end &&
frame->index < frame->block->SuccessorCount()) {
BasicBlock* succ = frame->block->SuccessorAt(frame->index++);
if (succ->rpo_number() == kBlockVisited1) continue;
if (succ->rpo_number() == kBlockOnStack) {
backedges_.push_back(Backedge(frame->block, frame->index - 1));
if (!HasLoopNumber(succ)) {
SetLoopNumber(succ, num_loops++);
}
} else {
DCHECK_EQ(kBlockUnvisited1, succ->rpo_number());
stack_depth = Push(stack_depth, succ, kBlockUnvisited1);
}
} else {
order = PushFront(order, frame->block);
frame->block->set_rpo_number(kBlockVisited1);
stack_depth--;
}
}
if (num_loops > static_cast<int>(loops_.size())) {
ComputeLoopInfo(&stack_, num_loops, &backedges_);
LoopInfo* loop =
HasLoopNumber(entry) ? &loops_[GetLoopNumber(entry)] : nullptr;
order = insertion_point;
stack_depth = Push(0, entry, kBlockUnvisited2);
while (stack_depth > 0) {
SpecialRPOStackFrame* frame = &stack_[stack_depth - 1];
BasicBlock* block = frame->block;
BasicBlock* succ = nullptr;
if (block != end && frame->index < block->SuccessorCount()) {
succ = block->SuccessorAt(frame->index++);
} else if (HasLoopNumber(block)) {
if (block->rpo_number() == kBlockOnStack) {
DCHECK(loop != nullptr && loop->header == block);
loop->start = PushFront(order, block);
order = loop->end;
block->set_rpo_number(kBlockVisited2);
loop = loop->prev;
}
size_t outgoing_index = frame->index - block->SuccessorCount();
LoopInfo* info = &loops_[GetLoopNumber(block)];
DCHECK(loop != info);
if (block != entry && info->outgoing != nullptr &&
outgoing_index < info->outgoing->size()) {
succ = info->outgoing->at(outgoing_index);
frame->index++;
}
}
if (succ != nullptr) {
if (succ->rpo_number() == kBlockOnStack) continue;
if (succ->rpo_number() == kBlockVisited2) continue;
DCHECK_EQ(kBlockUnvisited2, succ->rpo_number());
if (loop != nullptr && !loop->members->Contains(succ->id().ToInt())) {
loop->AddOutgoing(zone_, succ);
} else {
stack_depth = Push(stack_depth, succ, kBlockUnvisited2);
if (HasLoopNumber(succ)) {
DCHECK(GetLoopNumber(succ) < num_loops);
LoopInfo* next = &loops_[GetLoopNumber(succ)];
next->end = order;
next->prev = loop;
loop = next;
}
}
} else {
if (HasLoopNumber(block)) {
LoopInfo* info = &loops_[GetLoopNumber(block)];
for (BasicBlock* b = info->start; true; b = b->rpo_next()) {
if (b->rpo_next() == info->end) {
b->set_rpo_next(order);
info->end = order;
break;
}
}
order = info->start;
} else {
order = PushFront(order, block);
block->set_rpo_number(kBlockVisited2);
}
stack_depth--;
}
}
}
if (order_ == nullptr) order_ = order;
LoopInfo* current_loop = nullptr;
BasicBlock* current_header = entry->loop_header();
int32_t loop_depth = entry->loop_depth();
if (entry->IsLoopHeader()) --loop_depth;
for (BasicBlock* b = order; b != insertion_point; b = b->rpo_next()) {
BasicBlock* current = b;
current->set_rpo_number(kBlockUnvisited1);
while (current_header != nullptr &&
current == current_header->loop_end()) {
DCHECK(current_header->IsLoopHeader());
DCHECK_NOT_NULL(current_loop);
current_loop = current_loop->prev;
current_header =
current_loop == nullptr ? nullptr : current_loop->header;
--loop_depth;
}
current->set_loop_header(current_header);
if (HasLoopNumber(current)) {
++loop_depth;
current_loop = &loops_[GetLoopNumber(current)];
BasicBlock* loop_end = current_loop->end;
current->set_loop_end(loop_end == nullptr ? BeyondEndSentinel()
: loop_end);
current_header = current_loop->header;
TRACE("id:%d is a loop header, increment loop depth to %d\n",
current->id().ToInt(), loop_depth);
}
current->set_loop_depth(loop_depth);
if (current->loop_header() == nullptr) {
TRACE("id:%d is not in a loop (depth == %d)\n", current->id().ToInt(),
current->loop_depth());
} else {
TRACE("id:%d has loop header id:%d, (depth == %d)\n",
current->id().ToInt(), current->loop_header()->id().ToInt(),
current->loop_depth());
}
}
}
void ComputeLoopInfo(ZoneVector<SpecialRPOStackFrame>* queue,
size_t num_loops, ZoneVector<Backedge>* backedges) {
for (LoopInfo& loop : loops_) {
loop.members->Resize(static_cast<int>(schedule_->BasicBlockCount()),
zone_);
}
loops_.resize(num_loops, LoopInfo());
for (size_t i = 0; i < backedges->size(); i++) {
BasicBlock* member = backedges->at(i).first;
BasicBlock* header = member->SuccessorAt(backedges->at(i).second);
size_t loop_num = GetLoopNumber(header);
if (loops_[loop_num].header == nullptr) {
loops_[loop_num].header = header;
loops_[loop_num].members = zone_->New<BitVector>(
static_cast<int>(schedule_->BasicBlockCount()), zone_);
}
int queue_length = 0;
if (member != header) {
if (!loops_[loop_num].members->Contains(member->id().ToInt())) {
loops_[loop_num].members->Add(member->id().ToInt());
}
(*queue)[queue_length++].block = member;
}
while (queue_length > 0) {
BasicBlock* block = (*queue)[--queue_length].block;
for (size_t j = 0; j < block->PredecessorCount(); j++) {
BasicBlock* pred = block->PredecessorAt(j);
if (pred != header) {
if (!loops_[loop_num].members->Contains(pred->id().ToInt())) {
loops_[loop_num].members->Add(pred->id().ToInt());
(*queue)[queue_length++].block = pred;
}
}
}
}
}
}
#if DEBUG
void PrintRPO() {
StdoutStream os;
os << "RPO with " << loops_.size() << " loops";
if (loops_.size() > 0) {
os << " (";
for (size_t i = 0; i < loops_.size(); i++) {
if (i > 0) os << " ";
os << "id:" << loops_[i].header->id();
}
os << ")";
}
os << ":\n";
for (BasicBlock* block = order_; block != nullptr;
block = block->rpo_next()) {
os << std::setw(5) << "B" << block->rpo_number() << ":";
for (size_t i = 0; i < loops_.size(); i++) {
bool range = loops_[i].header->LoopContains(block);
bool membership = loops_[i].header != block && range;
os << (membership ? " |" : " ");
os << (range ? "x" : " ");
}
os << " id:" << block->id() << ": ";
if (block->loop_end() != nullptr) {
os << " range: [B" << block->rpo_number() << ", B"
<< block->loop_end()->rpo_number() << ")";
}
if (block->loop_header() != nullptr) {
os << " header: id:" << block->loop_header()->id();
}
if (block->loop_depth() > 0) {
os << " depth: " << block->loop_depth();
}
os << "\n";
}
}
void VerifySpecialRPO() {
BasicBlockVector* order = schedule_->rpo_order();
DCHECK_LT(0, order->size());
DCHECK_EQ(0, (*order)[0]->id().ToInt());
for (size_t i = 0; i < loops_.size(); i++) {
LoopInfo* loop = &loops_[i];
BasicBlock* header = loop->header;
BasicBlock* end = header->loop_end();
DCHECK_NOT_NULL(header);
DCHECK_LE(0, header->rpo_number());
DCHECK_LT(header->rpo_number(), order->size());
DCHECK_NOT_NULL(end);
DCHECK_LE(end->rpo_number(), order->size());
DCHECK_GT(end->rpo_number(), header->rpo_number());
DCHECK_NE(header->loop_header(), header);
int links = 0;
BasicBlock* block = loop->start;
DCHECK_EQ(header, block);
bool end_found;
while (true) {
if (block == nullptr || block == loop->end) {
end_found = (loop->end == block);
break;
}
DCHECK(block->rpo_number() == links + header->rpo_number());
links++;
block = block->rpo_next();
DCHECK_LT(links, static_cast<int>(2 * order->size()));
}
DCHECK_LT(0, links);
DCHECK_EQ(links, end->rpo_number() - header->rpo_number());
DCHECK(end_found);
int loop_depth = 0;
for (LoopInfo* outer = loop; outer != nullptr; outer = outer->prev) {
loop_depth++;
}
DCHECK_EQ(loop_depth, header->loop_depth());
int count = 0;
for (int j = 0; j < static_cast<int>(order->size()); j++) {
block = order->at(j);
DCHECK_EQ(block->rpo_number(), j);
if (j < header->rpo_number() || j >= end->rpo_number()) {
DCHECK(!header->LoopContains(block));
} else {
DCHECK(header->LoopContains(block));
DCHECK_GE(block->loop_depth(), loop_depth);
count++;
}
}
DCHECK_EQ(links, count);
}
}
#endif
Zone* zone_;
Schedule* schedule_;
BasicBlock* order_;
BasicBlock* beyond_end_;
ZoneVector<LoopInfo> loops_;
ZoneVector<Backedge> backedges_;
ZoneVector<SpecialRPOStackFrame> stack_;
size_t previous_block_count_;
ZoneVector<BasicBlock*> const empty_;
};
BasicBlockVector* Scheduler::ComputeSpecialRPO(Zone* zone, Schedule* schedule) {
SpecialRPONumberer numberer(zone, schedule);
numberer.ComputeSpecialRPO();
numberer.SerializeRPOIntoSchedule();
numberer.PrintAndVerifySpecialRPO();
return schedule->rpo_order();
}
void Scheduler::ComputeSpecialRPONumbering() {
TRACE("--- COMPUTING SPECIAL RPO ----------------------------------\n");
special_rpo_ = zone_->New<SpecialRPONumberer>(zone_, schedule_);
special_rpo_->ComputeSpecialRPO();
}
BasicBlock* Scheduler::GetCommonDominatorIfCached(BasicBlock* b1,
BasicBlock* b2) {
auto entry1 = common_dominator_cache_.find(b1->id().ToInt());
if (entry1 == common_dominator_cache_.end()) return nullptr;
auto entry2 = entry1->second->find(b2->id().ToInt());
if (entry2 == entry1->second->end()) return nullptr;
return entry2->second;
}
BasicBlock* Scheduler::GetCommonDominator(BasicBlock* b1, BasicBlock* b2) {
if (b1 == b2) return b1;
constexpr int kCacheGranularity = 63;
static_assert((kCacheGranularity & (kCacheGranularity + 1)) == 0);
int depth_difference = b1->dominator_depth() - b2->dominator_depth();
if (depth_difference > -kCacheGranularity &&
depth_difference < kCacheGranularity) {
for (int i = 0; i < kCacheGranularity; i++) {
if (b1->dominator_depth() < b2->dominator_depth()) {
b2 = b2->dominator();
} else {
b1 = b1->dominator();
}
if (b1 == b2) return b1;
}
}
if (b1->dominator_depth() < b2->dominator_depth()) std::swap(b1, b2);
while ((b1->dominator_depth() & kCacheGranularity) != 0) {
if (V8_LIKELY(b1->dominator_depth() > b2->dominator_depth())) {
b1 = b1->dominator();
} else {
b2 = b2->dominator();
}
if (b1 == b2) return b1;
}
constexpr int kMaxNewCacheEntries = 2 * 50;
int new_cache_entries[kMaxNewCacheEntries];
int new_cache_entries_cursor = 0;
while (b1 != b2) {
if ((b1->dominator_depth() & kCacheGranularity) == 0) {
BasicBlock* maybe_cache_hit = GetCommonDominatorIfCached(b1, b2);
if (maybe_cache_hit != nullptr) {
b1 = b2 = maybe_cache_hit;
break;
} else if (new_cache_entries_cursor < kMaxNewCacheEntries) {
new_cache_entries[new_cache_entries_cursor++] = b1->id().ToInt();
new_cache_entries[new_cache_entries_cursor++] = b2->id().ToInt();
}
}
if (V8_LIKELY(b1->dominator_depth() > b2->dominator_depth())) {
b1 = b1->dominator();
} else {
b2 = b2->dominator();
}
}
BasicBlock* result = b1;
for (int i = 0; i < new_cache_entries_cursor;) {
int id1 = new_cache_entries[i++];
int id2 = new_cache_entries[i++];
ZoneMap<int, BasicBlock*>* mapping;
auto entry = common_dominator_cache_.find(id1);
if (entry == common_dominator_cache_.end()) {
mapping = zone_->New<ZoneMap<int, BasicBlock*>>(zone_);
common_dominator_cache_[id1] = mapping;
} else {
mapping = entry->second;
}
DCHECK_EQ(mapping->find(id2), mapping->end());
mapping->insert({id2, result});
}
return result;
}
void Scheduler::PropagateImmediateDominators(BasicBlock* block) {
for (; block != nullptr; block = block->rpo_next()) {
auto pred = block->predecessors().begin();
auto end = block->predecessors().end();
DCHECK(pred != end);
BasicBlock* dominator = *pred;
bool deferred = dominator->deferred();
BasicBlock* cache = nullptr;
for (++pred; pred != end; ++pred) {
if ((*pred)->dominator_depth() < 0) continue;
if ((*pred)->dominator_depth() > 3 &&
((*pred)->dominator()->dominator() == cache ||
(*pred)->dominator()->dominator()->dominator() == cache)) {
DCHECK_EQ(dominator, BasicBlock::GetCommonDominator(dominator, *pred));
} else {
dominator = BasicBlock::GetCommonDominator(dominator, *pred);
}
cache = (*pred)->dominator();
deferred = deferred & (*pred)->deferred();
}
block->set_dominator(dominator);
block->set_dominator_depth(dominator->dominator_depth() + 1);
block->set_deferred(deferred | block->deferred());
TRACE("Block id:%d's idom is id:%d, depth = %d\n", block->id().ToInt(),
dominator->id().ToInt(), block->dominator_depth());
}
}
void Scheduler::GenerateDominatorTree(Schedule* schedule) {
schedule->start()->set_dominator_depth(0);
PropagateImmediateDominators(schedule->start()->rpo_next());
}
void Scheduler::GenerateDominatorTree() {
TRACE("--- IMMEDIATE BLOCK DOMINATORS -----------------------------\n");
GenerateDominatorTree(schedule_);
}
class PrepareUsesVisitor {
public:
explicit PrepareUsesVisitor(Scheduler* scheduler, TFGraph* graph, Zone* zone)
: scheduler_(scheduler),
schedule_(scheduler->schedule_),
graph_(graph),
visited_(static_cast<int>(graph_->NodeCount()), zone),
stack_(zone) {}
void Run() {
InitializePlacement(graph_->end());
while (!stack_.empty()) {
Node* node = stack_.top();
stack_.pop();
VisitInputs(node);
}
}
private:
void InitializePlacement(Node* node) {
TRACE("Pre #%d:%s\n", node->id(), node->op()->mnemonic());
DCHECK(!Visited(node));
if (scheduler_->InitializePlacement(node) == Scheduler::kFixed) {
scheduler_->schedule_root_nodes_.push_back(node);
if (!schedule_->IsScheduled(node)) {
TRACE("Scheduling fixed position node #%d:%s\n", node->id(),
node->op()->mnemonic());
IrOpcode::Value opcode = node->opcode();
BasicBlock* block =
opcode == IrOpcode::kParameter
? schedule_->start()
: schedule_->block(NodeProperties::GetControlInput(node));
DCHECK_NOT_NULL(block);
schedule_->AddNode(block, node);
}
}
stack_.push(node);
visited_.Add(node->id());
}
void VisitInputs(Node* node) {
DCHECK_NE(scheduler_->GetPlacement(node), Scheduler::kUnknown);
bool is_scheduled = schedule_->IsScheduled(node);
std::optional<int> coupled_control_edge =
scheduler_->GetCoupledControlEdge(node);
for (auto edge : node->input_edges()) {
Node* to = edge.to();
DCHECK_EQ(node, edge.from());
if (!Visited(to)) {
InitializePlacement(to);
}
TRACE("PostEdge #%d:%s->#%d:%s\n", node->id(), node->op()->mnemonic(),
to->id(), to->op()->mnemonic());
DCHECK_NE(scheduler_->GetPlacement(to), Scheduler::kUnknown);
if (!is_scheduled && edge.index() != coupled_control_edge) {
scheduler_->IncrementUnscheduledUseCount(to, node);
}
}
}
bool Visited(Node* node) { return visited_.Contains(node->id()); }
Scheduler* scheduler_;
Schedule* schedule_;
TFGraph* graph_;
BitVector visited_;
ZoneStack<Node*> stack_;
};
void Scheduler::PrepareUses() {
TRACE("--- PREPARE USES -------------------------------------------\n");
PrepareUsesVisitor prepare_uses(this, graph_, zone_);
prepare_uses.Run();
}
class ScheduleEarlyNodeVisitor {
public:
ScheduleEarlyNodeVisitor(Zone* zone, Scheduler* scheduler)
: scheduler_(scheduler), schedule_(scheduler->schedule_), queue_(zone) {}
void Run(NodeVector* roots) {
for (Node* const root : *roots) {
queue_.push(root);
}
while (!queue_.empty()) {
scheduler_->tick_counter_->TickAndMaybeEnterSafepoint();
VisitNode(queue_.front());
queue_.pop();
}
}
private:
void VisitNode(Node* node) {
Scheduler::SchedulerData* data = scheduler_->GetData(node);
if (scheduler_->GetPlacement(node) == Scheduler::kFixed) {
data->minimum_block_ = schedule_->block(node);
TRACE("Fixing #%d:%s minimum_block = id:%d, dominator_depth = %d\n",
node->id(), node->op()->mnemonic(),
data->minimum_block_->id().ToInt(),
data->minimum_block_->dominator_depth());
}
if (data->minimum_block_ == schedule_->start()) return;
DCHECK_NOT_NULL(data->minimum_block_);
for (auto use : node->uses()) {
if (scheduler_->IsLive(use)) {
PropagateMinimumPositionToNode(data->minimum_block_, use);
}
}
}
void PropagateMinimumPositionToNode(BasicBlock* block, Node* node) {
Scheduler::SchedulerData* data = scheduler_->GetData(node);
if (scheduler_->GetPlacement(node) == Scheduler::kFixed) return;
if (scheduler_->GetPlacement(node) == Scheduler::kCoupled) {
Node* control = NodeProperties::GetControlInput(node);
PropagateMinimumPositionToNode(block, control);
}
DCHECK(InsideSameDominatorChain(block, data->minimum_block_));
if (block->dominator_depth() > data->minimum_block_->dominator_depth()) {
data->minimum_block_ = block;
queue_.push(node);
TRACE("Propagating #%d:%s minimum_block = id:%d, dominator_depth = %d\n",
node->id(), node->op()->mnemonic(),
data->minimum_block_->id().ToInt(),
data->minimum_block_->dominator_depth());
}
}
#if DEBUG
bool InsideSameDominatorChain(BasicBlock* b1, BasicBlock* b2) {
BasicBlock* dominator = BasicBlock::GetCommonDominator(b1, b2);
return dominator == b1 || dominator == b2;
}
#endif
Scheduler* scheduler_;
Schedule* schedule_;
ZoneQueue<Node*> queue_;
};
void Scheduler::ScheduleEarly() {
if (!special_rpo_->HasLoopBlocks()) {
TRACE("--- NO LOOPS SO SKIPPING SCHEDULE EARLY --------------------\n");
return;
}
TRACE("--- SCHEDULE EARLY -----------------------------------------\n");
if (v8_flags.trace_turbo_scheduler) {
TRACE("roots: ");
for (Node* node : schedule_root_nodes_) {
TRACE("#%d:%s ", node->id(), node->op()->mnemonic());
}
TRACE("\n");
}
ScheduleEarlyNodeVisitor schedule_early_visitor(zone_, this);
schedule_early_visitor.Run(&schedule_root_nodes_);
}
class ScheduleLateNodeVisitor {
public:
ScheduleLateNodeVisitor(Zone* zone, Scheduler* scheduler)
: zone_(zone),
scheduler_(scheduler),
schedule_(scheduler_->schedule_),
marking_queue_(scheduler->zone_) {}
void Run(NodeVector* roots) {
for (Node* const root : *roots) {
ProcessQueue(root);
}
}
private:
void ProcessQueue(Node* root) {
ZoneQueue<Node*>* queue = &(scheduler_->schedule_queue_);
for (Node* node : root->inputs()) {
if (scheduler_->GetPlacement(node) == Scheduler::kCoupled) {
node = NodeProperties::GetControlInput(node);
}
if (scheduler_->GetData(node)->unscheduled_count_ != 0) continue;
queue->push(node);
do {
scheduler_->tick_counter_->TickAndMaybeEnterSafepoint();
Node* const n = queue->front();
queue->pop();
VisitNode(n);
} while (!queue->empty());
}
}
void VisitNode(Node* node) {
DCHECK_EQ(0, scheduler_->GetData(node)->unscheduled_count_);
if (schedule_->IsScheduled(node)) return;
DCHECK_EQ(Scheduler::kSchedulable, scheduler_->GetPlacement(node));
TRACE("Scheduling #%d:%s\n", node->id(), node->op()->mnemonic());
BasicBlock* block = GetCommonDominatorOfUses(node);
DCHECK_NOT_NULL(block);
BasicBlock* min_block = scheduler_->GetData(node)->minimum_block_;
DCHECK_EQ(min_block, BasicBlock::GetCommonDominator(block, min_block));
TRACE(
"Schedule late of #%d:%s is id:%d at loop depth %d, minimum = id:%d\n",
node->id(), node->op()->mnemonic(), block->id().ToInt(),
block->loop_depth(), min_block->id().ToInt());
BasicBlock* hoist_block = GetHoistBlock(block);
if (hoist_block &&
hoist_block->dominator_depth() >= min_block->dominator_depth()) {
DCHECK(scheduler_->special_rpo_->HasLoopBlocks());
do {
TRACE(" hoisting #%d:%s to block id:%d\n", node->id(),
node->op()->mnemonic(), hoist_block->id().ToInt());
DCHECK_LT(hoist_block->loop_depth(), block->loop_depth());
block = hoist_block;
hoist_block = GetHoistBlock(hoist_block);
} while (hoist_block &&
hoist_block->dominator_depth() >= min_block->dominator_depth());
} else if (scheduler_->flags_ & Scheduler::kSplitNodes) {
block = SplitNode(block, node);
}
if (IrOpcode::IsMergeOpcode(node->opcode())) {
ScheduleFloatingControl(block, node);
} else if (node->opcode() == IrOpcode::kFinishRegion) {
ScheduleRegion(block, node);
} else {
ScheduleNode(block, node);
}
}
bool IsMarked(BasicBlock* block) const {
return marked_.Contains(block->id().ToInt());
}
void Mark(BasicBlock* block) { marked_.Add(block->id().ToInt()); }
void MarkBlock(BasicBlock* block) {
Mark(block);
for (BasicBlock* pred_block : block->predecessors()) {
if (IsMarked(pred_block)) continue;
marking_queue_.push_back(pred_block);
}
}
BasicBlock* SplitNode(BasicBlock* block, Node* node) {
if (!node->op()->HasProperty(Operator::kPure)) return block;
if (node->opcode() == IrOpcode::kProjection) return block;
DCHECK_EQ(block, GetCommonDominatorOfUses(node));
if (block->SuccessorCount() < 2) return block;
DCHECK(marking_queue_.empty());
marked_.Clear();
int new_size = static_cast<int>(schedule_->BasicBlockCount() + 1);
if (marked_.length() < new_size) {
marked_.Resize(new_size, scheduler_->zone_);
}
for (Edge edge : node->use_edges()) {
if (!scheduler_->IsLive(edge.from())) continue;
BasicBlock* use_block = GetBlockForUse(edge);
if (use_block == nullptr || IsMarked(use_block)) continue;
if (use_block == block) {
TRACE(" not splitting #%d:%s, it is used in id:%d\n", node->id(),
node->op()->mnemonic(), block->id().ToInt());
marking_queue_.clear();
return block;
}
MarkBlock(use_block);
}
do {
BasicBlock* top_block = marking_queue_.front();
marking_queue_.pop_front();
if (IsMarked(top_block)) continue;
bool marked = true;
if (top_block->loop_depth() == block->loop_depth()) {
for (BasicBlock* successor : top_block->successors()) {
if (!IsMarked(successor)) {
marked = false;
break;
}
}
}
if (marked) MarkBlock(top_block);
} while (!marking_queue_.empty());
if (IsMarked(block)) {
TRACE(" not splitting #%d:%s, its common dominator id:%d is perfect\n",
node->id(), node->op()->mnemonic(), block->id().ToInt());
return block;
}
ZoneMap<BasicBlock*, Node*> dominators(scheduler_->zone_);
for (Edge edge : node->use_edges()) {
if (!scheduler_->IsLive(edge.from())) continue;
BasicBlock* use_block = GetBlockForUse(edge);
if (use_block == nullptr) continue;
while (IsMarked(use_block->dominator())) {
use_block = use_block->dominator();
}
auto& use_node = dominators[use_block];
if (use_node == nullptr) {
if (dominators.size() == 1u) {
block = use_block;
use_node = node;
TRACE(" pushing #%d:%s down to id:%d\n", node->id(),
node->op()->mnemonic(), block->id().ToInt());
} else {
use_node = CloneNode(node);
TRACE(" cloning #%d:%s for id:%d\n", use_node->id(),
use_node->op()->mnemonic(), use_block->id().ToInt());
scheduler_->schedule_queue_.push(use_node);
}
}
edge.UpdateTo(use_node);
}
return block;
}
BasicBlock* GetHoistBlock(BasicBlock* block) {
if (!scheduler_->special_rpo_->HasLoopBlocks()) return nullptr;
if (block->IsLoopHeader()) return block->dominator();
if (BasicBlock* header_block = block->loop_header()) {
for (BasicBlock* outgoing_block :
scheduler_->special_rpo_->GetOutgoingBlocks(header_block)) {
if (scheduler_->GetCommonDominator(block, outgoing_block) != block) {
return nullptr;
}
}
return header_block->dominator();
}
return nullptr;
}
BasicBlock* GetCommonDominatorOfUses(Node* node) {
BasicBlock* block = nullptr;
for (Edge edge : node->use_edges()) {
if (!scheduler_->IsLive(edge.from())) continue;
BasicBlock* use_block = GetBlockForUse(edge);
block = block == nullptr
? use_block
: use_block == nullptr
? block
: scheduler_->GetCommonDominator(block, use_block);
}
return block;
}
BasicBlock* FindPredecessorBlock(Node* node) {
return scheduler_->control_flow_builder_->FindPredecessorBlock(node);
}
BasicBlock* GetBlockForUse(Edge edge) {
Node* use = edge.from();
if (IrOpcode::IsPhiOpcode(use->opcode())) {
if (scheduler_->GetPlacement(use) == Scheduler::kCoupled) {
TRACE(" inspecting uses of coupled #%d:%s\n", use->id(),
use->op()->mnemonic());
return GetCommonDominatorOfUses(use);
}
if (scheduler_->GetPlacement(use) == Scheduler::kFixed) {
TRACE(" input@%d into a fixed phi #%d:%s\n", edge.index(), use->id(),
use->op()->mnemonic());
Node* merge = NodeProperties::GetControlInput(use, 0);
DCHECK(IrOpcode::IsMergeOpcode(merge->opcode()));
Node* input = NodeProperties::GetControlInput(merge, edge.index());
return FindPredecessorBlock(input);
}
} else if (IrOpcode::IsMergeOpcode(use->opcode())) {
if (scheduler_->GetPlacement(use) == Scheduler::kFixed) {
TRACE(" input@%d into a fixed merge #%d:%s\n", edge.index(), use->id(),
use->op()->mnemonic());
return FindPredecessorBlock(edge.to());
}
}
BasicBlock* result = schedule_->block(use);
if (result == nullptr) return nullptr;
TRACE(" must dominate use #%d:%s in id:%d\n", use->id(),
use->op()->mnemonic(), result->id().ToInt());
return result;
}
void ScheduleFloatingControl(BasicBlock* block, Node* node) {
scheduler_->FuseFloatingControl(block, node);
}
void ScheduleRegion(BasicBlock* block, Node* region_end) {
CHECK_EQ(IrOpcode::kFinishRegion, region_end->opcode());
ScheduleNode(block, region_end);
Node* node = NodeProperties::GetEffectInput(region_end);
while (node->opcode() != IrOpcode::kBeginRegion) {
DCHECK_EQ(0, scheduler_->GetData(node)->unscheduled_count_);
DCHECK_EQ(1, node->op()->EffectInputCount());
DCHECK_EQ(1, node->op()->EffectOutputCount());
DCHECK_EQ(0, node->op()->ControlOutputCount());
DCHECK(node->op()->ValueOutputCount() == 0 ||
node == region_end->InputAt(0));
ScheduleNode(block, node);
node = NodeProperties::GetEffectInput(node);
}
DCHECK_EQ(0, scheduler_->GetData(node)->unscheduled_count_);
ScheduleNode(block, node);
}
void ScheduleNode(BasicBlock* block, Node* node) {
schedule_->PlanNode(block, node);
size_t block_id = block->id().ToSize();
if (!scheduler_->scheduled_nodes_[block_id]) {
scheduler_->scheduled_nodes_[block_id] = zone_->New<NodeVector>(zone_);
}
scheduler_->scheduled_nodes_[block_id]->push_back(node);
scheduler_->UpdatePlacement(node, Scheduler::kScheduled);
}
Node* CloneNode(Node* node) {
int const input_count = node->InputCount();
std::optional<int> coupled_control_edge =
scheduler_->GetCoupledControlEdge(node);
for (int index = 0; index < input_count; ++index) {
if (index != coupled_control_edge) {
Node* const input = node->InputAt(index);
scheduler_->IncrementUnscheduledUseCount(input, node);
}
}
Node* const copy = scheduler_->graph_->CloneNode(node);
TRACE(("clone #%d:%s -> #%d\n"), node->id(), node->op()->mnemonic(),
copy->id());
scheduler_->node_data_.resize(copy->id() + 1,
scheduler_->DefaultSchedulerData());
scheduler_->node_data_[copy->id()] = scheduler_->node_data_[node->id()];
return copy;
}
Zone* zone_;
Scheduler* scheduler_;
Schedule* schedule_;
BitVector marked_;
ZoneDeque<BasicBlock*> marking_queue_;
};
void Scheduler::ScheduleLate() {
TRACE("--- SCHEDULE LATE ------------------------------------------\n");
if (v8_flags.trace_turbo_scheduler) {
TRACE("roots: ");
for (Node* node : schedule_root_nodes_) {
TRACE("#%d:%s ", node->id(), node->op()->mnemonic());
}
TRACE("\n");
}
ScheduleLateNodeVisitor schedule_late_visitor(zone_, this);
schedule_late_visitor.Run(&schedule_root_nodes_);
}
void Scheduler::SealFinalSchedule() {
TRACE("--- SEAL FINAL SCHEDULE ------------------------------------\n");
special_rpo_->SerializeRPOIntoSchedule();
special_rpo_->PrintAndVerifySpecialRPO();
int block_num = 0;
for (NodeVector* nodes : scheduled_nodes_) {
BasicBlock::Id id = BasicBlock::Id::FromInt(block_num++);
BasicBlock* block = schedule_->GetBlockById(id);
if (nodes) {
for (Node* node : base::Reversed(*nodes)) {
schedule_->AddNode(block, node);
}
}
}
#ifdef LOG_BUILTIN_BLOCK_COUNT
if (const ProfileDataFromFile* profile_data = this->profile_data()) {
for (BasicBlock* block : *schedule_->all_blocks()) {
uint64_t executed_count =
profile_data->GetExecutedCount(block->id().ToSize());
block->set_pgo_execution_count(executed_count);
}
}
#endif
}
void Scheduler::FuseFloatingControl(BasicBlock* block, Node* node) {
TRACE("--- FUSE FLOATING CONTROL ----------------------------------\n");
if (v8_flags.trace_turbo_scheduler) {
StdoutStream{} << "Schedule before control flow fusion:\n" << *schedule_;
}
control_flow_builder_->Run(block, node);
special_rpo_->UpdateSpecialRPO(block, schedule_->block(node));
for (BasicBlock* b = block->rpo_next(); b != nullptr; b = b->rpo_next()) {
b->set_dominator_depth(-1);
b->set_dominator(nullptr);
}
PropagateImmediateDominators(block->rpo_next());
NodeVector propagation_roots(control_flow_builder_->control_);
for (Node* control : control_flow_builder_->control_) {
for (Node* use : control->uses()) {
if (NodeProperties::IsPhi(use) && IsLive(use)) {
propagation_roots.push_back(use);
}
}
}
if (v8_flags.trace_turbo_scheduler) {
TRACE("propagation roots: ");
for (Node* r : propagation_roots) {
TRACE("#%d:%s ", r->id(), r->op()->mnemonic());
}
TRACE("\n");
}
ScheduleEarlyNodeVisitor schedule_early_visitor(zone_, this);
schedule_early_visitor.Run(&propagation_roots);
scheduled_nodes_.resize(schedule_->BasicBlockCount());
MovePlannedNodes(block, schedule_->block(node));
if (v8_flags.trace_turbo_scheduler) {
StdoutStream{} << "Schedule after control flow fusion:\n" << *schedule_;
}
}
void Scheduler::MovePlannedNodes(BasicBlock* from, BasicBlock* to) {
TRACE("Move planned nodes from id:%d to id:%d\n", from->id().ToInt(),
to->id().ToInt());
NodeVector* from_nodes = scheduled_nodes_[from->id().ToSize()];
NodeVector* to_nodes = scheduled_nodes_[to->id().ToSize()];
if (!from_nodes) return;
for (Node* const node : *from_nodes) {
schedule_->SetBlockForNode(to, node);
}
if (to_nodes) {
to_nodes->insert(to_nodes->end(), from_nodes->begin(), from_nodes->end());
from_nodes->clear();
} else {
std::swap(scheduled_nodes_[from->id().ToSize()],
scheduled_nodes_[to->id().ToSize()]);
}
}
#undef TRACE
}
}
}