diff options
Diffstat (limited to 'llvm/lib/Transforms/Utils/MemoryOpRemark.cpp')
-rw-r--r-- | llvm/lib/Transforms/Utils/MemoryOpRemark.cpp | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/llvm/lib/Transforms/Utils/MemoryOpRemark.cpp b/llvm/lib/Transforms/Utils/MemoryOpRemark.cpp new file mode 100644 index 000000000000..68d4dd9d576b --- /dev/null +++ b/llvm/lib/Transforms/Utils/MemoryOpRemark.cpp @@ -0,0 +1,408 @@ +//===-- 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" + +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 Optional<uint64_t> getSizeInBytes(Optional<uint64_t> SizeInBits) { + if (!SizeInBits || *SizeInBits % 8 != 0) + return None; + 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 Optional<StringRef> nameOrNone(const Value *V) { + if (V->hasName()) + return V->getName(); + return None; +} + +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).getFixedSize(); + 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 : + FindDbgAddrUses(const_cast<Value *>(V))) { + if (DILocalVariable *DILV = DVI->getVariable()) { + 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. + Optional<TypeSize> TySize = AI->getAllocationSizeInBits(DL); + Optional<uint64_t> Size = + TySize ? getSizeInBytes(TySize->getFixedSize()) : None; + 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({None, 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 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"); +} |