blob: d5e6a82fd47cee8447395ea9e7aeaa48924cb717 [file] [log] [blame]
// Copyright 2022 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.
#include "src/maglev/maglev-graph-printer.h"
#include <initializer_list>
#include <iomanip>
#include <ostream>
#include <type_traits>
#include <vector>
#include "src/base/logging.h"
#include "src/maglev/maglev-basic-block.h"
#include "src/maglev/maglev-graph-labeller.h"
#include "src/maglev/maglev-graph-processor.h"
#include "src/maglev/maglev-graph.h"
#include "src/maglev/maglev-ir.h"
#include "src/utils/utils.h"
namespace v8 {
namespace internal {
namespace maglev {
namespace {
int IntWidth(int val) { return std::ceil(std::log10(val + 1)); }
int MaxIdWidth(MaglevGraphLabeller* graph_labeller, NodeIdT max_node_id,
int padding_adjustement = 0) {
int max_width = IntWidth(graph_labeller->max_node_id());
if (max_node_id != kInvalidNodeId) {
max_width += IntWidth(max_node_id) + 1;
}
return max_width + 2 + padding_adjustement;
}
void PrintPaddedId(std::ostream& os, MaglevGraphLabeller* graph_labeller,
NodeIdT max_node_id, NodeBase* node,
std::string padding = " ", int padding_adjustement = 0) {
int id = graph_labeller->NodeId(node);
int id_width = IntWidth(id);
int other_id_width = node->has_id() ? 1 + IntWidth(node->id()) : 0;
int max_width = MaxIdWidth(graph_labeller, max_node_id, padding_adjustement);
int padding_width = std::max(0, max_width - id_width - other_id_width);
for (int i = 0; i < padding_width; ++i) {
os << padding;
}
if (v8_flags.log_colour) os << "\033[0m";
if (node->has_id()) {
os << node->id() << "/";
}
os << graph_labeller->NodeId(node) << ": ";
}
void PrintPadding(std::ostream& os, int size) {
os << std::setfill(' ') << std::setw(size) << "";
}
void PrintPadding(std::ostream& os, MaglevGraphLabeller* graph_labeller,
NodeIdT max_node_id, int padding_adjustement) {
PrintPadding(os,
MaxIdWidth(graph_labeller, max_node_id, padding_adjustement));
}
enum ConnectionLocation {
kTop = 1 << 0,
kLeft = 1 << 1,
kRight = 1 << 2,
kBottom = 1 << 3
};
struct Connection {
void Connect(ConnectionLocation loc) { connected |= loc; }
void AddHorizontal() {
Connect(kLeft);
Connect(kRight);
}
void AddVertical() {
Connect(kTop);
Connect(kBottom);
}
const char* ToString() const {
switch (connected) {
case 0:
return " ";
case kTop:
return "╵";
case kLeft:
return "╴";
case kRight:
return "╶";
case kBottom:
return "╷";
case kTop | kLeft:
return "╯";
case kTop | kRight:
return "╰";
case kBottom | kLeft:
return "╮";
case kBottom | kRight:
return "╭";
case kTop | kBottom:
return "│";
case kLeft | kRight:
return "─";
case kTop | kBottom | kLeft:
return "┤";
case kTop | kBottom | kRight:
return "├";
case kLeft | kRight | kTop:
return "┴";
case kLeft | kRight | kBottom:
return "┬";
case kTop | kLeft | kRight | kBottom:
return "┼";
}
UNREACHABLE();
}
uint8_t connected = 0;
};
std::ostream& operator<<(std::ostream& os, const Connection& c) {
return os << c.ToString();
}
// Print the vertical parts of connection arrows, optionally connecting arrows
// that were only first created on this line (passed in "arrows_starting_here")
// and should therefore connect rightwards instead of upwards.
void PrintVerticalArrows(std::ostream& os,
const std::vector<BasicBlock*>& targets,
std::set<size_t> arrows_starting_here = {},
std::set<BasicBlock*> targets_starting_here = {},
bool is_loop = false) {
bool saw_start = false;
int line_color = -1;
int current_color = -1;
for (size_t i = 0; i < targets.size(); ++i) {
int desired_color = line_color;
Connection c;
if (saw_start) {
c.AddHorizontal();
}
if (arrows_starting_here.find(i) != arrows_starting_here.end() ||
targets_starting_here.find(targets[i]) != targets_starting_here.end()) {
desired_color = (i % 6) + 1;
line_color = desired_color;
c.Connect(kRight);
c.Connect(is_loop ? kTop : kBottom);
saw_start = true;
}
// Only add the vertical connection if there was no other connection.
if (c.connected == 0 && targets[i] != nullptr) {
desired_color = (i % 6) + 1;
c.AddVertical();
}
if (v8_flags.log_colour && desired_color != current_color &&
desired_color != -1) {
os << "\033[0;3" << desired_color << "m";
current_color = desired_color;
}
os << c;
}
// If there are no arrows starting here, clear the color. Otherwise,
// PrintPaddedId will clear it.
if (v8_flags.log_colour && arrows_starting_here.empty() &&
targets_starting_here.empty()) {
os << "\033[0m";
}
}
// Add a target to the target list in the first non-null position from the end.
// This might have to extend the target list if there is no free spot.
size_t AddTarget(std::vector<BasicBlock*>& targets, BasicBlock* target) {
if (targets.size() == 0 || targets.back() != nullptr) {
targets.push_back(target);
return targets.size() - 1;
}
size_t i = targets.size();
while (i > 0) {
if (targets[i - 1] != nullptr) break;
i--;
}
targets[i] = target;
return i;
}
// If the target is not a fallthrough, add i to the target list in the first
// non-null position from the end. This might have to extend the target list if
// there is no free spot. Returns true if it was added, false if it was a
// fallthrough.
bool AddTargetIfNotNext(std::vector<BasicBlock*>& targets, BasicBlock* target,
BasicBlock* next_block,
std::set<size_t>* arrows_starting_here = nullptr) {
if (next_block == target) return false;
size_t index = AddTarget(targets, target);
if (arrows_starting_here != nullptr) arrows_starting_here->insert(index);
return true;
}
class MaglevPrintingVisitorOstream : public std::ostream,
private std::streambuf {
public:
MaglevPrintingVisitorOstream(std::ostream& os,
std::vector<BasicBlock*>* targets)
: std::ostream(this), os_(os), targets_(targets), padding_size_(0) {}
~MaglevPrintingVisitorOstream() override = default;
static MaglevPrintingVisitorOstream* cast(
const std::unique_ptr<std::ostream>& os) {
return static_cast<MaglevPrintingVisitorOstream*>(os.get());
}
void set_padding(int padding_size) { padding_size_ = padding_size; }
protected:
int overflow(int c) override;
private:
std::ostream& os_;
std::vector<BasicBlock*>* targets_;
int padding_size_;
bool previous_was_new_line_ = true;
};
int MaglevPrintingVisitorOstream::overflow(int c) {
if (c == EOF) return c;
if (previous_was_new_line_) {
PrintVerticalArrows(os_, *targets_);
PrintPadding(os_, padding_size_);
}
os_.rdbuf()->sputc(c);
previous_was_new_line_ = (c == '\n');
return c;
}
} // namespace
MaglevPrintingVisitor::MaglevPrintingVisitor(
MaglevGraphLabeller* graph_labeller, std::ostream& os)
: graph_labeller_(graph_labeller),
os_(os),
os_for_additional_info_(
new MaglevPrintingVisitorOstream(os_, &targets_)) {}
void MaglevPrintingVisitor::PreProcessGraph(Graph* graph) {
os_ << "Graph\n\n";
for (BasicBlock* block : *graph) {
if (block->control_node()->Is<JumpLoop>()) {
loop_headers_.insert(block->control_node()->Cast<JumpLoop>()->target());
}
if (max_node_id_ == kInvalidNodeId) {
if (block->control_node()->has_id()) {
max_node_id_ = block->control_node()->id();
}
} else {
max_node_id_ = std::max(max_node_id_, block->control_node()->id());
}
}
// Precalculate the maximum number of targets.
for (BlockConstIterator block_it = graph->begin(); block_it != graph->end();
++block_it) {
BasicBlock* block = *block_it;
std::replace(targets_.begin(), targets_.end(), block,
static_cast<BasicBlock*>(nullptr));
if (loop_headers_.find(block) != loop_headers_.end()) {
AddTarget(targets_, block);
}
ControlNode* node = block->control_node();
if (node->Is<JumpLoop>()) {
BasicBlock* target = node->Cast<JumpLoop>()->target();
std::replace(targets_.begin(), targets_.end(), target,
static_cast<BasicBlock*>(nullptr));
} else if (node->Is<UnconditionalControlNode>()) {
AddTargetIfNotNext(targets_,
node->Cast<UnconditionalControlNode>()->target(),
*(block_it + 1));
} else if (node->Is<BranchControlNode>()) {
AddTargetIfNotNext(targets_, node->Cast<BranchControlNode>()->if_true(),
*(block_it + 1));
AddTargetIfNotNext(targets_, node->Cast<BranchControlNode>()->if_false(),
*(block_it + 1));
} else if (node->Is<Switch>()) {
for (int i = 0; i < node->Cast<Switch>()->size(); i++) {
const BasicBlockRef& target = node->Cast<Switch>()->targets()[i];
AddTargetIfNotNext(targets_, target.block_ptr(), *(block_it + 1));
}
if (node->Cast<Switch>()->has_fallthrough()) {
BasicBlock* fallthrough_target = node->Cast<Switch>()->fallthrough();
AddTargetIfNotNext(targets_, fallthrough_target, *(block_it + 1));
}
}
}
DCHECK(std::all_of(targets_.begin(), targets_.end(),
[](BasicBlock* block) { return block == nullptr; }));
}
void MaglevPrintingVisitor::PreProcessBasicBlock(BasicBlock* block) {
size_t loop_position = static_cast<size_t>(-1);
if (loop_headers_.erase(block) > 0) {
loop_position = AddTarget(targets_, block);
}
{
bool saw_start = false;
int current_color = -1;
int line_color = -1;
for (size_t i = 0; i < targets_.size(); ++i) {
int desired_color = line_color;
Connection c;
if (saw_start) {
c.AddHorizontal();
}
// If this is one of the arrows pointing to this block, terminate the
// line by connecting it rightwards.
if (targets_[i] == block) {
// Update the color of the line.
desired_color = (i % 6) + 1;
line_color = desired_color;
c.Connect(kRight);
// If this is the loop header, go down instead of up and don't clear
// the target.
if (i == loop_position) {
c.Connect(kBottom);
} else {
c.Connect(kTop);
targets_[i] = nullptr;
}
saw_start = true;
} else if (c.connected == 0 && targets_[i] != nullptr) {
// If this is another arrow, connect it, but only if that doesn't
// clobber any existing drawing. Set the current color, but don't update
// the overall color.
desired_color = (i % 6) + 1;
c.AddVertical();
}
if (v8_flags.log_colour && current_color != desired_color &&
desired_color != -1) {
os_ << "\033[0;3" << desired_color << "m";
current_color = desired_color;
}
os_ << c;
}
os_ << (saw_start ? "►" : " ");
if (v8_flags.log_colour) os_ << "\033[0m";
}
int block_id = graph_labeller_->BlockId(block);
os_ << "Block b" << block_id;
if (block->is_exception_handler_block()) {
os_ << " (exception handler)";
}
os_ << "\n";
MaglevPrintingVisitorOstream::cast(os_for_additional_info_)->set_padding(1);
}
namespace {
void PrintSingleDeoptFrame(
std::ostream& os, MaglevGraphLabeller* graph_labeller,
const DeoptFrame& frame, InputLocation*& current_input_location,
LazyDeoptInfo* lazy_deopt_info_if_top_frame = nullptr) {
switch (frame.type()) {
case DeoptFrame::FrameType::kInterpretedFrame: {
os << "@" << frame.as_interpreted().bytecode_position();
if (!v8_flags.print_maglev_deopt_verbose) {
int count = 0;
frame.as_interpreted().frame_state()->ForEachValue(
frame.as_interpreted().unit(),
[&](ValueNode* node, interpreter::Register reg) { count++; });
os << " (" << count << " live vars)";
return;
}
os << " : {";
bool first = true;
frame.as_interpreted().frame_state()->ForEachValue(
frame.as_interpreted().unit(),
[&](ValueNode* node, interpreter::Register reg) {
if (first) {
first = false;
} else {
os << ", ";
}
os << reg.ToString() << ":";
if (lazy_deopt_info_if_top_frame &&
lazy_deopt_info_if_top_frame->IsResultRegister(reg)) {
os << "<result>";
} else {
os << PrintNodeLabel(graph_labeller, node) << ":"
<< current_input_location->operand();
current_input_location++;
}
});
os << "}";
break;
}
case DeoptFrame::FrameType::kConstructStubFrame: {
if (frame.as_construct_stub().bytecode_position() ==
BytecodeOffset::ConstructStubCreate()) {
os << "@ConstructStubCreate";
} else {
os << "@ConstructStubInvoke";
}
if (!v8_flags.print_maglev_deopt_verbose) return;
os << " : {";
auto arguments_without_receiver =
frame.as_construct_stub().arguments_without_receiver();
os << "<this>:"
<< PrintNodeLabel(graph_labeller, frame.as_construct_stub().receiver())
<< ":" << current_input_location->operand();
current_input_location++;
if (arguments_without_receiver.size() > 0) {
os << ", ";
}
for (size_t i = 0; i < arguments_without_receiver.size(); i++) {
os << "a" << i << ":"
<< PrintNodeLabel(graph_labeller, arguments_without_receiver[i])
<< ":" << current_input_location->operand();
current_input_location++;
os << ", ";
}
os << "<context>:"
<< PrintNodeLabel(graph_labeller, frame.as_construct_stub().context())
<< ":" << current_input_location->operand();
current_input_location++;
os << "}";
break;
}
case DeoptFrame::FrameType::kInlinedArgumentsFrame: {
os << "@" << frame.as_inlined_arguments().bytecode_position();
if (!v8_flags.print_maglev_deopt_verbose) return;
os << " : {";
auto arguments = frame.as_inlined_arguments().arguments();
DCHECK_GT(arguments.size(), 0);
os << "<this>:" << PrintNodeLabel(graph_labeller, arguments[0]) << ":"
<< current_input_location->operand();
current_input_location++;
if (arguments.size() > 1) {
os << ", ";
}
for (size_t i = 1; i < arguments.size(); i++) {
os << "a" << (i - 1) << ":"
<< PrintNodeLabel(graph_labeller, arguments[i]) << ":"
<< current_input_location->operand();
current_input_location++;
os << ", ";
}
os << "}";
break;
}
case DeoptFrame::FrameType::kBuiltinContinuationFrame: {
os << "@" << Builtins::name(frame.as_builtin_continuation().builtin_id());
if (!v8_flags.print_maglev_deopt_verbose) return;
os << " : {";
int arg_index = 0;
for (ValueNode* node : frame.as_builtin_continuation().parameters()) {
os << "a" << arg_index << ":" << PrintNodeLabel(graph_labeller, node)
<< ":" << current_input_location->operand();
arg_index++;
current_input_location++;
os << ", ";
}
os << "<context>:"
<< PrintNodeLabel(graph_labeller,
frame.as_builtin_continuation().context())
<< ":" << current_input_location->operand();
current_input_location++;
os << "}";
break;
}
}
}
void RecursivePrintEagerDeopt(std::ostream& os,
std::vector<BasicBlock*> targets,
const DeoptFrame& frame,
MaglevGraphLabeller* graph_labeller,
int max_node_id,
InputLocation*& current_input_location) {
if (frame.parent()) {
RecursivePrintEagerDeopt(os, targets, *frame.parent(), graph_labeller,
max_node_id, current_input_location);
}
PrintVerticalArrows(os, targets);
PrintPadding(os, graph_labeller, max_node_id, 0);
if (!frame.parent()) {
os << " ↱ eager ";
} else {
os << " │ ";
}
PrintSingleDeoptFrame(os, graph_labeller, frame, current_input_location);
os << "\n";
}
void PrintEagerDeopt(std::ostream& os, std::vector<BasicBlock*> targets,
NodeBase* node, MaglevGraphLabeller* graph_labeller,
int max_node_id) {
EagerDeoptInfo* deopt_info = node->eager_deopt_info();
InputLocation* current_input_location = deopt_info->input_locations();
RecursivePrintEagerDeopt(os, targets, deopt_info->top_frame(), graph_labeller,
max_node_id, current_input_location);
}
void MaybePrintEagerDeopt(std::ostream& os, std::vector<BasicBlock*> targets,
NodeBase* node, MaglevGraphLabeller* graph_labeller,
int max_node_id) {
if (node->properties().can_eager_deopt()) {
PrintEagerDeopt(os, targets, node, graph_labeller, max_node_id);
}
}
void RecursivePrintLazyDeopt(std::ostream& os, std::vector<BasicBlock*> targets,
const DeoptFrame& frame,
MaglevGraphLabeller* graph_labeller,
int max_node_id,
InputLocation*& current_input_location) {
if (frame.parent()) {
RecursivePrintLazyDeopt(os, targets, *frame.parent(), graph_labeller,
max_node_id, current_input_location);
}
PrintVerticalArrows(os, targets);
PrintPadding(os, graph_labeller, max_node_id, 0);
os << " │ ";
PrintSingleDeoptFrame(os, graph_labeller, frame, current_input_location);
os << "\n";
}
template <typename NodeT>
void PrintLazyDeopt(std::ostream& os, std::vector<BasicBlock*> targets,
NodeT* node, MaglevGraphLabeller* graph_labeller,
int max_node_id) {
LazyDeoptInfo* deopt_info = node->lazy_deopt_info();
InputLocation* current_input_location = deopt_info->input_locations();
const DeoptFrame& top_frame = deopt_info->top_frame();
if (top_frame.parent()) {
RecursivePrintLazyDeopt(os, targets, *top_frame.parent(), graph_labeller,
max_node_id, current_input_location);
}
PrintVerticalArrows(os, targets);
PrintPadding(os, graph_labeller, max_node_id, 0);
os << " ↳ lazy ";
PrintSingleDeoptFrame(os, graph_labeller, top_frame, current_input_location,
deopt_info);
os << "\n";
}
template <typename NodeT>
void PrintExceptionHandlerPoint(std::ostream& os,
std::vector<BasicBlock*> targets, NodeT* node,
MaglevGraphLabeller* graph_labeller,
int max_node_id) {
// If no handler info, then we cannot throw.
ExceptionHandlerInfo* info = node->exception_handler_info();
if (!info->HasExceptionHandler()) return;
BasicBlock* block = info->catch_block.block_ptr();
DCHECK(block->is_exception_handler_block());
Phi* first_phi = block->phis()->first();
if (first_phi == nullptr) {
// No phis in the block.
return;
}
int handler_offset = first_phi->merge_state()->merge_offset();
// The exception handler liveness should be a subset of lazy_deopt_info one.
auto* liveness = block->state()->frame_state().liveness();
LazyDeoptInfo* deopt_info = node->lazy_deopt_info();
const InterpretedDeoptFrame* lazy_frame;
switch (deopt_info->top_frame().type()) {
case DeoptFrame::FrameType::kInterpretedFrame:
lazy_frame = &deopt_info->top_frame().as_interpreted();
break;
case DeoptFrame::FrameType::kInlinedArgumentsFrame:
UNREACHABLE();
case DeoptFrame::FrameType::kConstructStubFrame:
case DeoptFrame::FrameType::kBuiltinContinuationFrame:
lazy_frame = &deopt_info->top_frame().parent()->as_interpreted();
break;
}
PrintVerticalArrows(os, targets);
PrintPadding(os, graph_labeller, max_node_id, 0);
os << " ↳ throw @" << handler_offset << " : {";
bool first = true;
lazy_frame->as_interpreted().frame_state()->ForEachValue(
lazy_frame->as_interpreted().unit(),
[&](ValueNode* node, interpreter::Register reg) {
if (!reg.is_parameter() && !liveness->RegisterIsLive(reg.index())) {
// Skip, since not live at the handler offset.
return;
}
if (first) {
first = false;
} else {
os << ", ";
}
os << reg.ToString() << ":" << PrintNodeLabel(graph_labeller, node);
});
os << "}\n";
}
void MaybePrintLazyDeoptOrExceptionHandler(std::ostream& os,
std::vector<BasicBlock*> targets,
NodeBase* node,
MaglevGraphLabeller* graph_labeller,
int max_node_id) {
switch (node->opcode()) {
#define CASE(Name) \
case Opcode::k##Name: \
if constexpr (Name::kProperties.can_lazy_deopt()) { \
PrintLazyDeopt<Name>(os, targets, node->Cast<Name>(), graph_labeller, \
max_node_id); \
} \
if constexpr (Name::kProperties.can_throw()) { \
PrintExceptionHandlerPoint<Name>(os, targets, node->Cast<Name>(), \
graph_labeller, max_node_id); \
} \
break;
NODE_BASE_LIST(CASE)
#undef CASE
}
}
} // namespace
ProcessResult MaglevPrintingVisitor::Process(Phi* phi,
const ProcessingState& state) {
PrintVerticalArrows(os_, targets_);
PrintPaddedId(os_, graph_labeller_, max_node_id_, phi);
os_ << "φ";
switch (phi->value_representation()) {
case ValueRepresentation::kTagged:
os_ << "ᵀ";
break;
case ValueRepresentation::kInt32:
os_ << "ᴵ";
break;
case ValueRepresentation::kUint32:
os_ << "ᵁ";
break;
case ValueRepresentation::kFloat64:
os_ << "ᶠ";
break;
case ValueRepresentation::kHoleyFloat64:
os_ << "ʰᶠ";
break;
case ValueRepresentation::kWord64:
UNREACHABLE();
}
if (phi->input_count() == 0) {
os_ << "ₑ " << phi->owner().ToString();
} else {
os_ << " (";
// Manually walk Phi inputs to print just the node labels, without
// input locations (which are shown in the predecessor block's gap
// moves).
for (int i = 0; i < phi->input_count(); ++i) {
if (i > 0) os_ << ", ";
os_ << PrintNodeLabel(graph_labeller_, phi->input(i).node());
}
os_ << ")";
}
if (phi->is_tagged() && !phi->result().operand().IsUnallocated()) {
if (phi->decompresses_tagged_result()) {
os_ << " (decompressed)";
} else {
os_ << " (compressed)";
}
}
os_ << " → " << phi->result().operand();
if (phi->has_valid_live_range()) {
os_ << ", live range: [" << phi->live_range().start << "-"
<< phi->live_range().end << "]";
}
os_ << "\n";
MaglevPrintingVisitorOstream::cast(os_for_additional_info_)
->set_padding(MaxIdWidth(graph_labeller_, max_node_id_, 2));
return ProcessResult::kContinue;
}
ProcessResult MaglevPrintingVisitor::Process(Node* node,
const ProcessingState& state) {
MaybePrintEagerDeopt(os_, targets_, node, graph_labeller_, max_node_id_);
PrintVerticalArrows(os_, targets_);
PrintPaddedId(os_, graph_labeller_, max_node_id_, node);
if (node->properties().is_call()) {
os_ << "🐢 ";
}
os_ << PrintNode(graph_labeller_, node) << "\n";
MaglevPrintingVisitorOstream::cast(os_for_additional_info_)
->set_padding(MaxIdWidth(graph_labeller_, max_node_id_, 2));
MaybePrintLazyDeoptOrExceptionHandler(os_, targets_, node, graph_labeller_,
max_node_id_);
return ProcessResult::kContinue;
}
ProcessResult MaglevPrintingVisitor::Process(ControlNode* control_node,
const ProcessingState& state) {
MaybePrintEagerDeopt(os_, targets_, control_node, graph_labeller_,
max_node_id_);
bool has_fallthrough = false;
if (control_node->Is<JumpLoop>()) {
BasicBlock* target = control_node->Cast<JumpLoop>()->target();
PrintVerticalArrows(os_, targets_, {}, {target}, true);
os_ << "◄─";
PrintPaddedId(os_, graph_labeller_, max_node_id_, control_node, "─", -2);
std::replace(targets_.begin(), targets_.end(), target,
static_cast<BasicBlock*>(nullptr));
} else if (control_node->Is<UnconditionalControlNode>()) {
BasicBlock* target =
control_node->Cast<UnconditionalControlNode>()->target();
std::set<size_t> arrows_starting_here;
has_fallthrough |= !AddTargetIfNotNext(targets_, target, state.next_block(),
&arrows_starting_here);
PrintVerticalArrows(os_, targets_, arrows_starting_here);
PrintPaddedId(os_, graph_labeller_, max_node_id_, control_node,
has_fallthrough ? " " : "─");
} else if (control_node->Is<BranchControlNode>()) {
BasicBlock* true_target =
control_node->Cast<BranchControlNode>()->if_true();
BasicBlock* false_target =
control_node->Cast<BranchControlNode>()->if_false();
std::set<size_t> arrows_starting_here;
has_fallthrough |= !AddTargetIfNotNext(
targets_, false_target, state.next_block(), &arrows_starting_here);
has_fallthrough |= !AddTargetIfNotNext(
targets_, true_target, state.next_block(), &arrows_starting_here);
PrintVerticalArrows(os_, targets_, arrows_starting_here);
PrintPaddedId(os_, graph_labeller_, max_node_id_, control_node, "─");
} else if (control_node->Is<Switch>()) {
std::set<size_t> arrows_starting_here;
for (int i = 0; i < control_node->Cast<Switch>()->size(); i++) {
const BasicBlockRef& target = control_node->Cast<Switch>()->targets()[i];
has_fallthrough |=
!AddTargetIfNotNext(targets_, target.block_ptr(), state.next_block(),
&arrows_starting_here);
}
if (control_node->Cast<Switch>()->has_fallthrough()) {
BasicBlock* fallthrough_target =
control_node->Cast<Switch>()->fallthrough();
has_fallthrough |=
!AddTargetIfNotNext(targets_, fallthrough_target, state.next_block(),
&arrows_starting_here);
}
PrintVerticalArrows(os_, targets_, arrows_starting_here);
PrintPaddedId(os_, graph_labeller_, max_node_id_, control_node, "─");
} else {
PrintVerticalArrows(os_, targets_);
PrintPaddedId(os_, graph_labeller_, max_node_id_, control_node);
}
os_ << PrintNode(graph_labeller_, control_node) << "\n";
bool printed_phis = false;
if (control_node->Is<UnconditionalControlNode>()) {
BasicBlock* target =
control_node->Cast<UnconditionalControlNode>()->target();
if (target->has_phi()) {
printed_phis = true;
PrintVerticalArrows(os_, targets_);
PrintPadding(os_, graph_labeller_, max_node_id_, -1);
os_ << (has_fallthrough ? "│" : " ");
os_ << " with gap moves:\n";
int pid = state.block()->predecessor_id();
for (Phi* phi : *target->phis()) {
PrintVerticalArrows(os_, targets_);
PrintPadding(os_, graph_labeller_, max_node_id_, -1);
os_ << (has_fallthrough ? "│" : " ");
os_ << " - ";
graph_labeller_->PrintInput(os_, phi->input(pid));
os_ << " → " << graph_labeller_->NodeId(phi) << ": φ";
switch (phi->value_representation()) {
case ValueRepresentation::kTagged:
os_ << "ᵀ";
break;
case ValueRepresentation::kInt32:
os_ << "ᴵ";
break;
case ValueRepresentation::kUint32:
os_ << "ᵁ";
break;
case ValueRepresentation::kFloat64:
os_ << "ᶠ";
break;
case ValueRepresentation::kHoleyFloat64:
os_ << "ʰᶠ";
break;
case ValueRepresentation::kWord64:
UNREACHABLE();
}
os_ << " " << phi->result().operand() << "\n";
}
if (target->state()->register_state().is_initialized()) {
PrintVerticalArrows(os_, targets_);
PrintPadding(os_, graph_labeller_, max_node_id_, -1);
os_ << (has_fallthrough ? "│" : " ");
os_ << " with register merges:\n";
auto print_register_merges = [&](auto reg, RegisterState& state) {
ValueNode* node;
RegisterMerge* merge;
if (LoadMergeState(state, &node, &merge)) {
compiler::InstructionOperand source = merge->operand(pid);
PrintVerticalArrows(os_, targets_);
PrintPadding(os_, graph_labeller_, max_node_id_, -1);
os_ << (has_fallthrough ? "│" : " ");
os_ << " - " << source << " → " << reg << "\n";
}
};
target->state()->register_state().ForEachGeneralRegister(
print_register_merges);
target->state()->register_state().ForEachDoubleRegister(
print_register_merges);
}
}
}
PrintVerticalArrows(os_, targets_);
if (has_fallthrough) {
PrintPadding(os_, graph_labeller_, max_node_id_, -1);
if (printed_phis) {
os_ << "▼";
} else {
os_ << "↓";
}
}
os_ << "\n";
// TODO(leszeks): Allow MaglevPrintingVisitorOstream to print the arrowhead
// so that it overlaps the fallthrough arrow.
MaglevPrintingVisitorOstream::cast(os_for_additional_info_)
->set_padding(MaxIdWidth(graph_labeller_, max_node_id_, 2));
return ProcessResult::kContinue;
}
void PrintGraph(std::ostream& os, MaglevCompilationInfo* compilation_info,
Graph* const graph) {
GraphProcessor<MaglevPrintingVisitor, /*visit_identity_nodes*/ true> printer(
compilation_info->graph_labeller(), os);
printer.ProcessGraph(graph);
}
void PrintNode::Print(std::ostream& os) const {
node_->Print(os, graph_labeller_, skip_targets_);
}
std::ostream& operator<<(std::ostream& os, const PrintNode& printer) {
printer.Print(os);
return os;
}
void PrintNodeLabel::Print(std::ostream& os) const {
graph_labeller_->PrintNodeLabel(os, node_);
}
std::ostream& operator<<(std::ostream& os, const PrintNodeLabel& printer) {
printer.Print(os);
return os;
}
} // namespace maglev
} // namespace internal
} // namespace v8