1//===-- PGOMemOPSizeOpt.cpp - Optimizations based on value profiling ===//
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// This file implements the transformation that optimizes memory intrinsics
10// such as memcpy using the size value profile. When memory intrinsic size
11// value profile metadata is available, a single memory intrinsic is expanded
12// to a sequence of guarded specialized versions that are called with the
13// hottest size(s), for later expansion into more optimal inline sequences.
14//
15//===----------------------------------------------------------------------===//
16
17#include "llvm/ADT/ArrayRef.h"
18#include "llvm/ADT/Statistic.h"
19#include "llvm/ADT/StringRef.h"
20#include "llvm/ADT/Twine.h"
21#include "llvm/Analysis/BlockFrequencyInfo.h"
22#include "llvm/Analysis/DomTreeUpdater.h"
23#include "llvm/Analysis/OptimizationRemarkEmitter.h"
24#include "llvm/Analysis/TargetLibraryInfo.h"
25#include "llvm/IR/BasicBlock.h"
26#include "llvm/IR/DerivedTypes.h"
27#include "llvm/IR/Dominators.h"
28#include "llvm/IR/Function.h"
29#include "llvm/IR/IRBuilder.h"
30#include "llvm/IR/InstVisitor.h"
31#include "llvm/IR/Instruction.h"
32#include "llvm/IR/Instructions.h"
33#include "llvm/IR/LLVMContext.h"
34#include "llvm/IR/PassManager.h"
35#include "llvm/IR/Type.h"
36#include "llvm/ProfileData/InstrProf.h"
37#define INSTR_PROF_VALUE_PROF_MEMOP_API
38#include "llvm/ProfileData/InstrProfData.inc"
39#include "llvm/Support/Casting.h"
40#include "llvm/Support/CommandLine.h"
41#include "llvm/Support/Debug.h"
42#include "llvm/Support/ErrorHandling.h"
43#include "llvm/Support/MathExtras.h"
44#include "llvm/Transforms/Instrumentation/PGOInstrumentation.h"
45#include "llvm/Transforms/Utils/BasicBlockUtils.h"
46#include <cassert>
47#include <cstdint>
48#include <vector>
49
50using namespace llvm;
51
52#define DEBUG_TYPE "pgo-memop-opt"
53
54STATISTIC(NumOfPGOMemOPOpt, "Number of memop intrinsics optimized.");
55STATISTIC(NumOfPGOMemOPAnnotate, "Number of memop intrinsics annotated.");
56
57namespace llvm {
58
59// The minimum call count to optimize memory intrinsic calls.
60static cl::opt<unsigned>
61 MemOPCountThreshold("pgo-memop-count-threshold", cl::Hidden, cl::init(Val: 1000),
62 cl::desc("The minimum count to optimize memory "
63 "intrinsic calls"));
64
65// Command line option to disable memory intrinsic optimization. The default is
66// false. This is for debug purpose.
67static cl::opt<bool> DisableMemOPOPT("disable-memop-opt", cl::init(Val: false),
68 cl::Hidden, cl::desc("Disable optimize"));
69
70// The percent threshold to optimize memory intrinsic calls.
71static cl::opt<unsigned>
72 MemOPPercentThreshold("pgo-memop-percent-threshold", cl::init(Val: 40),
73 cl::Hidden,
74 cl::desc("The percentage threshold for the "
75 "memory intrinsic calls optimization"));
76
77// Maximum number of versions for optimizing memory intrinsic call.
78static cl::opt<unsigned>
79 MemOPMaxVersion("pgo-memop-max-version", cl::init(Val: 3), cl::Hidden,
80 cl::desc("The max version for the optimized memory "
81 " intrinsic calls"));
82
83// Scale the counts from the annotation using the BB count value.
84static cl::opt<bool>
85 MemOPScaleCount("pgo-memop-scale-count", cl::init(Val: true), cl::Hidden,
86 cl::desc("Scale the memop size counts using the basic "
87 " block count value"));
88
89cl::opt<bool>
90 MemOPOptMemcmpBcmp("pgo-memop-optimize-memcmp-bcmp", cl::init(Val: true),
91 cl::Hidden,
92 cl::desc("Size-specialize memcmp and bcmp calls"));
93
94static cl::opt<unsigned>
95 MemOpMaxOptSize("memop-value-prof-max-opt-size", cl::Hidden, cl::init(Val: 128),
96 cl::desc("Optimize the memop size <= this value"));
97
98} // end namespace llvm
99
100namespace {
101
102static const char *getMIName(const MemIntrinsic *MI) {
103 switch (MI->getIntrinsicID()) {
104 case Intrinsic::memcpy:
105 return "memcpy";
106 case Intrinsic::memmove:
107 return "memmove";
108 case Intrinsic::memset:
109 return "memset";
110 default:
111 return "unknown";
112 }
113}
114
115// A class that abstracts a memop (memcpy, memmove, memset, memcmp and bcmp).
116struct MemOp {
117 Instruction *I;
118 MemOp(MemIntrinsic *MI) : I(MI) {}
119 MemOp(CallInst *CI) : I(CI) {}
120 MemIntrinsic *asMI() { return dyn_cast<MemIntrinsic>(Val: I); }
121 CallInst *asCI() { return cast<CallInst>(Val: I); }
122 MemOp clone() {
123 if (auto MI = asMI())
124 return MemOp(cast<MemIntrinsic>(Val: MI->clone()));
125 return MemOp(cast<CallInst>(Val: asCI()->clone()));
126 }
127 Value *getLength() {
128 if (auto MI = asMI())
129 return MI->getLength();
130 return asCI()->getArgOperand(i: 2);
131 }
132 void setLength(Value *Length) {
133 if (auto MI = asMI())
134 return MI->setLength(Length);
135 asCI()->setArgOperand(i: 2, v: Length);
136 }
137 StringRef getFuncName() {
138 if (auto MI = asMI())
139 return MI->getCalledFunction()->getName();
140 return asCI()->getCalledFunction()->getName();
141 }
142 bool isMemmove() {
143 if (auto MI = asMI())
144 if (MI->getIntrinsicID() == Intrinsic::memmove)
145 return true;
146 return false;
147 }
148 bool isMemcmp(TargetLibraryInfo &TLI) {
149 return asMI() == nullptr && TLI.getLibFunc(CB: *asCI()) == LibFunc_memcmp;
150 }
151 bool isBcmp(TargetLibraryInfo &TLI) {
152 return asMI() == nullptr && TLI.getLibFunc(CB: *asCI()) == LibFunc_bcmp;
153 }
154 const char *getName(TargetLibraryInfo &TLI) {
155 if (auto MI = asMI())
156 return getMIName(MI);
157 LibFunc Func = TLI.getLibFunc(CB: *asCI());
158 if (Func == LibFunc_memcmp)
159 return "memcmp";
160 if (Func == LibFunc_bcmp)
161 return "bcmp";
162 llvm_unreachable("Must be MemIntrinsic or memcmp/bcmp CallInst");
163 return nullptr;
164 }
165};
166
167class MemOPSizeOpt : public InstVisitor<MemOPSizeOpt> {
168public:
169 MemOPSizeOpt(Function &Func, BlockFrequencyInfo &BFI,
170 OptimizationRemarkEmitter &ORE, DominatorTree *DT,
171 TargetLibraryInfo &TLI)
172 : Func(Func), BFI(BFI), ORE(ORE), DT(DT), TLI(TLI), Changed(false) {}
173 bool isChanged() const { return Changed; }
174 void perform() {
175 WorkList.clear();
176 visit(F&: Func);
177
178 for (auto &MO : WorkList) {
179 ++NumOfPGOMemOPAnnotate;
180 if (perform(MO)) {
181 Changed = true;
182 ++NumOfPGOMemOPOpt;
183 LLVM_DEBUG(dbgs() << "MemOP call: " << MO.getFuncName()
184 << "is Transformed.\n");
185 }
186 }
187 }
188
189 void visitMemIntrinsic(MemIntrinsic &MI) {
190 Value *Length = MI.getLength();
191 // Not perform on constant length calls.
192 if (isa<ConstantInt>(Val: Length))
193 return;
194 WorkList.push_back(x: MemOp(&MI));
195 }
196
197 void visitCallInst(CallInst &CI) {
198 LibFunc Func = TLI.getLibFunc(CB: CI);
199 if ((Func == LibFunc_memcmp || Func == LibFunc_bcmp) &&
200 !isa<ConstantInt>(Val: CI.getArgOperand(i: 2))) {
201 WorkList.push_back(x: MemOp(&CI));
202 }
203 }
204
205private:
206 Function &Func;
207 BlockFrequencyInfo &BFI;
208 OptimizationRemarkEmitter &ORE;
209 DominatorTree *DT;
210 TargetLibraryInfo &TLI;
211 bool Changed;
212 std::vector<MemOp> WorkList;
213 bool perform(MemOp MO);
214};
215
216static bool isProfitable(uint64_t Count, uint64_t TotalCount) {
217 assert(Count <= TotalCount);
218 if (Count < MemOPCountThreshold)
219 return false;
220 if (Count < TotalCount * MemOPPercentThreshold / 100)
221 return false;
222 return true;
223}
224
225static inline uint64_t getScaledCount(uint64_t Count, uint64_t Num,
226 uint64_t Denom) {
227 if (!MemOPScaleCount)
228 return Count;
229 bool Overflowed;
230 uint64_t ScaleCount = SaturatingMultiply(X: Count, Y: Num, ResultOverflowed: &Overflowed);
231 return ScaleCount / Denom;
232}
233
234bool MemOPSizeOpt::perform(MemOp MO) {
235 assert(MO.I);
236 if (MO.isMemmove())
237 return false;
238 if (!MemOPOptMemcmpBcmp && (MO.isMemcmp(TLI) || MO.isBcmp(TLI)))
239 return false;
240
241 uint32_t MaxNumVals = INSTR_PROF_NUM_BUCKETS;
242 uint64_t TotalCount;
243 auto VDs =
244 getValueProfDataFromInst(Inst: *MO.I, ValueKind: IPVK_MemOPSize, MaxNumValueData: MaxNumVals, TotalC&: TotalCount);
245 if (VDs.empty())
246 return false;
247
248 uint64_t ActualCount = TotalCount;
249 uint64_t SavedTotalCount = TotalCount;
250 if (MemOPScaleCount) {
251 auto BBEdgeCount = BFI.getBlockProfileCount(BB: MO.I->getParent());
252 if (!BBEdgeCount)
253 return false;
254 ActualCount = *BBEdgeCount;
255 }
256
257 LLVM_DEBUG(dbgs() << "Read one memory intrinsic profile with count "
258 << ActualCount << "\n");
259 LLVM_DEBUG(
260 for (auto &VD
261 : VDs) { dbgs() << " (" << VD.Value << "," << VD.Count << ")\n"; });
262
263 if (ActualCount < MemOPCountThreshold)
264 return false;
265 // Skip if the total value profiled count is 0, in which case we can't
266 // scale up the counts properly (and there is no profitable transformation).
267 if (TotalCount == 0)
268 return false;
269
270 TotalCount = ActualCount;
271 if (MemOPScaleCount)
272 LLVM_DEBUG(dbgs() << "Scale counts: numerator = " << ActualCount
273 << " denominator = " << SavedTotalCount << "\n");
274
275 // Keeping track of the count of the default case:
276 uint64_t RemainCount = TotalCount;
277 uint64_t SavedRemainCount = SavedTotalCount;
278 SmallVector<uint64_t, 16> SizeIds;
279 SmallVector<uint64_t, 16> CaseCounts;
280 uint64_t MaxCount = 0;
281 unsigned Version = 0;
282 // Default case is in the front -- save the slot here.
283 CaseCounts.push_back(Elt: 0);
284 SmallVector<InstrProfValueData, 24> RemainingVDs;
285 for (auto I = VDs.begin(), E = VDs.end(); I != E; ++I) {
286 auto &VD = *I;
287 int64_t V = VD.Value;
288 uint64_t C = VD.Count;
289 if (MemOPScaleCount)
290 C = getScaledCount(Count: C, Num: ActualCount, Denom: SavedTotalCount);
291
292 if (!InstrProfIsSingleValRange(Value: V) || V > MemOpMaxOptSize) {
293 RemainingVDs.push_back(Elt: VD);
294 continue;
295 }
296
297 // ValueCounts are sorted on the count. Break at the first un-profitable
298 // value.
299 if (!isProfitable(Count: C, TotalCount: RemainCount)) {
300 RemainingVDs.insert(I: RemainingVDs.end(), From: I, To: E);
301 break;
302 }
303
304 SizeIds.push_back(Elt: V);
305 CaseCounts.push_back(Elt: C);
306 if (C > MaxCount)
307 MaxCount = C;
308
309 assert(RemainCount >= C);
310 RemainCount -= C;
311 assert(SavedRemainCount >= VD.Count);
312 SavedRemainCount -= VD.Count;
313
314 if (++Version >= MemOPMaxVersion && MemOPMaxVersion != 0) {
315 RemainingVDs.insert(I: RemainingVDs.end(), From: I + 1, To: E);
316 break;
317 }
318 }
319
320 if (Version == 0)
321 return false;
322
323 CaseCounts[0] = RemainCount;
324 if (RemainCount > MaxCount)
325 MaxCount = RemainCount;
326
327 uint64_t SumForOpt = TotalCount - RemainCount;
328
329 LLVM_DEBUG(dbgs() << "Optimize one memory intrinsic call to " << Version
330 << " Versions (covering " << SumForOpt << " out of "
331 << TotalCount << ")\n");
332
333 // mem_op(..., size)
334 // ==>
335 // switch (size) {
336 // case s1:
337 // mem_op(..., s1);
338 // goto merge_bb;
339 // case s2:
340 // mem_op(..., s2);
341 // goto merge_bb;
342 // ...
343 // default:
344 // mem_op(..., size);
345 // goto merge_bb;
346 // }
347 // merge_bb:
348
349 BasicBlock *BB = MO.I->getParent();
350 LLVM_DEBUG(dbgs() << "\n\n== Basic Block Before ==\n");
351 LLVM_DEBUG(dbgs() << *BB << "\n");
352 auto OrigBBFreq = BFI.getBlockFreq(BB);
353
354 BasicBlock *DefaultBB = SplitBlock(Old: BB, SplitPt: MO.I, DT);
355 BasicBlock::iterator It(*MO.I);
356 ++It;
357 assert(It != DefaultBB->end());
358 BasicBlock *MergeBB = SplitBlock(Old: DefaultBB, SplitPt: &(*It), DT);
359 MergeBB->setName("MemOP.Merge");
360 BFI.setBlockFreq(BB: MergeBB, Freq: OrigBBFreq);
361 DefaultBB->setName("MemOP.Default");
362
363 DomTreeUpdater DTU(DT, DomTreeUpdater::UpdateStrategy::Eager);
364 auto &Ctx = Func.getContext();
365 IRBuilder<> IRB(BB);
366 BB->getTerminator()->eraseFromParent();
367 Value *SizeVar = MO.getLength();
368 SwitchInst *SI = IRB.CreateSwitch(V: SizeVar, Dest: DefaultBB, NumCases: SizeIds.size());
369 Type *MemOpTy = MO.I->getType();
370 PHINode *PHI = nullptr;
371 if (!MemOpTy->isVoidTy()) {
372 // Insert a phi for the return values at the merge block.
373 IRBuilder<> IRBM(MergeBB, MergeBB->getFirstNonPHIIt());
374 PHI = IRBM.CreatePHI(Ty: MemOpTy, NumReservedValues: SizeIds.size() + 1, Name: "MemOP.RVMerge");
375 MO.I->replaceAllUsesWith(V: PHI);
376 PHI->addIncoming(V: MO.I, BB: DefaultBB);
377 }
378
379 // Clear the value profile data.
380 MO.I->setMetadata(KindID: LLVMContext::MD_prof, Node: nullptr);
381 // If all promoted, we don't need the MD.prof metadata.
382 if (SavedRemainCount > 0 || Version != VDs.size()) {
383 // Otherwise we need update with the un-promoted records back.
384 annotateValueSite(M&: *Func.getParent(), Inst&: *MO.I, VDs: RemainingVDs, Sum: SavedRemainCount,
385 ValueKind: IPVK_MemOPSize, MaxMDCount: VDs.size());
386 }
387
388 LLVM_DEBUG(dbgs() << "\n\n== Basic Block After==\n");
389
390 std::vector<DominatorTree::UpdateType> Updates;
391 if (DT)
392 Updates.reserve(n: 2 * SizeIds.size());
393
394 for (uint64_t SizeId : SizeIds) {
395 BasicBlock *CaseBB = BasicBlock::Create(
396 Context&: Ctx, Name: Twine("MemOP.Case.") + Twine(SizeId), Parent: &Func, InsertBefore: DefaultBB);
397 MemOp NewMO = MO.clone();
398 // Fix the argument.
399 auto *SizeType = dyn_cast<IntegerType>(Val: NewMO.getLength()->getType());
400 assert(SizeType && "Expected integer type size argument.");
401 ConstantInt *CaseSizeId = ConstantInt::get(Ty: SizeType, V: SizeId);
402 NewMO.setLength(CaseSizeId);
403 NewMO.I->insertInto(ParentBB: CaseBB, It: CaseBB->end());
404 IRBuilder<> IRBCase(CaseBB);
405 IRBCase.CreateBr(Dest: MergeBB);
406 SI->addCase(OnVal: CaseSizeId, Dest: CaseBB);
407 if (!MemOpTy->isVoidTy())
408 PHI->addIncoming(V: NewMO.I, BB: CaseBB);
409 if (DT) {
410 Updates.push_back(x: {DominatorTree::Insert, CaseBB, MergeBB});
411 Updates.push_back(x: {DominatorTree::Insert, BB, CaseBB});
412 }
413 LLVM_DEBUG(dbgs() << *CaseBB << "\n");
414 }
415 DTU.applyUpdates(Updates);
416 Updates.clear();
417
418 if (MaxCount)
419 setProfMetadata(TI: SI, EdgeCounts: CaseCounts, MaxCount);
420
421 LLVM_DEBUG(dbgs() << *BB << "\n");
422 LLVM_DEBUG(dbgs() << *DefaultBB << "\n");
423 LLVM_DEBUG(dbgs() << *MergeBB << "\n");
424
425 ORE.emit(RemarkBuilder: [&]() {
426 using namespace ore;
427 return OptimizationRemark(DEBUG_TYPE, "memopt-opt", MO.I)
428 << "optimized " << NV("Memop", MO.getName(TLI)) << " with count "
429 << NV("Count", SumForOpt) << " out of " << NV("Total", TotalCount)
430 << " for " << NV("Versions", Version) << " versions";
431 });
432
433 return true;
434}
435} // namespace
436
437static bool PGOMemOPSizeOptImpl(Function &F, BlockFrequencyInfo &BFI,
438 OptimizationRemarkEmitter &ORE,
439 DominatorTree *DT, TargetLibraryInfo &TLI) {
440 if (DisableMemOPOPT)
441 return false;
442
443 if (F.hasOptSize())
444 return false;
445 MemOPSizeOpt MemOPSizeOpt(F, BFI, ORE, DT, TLI);
446 MemOPSizeOpt.perform();
447 return MemOPSizeOpt.isChanged();
448}
449
450PreservedAnalyses PGOMemOPSizeOpt::run(Function &F,
451 FunctionAnalysisManager &FAM) {
452 auto &BFI = FAM.getResult<BlockFrequencyAnalysis>(IR&: F);
453 auto &ORE = FAM.getResult<OptimizationRemarkEmitterAnalysis>(IR&: F);
454 auto *DT = FAM.getCachedResult<DominatorTreeAnalysis>(IR&: F);
455 auto &TLI = FAM.getResult<TargetLibraryAnalysis>(IR&: F);
456 bool Changed = PGOMemOPSizeOptImpl(F, BFI, ORE, DT, TLI);
457 if (!Changed)
458 return PreservedAnalyses::all();
459 auto PA = PreservedAnalyses();
460 PA.preserve<DominatorTreeAnalysis>();
461 return PA;
462}
463