
Annotation metadata supports adding singular annotation strings to annotation block. This patch adds the ability to insert a tuple of strings into the metadata array. The idea here is that each tuple of strings represents a piece of information that can be all related. It makes it easier to parse through related metadata information given it will be contained in one tuple. For example in remarks any pass that implements annotation remarks can have different type of remarks and pass additional information for each. The original behaviour of annotation remarks is preserved here and we can mix tuple annotations and single annotations for the same instruction. Reviewed By: paquette Differential Revision: https://reviews.llvm.org/D148328
412 lines
12 KiB
C++
412 lines
12 KiB
C++
//===-- MemoryOpRemark.cpp - Auto-init remark analysis---------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Implementation of the analysis for the "auto-init" remark.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/Transforms/Utils/MemoryOpRemark.h"
|
|
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
|
|
#include "llvm/Analysis/ValueTracking.h"
|
|
#include "llvm/IR/DebugInfo.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
#include <optional>
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::ore;
|
|
|
|
MemoryOpRemark::~MemoryOpRemark() = default;
|
|
|
|
bool MemoryOpRemark::canHandle(const Instruction *I, const TargetLibraryInfo &TLI) {
|
|
if (isa<StoreInst>(I))
|
|
return true;
|
|
|
|
if (auto *II = dyn_cast<IntrinsicInst>(I)) {
|
|
switch (II->getIntrinsicID()) {
|
|
case Intrinsic::memcpy_inline:
|
|
case Intrinsic::memcpy:
|
|
case Intrinsic::memmove:
|
|
case Intrinsic::memset:
|
|
case Intrinsic::memcpy_element_unordered_atomic:
|
|
case Intrinsic::memmove_element_unordered_atomic:
|
|
case Intrinsic::memset_element_unordered_atomic:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (auto *CI = dyn_cast<CallInst>(I)) {
|
|
auto *CF = CI->getCalledFunction();
|
|
if (!CF)
|
|
return false;
|
|
|
|
if (!CF->hasName())
|
|
return false;
|
|
|
|
LibFunc LF;
|
|
bool KnownLibCall = TLI.getLibFunc(*CF, LF) && TLI.has(LF);
|
|
if (!KnownLibCall)
|
|
return false;
|
|
|
|
switch (LF) {
|
|
case LibFunc_memcpy_chk:
|
|
case LibFunc_mempcpy_chk:
|
|
case LibFunc_memset_chk:
|
|
case LibFunc_memmove_chk:
|
|
case LibFunc_memcpy:
|
|
case LibFunc_mempcpy:
|
|
case LibFunc_memset:
|
|
case LibFunc_memmove:
|
|
case LibFunc_bzero:
|
|
case LibFunc_bcopy:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MemoryOpRemark::visit(const Instruction *I) {
|
|
// For some of them, we can provide more information:
|
|
|
|
// For stores:
|
|
// * size
|
|
// * volatile / atomic
|
|
if (auto *SI = dyn_cast<StoreInst>(I)) {
|
|
visitStore(*SI);
|
|
return;
|
|
}
|
|
|
|
// For intrinsics:
|
|
// * user-friendly name
|
|
// * size
|
|
if (auto *II = dyn_cast<IntrinsicInst>(I)) {
|
|
visitIntrinsicCall(*II);
|
|
return;
|
|
}
|
|
|
|
// For calls:
|
|
// * known/unknown function (e.g. the compiler knows bzero, but it doesn't
|
|
// know my_bzero)
|
|
// * memory operation size
|
|
if (auto *CI = dyn_cast<CallInst>(I)) {
|
|
visitCall(*CI);
|
|
return;
|
|
}
|
|
|
|
visitUnknown(*I);
|
|
}
|
|
|
|
std::string MemoryOpRemark::explainSource(StringRef Type) const {
|
|
return (Type + ".").str();
|
|
}
|
|
|
|
StringRef MemoryOpRemark::remarkName(RemarkKind RK) const {
|
|
switch (RK) {
|
|
case RK_Store:
|
|
return "MemoryOpStore";
|
|
case RK_Unknown:
|
|
return "MemoryOpUnknown";
|
|
case RK_IntrinsicCall:
|
|
return "MemoryOpIntrinsicCall";
|
|
case RK_Call:
|
|
return "MemoryOpCall";
|
|
}
|
|
llvm_unreachable("missing RemarkKind case");
|
|
}
|
|
|
|
static void inlineVolatileOrAtomicWithExtraArgs(bool *Inline, bool Volatile,
|
|
bool Atomic,
|
|
DiagnosticInfoIROptimization &R) {
|
|
if (Inline && *Inline)
|
|
R << " Inlined: " << NV("StoreInlined", true) << ".";
|
|
if (Volatile)
|
|
R << " Volatile: " << NV("StoreVolatile", true) << ".";
|
|
if (Atomic)
|
|
R << " Atomic: " << NV("StoreAtomic", true) << ".";
|
|
// Emit the false cases under ExtraArgs. This won't show them in the remark
|
|
// message but will end up in the serialized remarks.
|
|
if ((Inline && !*Inline) || !Volatile || !Atomic)
|
|
R << setExtraArgs();
|
|
if (Inline && !*Inline)
|
|
R << " Inlined: " << NV("StoreInlined", false) << ".";
|
|
if (!Volatile)
|
|
R << " Volatile: " << NV("StoreVolatile", false) << ".";
|
|
if (!Atomic)
|
|
R << " Atomic: " << NV("StoreAtomic", false) << ".";
|
|
}
|
|
|
|
static std::optional<uint64_t>
|
|
getSizeInBytes(std::optional<uint64_t> SizeInBits) {
|
|
if (!SizeInBits || *SizeInBits % 8 != 0)
|
|
return std::nullopt;
|
|
return *SizeInBits / 8;
|
|
}
|
|
|
|
template<typename ...Ts>
|
|
std::unique_ptr<DiagnosticInfoIROptimization>
|
|
MemoryOpRemark::makeRemark(Ts... Args) {
|
|
switch (diagnosticKind()) {
|
|
case DK_OptimizationRemarkAnalysis:
|
|
return std::make_unique<OptimizationRemarkAnalysis>(Args...);
|
|
case DK_OptimizationRemarkMissed:
|
|
return std::make_unique<OptimizationRemarkMissed>(Args...);
|
|
default:
|
|
llvm_unreachable("unexpected DiagnosticKind");
|
|
}
|
|
}
|
|
|
|
void MemoryOpRemark::visitStore(const StoreInst &SI) {
|
|
bool Volatile = SI.isVolatile();
|
|
bool Atomic = SI.isAtomic();
|
|
int64_t Size = DL.getTypeStoreSize(SI.getOperand(0)->getType());
|
|
|
|
auto R = makeRemark(RemarkPass.data(), remarkName(RK_Store), &SI);
|
|
*R << explainSource("Store") << "\nStore size: " << NV("StoreSize", Size)
|
|
<< " bytes.";
|
|
visitPtr(SI.getOperand(1), /*IsRead=*/false, *R);
|
|
inlineVolatileOrAtomicWithExtraArgs(nullptr, Volatile, Atomic, *R);
|
|
ORE.emit(*R);
|
|
}
|
|
|
|
void MemoryOpRemark::visitUnknown(const Instruction &I) {
|
|
auto R = makeRemark(RemarkPass.data(), remarkName(RK_Unknown), &I);
|
|
*R << explainSource("Initialization");
|
|
ORE.emit(*R);
|
|
}
|
|
|
|
void MemoryOpRemark::visitIntrinsicCall(const IntrinsicInst &II) {
|
|
SmallString<32> CallTo;
|
|
bool Atomic = false;
|
|
bool Inline = false;
|
|
switch (II.getIntrinsicID()) {
|
|
case Intrinsic::memcpy_inline:
|
|
CallTo = "memcpy";
|
|
Inline = true;
|
|
break;
|
|
case Intrinsic::memcpy:
|
|
CallTo = "memcpy";
|
|
break;
|
|
case Intrinsic::memmove:
|
|
CallTo = "memmove";
|
|
break;
|
|
case Intrinsic::memset:
|
|
CallTo = "memset";
|
|
break;
|
|
case Intrinsic::memcpy_element_unordered_atomic:
|
|
CallTo = "memcpy";
|
|
Atomic = true;
|
|
break;
|
|
case Intrinsic::memmove_element_unordered_atomic:
|
|
CallTo = "memmove";
|
|
Atomic = true;
|
|
break;
|
|
case Intrinsic::memset_element_unordered_atomic:
|
|
CallTo = "memset";
|
|
Atomic = true;
|
|
break;
|
|
default:
|
|
return visitUnknown(II);
|
|
}
|
|
|
|
auto R = makeRemark(RemarkPass.data(), remarkName(RK_IntrinsicCall), &II);
|
|
visitCallee(CallTo.str(), /*KnownLibCall=*/true, *R);
|
|
visitSizeOperand(II.getOperand(2), *R);
|
|
|
|
auto *CIVolatile = dyn_cast<ConstantInt>(II.getOperand(3));
|
|
// No such thing as a memory intrinsic that is both atomic and volatile.
|
|
bool Volatile = !Atomic && CIVolatile && CIVolatile->getZExtValue();
|
|
switch (II.getIntrinsicID()) {
|
|
case Intrinsic::memcpy_inline:
|
|
case Intrinsic::memcpy:
|
|
case Intrinsic::memmove:
|
|
case Intrinsic::memcpy_element_unordered_atomic:
|
|
visitPtr(II.getOperand(1), /*IsRead=*/true, *R);
|
|
visitPtr(II.getOperand(0), /*IsRead=*/false, *R);
|
|
break;
|
|
case Intrinsic::memset:
|
|
case Intrinsic::memset_element_unordered_atomic:
|
|
visitPtr(II.getOperand(0), /*IsRead=*/false, *R);
|
|
break;
|
|
}
|
|
inlineVolatileOrAtomicWithExtraArgs(&Inline, Volatile, Atomic, *R);
|
|
ORE.emit(*R);
|
|
}
|
|
|
|
void MemoryOpRemark::visitCall(const CallInst &CI) {
|
|
Function *F = CI.getCalledFunction();
|
|
if (!F)
|
|
return visitUnknown(CI);
|
|
|
|
LibFunc LF;
|
|
bool KnownLibCall = TLI.getLibFunc(*F, LF) && TLI.has(LF);
|
|
auto R = makeRemark(RemarkPass.data(), remarkName(RK_Call), &CI);
|
|
visitCallee(F, KnownLibCall, *R);
|
|
visitKnownLibCall(CI, LF, *R);
|
|
ORE.emit(*R);
|
|
}
|
|
|
|
template <typename FTy>
|
|
void MemoryOpRemark::visitCallee(FTy F, bool KnownLibCall,
|
|
DiagnosticInfoIROptimization &R) {
|
|
R << "Call to ";
|
|
if (!KnownLibCall)
|
|
R << NV("UnknownLibCall", "unknown") << " function ";
|
|
R << NV("Callee", F) << explainSource("");
|
|
}
|
|
|
|
void MemoryOpRemark::visitKnownLibCall(const CallInst &CI, LibFunc LF,
|
|
DiagnosticInfoIROptimization &R) {
|
|
switch (LF) {
|
|
default:
|
|
return;
|
|
case LibFunc_memset_chk:
|
|
case LibFunc_memset:
|
|
visitSizeOperand(CI.getOperand(2), R);
|
|
visitPtr(CI.getOperand(0), /*IsRead=*/false, R);
|
|
break;
|
|
case LibFunc_bzero:
|
|
visitSizeOperand(CI.getOperand(1), R);
|
|
visitPtr(CI.getOperand(0), /*IsRead=*/false, R);
|
|
break;
|
|
case LibFunc_memcpy_chk:
|
|
case LibFunc_mempcpy_chk:
|
|
case LibFunc_memmove_chk:
|
|
case LibFunc_memcpy:
|
|
case LibFunc_mempcpy:
|
|
case LibFunc_memmove:
|
|
case LibFunc_bcopy:
|
|
visitSizeOperand(CI.getOperand(2), R);
|
|
visitPtr(CI.getOperand(1), /*IsRead=*/true, R);
|
|
visitPtr(CI.getOperand(0), /*IsRead=*/false, R);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MemoryOpRemark::visitSizeOperand(Value *V, DiagnosticInfoIROptimization &R) {
|
|
if (auto *Len = dyn_cast<ConstantInt>(V)) {
|
|
uint64_t Size = Len->getZExtValue();
|
|
R << " Memory operation size: " << NV("StoreSize", Size) << " bytes.";
|
|
}
|
|
}
|
|
|
|
static std::optional<StringRef> nameOrNone(const Value *V) {
|
|
if (V->hasName())
|
|
return V->getName();
|
|
return std::nullopt;
|
|
}
|
|
|
|
void MemoryOpRemark::visitVariable(const Value *V,
|
|
SmallVectorImpl<VariableInfo> &Result) {
|
|
if (auto *GV = dyn_cast<GlobalVariable>(V)) {
|
|
auto *Ty = GV->getValueType();
|
|
uint64_t Size = DL.getTypeSizeInBits(Ty).getFixedValue();
|
|
VariableInfo Var{nameOrNone(GV), Size};
|
|
if (!Var.isEmpty())
|
|
Result.push_back(std::move(Var));
|
|
return;
|
|
}
|
|
|
|
// If we find some information in the debug info, take that.
|
|
bool FoundDI = false;
|
|
// Try to get an llvm.dbg.declare, which has a DILocalVariable giving us the
|
|
// real debug info name and size of the variable.
|
|
for (const DbgVariableIntrinsic *DVI :
|
|
FindDbgDeclareUses(const_cast<Value *>(V))) {
|
|
if (DILocalVariable *DILV = DVI->getVariable()) {
|
|
std::optional<uint64_t> DISize = getSizeInBytes(DILV->getSizeInBits());
|
|
VariableInfo Var{DILV->getName(), DISize};
|
|
if (!Var.isEmpty()) {
|
|
Result.push_back(std::move(Var));
|
|
FoundDI = true;
|
|
}
|
|
}
|
|
}
|
|
if (FoundDI) {
|
|
assert(!Result.empty());
|
|
return;
|
|
}
|
|
|
|
const auto *AI = dyn_cast<AllocaInst>(V);
|
|
if (!AI)
|
|
return;
|
|
|
|
// If not, get it from the alloca.
|
|
std::optional<TypeSize> TySize = AI->getAllocationSize(DL);
|
|
std::optional<uint64_t> Size =
|
|
TySize ? std::optional(TySize->getFixedValue()) : std::nullopt;
|
|
VariableInfo Var{nameOrNone(AI), Size};
|
|
if (!Var.isEmpty())
|
|
Result.push_back(std::move(Var));
|
|
}
|
|
|
|
void MemoryOpRemark::visitPtr(Value *Ptr, bool IsRead, DiagnosticInfoIROptimization &R) {
|
|
// Find if Ptr is a known variable we can give more information on.
|
|
SmallVector<Value *, 2> Objects;
|
|
getUnderlyingObjectsForCodeGen(Ptr, Objects);
|
|
SmallVector<VariableInfo, 2> VIs;
|
|
for (const Value *V : Objects)
|
|
visitVariable(V, VIs);
|
|
|
|
if (VIs.empty()) {
|
|
bool CanBeNull;
|
|
bool CanBeFreed;
|
|
uint64_t Size = Ptr->getPointerDereferenceableBytes(DL, CanBeNull, CanBeFreed);
|
|
if (!Size)
|
|
return;
|
|
VIs.push_back({std::nullopt, Size});
|
|
}
|
|
|
|
R << (IsRead ? "\n Read Variables: " : "\n Written Variables: ");
|
|
for (unsigned i = 0; i < VIs.size(); ++i) {
|
|
const VariableInfo &VI = VIs[i];
|
|
assert(!VI.isEmpty() && "No extra content to display.");
|
|
if (i != 0)
|
|
R << ", ";
|
|
if (VI.Name)
|
|
R << NV(IsRead ? "RVarName" : "WVarName", *VI.Name);
|
|
else
|
|
R << NV(IsRead ? "RVarName" : "WVarName", "<unknown>");
|
|
if (VI.Size)
|
|
R << " (" << NV(IsRead ? "RVarSize" : "WVarSize", *VI.Size) << " bytes)";
|
|
}
|
|
R << ".";
|
|
}
|
|
|
|
bool AutoInitRemark::canHandle(const Instruction *I) {
|
|
if (!I->hasMetadata(LLVMContext::MD_annotation))
|
|
return false;
|
|
return any_of(I->getMetadata(LLVMContext::MD_annotation)->operands(),
|
|
[](const MDOperand &Op) {
|
|
return isa<MDString>(Op.get()) &&
|
|
cast<MDString>(Op.get())->getString() == "auto-init";
|
|
});
|
|
}
|
|
|
|
std::string AutoInitRemark::explainSource(StringRef Type) const {
|
|
return (Type + " inserted by -ftrivial-auto-var-init.").str();
|
|
}
|
|
|
|
StringRef AutoInitRemark::remarkName(RemarkKind RK) const {
|
|
switch (RK) {
|
|
case RK_Store:
|
|
return "AutoInitStore";
|
|
case RK_Unknown:
|
|
return "AutoInitUnknownInstruction";
|
|
case RK_IntrinsicCall:
|
|
return "AutoInitIntrinsicCall";
|
|
case RK_Call:
|
|
return "AutoInitCall";
|
|
}
|
|
llvm_unreachable("missing RemarkKind case");
|
|
}
|