#include "llvm/Transforms/Utils/MisExpect.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/Support/BranchProbability.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/FormatVariadic.h"
#include <cstdint>
#include <functional>
#include <numeric>
#define DEBUG_TYPE "misexpect"
using namespace llvm;
using namespace misexpect;
namespace llvm {
static cl::opt<bool> PGOWarnMisExpect(
"pgo-warn-misexpect", cl::init(false), cl::Hidden,
cl::desc("Use this option to turn on/off "
"warnings about incorrect usage of llvm.expect intrinsics."));
static cl::opt<unsigned> MisExpectTolerance(
"misexpect-tolerance", cl::init(0),
cl::desc("Prevents emiting diagnostics when profile counts are "
"within N% of the threshold.."));
}
namespace {
bool isMisExpectDiagEnabled(LLVMContext &Ctx) {
return PGOWarnMisExpect || Ctx.getMisExpectWarningRequested();
}
uint64_t getMisExpectTolerance(LLVMContext &Ctx) {
return std::max(static_cast<uint64_t>(MisExpectTolerance),
Ctx.getDiagnosticsMisExpectTolerance());
}
Instruction *getInstCondition(Instruction *I) {
assert(I != nullptr && "MisExpect target Instruction cannot be nullptr");
Instruction *Ret = nullptr;
if (auto *B = dyn_cast<BranchInst>(I)) {
Ret = dyn_cast<Instruction>(B->getCondition());
}
else if (auto *S = dyn_cast<SwitchInst>(I)) {
Ret = dyn_cast<Instruction>(S->getCondition());
}
return Ret ? Ret : I;
}
void emitMisexpectDiagnostic(Instruction *I, LLVMContext &Ctx,
uint64_t ProfCount, uint64_t TotalCount) {
double PercentageCorrect = (double)ProfCount / TotalCount;
auto PerString =
formatv("{0:P} ({1} / {2})", PercentageCorrect, ProfCount, TotalCount);
auto RemStr = formatv(
"Potential performance regression from use of the llvm.expect intrinsic: "
"Annotation was correct on {0} of profiled executions.",
PerString);
Twine Msg(PerString);
Instruction *Cond = getInstCondition(I);
if (isMisExpectDiagEnabled(Ctx))
Ctx.diagnose(DiagnosticInfoMisExpect(Cond, Msg));
OptimizationRemarkEmitter ORE(I->getParent()->getParent());
ORE.emit(OptimizationRemark(DEBUG_TYPE, "misexpect", Cond) << RemStr.str());
}
}
namespace llvm {
namespace misexpect {
Optional<SmallVector<uint32_t, 4>> extractWeights(Instruction *I,
LLVMContext &Ctx) {
assert(I && "MisExpect::extractWeights given invalid pointer");
auto *ProfileData = I->getMetadata(LLVMContext::MD_prof);
if (!ProfileData)
return None;
unsigned NOps = ProfileData->getNumOperands();
if (NOps < 3)
return None;
auto *ProfDataName = dyn_cast<MDString>(ProfileData->getOperand(0));
if (!ProfDataName || !ProfDataName->getString().equals("branch_weights"))
return None;
SmallVector<uint32_t, 4> Weights(NOps - 1);
for (unsigned Idx = 1; Idx < NOps; Idx++) {
ConstantInt *Value =
mdconst::dyn_extract<ConstantInt>(ProfileData->getOperand(Idx));
uint32_t V = Value->getZExtValue();
Weights[Idx - 1] = V;
}
return Weights;
}
uint32_t clamp(uint64_t value, uint32_t low, uint32_t hi) {
if (value > hi)
return hi;
if (value < low)
return low;
return value;
}
void verifyMisExpect(Instruction &I, ArrayRef<uint32_t> RealWeights,
ArrayRef<uint32_t> ExpectedWeights) {
uint64_t LikelyBranchWeight = 0,
UnlikelyBranchWeight = std::numeric_limits<uint32_t>::max();
size_t MaxIndex = 0;
for (size_t Idx = 0, End = ExpectedWeights.size(); Idx < End; Idx++) {
uint32_t V = ExpectedWeights[Idx];
if (LikelyBranchWeight < V) {
LikelyBranchWeight = V;
MaxIndex = Idx;
}
if (UnlikelyBranchWeight > V) {
UnlikelyBranchWeight = V;
}
}
const uint64_t ProfiledWeight = RealWeights[MaxIndex];
const uint64_t RealWeightsTotal =
std::accumulate(RealWeights.begin(), RealWeights.end(), (uint64_t)0,
std::plus<uint64_t>());
const uint64_t NumUnlikelyTargets = RealWeights.size() - 1;
uint64_t TotalBranchWeight =
LikelyBranchWeight + (UnlikelyBranchWeight * NumUnlikelyTargets);
if ((TotalBranchWeight == 0) || (TotalBranchWeight <= LikelyBranchWeight))
return;
auto LikelyProbablilty = BranchProbability::getBranchProbability(
LikelyBranchWeight, TotalBranchWeight);
uint64_t ScaledThreshold = LikelyProbablilty.scale(RealWeightsTotal);
auto Tolerance = getMisExpectTolerance(I.getContext());
Tolerance = clamp(Tolerance, 0, 99);
if (Tolerance > 0)
ScaledThreshold *= (1.0 - Tolerance / 100.0);
if (ProfiledWeight < ScaledThreshold)
emitMisexpectDiagnostic(&I, I.getContext(), ProfiledWeight,
RealWeightsTotal);
}
void checkBackendInstrumentation(Instruction &I,
const ArrayRef<uint32_t> RealWeights) {
auto ExpectedWeightsOpt = extractWeights(&I, I.getContext());
if (!ExpectedWeightsOpt)
return;
auto ExpectedWeights = ExpectedWeightsOpt.value();
verifyMisExpect(I, RealWeights, ExpectedWeights);
}
void checkFrontendInstrumentation(Instruction &I,
const ArrayRef<uint32_t> ExpectedWeights) {
auto RealWeightsOpt = extractWeights(&I, I.getContext());
if (!RealWeightsOpt)
return;
auto RealWeights = RealWeightsOpt.value();
verifyMisExpect(I, RealWeights, ExpectedWeights);
}
void checkExpectAnnotations(Instruction &I,
const ArrayRef<uint32_t> ExistingWeights,
bool IsFrontendInstr) {
if (IsFrontendInstr) {
checkFrontendInstrumentation(I, ExistingWeights);
} else {
checkBackendInstrumentation(I, ExistingWeights);
}
}
} } #undef DEBUG_TYPE