Skip to content
This repository was archived by the owner on Mar 28, 2020. It is now read-only.

Commit 3ecd753

Browse files
committed
Output optimization remarks in YAML
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
1 parent bf2f7ba commit 3ecd753

File tree

10 files changed

+331
-10
lines changed

10 files changed

+331
-10
lines changed

include/llvm/Analysis/OptimizationDiagnosticInfo.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "llvm/ADT/Optional.h"
1919
#include "llvm/Analysis/BlockFrequencyInfo.h"
20+
#include "llvm/IR/DiagnosticInfo.h"
2021
#include "llvm/IR/PassManager.h"
2122
#include "llvm/Pass.h"
2223

@@ -62,6 +63,9 @@ class OptimizationRemarkEmitter {
6263
return *this;
6364
}
6465

66+
/// The new interface to emit remarks.
67+
void emit(DiagnosticInfoOptimizationBase &OptDiag);
68+
6569
/// Emit an optimization-applied message.
6670
///
6771
/// \p PassName is the name of the pass emitting the message. If -Rpass= is
@@ -198,8 +202,13 @@ class OptimizationRemarkEmitter {
198202
/// If we generate BFI on demand, we need to free it when ORE is freed.
199203
std::unique_ptr<BlockFrequencyInfo> OwnedBFI;
200204

205+
/// Compute hotness from IR value (currently assumed to be a block) if PGO is
206+
/// available.
201207
Optional<uint64_t> computeHotness(const Value *V);
202208

209+
/// Similar but use value from \p OptDiag and update hotness there.
210+
void computeHotness(DiagnosticInfoOptimizationBase &OptDiag);
211+
203212
/// \brief Only allow verbose messages if we know we're filtering by hotness
204213
/// (BFI is only set in this case).
205214
bool shouldEmitVerbose() { return BFI != nullptr; }
@@ -208,6 +217,14 @@ class OptimizationRemarkEmitter {
208217
void operator=(const OptimizationRemarkEmitter &) = delete;
209218
};
210219

220+
/// \brief Add a small namespace to avoid name clashes with the classes used in
221+
/// the streaming interface. We want these to be short for better
222+
/// write/readability.
223+
namespace ore {
224+
using NV = DiagnosticInfoOptimizationBase::Argument;
225+
using setIsVerbose = DiagnosticInfoOptimizationBase::setIsVerbose;
226+
}
227+
211228
/// OptimizationRemarkEmitter legacy analysis pass
212229
///
213230
/// Note that this pass shouldn't generally be marked as preserved by other

include/llvm/IR/DiagnosticInfo.h

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717

1818
#include "llvm-c/Types.h"
1919
#include "llvm/ADT/Optional.h"
20+
#include "llvm/ADT/SmallString.h"
2021
#include "llvm/ADT/StringRef.h"
2122
#include "llvm/ADT/Twine.h"
2223
#include "llvm/IR/DebugLoc.h"
2324
#include "llvm/Support/CBindingWrapping.h"
25+
#include "llvm/Support/YAMLTraits.h"
2426
#include <functional>
2527
#include <string>
2628

@@ -375,6 +377,38 @@ class DiagnosticInfoWithDebugLocBase : public DiagnosticInfo {
375377
/// Common features for diagnostics dealing with optimization remarks.
376378
class DiagnosticInfoOptimizationBase : public DiagnosticInfoWithDebugLocBase {
377379
public:
380+
/// \brief Used to set IsVerbose via the stream interface.
381+
struct setIsVerbose {};
382+
383+
/// \brief Used in the streaming interface as the general argument type. It
384+
/// internally converts everything into a key-value pair.
385+
struct Argument {
386+
StringRef Key;
387+
std::string Val;
388+
389+
explicit Argument(StringRef Str = "") : Key("String"), Val(Str) {}
390+
explicit Argument(StringRef Key, Value *V) : Key(Key), Val(V->getName()) {}
391+
explicit Argument(StringRef Key, int N)
392+
: Key(Key), Val(std::to_string(N)) {}
393+
};
394+
395+
/// \p PassName is the name of the pass emitting this diagnostic. \p
396+
/// RemarkName is a textual identifier for the remark. \p Fn is the function
397+
/// where the diagnostic is being emitted. \p DLoc is the location information
398+
/// to use in the diagnostic. If line table information is available, the
399+
/// diagnostic will include the source code location. \p CodeRegion is IR
400+
/// value (currently basic block) that the optimization operates on. This is
401+
/// currently used to provide run-time hotness information with PGO.
402+
DiagnosticInfoOptimizationBase(enum DiagnosticKind Kind,
403+
enum DiagnosticSeverity Severity,
404+
const char *PassName, StringRef RemarkName,
405+
const Function &Fn, const DebugLoc &DLoc,
406+
Value *CodeRegion = nullptr)
407+
: DiagnosticInfoWithDebugLocBase(Kind, Severity, Fn, DLoc),
408+
PassName(PassName), RemarkName(RemarkName), CodeRegion(CodeRegion),
409+
IsVerbose(false) {}
410+
411+
/// Legacy interface.
378412
/// \p PassName is the name of the pass emitting this diagnostic.
379413
/// \p Fn is the function where the diagnostic is being emitted. \p DLoc is
380414
/// the location information to use in the diagnostic. If line table
@@ -388,7 +422,13 @@ class DiagnosticInfoOptimizationBase : public DiagnosticInfoWithDebugLocBase {
388422
const DebugLoc &DLoc, const Twine &Msg,
389423
Optional<uint64_t> Hotness = None)
390424
: DiagnosticInfoWithDebugLocBase(Kind, Severity, Fn, DLoc),
391-
PassName(PassName), Msg(Msg), Hotness(Hotness) {}
425+
PassName(PassName), Hotness(Hotness), IsVerbose(false) {
426+
Args.push_back(Argument(Msg.str()));
427+
}
428+
429+
DiagnosticInfoOptimizationBase &operator<<(StringRef S);
430+
DiagnosticInfoOptimizationBase &operator<<(Argument A);
431+
DiagnosticInfoOptimizationBase &operator<<(setIsVerbose V);
392432

393433
/// \see DiagnosticInfo::print.
394434
void print(DiagnosticPrinter &DP) const override;
@@ -401,8 +441,13 @@ class DiagnosticInfoOptimizationBase : public DiagnosticInfoWithDebugLocBase {
401441
virtual bool isEnabled() const = 0;
402442

403443
const char *getPassName() const { return PassName; }
404-
const Twine &getMsg() const { return Msg; }
444+
std::string getMsg() const;
405445
Optional<uint64_t> getHotness() const { return Hotness; }
446+
void setHotness(Optional<uint64_t> H) { Hotness = H; }
447+
448+
Value *getCodeRegion() const { return CodeRegion; }
449+
450+
bool isVerbose() const { return IsVerbose; }
406451

407452
static bool classof(const DiagnosticInfo *DI) {
408453
return DI->getKind() >= DK_FirstRemark &&
@@ -415,12 +460,25 @@ class DiagnosticInfoOptimizationBase : public DiagnosticInfoWithDebugLocBase {
415460
/// be emitted.
416461
const char *PassName;
417462

418-
/// Message to report.
419-
const Twine &Msg;
463+
/// Textual identifier for the remark. Can be used by external tools reading
464+
/// the YAML output file for optimization remarks to identify the remark.
465+
StringRef RemarkName;
420466

421467
/// If profile information is available, this is the number of times the
422468
/// corresponding code was executed in a profile instrumentation run.
423469
Optional<uint64_t> Hotness;
470+
471+
/// The IR value (currently basic block) that the optimization operates on.
472+
/// This is currently used to provide run-time hotness information with PGO.
473+
Value *CodeRegion;
474+
475+
/// Arguments collected via the streaming interface.
476+
SmallVector<Argument, 4> Args;
477+
478+
/// The remark is expected to be noisy.
479+
bool IsVerbose;
480+
481+
friend struct yaml::MappingTraits<DiagnosticInfoOptimizationBase *>;
424482
};
425483

426484
/// Diagnostic information for applied optimization remarks.
@@ -467,6 +525,14 @@ class DiagnosticInfoOptimizationRemarkMissed
467525
: DiagnosticInfoOptimizationBase(DK_OptimizationRemarkMissed, DS_Remark,
468526
PassName, Fn, DLoc, Msg, Hotness) {}
469527

528+
/// \p PassName is the name of the pass emitting this diagnostic. If this name
529+
/// matches the regular expression given in -Rpass-missed=, then the
530+
/// diagnostic will be emitted. \p RemarkName is a textual identifier for the
531+
/// remark. \p Inst is the instruction that the optimization operates on.
532+
DiagnosticInfoOptimizationRemarkMissed(const char *PassName,
533+
StringRef RemarkName,
534+
Instruction *Inst);
535+
470536
static bool classof(const DiagnosticInfo *DI) {
471537
return DI->getKind() == DK_OptimizationRemarkMissed;
472538
}

include/llvm/IR/LLVMContext.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ template <typename T> class SmallVectorImpl;
3434
class Function;
3535
class DebugLoc;
3636
class OptBisect;
37+
namespace yaml {
38+
class Output;
39+
}
3740

3841
/// This is an important class for using LLVM in a threaded context. It
3942
/// (opaquely) owns and manages the core "global" data of LLVM's core
@@ -181,6 +184,17 @@ class LLVMContext {
181184
/// diagnostics.
182185
void setDiagnosticHotnessRequested(bool Requested);
183186

187+
/// \brief Return the YAML file used by the backend to save optimization
188+
/// diagnostics. If null, diagnostics are not saved in a file but only
189+
/// emitted via the diagnostic handler.
190+
yaml::Output *getDiagnosticsOutputFile();
191+
/// Set the diagnostics output file used for optimization diagnostics.
192+
///
193+
/// By default or if invoked with null, diagnostics are not saved in a file
194+
/// but only emitted via the diagnostic handler. Even if an output file is
195+
/// set, the handler is invoked for each diagnostic message.
196+
void setDiagnosticsOutputFile(yaml::Output *F);
197+
184198
/// \brief Get the prefix that should be printed in front of a diagnostic of
185199
/// the given \p Severity
186200
static const char *getDiagnosticMessagePrefix(DiagnosticSeverity Severity);

lib/Analysis/OptimizationDiagnosticInfo.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "llvm/Analysis/BranchProbabilityInfo.h"
1717
#include "llvm/Analysis/LazyBlockFrequencyInfo.h"
1818
#include "llvm/Analysis/LoopInfo.h"
19+
#include "llvm/IR/DebugInfo.h"
1920
#include "llvm/IR/DiagnosticInfo.h"
2021
#include "llvm/IR/Dominators.h"
2122
#include "llvm/IR/LLVMContext.h"
@@ -51,6 +52,85 @@ Optional<uint64_t> OptimizationRemarkEmitter::computeHotness(const Value *V) {
5152
return BFI->getBlockProfileCount(cast<BasicBlock>(V));
5253
}
5354

55+
template <> struct yaml::MappingTraits<DiagnosticInfoOptimizationBase *> {
56+
static void mapping(IO &io, DiagnosticInfoOptimizationBase *&OptDiag) {
57+
assert(io.outputting() && "input not yet implemented");
58+
59+
if (io.mapTag("!Missed", OptDiag->getKind() == DK_OptimizationRemarkMissed))
60+
;
61+
else
62+
llvm_unreachable("todo");
63+
64+
// These are read-only for now.
65+
DebugLoc DL = OptDiag->getDebugLoc();
66+
StringRef FN = OptDiag->getFunction().getName();
67+
68+
StringRef PassName(OptDiag->PassName);
69+
io.mapRequired("Pass", PassName);
70+
io.mapRequired("Name", OptDiag->RemarkName);
71+
if (!io.outputting() || DL)
72+
io.mapOptional("DebugLoc", DL);
73+
io.mapRequired("Function", FN);
74+
io.mapOptional("Hotness", OptDiag->Hotness);
75+
io.mapOptional("Args", OptDiag->Args);
76+
}
77+
};
78+
79+
template <> struct yaml::MappingTraits<DebugLoc> {
80+
static void mapping(IO &io, DebugLoc &DL) {
81+
assert(io.outputting() && "input not yet implemented");
82+
83+
auto *Scope = cast<DIScope>(DL.getScope());
84+
StringRef File = Scope->getFilename();
85+
unsigned Line = DL.getLine();
86+
unsigned Col = DL.getCol();
87+
88+
io.mapRequired("File", File);
89+
io.mapRequired("Line", Line);
90+
io.mapRequired("Column", Col);
91+
}
92+
93+
static const bool flow = true;
94+
};
95+
96+
template <>
97+
struct yaml::ScalarTraits<DiagnosticInfoOptimizationBase::Argument> {
98+
static void output(const DiagnosticInfoOptimizationBase::Argument &Arg,
99+
void *, llvm::raw_ostream &out) {
100+
out << Arg.Key << ": " << Arg.Val;
101+
}
102+
103+
static StringRef input(StringRef scalar, void *,
104+
DiagnosticInfoOptimizationBase::Argument &Arg) {
105+
llvm_unreachable("input not yet implemented");
106+
}
107+
108+
static bool mustQuote(StringRef) { return false; }
109+
};
110+
111+
LLVM_YAML_IS_SEQUENCE_VECTOR(DiagnosticInfoOptimizationBase::Argument)
112+
113+
void OptimizationRemarkEmitter::computeHotness(
114+
DiagnosticInfoOptimizationBase &OptDiag) {
115+
Value *V = OptDiag.getCodeRegion();
116+
if (V)
117+
OptDiag.setHotness(computeHotness(V));
118+
}
119+
120+
void OptimizationRemarkEmitter::emit(DiagnosticInfoOptimizationBase &OptDiag) {
121+
computeHotness(OptDiag);
122+
123+
yaml::Output *Out = F->getContext().getDiagnosticsOutputFile();
124+
if (Out && OptDiag.isEnabled()) {
125+
auto *P = &const_cast<DiagnosticInfoOptimizationBase &>(OptDiag);
126+
*Out << P;
127+
}
128+
// FIXME: now that IsVerbose is part of DI, filtering for this will be moved
129+
// from here to clang.
130+
if (!OptDiag.isVerbose() || shouldEmitVerbose())
131+
F->getContext().diagnose(OptDiag);
132+
}
133+
54134
void OptimizationRemarkEmitter::emitOptimizationRemark(const char *PassName,
55135
const DebugLoc &DLoc,
56136
const Value *V,

lib/IR/DiagnosticInfo.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ bool DiagnosticInfoOptimizationRemark::isEnabled() const {
181181
PassRemarksOptLoc.Pattern->match(getPassName());
182182
}
183183

184+
DiagnosticInfoOptimizationRemarkMissed::DiagnosticInfoOptimizationRemarkMissed(
185+
const char *PassName, StringRef RemarkName, Instruction *Inst)
186+
: DiagnosticInfoOptimizationBase(DK_OptimizationRemarkMissed, DS_Remark,
187+
PassName, RemarkName,
188+
*Inst->getParent()->getParent(),
189+
Inst->getDebugLoc(), Inst->getParent()) {}
190+
184191
bool DiagnosticInfoOptimizationRemarkMissed::isEnabled() const {
185192
return PassRemarksMissedOptLoc.Pattern &&
186193
PassRemarksMissedOptLoc.Pattern->match(getPassName());
@@ -266,3 +273,29 @@ void llvm::emitLoopInterleaveWarning(LLVMContext &Ctx, const Function &Fn,
266273
void DiagnosticInfoISelFallback::print(DiagnosticPrinter &DP) const {
267274
DP << "Instruction selection used fallback path for " << getFunction();
268275
}
276+
277+
DiagnosticInfoOptimizationBase &DiagnosticInfoOptimizationBase::
278+
operator<<(StringRef S) {
279+
Args.emplace_back(S);
280+
return *this;
281+
}
282+
283+
DiagnosticInfoOptimizationBase &DiagnosticInfoOptimizationBase::
284+
operator<<(Argument A) {
285+
Args.push_back(std::move(A));
286+
return *this;
287+
}
288+
289+
DiagnosticInfoOptimizationBase &DiagnosticInfoOptimizationBase::
290+
operator<<(setIsVerbose V) {
291+
IsVerbose = true;
292+
return *this;
293+
}
294+
295+
std::string DiagnosticInfoOptimizationBase::getMsg() const {
296+
std::string Str;
297+
raw_string_ostream OS(Str);
298+
for (const DiagnosticInfoOptimizationBase::Argument &Arg : Args)
299+
OS << Arg.Val;
300+
return OS.str();
301+
}

lib/IR/LLVMContext.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ bool LLVMContext::getDiagnosticHotnessRequested() const {
203203
return pImpl->DiagnosticHotnessRequested;
204204
}
205205

206+
yaml::Output *LLVMContext::getDiagnosticsOutputFile() {
207+
return pImpl->DiagnosticsOutputFile.get();
208+
}
209+
210+
void LLVMContext::setDiagnosticsOutputFile(yaml::Output *F) {
211+
pImpl->DiagnosticsOutputFile.reset(F);
212+
}
213+
206214
LLVMContext::DiagnosticHandlerTy LLVMContext::getDiagnosticHandler() const {
207215
return pImpl->DiagnosticHandler;
208216
}

lib/IR/LLVMContextImpl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "llvm/IR/Metadata.h"
3434
#include "llvm/IR/ValueHandle.h"
3535
#include "llvm/Support/Dwarf.h"
36+
#include "llvm/Support/YAMLTraits.h"
3637
#include <vector>
3738

3839
namespace llvm {
@@ -1043,6 +1044,7 @@ class LLVMContextImpl {
10431044
void *DiagnosticContext;
10441045
bool RespectDiagnosticFilters;
10451046
bool DiagnosticHotnessRequested;
1047+
std::unique_ptr<yaml::Output> DiagnosticsOutputFile;
10461048

10471049
LLVMContext::YieldCallbackTy YieldCallback;
10481050
void *YieldOpaqueHandle;

lib/Transforms/IPO/Inliner.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,15 @@ inlineCallsImpl(CallGraphSCC &SCC, CallGraph &CG,
469469
// direct call, so we keep it.
470470
if (Function *Callee = CS.getCalledFunction())
471471
if (Callee->isDeclaration()) {
472-
ORE.emitOptimizationRemarkMissedAndAnalysis(
473-
DEBUG_TYPE, &I,
474-
Twine(Callee->getName()) + " will not be inlined into " +
475-
CS.getCaller()->getName(),
476-
Twine("definition of ") + Callee->getName() +
477-
" is not available",
472+
ORE.emitOptimizationRemarkAnalysis(
473+
DEBUG_TYPE, &I, Twine("definition of ") + Callee->getName() +
474+
" is not available",
478475
/*Verbose=*/true);
476+
using namespace ore;
477+
ORE.emit(DiagnosticInfoOptimizationRemarkMissed(DEBUG_TYPE,
478+
"NotInlined", &I)
479+
<< NV("Callee", Callee) << " will not be inlined into "
480+
<< NV("Caller", CS.getCaller()) << setIsVerbose());
479481
continue;
480482
}
481483

0 commit comments

Comments
 (0)