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/base/platform/wrappers.h"
12#include "src/base/vector.h"
13#include "src/codegen/optimized-compilation-info.h"
14#include "src/codegen/source-position.h"
15#include "src/compiler/all-nodes.h"
16#include "src/compiler/backend/register-allocation.h"
17#include "src/compiler/backend/register-allocator.h"
18#include "src/compiler/compiler-source-position-table.h"
19#include "src/compiler/graph.h"
20#include "src/compiler/node-origin-table.h"
21#include "src/compiler/node-properties.h"
22#include "src/compiler/node.h"
23#include "src/compiler/opcodes.h"
24#include "src/compiler/operator-properties.h"
25#include "src/compiler/operator.h"
26#include "src/compiler/schedule.h"
27#include "src/compiler/scheduler.h"
28#include "src/interpreter/bytecodes.h"
29#include "src/objects/script-inl.h"
30#include "src/objects/shared-function-info.h"
31#include "src/utils/ostreams.h"
32
33namespace v8 {
34namespace internal {
35namespace compiler {
36
37const char* get_cached_trace_turbo_filename(OptimizedCompilationInfo* info) {
38  if (!info->trace_turbo_filename()) {
39    info->set_trace_turbo_filename(
40        GetVisualizerLogFileName(info, FLAG_trace_turbo_path, nullptr, "json"));
41  }
42  return info->trace_turbo_filename();
43}
44
45TurboJsonFile::TurboJsonFile(OptimizedCompilationInfo* info,
46                             std::ios_base::openmode mode)
47    : std::ofstream(get_cached_trace_turbo_filename(info), mode) {}
48
49TurboJsonFile::~TurboJsonFile() { flush(); }
50
51TurboCfgFile::TurboCfgFile(Isolate* isolate)
52    : std::ofstream(Isolate::GetTurboCfgFileName(isolate).c_str(),
53                    std::ios_base::app) {}
54
55TurboCfgFile::~TurboCfgFile() { flush(); }
56
57std::ostream& operator<<(std::ostream& out,
58                         const SourcePositionAsJSON& asJSON) {
59  asJSON.sp.PrintJson(out);
60  return out;
61}
62
63std::ostream& operator<<(std::ostream& out, const NodeOriginAsJSON& asJSON) {
64  asJSON.no.PrintJson(out);
65  return out;
66}
67
68class JSONEscaped {
69 public:
70  explicit JSONEscaped(const std::ostringstream& os) : str_(os.str()) {}
71
72  friend std::ostream& operator<<(std::ostream& os, const JSONEscaped& e) {
73    for (char c : e.str_) PipeCharacter(os, c);
74    return os;
75  }
76
77 private:
78  static std::ostream& PipeCharacter(std::ostream& os, char c) {
79    if (c == '"') return os << "\\\"";
80    if (c == '\\') return os << "\\\\";
81    if (c == '\b') return os << "\\b";
82    if (c == '\f') return os << "\\f";
83    if (c == '\n') return os << "\\n";
84    if (c == '\r') return os << "\\r";
85    if (c == '\t') return os << "\\t";
86    return os << c;
87  }
88
89  const std::string str_;
90};
91
92void JsonPrintFunctionSource(std::ostream& os, int source_id,
93                             std::unique_ptr<char[]> function_name,
94                             Handle<Script> script, Isolate* isolate,
95                             Handle<SharedFunctionInfo> shared, bool with_key) {
96  if (with_key) os << "\"" << source_id << "\" : ";
97
98  os << "{ ";
99  os << "\"sourceId\": " << source_id;
100  os << ", \"functionName\": \"" << function_name.get() << "\" ";
101
102  int start = 0;
103  int end = 0;
104  if (!script.is_null() && !script->IsUndefined(isolate) && !shared.is_null()) {
105    Object source_name = script->name();
106    os << ", \"sourceName\": \"";
107    if (source_name.IsString()) {
108      std::ostringstream escaped_name;
109      escaped_name << String::cast(source_name).ToCString().get();
110      os << JSONEscaped(escaped_name);
111    }
112    os << "\"";
113    {
114      DisallowGarbageCollection no_gc;
115      start = shared->StartPosition();
116      end = shared->EndPosition();
117      os << ", \"sourceText\": \"";
118      int len = shared->EndPosition() - start;
119      SubStringRange source(String::cast(script->source()), no_gc, start, len);
120      for (auto c : source) {
121        os << AsEscapedUC16ForJSON(c);
122      }
123      os << "\"";
124    }
125  } else {
126    os << ", \"sourceName\": \"\"";
127    os << ", \"sourceText\": \"\"";
128  }
129  os << ", \"startPosition\": " << start;
130  os << ", \"endPosition\": " << end;
131  os << "}";
132}
133
134int SourceIdAssigner::GetIdFor(Handle<SharedFunctionInfo> shared) {
135  for (unsigned i = 0; i < printed_.size(); i++) {
136    if (printed_.at(i).is_identical_to(shared)) {
137      source_ids_.push_back(i);
138      return i;
139    }
140  }
141  const int source_id = static_cast<int>(printed_.size());
142  printed_.push_back(shared);
143  source_ids_.push_back(source_id);
144  return source_id;
145}
146
147namespace {
148
149void JsonPrintInlinedFunctionInfo(
150    std::ostream& os, int source_id, int inlining_id,
151    const OptimizedCompilationInfo::InlinedFunctionHolder& h) {
152  os << "\"" << inlining_id << "\" : ";
153  os << "{ \"inliningId\" : " << inlining_id;
154  os << ", \"sourceId\" : " << source_id;
155  const SourcePosition position = h.position.position;
156  if (position.IsKnown()) {
157    os << ", \"inliningPosition\" : " << AsJSON(position);
158  }
159  os << "}";
160}
161
162}  // namespace
163
164void JsonPrintAllSourceWithPositions(std::ostream& os,
165                                     OptimizedCompilationInfo* info,
166                                     Isolate* isolate) {
167  os << "\"sources\" : {";
168  Handle<Script> script =
169      (info->shared_info().is_null() ||
170       info->shared_info()->script() == Object())
171          ? Handle<Script>()
172          : handle(Script::cast(info->shared_info()->script()), isolate);
173  JsonPrintFunctionSource(os, -1,
174                          info->shared_info().is_null()
175                              ? std::unique_ptr<char[]>(new char[1]{0})
176                              : info->shared_info()->DebugNameCStr(),
177                          script, isolate, info->shared_info(), true);
178  const auto& inlined = info->inlined_functions();
179  SourceIdAssigner id_assigner(info->inlined_functions().size());
180  for (unsigned id = 0; id < inlined.size(); id++) {
181    os << ", ";
182    Handle<SharedFunctionInfo> shared = inlined[id].shared_info;
183    const int source_id = id_assigner.GetIdFor(shared);
184    JsonPrintFunctionSource(os, source_id, shared->DebugNameCStr(),
185                            handle(Script::cast(shared->script()), isolate),
186                            isolate, shared, true);
187  }
188  os << "}, ";
189  os << "\"inlinings\" : {";
190  bool need_comma = false;
191  for (unsigned id = 0; id < inlined.size(); id++) {
192    if (need_comma) os << ", ";
193    const int source_id = id_assigner.GetIdAt(id);
194    JsonPrintInlinedFunctionInfo(os, source_id, id, inlined[id]);
195    need_comma = true;
196  }
197  os << "}";
198}
199
200std::unique_ptr<char[]> GetVisualizerLogFileName(OptimizedCompilationInfo* info,
201                                                 const char* optional_base_dir,
202                                                 const char* phase,
203                                                 const char* suffix) {
204  base::EmbeddedVector<char, 256> filename(0);
205  std::unique_ptr<char[]> debug_name = info->GetDebugName();
206  int optimization_id = info->IsOptimizing() ? info->optimization_id() : 0;
207  if (strlen(debug_name.get()) > 0) {
208    SNPrintF(filename, "turbo-%s-%i", debug_name.get(), optimization_id);
209  } else if (info->has_shared_info()) {
210    SNPrintF(filename, "turbo-%p-%i",
211             reinterpret_cast<void*>(info->shared_info()->address()),
212             optimization_id);
213  } else {
214    SNPrintF(filename, "turbo-none-%i", optimization_id);
215  }
216  base::EmbeddedVector<char, 256> source_file(0);
217  bool source_available = false;
218  if (FLAG_trace_file_names && info->has_shared_info() &&
219      info->shared_info()->script().IsScript()) {
220    Object source_name = Script::cast(info->shared_info()->script()).name();
221    if (source_name.IsString()) {
222      String str = String::cast(source_name);
223      if (str.length() > 0) {
224        SNPrintF(source_file, "%s", str.ToCString().get());
225        std::replace(source_file.begin(),
226                     source_file.begin() + source_file.length(), '/', '_');
227        source_available = true;
228      }
229    }
230  }
231  std::replace(filename.begin(), filename.begin() + filename.length(), ' ',
232               '_');
233  std::replace(filename.begin(), filename.begin() + filename.length(), ':',
234               '-');
235
236  base::EmbeddedVector<char, 256> base_dir;
237  if (optional_base_dir != nullptr) {
238    SNPrintF(base_dir, "%s%c", optional_base_dir,
239             base::OS::DirectorySeparator());
240  } else {
241    base_dir[0] = '\0';
242  }
243
244  base::EmbeddedVector<char, 256> full_filename;
245  if (phase == nullptr && !source_available) {
246    SNPrintF(full_filename, "%s%s.%s", base_dir.begin(), filename.begin(),
247             suffix);
248  } else if (phase != nullptr && !source_available) {
249    SNPrintF(full_filename, "%s%s-%s.%s", base_dir.begin(), filename.begin(),
250             phase, suffix);
251  } else if (phase == nullptr && source_available) {
252    SNPrintF(full_filename, "%s%s_%s.%s", base_dir.begin(), filename.begin(),
253             source_file.begin(), suffix);
254  } else {
255    SNPrintF(full_filename, "%s%s_%s-%s.%s", base_dir.begin(), filename.begin(),
256             source_file.begin(), phase, suffix);
257  }
258
259  char* buffer = new char[full_filename.length() + 1];
260  memcpy(buffer, full_filename.begin(), full_filename.length());
261  buffer[full_filename.length()] = '\0';
262  return std::unique_ptr<char[]>(buffer);
263}
264
265
266static int SafeId(Node* node) { return node == nullptr ? -1 : node->id(); }
267static const char* SafeMnemonic(Node* node) {
268  return node == nullptr ? "null" : node->op()->mnemonic();
269}
270
271JSONGraphWriter::JSONGraphWriter(std::ostream& os, const Graph* graph,
272                                 const SourcePositionTable* positions,
273                                 const NodeOriginTable* origins)
274    : os_(os),
275      zone_(nullptr),
276      graph_(graph),
277      positions_(positions),
278      origins_(origins),
279      first_node_(true),
280      first_edge_(true) {}
281
282void JSONGraphWriter::PrintPhase(const char* phase_name) {
283  os_ << "{\"name\":\"" << phase_name << "\",\"type\":\"graph\",\"data\":";
284  Print();
285  os_ << "},\n";
286}
287
288void JSONGraphWriter::Print() {
289  AccountingAllocator allocator;
290  Zone tmp_zone(&allocator, ZONE_NAME);
291  zone_ = &tmp_zone;
292
293  AllNodes all(zone_, graph_, false);
294  AllNodes live(zone_, graph_, true);
295
296  os_ << "{\n\"nodes\":[";
297  for (Node* const node : all.reachable) PrintNode(node, live.IsLive(node));
298  os_ << "\n";
299  os_ << "],\n\"edges\":[";
300  for (Node* const node : all.reachable) PrintEdges(node);
301  os_ << "\n";
302  os_ << "]}";
303  zone_ = nullptr;
304}
305
306void JSONGraphWriter::PrintNode(Node* node, bool is_live) {
307  if (first_node_) {
308    first_node_ = false;
309  } else {
310    os_ << ",\n";
311  }
312  std::ostringstream label, title, properties;
313  node->op()->PrintTo(label, Operator::PrintVerbosity::kSilent);
314  node->op()->PrintTo(title, Operator::PrintVerbosity::kVerbose);
315  node->op()->PrintPropsTo(properties);
316  os_ << "{\"id\":" << SafeId(node) << ",\"label\":\"" << JSONEscaped(label)
317      << "\""
318      << ",\"title\":\"" << JSONEscaped(title) << "\""
319      << ",\"live\": " << (is_live ? "true" : "false") << ",\"properties\":\""
320      << JSONEscaped(properties) << "\"";
321  IrOpcode::Value opcode = node->opcode();
322  if (IrOpcode::IsPhiOpcode(opcode)) {
323    os_ << ",\"rankInputs\":[0," << NodeProperties::FirstControlIndex(node)
324        << "]";
325    os_ << ",\"rankWithInput\":[" << NodeProperties::FirstControlIndex(node)
326        << "]";
327  } else if (opcode == IrOpcode::kIfTrue || opcode == IrOpcode::kIfFalse ||
328             opcode == IrOpcode::kLoop) {
329    os_ << ",\"rankInputs\":[" << NodeProperties::FirstControlIndex(node)
330        << "]";
331  }
332  if (opcode == IrOpcode::kBranch) {
333    os_ << ",\"rankInputs\":[0]";
334  }
335  if (positions_ != nullptr) {
336    SourcePosition position = positions_->GetSourcePosition(node);
337    if (position.IsKnown()) {
338      os_ << ", \"sourcePosition\" : " << AsJSON(position);
339    }
340  }
341  if (origins_) {
342    NodeOrigin origin = origins_->GetNodeOrigin(node);
343    if (origin.IsKnown()) {
344      os_ << ", \"origin\" : " << AsJSON(origin);
345    }
346  }
347  os_ << ",\"opcode\":\"" << IrOpcode::Mnemonic(node->opcode()) << "\"";
348  os_ << ",\"control\":"
349      << (NodeProperties::IsControl(node) ? "true" : "false");
350  os_ << ",\"opinfo\":\"" << node->op()->ValueInputCount() << " v "
351      << node->op()->EffectInputCount() << " eff "
352      << node->op()->ControlInputCount() << " ctrl in, "
353      << node->op()->ValueOutputCount() << " v "
354      << node->op()->EffectOutputCount() << " eff "
355      << node->op()->ControlOutputCount() << " ctrl out\"";
356  if (auto type_opt = GetType(node)) {
357    std::ostringstream type_out;
358    type_opt->PrintTo(type_out);
359    os_ << ",\"type\":\"" << JSONEscaped(type_out) << "\"";
360  }
361  os_ << "}";
362}
363
364void JSONGraphWriter::PrintEdges(Node* node) {
365  for (int i = 0; i < node->InputCount(); i++) {
366    Node* input = node->InputAt(i);
367    if (input == nullptr) continue;
368    PrintEdge(node, i, input);
369  }
370}
371
372void JSONGraphWriter::PrintEdge(Node* from, int index, Node* to) {
373  if (first_edge_) {
374    first_edge_ = false;
375  } else {
376    os_ << ",\n";
377  }
378  const char* edge_type = nullptr;
379  if (index < NodeProperties::FirstValueIndex(from)) {
380    edge_type = "unknown";
381  } else if (index < NodeProperties::FirstContextIndex(from)) {
382    edge_type = "value";
383  } else if (index < NodeProperties::FirstFrameStateIndex(from)) {
384    edge_type = "context";
385  } else if (index < NodeProperties::FirstEffectIndex(from)) {
386    edge_type = "frame-state";
387  } else if (index < NodeProperties::FirstControlIndex(from)) {
388    edge_type = "effect";
389  } else {
390    edge_type = "control";
391  }
392  os_ << "{\"source\":" << SafeId(to) << ",\"target\":" << SafeId(from)
393      << ",\"index\":" << index << ",\"type\":\"" << edge_type << "\"}";
394}
395
396base::Optional<Type> JSONGraphWriter::GetType(Node* node) {
397  if (!NodeProperties::IsTyped(node)) return base::nullopt;
398  return NodeProperties::GetType(node);
399}
400
401std::ostream& operator<<(std::ostream& os, const GraphAsJSON& ad) {
402  JSONGraphWriter writer(os, &ad.graph, ad.positions, ad.origins);
403  writer.Print();
404  return os;
405}
406
407
408class GraphC1Visualizer {
409 public:
410  GraphC1Visualizer(std::ostream& os, Zone* zone);
411  GraphC1Visualizer(const GraphC1Visualizer&) = delete;
412  GraphC1Visualizer& operator=(const GraphC1Visualizer&) = delete;
413
414  void PrintCompilation(const OptimizedCompilationInfo* info);
415  void PrintSchedule(const char* phase, const Schedule* schedule,
416                     const SourcePositionTable* positions,
417                     const InstructionSequence* instructions);
418  void PrintLiveRanges(const char* phase,
419                       const TopTierRegisterAllocationData* data);
420  Zone* zone() const { return zone_; }
421
422 private:
423  void PrintIndent();
424  void PrintStringProperty(const char* name, const char* value);
425  void PrintLongProperty(const char* name, int64_t value);
426  void PrintIntProperty(const char* name, int value);
427  void PrintBlockProperty(const char* name, int rpo_number);
428  void PrintNodeId(Node* n);
429  void PrintNode(Node* n);
430  void PrintInputs(Node* n);
431  template <typename InputIterator>
432  void PrintInputs(InputIterator* i, int count, const char* prefix);
433  void PrintType(Node* node);
434
435  void PrintLiveRange(const LiveRange* range, const char* type, int vreg);
436  void PrintLiveRangeChain(const TopLevelLiveRange* range, const char* type);
437
438  class Tag final {
439   public:
440    Tag(GraphC1Visualizer* visualizer, const char* name) {
441      name_ = name;
442      visualizer_ = visualizer;
443      visualizer->PrintIndent();
444      visualizer_->os_ << "begin_" << name << "\n";
445      visualizer->indent_++;
446    }
447
448    ~Tag() {
449      visualizer_->indent_--;
450      visualizer_->PrintIndent();
451      visualizer_->os_ << "end_" << name_ << "\n";
452      DCHECK_LE(0, visualizer_->indent_);
453    }
454
455   private:
456    GraphC1Visualizer* visualizer_;
457    const char* name_;
458  };
459
460  std::ostream& os_;
461  int indent_;
462  Zone* zone_;
463};
464
465
466void GraphC1Visualizer::PrintIndent() {
467  for (int i = 0; i < indent_; i++) {
468    os_ << "  ";
469  }
470}
471
472
473GraphC1Visualizer::GraphC1Visualizer(std::ostream& os, Zone* zone)
474    : os_(os), indent_(0), zone_(zone) {}
475
476
477void GraphC1Visualizer::PrintStringProperty(const char* name,
478                                            const char* value) {
479  PrintIndent();
480  os_ << name << " \"" << value << "\"\n";
481}
482
483
484void GraphC1Visualizer::PrintLongProperty(const char* name, int64_t value) {
485  PrintIndent();
486  os_ << name << " " << static_cast<int>(value / 1000) << "\n";
487}
488
489
490void GraphC1Visualizer::PrintBlockProperty(const char* name, int rpo_number) {
491  PrintIndent();
492  os_ << name << " \"B" << rpo_number << "\"\n";
493}
494
495
496void GraphC1Visualizer::PrintIntProperty(const char* name, int value) {
497  PrintIndent();
498  os_ << name << " " << value << "\n";
499}
500
501void GraphC1Visualizer::PrintCompilation(const OptimizedCompilationInfo* info) {
502  Tag tag(this, "compilation");
503  std::unique_ptr<char[]> name = info->GetDebugName();
504  if (info->IsOptimizing()) {
505    PrintStringProperty("name", name.get());
506    PrintIndent();
507    os_ << "method \"" << name.get() << ":" << info->optimization_id()
508        << "\"\n";
509  } else {
510    PrintStringProperty("name", name.get());
511    PrintStringProperty("method", "stub");
512  }
513  PrintLongProperty(
514      "date",
515      static_cast<int64_t>(V8::GetCurrentPlatform()->CurrentClockTimeMillis()));
516}
517
518
519void GraphC1Visualizer::PrintNodeId(Node* n) { os_ << "n" << SafeId(n); }
520
521
522void GraphC1Visualizer::PrintNode(Node* n) {
523  PrintNodeId(n);
524  os_ << " " << *n->op() << " ";
525  PrintInputs(n);
526}
527
528
529template <typename InputIterator>
530void GraphC1Visualizer::PrintInputs(InputIterator* i, int count,
531                                    const char* prefix) {
532  if (count > 0) {
533    os_ << prefix;
534  }
535  while (count > 0) {
536    os_ << " ";
537    PrintNodeId(**i);
538    ++(*i);
539    count--;
540  }
541}
542
543
544void GraphC1Visualizer::PrintInputs(Node* node) {
545  auto i = node->inputs().begin();
546  PrintInputs(&i, node->op()->ValueInputCount(), " ");
547  PrintInputs(&i, OperatorProperties::GetContextInputCount(node->op()),
548              " Ctx:");
549  PrintInputs(&i, OperatorProperties::GetFrameStateInputCount(node->op()),
550              " FS:");
551  PrintInputs(&i, node->op()->EffectInputCount(), " Eff:");
552  PrintInputs(&i, node->op()->ControlInputCount(), " Ctrl:");
553}
554
555
556void GraphC1Visualizer::PrintType(Node* node) {
557  if (NodeProperties::IsTyped(node)) {
558    Type type = NodeProperties::GetType(node);
559    os_ << " type:" << type;
560  }
561}
562
563
564void GraphC1Visualizer::PrintSchedule(const char* phase,
565                                      const Schedule* schedule,
566                                      const SourcePositionTable* positions,
567                                      const InstructionSequence* instructions) {
568  Tag tag(this, "cfg");
569  PrintStringProperty("name", phase);
570  const BasicBlockVector* rpo = schedule->rpo_order();
571  for (size_t i = 0; i < rpo->size(); i++) {
572    BasicBlock* current = (*rpo)[i];
573    Tag block_tag(this, "block");
574    PrintBlockProperty("name", current->rpo_number());
575    PrintIntProperty("from_bci", -1);
576    PrintIntProperty("to_bci", -1);
577
578    PrintIndent();
579    os_ << "predecessors";
580    for (BasicBlock* predecessor : current->predecessors()) {
581      os_ << " \"B" << predecessor->rpo_number() << "\"";
582    }
583    os_ << "\n";
584
585    PrintIndent();
586    os_ << "successors";
587    for (BasicBlock* successor : current->successors()) {
588      os_ << " \"B" << successor->rpo_number() << "\"";
589    }
590    os_ << "\n";
591
592    PrintIndent();
593    os_ << "xhandlers\n";
594
595    PrintIndent();
596    os_ << "flags\n";
597
598    if (current->dominator() != nullptr) {
599      PrintBlockProperty("dominator", current->dominator()->rpo_number());
600    }
601
602    PrintIntProperty("loop_depth", current->loop_depth());
603
604    const InstructionBlock* instruction_block =
605        instructions->InstructionBlockAt(
606            RpoNumber::FromInt(current->rpo_number()));
607    if (instruction_block->code_start() >= 0) {
608      int first_index = instruction_block->first_instruction_index();
609      int last_index = instruction_block->last_instruction_index();
610      PrintIntProperty(
611          "first_lir_id",
612          LifetimePosition::GapFromInstructionIndex(first_index).value());
613      PrintIntProperty("last_lir_id",
614                       LifetimePosition::InstructionFromInstructionIndex(
615                           last_index).value());
616    }
617
618    {
619      Tag states_tag(this, "states");
620      Tag locals_tag(this, "locals");
621      int total = 0;
622      for (BasicBlock::const_iterator it = current->begin();
623           it != current->end(); ++it) {
624        if ((*it)->opcode() == IrOpcode::kPhi) total++;
625      }
626      PrintIntProperty("size", total);
627      PrintStringProperty("method", "None");
628      int index = 0;
629      for (BasicBlock::const_iterator it = current->begin();
630           it != current->end(); ++it) {
631        if ((*it)->opcode() != IrOpcode::kPhi) continue;
632        PrintIndent();
633        os_ << index << " ";
634        PrintNodeId(*it);
635        os_ << " [";
636        PrintInputs(*it);
637        os_ << "]\n";
638        index++;
639      }
640    }
641
642    {
643      Tag HIR_tag(this, "HIR");
644      for (BasicBlock::const_iterator it = current->begin();
645           it != current->end(); ++it) {
646        Node* node = *it;
647        if (node->opcode() == IrOpcode::kPhi) continue;
648        int uses = node->UseCount();
649        PrintIndent();
650        os_ << "0 " << uses << " ";
651        PrintNode(node);
652        if (FLAG_trace_turbo_types) {
653          os_ << " ";
654          PrintType(node);
655        }
656        if (positions != nullptr) {
657          SourcePosition position = positions->GetSourcePosition(node);
658          if (position.IsKnown()) {
659            os_ << " pos:";
660            if (position.isInlined()) {
661              os_ << "inlining(" << position.InliningId() << "),";
662            }
663            os_ << position.ScriptOffset();
664          }
665        }
666        os_ << " <|@\n";
667      }
668
669      BasicBlock::Control control = current->control();
670      if (control != BasicBlock::kNone) {
671        PrintIndent();
672        os_ << "0 0 ";
673        if (current->control_input() != nullptr) {
674          PrintNode(current->control_input());
675        } else {
676          os_ << -1 - current->rpo_number() << " Goto";
677        }
678        os_ << " ->";
679        for (BasicBlock* successor : current->successors()) {
680          os_ << " B" << successor->rpo_number();
681        }
682        if (FLAG_trace_turbo_types && current->control_input() != nullptr) {
683          os_ << " ";
684          PrintType(current->control_input());
685        }
686        os_ << " <|@\n";
687      }
688    }
689
690    if (instructions != nullptr) {
691      Tag LIR_tag(this, "LIR");
692      for (int j = instruction_block->first_instruction_index();
693           j <= instruction_block->last_instruction_index(); j++) {
694        PrintIndent();
695        os_ << j << " " << *instructions->InstructionAt(j) << " <|@\n";
696      }
697    }
698  }
699}
700
701void GraphC1Visualizer::PrintLiveRanges(
702    const char* phase, const TopTierRegisterAllocationData* data) {
703  Tag tag(this, "intervals");
704  PrintStringProperty("name", phase);
705
706  for (const TopLevelLiveRange* range : data->fixed_double_live_ranges()) {
707    PrintLiveRangeChain(range, "fixed");
708  }
709
710  for (const TopLevelLiveRange* range : data->fixed_live_ranges()) {
711    PrintLiveRangeChain(range, "fixed");
712  }
713
714  for (const TopLevelLiveRange* range : data->live_ranges()) {
715    PrintLiveRangeChain(range, "object");
716  }
717}
718
719void GraphC1Visualizer::PrintLiveRangeChain(const TopLevelLiveRange* range,
720                                            const char* type) {
721  if (range == nullptr || range->IsEmpty()) return;
722  int vreg = range->vreg();
723  for (const LiveRange* child = range; child != nullptr;
724       child = child->next()) {
725    PrintLiveRange(child, type, vreg);
726  }
727}
728
729void GraphC1Visualizer::PrintLiveRange(const LiveRange* range, const char* type,
730                                       int vreg) {
731  if (range != nullptr && !range->IsEmpty()) {
732    PrintIndent();
733    os_ << vreg << ":" << range->relative_id() << " " << type;
734    if (range->HasRegisterAssigned()) {
735      AllocatedOperand op = AllocatedOperand::cast(range->GetAssignedOperand());
736      if (op.IsRegister()) {
737        os_ << " \"" << Register::from_code(op.register_code()) << "\"";
738      } else if (op.IsDoubleRegister()) {
739        os_ << " \"" << DoubleRegister::from_code(op.register_code()) << "\"";
740      } else if (op.IsFloatRegister()) {
741        os_ << " \"" << FloatRegister::from_code(op.register_code()) << "\"";
742      } else {
743        DCHECK(op.IsSimd128Register());
744        os_ << " \"" << Simd128Register::from_code(op.register_code()) << "\"";
745      }
746    } else if (range->spilled()) {
747      const TopLevelLiveRange* top = range->TopLevel();
748      int index = -1;
749      if (top->HasSpillRange()) {
750        index = kMaxInt;  // This hasn't been set yet.
751      } else if (top->GetSpillOperand()->IsConstant()) {
752        os_ << " \"const(nostack):"
753            << ConstantOperand::cast(top->GetSpillOperand())->virtual_register()
754            << "\"";
755      } else {
756        index = AllocatedOperand::cast(top->GetSpillOperand())->index();
757        if (IsFloatingPoint(top->representation())) {
758          os_ << " \"fp_stack:" << index << "\"";
759        } else {
760          os_ << " \"stack:" << index << "\"";
761        }
762      }
763    }
764
765    const TopLevelLiveRange* parent = range->TopLevel();
766    os_ << " " << parent->vreg() << ":" << parent->relative_id();
767
768    // TODO(herhut) Find something useful to print for the hint field
769    if (range->get_bundle() != nullptr) {
770      os_ << " B" << range->get_bundle()->id();
771    } else {
772      os_ << " unknown";
773    }
774
775    for (const UseInterval* interval = range->first_interval();
776         interval != nullptr; interval = interval->next()) {
777      os_ << " [" << interval->start().value() << ", "
778          << interval->end().value() << "[";
779    }
780
781    UsePosition* current_pos = range->first_pos();
782    while (current_pos != nullptr) {
783      if (current_pos->RegisterIsBeneficial() || FLAG_trace_all_uses) {
784        os_ << " " << current_pos->pos().value() << " M";
785      }
786      current_pos = current_pos->next();
787    }
788
789    os_ << " \"\"\n";
790  }
791}
792
793
794std::ostream& operator<<(std::ostream& os, const AsC1VCompilation& ac) {
795  AccountingAllocator allocator;
796  Zone tmp_zone(&allocator, ZONE_NAME);
797  GraphC1Visualizer(os, &tmp_zone).PrintCompilation(ac.info_);
798  return os;
799}
800
801
802std::ostream& operator<<(std::ostream& os, const AsC1V& ac) {
803  AccountingAllocator allocator;
804  Zone tmp_zone(&allocator, ZONE_NAME);
805  GraphC1Visualizer(os, &tmp_zone)
806      .PrintSchedule(ac.phase_, ac.schedule_, ac.positions_, ac.instructions_);
807  return os;
808}
809
810
811std::ostream& operator<<(std::ostream& os,
812                         const AsC1VRegisterAllocationData& ac) {
813  // TODO(rmcilroy): Add support for fast register allocator.
814  if (ac.data_->type() == RegisterAllocationData::kTopTier) {
815    AccountingAllocator allocator;
816    Zone tmp_zone(&allocator, ZONE_NAME);
817    GraphC1Visualizer(os, &tmp_zone)
818        .PrintLiveRanges(ac.phase_,
819                         TopTierRegisterAllocationData::cast(ac.data_));
820  }
821  return os;
822}
823
824const int kUnvisited = 0;
825const int kOnStack = 1;
826const int kVisited = 2;
827
828std::ostream& operator<<(std::ostream& os, const AsRPO& ar) {
829  AccountingAllocator allocator;
830  Zone local_zone(&allocator, ZONE_NAME);
831
832  // Do a post-order depth-first search on the RPO graph. For every node,
833  // print:
834  //
835  //   - the node id
836  //   - the operator mnemonic
837  //   - in square brackets its parameter (if present)
838  //   - in parentheses the list of argument ids and their mnemonics
839  //   - the node type (if it is typed)
840
841  // Post-order guarantees that all inputs of a node will be printed before
842  // the node itself, if there are no cycles. Any cycles are broken
843  // arbitrarily.
844
845  ZoneVector<byte> state(ar.graph.NodeCount(), kUnvisited, &local_zone);
846  ZoneStack<Node*> stack(&local_zone);
847
848  stack.push(ar.graph.end());
849  state[ar.graph.end()->id()] = kOnStack;
850  while (!stack.empty()) {
851    Node* n = stack.top();
852    bool pop = true;
853    for (Node* const i : n->inputs()) {
854      if (state[i->id()] == kUnvisited) {
855        state[i->id()] = kOnStack;
856        stack.push(i);
857        pop = false;
858        break;
859      }
860    }
861    if (pop) {
862      state[n->id()] = kVisited;
863      stack.pop();
864      os << "#" << n->id() << ":" << *n->op() << "(";
865      // Print the inputs.
866      int j = 0;
867      for (Node* const i : n->inputs()) {
868        if (j++ > 0) os << ", ";
869        os << "#" << SafeId(i) << ":" << SafeMnemonic(i);
870      }
871      os << ")";
872      // Print the node type, if any.
873      if (NodeProperties::IsTyped(n)) {
874        os << "  [Type: " << NodeProperties::GetType(n) << "]";
875      }
876      os << std::endl;
877    }
878  }
879  return os;
880}
881
882namespace {
883
884void PrintIndent(std::ostream& os, int indent) {
885  os << "     ";
886  for (int i = 0; i < indent; i++) {
887    os << ". ";
888  }
889}
890
891void PrintScheduledNode(std::ostream& os, int indent, Node* n) {
892  PrintIndent(os, indent);
893  os << "#" << n->id() << ":" << *n->op() << "(";
894  // Print the inputs.
895  int j = 0;
896  for (Node* const i : n->inputs()) {
897    if (j++ > 0) os << ", ";
898    os << "#" << SafeId(i) << ":" << SafeMnemonic(i);
899  }
900  os << ")";
901  // Print the node type, if any.
902  if (NodeProperties::IsTyped(n)) {
903    os << "  [Type: " << NodeProperties::GetType(n) << "]";
904  }
905}
906
907void PrintScheduledGraph(std::ostream& os, const Schedule* schedule) {
908  const BasicBlockVector* rpo = schedule->rpo_order();
909  for (size_t i = 0; i < rpo->size(); i++) {
910    BasicBlock* current = (*rpo)[i];
911    int indent = current->loop_depth();
912
913    os << "  + Block B" << current->rpo_number() << " (pred:";
914    for (BasicBlock* predecessor : current->predecessors()) {
915      os << " B" << predecessor->rpo_number();
916    }
917    if (current->IsLoopHeader()) {
918      os << ", loop until B" << current->loop_end()->rpo_number();
919    } else if (current->loop_header()) {
920      os << ", in loop B" << current->loop_header()->rpo_number();
921    }
922    os << ")" << std::endl;
923
924    for (BasicBlock::const_iterator it = current->begin(); it != current->end();
925         ++it) {
926      Node* node = *it;
927      PrintScheduledNode(os, indent, node);
928      os << std::endl;
929    }
930
931    if (current->SuccessorCount() > 0) {
932      if (current->control_input() != nullptr) {
933        PrintScheduledNode(os, indent, current->control_input());
934      } else {
935        PrintIndent(os, indent);
936        os << "Goto";
937      }
938      os << " ->";
939
940      bool isFirst = true;
941      for (BasicBlock* successor : current->successors()) {
942        if (isFirst) {
943          isFirst = false;
944        } else {
945          os << ",";
946        }
947        os << " B" << successor->rpo_number();
948      }
949      os << std::endl;
950    } else {
951      DCHECK_NULL(current->control_input());
952    }
953  }
954}
955
956}  // namespace
957
958std::ostream& operator<<(std::ostream& os,
959                         const LiveRangeAsJSON& live_range_json) {
960  const LiveRange& range = live_range_json.range_;
961  os << "{\"id\":" << range.relative_id() << ",\"type\":";
962  if (range.HasRegisterAssigned()) {
963    const InstructionOperand op = range.GetAssignedOperand();
964    os << "\"assigned\",\"op\":"
965       << InstructionOperandAsJSON{&op, &(live_range_json.code_)};
966  } else if (range.spilled() && !range.TopLevel()->HasNoSpillType()) {
967    const TopLevelLiveRange* top = range.TopLevel();
968    if (top->HasSpillOperand()) {
969      os << "\"assigned\",\"op\":"
970         << InstructionOperandAsJSON{top->GetSpillOperand(),
971                                     &(live_range_json.code_)};
972    } else {
973      int index = top->GetSpillRange()->assigned_slot();
974      os << "\"spilled\",\"op\":";
975      if (IsFloatingPoint(top->representation())) {
976        os << "\"fp_stack:" << index << "\"";
977      } else {
978        os << "\"stack:" << index << "\"";
979      }
980    }
981  } else {
982    os << "\"none\"";
983  }
984
985  os << ",\"intervals\":[";
986  bool first = true;
987  for (const UseInterval* interval = range.first_interval();
988       interval != nullptr; interval = interval->next()) {
989    if (first) {
990      first = false;
991    } else {
992      os << ",";
993    }
994    os << "[" << interval->start().value() << "," << interval->end().value()
995       << "]";
996  }
997
998  os << "],\"uses\":[";
999  first = true;
1000  for (UsePosition* current_pos = range.first_pos(); current_pos != nullptr;
1001       current_pos = current_pos->next()) {
1002    if (first) {
1003      first = false;
1004    } else {
1005      os << ",";
1006    }
1007    os << current_pos->pos().value();
1008  }
1009
1010  os << "]}";
1011  return os;
1012}
1013
1014std::ostream& operator<<(
1015    std::ostream& os,
1016    const TopLevelLiveRangeAsJSON& top_level_live_range_json) {
1017  int vreg = top_level_live_range_json.range_.vreg();
1018  bool first = true;
1019  os << "\"" << (vreg > 0 ? vreg : -vreg) << "\":{ \"child_ranges\":[";
1020  for (const LiveRange* child = &(top_level_live_range_json.range_);
1021       child != nullptr; child = child->next()) {
1022    if (!top_level_live_range_json.range_.IsEmpty()) {
1023      if (first) {
1024        first = false;
1025      } else {
1026        os << ",";
1027      }
1028      os << LiveRangeAsJSON{*child, top_level_live_range_json.code_};
1029    }
1030  }
1031  os << "]";
1032  if (top_level_live_range_json.range_.IsFixed()) {
1033    os << ", \"is_deferred\": "
1034       << (top_level_live_range_json.range_.IsDeferredFixed() ? "true"
1035                                                              : "false");
1036  }
1037  os << "}";
1038  return os;
1039}
1040
1041void PrintTopLevelLiveRanges(std::ostream& os,
1042                             const ZoneVector<TopLevelLiveRange*> ranges,
1043                             const InstructionSequence& code) {
1044  bool first = true;
1045  os << "{";
1046  for (const TopLevelLiveRange* range : ranges) {
1047    if (range != nullptr && !range->IsEmpty()) {
1048      if (first) {
1049        first = false;
1050      } else {
1051        os << ",";
1052      }
1053      os << TopLevelLiveRangeAsJSON{*range, code};
1054    }
1055  }
1056  os << "}";
1057}
1058
1059std::ostream& operator<<(std::ostream& os,
1060                         const RegisterAllocationDataAsJSON& ac) {
1061  if (ac.data_.type() == RegisterAllocationData::kTopTier) {
1062    const TopTierRegisterAllocationData& ac_data =
1063        TopTierRegisterAllocationData::cast(ac.data_);
1064    os << "\"fixed_double_live_ranges\": ";
1065    PrintTopLevelLiveRanges(os, ac_data.fixed_double_live_ranges(), ac.code_);
1066    os << ",\"fixed_live_ranges\": ";
1067    PrintTopLevelLiveRanges(os, ac_data.fixed_live_ranges(), ac.code_);
1068    os << ",\"live_ranges\": ";
1069    PrintTopLevelLiveRanges(os, ac_data.live_ranges(), ac.code_);
1070  } else {
1071    // TODO(rmcilroy): Add support for fast register allocation data. For now
1072    // output the expected fields to keep Turbolizer happy.
1073    os << "\"fixed_double_live_ranges\": {}";
1074    os << ",\"fixed_live_ranges\": {}";
1075    os << ",\"live_ranges\": {}";
1076  }
1077  return os;
1078}
1079
1080std::ostream& operator<<(std::ostream& os, const AsScheduledGraph& scheduled) {
1081  PrintScheduledGraph(os, scheduled.schedule);
1082  return os;
1083}
1084
1085std::ostream& operator<<(std::ostream& os, const InstructionOperandAsJSON& o) {
1086  const InstructionOperand* op = o.op_;
1087  const InstructionSequence* code = o.code_;
1088  os << "{";
1089  switch (op->kind()) {
1090    case InstructionOperand::UNALLOCATED: {
1091      const UnallocatedOperand* unalloc = UnallocatedOperand::cast(op);
1092      os << "\"type\": \"unallocated\", ";
1093      os << "\"text\": \"v" << unalloc->virtual_register() << "\"";
1094      if (unalloc->basic_policy() == UnallocatedOperand::FIXED_SLOT) {
1095        os << ",\"tooltip\": \"FIXED_SLOT: " << unalloc->fixed_slot_index()
1096           << "\"";
1097        break;
1098      }
1099      switch (unalloc->extended_policy()) {
1100        case UnallocatedOperand::NONE:
1101          break;
1102        case UnallocatedOperand::FIXED_REGISTER: {
1103          os << ",\"tooltip\": \"FIXED_REGISTER: "
1104             << Register::from_code(unalloc->fixed_register_index()) << "\"";
1105          break;
1106        }
1107        case UnallocatedOperand::FIXED_FP_REGISTER: {
1108          os << ",\"tooltip\": \"FIXED_FP_REGISTER: "
1109             << DoubleRegister::from_code(unalloc->fixed_register_index())
1110             << "\"";
1111          break;
1112        }
1113        case UnallocatedOperand::MUST_HAVE_REGISTER: {
1114          os << ",\"tooltip\": \"MUST_HAVE_REGISTER\"";
1115          break;
1116        }
1117        case UnallocatedOperand::MUST_HAVE_SLOT: {
1118          os << ",\"tooltip\": \"MUST_HAVE_SLOT\"";
1119          break;
1120        }
1121        case UnallocatedOperand::SAME_AS_INPUT: {
1122          os << ",\"tooltip\": \"SAME_AS_INPUT: " << unalloc->input_index()
1123             << "\"";
1124          break;
1125        }
1126        case UnallocatedOperand::REGISTER_OR_SLOT: {
1127          os << ",\"tooltip\": \"REGISTER_OR_SLOT\"";
1128          break;
1129        }
1130        case UnallocatedOperand::REGISTER_OR_SLOT_OR_CONSTANT: {
1131          os << ",\"tooltip\": \"REGISTER_OR_SLOT_OR_CONSTANT\"";
1132          break;
1133        }
1134      }
1135      break;
1136    }
1137    case InstructionOperand::CONSTANT: {
1138      int vreg = ConstantOperand::cast(op)->virtual_register();
1139      os << "\"type\": \"constant\", ";
1140      os << "\"text\": \"v" << vreg << "\",";
1141      os << "\"tooltip\": \"";
1142      std::stringstream tooltip;
1143      tooltip << code->GetConstant(vreg);
1144      for (const auto& c : tooltip.str()) {
1145        os << AsEscapedUC16ForJSON(c);
1146      }
1147      os << "\"";
1148      break;
1149    }
1150    case InstructionOperand::IMMEDIATE: {
1151      os << "\"type\": \"immediate\", ";
1152      const ImmediateOperand* imm = ImmediateOperand::cast(op);
1153      switch (imm->type()) {
1154        case ImmediateOperand::INLINE_INT32: {
1155          os << "\"text\": \"#" << imm->inline_int32_value() << "\"";
1156          break;
1157        }
1158        case ImmediateOperand::INLINE_INT64: {
1159          os << "\"text\": \"#" << imm->inline_int64_value() << "\"";
1160          break;
1161        }
1162        case ImmediateOperand::INDEXED_RPO:
1163        case ImmediateOperand::INDEXED_IMM: {
1164          int index = imm->indexed_value();
1165          os << "\"text\": \"imm:" << index << "\",";
1166          os << "\"tooltip\": \"";
1167          std::stringstream tooltip;
1168          tooltip << code->GetImmediate(imm);
1169          for (const auto& c : tooltip.str()) {
1170            os << AsEscapedUC16ForJSON(c);
1171          }
1172          os << "\"";
1173          break;
1174        }
1175      }
1176      break;
1177    }
1178    case InstructionOperand::ALLOCATED: {
1179      const LocationOperand* allocated = LocationOperand::cast(op);
1180      os << "\"type\": \"allocated\", ";
1181      os << "\"text\": \"";
1182      if (op->IsStackSlot()) {
1183        os << "stack:" << allocated->index();
1184      } else if (op->IsFPStackSlot()) {
1185        os << "fp_stack:" << allocated->index();
1186      } else if (op->IsRegister()) {
1187        if (allocated->register_code() < Register::kNumRegisters) {
1188          os << Register::from_code(allocated->register_code());
1189        } else {
1190          os << Register::GetSpecialRegisterName(allocated->register_code());
1191        }
1192      } else if (op->IsDoubleRegister()) {
1193        os << DoubleRegister::from_code(allocated->register_code());
1194      } else if (op->IsFloatRegister()) {
1195        os << FloatRegister::from_code(allocated->register_code());
1196      } else {
1197        DCHECK(op->IsSimd128Register());
1198        os << Simd128Register::from_code(allocated->register_code());
1199      }
1200      os << "\",";
1201      os << "\"tooltip\": \""
1202         << MachineReprToString(allocated->representation()) << "\"";
1203      break;
1204    }
1205    case InstructionOperand::PENDING:
1206    case InstructionOperand::INVALID:
1207      UNREACHABLE();
1208  }
1209  os << "}";
1210  return os;
1211}
1212
1213std::ostream& operator<<(std::ostream& os, const InstructionAsJSON& i_json) {
1214  const Instruction* instr = i_json.instr_;
1215
1216  os << "{";
1217  os << "\"id\": " << i_json.index_ << ",";
1218  os << "\"opcode\": \"" << ArchOpcodeField::decode(instr->opcode()) << "\",";
1219  os << "\"flags\": \"";
1220  FlagsMode fm = FlagsModeField::decode(instr->opcode());
1221  AddressingMode am = AddressingModeField::decode(instr->opcode());
1222  if (am != kMode_None) {
1223    os << " : " << AddressingModeField::decode(instr->opcode());
1224  }
1225  if (fm != kFlags_none) {
1226    os << " && " << fm << " if "
1227       << FlagsConditionField::decode(instr->opcode());
1228  }
1229  os << "\",";
1230
1231  os << "\"gaps\": [";
1232  for (int i = Instruction::FIRST_GAP_POSITION;
1233       i <= Instruction::LAST_GAP_POSITION; i++) {
1234    if (i != Instruction::FIRST_GAP_POSITION) os << ",";
1235    os << "[";
1236    const ParallelMove* pm = instr->parallel_moves()[i];
1237    if (pm == nullptr) {
1238      os << "]";
1239      continue;
1240    }
1241    bool first = true;
1242    for (MoveOperands* move : *pm) {
1243      if (move->IsEliminated()) continue;
1244      if (first) {
1245        first = false;
1246      } else {
1247        os << ",";
1248      }
1249      os << "[" << InstructionOperandAsJSON{&move->destination(), i_json.code_}
1250         << "," << InstructionOperandAsJSON{&move->source(), i_json.code_}
1251         << "]";
1252    }
1253    os << "]";
1254  }
1255  os << "],";
1256
1257  os << "\"outputs\": [";
1258  bool need_comma = false;
1259  for (size_t i = 0; i < instr->OutputCount(); i++) {
1260    if (need_comma) os << ",";
1261    need_comma = true;
1262    os << InstructionOperandAsJSON{instr->OutputAt(i), i_json.code_};
1263  }
1264  os << "],";
1265
1266  os << "\"inputs\": [";
1267  need_comma = false;
1268  for (size_t i = 0; i < instr->InputCount(); i++) {
1269    if (need_comma) os << ",";
1270    need_comma = true;
1271    os << InstructionOperandAsJSON{instr->InputAt(i), i_json.code_};
1272  }
1273  os << "],";
1274
1275  os << "\"temps\": [";
1276  need_comma = false;
1277  for (size_t i = 0; i < instr->TempCount(); i++) {
1278    if (need_comma) os << ",";
1279    need_comma = true;
1280    os << InstructionOperandAsJSON{instr->TempAt(i), i_json.code_};
1281  }
1282  os << "]";
1283  os << "}";
1284
1285  return os;
1286}
1287
1288std::ostream& operator<<(std::ostream& os, const InstructionBlockAsJSON& b) {
1289  const InstructionBlock* block = b.block_;
1290  const InstructionSequence* code = b.code_;
1291  os << "{";
1292  os << "\"id\": " << block->rpo_number() << ",";
1293  os << "\"deferred\": " << (block->IsDeferred() ? "true" : "false");
1294  os << ",";
1295  os << "\"loop_header\": " << block->IsLoopHeader() << ",";
1296  if (block->IsLoopHeader()) {
1297    os << "\"loop_end\": " << block->loop_end() << ",";
1298  }
1299  os << "\"predecessors\": [";
1300  bool need_comma = false;
1301  for (RpoNumber pred : block->predecessors()) {
1302    if (need_comma) os << ",";
1303    need_comma = true;
1304    os << pred.ToInt();
1305  }
1306  os << "],";
1307  os << "\"successors\": [";
1308  need_comma = false;
1309  for (RpoNumber succ : block->successors()) {
1310    if (need_comma) os << ",";
1311    need_comma = true;
1312    os << succ.ToInt();
1313  }
1314  os << "],";
1315  os << "\"phis\": [";
1316  bool needs_comma = false;
1317  InstructionOperandAsJSON json_op = {nullptr, code};
1318  for (const PhiInstruction* phi : block->phis()) {
1319    if (needs_comma) os << ",";
1320    needs_comma = true;
1321    json_op.op_ = &phi->output();
1322    os << "{\"output\" : " << json_op << ",";
1323    os << "\"operands\": [";
1324    bool op_needs_comma = false;
1325    for (int input : phi->operands()) {
1326      if (op_needs_comma) os << ",";
1327      op_needs_comma = true;
1328      os << "\"v" << input << "\"";
1329    }
1330    os << "]}";
1331  }
1332  os << "],";
1333
1334  os << "\"instructions\": [";
1335  InstructionAsJSON json_instr = {-1, nullptr, code};
1336  need_comma = false;
1337  for (int j = block->first_instruction_index();
1338       j <= block->last_instruction_index(); j++) {
1339    if (need_comma) os << ",";
1340    need_comma = true;
1341    json_instr.index_ = j;
1342    json_instr.instr_ = code->InstructionAt(j);
1343    os << json_instr;
1344  }
1345  os << "]";
1346  os << "}";
1347
1348  return os;
1349}
1350
1351std::ostream& operator<<(std::ostream& os, const InstructionSequenceAsJSON& s) {
1352  const InstructionSequence* code = s.sequence_;
1353
1354  os << "[";
1355
1356  bool need_comma = false;
1357  for (int i = 0; i < code->InstructionBlockCount(); i++) {
1358    if (need_comma) os << ",";
1359    need_comma = true;
1360    os << InstructionBlockAsJSON{
1361        code->InstructionBlockAt(RpoNumber::FromInt(i)), code};
1362  }
1363  os << "]";
1364
1365  return os;
1366}
1367
1368}  // namespace compiler
1369}  // namespace internal
1370}  // namespace v8
1371