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