Skip to content

Commit

Permalink
Output optimization remarks in YAML
Browse files Browse the repository at this point in the history
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

llvm-svn: 282499
  • Loading branch information
anemet committed Sep 27, 2016
1 parent b897fa5 commit 92e928c
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 10 deletions.
17 changes: 17 additions & 0 deletions llvm/include/llvm/Analysis/OptimizationDiagnosticInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -62,6 +63,9 @@ class OptimizationRemarkEmitter {
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
Expand Down Expand Up @@ -198,8 +202,13 @@ class OptimizationRemarkEmitter {
/// 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; }
Expand All @@ -208,6 +217,14 @@ class OptimizationRemarkEmitter {
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
Expand Down
74 changes: 70 additions & 4 deletions llvm/include/llvm/IR/DiagnosticInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <functional>
#include <string>

Expand Down Expand Up @@ -375,6 +377,38 @@ class DiagnosticInfoWithDebugLocBase : public DiagnosticInfo {
/// 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
Expand All @@ -388,7 +422,13 @@ class DiagnosticInfoOptimizationBase : public DiagnosticInfoWithDebugLocBase {
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;
Expand All @@ -401,8 +441,13 @@ class DiagnosticInfoOptimizationBase : public DiagnosticInfoWithDebugLocBase {
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 &&
Expand All @@ -415,12 +460,25 @@ class DiagnosticInfoOptimizationBase : public DiagnosticInfoWithDebugLocBase {
/// 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.
Expand Down Expand Up @@ -467,6 +525,14 @@ class DiagnosticInfoOptimizationRemarkMissed
: 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;
}
Expand Down
14 changes: 14 additions & 0 deletions llvm/include/llvm/IR/LLVMContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ template <typename T> class SmallVectorImpl;
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
Expand Down Expand Up @@ -181,6 +184,17 @@ class LLVMContext {
/// 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);
Expand Down
80 changes: 80 additions & 0 deletions llvm/lib/Analysis/OptimizationDiagnosticInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -51,6 +52,85 @@ Optional<uint64_t> OptimizationRemarkEmitter::computeHotness(const Value *V) {
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,
Expand Down
33 changes: 33 additions & 0 deletions llvm/lib/IR/DiagnosticInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ bool DiagnosticInfoOptimizationRemark::isEnabled() const {
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());
Expand Down Expand Up @@ -266,3 +273,29 @@ void llvm::emitLoopInterleaveWarning(LLVMContext &Ctx, const Function &Fn,
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();
}
8 changes: 8 additions & 0 deletions llvm/lib/IR/LLVMContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ bool LLVMContext::getDiagnosticHotnessRequested() const {
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;
}
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/IR/LLVMContextImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <vector>

namespace llvm {
Expand Down Expand Up @@ -1043,6 +1044,7 @@ class LLVMContextImpl {
void *DiagnosticContext;
bool RespectDiagnosticFilters;
bool DiagnosticHotnessRequested;
std::unique_ptr<yaml::Output> DiagnosticsOutputFile;

LLVMContext::YieldCallbackTy YieldCallback;
void *YieldOpaqueHandle;
Expand Down
14 changes: 8 additions & 6 deletions llvm/lib/Transforms/IPO/Inliner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,13 +469,15 @@ inlineCallsImpl(CallGraphSCC &SCC, CallGraph &CG,
// 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;
}

Expand Down
Loading

0 comments on commit 92e928c

Please sign in to comment.