1 // Copyright 2022 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/maglev/maglev-graph-printer.h"
6
7 #include <initializer_list>
8 #include <iomanip>
9 #include <ostream>
10 #include <type_traits>
11 #include <vector>
12
13 #include "src/maglev/maglev-basic-block.h"
14 #include "src/maglev/maglev-graph-labeller.h"
15 #include "src/maglev/maglev-graph-processor.h"
16 #include "src/maglev/maglev-graph.h"
17 #include "src/maglev/maglev-ir.h"
18
19 namespace v8 {
20 namespace internal {
21 namespace maglev {
22
23 namespace {
24
PrintPaddedId(std::ostream & os,MaglevGraphLabeller * graph_labeller,NodeBase * node,std::string padding=" ",int padding_adjustement=0)25 void PrintPaddedId(std::ostream& os, MaglevGraphLabeller* graph_labeller,
26 NodeBase* node, std::string padding = " ",
27 int padding_adjustement = 0) {
28 int id = graph_labeller->NodeId(node);
29 int id_width = std::ceil(std::log10(id + 1));
30 int max_width = graph_labeller->max_node_id_width() + 2 + padding_adjustement;
31 int padding_width = std::max(0, max_width - id_width);
32
33 for (int i = 0; i < padding_width; ++i) {
34 os << padding;
35 }
36 os << graph_labeller->NodeId(node) << ": ";
37 }
38
PrintPadding(std::ostream & os,int size)39 void PrintPadding(std::ostream& os, int size) {
40 os << std::setfill(' ') << std::setw(size) << "";
41 }
42
PrintPadding(std::ostream & os,MaglevGraphLabeller * graph_labeller,int padding_adjustement=0)43 void PrintPadding(std::ostream& os, MaglevGraphLabeller* graph_labeller,
44 int padding_adjustement = 0) {
45 PrintPadding(os,
46 graph_labeller->max_node_id_width() + 2 + padding_adjustement);
47 }
48
49 enum ConnectionLocation {
50 kTop = 1 << 0,
51 kLeft = 1 << 1,
52 kRight = 1 << 2,
53 kBottom = 1 << 3
54 };
55
56 struct Connection {
Connectv8::internal::maglev::__anonfd84d97e0111::Connection57 void Connect(ConnectionLocation loc) { connected |= loc; }
58
AddHorizontalv8::internal::maglev::__anonfd84d97e0111::Connection59 void AddHorizontal() {
60 Connect(kLeft);
61 Connect(kRight);
62 }
63
AddVerticalv8::internal::maglev::__anonfd84d97e0111::Connection64 void AddVertical() {
65 Connect(kTop);
66 Connect(kBottom);
67 }
68
ToStringv8::internal::maglev::__anonfd84d97e0111::Connection69 const char* ToString() const {
70 switch (connected) {
71 case 0:
72 return " ";
73 case kTop:
74 return "╵";
75 case kLeft:
76 return "╴";
77 case kRight:
78 return "╶";
79 case kBottom:
80 return "╷";
81 case kTop | kLeft:
82 return "╯";
83 case kTop | kRight:
84 return "╰";
85 case kBottom | kLeft:
86 return "╮";
87 case kBottom | kRight:
88 return "╭";
89 case kTop | kBottom:
90 return "│";
91 case kLeft | kRight:
92 return "─";
93 case kTop | kBottom | kLeft:
94 return "┤";
95 case kTop | kBottom | kRight:
96 return "├";
97 case kLeft | kRight | kTop:
98 return "┴";
99 case kLeft | kRight | kBottom:
100 return "┬";
101 case kTop | kLeft | kRight | kBottom:
102 return "┼";
103 }
104 UNREACHABLE();
105 }
106
107 uint8_t connected = 0;
108 };
109
operator <<(std::ostream & os,const Connection & c)110 std::ostream& operator<<(std::ostream& os, const Connection& c) {
111 return os << c.ToString();
112 }
113
114 // Print the vertical parts of connection arrows, optionally connecting arrows
115 // that were only first created on this line (passed in "arrows_starting_here")
116 // and should therefore connect rightwards instead of upwards.
PrintVerticalArrows(std::ostream & os,const std::vector<BasicBlock * > & targets,const std::set<size_t> & arrows_starting_here={},const std::set<BasicBlock * > & targets_starting_here={},bool is_loop=false)117 void PrintVerticalArrows(
118 std::ostream& os, const std::vector<BasicBlock*>& targets,
119 const std::set<size_t>& arrows_starting_here = {},
120 const std::set<BasicBlock*>& targets_starting_here = {},
121 bool is_loop = false) {
122 bool saw_start = false;
123 for (size_t i = 0; i < targets.size(); ++i) {
124 Connection c;
125 if (saw_start) {
126 c.AddHorizontal();
127 }
128 if (arrows_starting_here.find(i) != arrows_starting_here.end() ||
129 targets_starting_here.find(targets[i]) != targets_starting_here.end()) {
130 c.Connect(kRight);
131 c.Connect(is_loop ? kTop : kBottom);
132 saw_start = true;
133 }
134
135 // Only add the vertical connection if there was no other connection.
136 if (c.connected == 0 && targets[i] != nullptr) {
137 c.AddVertical();
138 }
139 os << c;
140 }
141 }
142
143 // Add a target to the target list in the first non-null position from the end.
144 // This might have to extend the target list if there is no free spot.
AddTarget(std::vector<BasicBlock * > & targets,BasicBlock * target)145 size_t AddTarget(std::vector<BasicBlock*>& targets, BasicBlock* target) {
146 if (targets.size() == 0 || targets.back() != nullptr) {
147 targets.push_back(target);
148 return targets.size() - 1;
149 }
150
151 size_t i = targets.size();
152 while (i > 0) {
153 if (targets[i - 1] != nullptr) break;
154 i--;
155 }
156 targets[i] = target;
157 return i;
158 }
159
160 // If the target is not a fallthrough, add i to the target list in the first
161 // non-null position from the end. This might have to extend the target list if
162 // there is no free spot. Returns true if it was added, false if it was a
163 // fallthrough.
AddTargetIfNotNext(std::vector<BasicBlock * > & targets,BasicBlock * target,BasicBlock * next_block,std::set<size_t> * arrows_starting_here=nullptr)164 bool AddTargetIfNotNext(std::vector<BasicBlock*>& targets, BasicBlock* target,
165 BasicBlock* next_block,
166 std::set<size_t>* arrows_starting_here = nullptr) {
167 if (next_block == target) return false;
168 size_t index = AddTarget(targets, target);
169 if (arrows_starting_here != nullptr) arrows_starting_here->insert(index);
170 return true;
171 }
172
173 class MaglevPrintingVisitorOstream : public std::ostream,
174 private std::streambuf {
175 public:
MaglevPrintingVisitorOstream(std::ostream & os,std::vector<BasicBlock * > * targets)176 MaglevPrintingVisitorOstream(std::ostream& os,
177 std::vector<BasicBlock*>* targets)
178 : std::ostream(this), os_(os), targets_(targets), padding_size_(0) {}
179 ~MaglevPrintingVisitorOstream() override = default;
180
cast(const std::unique_ptr<std::ostream> & os)181 static MaglevPrintingVisitorOstream* cast(
182 const std::unique_ptr<std::ostream>& os) {
183 return static_cast<MaglevPrintingVisitorOstream*>(os.get());
184 }
185
set_padding(int padding_size)186 void set_padding(int padding_size) { padding_size_ = padding_size; }
187
188 protected:
189 int overflow(int c) override;
190
191 private:
192 std::ostream& os_;
193 std::vector<BasicBlock*>* targets_;
194 int padding_size_;
195 bool previous_was_new_line_ = true;
196 };
197
overflow(int c)198 int MaglevPrintingVisitorOstream::overflow(int c) {
199 if (c == EOF) return c;
200
201 if (previous_was_new_line_) {
202 PrintVerticalArrows(os_, *targets_);
203 PrintPadding(os_, padding_size_);
204 }
205 os_.rdbuf()->sputc(c);
206 previous_was_new_line_ = (c == '\n');
207 return c;
208 }
209
210 } // namespace
211
MaglevPrintingVisitor(std::ostream & os)212 MaglevPrintingVisitor::MaglevPrintingVisitor(std::ostream& os)
213 : os_(os),
214 os_for_additional_info_(
215 new MaglevPrintingVisitorOstream(os_, &targets_)) {}
216
PreProcessGraph(MaglevCompilationUnit * compilation_unit,Graph * graph)217 void MaglevPrintingVisitor::PreProcessGraph(
218 MaglevCompilationUnit* compilation_unit, Graph* graph) {
219 os_ << "Graph (param count: " << compilation_unit->parameter_count()
220 << ", frame size: " << compilation_unit->register_count() << ")\n\n";
221
222 for (BasicBlock* block : *graph) {
223 if (block->control_node()->Is<JumpLoop>()) {
224 loop_headers_.insert(block->control_node()->Cast<JumpLoop>()->target());
225 }
226 }
227
228 // Precalculate the maximum number of targets.
229 for (BlockConstIterator block_it = graph->begin(); block_it != graph->end();
230 ++block_it) {
231 BasicBlock* block = *block_it;
232 std::replace(targets_.begin(), targets_.end(), block,
233 static_cast<BasicBlock*>(nullptr));
234
235 if (loop_headers_.find(block) != loop_headers_.end()) {
236 AddTarget(targets_, block);
237 }
238 ControlNode* node = block->control_node();
239 if (node->Is<JumpLoop>()) {
240 BasicBlock* target = node->Cast<JumpLoop>()->target();
241 std::replace(targets_.begin(), targets_.end(), target,
242 static_cast<BasicBlock*>(nullptr));
243 } else if (node->Is<UnconditionalControlNode>()) {
244 AddTargetIfNotNext(targets_,
245 node->Cast<UnconditionalControlNode>()->target(),
246 *(block_it + 1));
247 } else if (node->Is<ConditionalControlNode>()) {
248 AddTargetIfNotNext(targets_,
249 node->Cast<ConditionalControlNode>()->if_true(),
250 *(block_it + 1));
251 AddTargetIfNotNext(targets_,
252 node->Cast<ConditionalControlNode>()->if_false(),
253 *(block_it + 1));
254 }
255 }
256 DCHECK(std::all_of(targets_.begin(), targets_.end(),
257 [](BasicBlock* block) { return block == nullptr; }));
258 }
259
PreProcessBasicBlock(MaglevCompilationUnit * compilation_unit,BasicBlock * block)260 void MaglevPrintingVisitor::PreProcessBasicBlock(
261 MaglevCompilationUnit* compilation_unit, BasicBlock* block) {
262 MaglevGraphLabeller* graph_labeller = compilation_unit->graph_labeller();
263
264 size_t loop_position = static_cast<size_t>(-1);
265 if (loop_headers_.erase(block) > 0) {
266 loop_position = AddTarget(targets_, block);
267 }
268 {
269 bool saw_start = false;
270 for (size_t i = 0; i < targets_.size(); ++i) {
271 Connection c;
272 if (saw_start) {
273 c.AddHorizontal();
274 }
275 // If this is one of the arrows pointing to this block, terminate the
276 // line by connecting it rightwards.
277 if (targets_[i] == block) {
278 c.Connect(kRight);
279 // If this is the loop header, go down instead of up and don't clear
280 // the target.
281 if (i == loop_position) {
282 c.Connect(kBottom);
283 } else {
284 c.Connect(kTop);
285 targets_[i] = nullptr;
286 }
287 saw_start = true;
288 } else if (c.connected == 0 && targets_[i] != nullptr) {
289 // If this is another arrow, connect it, but only if that doesn't
290 // clobber any existing drawing.
291 c.AddVertical();
292 }
293 os_ << c;
294 }
295 os_ << (saw_start ? "►" : " ");
296 }
297
298 int block_id = graph_labeller->BlockId(block);
299 os_ << "Block b" << block_id << "\n";
300
301 MaglevPrintingVisitorOstream::cast(os_for_additional_info_)->set_padding(1);
302 }
303
304 namespace {
305
306 template <typename NodeT>
PrintEagerDeopt(std::ostream & os,std::vector<BasicBlock * > targets,NodeT * node,const ProcessingState & state)307 void PrintEagerDeopt(std::ostream& os, std::vector<BasicBlock*> targets,
308 NodeT* node, const ProcessingState& state) {
309 MaglevGraphLabeller* graph_labeller = state.graph_labeller();
310
311 PrintVerticalArrows(os, targets);
312 PrintPadding(os, graph_labeller, 0);
313
314 EagerDeoptInfo* deopt_info = node->eager_deopt_info();
315 os << " ↱ eager @" << deopt_info->state.bytecode_position << " : {";
316 bool first = true;
317 int index = 0;
318 deopt_info->state.register_frame->ForEachValue(
319 *state.compilation_unit(),
320 [&](ValueNode* node, interpreter::Register reg) {
321 if (first) {
322 first = false;
323 } else {
324 os << ", ";
325 }
326 os << reg.ToString() << ":" << PrintNodeLabel(graph_labeller, node)
327 << ":" << deopt_info->input_locations[index].operand();
328 index++;
329 });
330 os << "}\n";
331 }
MaybePrintEagerDeopt(std::ostream & os,std::vector<BasicBlock * > targets,NodeBase * node,const ProcessingState & state)332 void MaybePrintEagerDeopt(std::ostream& os, std::vector<BasicBlock*> targets,
333 NodeBase* node, const ProcessingState& state) {
334 switch (node->opcode()) {
335 #define CASE(Name) \
336 case Opcode::k##Name: \
337 if constexpr (Name::kProperties.can_eager_deopt()) { \
338 PrintEagerDeopt<Name>(os, targets, node->Cast<Name>(), state); \
339 } \
340 break;
341 NODE_BASE_LIST(CASE)
342 #undef CASE
343 }
344 }
345
346 template <typename NodeT>
PrintLazyDeopt(std::ostream & os,std::vector<BasicBlock * > targets,NodeT * node,const ProcessingState & state)347 void PrintLazyDeopt(std::ostream& os, std::vector<BasicBlock*> targets,
348 NodeT* node, const ProcessingState& state) {
349 MaglevGraphLabeller* graph_labeller = state.graph_labeller();
350
351 PrintVerticalArrows(os, targets);
352 PrintPadding(os, graph_labeller, 0);
353
354 LazyDeoptInfo* deopt_info = node->lazy_deopt_info();
355 os << " ↳ lazy @" << deopt_info->state.bytecode_position << " : {";
356 bool first = true;
357 int index = 0;
358 deopt_info->state.register_frame->ForEachValue(
359 *state.compilation_unit(),
360 [&](ValueNode* node, interpreter::Register reg) {
361 if (first) {
362 first = false;
363 } else {
364 os << ", ";
365 }
366 os << reg.ToString() << ":";
367 if (reg == deopt_info->result_location) {
368 os << "<result>";
369 } else {
370 os << PrintNodeLabel(graph_labeller, node) << ":"
371 << deopt_info->input_locations[index].operand();
372 }
373 index++;
374 });
375 os << "}\n";
376 }
MaybePrintLazyDeopt(std::ostream & os,std::vector<BasicBlock * > targets,NodeBase * node,const ProcessingState & state)377 void MaybePrintLazyDeopt(std::ostream& os, std::vector<BasicBlock*> targets,
378 NodeBase* node, const ProcessingState& state) {
379 switch (node->opcode()) {
380 #define CASE(Name) \
381 case Opcode::k##Name: \
382 if constexpr (Name::kProperties.can_lazy_deopt()) { \
383 PrintLazyDeopt<Name>(os, targets, node->Cast<Name>(), state); \
384 } \
385 break;
386 NODE_BASE_LIST(CASE)
387 #undef CASE
388 }
389 }
390
391 } // namespace
392
Process(Phi * phi,const ProcessingState & state)393 void MaglevPrintingVisitor::Process(Phi* phi, const ProcessingState& state) {
394 MaglevGraphLabeller* graph_labeller = state.graph_labeller();
395
396 PrintVerticalArrows(os_, targets_);
397 PrintPaddedId(os_, graph_labeller, phi);
398 os_ << "Phi (";
399 // Manually walk Phi inputs to print just the node labels, without
400 // input locations (which are shown in the predecessor block's gap
401 // moves).
402 for (int i = 0; i < phi->input_count(); ++i) {
403 if (i > 0) os_ << ", ";
404 if (state.block()->predecessor_at(i) == nullptr) {
405 os_ << "<dead>";
406 } else {
407 os_ << PrintNodeLabel(graph_labeller, phi->input(i).node());
408 }
409 }
410 os_ << ") → " << phi->result().operand() << "\n";
411
412 MaglevPrintingVisitorOstream::cast(os_for_additional_info_)
413 ->set_padding(graph_labeller->max_node_id_width() + 4);
414 }
415
Process(Node * node,const ProcessingState & state)416 void MaglevPrintingVisitor::Process(Node* node, const ProcessingState& state) {
417 MaglevGraphLabeller* graph_labeller = state.graph_labeller();
418
419 MaybePrintEagerDeopt(os_, targets_, node, state);
420
421 PrintVerticalArrows(os_, targets_);
422 PrintPaddedId(os_, graph_labeller, node);
423 os_ << PrintNode(graph_labeller, node) << "\n";
424
425 MaglevPrintingVisitorOstream::cast(os_for_additional_info_)
426 ->set_padding(graph_labeller->max_node_id_width() + 4);
427
428 MaybePrintLazyDeopt(os_, targets_, node, state);
429 }
430
Process(ControlNode * control_node,const ProcessingState & state)431 void MaglevPrintingVisitor::Process(ControlNode* control_node,
432 const ProcessingState& state) {
433 MaglevGraphLabeller* graph_labeller = state.graph_labeller();
434
435 MaybePrintEagerDeopt(os_, targets_, control_node, state);
436
437 bool has_fallthrough = false;
438
439 if (control_node->Is<JumpLoop>()) {
440 BasicBlock* target = control_node->Cast<JumpLoop>()->target();
441
442 PrintVerticalArrows(os_, targets_, {}, {target}, true);
443 os_ << "◄─";
444 PrintPaddedId(os_, graph_labeller, control_node, "─", -2);
445 std::replace(targets_.begin(), targets_.end(), target,
446 static_cast<BasicBlock*>(nullptr));
447
448 } else if (control_node->Is<UnconditionalControlNode>()) {
449 BasicBlock* target =
450 control_node->Cast<UnconditionalControlNode>()->target();
451
452 std::set<size_t> arrows_starting_here;
453 has_fallthrough |= !AddTargetIfNotNext(targets_, target, state.next_block(),
454 &arrows_starting_here);
455 PrintVerticalArrows(os_, targets_, arrows_starting_here);
456 PrintPaddedId(os_, graph_labeller, control_node,
457 has_fallthrough ? " " : "─");
458
459 } else if (control_node->Is<ConditionalControlNode>()) {
460 BasicBlock* true_target =
461 control_node->Cast<ConditionalControlNode>()->if_true();
462 BasicBlock* false_target =
463 control_node->Cast<ConditionalControlNode>()->if_false();
464
465 std::set<size_t> arrows_starting_here;
466 has_fallthrough |= !AddTargetIfNotNext(
467 targets_, false_target, state.next_block(), &arrows_starting_here);
468 has_fallthrough |= !AddTargetIfNotNext(
469 targets_, true_target, state.next_block(), &arrows_starting_here);
470 PrintVerticalArrows(os_, targets_, arrows_starting_here);
471 PrintPaddedId(os_, graph_labeller, control_node, "─");
472
473 } else {
474 PrintVerticalArrows(os_, targets_);
475 PrintPaddedId(os_, graph_labeller, control_node);
476 }
477
478 os_ << PrintNode(graph_labeller, control_node) << "\n";
479
480 bool printed_phis = false;
481 if (control_node->Is<UnconditionalControlNode>()) {
482 BasicBlock* target =
483 control_node->Cast<UnconditionalControlNode>()->target();
484 if (target->has_phi()) {
485 printed_phis = true;
486 PrintVerticalArrows(os_, targets_);
487 PrintPadding(os_, graph_labeller, -1);
488 os_ << (has_fallthrough ? "│" : " ");
489 os_ << " with gap moves:\n";
490 int pid = state.block()->predecessor_id();
491 for (Phi* phi : *target->phis()) {
492 PrintVerticalArrows(os_, targets_);
493 PrintPadding(os_, graph_labeller, -1);
494 os_ << (has_fallthrough ? "│" : " ");
495 os_ << " - ";
496 graph_labeller->PrintInput(os_, phi->input(pid));
497 os_ << " → " << graph_labeller->NodeId(phi) << ": Phi "
498 << phi->result().operand() << "\n";
499 }
500 }
501 }
502
503 PrintVerticalArrows(os_, targets_);
504 if (has_fallthrough) {
505 PrintPadding(os_, graph_labeller, -1);
506 if (printed_phis) {
507 os_ << "▼";
508 } else {
509 os_ << "↓";
510 }
511 }
512 os_ << "\n";
513
514 // TODO(leszeks): Allow MaglevPrintingVisitorOstream to print the arrowhead
515 // so that it overlaps the fallthrough arrow.
516 MaglevPrintingVisitorOstream::cast(os_for_additional_info_)
517 ->set_padding(graph_labeller->max_node_id_width() + 4);
518 }
519
PrintGraph(std::ostream & os,MaglevCompilationUnit * compilation_unit,Graph * const graph)520 void PrintGraph(std::ostream& os, MaglevCompilationUnit* compilation_unit,
521 Graph* const graph) {
522 GraphProcessor<MaglevPrintingVisitor> printer(compilation_unit, os);
523 printer.ProcessGraph(graph);
524 }
525
Print(std::ostream & os) const526 void PrintNode::Print(std::ostream& os) const {
527 node_->Print(os, graph_labeller_);
528 }
529
operator <<(std::ostream & os,const PrintNode & printer)530 std::ostream& operator<<(std::ostream& os, const PrintNode& printer) {
531 printer.Print(os);
532 return os;
533 }
534
Print(std::ostream & os) const535 void PrintNodeLabel::Print(std::ostream& os) const {
536 graph_labeller_->PrintNodeLabel(os, node_);
537 }
538
operator <<(std::ostream & os,const PrintNodeLabel & printer)539 std::ostream& operator<<(std::ostream& os, const PrintNodeLabel& printer) {
540 printer.Print(os);
541 return os;
542 }
543
544 } // namespace maglev
545 } // namespace internal
546 } // namespace v8
547