xref: /freebsd/contrib/llvm-project/llvm/lib/Transforms/Utils/MemoryOpRemark.cpp (revision 7ef62cebc2f965b0f640263e179276928885e33d)
1 //===-- MemoryOpRemark.cpp - Auto-init remark analysis---------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Implementation of the analysis for the "auto-init" remark.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "llvm/Transforms/Utils/MemoryOpRemark.h"
14 #include "llvm/Analysis/OptimizationRemarkEmitter.h"
15 #include "llvm/Analysis/ValueTracking.h"
16 #include "llvm/IR/DebugInfo.h"
17 #include "llvm/IR/Instructions.h"
18 #include "llvm/IR/IntrinsicInst.h"
19 #include <optional>
20 
21 using namespace llvm;
22 using namespace llvm::ore;
23 
24 MemoryOpRemark::~MemoryOpRemark() = default;
25 
26 bool MemoryOpRemark::canHandle(const Instruction *I, const TargetLibraryInfo &TLI) {
27   if (isa<StoreInst>(I))
28     return true;
29 
30   if (auto *II = dyn_cast<IntrinsicInst>(I)) {
31     switch (II->getIntrinsicID()) {
32     case Intrinsic::memcpy_inline:
33     case Intrinsic::memcpy:
34     case Intrinsic::memmove:
35     case Intrinsic::memset:
36     case Intrinsic::memcpy_element_unordered_atomic:
37     case Intrinsic::memmove_element_unordered_atomic:
38     case Intrinsic::memset_element_unordered_atomic:
39       return true;
40     default:
41       return false;
42     }
43   }
44 
45   if (auto *CI = dyn_cast<CallInst>(I)) {
46     auto *CF = CI->getCalledFunction();
47     if (!CF)
48       return false;
49 
50     if (!CF->hasName())
51       return false;
52 
53     LibFunc LF;
54     bool KnownLibCall = TLI.getLibFunc(*CF, LF) && TLI.has(LF);
55     if (!KnownLibCall)
56       return false;
57 
58     switch (LF) {
59     case LibFunc_memcpy_chk:
60     case LibFunc_mempcpy_chk:
61     case LibFunc_memset_chk:
62     case LibFunc_memmove_chk:
63     case LibFunc_memcpy:
64     case LibFunc_mempcpy:
65     case LibFunc_memset:
66     case LibFunc_memmove:
67     case LibFunc_bzero:
68     case LibFunc_bcopy:
69       return true;
70     default:
71       return false;
72     }
73   }
74 
75   return false;
76 }
77 
78 void MemoryOpRemark::visit(const Instruction *I) {
79   // For some of them, we can provide more information:
80 
81   // For stores:
82   // * size
83   // * volatile / atomic
84   if (auto *SI = dyn_cast<StoreInst>(I)) {
85     visitStore(*SI);
86     return;
87   }
88 
89   // For intrinsics:
90   // * user-friendly name
91   // * size
92   if (auto *II = dyn_cast<IntrinsicInst>(I)) {
93     visitIntrinsicCall(*II);
94     return;
95   }
96 
97   // For calls:
98   // * known/unknown function (e.g. the compiler knows bzero, but it doesn't
99   //                                know my_bzero)
100   // * memory operation size
101   if (auto *CI = dyn_cast<CallInst>(I)) {
102     visitCall(*CI);
103     return;
104   }
105 
106   visitUnknown(*I);
107 }
108 
109 std::string MemoryOpRemark::explainSource(StringRef Type) const {
110   return (Type + ".").str();
111 }
112 
113 StringRef MemoryOpRemark::remarkName(RemarkKind RK) const {
114   switch (RK) {
115   case RK_Store:
116     return "MemoryOpStore";
117   case RK_Unknown:
118     return "MemoryOpUnknown";
119   case RK_IntrinsicCall:
120     return "MemoryOpIntrinsicCall";
121   case RK_Call:
122     return "MemoryOpCall";
123   }
124   llvm_unreachable("missing RemarkKind case");
125 }
126 
127 static void inlineVolatileOrAtomicWithExtraArgs(bool *Inline, bool Volatile,
128                                                 bool Atomic,
129                                                 DiagnosticInfoIROptimization &R) {
130   if (Inline && *Inline)
131     R << " Inlined: " << NV("StoreInlined", true) << ".";
132   if (Volatile)
133     R << " Volatile: " << NV("StoreVolatile", true) << ".";
134   if (Atomic)
135     R << " Atomic: " << NV("StoreAtomic", true) << ".";
136   // Emit the false cases under ExtraArgs. This won't show them in the remark
137   // message but will end up in the serialized remarks.
138   if ((Inline && !*Inline) || !Volatile || !Atomic)
139     R << setExtraArgs();
140   if (Inline && !*Inline)
141     R << " Inlined: " << NV("StoreInlined", false) << ".";
142   if (!Volatile)
143     R << " Volatile: " << NV("StoreVolatile", false) << ".";
144   if (!Atomic)
145     R << " Atomic: " << NV("StoreAtomic", false) << ".";
146 }
147 
148 static std::optional<uint64_t>
149 getSizeInBytes(std::optional<uint64_t> SizeInBits) {
150   if (!SizeInBits || *SizeInBits % 8 != 0)
151     return std::nullopt;
152   return *SizeInBits / 8;
153 }
154 
155 template<typename ...Ts>
156 std::unique_ptr<DiagnosticInfoIROptimization>
157 MemoryOpRemark::makeRemark(Ts... Args) {
158   switch (diagnosticKind()) {
159   case DK_OptimizationRemarkAnalysis:
160     return std::make_unique<OptimizationRemarkAnalysis>(Args...);
161   case DK_OptimizationRemarkMissed:
162     return std::make_unique<OptimizationRemarkMissed>(Args...);
163   default:
164     llvm_unreachable("unexpected DiagnosticKind");
165   }
166 }
167 
168 void MemoryOpRemark::visitStore(const StoreInst &SI) {
169   bool Volatile = SI.isVolatile();
170   bool Atomic = SI.isAtomic();
171   int64_t Size = DL.getTypeStoreSize(SI.getOperand(0)->getType());
172 
173   auto R = makeRemark(RemarkPass.data(), remarkName(RK_Store), &SI);
174   *R << explainSource("Store") << "\nStore size: " << NV("StoreSize", Size)
175      << " bytes.";
176   visitPtr(SI.getOperand(1), /*IsRead=*/false, *R);
177   inlineVolatileOrAtomicWithExtraArgs(nullptr, Volatile, Atomic, *R);
178   ORE.emit(*R);
179 }
180 
181 void MemoryOpRemark::visitUnknown(const Instruction &I) {
182   auto R = makeRemark(RemarkPass.data(), remarkName(RK_Unknown), &I);
183   *R << explainSource("Initialization");
184   ORE.emit(*R);
185 }
186 
187 void MemoryOpRemark::visitIntrinsicCall(const IntrinsicInst &II) {
188   SmallString<32> CallTo;
189   bool Atomic = false;
190   bool Inline = false;
191   switch (II.getIntrinsicID()) {
192   case Intrinsic::memcpy_inline:
193     CallTo = "memcpy";
194     Inline = true;
195     break;
196   case Intrinsic::memcpy:
197     CallTo = "memcpy";
198     break;
199   case Intrinsic::memmove:
200     CallTo = "memmove";
201     break;
202   case Intrinsic::memset:
203     CallTo = "memset";
204     break;
205   case Intrinsic::memcpy_element_unordered_atomic:
206     CallTo = "memcpy";
207     Atomic = true;
208     break;
209   case Intrinsic::memmove_element_unordered_atomic:
210     CallTo = "memmove";
211     Atomic = true;
212     break;
213   case Intrinsic::memset_element_unordered_atomic:
214     CallTo = "memset";
215     Atomic = true;
216     break;
217   default:
218     return visitUnknown(II);
219   }
220 
221   auto R = makeRemark(RemarkPass.data(), remarkName(RK_IntrinsicCall), &II);
222   visitCallee(CallTo.str(), /*KnownLibCall=*/true, *R);
223   visitSizeOperand(II.getOperand(2), *R);
224 
225   auto *CIVolatile = dyn_cast<ConstantInt>(II.getOperand(3));
226   // No such thing as a memory intrinsic that is both atomic and volatile.
227   bool Volatile = !Atomic && CIVolatile && CIVolatile->getZExtValue();
228   switch (II.getIntrinsicID()) {
229   case Intrinsic::memcpy_inline:
230   case Intrinsic::memcpy:
231   case Intrinsic::memmove:
232   case Intrinsic::memcpy_element_unordered_atomic:
233     visitPtr(II.getOperand(1), /*IsRead=*/true, *R);
234     visitPtr(II.getOperand(0), /*IsRead=*/false, *R);
235     break;
236   case Intrinsic::memset:
237   case Intrinsic::memset_element_unordered_atomic:
238     visitPtr(II.getOperand(0), /*IsRead=*/false, *R);
239     break;
240   }
241   inlineVolatileOrAtomicWithExtraArgs(&Inline, Volatile, Atomic, *R);
242   ORE.emit(*R);
243 }
244 
245 void MemoryOpRemark::visitCall(const CallInst &CI) {
246   Function *F = CI.getCalledFunction();
247   if (!F)
248     return visitUnknown(CI);
249 
250   LibFunc LF;
251   bool KnownLibCall = TLI.getLibFunc(*F, LF) && TLI.has(LF);
252   auto R = makeRemark(RemarkPass.data(), remarkName(RK_Call), &CI);
253   visitCallee(F, KnownLibCall, *R);
254   visitKnownLibCall(CI, LF, *R);
255   ORE.emit(*R);
256 }
257 
258 template <typename FTy>
259 void MemoryOpRemark::visitCallee(FTy F, bool KnownLibCall,
260                                  DiagnosticInfoIROptimization &R) {
261   R << "Call to ";
262   if (!KnownLibCall)
263     R << NV("UnknownLibCall", "unknown") << " function ";
264   R << NV("Callee", F) << explainSource("");
265 }
266 
267 void MemoryOpRemark::visitKnownLibCall(const CallInst &CI, LibFunc LF,
268                                        DiagnosticInfoIROptimization &R) {
269   switch (LF) {
270   default:
271     return;
272   case LibFunc_memset_chk:
273   case LibFunc_memset:
274     visitSizeOperand(CI.getOperand(2), R);
275     visitPtr(CI.getOperand(0), /*IsRead=*/false, R);
276     break;
277   case LibFunc_bzero:
278     visitSizeOperand(CI.getOperand(1), R);
279     visitPtr(CI.getOperand(0), /*IsRead=*/false, R);
280     break;
281   case LibFunc_memcpy_chk:
282   case LibFunc_mempcpy_chk:
283   case LibFunc_memmove_chk:
284   case LibFunc_memcpy:
285   case LibFunc_mempcpy:
286   case LibFunc_memmove:
287   case LibFunc_bcopy:
288     visitSizeOperand(CI.getOperand(2), R);
289     visitPtr(CI.getOperand(1), /*IsRead=*/true, R);
290     visitPtr(CI.getOperand(0), /*IsRead=*/false, R);
291     break;
292   }
293 }
294 
295 void MemoryOpRemark::visitSizeOperand(Value *V, DiagnosticInfoIROptimization &R) {
296   if (auto *Len = dyn_cast<ConstantInt>(V)) {
297     uint64_t Size = Len->getZExtValue();
298     R << " Memory operation size: " << NV("StoreSize", Size) << " bytes.";
299   }
300 }
301 
302 static std::optional<StringRef> nameOrNone(const Value *V) {
303   if (V->hasName())
304     return V->getName();
305   return std::nullopt;
306 }
307 
308 void MemoryOpRemark::visitVariable(const Value *V,
309                                    SmallVectorImpl<VariableInfo> &Result) {
310   if (auto *GV = dyn_cast<GlobalVariable>(V)) {
311     auto *Ty = GV->getValueType();
312     uint64_t Size = DL.getTypeSizeInBits(Ty).getFixedValue();
313     VariableInfo Var{nameOrNone(GV), Size};
314     if (!Var.isEmpty())
315       Result.push_back(std::move(Var));
316     return;
317   }
318 
319   // If we find some information in the debug info, take that.
320   bool FoundDI = false;
321   // Try to get an llvm.dbg.declare, which has a DILocalVariable giving us the
322   // real debug info name and size of the variable.
323   for (const DbgVariableIntrinsic *DVI :
324        FindDbgAddrUses(const_cast<Value *>(V))) {
325     if (DILocalVariable *DILV = DVI->getVariable()) {
326       std::optional<uint64_t> DISize = getSizeInBytes(DILV->getSizeInBits());
327       VariableInfo Var{DILV->getName(), DISize};
328       if (!Var.isEmpty()) {
329         Result.push_back(std::move(Var));
330         FoundDI = true;
331       }
332     }
333   }
334   if (FoundDI) {
335     assert(!Result.empty());
336     return;
337   }
338 
339   const auto *AI = dyn_cast<AllocaInst>(V);
340   if (!AI)
341     return;
342 
343   // If not, get it from the alloca.
344   std::optional<TypeSize> TySize = AI->getAllocationSize(DL);
345   std::optional<uint64_t> Size =
346       TySize ? std::optional(TySize->getFixedValue()) : std::nullopt;
347   VariableInfo Var{nameOrNone(AI), Size};
348   if (!Var.isEmpty())
349     Result.push_back(std::move(Var));
350 }
351 
352 void MemoryOpRemark::visitPtr(Value *Ptr, bool IsRead, DiagnosticInfoIROptimization &R) {
353   // Find if Ptr is a known variable we can give more information on.
354   SmallVector<Value *, 2> Objects;
355   getUnderlyingObjectsForCodeGen(Ptr, Objects);
356   SmallVector<VariableInfo, 2> VIs;
357   for (const Value *V : Objects)
358     visitVariable(V, VIs);
359 
360   if (VIs.empty()) {
361     bool CanBeNull;
362     bool CanBeFreed;
363     uint64_t Size = Ptr->getPointerDereferenceableBytes(DL, CanBeNull, CanBeFreed);
364     if (!Size)
365       return;
366     VIs.push_back({std::nullopt, Size});
367   }
368 
369   R << (IsRead ? "\n Read Variables: " : "\n Written Variables: ");
370   for (unsigned i = 0; i < VIs.size(); ++i) {
371     const VariableInfo &VI = VIs[i];
372     assert(!VI.isEmpty() && "No extra content to display.");
373     if (i != 0)
374       R << ", ";
375     if (VI.Name)
376       R << NV(IsRead ? "RVarName" : "WVarName", *VI.Name);
377     else
378       R << NV(IsRead ? "RVarName" : "WVarName", "<unknown>");
379     if (VI.Size)
380       R << " (" << NV(IsRead ? "RVarSize" : "WVarSize", *VI.Size) << " bytes)";
381   }
382   R << ".";
383 }
384 
385 bool AutoInitRemark::canHandle(const Instruction *I) {
386   if (!I->hasMetadata(LLVMContext::MD_annotation))
387     return false;
388   return any_of(I->getMetadata(LLVMContext::MD_annotation)->operands(),
389                 [](const MDOperand &Op) {
390                   return cast<MDString>(Op.get())->getString() == "auto-init";
391                 });
392 }
393 
394 std::string AutoInitRemark::explainSource(StringRef Type) const {
395   return (Type + " inserted by -ftrivial-auto-var-init.").str();
396 }
397 
398 StringRef AutoInitRemark::remarkName(RemarkKind RK) const {
399   switch (RK) {
400   case RK_Store:
401     return "AutoInitStore";
402   case RK_Unknown:
403     return "AutoInitUnknownInstruction";
404   case RK_IntrinsicCall:
405     return "AutoInitIntrinsicCall";
406   case RK_Call:
407     return "AutoInitCall";
408   }
409   llvm_unreachable("missing RemarkKind case");
410 }
411