Skip to content

Add option to dump IR to files instead of stderr #66412

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

Merged
merged 1 commit into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions llvm/include/llvm/Passes/StandardInstrumentations.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ class PrintIRInstrumentation {
void registerCallbacks(PassInstrumentationCallbacks &PIC);

private:
struct PassRunDescriptor {
const Module *M;
const std::string DumpIRFilename;
const std::string IRName;
const StringRef PassID;

PassRunDescriptor(const Module *M, std::string DumpIRFilename,
std::string IRName, const StringRef PassID)
: M{M}, DumpIRFilename{DumpIRFilename}, IRName{IRName}, PassID(PassID) {
}
};

void printBeforePass(StringRef PassID, Any IR);
void printAfterPass(StringRef PassID, Any IR);
void printAfterPassInvalidated(StringRef PassID);
Expand All @@ -55,15 +67,15 @@ class PrintIRInstrumentation {
bool shouldPrintPassNumbers();
bool shouldPrintAtPassNumber();

using PrintModuleDesc = std::tuple<const Module *, std::string, StringRef>;

void pushModuleDesc(StringRef PassID, Any IR);
PrintModuleDesc popModuleDesc(StringRef PassID);
void pushPassRunDescriptor(StringRef PassID, Any IR,
std::string &DumpIRFilename);
PassRunDescriptor popPassRunDescriptor(StringRef PassID);
std::string fetchDumpFilename(StringRef PassId, Any IR);

PassInstrumentationCallbacks *PIC;
/// Stack of Module description, enough to print the module after a given
/// Stack of Pass Run descriptions, enough to print the IR unit after a given
/// pass.
SmallVector<PrintModuleDesc, 2> ModuleDescStack;
SmallVector<PassRunDescriptor, 2> PassRunDescriptorStack;

/// Used for print-at-pass-number
unsigned CurrentPassNumber = 0;
Expand Down
204 changes: 168 additions & 36 deletions llvm/lib/Passes/StandardInstrumentations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "llvm/Analysis/CallGraphSCCPass.h"
#include "llvm/Analysis/LazyCallGraph.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/CodeGen/StableHashing.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should pull StableHashing.h out of CodeGen first

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#67704, although I'll wait for this PR to go in first

#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Module.h"
Expand All @@ -33,6 +34,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"
Expand Down Expand Up @@ -120,6 +122,13 @@ static cl::opt<unsigned>
cl::desc("Print IR at pass with this number as "
"reported by print-passes-names"));

static cl::opt<std::string> IRDumpDirectory(
"ir-dump-directory",
cl::desc("If specified, IR printed using the "
"-print-[before|after]{-all} options will be dumped into "
"files in this directory rather than written to stderr"),
cl::Hidden, cl::value_desc("filename"));

namespace {

// An option for specifying an executable that will be called with the IR
Expand Down Expand Up @@ -681,33 +690,120 @@ bool IRComparer<T>::generateFunctionData(IRDataT<T> &Data, const Function &F) {
}

PrintIRInstrumentation::~PrintIRInstrumentation() {
assert(ModuleDescStack.empty() && "ModuleDescStack is not empty at exit");
assert(PassRunDescriptorStack.empty() &&
"PassRunDescriptorStack is not empty at exit");
}

static SmallString<32> getIRFileDisplayName(Any IR) {
SmallString<32> Result;
raw_svector_ostream ResultStream(Result);
const Module *M = unwrapModule(IR);
stable_hash NameHash = stable_hash_combine_string(M->getName());
unsigned int MaxHashWidth = sizeof(stable_hash) * 8 / 4;
write_hex(ResultStream, NameHash, HexPrintStyle::Lower, MaxHashWidth);
if (any_cast<const Module *>(&IR)) {
ResultStream << "-module";
} else if (const Function **F = any_cast<const Function *>(&IR)) {
ResultStream << "-function-";
stable_hash FunctionNameHash = stable_hash_combine_string((*F)->getName());
write_hex(ResultStream, FunctionNameHash, HexPrintStyle::Lower,
MaxHashWidth);
} else if (const LazyCallGraph::SCC **C =
any_cast<const LazyCallGraph::SCC *>(&IR)) {
ResultStream << "-scc-";
stable_hash SCCNameHash = stable_hash_combine_string((*C)->getName());
write_hex(ResultStream, SCCNameHash, HexPrintStyle::Lower, MaxHashWidth);
} else if (const Loop **L = any_cast<const Loop *>(&IR)) {
ResultStream << "-loop-";
stable_hash LoopNameHash = stable_hash_combine_string((*L)->getName());
write_hex(ResultStream, LoopNameHash, HexPrintStyle::Lower, MaxHashWidth);
} else {
llvm_unreachable("Unknown wrapped IR type");
}
return Result;
}

std::string PrintIRInstrumentation::fetchDumpFilename(StringRef PassName,
Any IR) {
const StringRef RootDirectory = IRDumpDirectory;
assert(!RootDirectory.empty() &&
"The flag -ir-dump-directory must be passed to dump IR to files");
SmallString<128> ResultPath;
ResultPath += RootDirectory;
SmallString<64> Filename;
raw_svector_ostream FilenameStream(Filename);
FilenameStream << CurrentPassNumber;
FilenameStream << "-";
FilenameStream << getIRFileDisplayName(IR);
FilenameStream << "-";
FilenameStream << PassName;
sys::path::append(ResultPath, Filename);
return std::string(ResultPath);
}

enum class IRDumpFileSuffixType {
Before,
After,
Invalidated,
};

static StringRef getFileSuffix(IRDumpFileSuffixType Type) {
static constexpr std::array FileSuffixes = {"-before.ll", "-after.ll",
"-invalidated.ll"};
return FileSuffixes[static_cast<size_t>(Type)];
}

void PrintIRInstrumentation::pushModuleDesc(StringRef PassID, Any IR) {
void PrintIRInstrumentation::pushPassRunDescriptor(
StringRef PassID, Any IR, std::string &DumpIRFilename) {
const Module *M = unwrapModule(IR);
ModuleDescStack.emplace_back(M, getIRName(IR), PassID);
PassRunDescriptorStack.emplace_back(
PassRunDescriptor(M, DumpIRFilename, getIRName(IR), PassID));
}

PrintIRInstrumentation::PrintModuleDesc
PrintIRInstrumentation::popModuleDesc(StringRef PassID) {
assert(!ModuleDescStack.empty() && "empty ModuleDescStack");
PrintModuleDesc ModuleDesc = ModuleDescStack.pop_back_val();
assert(std::get<2>(ModuleDesc).equals(PassID) && "malformed ModuleDescStack");
return ModuleDesc;
PrintIRInstrumentation::PassRunDescriptor
PrintIRInstrumentation::popPassRunDescriptor(StringRef PassID) {
assert(!PassRunDescriptorStack.empty() && "empty PassRunDescriptorStack");
PassRunDescriptor Descriptor = PassRunDescriptorStack.pop_back_val();
assert(Descriptor.PassID.equals(PassID) &&
"malformed PassRunDescriptorStack");
return Descriptor;
}

// Callers are responsible for closing the returned file descriptor
static int prepareDumpIRFileDescriptor(const StringRef DumpIRFilename) {
std::error_code EC;
auto ParentPath = llvm::sys::path::parent_path(DumpIRFilename);
if (!ParentPath.empty()) {
std::error_code EC = llvm::sys::fs::create_directories(ParentPath);
if (EC)
report_fatal_error(Twine("Failed to create directory ") + ParentPath +
" to support -ir-dump-directory: " + EC.message());
}
int Result = 0;
EC = sys::fs::openFile(DumpIRFilename, Result, sys::fs::CD_OpenAlways,
sys::fs::FA_Write, sys::fs::OF_None);
if (EC)
report_fatal_error(Twine("Failed to open ") + DumpIRFilename +
" to support -ir-dump-directory: " + EC.message());
return Result;
}

void PrintIRInstrumentation::printBeforePass(StringRef PassID, Any IR) {
if (isIgnored(PassID))
return;

std::string DumpIRFilename;
if (!IRDumpDirectory.empty() &&
(shouldPrintBeforePass(PassID) || shouldPrintAfterPass(PassID)))
DumpIRFilename = fetchDumpFilename(PassID, IR);

// Saving Module for AfterPassInvalidated operations.
// Note: here we rely on a fact that we do not change modules while
// traversing the pipeline, so the latest captured module is good
// for all print operations that has not happen yet.
if (shouldPrintPassNumbers() || shouldPrintAtPassNumber() ||
shouldPrintAfterPass(PassID))
pushModuleDesc(PassID, IR);
pushPassRunDescriptor(PassID, IR, DumpIRFilename);

if (!shouldPrintIR(IR))
return;
Expand All @@ -720,9 +816,20 @@ void PrintIRInstrumentation::printBeforePass(StringRef PassID, Any IR) {
if (!shouldPrintBeforePass(PassID))
return;

dbgs() << "*** IR Dump Before " << PassID << " on " << getIRName(IR)
<< " ***\n";
unwrapAndPrint(dbgs(), IR);
auto WriteIRToStream = [&](raw_ostream &Stream) {
Stream << "; *** IR Dump Before " << PassID << " on " << getIRName(IR)
<< " ***\n";
unwrapAndPrint(Stream, IR);
};

if (!DumpIRFilename.empty()) {
DumpIRFilename += getFileSuffix(IRDumpFileSuffixType::Before);
llvm::raw_fd_ostream DumpIRFileStream{
prepareDumpIRFileDescriptor(DumpIRFilename), /* shouldClose */ true};
WriteIRToStream(DumpIRFileStream);
} else {
WriteIRToStream(dbgs());
}
}

void PrintIRInstrumentation::printAfterPass(StringRef PassID, Any IR) {
Expand All @@ -733,21 +840,33 @@ void PrintIRInstrumentation::printAfterPass(StringRef PassID, Any IR) {
!shouldPrintAtPassNumber())
return;

const Module *M;
std::string IRName;
StringRef StoredPassID;
std::tie(M, IRName, StoredPassID) = popModuleDesc(PassID);
auto [M, DumpIRFilename, IRName, StoredPassID] = popPassRunDescriptor(PassID);
assert(StoredPassID == PassID && "mismatched PassID");

if (!shouldPrintIR(IR) || !shouldPrintAfterPass(PassID))
return;

dbgs() << "*** IR Dump "
<< (shouldPrintAtPassNumber()
? StringRef(formatv("At {0}-{1}", CurrentPassNumber, PassID))
: StringRef(formatv("After {0}", PassID)))
<< " on " << IRName << " ***\n";
unwrapAndPrint(dbgs(), IR);
auto WriteIRToStream = [&](raw_ostream &Stream, const StringRef IRName) {
Stream << "; *** IR Dump "
<< (shouldPrintAtPassNumber()
? StringRef(formatv("At {0}-{1}", CurrentPassNumber, PassID))
: StringRef(formatv("After {0}", PassID)))
<< " on " << IRName << " ***\n";
unwrapAndPrint(Stream, IR);
};

if (!IRDumpDirectory.empty()) {
assert(!DumpIRFilename.empty() && "DumpIRFilename must not be empty and "
"should be set in printBeforePass");
const std::string DumpIRFilenameWithSuffix =
DumpIRFilename + getFileSuffix(IRDumpFileSuffixType::After).str();
llvm::raw_fd_ostream DumpIRFileStream{
prepareDumpIRFileDescriptor(DumpIRFilenameWithSuffix),
/* shouldClose */ true};
WriteIRToStream(DumpIRFileStream, IRName);
} else {
WriteIRToStream(dbgs(), IRName);
}
}

void PrintIRInstrumentation::printAfterPassInvalidated(StringRef PassID) {
Expand All @@ -758,25 +877,38 @@ void PrintIRInstrumentation::printAfterPassInvalidated(StringRef PassID) {
!shouldPrintAtPassNumber())
return;

const Module *M;
std::string IRName;
StringRef StoredPassID;
std::tie(M, IRName, StoredPassID) = popModuleDesc(PassID);
auto [M, DumpIRFilename, IRName, StoredPassID] = popPassRunDescriptor(PassID);
assert(StoredPassID == PassID && "mismatched PassID");
// Additional filtering (e.g. -filter-print-func) can lead to module
// printing being skipped.
if (!M || !shouldPrintAfterPass(PassID))
return;

SmallString<20> Banner;
if (shouldPrintAtPassNumber())
Banner = formatv("*** IR Dump At {0}-{1} on {2} (invalidated) ***",
CurrentPassNumber, PassID, IRName);
else
Banner = formatv("*** IR Dump After {0} on {1} (invalidated) ***",
PassID, IRName);
dbgs() << Banner << "\n";
printIR(dbgs(), M);
auto WriteIRToStream = [&](raw_ostream &Stream, const Module *M,
const StringRef IRName) {
SmallString<20> Banner;
if (shouldPrintAtPassNumber())
Banner = formatv("; *** IR Dump At {0}-{1} on {2} (invalidated) ***",
CurrentPassNumber, PassID, IRName);
else
Banner = formatv("; *** IR Dump After {0} on {1} (invalidated) ***",
PassID, IRName);
Stream << Banner << "\n";
printIR(Stream, M);
};

if (!IRDumpDirectory.empty()) {
assert(!DumpIRFilename.empty() && "DumpIRFilename must not be empty and "
"should be set in printBeforePass");
const std::string DumpIRFilenameWithSuffix =
DumpIRFilename + getFileSuffix(IRDumpFileSuffixType::Invalidated).str();
llvm::raw_fd_ostream DumpIRFileStream{
prepareDumpIRFileDescriptor(DumpIRFilenameWithSuffix),
/* shouldClose */ true};
WriteIRToStream(DumpIRFileStream, M, IRName);
} else {
WriteIRToStream(dbgs(), M, IRName);
}
}

bool PrintIRInstrumentation::shouldPrintBeforePass(StringRef PassID) {
Expand Down
24 changes: 24 additions & 0 deletions llvm/test/Other/dump-before-after-invalidated.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
; RUN: rm -rf %t/logs
; RUN: opt %s -disable-output -passes=loop-deletion -ir-dump-directory %t/logs -print-after=loop-deletion

; RUN: ls %t/logs | FileCheck %s
; CHECK: 2-{{[a-z0-9]+}}-loop-{{[a-z0-9]+}}-LoopDeletionPass-invalidated.ll

; RUN: ls %t/logs | count 1
; RUN: cat %t/logs/* | FileCheck %s --check-prefix=CHECK-CONTENTS
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully this globbing is ok


; CHECK-CONTENTS: ; *** IR Dump After LoopDeletionPass on bb1 (invalidated) ***
; CHECK-CONTENTS: define void @foo() {
; CHECK-CONTENTS: br label %bb2
; CHECK-CONTENTS: bb2: ; preds = %0
; CHECK-CONTENTS: ret void
; CHECK-CONTENTS: }


define void @foo() {
br label %bb1
bb1:
br i1 false, label %bb1, label %bb2
bb2:
ret void
}
Loading