Skip to content

Commit 5d395c8

Browse files
author
Nuri Amari
committed
Add flags to dump IR to a file before and after LLVM passes
Summary: LLVM offers -print-after and -print-before flags that allow you to print IR to stderr before and after any pass you want. This can be useful for debugging LLVM optimization issue, but is far too noisy for large builds. This patch adds analogous options -dump-after and -dump-before that dump the IR to appropriately named files. In addition, it also introduces flags -dump-after-all, -dump-before-all and -ir-dump-directory to control where the files are written to. Test Plan: Included LIT tests: ``` ninja check-llvm ```
1 parent 7c4f455 commit 5d395c8

File tree

9 files changed

+496
-1
lines changed

9 files changed

+496
-1
lines changed

llvm/include/llvm/IR/PrintPasses.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ bool shouldPrintAfterAll();
4848
std::vector<std::string> printBeforePasses();
4949
std::vector<std::string> printAfterPasses();
5050

51+
// Returns true if dumping IR to a file before/after some pass is enabled
52+
// wether all passes or a specific pass.
53+
bool shouldDumpBeforeSomePass();
54+
bool shouldDumpAfterSomePass();
55+
56+
// Returns true if we should dump IR to a file before/after a specific pass. The
57+
// argument should be the pass ID, e.g. "instcombine"
58+
bool shouldDumpBeforePass(StringRef PassID);
59+
bool shouldDumpAfterPass(StringRef PassID);
60+
61+
// Returns true if we should dump IR to a file before/after all passes.
62+
bool shouldDumpBeforeAll();
63+
bool shouldDumpAfterAll();
64+
65+
// The list of passes to dump IR to a file before/after, if we only want
66+
// to print before/after specific passes.
67+
std::vector<std::string> dumpBeforePasses();
68+
std::vector<std::string> dumpAfterPasses();
69+
70+
StringRef irInstrumentationDumpDirectory();
71+
5172
// Returns true if we should always print the entire module.
5273
bool forcePrintModuleIR();
5374

llvm/include/llvm/Passes/StandardInstrumentations.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,62 @@ class PrintIRInstrumentation {
6969
unsigned CurrentPassNumber = 0;
7070
};
7171

