Store num args instead of offset in prologue and func entry SrcKeys
[hiphop-php.git] / hphp / runtime / vm / jit / print.cpp
blobdf0a6eada56aff68b6728690c1a7ce202ed6f634
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/print.h"
19 #include <folly/dynamic.h>
20 #include <folly/json.h>
22 #include <iomanip>
23 #include <iostream>
24 #include <sstream>
25 #include <vector>
26 #include <algorithm>
28 #include "hphp/util/arch.h"
29 #include "hphp/util/disasm.h"
30 #include "hphp/util/struct-log.h"
31 #include "hphp/util/text-color.h"
32 #include "hphp/util/text-util.h"
34 #include "hphp/runtime/base/rds.h"
35 #include "hphp/runtime/base/stats.h"
37 #include "hphp/runtime/vm/jit/array-access-profile.h"
38 #include "hphp/runtime/vm/jit/array-iter-profile.h"
39 #include "hphp/runtime/vm/jit/asm-info.h"
40 #include "hphp/runtime/vm/jit/block.h"
41 #include "hphp/runtime/vm/jit/call-target-profile.h"
42 #include "hphp/runtime/vm/jit/cfg.h"
43 #include "hphp/runtime/vm/jit/cls-cns-profile.h"
44 #include "hphp/runtime/vm/jit/decref-profile.h"
45 #include "hphp/runtime/vm/jit/incref-profile.h"
46 #include "hphp/runtime/vm/jit/containers.h"
47 #include "hphp/runtime/vm/jit/guard-constraints.h"
48 #include "hphp/runtime/vm/jit/ir-opcode.h"
49 #include "hphp/runtime/vm/jit/mcgen.h"
50 #include "hphp/runtime/vm/jit/meth-profile.h"
51 #include "hphp/runtime/vm/jit/switch-profile.h"
52 #include "hphp/runtime/vm/jit/type-profile.h"
54 #include "hphp/vixl/a64/disasm-a64.h"
56 namespace HPHP::jit {
58 ///////////////////////////////////////////////////////////////////////////////
60 namespace {
62 ///////////////////////////////////////////////////////////////////////////////
64 // Helper for pretty-printing punctuation.
65 std::string punc(const char* str) {
66 return folly::format("{}{}{}",
67 color(ANSI_COLOR_DARK_GRAY), str, color(ANSI_COLOR_END)).str();
70 std::string constToString(Type t) {
71 std::ostringstream os;
72 os << color(ANSI_COLOR_LIGHT_BLUE)
73 << t.constValString()
74 << color(ANSI_COLOR_END);
75 return os.str();
78 void printSrc(std::ostream& ostream, const IRInstruction* inst, uint32_t i) {
79 SSATmp* src = inst->src(i);
80 if (src != nullptr) {
81 print(ostream, src);
82 } else {
83 ostream << color(ANSI_COLOR_RED)
84 << "!!!NULL @ " << i
85 << color(ANSI_COLOR_END)
90 void printLabel(std::ostream& os, const Block* block) {
91 os << color(ANSI_COLOR_MAGENTA);
92 os << "B" << block->id();
93 if (block->isCatch()) {
94 os << "<Catch>";
95 } else {
96 switch (block->hint()) {
97 case Block::Hint::Unused: os << "<Unused>"; break;
98 case Block::Hint::Unlikely: os << "<Unlikely>"; break;
99 case Block::Hint::Likely: os << "<Likely>"; break;
100 default:
101 break;
104 os << color(ANSI_COLOR_END);
107 // Simple tuple-like class used to order instructions for printing.
108 struct InstAreaRange {
109 // order by instruction index, area then instruction range.
110 bool operator<(const InstAreaRange& other) const {
111 return (m_instIdx < other.m_instIdx ||
112 (m_instIdx == other.m_instIdx &&
113 (m_area < other.m_area ||
114 (m_area == other.m_area &&
115 (m_instRange.begin() < other.m_instRange.begin() ||
116 (m_instRange.begin() == other.m_instRange.begin() &&
117 (m_instRange.end() < other.m_instRange.end())))))));
120 bool isContiguous(const InstAreaRange& other) const {
121 // TODO(T52857006) - check assertions as described in D16623372
122 return (m_instRange.end() == other.m_instRange.begin() &&
123 m_area == other.m_area &&
124 m_instIdx == other.m_instIdx);
127 InstAreaRange(size_t instIdx,
128 AreaIndex area,
129 TcaRange instRange)
130 : m_instIdx(instIdx),
131 m_area(area),
132 m_instRange(instRange)
135 size_t m_instIdx{0};
136 AreaIndex m_area{AreaIndex::Main};
137 TcaRange m_instRange;
140 bool dumpPrettyIR(int level) {
141 return HPHP::Trace::moduleEnabledRelease(HPHP::Trace::printir, level) ||
142 (RuntimeOption::EvalDumpIR >= level);
145 bool dumpJsonIR(int level) {
146 return HPHP::Trace::moduleEnabledRelease(HPHP::Trace::printir_json, level) ||
147 (RuntimeOption::EvalDumpIRJson >= level);
150 bool dumpRuntimeIR(int level) {
151 return RuntimeOption::EvalDumpIR >= level ||
152 RuntimeOption::EvalDumpIRJson >= level;
155 ///////////////////////////////////////////////////////////////////////////////
159 static constexpr auto kIndent = 4;
161 namespace get_json {
162 using folly::dynamic;
164 dynamic getSSATmp(const SSATmp* tmp) {
165 auto const type = tmp->inst()->is(DefConst)
166 ? tmp->type().constValString()
167 : tmp->type().toString();
168 return dynamic::object("id", tmp->id())("type", type);
171 dynamic getLabel(const Block* block) {
172 dynamic id = block->id();
174 return dynamic::object("id", id)
175 ("isCatch", block->isCatch())
176 ("hint", blockHintName(block->hint()));
180 dynamic getOpcode(const IRInstruction* inst,
181 const GuardConstraints* constraints) {
182 const dynamic typeParam = inst->hasTypeParam() ?
183 inst->typeParam().toString() :
184 dynamic(nullptr);
185 const dynamic extra = inst->hasExtra() ?
186 showExtra(inst->op(), inst->rawExtra()) :
187 dynamic(nullptr);
189 const bool isGuard = constraints &&
190 !inst->isTransient() &&
191 isGuardOp(inst->op());
192 dynamic guard;
193 if (isGuard) {
194 auto const it = constraints->guards.find(inst);
195 guard = (it == constraints->guards.end() ?
196 "unused" :
197 it->second.toString());
198 } else {
199 guard = dynamic(nullptr);
202 return dynamic::object("opcodeName", opcodeName(inst->op()))
203 ("typeParam", typeParam)
204 ("extra", extra)
205 ("guard", guard);
208 dynamic getSrcs(const IRInstruction* inst) {
209 // TODO(T52857257)
210 if (inst->op() == IncStat) {
211 return dynamic::object("counterName",
212 Stats::g_counterNames[inst->src(0)->intVal()]);
214 dynamic srcs = dynamic::array;
215 for (uint32_t i = 0, n = inst->numSrcs(); i < n; i++) {
216 srcs.push_back(getSSATmp(inst->src(i)));
218 return srcs;
221 dynamic getDsts(const IRInstruction* inst) {
222 dynamic dsts = dynamic::array;
223 for (uint32_t i = 0, n = inst->numDsts(); i < n; i++) {
224 dsts.push_back(getSSATmp(inst->dst(i)));
226 return dsts;
229 dynamic getIRInstruction(const IRInstruction& inst,
230 const GuardConstraints* guards) {
231 dynamic result = dynamic::object;
232 dynamic markerObj = dynamic::object;
233 std::ostringstream mStr;
234 std::ostringstream funcStr;
235 auto const sk = inst.marker().sk();
236 if (!sk.valid()) {
237 markerObj = dynamic(nullptr);
238 } else {
239 mStr << std::string(kIndent, ' ')
240 << inst.marker().show()
241 << std::endl
242 << std::string(kIndent, ' ')
243 << sk.showInst()
244 << std::endl;
245 // TODO(T46690139)
246 std::vector<std::string> vec;
247 folly::split('\n', mStr.str(), vec);
248 for (auto const& s : vec) {
249 if (s.empty()) continue;
250 funcStr << s << '\n';
252 markerObj["raw"] = funcStr.str();
254 result["marker"] = markerObj;
256 dynamic phiPseudoInstrs = dynamic::array;
257 if (inst.op() == DefLabel) {
258 // print phi pseudo-instructions
259 for (unsigned i = 0, n = inst.numDsts(); i < n; ++i) {
260 dynamic phiPseudoInstr = dynamic::object;
261 phiPseudoInstr["dst"] = getSSATmp(inst.dst(i));
263 dynamic srcs = dynamic::array;
264 inst.block()->forEachSrc(i, [&](IRInstruction* jmp, SSATmp*) {
265 srcs.push_back(dynamic::object("src", getSSATmp(jmp->src(i)))
266 ("label", getLabel(jmp->block())));
268 phiPseudoInstr["srcs"] = srcs;
270 phiPseudoInstrs.push_back(phiPseudoInstr);
273 result["phiPseudoInstrs"] = phiPseudoInstrs;
275 const dynamic id = inst.isTransient() ? dynamic(nullptr) : inst.id();
277 const Block* taken = inst.taken();
278 const dynamic takenObj = taken ? getLabel(taken) : dynamic(nullptr);
280 result.update(dynamic::merge(getOpcode(&inst, guards),
281 dynamic::object("id", id)
282 ("taken", takenObj)
283 ("srcs", getSrcs(&inst))
284 ("dsts", getDsts(&inst))
285 ("offset", sk.printableOffset())
286 ("iroff", inst.iroff())
287 ("startLine", sk.lineNumber())));
288 return result;
291 dynamic getTCRange(const AreaIndex area,
292 const TransKind kind,
293 const TcaRange& range,
294 uint64_t offset) {
295 std::ostringstream disasmStr;
296 disasmRange(disasmStr, kind, range.begin(), range.end(), offset);
297 auto const startStr = folly::sformat("{}", static_cast<void*>(range.begin()));
298 auto const endStr = folly::sformat("{}", static_cast<void*>(range.end()));
299 return dynamic::object("area", areaAsString(area))
300 ("start", startStr)
301 ("end", endStr)
302 ("disasm", disasmStr.str());
305 dynamic getBlock(const Block* block,
306 const TransKind kind,
307 const AsmInfo* asmInfo,
308 const GuardConstraints* guards,
309 const IRUnit& unit) {
310 dynamic result = dynamic::object;
312 result["label"] = getLabel(block);
313 result["profCount"] = block->profCount();
315 dynamic predIds = dynamic::array;
317 auto const& preds = block->preds();
318 if (!preds.empty()) {
319 for (auto const& edge : preds) {
320 predIds.push_back(edge.from()->id());
323 result["preds"] = predIds;
324 result["next"] = block->next() ? getLabel(block->next()) : dynamic(nullptr);
325 result["instrs"] = dynamic::array;
327 if (block->empty()) return result;
329 if (asmInfo) {
330 std::vector<const IRInstruction*> instrs;
331 std::vector<InstAreaRange> instRanges;
332 std::array<TcaRange, kNumAreas> lastRange;
333 size_t instIdx = 0;
335 // Collect all the instruction ranges for the current block and sort
336 // them. Entries will be sorted by instruction index, area then machine
337 // code pc. This reflects the order in which code will be printed.
338 // IR instructions with no associated assembly will still get entries
339 // in the instRanges vector so they will be printed too (they just get
340 // an empty machine code range).
341 for (auto it = block->begin(); it != block->end(); ++it, ++instIdx) {
342 const auto& inst = *it;
343 const size_t lastInstRangesSize = instRanges.size();
344 instrs.push_back(&inst); // Map back to IRInstruction from index.
346 for (auto i = 0; i < kNumAreas; ++i) {
347 const auto instArea = static_cast<AreaIndex>(i);
348 auto const& areaRanges = asmInfo->instRangesForArea(instArea);
349 auto const& rngs = areaRanges[inst];
350 for (auto itr = rngs.first; itr != rngs.second; ++itr) {
351 auto const range = TcaRange(itr->second.start() + areaRanges.offset,
352 itr->second.end() + areaRanges.offset);
353 instRanges.push_back(InstAreaRange(instIdx, instArea, range));
354 lastRange[(int)instArea] = range;
358 // Add an entry for IRInstructions that have no associated machine
359 // code. Use the end address of the last instruction range to assign
360 // an empty range to this element.
361 if (instRanges.size() == lastInstRangesSize) {
362 instRanges.push_back(
363 InstAreaRange(instIdx,
364 AreaIndex::Main,
365 TcaRange(lastRange[(int)AreaIndex::Main].end(),
366 lastRange[(int)AreaIndex::Main].end())));
370 std::sort(instRanges.begin(), instRanges.end());
372 std::vector<InstAreaRange> collatedInstRanges;
373 for (auto const& inst : instRanges) {
374 if (collatedInstRanges.empty()) {
375 collatedInstRanges.push_back(inst);
376 continue;
379 auto& prevRange = collatedInstRanges.back();
380 if (prevRange.isContiguous(inst)) {
381 prevRange.m_instRange = TcaRange(prevRange.m_instRange.begin(),
382 inst.m_instRange.end());
383 } else {
384 collatedInstRanges.push_back(inst);
387 instRanges = collatedInstRanges;
389 const IRInstruction* lastInst = nullptr;
390 AreaIndex lastArea = AreaIndex::Main;
391 bool printArea = false;
393 dynamic currInstrObj = (dynamic) nullptr;
395 for (auto itr = instRanges.begin(); itr != instRanges.end(); ++itr) {
396 auto const currInstIdx = itr->m_instIdx;
397 auto const currInst = instrs[currInstIdx];
398 auto const currArea = itr->m_area;
399 auto const instRange = itr->m_instRange;
400 if (lastInst != currInst) {
401 if (!currInstrObj.isNull()) {
402 result["instrs"].push_back(currInstrObj);
404 currInstrObj = dynamic::merge(getIRInstruction(*currInst, guards),
405 dynamic::object("tc_ranges",
406 dynamic::array));
407 printArea = true;
408 lastInst = currInst;
409 lastRange[(int)currArea] = TcaRange(nullptr,nullptr);
411 if (printArea || currArea != lastArea) {
412 lastArea = currArea;
413 printArea = false;
414 lastRange[(int)currArea] = TcaRange(nullptr,nullptr);
417 const auto lastEnd = lastRange[(int)currArea].end();
419 auto const offset = asmInfo->instRangesForArea(currArea).offset;
420 if (lastEnd && lastEnd != instRange.begin()) {
421 // There may be gaps between instruction ranges that have been
422 // added by the relocator, e.g. adding nops. This check will
423 // determine if the gap belongs to another instruction or not.
424 // If it doesn't belong to any other instruction then print it.
425 auto const gapRange = TcaRange(lastEnd - offset,
426 instRange.begin() - offset);
427 if (!asmInfo->instRangeExists(currArea, gapRange)) {
428 currInstrObj["tc_ranges"].push_back(getTCRange(currArea,
429 kind,
430 TcaRange(
431 lastEnd,
432 instRange.begin()),
433 offset));
436 currInstrObj["tc_ranges"].push_back(getTCRange(currArea,
437 kind,
438 instRange,
439 offset));
440 lastRange[(int)currArea] = instRange;
442 if (!currInstrObj.isNull()) {
443 result["instrs"].push_back(currInstrObj);
445 } else {
446 for (auto it = block->begin(); it != block->end(); ++it) {
447 result["instrs"].push_back(dynamic::merge(getIRInstruction(*it, guards),
448 dynamic::object("tc_ranges",
449 dynamic())));
453 return result;
456 dynamic getSrcKey(const SrcKey& sk) {
457 auto const unit = sk.unit();
458 return dynamic::object("func", sk.func()->name()->slice())
459 ("unit", unit->origFilepath()->slice())
460 ("prologue", sk.prologue())
461 ("funcEntry", sk.funcEntry())
462 ("offset", sk.printableOffset())
463 ("resumeMode", resumeModeShortName(sk.resumeMode()))
464 ("hasThis", sk.hasThis())
465 ("startLine", sk.lineNumber());
468 dynamic getTransContext(const TransContext& ctx) {
469 auto const func = ctx.initSrcKey.func();
470 return dynamic::object("kind", show(ctx.kind))
471 ("id", folly::join(",", ctx.transIDs))
472 ("optIndex", ctx.optIndex)
473 ("srcKey", getSrcKey(ctx.initSrcKey))
474 ("funcName", func->fullName()->data())
475 ("sourceFile", func->filename()->data())
476 ("startLine", func->line1())
477 ("endLine", func->line2());
480 dynamic getUnit(const IRUnit& unit,
481 const AsmInfo* asmInfo,
482 const GuardConstraints* guards) {
483 dynamic result = dynamic::object;
485 auto const& ctx = unit.context();
486 auto const kind = ctx.kind;
487 result["translation"] = getTransContext(ctx);
489 result["inliningDecisions"] = unit.annotationData ?
490 unit.annotationData->getInliningDynamic() :
491 dynamic::array();
493 auto blocks = rpoSortCfg(unit);
494 // Partition into main, cold and frozen, without changing relative order.
495 auto const cold = std::stable_partition(blocks.begin(), blocks.end(),
496 [&] (Block* b) {
497 return b->hint() == Block::Hint::Neither ||
498 b->hint() == Block::Hint::Likely;
501 auto const frozen = std::stable_partition(cold, blocks.end(),
502 [&] (Block* b) { return b->hint() == Block::Hint::Unlikely; }
505 dynamic blockObjs = dynamic::array;
507 AreaIndex currArea = AreaIndex::Main;
508 for (auto it = blocks.begin(); it != blocks.end(); ++it) {
509 if (it == cold) {
510 currArea = AreaIndex::Cold;
512 if (it == frozen) {
513 currArea = AreaIndex::Frozen;
516 blockObjs.push_back(dynamic::merge(
517 getBlock(*it, kind, asmInfo, guards, unit),
518 dynamic::object("area", areaAsString(currArea))));
520 result["blocks"] = blockObjs;
521 return result;
526 ///////////////////////////////////////////////////////////////////////////////
527 // IRInstruction.
529 void printOpcode(std::ostream& os, const IRInstruction* inst,
530 const GuardConstraints* constraints) {
531 os << color(ANSI_COLOR_CYAN)
532 << opcodeName(inst->op())
533 << color(ANSI_COLOR_END)
536 auto const hasTypeParam = inst->hasTypeParam();
537 auto const hasExtra = inst->hasExtra();
538 auto const isGuard =
539 constraints && !inst->isTransient() && isGuardOp(inst->op());
541 if (!hasTypeParam && !hasExtra && !isGuard) return;
542 os << color(ANSI_COLOR_LIGHT_BLUE) << '<' << color(ANSI_COLOR_END);
544 if (hasTypeParam) {
545 os << color(ANSI_COLOR_GREEN)
546 << inst->typeParam().toString()
547 << color(ANSI_COLOR_END)
549 if (hasExtra || isGuard) os << punc(",");
552 if (hasExtra) {
553 os << color(ANSI_COLOR_GREEN)
554 << showExtra(inst->op(), inst->rawExtra())
555 << color(ANSI_COLOR_END);
556 if (isGuard) os << punc(",");
559 if (isGuard) {
560 auto it = constraints->guards.find(inst);
561 os << (it == constraints->guards.end() ? "unused" : it->second.toString());
564 os << color(ANSI_COLOR_LIGHT_BLUE)
565 << '>'
566 << color(ANSI_COLOR_END);
569 void printSrcs(std::ostream& os, const IRInstruction* inst) {
570 bool first = true;
571 if (inst->op() == IncStat) {
572 os << " " << Stats::g_counterNames[inst->src(0)->intVal()];
573 return;
575 for (uint32_t i = 0, n = inst->numSrcs(); i < n; i++) {
576 if (!first) {
577 os << punc(", ");
578 } else {
579 os << " ";
580 first = false;
582 printSrc(os, inst, i);
586 void printDsts(std::ostream& os, const IRInstruction* inst) {
587 const char* sep = "";
588 for (unsigned i = 0, n = inst->numDsts(); i < n; i++) {
589 os << punc(sep);
590 print(os, inst->dst(i));
591 sep = ", ";
595 void printInstr(std::ostream& ostream, const IRInstruction* inst,
596 const GuardConstraints* guards) {
597 printDsts(ostream, inst);
598 if (inst->numDsts()) ostream << punc(" = ");
599 printOpcode(ostream, inst, guards);
600 printSrcs(ostream, inst);
603 void print(std::ostream& ostream, const IRInstruction* inst,
604 const GuardConstraints* guards) {
605 if (!inst->isTransient()) {
606 ostream << color(ANSI_COLOR_YELLOW);
607 ostream << folly::format("({:02d}) ", inst->id());
608 ostream << color(ANSI_COLOR_END);
610 printInstr(ostream, inst, guards);
611 if (Block* taken = inst->taken()) {
612 ostream << punc(" -> ");
613 printLabel(ostream, taken);
617 void print(const IRInstruction* inst) {
618 print(std::cerr, inst);
619 std::cerr << std::endl;
622 ///////////////////////////////////////////////////////////////////////////////
623 // SSATmp.
625 void print(std::ostream& os, const SSATmp* tmp) {
626 if (tmp->inst()->is(DefConst)) {
627 os << constToString(tmp->type());
628 return;
630 os << color(ANSI_COLOR_WHITE);
631 os << "t" << tmp->id();
632 os << color(ANSI_COLOR_END);
633 os << punc(":")
634 << color(ANSI_COLOR_GREEN)
635 << tmp->type().toString()
636 << color(ANSI_COLOR_END)
640 void print(const SSATmp* tmp) {
641 print(std::cerr, tmp);
642 std::cerr << std::endl;
645 ///////////////////////////////////////////////////////////////////////////////
646 // Block.
648 void disasmRange(std::ostream& os,
649 TransKind kind,
650 TCA begin,
651 TCA end,
652 uint64_t adjust,
653 bool useColor) {
654 assertx(begin <= end);
655 if (!dumpIREnabled(kind, kDisasmLevel)) return;
656 int const indent = kIndent + 4;
657 bool const printEncoding = dumpIREnabled(kind, kAsmEncodingLevel);
658 char const* colorStr = useColor ? color(ANSI_COLOR_BROWN) : "";
660 switch (arch()) {
661 case Arch::X64: {
662 Disasm disasm(Disasm::Options().indent(indent)
663 .printEncoding(printEncoding)
664 .color(colorStr));
665 disasm.disasm(os, begin, end, adjust);
666 return;
669 case Arch::ARM: {
670 vixl::Decoder dec;
671 vixl::PrintDisassembler disasm(os, indent, printEncoding, colorStr);
672 disasm.setShouldDereferencePCRelativeLiterals(true);
673 dec.AppendVisitor(&disasm);
674 for (; begin < end; begin += vixl::kInstructionSize) {
675 dec.Decode(vixl::Instruction::Cast(begin));
677 return;
681 not_reached();
684 template <typename T>
685 std::vector<TcaRange> makeSortedRanges(const T& itrPair) {
686 std::vector<TcaRange> ranges;
687 for (auto itr = itrPair.first; itr != itrPair.second; ++itr) {
688 ranges.push_back(itr->second);
690 std::sort(ranges.begin(),
691 ranges.end(),
692 [](const TcaRange& a, const TcaRange& b) {
693 return a.begin() < b.begin();
695 return ranges;
698 void printIRInstruction(std::ostream& os,
699 const IRInstruction& inst,
700 const GuardConstraints* guards,
701 BCMarker& curMarker,
702 const char*& markerEndl) {
703 if (inst.marker() != curMarker) {
704 std::ostringstream mStr;
705 auto const& newMarker = inst.marker();
706 if (!newMarker.hasFunc()) {
707 os << color(ANSI_COLOR_BLUE)
708 << std::string(kIndent, ' ')
709 << "--- invalid marker"
710 << color(ANSI_COLOR_END)
711 << std::endl;
712 } else {
713 mStr << std::string(kIndent, ' ')
714 << newMarker.show()
715 << std::endl
716 << std::string(kIndent, ' ')
717 << newMarker.sk().showInst()
718 << std::endl;
720 std::vector<std::string> vec;
721 folly::split('\n', mStr.str(), vec);
722 os << markerEndl;
723 markerEndl = "\n";
724 for (auto& s : vec) {
725 if (s.empty()) continue;
726 os << color(ANSI_COLOR_BLUE) << s << color(ANSI_COLOR_END) << '\n';
730 curMarker = newMarker;
733 if (inst.op() == DefLabel) {
734 // print phi pseudo-instructions
735 for (unsigned i = 0, n = inst.numDsts(); i < n; ++i) {
736 os << std::string(kIndent +
737 folly::format("({}) ", inst.id()).str().size(),
738 ' ');
739 auto dst = inst.dst(i);
740 jit::print(os, dst);
741 os << punc(" = ") << color(ANSI_COLOR_CYAN) << "phi "
742 << color(ANSI_COLOR_END);
743 bool first = true;
744 inst.block()->forEachSrc(i, [&](IRInstruction* jmp, SSATmp*) {
745 if (!first) os << punc(", ");
746 first = false;
747 printSrc(os, jmp, i);
748 os << punc("@");
749 printLabel(os, jmp->block());
751 os << '\n';
755 os << std::string(kIndent, ' ');
756 jit::print(os, &inst, guards);
757 os << '\n';
760 void print(std::ostream& os, const Block* block, TransKind kind,
761 const AsmInfo* asmInfo, const GuardConstraints* guards,
762 BCMarker* markerPtr) {
763 BCMarker dummy;
764 BCMarker& curMarker = markerPtr ? *markerPtr : dummy;
766 os << '\n' << std::string(kIndent - 3, ' ');
767 printLabel(os, block);
768 os << punc(":") << " [profCount=" << block->profCount() << "]";
770 switch (block->hint()) {
771 case Block::Hint::Unused: os << "<Unused>"; break;
772 case Block::Hint::Unlikely: os << "<Unlikely>"; break;
773 case Block::Hint::Likely: os << "<Likely>"; break;
774 default: break;
777 auto& preds = block->preds();
778 if (!preds.empty()) {
779 os << " (preds";
780 for (auto const& edge : preds) {
781 os << " B" << edge.from()->id();
783 os << ')';
785 os << "\n";
787 if (block->empty()) {
788 os << std::string(kIndent, ' ') << "empty block\n";
789 return;
792 const char* markerEndl = "";
794 if (asmInfo) {
795 std::vector<const IRInstruction*> instrs;
796 std::vector<InstAreaRange> instRanges;
797 TcaRange lastRange[kNumAreas];
798 size_t instIdx = 0;
800 // Collect all the instruction ranges for the current block and sort
801 // them. Entries will be sorted by instruction index, area then machine
802 // code pc. This reflects the order in which code will be printed.
803 // IR instructions with no associated assembly will still get entries
804 // in the instRanges vector so they will be printed too (they just get
805 // an empty machine code range).
806 for (auto it = block->begin(); it != block->end(); ++it, ++instIdx) {
807 const auto& inst = *it;
808 const size_t lastInstRangesSize = instRanges.size();
809 instrs.push_back(&inst); // Map back to IRInstruction from index.
811 for (auto i = 0; i < kNumAreas; ++i) {
812 const auto instArea = static_cast<AreaIndex>(i);
813 auto const& areaRanges = asmInfo->instRangesForArea(instArea);
814 auto const& rngs = areaRanges[inst];
815 for (auto itr = rngs.first; itr != rngs.second; ++itr) {
816 auto range = TcaRange { itr->second.start() + areaRanges.offset,
817 itr->second.end() + areaRanges.offset };
818 instRanges.push_back(InstAreaRange(instIdx, instArea, range));
819 lastRange[(int)instArea] = range;
823 // Add an entry for IRInstructions that have no associated machine
824 // code. Use the end address of the last instruction range to assign
825 // an empty range to this element.
826 if (instRanges.size() == lastInstRangesSize) {
827 instRanges.push_back(
828 InstAreaRange(instIdx,
829 AreaIndex::Main,
830 TcaRange(lastRange[(int)AreaIndex::Main].end(),
831 lastRange[(int)AreaIndex::Main].end())));
835 std::sort(instRanges.begin(), instRanges.end());
837 const IRInstruction* lastInst = nullptr;
838 AreaIndex lastArea = AreaIndex::Main;
839 bool printArea = false;
840 for (auto itr = instRanges.begin(); itr != instRanges.end(); ++itr) {
841 auto currInstIdx = itr->m_instIdx;
842 auto currInst = instrs[currInstIdx];
843 auto currArea = itr->m_area;
844 auto instRange = itr->m_instRange;
845 if (lastInst != currInst) {
846 if (lastInst && !lastRange[(int)currArea].empty()) os << "\n";
847 printIRInstruction(os, *currInst, guards, curMarker, markerEndl);
848 printArea = true;
849 lastInst = currInst;
850 lastRange[(int)currArea] = TcaRange{nullptr,nullptr};
852 if (printArea || currArea != lastArea) {
853 if (!instRange.empty()) {
854 if (!printArea) os << "\n";
855 os << std::string(kIndent + 4, ' ') << areaAsString(currArea);
856 os << ":\n";
858 lastArea = currArea;
859 printArea = false;
860 lastRange[(int)currArea] = TcaRange{nullptr,nullptr};
863 const auto lastEnd = lastRange[(int)currArea].end();
865 auto const offset = asmInfo->instRangesForArea(currArea).offset;
866 if (lastEnd && lastEnd != instRange.begin()) {
867 // There may be gaps between instruction ranges that have been
868 // added by the relocator, e.g. adding nops. This check will
869 // determine if the gap belongs to another instruction or not.
870 // If it doesn't belong to any other instruction then print it.
871 if (!asmInfo->instRangeExists(currArea,
872 TcaRange(lastEnd - offset,
873 instRange.begin() - offset))) {
874 disasmRange(os, kind, lastEnd, instRange.begin(), offset, true);
875 } else {
876 os << "\n";
879 disasmRange(os, kind, instRange.begin(), instRange.end(), offset, true);
880 lastRange[(int)currArea] = instRange;
882 os << "\n";
883 } else {
884 for (auto it = block->begin(); it != block->end(); ++it) {
885 printIRInstruction(os, *it, guards, curMarker, markerEndl);
889 os << std::string(kIndent - 2, ' ');
890 auto next = block->empty() ? nullptr : block->next();
891 if (next) {
892 os << punc("-> ");
893 printLabel(os, next);
894 os << '\n';
895 } else {
896 os << "no fallthrough\n";
900 void print(const Block* block) {
901 print(std::cerr, block, TransKind::Optimize);
902 std::cerr << std::endl;
905 std::string Block::toString() const {
906 std::ostringstream out;
907 print(out, this, TransKind::Optimize);
908 return out.str();
911 ///////////////////////////////////////////////////////////////////////////////
912 // Unit.
914 void printOpcodeStats(std::ostream& os, const BlockList& blocks) {
915 uint32_t counts[kNumOpcodes];
916 memset(counts, 0, sizeof(counts));
918 for (auto block : blocks) {
919 for (auto& inst : *block) ++counts[static_cast<size_t>(inst.op())];
922 os << "\nopcode counts:\n";
923 for (unsigned i = 0; i < kNumOpcodes; ++i) {
924 if (counts[i] == 0) continue;
925 auto op = safe_cast<Opcode>(i);
926 os << folly::format("{:>5} {}\n", counts[i], opcodeName(op));
928 os << '\n';
931 void print(std::ostream& os, const IRUnit& unit, const AsmInfo* asmInfo,
932 const GuardConstraints* guards) {
933 // For nice-looking dumps, we want to remember curMarker between blocks.
934 BCMarker curMarker;
935 static bool dotBodies = getenv("HHIR_DOT_BODIES");
936 auto const kind = unit.context().kind;
937 os << "TransKind: " << show(kind) << "\n";
938 if (unit.context().kind == TransKind::Optimize) {
939 os << "OptIndex : " << unit.context().optIndex << "\n";
941 auto blocks = rpoSortCfg(unit);
942 // Partition into main, cold and frozen, without changing relative order.
943 auto cold = std::stable_partition(blocks.begin(), blocks.end(),
944 [&] (Block* b) {
945 return b->hint() == Block::Hint::Neither ||
946 b->hint() == Block::Hint::Likely;
949 auto frozen = std::stable_partition(cold, blocks.end(),
950 [&] (Block* b) { return b->hint() == Block::Hint::Unlikely; }
953 if (dumpIREnabled(kind, kExtraExtraLevel)) printOpcodeStats(os, blocks);
955 // Print the block CFG above the actual code.
957 auto const retreating_edges = findRetreatingEdges(unit);
958 os << "digraph G {\n";
959 for (auto block : blocks) {
960 if (block->empty()) continue;
961 if (dotBodies) {
962 if (block->hint() != Block::Hint::Unlikely &&
963 block->hint() != Block::Hint::Unused) {
964 // Include the IR in the body of the node
965 std::ostringstream out;
966 print(out, block, kind, asmInfo, guards, &curMarker);
967 auto bodyRaw = out.str();
968 std::string body;
969 body.reserve(bodyRaw.size() * 1.25);
970 for (auto c : bodyRaw) {
971 if (c == '\n') body += "\\n";
972 else if (c == '"') body += "\\\"";
973 else if (c == '\\') body += "\\\\";
974 else body += c;
976 os << folly::format("B{} [shape=box,label=\"{}\"]\n",
977 block->id(), body);
979 } else {
980 const auto color = [&] {
981 switch (block->hint()) {
982 case Block::Hint::Likely : return "red";
983 case Block::Hint::Neither: return "orange";
984 case Block::Hint::Unlikely: return "blue";
985 case Block::Hint::Unused: return "gray";
987 not_reached();
988 }();
989 os << folly::format(
990 "B{} [shape=box,color={},label=\"B{}\\ncount={}\"]\n",
991 block->id(), color, block->id(), block->profCount()
995 auto next = block->nextEdge();
996 auto taken = block->takenEdge();
997 if (!next && !taken) continue;
998 auto edge_color = [&] (Edge* edge) {
999 auto const target = edge->to();
1000 return
1001 target->isCatch() ? " [color=gray]" :
1002 target->hint() == Block::Hint::Unlikely ? " [color=blue]" :
1003 retreating_edges.count(edge) ? " [color=red]" : "";
1005 auto show_edge = [&] (Edge* edge) {
1006 os << folly::format(
1007 "B{} -> B{}{}",
1008 block->id(),
1009 edge->to()->id(),
1010 edge_color(edge)
1013 if (next) {
1014 show_edge(next);
1015 if (taken) os << "; ";
1017 if (taken) show_edge(taken);
1018 os << "\n";
1020 os << "}\n";
1022 curMarker = BCMarker();
1023 for (auto it = blocks.begin(); it != blocks.end(); ++it) {
1024 if (it == cold) {
1025 os << folly::format("\n{:-^60}", "cold blocks");
1027 if (it == frozen) {
1028 os << folly::format("\n{:-^60}", "frozen blocks");
1030 print(os, *it, kind, asmInfo, guards, &curMarker);
1034 void print(const IRUnit& unit) {
1035 print(std::cerr, unit);
1036 std::cerr << std::endl;
1039 std::string show(const IRUnit& unit) {
1040 std::ostringstream out;
1041 print(out, unit);
1042 return out.str();
1045 std::string banner(const char* caption) {
1046 return folly::sformat(
1047 "{}{:-^80}{}\n",
1048 color(ANSI_COLOR_BLACK, ANSI_BGCOLOR_GREEN),
1049 caption,
1050 color(ANSI_COLOR_END)
1054 // Suggested captions: "before jiffy removal", "after goat saturation",
1055 // etc.
1056 void printUnit(int level, const IRUnit& unit, const char* caption,
1057 AsmInfo* ai,
1058 const GuardConstraints* guards, Annotations* annotations) {
1059 if (dumpIREnabled(unit.context().kind, level)) {
1060 std::ostringstream str;
1061 if (dumpPrettyIR(level)) {
1062 str << banner(caption);
1063 print(str, unit, ai, guards);
1064 str << banner("");
1065 if (HPHP::Trace::moduleEnabledRelease(HPHP::Trace::printir, level)) {
1066 HPHP::Trace::traceRelease("%s\n", str.str().c_str());
1068 } else if (dumpJsonIR(level)) {
1069 str << "json:" << get_json::getUnit(unit, ai, guards);
1070 if (HPHP::Trace::moduleEnabledRelease(HPHP::Trace::printir_json, level)) {
1071 HPHP::Trace::traceRelease("%s\n", str.str().c_str());
1074 if (annotations && dumpRuntimeIR(level)) {
1075 annotations->emplace_back(caption, str.str());
1080 bool dumpIREnabled(TransKind kind, int level /* = 1 */) {
1081 return HPHP::Trace::moduleEnabledRelease(HPHP::Trace::printir, level) ||
1082 HPHP::Trace::moduleEnabledRelease(HPHP::Trace::printir_json, level) ||
1083 (dumpRuntimeIR(level) &&
1084 mcgen::dumpTCAnnotation(kind));
1087 ///////////////////////////////////////////////////////////////////////////////