diff --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h index 331130c6b22d9..2ec36cad244fd 100644 --- a/llvm/include/llvm/Passes/StandardInstrumentations.h +++ b/llvm/include/llvm/Passes/StandardInstrumentations.h @@ -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); @@ -55,15 +67,15 @@ class PrintIRInstrumentation { bool shouldPrintPassNumbers(); bool shouldPrintAtPassNumber(); - using PrintModuleDesc = std::tuple; - - 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 ModuleDescStack; + SmallVector PassRunDescriptorStack; /// Used for print-at-pass-number unsigned CurrentPassNumber = 0; diff --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp index 6244c0a5a949b..a0d0bf68ab425 100644 --- a/llvm/lib/Passes/StandardInstrumentations.cpp +++ b/llvm/lib/Passes/StandardInstrumentations.cpp @@ -18,6 +18,7 @@ #include "llvm/Analysis/CallGraphSCCPass.h" #include "llvm/Analysis/LazyCallGraph.h" #include "llvm/Analysis/LoopInfo.h" +#include "llvm/CodeGen/StableHashing.h" #include "llvm/IR/Constants.h" #include "llvm/IR/Function.h" #include "llvm/IR/Module.h" @@ -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" @@ -120,6 +122,13 @@ static cl::opt cl::desc("Print IR at pass with this number as " "reported by print-passes-names")); +static cl::opt 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 @@ -681,33 +690,120 @@ bool IRComparer::generateFunctionData(IRDataT &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(&IR)) { + ResultStream << "-module"; + } else if (const Function **F = any_cast(&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(&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(&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(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; @@ -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) { @@ -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) { @@ -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) { diff --git a/llvm/test/Other/dump-before-after-invalidated.ll b/llvm/test/Other/dump-before-after-invalidated.ll new file mode 100644 index 0000000000000..00b1a599b67f8 --- /dev/null +++ b/llvm/test/Other/dump-before-after-invalidated.ll @@ -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 + +; 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 +} diff --git a/llvm/test/Other/dump-before-after.ll b/llvm/test/Other/dump-before-after.ll new file mode 100644 index 0000000000000..2934a77071a75 --- /dev/null +++ b/llvm/test/Other/dump-before-after.ll @@ -0,0 +1,72 @@ +; RUN: rm -rf %t/logs + +; Basic dump before and after a single module pass + +; RUN: opt %s -disable-output -passes='no-op-module' -ir-dump-directory %t/logs -print-after=no-op-module -print-before=no-op-module +; RUN: ls %t/logs | FileCheck %s --check-prefix=SINGLE-PASS +; RUN: ls %t/logs | count 2 +; SINGLE-PASS-DAG: 0-[[MODULE_NAME_HASH:[a-z0-9]+]]-module-NoOpModulePass-after.ll +; SINGLE-PASS-DAG: 0-[[MODULE_NAME_HASH]]-module-NoOpModulePass-before.ll +; RUN: cat %t/logs/*after.ll | FileCheck %s --check-prefix=SINGLE-PASS-CONTENTS + +; SINGLE-PASS-CONTENTS: ; *** IR Dump After NoOpModulePass on [module] *** +; SINGLE-PASS-CONTENTS: define void @foo() { +; SINGLE-PASS-CONTENTS: ret void +; SINGLE-PASS-CONTENTS: } +; SINGLE-PASS-CONTENTS: define void @bar() { +; SINGLE-PASS-CONTENTS: entry: +; SINGLE-PASS-CONTENTS: br label %my-loop +; SINGLE-PASS-CONTENTS: my-loop: ; preds = %my-loop, %entry +; SINGLE-PASS-CONTENTS: br label %my-loop +; SINGLE-PASS-CONTENTS: } + +; RUN: rm -rf %t/logs + +; Dump before and after multiple runs of the same module pass +; The integers preceeding log files represent relative pass execution order, +; but they are not necessarily continuous. That is passes which are run +; but not printed, still increment the count -- leading to gaps in the printed +; integers. + +; RUN: opt %s -disable-output -passes='no-op-module,no-op-module,no-op-module' -ir-dump-directory %t/logs -print-after=no-op-module -print-before=no-op-module +; RUN: ls %t/logs | FileCheck %s --check-prefix=MULTIPLE-PASSES +; RUN: ls %t/logs | count 6 +; MULTIPLE-PASSES-DAG: 0-[[MODULE_NAME_HASH:[a-z0-9]+]]-module-NoOpModulePass-after.ll +; MULTIPLE-PASSES-DAG: 0-[[MODULE_NAME_HASH]]-module-NoOpModulePass-before.ll +; MULTIPLE-PASSES-DAG: 1-[[MODULE_NAME_HASH]]-module-NoOpModulePass-after.ll +; MULTIPLE-PASSES-DAG: 1-[[MODULE_NAME_HASH]]-module-NoOpModulePass-before.ll +; MULTIPLE-PASSES-DAG: 2-[[MODULE_NAME_HASH]]-module-NoOpModulePass-after.ll +; MULTIPLE-PASSES-DAG: 2-[[MODULE_NAME_HASH]]-module-NoOpModulePass-before.ll +; RUN: rm -rf %t/logs + +; Dump before and after multiple passes, of various levels of granularity + +; RUN: opt %s -disable-output -passes='no-op-module,cgscc(no-op-cgscc),function(no-op-function),function(loop(no-op-loop)),no-op-module' -ir-dump-directory %t/logs -print-after=no-op-module,no-op-cgscc,no-op-function,no-op-loop -print-before=no-op-module,no-op-cgscc,no-op-function,no-op-loop +; RUN: ls %t/logs | FileCheck %s --check-prefix=MULTIPLE-GRANULAR-PASSES +; RUN: ls %t/logs | count 14 +; MULTIPLE-GRANULAR-PASSES-DAG: 0-[[MODULE_NAME_HASH:[a-z0-9]+]]-module-NoOpModulePass-after.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 0-[[MODULE_NAME_HASH]]-module-NoOpModulePass-before.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 1-[[MODULE_NAME_HASH]]-scc-[[SCC_FOO_HASH:[a-z0-9]+]]-NoOpCGSCCPass-after.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 1-[[MODULE_NAME_HASH]]-scc-[[SCC_FOO_HASH]]-NoOpCGSCCPass-before.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 2-[[MODULE_NAME_HASH]]-scc-[[SCC_BAR_HASH:[a-z0-9]+]]-NoOpCGSCCPass-after.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 2-[[MODULE_NAME_HASH]]-scc-[[SCC_BAR_HASH]]-NoOpCGSCCPass-before.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 3-[[MODULE_NAME_HASH]]-function-[[FUNCTION_FOO_HASH:[a-z0-9]+]]-NoOpFunctionPass-after.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 3-[[MODULE_NAME_HASH]]-function-[[FUNCTION_FOO_HASH]]-NoOpFunctionPass-before.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 4-[[MODULE_NAME_HASH]]-function-[[FUNCTION_BAR_HASH:[a-z0-9]+]]-NoOpFunctionPass-after.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 4-[[MODULE_NAME_HASH]]-function-[[FUNCTION_BAR_HASH]]-NoOpFunctionPass-before.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 9-[[MODULE_NAME_HASH]]-loop-[[LOOP_NAME_HASH:[a-z0-9]+]]-NoOpLoopPass-after.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 9-[[MODULE_NAME_HASH]]-loop-[[LOOP_NAME_HASH]]-NoOpLoopPass-before.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 10-[[MODULE_NAME_HASH]]-module-NoOpModulePass-after.ll +; MULTIPLE-GRANULAR-PASSES-DAG: 10-[[MODULE_NAME_HASH]]-module-NoOpModulePass-before.ll +; RUN: rm -rf %t/logs + +define void @foo() { + ret void +} + +define void @bar() { +entry: + br label %my-loop +my-loop: + br label %my-loop +}