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::() = default; |
26 | |
27 | bool MemoryOpRemark::canHandle(const Instruction *I, const TargetLibraryInfo &TLI) { |
28 | if (isa<StoreInst>(Val: I)) |
29 | return true; |
30 | |
31 | if (auto *II = dyn_cast<IntrinsicInst>(Val: 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>(Val: 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(FDecl: *CF, F&: LF) && TLI.has(F: 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::(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>(Val: I)) { |
86 | visitStore(SI: *SI); |
87 | return; |
88 | } |
89 | |
90 | // For intrinsics: |
91 | // * user-friendly name |
92 | // * size |
93 | if (auto *II = dyn_cast<IntrinsicInst>(Val: I)) { |
94 | visitIntrinsicCall(II: *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>(Val: I)) { |
103 | visitCall(CI: *CI); |
104 | return; |
105 | } |
106 | |
107 | visitUnknown(I: *I); |
108 | } |
109 | |
110 | std::string MemoryOpRemark::(StringRef Type) const { |
111 | return (Type + "." ).str(); |
112 | } |
113 | |
114 | StringRef MemoryOpRemark::(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 (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::(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::(const StoreInst &SI) { |
170 | bool Volatile = SI.isVolatile(); |
171 | bool Atomic = SI.isAtomic(); |
172 | int64_t Size = DL.getTypeStoreSize(Ty: SI.getOperand(i_nocapture: 0)->getType()); |
173 | |
174 | auto R = makeRemark(Args: RemarkPass.data(), Args: remarkName(RK: RK_Store), Args: &SI); |
175 | *R << explainSource(Type: "Store" ) << "\nStore size: " << NV("StoreSize" , Size) |
176 | << " bytes." ; |
177 | visitPtr(V: SI.getOperand(i_nocapture: 1), /*IsRead=*/IsSrc: false, R&: *R); |
178 | inlineVolatileOrAtomicWithExtraArgs(Inline: nullptr, Volatile, Atomic, R&: *R); |
179 | ORE.emit(OptDiag&: *R); |
180 | } |
181 | |
182 | void MemoryOpRemark::(const Instruction &I) { |
183 | auto R = makeRemark(Args: RemarkPass.data(), Args: remarkName(RK: RK_Unknown), Args: &I); |
184 | *R << explainSource(Type: "Initialization" ); |
185 | ORE.emit(OptDiag&: *R); |
186 | } |
187 | |
188 | void MemoryOpRemark::(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(I: II); |
220 | } |
221 | |
222 | auto R = makeRemark(Args: RemarkPass.data(), Args: remarkName(RK: RK_IntrinsicCall), Args: &II); |
223 | visitCallee(F: CallTo.str(), /*KnownLibCall=*/true, R&: *R); |
224 | visitSizeOperand(V: II.getOperand(i_nocapture: 2), R&: *R); |
225 | |
226 | auto *CIVolatile = dyn_cast<ConstantInt>(Val: II.getOperand(i_nocapture: 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(V: II.getOperand(i_nocapture: 1), /*IsRead=*/IsSrc: true, R&: *R); |
235 | visitPtr(V: II.getOperand(i_nocapture: 0), /*IsRead=*/IsSrc: false, R&: *R); |
236 | break; |
237 | case Intrinsic::memset: |
238 | case Intrinsic::memset_element_unordered_atomic: |
239 | visitPtr(V: II.getOperand(i_nocapture: 0), /*IsRead=*/IsSrc: false, R&: *R); |
240 | break; |
241 | } |
242 | inlineVolatileOrAtomicWithExtraArgs(Inline: &Inline, Volatile, Atomic, R&: *R); |
243 | ORE.emit(OptDiag&: *R); |
244 | } |
245 | |
246 | void MemoryOpRemark::(const CallInst &CI) { |
247 | Function *F = CI.getCalledFunction(); |
248 | if (!F) |
249 | return visitUnknown(I: CI); |
250 | |
251 | LibFunc LF; |
252 | bool KnownLibCall = TLI.getLibFunc(FDecl: *F, F&: LF) && TLI.has(F: LF); |
253 | auto R = makeRemark(Args: RemarkPass.data(), Args: remarkName(RK: RK_Call), Args: &CI); |
254 | visitCallee(F, KnownLibCall, R&: *R); |
255 | visitKnownLibCall(CI, LF, R&: *R); |
256 | ORE.emit(OptDiag&: *R); |
257 | } |
258 | |
259 | template <typename FTy> |
260 | void MemoryOpRemark::(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(Type: "" ); |
266 | } |
267 | |
268 | void MemoryOpRemark::(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(V: CI.getOperand(i_nocapture: 2), R); |
276 | visitPtr(V: CI.getOperand(i_nocapture: 0), /*IsRead=*/IsSrc: false, R); |
277 | break; |
278 | case LibFunc_bzero: |
279 | visitSizeOperand(V: CI.getOperand(i_nocapture: 1), R); |
280 | visitPtr(V: CI.getOperand(i_nocapture: 0), /*IsRead=*/IsSrc: 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(V: CI.getOperand(i_nocapture: 2), R); |
290 | visitPtr(V: CI.getOperand(i_nocapture: 1), /*IsRead=*/IsSrc: true, R); |
291 | visitPtr(V: CI.getOperand(i_nocapture: 0), /*IsRead=*/IsSrc: false, R); |
292 | break; |
293 | } |
294 | } |
295 | |
296 | void MemoryOpRemark::visitSizeOperand(Value *V, DiagnosticInfoIROptimization &R) { |
297 | if (auto *Len = dyn_cast<ConstantInt>(Val: 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::(const Value *V, |
310 | SmallVectorImpl<VariableInfo> &Result) { |
311 | if (auto *GV = dyn_cast<GlobalVariable>(Val: V)) { |
312 | auto *Ty = GV->getValueType(); |
313 | uint64_t Size = DL.getTypeSizeInBits(Ty).getFixedValue(); |
314 | VariableInfo Var{.Name: nameOrNone(V: GV), .Size: Size}; |
315 | if (!Var.isEmpty()) |
316 | Result.push_back(Elt: 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(SizeInBits: DILV->getSizeInBits()); |
327 | VariableInfo Var{.Name: DILV->getName(), .Size: DISize}; |
328 | if (!Var.isEmpty()) { |
329 | Result.push_back(Elt: std::move(Var)); |
330 | FoundDI = true; |
331 | } |
332 | } |
333 | }; |
334 | for_each(Range: findDbgDeclares(V: const_cast<Value *>(V)), F: FindDI); |
335 | for_each(Range: findDVRDeclares(V: const_cast<Value *>(V)), F: FindDI); |
336 | |
337 | if (FoundDI) { |
338 | assert(!Result.empty()); |
339 | return; |
340 | } |
341 | |
342 | const auto *AI = dyn_cast<AllocaInst>(Val: 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{.Name: nameOrNone(V: AI), .Size: Size}; |
351 | if (!Var.isEmpty()) |
352 | Result.push_back(Elt: std::move(Var)); |
353 | } |
354 | |
355 | void MemoryOpRemark::(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(V: Ptr, Objects); |
359 | SmallVector<VariableInfo, 2> VIs; |
360 | for (const Value *V : Objects) |
361 | visitVariable(V, Result&: 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(Elt: {.Name: std::nullopt, .Size: 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(KindID: LLVMContext::MD_annotation)) |
390 | return false; |
391 | return any_of(Range: I->getMetadata(KindID: LLVMContext::MD_annotation)->operands(), |
392 | P: [](const MDOperand &Op) { |
393 | return isa<MDString>(Val: Op.get()) && |
394 | cast<MDString>(Val: Op.get())->getString() == "auto-init" ; |
395 | }); |
396 | } |
397 | |
398 | std::string AutoInitRemark::(StringRef Type) const { |
399 | return (Type + " inserted by -ftrivial-auto-var-init." ).str(); |
400 | } |
401 | |
402 | StringRef AutoInitRemark::(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 | |