Index: llvm/trunk/include/llvm/Analysis/OptimizationDiagnosticInfo.h =================================================================== --- llvm/trunk/include/llvm/Analysis/OptimizationDiagnosticInfo.h +++ llvm/trunk/include/llvm/Analysis/OptimizationDiagnosticInfo.h @@ -17,6 +17,7 @@ #include "llvm/ADT/Optional.h" #include "llvm/Analysis/BlockFrequencyInfo.h" +#include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/PassManager.h" #include "llvm/Pass.h" @@ -62,6 +63,9 @@ return *this; } + /// The new interface to emit remarks. + void emit(DiagnosticInfoOptimizationBase &OptDiag); + /// Emit an optimization-applied message. /// /// \p PassName is the name of the pass emitting the message. If -Rpass= is @@ -198,8 +202,13 @@ /// If we generate BFI on demand, we need to free it when ORE is freed. std::unique_ptr OwnedBFI; + /// Compute hotness from IR value (currently assumed to be a block) if PGO is + /// available. Optional computeHotness(const Value *V); + /// Similar but use value from \p OptDiag and update hotness there. + void computeHotness(DiagnosticInfoOptimizationBase &OptDiag); + /// \brief Only allow verbose messages if we know we're filtering by hotness /// (BFI is only set in this case). bool shouldEmitVerbose() { return BFI != nullptr; } @@ -208,6 +217,14 @@ void operator=(const OptimizationRemarkEmitter &) = delete; }; +/// \brief Add a small namespace to avoid name clashes with the classes used in +/// the streaming interface. We want these to be short for better +/// write/readability. +namespace ore { +using NV = DiagnosticInfoOptimizationBase::Argument; +using setIsVerbose = DiagnosticInfoOptimizationBase::setIsVerbose; +} + /// OptimizationRemarkEmitter legacy analysis pass /// /// Note that this pass shouldn't generally be marked as preserved by other Index: llvm/trunk/include/llvm/IR/DiagnosticInfo.h =================================================================== --- llvm/trunk/include/llvm/IR/DiagnosticInfo.h +++ llvm/trunk/include/llvm/IR/DiagnosticInfo.h @@ -17,10 +17,12 @@ #include "llvm-c/Types.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/IR/DebugLoc.h" #include "llvm/Support/CBindingWrapping.h" +#include "llvm/Support/YAMLTraits.h" #include #include @@ -375,6 +377,38 @@ /// Common features for diagnostics dealing with optimization remarks. class DiagnosticInfoOptimizationBase : public DiagnosticInfoWithDebugLocBase { public: + /// \brief Used to set IsVerbose via the stream interface. + struct setIsVerbose {}; + + /// \brief Used in the streaming interface as the general argument type. It + /// internally converts everything into a key-value pair. + struct Argument { + StringRef Key; + std::string Val; + + explicit Argument(StringRef Str = "") : Key("String"), Val(Str) {} + explicit Argument(StringRef Key, Value *V) : Key(Key), Val(V->getName()) {} + explicit Argument(StringRef Key, int N) + : Key(Key), Val(std::to_string(N)) {} + }; + + /// \p PassName is the name of the pass emitting this diagnostic. \p + /// RemarkName is a textual identifier for the remark. \p Fn is the function + /// where the diagnostic is being emitted. \p DLoc is the location information + /// to use in the diagnostic. If line table information is available, the + /// diagnostic will include the source code location. \p CodeRegion is IR + /// value (currently basic block) that the optimization operates on. This is + /// currently used to provide run-time hotness information with PGO. + DiagnosticInfoOptimizationBase(enum DiagnosticKind Kind, + enum DiagnosticSeverity Severity, + const char *PassName, StringRef RemarkName, + const Function &Fn, const DebugLoc &DLoc, + Value *CodeRegion = nullptr) + : DiagnosticInfoWithDebugLocBase(Kind, Severity, Fn, DLoc), + PassName(PassName), RemarkName(RemarkName), CodeRegion(CodeRegion), + IsVerbose(false) {} + + /// Legacy interface. /// \p PassName is the name of the pass emitting this diagnostic. /// \p Fn is the function where the diagnostic is being emitted. \p DLoc is /// the location information to use in the diagnostic. If line table @@ -388,7 +422,13 @@ const DebugLoc &DLoc, const Twine &Msg, Optional Hotness = None) : DiagnosticInfoWithDebugLocBase(Kind, Severity, Fn, DLoc), - PassName(PassName), Msg(Msg), Hotness(Hotness) {} + PassName(PassName), Hotness(Hotness), IsVerbose(false) { + Args.push_back(Argument(Msg.str())); + } + + DiagnosticInfoOptimizationBase &operator<<(StringRef S); + DiagnosticInfoOptimizationBase &operator<<(Argument A); + DiagnosticInfoOptimizationBase &operator<<(setIsVerbose V); /// \see DiagnosticInfo::print. void print(DiagnosticPrinter &DP) const override; @@ -401,8 +441,13 @@ virtual bool isEnabled() const = 0; const char *getPassName() const { return PassName; } - const Twine &getMsg() const { return Msg; } + std::string getMsg() const; Optional getHotness() const { return Hotness; } + void setHotness(Optional H) { Hotness = H; } + + Value *getCodeRegion() const { return CodeRegion; } + + bool isVerbose() const { return IsVerbose; } static bool classof(const DiagnosticInfo *DI) { return DI->getKind() >= DK_FirstRemark && @@ -415,12 +460,25 @@ /// be emitted. const char *PassName; - /// Message to report. - const Twine &Msg; + /// Textual identifier for the remark. Can be used by external tools reading + /// the YAML output file for optimization remarks to identify the remark. + StringRef RemarkName; /// If profile information is available, this is the number of times the /// corresponding code was executed in a profile instrumentation run. Optional Hotness; + + /// The IR value (currently basic block) that the optimization operates on. + /// This is currently used to provide run-time hotness information with PGO. + Value *CodeRegion; + + /// Arguments collected via the streaming interface. + SmallVector Args; + + /// The remark is expected to be noisy. + bool IsVerbose; + + friend struct yaml::MappingTraits; }; /// Diagnostic information for applied optimization remarks. @@ -467,6 +525,14 @@ : DiagnosticInfoOptimizationBase(DK_OptimizationRemarkMissed, DS_Remark, PassName, Fn, DLoc, Msg, Hotness) {} + /// \p PassName is the name of the pass emitting this diagnostic. If this name + /// matches the regular expression given in -Rpass-missed=, then the + /// diagnostic will be emitted. \p RemarkName is a textual identifier for the + /// remark. \p Inst is the instruction that the optimization operates on. + DiagnosticInfoOptimizationRemarkMissed(const char *PassName, + StringRef RemarkName, + Instruction *Inst); + static bool classof(const DiagnosticInfo *DI) { return DI->getKind() == DK_OptimizationRemarkMissed; } Index: llvm/trunk/include/llvm/IR/LLVMContext.h =================================================================== --- llvm/trunk/include/llvm/IR/LLVMContext.h +++ llvm/trunk/include/llvm/IR/LLVMContext.h @@ -34,6 +34,9 @@ class Function; class DebugLoc; class OptBisect; +namespace yaml { +class Output; +} /// This is an important class for using LLVM in a threaded context. It /// (opaquely) owns and manages the core "global" data of LLVM's core @@ -181,6 +184,17 @@ /// diagnostics. void setDiagnosticHotnessRequested(bool Requested); + /// \brief Return the YAML file used by the backend to save optimization + /// diagnostics. If null, diagnostics are not saved in a file but only + /// emitted via the diagnostic handler. + yaml::Output *getDiagnosticsOutputFile(); + /// Set the diagnostics output file used for optimization diagnostics. + /// + /// By default or if invoked with null, diagnostics are not saved in a file + /// but only emitted via the diagnostic handler. Even if an output file is + /// set, the handler is invoked for each diagnostic message. + void setDiagnosticsOutputFile(yaml::Output *F); + /// \brief Get the prefix that should be printed in front of a diagnostic of /// the given \p Severity static const char *getDiagnosticMessagePrefix(DiagnosticSeverity Severity); Index: llvm/trunk/lib/Analysis/OptimizationDiagnosticInfo.cpp =================================================================== --- llvm/trunk/lib/Analysis/OptimizationDiagnosticInfo.cpp +++ llvm/trunk/lib/Analysis/OptimizationDiagnosticInfo.cpp @@ -16,6 +16,7 @@ #include "llvm/Analysis/BranchProbabilityInfo.h" #include "llvm/Analysis/LazyBlockFrequencyInfo.h" #include "llvm/Analysis/LoopInfo.h" +#include "llvm/IR/DebugInfo.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/LLVMContext.h" @@ -51,6 +52,85 @@ return BFI->getBlockProfileCount(cast(V)); } +template <> struct yaml::MappingTraits { + static void mapping(IO &io, DiagnosticInfoOptimizationBase *&OptDiag) { + assert(io.outputting() && "input not yet implemented"); + + if (io.mapTag("!Missed", OptDiag->getKind() == DK_OptimizationRemarkMissed)) + ; + else + llvm_unreachable("todo"); + + // These are read-only for now. + DebugLoc DL = OptDiag->getDebugLoc(); + StringRef FN = OptDiag->getFunction().getName(); + + StringRef PassName(OptDiag->PassName); + io.mapRequired("Pass", PassName); + io.mapRequired("Name", OptDiag->RemarkName); + if (!io.outputting() || DL) + io.mapOptional("DebugLoc", DL); + io.mapRequired("Function", FN); + io.mapOptional("Hotness", OptDiag->Hotness); + io.mapOptional("Args", OptDiag->Args); + } +}; + +template <> struct yaml::MappingTraits { + static void mapping(IO &io, DebugLoc &DL) { + assert(io.outputting() && "input not yet implemented"); + + auto *Scope = cast(DL.getScope()); + StringRef File = Scope->getFilename(); + unsigned Line = DL.getLine(); + unsigned Col = DL.getCol(); + + io.mapRequired("File", File); + io.mapRequired("Line", Line); + io.mapRequired("Column", Col); + } + + static const bool flow = true; +}; + +template <> +struct yaml::ScalarTraits { + static void output(const DiagnosticInfoOptimizationBase::Argument &Arg, + void *, llvm::raw_ostream &out) { + out << Arg.Key << ": " << Arg.Val; + } + + static StringRef input(StringRef scalar, void *, + DiagnosticInfoOptimizationBase::Argument &Arg) { + llvm_unreachable("input not yet implemented"); + } + + static bool mustQuote(StringRef) { return false; } +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(DiagnosticInfoOptimizationBase::Argument) + +void OptimizationRemarkEmitter::computeHotness( + DiagnosticInfoOptimizationBase &OptDiag) { + Value *V = OptDiag.getCodeRegion(); + if (V) + OptDiag.setHotness(computeHotness(V)); +} + +void OptimizationRemarkEmitter::emit(DiagnosticInfoOptimizationBase &OptDiag) { + computeHotness(OptDiag); + + yaml::Output *Out = F->getContext().getDiagnosticsOutputFile(); + if (Out && OptDiag.isEnabled()) { + auto *P = &const_cast(OptDiag); + *Out << P; + } + // FIXME: now that IsVerbose is part of DI, filtering for this will be moved + // from here to clang. + if (!OptDiag.isVerbose() || shouldEmitVerbose()) + F->getContext().diagnose(OptDiag); +} + void OptimizationRemarkEmitter::emitOptimizationRemark(const char *PassName, const DebugLoc &DLoc, const Value *V, Index: llvm/trunk/lib/IR/DiagnosticInfo.cpp =================================================================== --- llvm/trunk/lib/IR/DiagnosticInfo.cpp +++ llvm/trunk/lib/IR/DiagnosticInfo.cpp @@ -181,6 +181,13 @@ PassRemarksOptLoc.Pattern->match(getPassName()); } +DiagnosticInfoOptimizationRemarkMissed::DiagnosticInfoOptimizationRemarkMissed( + const char *PassName, StringRef RemarkName, Instruction *Inst) + : DiagnosticInfoOptimizationBase(DK_OptimizationRemarkMissed, DS_Remark, + PassName, RemarkName, + *Inst->getParent()->getParent(), + Inst->getDebugLoc(), Inst->getParent()) {} + bool DiagnosticInfoOptimizationRemarkMissed::isEnabled() const { return PassRemarksMissedOptLoc.Pattern && PassRemarksMissedOptLoc.Pattern->match(getPassName()); @@ -266,3 +273,29 @@ void DiagnosticInfoISelFallback::print(DiagnosticPrinter &DP) const { DP << "Instruction selection used fallback path for " << getFunction(); } + +DiagnosticInfoOptimizationBase &DiagnosticInfoOptimizationBase:: +operator<<(StringRef S) { + Args.emplace_back(S); + return *this; +} + +DiagnosticInfoOptimizationBase &DiagnosticInfoOptimizationBase:: +operator<<(Argument A) { + Args.push_back(std::move(A)); + return *this; +} + +DiagnosticInfoOptimizationBase &DiagnosticInfoOptimizationBase:: +operator<<(setIsVerbose V) { + IsVerbose = true; + return *this; +} + +std::string DiagnosticInfoOptimizationBase::getMsg() const { + std::string Str; + raw_string_ostream OS(Str); + for (const DiagnosticInfoOptimizationBase::Argument &Arg : Args) + OS << Arg.Val; + return OS.str(); +} Index: llvm/trunk/lib/IR/LLVMContext.cpp =================================================================== --- llvm/trunk/lib/IR/LLVMContext.cpp +++ llvm/trunk/lib/IR/LLVMContext.cpp @@ -203,6 +203,14 @@ return pImpl->DiagnosticHotnessRequested; } +yaml::Output *LLVMContext::getDiagnosticsOutputFile() { + return pImpl->DiagnosticsOutputFile.get(); +} + +void LLVMContext::setDiagnosticsOutputFile(yaml::Output *F) { + pImpl->DiagnosticsOutputFile.reset(F); +} + LLVMContext::DiagnosticHandlerTy LLVMContext::getDiagnosticHandler() const { return pImpl->DiagnosticHandler; } Index: llvm/trunk/lib/IR/LLVMContextImpl.h =================================================================== --- llvm/trunk/lib/IR/LLVMContextImpl.h +++ llvm/trunk/lib/IR/LLVMContextImpl.h @@ -33,6 +33,7 @@ #include "llvm/IR/Metadata.h" #include "llvm/IR/ValueHandle.h" #include "llvm/Support/Dwarf.h" +#include "llvm/Support/YAMLTraits.h" #include namespace llvm { @@ -1043,6 +1044,7 @@ void *DiagnosticContext; bool RespectDiagnosticFilters; bool DiagnosticHotnessRequested; + std::unique_ptr DiagnosticsOutputFile; LLVMContext::YieldCallbackTy YieldCallback; void *YieldOpaqueHandle; Index: llvm/trunk/lib/Transforms/IPO/Inliner.cpp =================================================================== --- llvm/trunk/lib/Transforms/IPO/Inliner.cpp +++ llvm/trunk/lib/Transforms/IPO/Inliner.cpp @@ -469,13 +469,15 @@ // direct call, so we keep it. if (Function *Callee = CS.getCalledFunction()) if (Callee->isDeclaration()) { - ORE.emitOptimizationRemarkMissedAndAnalysis( - DEBUG_TYPE, &I, - Twine(Callee->getName()) + " will not be inlined into " + - CS.getCaller()->getName(), - Twine("definition of ") + Callee->getName() + - " is not available", + ORE.emitOptimizationRemarkAnalysis( + DEBUG_TYPE, &I, Twine("definition of ") + Callee->getName() + + " is not available", /*Verbose=*/true); + using namespace ore; + ORE.emit(DiagnosticInfoOptimizationRemarkMissed(DEBUG_TYPE, + "NotInlined", &I) + << NV("Callee", Callee) << " will not be inlined into " + << NV("Caller", CS.getCaller()) << setIsVerbose()); continue; } Index: llvm/trunk/test/Transforms/Inline/optimization-remarks-yaml.ll =================================================================== --- llvm/trunk/test/Transforms/Inline/optimization-remarks-yaml.ll +++ llvm/trunk/test/Transforms/Inline/optimization-remarks-yaml.ll @@ -0,0 +1,76 @@ +; RUN: opt < %s -inline -pass-remarks-missed=inline -pass-remarks-with-hotness \ +; RUN: -pass-remarks-output=%t 2>&1 | FileCheck %s +; RUN: cat %t | FileCheck -check-prefix=YAML %s + +; Check the YAML file generated for inliner remarks for this program: +; +; 1 int foo(); +; 2 int bar(); +; 3 +; 4 int baz() { +; 5 return foo() + bar(); +; 6 } + +; CHECK: remark: /tmp/s.c:5:10: foo will not be inlined into baz (hotness: 30) +; CHECK-NEXT: remark: /tmp/s.c:5:18: bar will not be inlined into baz (hotness: 30) + +; YAML: --- !Missed +; YAML-NEXT: Pass: inline +; YAML-NEXT: Name: NotInlined +; YAML-NEXT: DebugLoc: { File: /tmp/s.c, Line: 5, Column: 10 } +; YAML-NEXT: Function: baz +; YAML-NEXT: Hotness: 30 +; YAML-NEXT: Args: +; YAML-NEXT: - Callee: foo +; YAML-NEXT: - String: will not be inlined into +; YAML-NEXT: - Caller: baz +; YAML-NEXT: ... +; YAML-NEXT: --- !Missed +; YAML-NEXT: Pass: inline +; YAML-NEXT: Name: NotInlined +; YAML-NEXT: DebugLoc: { File: /tmp/s.c, Line: 5, Column: 18 } +; YAML-NEXT: Function: baz +; YAML-NEXT: Hotness: 30 +; YAML-NEXT: Args: +; YAML-NEXT: - Callee: bar +; YAML-NEXT: - String: will not be inlined into +; YAML-NEXT: - Caller: baz +; YAML-NEXT: ... + +; ModuleID = '/tmp/s.c' +source_filename = "/tmp/s.c" +target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-apple-macosx10.11.0" + +; Function Attrs: nounwind ssp uwtable +define i32 @baz() !dbg !7 !prof !14 { +entry: + %call = call i32 (...) @foo(), !dbg !9 + %call1 = call i32 (...) @bar(), !dbg !10 + %add = add nsw i32 %call, %call1, !dbg !12 + ret i32 %add, !dbg !13 +} + +declare i32 @foo(...) + +declare i32 @bar(...) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 4.0.0 (trunk 281293) (llvm/trunk 281290)", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, enums: !2) +!1 = !DIFile(filename: "/tmp/s.c", directory: "/tmp") +!2 = !{} +!3 = !{i32 2, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"PIC Level", i32 2} +!6 = !{!"clang version 4.0.0 (trunk 281293) (llvm/trunk 281290)"} +!7 = distinct !DISubprogram(name: "baz", scope: !1, file: !1, line: 4, type: !8, isLocal: false, isDefinition: true, scopeLine: 4, isOptimized: true, unit: !0, variables: !2) +!8 = !DISubroutineType(types: !2) +!9 = !DILocation(line: 5, column: 10, scope: !7) +!10 = !DILocation(line: 5, column: 18, scope: !11) +!11 = !DILexicalBlockFile(scope: !7, file: !1, discriminator: 1) +!12 = !DILocation(line: 5, column: 16, scope: !7) +!13 = !DILocation(line: 5, column: 3, scope: !7) +!14 = !{!"function_entry_count", i64 30} Index: llvm/trunk/tools/opt/opt.cpp =================================================================== --- llvm/trunk/tools/opt/opt.cpp +++ llvm/trunk/tools/opt/opt.cpp @@ -49,6 +49,7 @@ #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/YAMLTraits.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Transforms/Coroutines.h" #include "llvm/Transforms/IPO/AlwaysInliner.h" @@ -231,6 +232,11 @@ cl::desc("With PGO, include profile count in optimization remarks"), cl::Hidden); +static cl::opt + RemarksFilename("pass-remarks-output", + cl::desc("YAML output filename for pass remarks"), + cl::value_desc("filename")); + static inline void addPass(legacy::PassManagerBase &PM, Pass *P) { // Add the pass to the pass manager... PM.add(P); @@ -408,6 +414,18 @@ if (PassRemarksWithHotness) Context.setDiagnosticHotnessRequested(true); + std::unique_ptr YamlFile; + if (RemarksFilename != "") { + std::error_code EC; + YamlFile = llvm::make_unique(RemarksFilename, EC, + sys::fs::F_None); + if (EC) { + errs() << EC.message() << '\n'; + return 1; + } + Context.setDiagnosticsOutputFile(new yaml::Output(YamlFile->os())); + } + // Load the input module... std::unique_ptr M = parseIRFile(InputFilename, Err, Context); @@ -716,6 +734,8 @@ "the compile-twice option\n"; Out->os() << BOS->str(); Out->keep(); + if (YamlFile) + YamlFile->keep(); return 1; } Out->os() << BOS->str(); @@ -725,5 +745,8 @@ if (!NoOutput || PrintBreakpoints) Out->keep(); + if (YamlFile) + YamlFile->keep(); + return 0; }