Store num args instead of offset in prologue and func entry SrcKeys
[hiphop-php.git] / hphp / tools / tc-print / tc-print.cpp
blob01eb18aae204ed9a0215a5bcabf306d35ce0efb7
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/tools/tc-print/tc-print.h"
19 #include <stdio.h>
20 #include <assert.h>
21 #include <unistd.h>
23 #include <cstdint>
24 #include <string>
25 #include <vector>
26 #include <sstream>
28 #include <boost/uuid/uuid_io.hpp>
29 #include <boost/uuid/uuid_generators.hpp>
31 #include <folly/Format.h>
32 #include <folly/dynamic.h>
33 #include <folly/DynamicConverter.h>
34 #include <folly/json.h>
35 #include <folly/Singleton.h>
37 #include "hphp/util/build-info.h"
39 #include "hphp/runtime/base/preg.h"
40 #include "hphp/runtime/base/program-functions.h"
42 #include "hphp/tools/tc-print/perf-events.h"
43 #include "hphp/tools/tc-print/printir-annotation.h"
44 #include "hphp/tools/tc-print/offline-trans-data.h"
45 #include "hphp/tools/tc-print/offline-code.h"
46 #include "hphp/tools/tc-print/mappers.h"
47 #include "hphp/tools/tc-print/repo-wrapper.h"
48 #include "hphp/tools/tc-print/std-logger.h"
49 #include "hphp/tools/tc-print/tc-print-logger.h"
50 #ifdef FACEBOOK
51 #include "hphp/facebook/extensions/scribe/ext_scribe.h"
52 #include "hphp/tools/tc-print/facebook/db-logger.h"
53 #endif
55 using namespace HPHP;
56 using namespace HPHP::jit;
58 #define MAX_SYM_LEN 10240
60 std::string dumpDir("/tmp");
61 std::string profFileName;
62 std::string repoFileName;
63 uint32_t nTopTrans = 0;
64 uint32_t nTopFuncs = 0;
65 bool useJSON = false;
66 bool creationOrder = false;
67 bool transCFG = false;
68 bool collectBCStats = false;
69 bool inclusiveStats = false;
70 bool verboseStats = false;
71 bool hostOpcodes = false;
72 Optional<SHA1> sha1Filter;
73 PerfEventType sortBy = EVENT_CYCLES;
74 bool sortByDensity = false;
75 bool sortBySize = false;
76 double helpersMinPercentage = 0;
77 ExtOpcode filterByOpcode = 0;
78 std::string kindFilter = "all";
79 uint32_t selectedFuncId = INVALID_ID;
80 std::string selectedFuncName;
81 TCA minAddr = 0;
82 TCA maxAddr = (TCA)-1;
83 uint32_t annotationsVerbosity = 2;
84 #ifdef FACEBOOK
85 bool printToDB = false;
86 std::string hiveTable;
87 #endif
89 std::vector<uint32_t> transPrintOrder;
91 RepoWrapper* g_repo;
92 OfflineTransData* g_transData;
93 OfflineCode* transCode;
95 std::unique_ptr<AnnotationCache> g_annotations;
97 const char* kListKeyword = "list";
98 TCPrintLogger* g_logger;
100 PerfEventsMap<TCA> tcaPerfEvents;
101 PerfEventsMap<TransID> transPerfEvents;
103 #define NTRANS (g_transData->getNumTrans())
104 #define NFUNCS (g_transData->getNumFuncs())
105 #define TREC(TID) (g_transData->getTransRec(TID))
107 void warnTooFew(const std::string& name,
108 uint32_t requested,
109 uint32_t available) {
110 fprintf(stderr,
111 "Requested top %u %s, but there are only %u available.\n",
112 requested,
113 name.c_str(),
114 available);
117 template<typename T>
118 std::string toString(T value) {
119 std::stringstream ss;
120 ss << value;
121 return ss.str();
124 void usage() {
125 printf("Usage: tc-print [OPTIONS]\n"
126 " Options:\n"
127 " -D : used along with -t, this option sorts the top "
128 "translations by density (count / size) of the selected perf event\n"
129 " -d <DIRECTORY> : looks for dump file in <DIRECTORY> "
130 "(default: /tmp)\n"
131 " -F <FUNC_NAME> : prints the translations for the given function\n"
132 " -f <FUNC_ID> : prints the translations for the given "
133 "<FUNC_ID>, sorted by start offset\n"
134 " -g <FUNC_ID> : prints the CFG among the translations for the "
135 "given <FUNC_ID>\n"
136 " -p <FILE> : uses raw profile data from <FILE>\n"
137 " -r <REPO_FILE> : specifies the bytecode repo file to use\n"
138 " -s : prints all translations sorted by creation "
139 "order\n"
140 " -u <SHA1> : prints all translations from the specified "
141 "unit\n"
142 " -t <NUMBER> : prints top <NUMBER> translations according to "
143 "profiling info\n"
144 " -k <TRANS_KIND> : used with -t, filters only translations of the "
145 "given kind, e.g. TransLive (default: all)\n"
146 " -a <ADDR> : used with -t, filters only events at addresses "
147 ">= <ADDR>\n"
148 " -A <ADDR> : used with -t, filters only events at addresses "
149 "<= <ADDR>\n"
150 " -T <NUMBER> : prints top <NUMBER> functions according to "
151 "profiling info\n"
152 " -e <EVENT> : sorts by the specified perf event. Should either "
153 "be one of the predefined events or one passed with -E.\n"
154 " Pass '%s' to get a list of valid predefined event "
155 "types.\n"
156 " -E <EVENTS> : specifies a new replacement set of perf events. "
157 "Pass a comma-separated list of events to replace the default set.\n"
158 " -b : prints bytecode stats\n"
159 " -B <OPCODE> : used in conjunction with -e, prints the top "
160 "bytecode translation event type. Pass '%s' to get a "
161 "list of valid opcodes.\n"
162 " -i : reports inclusive stats by including helpers "
163 "(perf data must include call graph information)\n"
164 " -n <level> : level of verbosity for annotations. Use 0 for "
165 "no annotations, 1 - for inline, 2 - to print all annotations "
166 "including from a file (default: 2).\n"
167 " -o : print host opcodes\n"
168 " -v <PERCENTAGE> : sets the minimum percentage to <PERCENTAGE> "
169 "when printing the top helpers (implies -i). The lower the percentage,"
170 " the more helpers that will show up.\n"
171 " -j : outputs tc-dump in JSON format (not compatible with "
172 "some other flags).\n"
173 // TODO(T52857399) - investigate compatibility with other flags
174 #ifdef FACEBOOK
175 " -H <HIVE_TABLE> : used with -j, write the JSON output to Hive in the "
176 "table <HIVE_TABLE>\n"
177 " -x : log translations to database\n"
178 #endif
179 " -h : prints help message\n",
180 kListKeyword,
181 kListKeyword);
184 void printValidBytecodes() {
185 g_logger->printGeneric("<OPCODE>:\n");
186 auto validOpcodes = getValidOpcodeNames();
187 for (size_t i = 0; i < validOpcodes.size(); i++) {
188 g_logger->printGeneric(" * %s\n", validOpcodes[i].first.c_str());
192 void printValidEventTypes() {
193 g_logger->printGeneric("<EVENT>:\n");
194 // Note: The -e list option is only for printing the predefined (default)
195 // event types; thus ranging from [0,NUM_PREDEFINED_EVENT_TYPES).
196 for (size_t i = 0; i < NUM_PREDEFINED_EVENT_TYPES; i++) {
197 g_logger->printGeneric(" * %s\n",
198 eventTypeToCommandLineArgument((PerfEventType)i));
202 void parseOptions(int argc, char *argv[]) {
203 int c;
204 opterr = 0;
205 char* sortByArg = nullptr;
206 while ((c = getopt(argc, argv,
207 "hDd:F:f:G:g:ip:st:u:S:T:o:r:e:E:bB:v:k:a:A:n:jH:x"))
208 != -1) {
209 switch (c) {
210 case 'A':
211 if (sscanf(optarg, "%p", &maxAddr) != 1) {
212 usage();
213 exit(1);
215 break;
216 case 'a':
217 if (sscanf(optarg, "%p", &minAddr) != 1) {
218 usage();
219 exit(1);
221 break;
222 case 'h':
223 usage();
224 exit(0);
225 case 'd':
226 dumpDir = optarg;
227 break;
228 case 'F':
229 creationOrder = true;
230 selectedFuncName = optarg;
231 break;
232 case 'f':
233 creationOrder = true;
234 if (sscanf(optarg, "%u", &selectedFuncId) != 1) {
235 usage();
236 exit(1);
238 break;
239 case 'G':
240 transCFG = true;
241 selectedFuncName = optarg;
242 break;
243 case 'g':
244 transCFG = true;
245 if (sscanf(optarg, "%u", &selectedFuncId) != 1) {
246 usage();
247 exit(1);
249 break;
250 case 'p':
251 profFileName = optarg;
252 break;
253 case 'r':
254 repoFileName = optarg;
255 break;
256 case 's':
257 creationOrder = true;
258 break;
259 case 't':
260 if (sscanf(optarg, "%u", &nTopTrans) != 1) {
261 usage();
262 exit(1);
264 break;
265 case 'u':
266 if (strlen(optarg) == 32) {
267 sha1Filter = SHA1(optarg);
268 } else {
269 usage();
270 exit(1);
272 break;
273 case 'S':
274 sortBySize = true;
275 case 'T':
276 if (sscanf(optarg, "%u", &nTopFuncs) != 1) {
277 usage();
278 exit(1);
280 break;
281 case 'k':
282 kindFilter = optarg;
283 break;
284 case 'D':
285 sortByDensity = true;
286 break;
287 case 'e':
288 if (!strcmp(optarg, kListKeyword)) {
289 printValidEventTypes();
290 exit(0);
292 // Will lookup event type after parsing all args
293 sortByArg = optarg;
294 break;
295 case 'E':
297 char* p = strtok(optarg, ",");
298 while (p) {
299 addEventType(std::string(p));
300 p = strtok(nullptr, ",");
303 break;
304 case 'b':
305 collectBCStats = true;
306 break;
307 case 'B':
308 if (!strcmp(optarg, kListKeyword)) {
309 printValidBytecodes();
310 exit(0);
312 filterByOpcode = stringToExtOpcode(optarg);
313 if (!filterByOpcode) {
314 usage();
315 exit(1);
317 break;
318 case 'i':
319 inclusiveStats = true;
320 break;
321 case 'n':
322 if (sscanf(optarg, "%u", &annotationsVerbosity) != 1) {
323 usage();
324 exit(1);
326 break;
327 case 'v':
328 verboseStats = true;
329 // -v implies -i
330 inclusiveStats = true;
331 if (sscanf(optarg, "%lf", &helpersMinPercentage) != 1) {
332 usage();
333 exit(1);
335 break;
336 case '?':
337 if (optopt == 'd' || optopt == 'c' || optopt == 'p' || optopt == 't') {
338 fprintf (stderr, "Error: -%c expects an argument\n\n", optopt);
340 case 'o':
341 hostOpcodes = true;
342 break;
343 case 'j':
344 useJSON = true;
345 break;
346 #ifdef FACEBOOK
347 case 'x':
348 printToDB = true;
349 break;
350 case 'H':
351 hiveTable = optarg;
352 break;
353 #endif
354 default:
355 usage();
356 exit(1);
360 // Lookup the event type of a event to sort by was specified with -e
361 if (sortByArg) {
362 sortBy = commandLineArgumentToEventType(sortByArg);
363 if (sortBy < getFirstEventType() || sortBy >= getNumEventTypes()) {
364 usage();
365 exit(1);
367 } else {
368 // If not specified, then default the sorting to the first event type.
369 // If using predefined events, then EVENT_CYCLES.
370 sortBy = static_cast<PerfEventType>(getFirstEventType());
374 void sortTrans() {
375 for (uint32_t tid = 0; tid < NTRANS; tid++) {
376 const auto trec = TREC(tid);
377 if (trec->isValid() &&
378 (selectedFuncId == INVALID_ID ||
379 selectedFuncId == trec->src.funcID().toInt()) &&
380 (kindFilter == "all" || kindFilter == show(trec->kind).c_str())) {
381 transPrintOrder.push_back(tid);
384 std::sort(transPrintOrder.begin(), transPrintOrder.end(),
385 [] (uint32_t tid1, uint32_t tid2) {
386 auto const tr1 = TREC(tid1);
387 auto const tr2 = TREC(tid2);
389 if (tr1->aStart != tr2->aStart) {
390 return std::less<void*>{}(tr1->aStart, tr2->aStart);
392 return tid1 < tid2;
396 void loadPerfEvents() {
397 FILE* profFile;
399 profFile = fopen(profFileName.c_str(), "rt");
401 if (!profFile) {
402 error("Error opening file " + profFileName);
405 char program[MAX_SYM_LEN];
406 char eventCaption[MAX_SYM_LEN];
407 char line[2*MAX_SYM_LEN];
408 TCA addr;
409 vector<uint32_t> tcSamples(getNumEventTypes(), 0);
410 vector<uint32_t> hhvmSamples(getNumEventTypes(), 0);
411 size_t numEntries = 0;
412 PerfEventType eventType = EVENT_NULL;
413 // samplesPerKind[event][kind], samplesPerTCRegion[event][TCRegion]
414 vector< vector<uint32_t> > samplesPerKind(getNumEventTypes(),
415 vector<uint32_t>(NumTransKinds, 0));
416 vector< vector<uint32_t> > samplesPerTCRegion(getNumEventTypes(),
417 vector<uint32_t>(TCRCount, 0));
419 while (fgets(line, 2*MAX_SYM_LEN, profFile) != nullptr) {
420 always_assert(sscanf(line, "%s %s %lu", program, eventCaption, &numEntries)
421 == 3);
422 always_assert(numEntries);
424 std::vector<std::pair<TCA,std::string>> entries;
426 for (size_t i = 0; i < numEntries; i++) {
427 fscanf(profFile, "%p %s\n", &addr, line);
428 entries.push_back(std::pair<TCA,std::string>(addr, line));
431 // Strip :p+ PEBS suffix.
432 if (auto pos = strchr(eventCaption, ':')) {
433 *pos = '\0';
436 if (strncmp(program, "hhvm", 4) == 0) {
437 eventType = perfScriptOutputToEventType(eventCaption);
438 if (eventType == EVENT_NULL) {
439 error(folly::sformat("loadProfData: invalid event caption {}",
440 eventCaption));
443 hhvmSamples[eventType]++;
445 size_t selIdx = 0;
446 addr = entries[0].first;
448 if (inclusiveStats) {
449 for (size_t i = 0; i < entries.size(); i++) {
450 if (g_transData->isAddrInSomeTrans(entries[i].first)) {
451 addr = entries[i].first;
452 selIdx = i;
453 break;
458 if (!(minAddr <= addr && addr <= maxAddr)) continue;
459 if (!g_transData->isAddrInSomeTrans(addr)) continue;
460 TransID transId = g_transData->getTransContaining(addr);
461 always_assert(transId != INVALID_ID);
462 tcSamples[eventType]++;
464 const TransRec* trec = g_transData->getTransRec(transId);
465 TransKind kind = trec->kind;
466 samplesPerKind[eventType][static_cast<uint32_t>(kind)]++;
467 TCRegion region = transCode->findTCRegionContaining(addr);
468 always_assert(region != TCRCount);
469 samplesPerTCRegion[eventType][region]++;
471 std::vector<std::string> stackTrace;
472 if (verboseStats) {
473 for (size_t i = 0; i < selIdx; i++) {
475 if (!strcmp(entries[i].second.c_str(), "[unknown]")) {
477 // Append the address to disambiguate.
478 entries[i].second += std::string("@")
479 + toString((void*)entries[i].first);
482 stackTrace.push_back(entries[i].second);
484 reverse(stackTrace.begin(), stackTrace.end());
487 if (selIdx) addr--;
488 tcaPerfEvents.addEvent(addr, (PerfEvent){eventType, 1}, stackTrace);
492 AddrToTransMapper transMapper(g_transData);
493 transPerfEvents = tcaPerfEvents.mapTo(transMapper);
495 g_logger->printGeneric("# Number of hhvm samples read "
496 "(%% in TC) from file %s\n",
497 profFileName.c_str());
499 for (size_t i = getFirstEventType(); i < getNumEventTypes(); i++) {
500 if (!hhvmSamples[i]) continue;
502 g_logger->printGeneric("# %-19s TOTAL: %10u (%u in TC = %5.2lf%%)\n",
503 eventTypeToCommandLineArgument((PerfEventType)i),
504 hhvmSamples[i],
505 tcSamples[i],
506 100.0 * tcSamples[i] / hhvmSamples[i]);
508 for (size_t j = 0; j < NumTransKinds; ++j) {
509 auto ct = samplesPerKind[i][j];
510 if (!ct) continue;
511 std::string kind = show(static_cast<TransKind>(j));
512 g_logger->printGeneric("# %26s: %-8u (%5.2lf%%)\n",
513 kind.c_str(), ct, 100.0 * ct / tcSamples[i]);
515 g_logger->printGeneric("#\n");
517 g_logger->printGeneric("\n");
519 // print per-TCRegion information
521 // header
522 g_logger->printGeneric("# TCRegion ");
523 for (size_t i = getFirstEventType(); i < getNumEventTypes(); i++) {
524 g_logger->printGeneric("%17s ",
525 eventTypeToCommandLineArgument((PerfEventType)i));
527 g_logger->printGeneric("\n");
529 // HW events for each region
530 for (size_t i = 0; i < TCRCount; i++) {
531 g_logger->printGeneric("# %8s ",
532 tcRegionToString(static_cast<TCRegion>(i)).c_str());
533 for (size_t j = getFirstEventType(); j < getNumEventTypes(); j++) {
534 auto ct = samplesPerTCRegion[j][i];
535 g_logger->printGeneric("%8u (%5.2lf%%) ", ct,
536 ct ? (100.0 * ct / tcSamples[j]) : 0);
538 g_logger->printGeneric("\n");
540 g_logger->printGeneric("#\n\n");
542 fclose(profFile);
545 void loadProfData() {
546 if (!profFileName.empty()) {
547 loadPerfEvents();
551 std::string getDisasmStr(OfflineCode* code,
552 TCA startAddr,
553 uint32_t len,
554 const std::vector<TransBCMapping>& bcMap,
555 const PerfEventsMap<TCA>& perfEvents,
556 bool hostOpcodes) {
557 std::ostringstream os;
558 code->printDisasm(os, startAddr, len, bcMap, perfEvents, hostOpcodes);
559 return os.str();
562 namespace get_json {
564 using folly::dynamic;
566 std::string show(TCA tca) {
567 return folly::sformat("{}", static_cast<void*>(tca));
570 dynamic getAnnotation(const Annotations& annotations) {
571 // for JSON outputs, we only care about the annotation "after code gen"
572 std::string annotationStr;
573 for (auto const& annotation : annotations) {
574 if (annotation.first == " after code gen ") {
575 annotationStr = annotation.second;
576 break;
579 if (annotationStr.empty()) return dynamic();
581 auto const rawValue = g_annotations->getAnnotation(annotationStr);
582 if (rawValue.subpiece(0, 5) != "json:") return rawValue;
584 try {
585 return folly::parseJson(rawValue.subpiece(5));
587 catch (const std::runtime_error& re){
588 std::cerr << re.what() << std::endl;
589 std::cerr << "Parsing annotation as JSON failed. "
590 << "Annotation included as raw value\n";
591 return rawValue;
595 dynamic getTransRec(const TransRec* tRec,
596 const PerfEventsMap<TransID>& transPerfEvents) {
597 auto const guards = dynamic(tRec->guards.begin(), tRec->guards.end());
599 auto const sk = tRec->src;
600 auto const resumeMode = [&] {
601 switch (sk.resumeMode()) {
602 case ResumeMode::None: return "None";
603 case ResumeMode::Async: return "Async";
604 case ResumeMode::GenIter: return "GenIter";
606 always_assert(false);
607 }();
609 auto const offset = sk.prologue() || sk.funcEntry()
610 ? 0 // Unable to lookup entry offset, assume main entry.
611 : sk.offset();
613 dynamic src = dynamic::object("sha1", tRec->sha1.toString())
614 ("funcId", sk.funcID().toInt())
615 ("funcName", tRec->funcName)
616 ("resumeMode", resumeMode)
617 ("hasThis", sk.hasThis())
618 ("prologue", sk.prologue())
619 ("funcEntry", sk.funcEntry())
620 ("bcStartOffset", offset)
621 ("guards", guards);
623 if (sk.prologue() || sk.funcEntry()) {
624 src["numEntryArgs"] = sk.numEntryArgs();
627 const dynamic result = dynamic::object("id", tRec->id)
628 ("src", src)
629 ("kind", show(tRec->kind))
630 ("hasLoop", tRec->hasLoop)
631 ("aStart", show(tRec->aStart))
632 ("aLen", tRec->aLen)
633 ("coldStart", show(tRec->acoldStart))
634 ("coldLen", tRec->acoldLen)
635 ("frozenStart",
636 show(tRec->afrozenStart))
637 ("frozenLen", tRec->afrozenLen);
639 return result;
642 dynamic getPerfEvents(const PerfEventsMap<TransID>& transPerfEvents,
643 const TransID transId) {
644 dynamic eventsObj = dynamic::object();
645 auto const events = transPerfEvents.getAllEvents(transId);
647 bool hasEvents = false;
648 for (auto const c : events) {
649 if (c) hasEvents = true;
651 if (!hasEvents) return eventsObj;
653 auto const numEvents = getNumEventTypes();
654 for (int i = 0; i < numEvents; i++) {
655 auto const event = static_cast<PerfEventType>(i);
656 auto const eventName = eventTypeToCommandLineArgument(event);
657 eventsObj[eventName] = events[i];
660 return eventsObj;
663 dynamic getTrans(TransID transId) {
664 always_assert(transId < NTRANS);
666 auto const* tRec = TREC(transId);
667 if (!tRec->isValid()) return dynamic();
669 auto const transRec = getTransRec(tRec, transPerfEvents);
670 auto const perfEvents = getPerfEvents(transPerfEvents, transId);
672 dynamic blocks = dynamic::array;
673 for (auto const& block : tRec->blocks) {
674 std::stringstream byteInfo; // TODO(T52857125) - translate to actual data
676 auto sk = block.sk;
677 if (sk.valid()) {
678 assertx(!sk.prologue());
679 if (sk.funcEntry()) sk.advance();
680 sk.func()->prettyPrint(
681 byteInfo,
682 HPHP::Func::PrintOpts()
683 .noName()
684 .noMetadata()
685 .range(sk.offset(), block.bcPast)
689 auto const offset = [&]() {
690 if (!sk.prologue() && !sk.funcEntry()) return sk.offset();
691 if (sk.valid()) return sk.entryOffset();
692 // Unable to lookup entry offset, assume main entry.
693 return 0;
694 }();
696 blocks.push_back(dynamic::object("sha1", block.sha1.toString())
697 ("start", offset)
698 ("end", block.bcPast)
699 ("unit", sk.valid() ?
700 byteInfo.str() :
701 dynamic()));
704 auto const annotationDynamic = getAnnotation(tRec->annotations);
706 auto const maybeUnit = [&]() -> Optional<printir::Unit> {
707 if (!annotationDynamic.isObject()) return std::nullopt;
708 try {
709 return folly::convertTo<printir::Unit>(annotationDynamic);
711 catch (const printir::ParseError& pe) {
712 std::cerr << pe.what() << std::endl;
713 return std::nullopt;
715 }();
717 const dynamic mainDisasm = tRec->aLen ?
718 transCode->getDisasm(tRec->aStart,
719 tRec->aLen,
720 tRec->bcMapping,
721 tcaPerfEvents,
722 hostOpcodes,
723 maybeUnit) :
724 dynamic();
726 auto const coldIsFrozen = tRec->acoldStart == tRec->afrozenStart;
727 const dynamic coldDisasm = !coldIsFrozen && tRec->acoldLen ?
728 transCode->getDisasm(tRec->acoldStart,
729 tRec->acoldLen,
730 tRec->bcMapping,
731 tcaPerfEvents,
732 hostOpcodes,
733 maybeUnit) :
734 dynamic();
735 const dynamic frozenDisasm = tRec -> afrozenLen ?
736 transCode->getDisasm(tRec->afrozenStart,
737 tRec->afrozenLen,
738 tRec->bcMapping,
739 tcaPerfEvents,
740 hostOpcodes,
741 maybeUnit) :
742 dynamic();
743 const dynamic disasmObj = dynamic::object("main", mainDisasm)
744 ("cold", coldDisasm)
745 ("frozen", frozenDisasm);
747 return dynamic::object("transRec", transRec)
748 ("blocks", blocks)
749 ("archName", transCode->getArchName())
750 ("regions", disasmObj)
751 ("perfEvents", perfEvents)
752 ("transId", transId)
753 ("ir_annotation", maybeUnit ?
754 folly::toDynamic(*maybeUnit) :
755 annotationDynamic);
756 // if annotation fails to parse, give raw to the user in annotation field
759 dynamic getTC() {
760 dynamic translations = dynamic::array;
761 for (uint32_t t = 0; t < NTRANS; t++) {
762 translations.push_back(getTrans(t));
765 return dynamic::object("repoSchema", repoSchemaId().begin())
766 ("translations", translations);
770 // Prints the metadata, bytecode, and disassembly for the given translation
771 void printTrans(TransID transId) {
772 always_assert(transId < NTRANS);
774 g_logger->printGeneric("\n====================\n");
775 g_transData->printTransRec(transId, transPerfEvents);
777 const TransRec* tRec = TREC(transId);
778 if (!tRec->isValid()) return;
780 if (!tRec->blocks.empty()) {
781 g_logger->printGeneric("----------\nbytecode:\n----------\n");
782 const Func* curFunc = nullptr;
783 for (auto& block : tRec->blocks) {
784 std::stringstream byteInfo;
785 auto sk = block.sk;
786 if (!sk.valid()) {
787 byteInfo << folly::format(
788 "<<< couldn't find func in {} to print bytecode range [{}{},{}) >>>\n",
789 block.sha1,
790 block.sk.prologue() || block.sk.funcEntry() ? "numEntryArgs " : "",
791 block.sk.prologue() || block.sk.funcEntry()
792 ? block.sk.numEntryArgs() : block.sk.offset(),
793 block.bcPast);
794 continue;
797 if (sk.func() != curFunc) {
798 curFunc = sk.func();
799 byteInfo << '\n';
800 curFunc->prettyPrint(
801 byteInfo,
802 Func::PrintOpts().noMetadata().noBytecode()
806 assertx(!sk.prologue());
807 if (sk.funcEntry()) sk.advance();
809 curFunc->prettyPrint(
810 byteInfo,
811 Func::PrintOpts()
812 .noName()
813 .noMetadata()
814 .range(sk.offset(), block.bcPast)
816 g_logger->printBytecode(byteInfo.str());
820 g_logger->printGeneric("----------\n%s: main\n----------\n",
821 transCode->getArchName());
822 g_logger->printAsm("%s", getDisasmStr(transCode,
823 tRec->aStart,
824 tRec->aLen,
825 tRec->bcMapping,
826 tcaPerfEvents,
827 hostOpcodes).c_str());
829 g_logger->printGeneric("----------\n%s: cold\n----------\n",
830 transCode->getArchName());
831 // Sometimes acoldStart is the same as afrozenStart. Avoid printing the code
832 // twice in such cases.
833 if (tRec->acoldStart != tRec->afrozenStart) {
834 g_logger->printAsm("%s", getDisasmStr(transCode,
835 tRec->acoldStart,
836 tRec->acoldLen,
837 tRec->bcMapping,
838 tcaPerfEvents,
839 hostOpcodes).c_str());
842 g_logger->printGeneric("----------\n%s: frozen\n----------\n",
843 transCode->getArchName());
844 g_logger->printAsm("%s", getDisasmStr(transCode,
845 tRec->afrozenStart,
846 tRec->afrozenLen,
847 tRec->bcMapping,
848 tcaPerfEvents,
849 hostOpcodes).c_str());
850 g_logger->printGeneric("----------\n");
855 void printCFGOutArcs(TransID transId) {
856 std::vector<TCA> jmpTargets;
858 TCA fallThru = transCode->getTransJmpTargets(
859 g_transData->getTransRec(transId), &jmpTargets);
861 auto const srcFuncId = TREC(transId)->src.funcID();
863 for (size_t i = 0; i < jmpTargets.size(); i++) {
864 TransID targetId = g_transData->getTransStartingAt(jmpTargets[i]);
865 if (targetId != INVALID_ID &&
866 // filter jumps to prologues of other funcs
867 TREC(targetId)->src.funcID() == srcFuncId &&
868 TREC(targetId)->kind != TransKind::Anchor) {
870 bool retrans = TREC(transId)->src == TREC(targetId)->src;
871 const char* color;
872 if (retrans) {
873 color = "darkorange";
874 } else if (jmpTargets[i] == fallThru) {
875 color = "brown";
876 } else {
877 color = "green4";
879 g_logger->printGeneric("t%u -> t%u [color=%s] ;\n", transId, targetId,
880 color);
886 void printCFG() {
887 std::vector<TransID> inodes;
889 g_logger->printGeneric("digraph CFG {\n");
891 g_transData->findFuncTrans(selectedFuncId, &inodes);
893 // Print nodes
894 for (auto& tid : inodes) {
895 auto const sk = TREC(tid)->src;
896 if (sk.prologue()) {
897 g_logger->printGeneric(
898 "t%u [shape=invtrapezium,label=\"T: %u\\nprologue\","
899 "style=filled,color=blue];\n",
900 tid, tid
902 continue;
905 auto const bcStart = sk.prologue() || sk.funcEntry()
906 ? folly::sformat("numEntryArgs {}", TREC(tid)->src.numEntryArgs())
907 : TREC(tid)->src.printableOffset();
908 uint32_t bcStop = TREC(tid)->bcPast();
909 auto const shape = [&] {
910 switch (TREC(tid)->kind) {
911 case TransKind::Optimize: return "oval";
912 case TransKind::Profile: return "hexagon";
913 default: return "box";
915 }();
916 g_logger->printGeneric(
917 "t%u [shape=%s,label=\"T: %u\\nbc: [%s-%d)\",style=filled];\n",
918 tid, shape, tid, bcStart.c_str(), bcStop
922 // Print arcs
923 for (uint32_t i = 0; i < inodes.size(); i++) {
924 uint32_t tid = inodes[i];
925 printCFGOutArcs(tid);
928 g_logger->printGeneric("}\n");
931 void printTopFuncs() {
932 if (!nTopFuncs) return;
933 std::map<FuncId,std::string> funcStrs;
934 for (uint32_t t = 0; t < NTRANS; t++) {
935 auto tRec = TREC(t);
936 if (!tRec->isValid()) continue;
937 auto funcId = tRec->src.funcID();
938 if (funcStrs.count(funcId)) continue;
939 auto funcName = tRec->funcName;
940 funcStrs[funcId] = folly::sformat("{:<6}: {}", funcId, funcName);
942 TransToFuncMapper funcMapper(g_transData);
943 PerfEventsMap<FuncId> funcPerfEvents = transPerfEvents.mapTo(funcMapper);
944 funcPerfEvents.printEventsSummary(sortBy,
945 "Function",
947 nTopFuncs,
948 verboseStats,
949 helpersMinPercentage,
950 funcStrs);
953 void printTopFuncsBySize() {
954 std::unordered_map<FuncId, size_t> funcSize;
955 for (TransID t = 0; t < NTRANS; t++) {
956 const auto trec = TREC(t);
957 if (trec->isValid()) {
958 const auto funcId = trec->src.funcID();
959 funcSize[funcId] += trec->aLen;
962 std::vector<std::pair<FuncId, size_t>> topN(nTopFuncs);
963 std::partial_sort_copy(funcSize.begin(), funcSize.end(),
964 topN.begin(), topN.end(),
965 [&](auto const& a, auto const& b) {
966 return a.second > b.second;
968 g_logger->printGeneric("FuncID: \tSize (total aLen in bytes):\n");
969 for (auto const& [fid, size] : topN) {
970 g_logger->printGeneric("%10u\t%10lu\n", fid.toInt(), size);
974 struct CompTrans {
975 private:
976 const PerfEventsMap<TransID>& transPerfEvents;
977 const PerfEventType etype;
979 public:
980 CompTrans(const PerfEventsMap<TransID>& _transPerfEvents,
981 PerfEventType _etype) :
982 transPerfEvents(_transPerfEvents), etype(_etype) {}
984 bool operator()(TransID t1, TransID t2) const {
985 const auto count1 = transPerfEvents.getEventCount(t1, etype);
986 const auto count2 = transPerfEvents.getEventCount(t2, etype);
987 if (sortByDensity) {
988 const auto size1 = TREC(t1)->aLen;
989 const auto size2 = TREC(t2)->aLen;
990 return count1 * size2 > count2 * size1;
992 return count1 > count2;
996 void printTopTrans() {
997 if (!nTopTrans) return;
999 // The summary currently includes all translations, so it's misleading
1000 // if we're filtering a specific kind of translations or address range.
1001 // It also doesn't sort by density, so do print it if sortByDensity is set.
1002 if (kindFilter == "all" && minAddr == 0 && maxAddr == (TCA)-1 &&
1003 !sortByDensity) {
1004 transPerfEvents.printEventsSummary(sortBy,
1005 "TransId",
1007 nTopTrans,
1008 verboseStats,
1009 helpersMinPercentage);
1012 // Sort and print the top translations.
1013 std::vector<TransID> transIds;
1015 for (TransID t = 0; t < NTRANS; t++) {
1016 if (TREC(t)->isValid() &&
1017 (kindFilter == "all" || kindFilter == show(TREC(t)->kind).c_str()) &&
1018 ((minAddr <= TREC(t)->aStart && TREC(t)->aStart <= maxAddr) ||
1019 (minAddr <= TREC(t)->acoldStart && TREC(t)->acoldStart <= maxAddr))) {
1020 transIds.push_back(t);
1024 CompTrans compTrans(transPerfEvents, sortBy);
1025 sort(transIds.begin(), transIds.end(), compTrans);
1027 size_t nPrint = nTopTrans;
1028 if (transIds.size() < nTopTrans) {
1029 fprintf(stderr, "Warning: too few translations selected (%lu)\n",
1030 transIds.size());
1031 nPrint = transIds.size();
1033 for (size_t i = 0; i < nPrint; i++) printTrans(transIds[i]);
1036 void printBytecodeStats(const OfflineTransData* tdata,
1037 const PerfEventsMap<TCA>& events,
1038 PerfEventType etype) {
1040 if (!g_repo) error("printBytecodeStats: null repo");
1041 if (!tdata) error("printBytecodeStats: null g_transData");
1043 AddrToBcMapper bcMapper(tdata);
1044 PerfEventsMap<ExtOpcode> bcPerfEvents = events.mapTo(bcMapper);
1046 std::map<ExtOpcode,std::string> opcodeToName;
1047 PerfEventsMap<ExtOpcode>::const_iterator it;
1049 for (it = bcPerfEvents.begin(); it != bcPerfEvents.end(); it++) {
1050 opcodeToName[it->first] = extOpcodeToString(it->first);
1053 bcPerfEvents.printEventsSummary(etype,
1054 "Opcode",
1056 PerfEventsMap<ExtOpcode>::kAllEntries,
1057 verboseStats,
1058 helpersMinPercentage,
1059 opcodeToName);
1062 void printTopBytecodes(const OfflineTransData* tdata,
1063 OfflineCode* olCode,
1064 const PerfEventsMap<TCA>& samples,
1065 PerfEventType etype,
1066 ExtOpcode filterBy) {
1068 always_assert(etype >= getFirstEventType() && etype < getNumEventTypes());
1070 AddrToTransFragmentMapper mapper(tdata, filterBy);
1071 PerfEventsMap<TransFragment> tfragPerfEvents = samples.mapTo(mapper);
1073 std::vector<std::pair<uint64_t, TransFragment> > ranking;
1074 PerfEventsMap<TransFragment>::const_iterator it;
1076 for (it = tfragPerfEvents.begin(); it != tfragPerfEvents.end(); it++) {
1077 ranking.push_back(std::make_pair(it->second[etype], it->first));
1080 sort(ranking.rbegin(), ranking.rend());
1082 for (size_t i = 0; i < ranking.size(); i++) {
1083 const TransFragment& tfrag = ranking[i].second;
1084 const TransRec* trec = tdata->getTransRec(tfrag.tid);
1086 Unit* unit = g_repo->getUnit(trec->sha1);
1087 always_assert(unit);
1089 g_logger->printGeneric("\n====================\n");
1090 g_logger->printGeneric("{\n");
1091 g_logger->printGeneric(" FuncID = %u\n", trec->src.funcID());
1092 g_logger->printGeneric(" TransID = %u\n", tfrag.tid);
1093 tfragPerfEvents.printEventsHeader(tfrag);
1094 g_logger->printGeneric("}\n\n");
1096 g_logger->printGeneric("----------\n%s: main\n----------\n",
1097 olCode->getArchName());
1098 g_logger->printAsm("%s", getDisasmStr(olCode,
1099 tfrag.aStart,
1100 tfrag.aLen,
1101 trec->bcMapping,
1102 samples,
1103 hostOpcodes).c_str());
1105 g_logger->printGeneric("----------\n%s: cold\n----------\n",
1106 olCode->getArchName());
1107 g_logger->printAsm("%s", getDisasmStr(olCode,
1108 tfrag.acoldStart,
1109 tfrag.acoldLen,
1110 trec->bcMapping,
1111 samples,
1112 hostOpcodes).c_str());
1114 g_logger->printGeneric("----------\n%s: frozen\n----------\n",
1115 olCode->getArchName());
1116 g_logger->printAsm("%s", getDisasmStr(olCode,
1117 tfrag.afrozenStart,
1118 tfrag.afrozenLen,
1119 trec->bcMapping,
1120 samples,
1121 hostOpcodes).c_str());
1125 static uint32_t findSelectedFuncId() {
1126 if (selectedFuncName.empty()) return INVALID_ID;
1127 for (uint32_t t = 0; t < NTRANS; t++) {
1128 auto tRec = TREC(t);
1129 if (tRec->isValid() && tRec->funcName == selectedFuncName) {
1130 return tRec->src.funcID().toInt();
1133 return INVALID_ID;
1136 int main(int argc, char *argv[]) {
1137 StaticString::CreateAll();
1138 folly::SingletonVault::singleton()->registrationComplete();
1140 pcre_init();
1142 // Initially use stdout logger while parsing arguments. Optionally switch
1143 // to DBLogger below if the printToDb argument was passed.
1144 StdLogger stdoutlogger{};
1145 g_logger = &stdoutlogger;
1147 parseOptions(argc, argv);
1149 #ifdef FACEBOOK
1150 Optional<DBLogger> dblogger = std::nullopt;
1151 if (printToDB) {
1152 dblogger = DBLogger{};
1153 g_logger = &dblogger.value();
1154 g_logger->printGeneric("Printing to database");
1156 #endif
1158 g_transData = new OfflineTransData(dumpDir);
1159 transCode = new OfflineCode(dumpDir,
1160 g_transData->getMainBase(),
1161 g_transData->getColdBase(),
1162 g_transData->getFrozenBase());
1163 g_repo = new RepoWrapper(g_transData->getRepoSchema(), repoFileName, !useJSON);
1164 g_transData->loadTCData(g_repo);
1165 g_annotations = std::make_unique<AnnotationCache>(dumpDir);
1167 loadProfData();
1169 g_transData->setAnnotationsVerbosity(annotationsVerbosity);
1171 if (selectedFuncId == INVALID_ID) {
1172 selectedFuncId = findSelectedFuncId();
1175 if (nTopFuncs) {
1176 if (nTopFuncs > NFUNCS) {
1177 warnTooFew("functions", nTopFuncs, NFUNCS);
1178 nTopFuncs = NFUNCS;
1180 if (sortBySize) {
1181 printTopFuncsBySize();
1182 } else {
1183 printTopFuncs();
1185 } else if (nTopTrans) {
1186 if (nTopTrans > NTRANS) {
1187 warnTooFew("translations", nTopTrans, NTRANS);
1188 nTopTrans = NTRANS;
1190 printTopTrans();
1191 } else if (transCFG) {
1192 printCFG();
1193 } else if (creationOrder) {
1194 // Print translations (all or for a given funcId) in the order
1195 // they were created.
1196 sortTrans();
1197 for (uint32_t i=0; i < transPrintOrder.size(); i++) {
1198 printTrans(transPrintOrder[i]);
1200 } else if (collectBCStats) {
1201 printBytecodeStats(g_transData, tcaPerfEvents, sortBy);
1202 } else if (filterByOpcode) {
1203 printTopBytecodes(g_transData,
1204 transCode,
1205 tcaPerfEvents,
1206 sortBy,
1207 filterByOpcode);
1208 } else if (useJSON) {
1209 auto const tcObj = get_json::getTC();
1211 auto const jsonStr = folly::toJson(tcObj);
1212 std::cout << jsonStr << std::endl;
1214 #ifdef FACEBOOK
1215 if (!hiveTable.empty()) {
1216 auto const uuid = boost::uuids::random_generator()();
1217 auto const uuidStr = boost::uuids::to_string(uuid);
1219 for (auto const& translation : tcObj["translations"]) {
1220 StructuredLogEntry entry;
1221 entry.force_init = true;
1223 entry.setStr("translation_json", folly::toJson(translation));
1224 entry.setInt("trans_id", translation["transId"].asInt());
1225 entry.setStr("repoSchema", repoSchemaId().begin());
1226 entry.setStr("func_name",
1227 translation["transRec"]["src"]["funcName"].asString());
1228 entry.setStr("username" , std::string(getlogin()));
1229 entry.setStr("uuid", uuidStr);
1231 writeStructLogLazyInit(hiveTable, entry);
1234 #endif
1235 } else {
1236 // Print all translations in original order, filtered by unit if desired.
1237 for (uint32_t t = 0; t < NTRANS; t++) {
1238 auto tRec = TREC(t);
1239 if (!tRec->isValid()) continue;
1240 if (tRec->kind == TransKind::Anchor) continue;
1241 if (sha1Filter && tRec->sha1 != *sha1Filter) continue;
1243 printTrans(t);
1244 bool opt = (tRec->kind == TransKind::OptPrologue
1245 || tRec->kind == TransKind::Optimize);
1246 g_logger->flushTranslation(tRec->funcName, opt);
1250 delete g_transData;
1251 delete transCode;
1252 delete g_repo;
1254 return 0;