-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Add flags to dump IR to a file before and after LLVM passes #65179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5d395c8
ec1052b
61f9cf0
c3a0df4
72b6502
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,31 @@ static cl::opt<bool> PrintAfterAll("print-after-all", | |
llvm::cl::desc("Print IR after each pass"), | ||
cl::init(false), cl::Hidden); | ||
|
||
static cl::list<std::string> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a lot duplication between the print code and the dump code, though there are differences, it might be nice to try to combine them a little more. |
||
DumpBefore("dump-before", | ||
llvm::cl::desc("Dump IR to a file before specified passes"), | ||
cl::CommaSeparated, cl::Hidden); | ||
|
||
static cl::list<std::string> | ||
DumpAfter("dump-after", | ||
llvm::cl::desc("Dump IR to a file after specified passes"), | ||
cl::CommaSeparated, cl::Hidden); | ||
|
||
static cl::opt<bool> | ||
DumpBeforeAll("dump-before-all", | ||
llvm::cl::desc("Dump IR to a file before each pass"), | ||
cl::init(false), cl::Hidden); | ||
static cl::opt<bool> | ||
DumpAfterAll("dump-after-all", | ||
llvm::cl::desc("Dump IR to a file after each pass"), | ||
cl::init(false), cl::Hidden); | ||
|
||
static cl::opt<std::string> DumpDirectory( | ||
"ir-dump-directory", | ||
llvm::cl::desc("A directory to dump IR log files into before and after " | ||
"passes as specified using -dump-before / -dump-after"), | ||
cl::init(""), cl::Hidden, cl::value_desc("filename")); | ||
|
||
// Print out the IR after passes, similar to -print-after-all except that it | ||
// only prints the IR after passes that change the IR. Those passes that do not | ||
// make changes to the IR are reported as not making any changes. In addition, | ||
|
@@ -139,6 +164,35 @@ std::vector<std::string> llvm::printAfterPasses() { | |
return std::vector<std::string>(PrintAfter); | ||
} | ||
|
||
bool llvm::shouldDumpBeforeSomePass() { | ||
return DumpBeforeAll || !DumpBefore.empty(); | ||
} | ||
|
||
bool llvm::shouldDumpAfterSomePass() { | ||
return DumpAfterAll || !DumpAfter.empty(); | ||
} | ||
|
||
bool llvm::shouldDumpBeforeAll() { return DumpBeforeAll; } | ||
|
||
bool llvm::shouldDumpAfterAll() { return DumpAfterAll; } | ||
|
||
bool llvm::shouldDumpBeforePass(StringRef PassID) { | ||
return DumpBeforeAll || llvm::is_contained(DumpBefore, PassID); | ||
} | ||
|
||
bool llvm::shouldDumpAfterPass(StringRef PassID) { | ||
return DumpAfterAll || llvm::is_contained(DumpAfter, PassID); | ||
} | ||
std::vector<std::string> llvm::dumpBeforePasses() { | ||
return std::vector<std::string>(DumpBefore); | ||
} | ||
|
||
std::vector<std::string> llvm::dumpAfterPasses() { | ||
return std::vector<std::string>(DumpAfter); | ||
} | ||
|
||
StringRef llvm::irInstrumentationDumpDirectory() { return DumpDirectory; } | ||
|
||
bool llvm::forcePrintModuleIR() { return PrintModuleScope; } | ||
|
||
bool llvm::isPassInPrintList(StringRef PassName) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ | |
#include "llvm/Support/FormatVariadic.h" | ||
#include "llvm/Support/GraphWriter.h" | ||
#include "llvm/Support/MemoryBuffer.h" | ||
#include "llvm/Support/Path.h" | ||
#include "llvm/Support/Program.h" | ||
#include "llvm/Support/Regex.h" | ||
#include "llvm/Support/Signals.h" | ||
|
@@ -830,6 +831,181 @@ void PrintIRInstrumentation::registerCallbacks( | |
} | ||
} | ||
|
||
void DumpIRInstrumentation::registerCallbacks( | ||
PassInstrumentationCallbacks &PIC) { | ||
|
||
if (!(shouldDumpBeforeSomePass() || shouldDumpAfterSomePass())) | ||
return; | ||
|
||
this->PIC = &PIC; | ||
|
||
PIC.registerBeforeNonSkippedPassCallback( | ||
[this](StringRef P, Any IR) { this->pushPass(P, IR); }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we omit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't yet understand why, but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmmm alright, then leave it as it for now I guess |
||
|
||
if (shouldDumpBeforeSomePass()) | ||
PIC.registerBeforeNonSkippedPassCallback( | ||
[this](StringRef P, Any IR) { this->dumpBeforePass(P, IR); }); | ||
|
||
if (shouldDumpAfterSomePass()) { | ||
PIC.registerAfterPassCallback( | ||
[this](StringRef P, Any IR, const PreservedAnalyses &) { | ||
this->dumpAfterPass(P, IR); | ||
}); | ||
} | ||
|
||
// It is important the the "popPass" callback fires after the dumpAfterPass | ||
// callback | ||
PIC.registerAfterPassCallback( | ||
[this](StringRef P, Any IR, const PreservedAnalyses &) { | ||
this->popPass(P); | ||
}); | ||
} | ||
|
||
void DumpIRInstrumentation::dumpBeforePass(StringRef PassID, Any IR) { | ||
if (isIgnored(PassID)) | ||
NuriAmari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
|
||
if (!shouldDumpBeforePass(PassID)) { | ||
return; | ||
} | ||
|
||
SmallString<16> OutputPath = | ||
fetchCurrentInstrumentationDumpFile("-before.ll"); | ||
std::error_code EC; | ||
llvm::raw_fd_ostream OS(OutputPath, EC, llvm::sys::fs::CD_CreateAlways); | ||
if (EC) | ||
report_fatal_error(Twine("Failed to open ") + OutputPath + | ||
" to support dump-before: " + EC.message()); | ||
|
||
OS << "*** IR Dump Before " << PassID << " on " << getIRName(IR) << " ***\n"; | ||
unwrapAndPrint(OS, IR); | ||
} | ||
|
||
void DumpIRInstrumentation::dumpAfterPass(StringRef PassID, Any IR) { | ||
if (isIgnored(PassID)) | ||
return; | ||
|
||
if (!shouldDumpAfterPass(PassID)) | ||
return; | ||
|
||
SmallString<16> OutputPath = fetchCurrentInstrumentationDumpFile("-after.ll"); | ||
std::error_code EC; | ||
llvm::raw_fd_ostream OS(OutputPath, EC, llvm::sys::fs::CD_CreateAlways); | ||
if (EC) | ||
report_fatal_error(Twine("Failed to open ") + OutputPath + | ||
" to support -dump-after: " + EC.message()); | ||
|
||
OS << "*** IR Dump After " << PassID << " on " << getIRName(IR) << " ***\n"; | ||
unwrapAndPrint(OS, IR); | ||
} | ||
|
||
bool DumpIRInstrumentation::shouldDumpBeforePass(StringRef PassID) const { | ||
if (shouldDumpBeforeAll()) | ||
return true; | ||
|
||
StringRef PassName = PIC->getPassNameForClassName(PassID); | ||
return is_contained(dumpBeforePasses(), PassName); | ||
} | ||
|
||
bool DumpIRInstrumentation::shouldDumpAfterPass(StringRef PassID) const { | ||
if (shouldDumpAfterAll()) | ||
return true; | ||
|
||
StringRef PassName = PIC->getPassNameForClassName(PassID); | ||
return is_contained(dumpAfterPasses(), PassName); | ||
} | ||
|
||
void DumpIRInstrumentation::pushPass(StringRef PassID, Any IR) { | ||
const Module *M = unwrapModule(IR); | ||
if (CurrentModule != M) { | ||
// If currentModule is nullptr, or is not equal to M, we are starting to | ||
// process a new module. | ||
|
||
// The first frame of the stack should maintain a frequency table | ||
// for module level passes. | ||
PipelineStateStack.clear(); | ||
PipelineStateStack.push_back(PipelineStateStackFrame(M->getName())); | ||
|
||
CurrentModule = M; | ||
} | ||
|
||
PassRunsFrequencyTableT &FreqTable = PipelineStateStack.back().FreqTable; | ||
if (!FreqTable.count(PassID)) | ||
FreqTable[PassID] = 0; | ||
|
||
PipelineStateStack.push_back(PipelineStateStackFrame(PassID)); | ||
} | ||
|
||
void DumpIRInstrumentation::popPass(StringRef PassID) { | ||
PipelineStateStack.pop_back(); | ||
assert(!PipelineStateStack.empty()); | ||
|
||
PassRunsFrequencyTableT &FreqTable = PipelineStateStack.back().FreqTable; | ||
assert(FreqTable.find(PassID) != FreqTable.end()); | ||
FreqTable[PassID]++; | ||
PipelineStateStack.back().PassCount++; | ||
} | ||
|
||
StringRef DumpIRInstrumentation::fetchInstrumentationDumpDirectory() { | ||
if (!InstrumentationDumpDirectory.empty()) | ||
return InstrumentationDumpDirectory; | ||
|
||
if (!irInstrumentationDumpDirectory().empty()) | ||
return irInstrumentationDumpDirectory(); | ||
|
||
std::error_code EC = | ||
sys::fs::createUniqueDirectory("dumped-ir", InstrumentationDumpDirectory); | ||
if (EC) | ||
report_fatal_error( | ||
Twine("Failed to create unique directory for IR dumping: ") + | ||
EC.message()); | ||
|
||
return InstrumentationDumpDirectory; | ||
} | ||
|
||
SmallString<16> | ||
DumpIRInstrumentation::fetchCurrentInstrumentationDumpFile(StringRef Suffix) { | ||
SmallString<16> OutputPath; | ||
sys::path::append(OutputPath, fetchInstrumentationDumpDirectory()); | ||
assert(CurrentModule); | ||
sys::path::append(OutputPath, | ||
sys::path::relative_path(CurrentModule->getName())); | ||
auto *StateStackIt = PipelineStateStack.begin(); | ||
// Skip over the first frame in the stack which represents the module being | ||
// processed | ||
for (++StateStackIt; StateStackIt != PipelineStateStack.end(); | ||
++StateStackIt) { | ||
SmallString<8> PathComponentName; | ||
// Check the previous frame's pass count to see how many passes of | ||
// any kind have run on this "nesting level". | ||
unsigned int PassCount = (StateStackIt - 1)->PassCount; | ||
PathComponentName += std::to_string(PassCount); | ||
PathComponentName += "."; | ||
// Check the previous frame's frequency table to see how many times | ||
// this pass has run at this "nesting level". | ||
PassRunsFrequencyTableT &FreqTable = (StateStackIt - 1)->FreqTable; | ||
StringRef FramePassID = StateStackIt->PassID; | ||
PathComponentName += FramePassID; | ||
PathComponentName += "."; | ||
assert(FreqTable.find(FramePassID) != FreqTable.end() && | ||
"DumpIRInstrumentation pass frequency table missing entry"); | ||
// Make sure the uint is converted to a character and not interpretted as | ||
// one | ||
PathComponentName += std::to_string(FreqTable[FramePassID]); | ||
sys::path::append(OutputPath, PathComponentName); | ||
} | ||
OutputPath += Suffix; | ||
|
||
// Make sure the directory we wish to write our log file into exists. | ||
StringRef ParentDirectory = sys::path::parent_path(OutputPath); | ||
if (!ParentDirectory.empty()) | ||
if (auto EC = llvm::sys::fs::create_directories(ParentDirectory)) | ||
report_fatal_error(Twine("Failed to create directory '") + | ||
ParentDirectory + "' :" + EC.message()); | ||
|
||
return OutputPath; | ||
} | ||
|
||
void OptNoneInstrumentation::registerCallbacks( | ||
PassInstrumentationCallbacks &PIC) { | ||
PIC.registerShouldRunOptionalPassCallback( | ||
|
@@ -2288,6 +2464,7 @@ void PrintCrashIRInstrumentation::registerCallbacks( | |
void StandardInstrumentations::registerCallbacks( | ||
PassInstrumentationCallbacks &PIC, ModuleAnalysisManager *MAM) { | ||
PrintIR.registerCallbacks(PIC); | ||
DumpIR.registerCallbacks(PIC); | ||
PrintPass.registerCallbacks(PIC); | ||
TimePasses.registerCallbacks(PIC); | ||
OptNone.registerCallbacks(PIC); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not implement a
dumpAfterPassInvalidated
callback, mostly because I didn't understand when that occurs. If it makes sense to include one, I can.