This allows various presentation of this data using an external tool.
This was first recommended here[1].
As an example, consider this module:
1 int foo();
2 int bar();
3
4 int baz() {
5 return foo() + bar();
6 }
The inliner generates these missed-optimization remarks today (the
hotness information is pulled from PGO):
remark: /tmp/s.c:5:10: foo will not be inlined into baz (hotness: 30)
remark: /tmp/s.c:5:18: bar will not be inlined into baz (hotness: 30)
Now with -pass-remarks-output=<yaml-file>, we generate this YAML file:
--- !Missed
Pass: inline
Name: NotInlined
DebugLoc: { File: /tmp/s.c, Line: 5, Column: 10 }
Function: baz
Hotness: 30
Args:
- Callee: foo
- String: will not be inlined into
- Caller: baz
...
--- !Missed
Pass: inline
Name: NotInlined
DebugLoc: { File: /tmp/s.c, Line: 5, Column: 18 }
Function: baz
Hotness: 30
Args:
- Callee: bar
- String: will not be inlined into
- Caller: baz
...
This is a summary of the high-level decisions:
* There is a new streaming interface to emit optimization remarks.
E.g. for the inliner remark above:
ORE.emit(DiagnosticInfoOptimizationRemarkMissed(
DEBUG_TYPE, "NotInlined", &I)
<< NV("Callee", Callee) << " will not be inlined into "
<< NV("Caller", CS.getCaller()) << setIsVerbose());
NV stands for named value and allows the YAML client to process a remark
using its name (NotInlined) and the named arguments (Callee and Caller)
without parsing the text of the message.
Subsequent patches will update ORE users to use the new streaming API.
* I am using YAML I/O for writing the YAML file. YAML I/O requires you
to specify reading and writing at once but reading is highly non-trivial
for some of the more complex LLVM types. Since it's not clear that we
(ever) want to use LLVM to parse this YAML file, the code supports and
asserts that we're writing only.
On the other hand, I did experiment that the class hierarchy starting at
DiagnosticInfoOptimizationBase can be mapped back from YAML generated
here (see D24479).
* The YAML stream is stored in the LLVM context.
* In the example, we can probably further specify the IR value used,
i.e. print "Function" rather than "Value".
* As before hotness is computed in the analysis pass instead of
DiganosticInfo. This avoids the layering problem since BFI is in
Analysis while DiagnosticInfo is in IR.
[1] https://reviews.llvm.org/D19678#419445
Differential Revision: https://reviews.llvm.org/D24587
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@282499
91177308-0d34-0410-b5e6-
96231b3b80d8
#include "llvm/ADT/Optional.h"
#include "llvm/Analysis/BlockFrequencyInfo.h"
+#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Pass.h"
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
/// If we generate BFI on demand, we need to free it when ORE is freed.
std::unique_ptr<BlockFrequencyInfo> OwnedBFI;
+ /// Compute hotness from IR value (currently assumed to be a block) if PGO is
+ /// available.
Optional<uint64_t> 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; }
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
#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 <functional>
#include <string>
/// 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
const DebugLoc &DLoc, const Twine &Msg,
Optional<uint64_t> 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;
virtual bool isEnabled() const = 0;
const char *getPassName() const { return PassName; }
- const Twine &getMsg() const { return Msg; }
+ std::string getMsg() const;
Optional<uint64_t> getHotness() const { return Hotness; }
+ void setHotness(Optional<uint64_t> H) { Hotness = H; }
+
+ Value *getCodeRegion() const { return CodeRegion; }
+
+ bool isVerbose() const { return IsVerbose; }
static bool classof(const DiagnosticInfo *DI) {
return DI->getKind() >= DK_FirstRemark &&
/// 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<uint64_t> 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<Argument, 4> Args;
+
+ /// The remark is expected to be noisy.
+ bool IsVerbose;
+
+ friend struct yaml::MappingTraits<DiagnosticInfoOptimizationBase *>;
};
/// Diagnostic information for applied optimization remarks.
: 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;
}
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
/// 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);
#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"
return BFI->getBlockProfileCount(cast<BasicBlock>(V));
}
+template <> struct yaml::MappingTraits<DiagnosticInfoOptimizationBase *> {
+ 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<DebugLoc> {
+ static void mapping(IO &io, DebugLoc &DL) {
+ assert(io.outputting() && "input not yet implemented");
+
+ auto *Scope = cast<DIScope>(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<DiagnosticInfoOptimizationBase::Argument> {
+ 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<DiagnosticInfoOptimizationBase &>(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,
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());
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();
+}
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;
}
#include "llvm/IR/Metadata.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/Support/Dwarf.h"
+#include "llvm/Support/YAMLTraits.h"
#include <vector>
namespace llvm {
void *DiagnosticContext;
bool RespectDiagnosticFilters;
bool DiagnosticHotnessRequested;
+ std::unique_ptr<yaml::Output> DiagnosticsOutputFile;
LLVMContext::YieldCallbackTy YieldCallback;
void *YieldOpaqueHandle;
// 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;
}
--- /dev/null
+; 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}
#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"
cl::desc("With PGO, include profile count in optimization remarks"),
cl::Hidden);
+static cl::opt<std::string>
+ 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);
if (PassRemarksWithHotness)
Context.setDiagnosticHotnessRequested(true);
+ std::unique_ptr<tool_output_file> YamlFile;
+ if (RemarksFilename != "") {
+ std::error_code EC;
+ YamlFile = llvm::make_unique<tool_output_file>(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<Module> M = parseIRFile(InputFilename, Err, Context);
"the compile-twice option\n";
Out->os() << BOS->str();
Out->keep();
+ if (YamlFile)
+ YamlFile->keep();
return 1;
}
Out->os() << BOS->str();
if (!NoOutput || PrintBreakpoints)
Out->keep();
+ if (YamlFile)
+ YamlFile->keep();
+
return 0;
}