2 +----------------------------------------------------------------------+
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>
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"
58 ///////////////////////////////////////////////////////////////////////////////
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
)
74 << color(ANSI_COLOR_END
);
78 void printSrc(std::ostream
& ostream
, const IRInstruction
* inst
, uint32_t i
) {
79 SSATmp
* src
= inst
->src(i
);
83 ostream
<< color(ANSI_COLOR_RED
)
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()) {
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;
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
,
130 : m_instIdx(instIdx
),
132 m_instRange(instRange
)
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;
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() :
185 const dynamic extra
= inst
->hasExtra() ?
186 showExtra(inst
->op(), inst
->rawExtra()) :
189 const bool isGuard
= constraints
&&
190 !inst
->isTransient() &&
191 isGuardOp(inst
->op());
194 auto const it
= constraints
->guards
.find(inst
);
195 guard
= (it
== constraints
->guards
.end() ?
197 it
->second
.toString());
199 guard
= dynamic(nullptr);
202 return dynamic::object("opcodeName", opcodeName(inst
->op()))
203 ("typeParam", typeParam
)
208 dynamic
getSrcs(const IRInstruction
* inst
) {
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
)));
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
)));
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();
237 markerObj
= dynamic(nullptr);
239 mStr
<< std::string(kIndent
, ' ')
240 << inst
.marker().show()
242 << std::string(kIndent
, ' ')
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
)
283 ("srcs", getSrcs(&inst
))
284 ("dsts", getDsts(&inst
))
285 ("offset", sk
.printableOffset())
286 ("iroff", inst
.iroff())
287 ("startLine", sk
.lineNumber())));
291 dynamic
getTCRange(const AreaIndex area
,
292 const TransKind kind
,
293 const TcaRange
& range
,
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
))
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
;
330 std::vector
<const IRInstruction
*> instrs
;
331 std::vector
<InstAreaRange
> instRanges
;
332 std::array
<TcaRange
, kNumAreas
> lastRange
;
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
,
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
);
379 auto& prevRange
= collatedInstRanges
.back();
380 if (prevRange
.isContiguous(inst
)) {
381 prevRange
.m_instRange
= TcaRange(prevRange
.m_instRange
.begin(),
382 inst
.m_instRange
.end());
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",
409 lastRange
[(int)currArea
] = TcaRange(nullptr,nullptr);
411 if (printArea
|| currArea
!= lastArea
) {
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
,
436 currInstrObj
["tc_ranges"].push_back(getTCRange(currArea
,
440 lastRange
[(int)currArea
] = instRange
;
442 if (!currInstrObj
.isNull()) {
443 result
["instrs"].push_back(currInstrObj
);
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",
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() :
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(),
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
) {
510 currArea
= AreaIndex::Cold
;
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
;
526 ///////////////////////////////////////////////////////////////////////////////
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();
539 constraints
&& !inst
->isTransient() && isGuardOp(inst
->op());
541 if (!hasTypeParam
&& !hasExtra
&& !isGuard
) return;
542 os
<< color(ANSI_COLOR_LIGHT_BLUE
) << '<' << color(ANSI_COLOR_END
);
545 os
<< color(ANSI_COLOR_GREEN
)
546 << inst
->typeParam().toString()
547 << color(ANSI_COLOR_END
)
549 if (hasExtra
|| isGuard
) os
<< punc(",");
553 os
<< color(ANSI_COLOR_GREEN
)
554 << showExtra(inst
->op(), inst
->rawExtra())
555 << color(ANSI_COLOR_END
);
556 if (isGuard
) os
<< punc(",");
560 auto it
= constraints
->guards
.find(inst
);
561 os
<< (it
== constraints
->guards
.end() ? "unused" : it
->second
.toString());
564 os
<< color(ANSI_COLOR_LIGHT_BLUE
)
566 << color(ANSI_COLOR_END
);
569 void printSrcs(std::ostream
& os
, const IRInstruction
* inst
) {
571 if (inst
->op() == IncStat
) {
572 os
<< " " << Stats::g_counterNames
[inst
->src(0)->intVal()];
575 for (uint32_t i
= 0, n
= inst
->numSrcs(); i
< n
; i
++) {
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
++) {
590 print(os
, inst
->dst(i
));
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 ///////////////////////////////////////////////////////////////////////////////
625 void print(std::ostream
& os
, const SSATmp
* tmp
) {
626 if (tmp
->inst()->is(DefConst
)) {
627 os
<< constToString(tmp
->type());
630 os
<< color(ANSI_COLOR_WHITE
);
631 os
<< "t" << tmp
->id();
632 os
<< color(ANSI_COLOR_END
);
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 ///////////////////////////////////////////////////////////////////////////////
648 void disasmRange(std::ostream
& os
,
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
) : "";
662 Disasm
disasm(Disasm::Options().indent(indent
)
663 .printEncoding(printEncoding
)
665 disasm
.disasm(os
, begin
, end
, adjust
);
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
));
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(),
692 [](const TcaRange
& a
, const TcaRange
& b
) {
693 return a
.begin() < b
.begin();
698 void printIRInstruction(std::ostream
& os
,
699 const IRInstruction
& inst
,
700 const GuardConstraints
* guards
,
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
)
713 mStr
<< std::string(kIndent
, ' ')
716 << std::string(kIndent
, ' ')
717 << newMarker
.sk().showInst()
720 std::vector
<std::string
> vec
;
721 folly::split('\n', mStr
.str(), vec
);
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(),
739 auto dst
= inst
.dst(i
);
741 os
<< punc(" = ") << color(ANSI_COLOR_CYAN
) << "phi "
742 << color(ANSI_COLOR_END
);
744 inst
.block()->forEachSrc(i
, [&](IRInstruction
* jmp
, SSATmp
*) {
745 if (!first
) os
<< punc(", ");
747 printSrc(os
, jmp
, i
);
749 printLabel(os
, jmp
->block());
755 os
<< std::string(kIndent
, ' ');
756 jit::print(os
, &inst
, guards
);
760 void print(std::ostream
& os
, const Block
* block
, TransKind kind
,
761 const AsmInfo
* asmInfo
, const GuardConstraints
* guards
,
762 BCMarker
* markerPtr
) {
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;
777 auto& preds
= block
->preds();
778 if (!preds
.empty()) {
780 for (auto const& edge
: preds
) {
781 os
<< " B" << edge
.from()->id();
787 if (block
->empty()) {
788 os
<< std::string(kIndent
, ' ') << "empty block\n";
792 const char* markerEndl
= "";
795 std::vector
<const IRInstruction
*> instrs
;
796 std::vector
<InstAreaRange
> instRanges
;
797 TcaRange lastRange
[kNumAreas
];
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
,
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
);
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
);
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);
879 disasmRange(os
, kind
, instRange
.begin(), instRange
.end(), offset
, true);
880 lastRange
[(int)currArea
] = instRange
;
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();
893 printLabel(os
, next
);
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
);
911 ///////////////////////////////////////////////////////////////////////////////
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
));
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.
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(),
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;
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();
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
+= "\\\\";
976 os
<< folly::format("B{} [shape=box,label=\"{}\"]\n",
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";
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();
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(
1015 if (taken
) os
<< "; ";
1017 if (taken
) show_edge(taken
);
1022 curMarker
= BCMarker();
1023 for (auto it
= blocks
.begin(); it
!= blocks
.end(); ++it
) {
1025 os
<< folly::format("\n{:-^60}", "cold blocks");
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
;
1045 std::string
banner(const char* caption
) {
1046 return folly::sformat(
1048 color(ANSI_COLOR_BLACK
, ANSI_BGCOLOR_GREEN
),
1050 color(ANSI_COLOR_END
)
1054 // Suggested captions: "before jiffy removal", "after goat saturation",
1056 void printUnit(int level
, const IRUnit
& unit
, const char* caption
,
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
);
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 ///////////////////////////////////////////////////////////////////////////////