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
canHandle(const Instruction * I,const TargetLibraryInfo & TLI)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
visit(const Instruction * I)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
explainSource(StringRef Type) const109 std::string MemoryOpRemark::explainSource(StringRef Type) const {
110 return (Type + ".").str();
111 }
112
remarkName(RemarkKind RK) const113 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
inlineVolatileOrAtomicWithExtraArgs(bool * Inline,bool Volatile,bool Atomic,DiagnosticInfoIROptimization & R)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>
getSizeInBytes(std::optional<uint64_t> SizeInBits)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>
makeRemark(Ts...Args)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
visitStore(const StoreInst & SI)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
visitUnknown(const Instruction & I)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
visitIntrinsicCall(const IntrinsicInst & II)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
visitCall(const CallInst & CI)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>
visitCallee(FTy F,bool KnownLibCall,DiagnosticInfoIROptimization & R)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
visitKnownLibCall(const CallInst & CI,LibFunc LF,DiagnosticInfoIROptimization & R)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
visitSizeOperand(Value * V,DiagnosticInfoIROptimization & R)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
nameOrNone(const Value * V)302 static std::optional<StringRef> nameOrNone(const Value *V) {
303 if (V->hasName())
304 return V->getName();
305 return std::nullopt;
306 }
307
visitVariable(const Value * V,SmallVectorImpl<VariableInfo> & Result)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
visitPtr(Value * Ptr,bool IsRead,DiagnosticInfoIROptimization & R)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
canHandle(const Instruction * I)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
explainSource(StringRef Type) const394 std::string AutoInitRemark::explainSource(StringRef Type) const {
395 return (Type + " inserted by -ftrivial-auto-var-init.").str();
396 }
397
remarkName(RemarkKind RK) const398 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