1// Copyright 2013 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/compiler/graph-visualizer.h"
6
7#include <memory>
8#include <sstream>
9#include <string>
10
11#include "src/code-stubs.h"
12#include "src/compilation-info.h"
13#include "src/compiler/all-nodes.h"
14#include "src/compiler/compiler-source-position-table.h"
15#include "src/compiler/graph.h"
16#include "src/compiler/node-properties.h"
17#include "src/compiler/node.h"
18#include "src/compiler/opcodes.h"
19#include "src/compiler/operator-properties.h"
20#include "src/compiler/operator.h"
21#include "src/compiler/register-allocator.h"
22#include "src/compiler/schedule.h"
23#include "src/compiler/scheduler.h"
24#include "src/interpreter/bytecodes.h"
25#include "src/ostreams.h"
26
27namespace v8 {
28namespace internal {
29namespace compiler {
30
31std::unique_ptr<char[]> GetVisualizerLogFileName(CompilationInfo* info,
32                                                 const char* phase,
33                                                 const char* suffix) {
34  EmbeddedVector<char, 256> filename(0);
35  std::unique_ptr<char[]> debug_name = info->GetDebugName();
36  if (strlen(debug_name.get()) > 0) {
37    SNPrintF(filename, "turbo-%s", debug_name.get());
38  } else if (info->has_shared_info()) {
39    SNPrintF(filename, "turbo-%p", static_cast<void*>(info));
40  } else {
41    SNPrintF(filename, "turbo-none-%s", phase);
42  }
43  EmbeddedVector<char, 256> source_file(0);
44  bool source_available = false;
45  if (FLAG_trace_file_names && info->parse_info()) {
46    Object* source_name = info->script()->name();
47    if (source_name->IsString()) {
48      String* str = String::cast(source_name);
49      if (str->length() > 0) {
50        SNPrintF(source_file, "%s", str->ToCString().get());
51        std::replace(source_file.start(),
52                     source_file.start() + source_file.length(), '/', '_');
53        source_available = true;
54      }
55    }
56  }
57  std::replace(filename.start(), filename.start() + filename.length(), ' ',
58               '_');
59
60  EmbeddedVector<char, 256> full_filename;
61  if (phase == nullptr && !source_available) {
62    SNPrintF(full_filename, "%s.%s", filename.start(), suffix);
63  } else if (phase != nullptr && !source_available) {
64    SNPrintF(full_filename, "%s-%s.%s", filename.start(), phase, suffix);
65  } else if (phase == nullptr && source_available) {
66    SNPrintF(full_filename, "%s_%s.%s", filename.start(), source_file.start(),
67             suffix);
68  } else {
69    SNPrintF(full_filename, "%s_%s-%s.%s", filename.start(),
70             source_file.start(), phase, suffix);
71  }
72
73  char* buffer = new char[full_filename.length() + 1];
74  memcpy(buffer, full_filename.start(), full_filename.length());
75  buffer[full_filename.length()] = '\0';
76  return std::unique_ptr<char[]>(buffer);
77}
78
79
80static int SafeId(Node* node) { return node == nullptr ? -1 : node->id(); }
81static const char* SafeMnemonic(Node* node) {
82  return node == nullptr ? "null" : node->op()->mnemonic();
83}
84
85class JSONEscaped {
86 public:
87  explicit JSONEscaped(const std::ostringstream& os) : str_(os.str()) {}
88
89  friend std::ostream& operator<<(std::ostream& os, const JSONEscaped& e) {
90    for (char c : e.str_) PipeCharacter(os, c);
91    return os;
92  }
93
94 private:
95  static std::ostream& PipeCharacter(std::ostream& os, char c) {
96    if (c == '"') return os << "\\\"";
97    if (c == '\\') return os << "\\\\";
98    if (c == '\b') return os << "\\b";
99    if (c == '\f') return os << "\\f";
100    if (c == '\n') return os << "\\n";
101    if (c == '\r') return os << "\\r";
102    if (c == '\t') return os << "\\t";
103    return os << c;
104  }
105
106  const std::string str_;
107};
108
109class JSONGraphNodeWriter {
110 public:
111  JSONGraphNodeWriter(std::ostream& os, Zone* zone, const Graph* graph,
112                      const SourcePositionTable* positions)
113      : os_(os),
114        all_(zone, graph, false),
115        live_(zone, graph, true),
116        positions_(positions),
117        first_node_(true) {}
118
119  void Print() {
120    for (Node* const node : all_.reachable) PrintNode(node);
121    os_ << "\n";
122  }
123
124  void PrintNode(Node* node) {
125    if (first_node_) {
126      first_node_ = false;
127    } else {
128      os_ << ",\n";
129    }
130    std::ostringstream label, title, properties;
131    node->op()->PrintTo(label, Operator::PrintVerbosity::kSilent);
132    node->op()->PrintTo(title, Operator::PrintVerbosity::kVerbose);
133    node->op()->PrintPropsTo(properties);
134    os_ << "{\"id\":" << SafeId(node) << ",\"label\":\"" << JSONEscaped(label)
135        << "\""
136        << ",\"title\":\"" << JSONEscaped(title) << "\""
137        << ",\"live\": " << (live_.IsLive(node) ? "true" : "false")
138        << ",\"properties\":\"" << JSONEscaped(properties) << "\"";
139    IrOpcode::Value opcode = node->opcode();
140    if (IrOpcode::IsPhiOpcode(opcode)) {
141      os_ << ",\"rankInputs\":[0," << NodeProperties::FirstControlIndex(node)
142          << "]";
143      os_ << ",\"rankWithInput\":[" << NodeProperties::FirstControlIndex(node)
144          << "]";
145    } else if (opcode == IrOpcode::kIfTrue || opcode == IrOpcode::kIfFalse ||
146               opcode == IrOpcode::kLoop) {
147      os_ << ",\"rankInputs\":[" << NodeProperties::FirstControlIndex(node)
148          << "]";
149    }
150    if (opcode == IrOpcode::kBranch) {
151      os_ << ",\"rankInputs\":[0]";
152    }
153    SourcePosition position = positions_->GetSourcePosition(node);
154    if (position.IsKnown()) {
155      os_ << ",\"pos\":" << position.ScriptOffset();
156    }
157    os_ << ",\"opcode\":\"" << IrOpcode::Mnemonic(node->opcode()) << "\"";
158    os_ << ",\"control\":" << (NodeProperties::IsControl(node) ? "true"
159                                                               : "false");
160    os_ << ",\"opinfo\":\"" << node->op()->ValueInputCount() << " v "
161        << node->op()->EffectInputCount() << " eff "
162        << node->op()->ControlInputCount() << " ctrl in, "
163        << node->op()->ValueOutputCount() << " v "
164        << node->op()->EffectOutputCount() << " eff "
165        << node->op()->ControlOutputCount() << " ctrl out\"";
166    if (NodeProperties::IsTyped(node)) {
167      Type* type = NodeProperties::GetType(node);
168      std::ostringstream type_out;
169      type->PrintTo(type_out);
170      os_ << ",\"type\":\"" << JSONEscaped(type_out) << "\"";
171    }
172    os_ << "}";
173  }
174
175 private:
176  std::ostream& os_;
177  AllNodes all_;
178  AllNodes live_;
179  const SourcePositionTable* positions_;
180  bool first_node_;
181
182  DISALLOW_COPY_AND_ASSIGN(JSONGraphNodeWriter);
183};
184
185
186class JSONGraphEdgeWriter {
187 public:
188  JSONGraphEdgeWriter(std::ostream& os, Zone* zone, const Graph* graph)
189      : os_(os), all_(zone, graph, false), first_edge_(true) {}
190
191  void Print() {
192    for (Node* const node : all_.reachable) PrintEdges(node);
193    os_ << "\n";
194  }
195
196  void PrintEdges(Node* node) {
197    for (int i = 0; i < node->InputCount(); i++) {
198      Node* input = node->InputAt(i);
199      if (input == nullptr) continue;
200      PrintEdge(node, i, input);
201    }
202  }
203
204  void PrintEdge(Node* from, int index, Node* to) {
205    if (first_edge_) {
206      first_edge_ = false;
207    } else {
208      os_ << ",\n";
209    }
210    const char* edge_type = nullptr;
211    if (index < NodeProperties::FirstValueIndex(from)) {
212      edge_type = "unknown";
213    } else if (index < NodeProperties::FirstContextIndex(from)) {
214      edge_type = "value";
215    } else if (index < NodeProperties::FirstFrameStateIndex(from)) {
216      edge_type = "context";
217    } else if (index < NodeProperties::FirstEffectIndex(from)) {
218      edge_type = "frame-state";
219    } else if (index < NodeProperties::FirstControlIndex(from)) {
220      edge_type = "effect";
221    } else {
222      edge_type = "control";
223    }
224    os_ << "{\"source\":" << SafeId(to) << ",\"target\":" << SafeId(from)
225        << ",\"index\":" << index << ",\"type\":\"" << edge_type << "\"}";
226  }
227
228 private:
229  std::ostream& os_;
230  AllNodes all_;
231  bool first_edge_;
232
233  DISALLOW_COPY_AND_ASSIGN(JSONGraphEdgeWriter);
234};
235
236
237std::ostream& operator<<(std::ostream& os, const AsJSON& ad) {
238  AccountingAllocator allocator;
239  Zone tmp_zone(&allocator, ZONE_NAME);
240  os << "{\n\"nodes\":[";
241  JSONGraphNodeWriter(os, &tmp_zone, &ad.graph, ad.positions).Print();
242  os << "],\n\"edges\":[";
243  JSONGraphEdgeWriter(os, &tmp_zone, &ad.graph).Print();
244  os << "]}";
245  return os;
246}
247
248
249class GraphC1Visualizer {
250 public:
251  GraphC1Visualizer(std::ostream& os, Zone* zone);  // NOLINT
252
253  void PrintCompilation(const CompilationInfo* info);
254  void PrintSchedule(const char* phase, const Schedule* schedule,
255                     const SourcePositionTable* positions,
256                     const InstructionSequence* instructions);
257  void PrintLiveRanges(const char* phase, const RegisterAllocationData* data);
258  Zone* zone() const { return zone_; }
259
260 private:
261  void PrintIndent();
262  void PrintStringProperty(const char* name, const char* value);
263  void PrintLongProperty(const char* name, int64_t value);
264  void PrintIntProperty(const char* name, int value);
265  void PrintBlockProperty(const char* name, int rpo_number);
266  void PrintNodeId(Node* n);
267  void PrintNode(Node* n);
268  void PrintInputs(Node* n);
269  template <typename InputIterator>
270  void PrintInputs(InputIterator* i, int count, const char* prefix);
271  void PrintType(Node* node);
272
273  void PrintLiveRange(const LiveRange* range, const char* type, int vreg);
274  void PrintLiveRangeChain(const TopLevelLiveRange* range, const char* type);
275
276  class Tag final BASE_EMBEDDED {
277   public:
278    Tag(GraphC1Visualizer* visualizer, const char* name) {
279      name_ = name;
280      visualizer_ = visualizer;
281      visualizer->PrintIndent();
282      visualizer_->os_ << "begin_" << name << "\n";
283      visualizer->indent_++;
284    }
285
286    ~Tag() {
287      visualizer_->indent_--;
288      visualizer_->PrintIndent();
289      visualizer_->os_ << "end_" << name_ << "\n";
290      DCHECK(visualizer_->indent_ >= 0);
291    }
292
293   private:
294    GraphC1Visualizer* visualizer_;
295    const char* name_;
296  };
297
298  std::ostream& os_;
299  int indent_;
300  Zone* zone_;
301
302  DISALLOW_COPY_AND_ASSIGN(GraphC1Visualizer);
303};
304
305
306void GraphC1Visualizer::PrintIndent() {
307  for (int i = 0; i < indent_; i++) {
308    os_ << "  ";
309  }
310}
311
312
313GraphC1Visualizer::GraphC1Visualizer(std::ostream& os, Zone* zone)
314    : os_(os), indent_(0), zone_(zone) {}
315
316
317void GraphC1Visualizer::PrintStringProperty(const char* name,
318                                            const char* value) {
319  PrintIndent();
320  os_ << name << " \"" << value << "\"\n";
321}
322
323
324void GraphC1Visualizer::PrintLongProperty(const char* name, int64_t value) {
325  PrintIndent();
326  os_ << name << " " << static_cast<int>(value / 1000) << "\n";
327}
328
329
330void GraphC1Visualizer::PrintBlockProperty(const char* name, int rpo_number) {
331  PrintIndent();
332  os_ << name << " \"B" << rpo_number << "\"\n";
333}
334
335
336void GraphC1Visualizer::PrintIntProperty(const char* name, int value) {
337  PrintIndent();
338  os_ << name << " " << value << "\n";
339}
340
341
342void GraphC1Visualizer::PrintCompilation(const CompilationInfo* info) {
343  Tag tag(this, "compilation");
344  std::unique_ptr<char[]> name = info->GetDebugName();
345  if (info->IsOptimizing()) {
346    PrintStringProperty("name", name.get());
347    PrintIndent();
348    os_ << "method \"" << name.get() << ":" << info->optimization_id()
349        << "\"\n";
350  } else {
351    PrintStringProperty("name", name.get());
352    PrintStringProperty("method", "stub");
353  }
354  PrintLongProperty("date",
355                    static_cast<int64_t>(base::OS::TimeCurrentMillis()));
356}
357
358
359void GraphC1Visualizer::PrintNodeId(Node* n) { os_ << "n" << SafeId(n); }
360
361
362void GraphC1Visualizer::PrintNode(Node* n) {
363  PrintNodeId(n);
364  os_ << " " << *n->op() << " ";
365  PrintInputs(n);
366}
367
368
369template <typename InputIterator>
370void GraphC1Visualizer::PrintInputs(InputIterator* i, int count,
371                                    const char* prefix) {
372  if (count > 0) {
373    os_ << prefix;
374  }
375  while (count > 0) {
376    os_ << " ";
377    PrintNodeId(**i);
378    ++(*i);
379    count--;
380  }
381}
382
383
384void GraphC1Visualizer::PrintInputs(Node* node) {
385  auto i = node->inputs().begin();
386  PrintInputs(&i, node->op()->ValueInputCount(), " ");
387  PrintInputs(&i, OperatorProperties::GetContextInputCount(node->op()),
388              " Ctx:");
389  PrintInputs(&i, OperatorProperties::GetFrameStateInputCount(node->op()),
390              " FS:");
391  PrintInputs(&i, node->op()->EffectInputCount(), " Eff:");
392  PrintInputs(&i, node->op()->ControlInputCount(), " Ctrl:");
393}
394
395
396void GraphC1Visualizer::PrintType(Node* node) {
397  if (NodeProperties::IsTyped(node)) {
398    Type* type = NodeProperties::GetType(node);
399    os_ << " type:";
400    type->PrintTo(os_);
401  }
402}
403
404
405void GraphC1Visualizer::PrintSchedule(const char* phase,
406                                      const Schedule* schedule,
407                                      const SourcePositionTable* positions,
408                                      const InstructionSequence* instructions) {
409  Tag tag(this, "cfg");
410  PrintStringProperty("name", phase);
411  const BasicBlockVector* rpo = schedule->rpo_order();
412  for (size_t i = 0; i < rpo->size(); i++) {
413    BasicBlock* current = (*rpo)[i];
414    Tag block_tag(this, "block");
415    PrintBlockProperty("name", current->rpo_number());
416    PrintIntProperty("from_bci", -1);
417    PrintIntProperty("to_bci", -1);
418
419    PrintIndent();
420    os_ << "predecessors";
421    for (BasicBlock* predecessor : current->predecessors()) {
422      os_ << " \"B" << predecessor->rpo_number() << "\"";
423    }
424    os_ << "\n";
425
426    PrintIndent();
427    os_ << "successors";
428    for (BasicBlock* successor : current->successors()) {
429      os_ << " \"B" << successor->rpo_number() << "\"";
430    }
431    os_ << "\n";
432
433    PrintIndent();
434    os_ << "xhandlers\n";
435
436    PrintIndent();
437    os_ << "flags\n";
438
439    if (current->dominator() != nullptr) {
440      PrintBlockProperty("dominator", current->dominator()->rpo_number());
441    }
442
443    PrintIntProperty("loop_depth", current->loop_depth());
444
445    const InstructionBlock* instruction_block =
446        instructions->InstructionBlockAt(
447            RpoNumber::FromInt(current->rpo_number()));
448    if (instruction_block->code_start() >= 0) {
449      int first_index = instruction_block->first_instruction_index();
450      int last_index = instruction_block->last_instruction_index();
451      PrintIntProperty(
452          "first_lir_id",
453          LifetimePosition::GapFromInstructionIndex(first_index).value());
454      PrintIntProperty("last_lir_id",
455                       LifetimePosition::InstructionFromInstructionIndex(
456                           last_index).value());
457    }
458
459    {
460      Tag states_tag(this, "states");
461      Tag locals_tag(this, "locals");
462      int total = 0;
463      for (BasicBlock::const_iterator i = current->begin(); i != current->end();
464           ++i) {
465        if ((*i)->opcode() == IrOpcode::kPhi) total++;
466      }
467      PrintIntProperty("size", total);
468      PrintStringProperty("method", "None");
469      int index = 0;
470      for (BasicBlock::const_iterator i = current->begin(); i != current->end();
471           ++i) {
472        if ((*i)->opcode() != IrOpcode::kPhi) continue;
473        PrintIndent();
474        os_ << index << " ";
475        PrintNodeId(*i);
476        os_ << " [";
477        PrintInputs(*i);
478        os_ << "]\n";
479        index++;
480      }
481    }
482
483    {
484      Tag HIR_tag(this, "HIR");
485      for (BasicBlock::const_iterator i = current->begin(); i != current->end();
486           ++i) {
487        Node* node = *i;
488        if (node->opcode() == IrOpcode::kPhi) continue;
489        int uses = node->UseCount();
490        PrintIndent();
491        os_ << "0 " << uses << " ";
492        PrintNode(node);
493        if (FLAG_trace_turbo_types) {
494          os_ << " ";
495          PrintType(node);
496        }
497        if (positions != nullptr) {
498          SourcePosition position = positions->GetSourcePosition(node);
499          if (position.IsKnown()) {
500            os_ << " pos:" << position.ScriptOffset();
501          }
502        }
503        os_ << " <|@\n";
504      }
505
506      BasicBlock::Control control = current->control();
507      if (control != BasicBlock::kNone) {
508        PrintIndent();
509        os_ << "0 0 ";
510        if (current->control_input() != nullptr) {
511          PrintNode(current->control_input());
512        } else {
513          os_ << -1 - current->rpo_number() << " Goto";
514        }
515        os_ << " ->";
516        for (BasicBlock* successor : current->successors()) {
517          os_ << " B" << successor->rpo_number();
518        }
519        if (FLAG_trace_turbo_types && current->control_input() != nullptr) {
520          os_ << " ";
521          PrintType(current->control_input());
522        }
523        os_ << " <|@\n";
524      }
525    }
526
527    if (instructions != nullptr) {
528      Tag LIR_tag(this, "LIR");
529      for (int j = instruction_block->first_instruction_index();
530           j <= instruction_block->last_instruction_index(); j++) {
531        PrintIndent();
532        PrintableInstruction printable = {RegisterConfiguration::Turbofan(),
533                                          instructions->InstructionAt(j)};
534        os_ << j << " " << printable << " <|@\n";
535      }
536    }
537  }
538}
539
540
541void GraphC1Visualizer::PrintLiveRanges(const char* phase,
542                                        const RegisterAllocationData* data) {
543  Tag tag(this, "intervals");
544  PrintStringProperty("name", phase);
545
546  for (const TopLevelLiveRange* range : data->fixed_double_live_ranges()) {
547    PrintLiveRangeChain(range, "fixed");
548  }
549
550  for (const TopLevelLiveRange* range : data->fixed_live_ranges()) {
551    PrintLiveRangeChain(range, "fixed");
552  }
553
554  for (const TopLevelLiveRange* range : data->live_ranges()) {
555    PrintLiveRangeChain(range, "object");
556  }
557}
558
559void GraphC1Visualizer::PrintLiveRangeChain(const TopLevelLiveRange* range,
560                                            const char* type) {
561  if (range == nullptr || range->IsEmpty()) return;
562  int vreg = range->vreg();
563  for (const LiveRange* child = range; child != nullptr;
564       child = child->next()) {
565    PrintLiveRange(child, type, vreg);
566  }
567}
568
569void GraphC1Visualizer::PrintLiveRange(const LiveRange* range, const char* type,
570                                       int vreg) {
571  if (range != nullptr && !range->IsEmpty()) {
572    PrintIndent();
573    os_ << vreg << ":" << range->relative_id() << " " << type;
574    if (range->HasRegisterAssigned()) {
575      AllocatedOperand op = AllocatedOperand::cast(range->GetAssignedOperand());
576      const auto config = RegisterConfiguration::Turbofan();
577      if (op.IsRegister()) {
578        os_ << " \"" << config->GetGeneralRegisterName(op.register_code())
579            << "\"";
580      } else if (op.IsDoubleRegister()) {
581        os_ << " \"" << config->GetDoubleRegisterName(op.register_code())
582            << "\"";
583      } else {
584        DCHECK(op.IsFloatRegister());
585        os_ << " \"" << config->GetFloatRegisterName(op.register_code())
586            << "\"";
587      }
588    } else if (range->spilled()) {
589      const TopLevelLiveRange* top = range->TopLevel();
590      int index = -1;
591      if (top->HasSpillRange()) {
592        index = kMaxInt;  // This hasn't been set yet.
593      } else if (top->GetSpillOperand()->IsConstant()) {
594        os_ << " \"const(nostack):"
595            << ConstantOperand::cast(top->GetSpillOperand())->virtual_register()
596            << "\"";
597      } else {
598        index = AllocatedOperand::cast(top->GetSpillOperand())->index();
599        if (IsFloatingPoint(top->representation())) {
600          os_ << " \"fp_stack:" << index << "\"";
601        } else {
602          os_ << " \"stack:" << index << "\"";
603        }
604      }
605    }
606
607    os_ << " " << vreg;
608    for (const UseInterval* interval = range->first_interval();
609         interval != nullptr; interval = interval->next()) {
610      os_ << " [" << interval->start().value() << ", "
611          << interval->end().value() << "[";
612    }
613
614    UsePosition* current_pos = range->first_pos();
615    while (current_pos != nullptr) {
616      if (current_pos->RegisterIsBeneficial() || FLAG_trace_all_uses) {
617        os_ << " " << current_pos->pos().value() << " M";
618      }
619      current_pos = current_pos->next();
620    }
621
622    os_ << " \"\"\n";
623  }
624}
625
626
627std::ostream& operator<<(std::ostream& os, const AsC1VCompilation& ac) {
628  AccountingAllocator allocator;
629  Zone tmp_zone(&allocator, ZONE_NAME);
630  GraphC1Visualizer(os, &tmp_zone).PrintCompilation(ac.info_);
631  return os;
632}
633
634
635std::ostream& operator<<(std::ostream& os, const AsC1V& ac) {
636  AccountingAllocator allocator;
637  Zone tmp_zone(&allocator, ZONE_NAME);
638  GraphC1Visualizer(os, &tmp_zone)
639      .PrintSchedule(ac.phase_, ac.schedule_, ac.positions_, ac.instructions_);
640  return os;
641}
642
643
644std::ostream& operator<<(std::ostream& os,
645                         const AsC1VRegisterAllocationData& ac) {
646  AccountingAllocator allocator;
647  Zone tmp_zone(&allocator, ZONE_NAME);
648  GraphC1Visualizer(os, &tmp_zone).PrintLiveRanges(ac.phase_, ac.data_);
649  return os;
650}
651
652const int kUnvisited = 0;
653const int kOnStack = 1;
654const int kVisited = 2;
655
656std::ostream& operator<<(std::ostream& os, const AsRPO& ar) {
657  AccountingAllocator allocator;
658  Zone local_zone(&allocator, ZONE_NAME);
659
660  // Do a post-order depth-first search on the RPO graph. For every node,
661  // print:
662  //
663  //   - the node id
664  //   - the operator mnemonic
665  //   - in square brackets its parameter (if present)
666  //   - in parentheses the list of argument ids and their mnemonics
667  //   - the node type (if it is typed)
668
669  // Post-order guarantees that all inputs of a node will be printed before
670  // the node itself, if there are no cycles. Any cycles are broken
671  // arbitrarily.
672
673  ZoneVector<byte> state(ar.graph.NodeCount(), kUnvisited, &local_zone);
674  ZoneStack<Node*> stack(&local_zone);
675
676  stack.push(ar.graph.end());
677  state[ar.graph.end()->id()] = kOnStack;
678  while (!stack.empty()) {
679    Node* n = stack.top();
680    bool pop = true;
681    for (Node* const i : n->inputs()) {
682      if (state[i->id()] == kUnvisited) {
683        state[i->id()] = kOnStack;
684        stack.push(i);
685        pop = false;
686        break;
687      }
688    }
689    if (pop) {
690      state[n->id()] = kVisited;
691      stack.pop();
692      os << "#" << n->id() << ":" << *n->op() << "(";
693      // Print the inputs.
694      int j = 0;
695      for (Node* const i : n->inputs()) {
696        if (j++ > 0) os << ", ";
697        os << "#" << SafeId(i) << ":" << SafeMnemonic(i);
698      }
699      os << ")";
700      // Print the node type, if any.
701      if (NodeProperties::IsTyped(n)) {
702        os << "  [Type: ";
703        NodeProperties::GetType(n)->PrintTo(os);
704        os << "]";
705      }
706      os << std::endl;
707    }
708  }
709  return os;
710}
711}  // namespace compiler
712}  // namespace internal
713}  // namespace v8
714