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