72+
class DumpIRInstrumentation {
73+
public:
74+
void registerCallbacks(PassInstrumentationCallbacks &PIC);
75+
76+
private:
77+
void dumpBeforePass(StringRef PassID, Any IR);
78+
void dumpAfterPass(StringRef PassID, Any IR);
79+
80+
bool shouldDumpBeforePass(StringRef PassID);
81+
bool shouldDumpAfterPass(StringRef PassID);
82+
83+
PassInstrumentationCallbacks *PIC;
84+
85+
// The module currently being processed in the pipeline.
86+
Module const *CurrentModule = nullptr;
87+
88+
void pushPass(StringRef PassID, Any IR);
89+
void popPass(StringRef PassID);
90+
91+
SmallString<16> InstrumentationDumpDirectory;
92+
StringRef fetchInstrumentationDumpDirectory();
93+
94+
SmallString<16> fetchCurrentInstrumentationDumpFile(StringRef Suffix);
95+
96+
// A table to store how many times a given pass has run at the current "nested
97+
// level"
98+
using PassRunsFrequencyTableT = DenseMap<StringRef, unsigned>;
99+
// A stack each frame of which (aside from the very first) represents a pass
100+
// being run on some unit of IR. The larger, the stack grows, the smaller the
101+
// unit of IR. For example, we would first push a module pass, then for each
102+
// function pass in that module pass, we would push a frame and so on. This
103+
// information is used to craft the output path for this logging.
104+
//
105+
// Each frame contains a map to track how many times a given subpass runs. For
106+
// example, to keep track of how many times a function pass Foo runs within a
107+
// module pass Bar. The first frame of the stack represents the module being
108+
// processed rather than any particular pass. This is to create a frequency
109+
// table to track module level pass run counts without having to special case
110+
// that logic.
111+
//
112+
// When a change in the module being processed is detected, this first frame
113+
// is updated accordingly.
114+
115+
struct PipelineStateStackFrame {
116+
StringRef PassID;
117+
PassRunsFrequencyTableT FreqTable;
118+
unsigned int PassCount;
119+
120+
PipelineStateStackFrame(StringRef PassID) : PassID{PassID}, PassCount{0} {}
121+
};
122+
123+
using PipelineStateStackT = SmallVector<PipelineStateStackFrame>;
124+
125+
PipelineStateStackT PipelineStateStack;
126+
};
127+
72128
class OptNoneInstrumentation {
73129
public:
74130
OptNoneInstrumentation(bool DebugLogging) : DebugLogging(DebugLogging) {}
@@ -555,6 +611,7 @@ class PrintCrashIRInstrumentation {
555611
/// instrumentations and manages their state (if any).
556612
class StandardInstrumentations {
557613
PrintIRInstrumentation PrintIR;
614+
DumpIRInstrumentation DumpIR;
558615
PrintPassInstrumentation PrintPass;
559616
TimePassesHandler TimePasses;
560617
TimeProfilingPassesHandler TimeProfilingPasses;

llvm/lib/IR/PrintPasses.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,31 @@ static cl::opt<bool> PrintAfterAll("print-after-all",
3333
llvm::cl::desc("Print IR after each pass"),
3434
cl::init(false), cl::Hidden);
3535

36+
static cl::list<std::string>
37+
DumpBefore("dump-before",
38+
llvm::cl::desc("Dump IR to a file before specified passes"),
39+
cl::CommaSeparated, cl::Hidden);
40+
41+
static cl::list<std::string>
42+
DumpAfter("dump-after",
43+
llvm::cl::desc("Dump IR to a file after specified passes"),
44+
cl::CommaSeparated, cl::Hidden);
45+
46+
static cl::opt<bool>
47+
DumpBeforeAll("dump-before-all",
48+
llvm::cl::desc("Dump IR to a file before each pass"),
49+
cl::init(false), cl::Hidden);
50+
static cl::opt<bool>
51+
DumpAfterAll("dump-after-all",
52+
llvm::cl::desc("Dump IR to a file after each pass"),
53+
cl::init(false), cl::Hidden);
54+
55+
static cl::opt<std::string> DumpDirectory(
56+
"ir-dump-directory",
57+
llvm::cl::desc("A directory to dump IR log files into before and after "
58+
"passes as specified using -dump-before / -dump-after"),
59+
cl::init(""), cl::Hidden, cl::value_desc("filename"));
60+
3661
// Print out the IR after passes, similar to -print-after-all except that it
3762
// only prints the IR after passes that change the IR. Those passes that do not
3863
// make changes to the IR are reported as not making any changes. In addition,
@@ -139,6 +164,35 @@ std::vector<std::string> llvm::printAfterPasses() {
139164
return std::vector<std::string>(PrintAfter);
140165
}
141166

167+
bool llvm::shouldDumpBeforeSomePass() {
168+
return DumpBeforeAll || !DumpBefore.empty();
169+
}
170+
171+
bool llvm::shouldDumpAfterSomePass() {
172+
return DumpAfterAll || !DumpAfter.empty();
173+
}
174+
175+
bool llvm::shouldDumpBeforeAll() { return DumpBeforeAll; }
176+
177+
bool llvm::shouldDumpAfterAll() { return DumpAfterAll; }
178+
179+
bool llvm::shouldDumpBeforePass(StringRef PassID) {
180+
return DumpBeforeAll || llvm::is_contained(DumpBefore, PassID);
181+
}
182+
183+
bool llvm::shouldDumpAfterPass(StringRef PassID) {
184+
return DumpAfterAll || llvm::is_contained(DumpAfter, PassID);
185+
}
186+
std::vector<std::string> llvm::dumpBeforePasses() {
187+
return std::vector<std::string>(DumpBefore);
188+
}
189+
190+
std::vector<std::string> llvm::dumpAfterPasses() {
191+
return std::vector<std::string>(DumpAfter);
192+
}
193+
194+
StringRef llvm::irInstrumentationDumpDirectory() { return DumpDirectory; }
195+
142196
bool llvm::forcePrintModuleIR() { return PrintModuleScope; }
143197

144198
bool llvm::isPassInPrintList(StringRef PassName) {

llvm/lib/Passes/PassBuilder.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,8 @@ AnalysisKey NoOpLoopAnalysis::Key;
386386
/// We currently only use this for --print-before/after.
387387
bool shouldPopulateClassToPassNames() {
388388
return PrintPipelinePasses || !printBeforePasses().empty() ||
389-
!printAfterPasses().empty() || !isFilterPassesEmpty();
389+
!printAfterPasses().empty() || !isFilterPassesEmpty() ||
390+
!dumpAfterPasses().empty() || !dumpBeforePasses().empty();
390391
}
391392

392393
// A pass for testing -print-on-crash.

llvm/lib/Passes/StandardInstrumentations.cpp

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "llvm/Support/FormatVariadic.h"
3434
#include "llvm/Support/GraphWriter.h"
3535
#include "llvm/Support/MemoryBuffer.h"
36+
#include "llvm/Support/Path.h"
3637
#include "llvm/Support/Program.h"
3738
#include "llvm/Support/Regex.h"
3839
#include "llvm/Support/Signals.h"
@@ -830,6 +831,182 @@ void PrintIRInstrumentation::registerCallbacks(
830831
}
831832
}
832833

834+
void DumpIRInstrumentation::registerCallbacks(
835+
PassInstrumentationCallbacks &PIC) {
836+
837+
if (!(shouldDumpBeforeSomePass() || shouldDumpAfterSomePass()))
838+
return;
839+
840+
this->PIC = &PIC;
841+
842+
PIC.registerBeforeNonSkippedPassCallback(
843+
[this](StringRef P, Any IR) { this->pushPass(P, IR); });
844+
845+
if (shouldDumpBeforeSomePass())
846+
PIC.registerBeforeNonSkippedPassCallback(
847+
[this](StringRef P, Any IR) { this->dumpBeforePass(P, IR); });
848+
849+
if (shouldDumpAfterSomePass()) {
850+
PIC.registerAfterPassCallback(
851+
[this](StringRef P, Any IR, const PreservedAnalyses &) {
852+
this->dumpAfterPass(P, IR);
853+
});
854+
}
855+
856+
// It is important the the "popPass" callback fires after the dumpAfterPass
857+
// callback
858+
PIC.registerAfterPassCallback(
859+
[this](StringRef P, Any IR, const PreservedAnalyses &) {
860+
this->popPass(P);
861+
});
862+
}
863+
864+
void DumpIRInstrumentation::dumpBeforePass(StringRef PassID, Any IR) {
865+
if (isIgnored(PassID))
866+
return;
867+
868+
if (!shouldDumpBeforePass(PassID)) {
869+
return;
870+
}
871+
872+
SmallString<16> OutputPath =
873+
fetchCurrentInstrumentationDumpFile("-before.ll");
874+
std::error_code EC;
875+
llvm::raw_fd_ostream OS(OutputPath, EC, llvm::sys::fs::CD_CreateAlways);
876+
if (EC)
877+
report_fatal_error(Twine("Failed to open ") + OutputPath +
878+
" to support dump-before: " + EC.message());
879+
880+
OS << "*** IR Dump Before " << PassID << " on " << getIRName(IR) << " ***\n";
881+
unwrapAndPrint(OS, IR);
882+
}
883+
884+
void DumpIRInstrumentation::dumpAfterPass(StringRef PassID, Any IR) {
885+
if (isIgnored(PassID))
886+
return;
887+
888+
if (!shouldDumpAfterPass(PassID))
889+
return;
890+
891+
SmallString<16> OutputPath = fetchCurrentInstrumentationDumpFile("-after.ll");
892+
std::error_code EC;
893+
llvm::raw_fd_ostream OS(OutputPath, EC, llvm::sys::fs::CD_CreateAlways);
894+
if (EC)
895+
report_fatal_error(Twine("Failed to open ") + OutputPath +
896+
" to support -dump-after: " + EC.message());
897+
898+
OS << "*** IR Dump After " << PassID << " on " << getIRName(IR) << " ***\n";
899+
unwrapAndPrint(OS, IR);
900+
}
901+
902+
bool DumpIRInstrumentation::shouldDumpBeforePass(StringRef PassID) {
903+
if (shouldDumpBeforeAll())
904+
return true;
905+
906+
StringRef PassName = PIC->getPassNameForClassName(PassID);
907+
return is_contained(dumpBeforePasses(), PassName);
908+
}
909+
910+
bool DumpIRInstrumentation::shouldDumpAfterPass(StringRef PassID) {
911+
if (shouldDumpAfterAll())
912+
return true;
913+
914+
StringRef PassName = PIC->getPassNameForClassName(PassID);
915+
return is_contained(dumpAfterPasses(), PassName);
916+
}
917+
918+
void DumpIRInstrumentation::pushPass(StringRef PassID, Any IR) {
919+
const Module *M = unwrapModule(IR);
920+
if (CurrentModule != M) {
921+
// If currentModule is nullptr, or is not equal to M, we are starting to
922+
// process a new module.
923+
924+
// The first frame of the stack should maintain a frequency table
925+
// for module level passes.
926+
PipelineStateStack.clear();
927+
PipelineStateStack.push_back(PipelineStateStackFrame(M->getName()));
928+
929+
CurrentModule = M;
930+
}
931+
932+
PassRunsFrequencyTableT &FreqTable = PipelineStateStack.back().FreqTable;
933+
if (FreqTable.find(PassID) == FreqTable.end())
934+
FreqTable[PassID] = 0;
935+
936+
PipelineStateStack.push_back(PipelineStateStackFrame(PassID));
937+
}
938+
939+
void DumpIRInstrumentation::popPass(StringRef PassID) {
940+
PipelineStateStack.pop_back();
941+
assert(!PipelineStateStack.empty());
942+
943+
PassRunsFrequencyTableT &FreqTable = PipelineStateStack.back().FreqTable;
944+
assert(FreqTable.find(PassID) != FreqTable.end());
945+
FreqTable[PassID]++;
946+
PipelineStateStack.back().PassCount++;
947+
}
948+
949+
StringRef DumpIRInstrumentation::fetchInstrumentationDumpDirectory() {
950+
if (!InstrumentationDumpDirectory.empty())
951+
return InstrumentationDumpDirectory;
952+
953+
if (!irInstrumentationDumpDirectory().empty())
954+
return irInstrumentationDumpDirectory();
955+
956+
std::error_code EC =
957+
sys::fs::createUniqueDirectory("dumped-ir", InstrumentationDumpDirectory);
958+
if (EC)
959+
report_fatal_error(
960+
Twine("Failed to create unique directory for IR dumping: ") +
961+
EC.message());
962+
963+
return InstrumentationDumpDirectory;
964+
}
965+
966+
SmallString<16>
967+
DumpIRInstrumentation::fetchCurrentInstrumentationDumpFile(StringRef Suffix) {
968+
SmallString<16> OutputPath;
969+
sys::path::append(OutputPath, fetchInstrumentationDumpDirectory());
970+
assert(CurrentModule);
971+
sys::path::append(OutputPath, CurrentModule->getName());
972+
auto *StateStackIt = PipelineStateStack.begin();
973+
// Skip over the first frame in the stack which represents the module being
974+
// processed
975+
for (++StateStackIt; StateStackIt != PipelineStateStack.end();
976+
++StateStackIt) {
977+
SmallString<8> PathComponentName;
978+
// Check the previous frame's pass count to see how many passes of
979+
// any kind have run on this "nesting level".
980+
unsigned int PassCount = (StateStackIt - 1)->PassCount;
981+
PathComponentName += std::to_string(PassCount);
982+
PathComponentName += ".";
983+
// Check the previous frame's frequency table to see how many times
984+
// this pass has run at this "nesting level".
985+
PassRunsFrequencyTableT &FreqTable = (StateStackIt - 1)->FreqTable;
986+
StringRef FramePassID = StateStackIt->PassID;
987+
PathComponentName += FramePassID;
988+
PathComponentName += ".";
989+
assert(FreqTable.find(FramePassID) != FreqTable.end() &&
990+
"DumpIRInstrumentation pass frequency table missing entry");
991+
// Make sure the uint is converted to a character and not interpretted as
992+
// one
993+
PathComponentName += std::to_string(FreqTable[FramePassID]);
994+
sys::path::append(OutputPath, PathComponentName);
995+
}
996+
OutputPath += Suffix;
997+
998+
// Make sure the directory we wish to write our log file into exists.
999+
StringRef ParentDirectory = sys::path::parent_path(OutputPath);
1000+
if (!ParentDirectory.empty()) {
1001+
if (auto EC = llvm::sys::fs::create_directories(ParentDirectory)) {
1002+
report_fatal_error(Twine("Failed to create directory '") +
1003+
ParentDirectory + "' :" + EC.message());
1004+
}
1005+
}
1006+
1007+
return OutputPath;
1008+
}
1009+
8331010
void OptNoneInstrumentation::registerCallbacks(
8341011
PassInstrumentationCallbacks &PIC) {
8351012
PIC.registerShouldRunOptionalPassCallback(
@@ -2288,6 +2465,7 @@ void PrintCrashIRInstrumentation::registerCallbacks(
22882465
void StandardInstrumentations::registerCallbacks(
22892466
PassInstrumentationCallbacks &PIC, ModuleAnalysisManager *MAM) {
22902467
PrintIR.registerCallbacks(PIC);
2468+
DumpIR.registerCallbacks(PIC);
22912469
PrintPass.registerCallbacks(PIC);
22922470
TimePasses.registerCallbacks(PIC);
22932471
OptNone.registerCallbacks(PIC);

0 commit comments

Comments
 (0)