1//===- X86DiscriminateMemOps.cpp - Unique IDs for Mem Ops -----------------===//
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 pass aids profile-driven cache prefetch insertion by ensuring all
10/// instructions that have a memory operand are distinguishible from each other.
11///
12//===----------------------------------------------------------------------===//
13
14#include "X86.h"
15#include "X86Subtarget.h"
16#include "llvm/CodeGen/MachineFunctionPass.h"
17#include "llvm/CodeGen/MachineModuleInfo.h"
18#include "llvm/IR/DebugInfoMetadata.h"
19#include "llvm/ProfileData/SampleProf.h"
20#include "llvm/ProfileData/SampleProfReader.h"
21#include "llvm/Support/Debug.h"
22#include <optional>
23using namespace llvm;
24
25#define DEBUG_TYPE "x86-discriminate-memops"
26
27static cl::opt<bool> EnableDiscriminateMemops(
28 DEBUG_TYPE, cl::init(Val: false),
29 cl::desc("Generate unique debug info for each instruction with a memory "
30 "operand. Should be enabled for profile-driven cache prefetching, "
31 "both in the build of the binary being profiled, as well as in "
32 "the build of the binary consuming the profile."),
33 cl::Hidden);
34
35static cl::opt<bool> BypassPrefetchInstructions(
36 "x86-bypass-prefetch-instructions", cl::init(Val: true),
37 cl::desc("When discriminating instructions with memory operands, ignore "
38 "prefetch instructions. This ensures the other memory operand "
39 "instructions have the same identifiers after inserting "
40 "prefetches, allowing for successive insertions."),
41 cl::Hidden);
42
43namespace {
44
45using Location = std::pair<StringRef, unsigned>;
46
47Location diToLocation(const DILocation *Loc) {
48 return std::make_pair(x: Loc->getFilename(), y: Loc->getLine());
49}
50
51/// Ensure each instruction having a memory operand has a distinct <LineNumber,
52/// Discriminator> pair.
53void updateDebugInfo(MachineInstr *MI, const DILocation *Loc) {
54 DebugLoc DL(Loc);
55 MI->setDebugLoc(DL);
56}
57
58class X86DiscriminateMemOps : public MachineFunctionPass {
59 bool runOnMachineFunction(MachineFunction &MF) override;
60 StringRef getPassName() const override {
61 return "X86 Discriminate Memory Operands";
62 }
63
64public:
65 static char ID;
66
67 /// Default construct and initialize the pass.
68 X86DiscriminateMemOps();
69};
70
71bool IsPrefetchOpcode(unsigned Opcode) {
72 return Opcode == X86::PREFETCHNTA || Opcode == X86::PREFETCHT0 ||
73 Opcode == X86::PREFETCHT1 || Opcode == X86::PREFETCHT2 ||
74 Opcode == X86::PREFETCHIT0 || Opcode == X86::PREFETCHIT1 ||
75 Opcode == X86::PREFETCHRST2;
76}
77} // end anonymous namespace
78
79//===----------------------------------------------------------------------===//
80// Implementation
81//===----------------------------------------------------------------------===//
82
83char X86DiscriminateMemOps::ID = 0;
84
85/// Default construct and initialize the pass.
86X86DiscriminateMemOps::X86DiscriminateMemOps() : MachineFunctionPass(ID) {}
87
88bool X86DiscriminateMemOps::runOnMachineFunction(MachineFunction &MF) {
89 if (!EnableDiscriminateMemops)
90 return false;
91
92 DISubprogram *FDI = MF.getFunction().getSubprogram();
93 if (!FDI || !FDI->getUnit()->getDebugInfoForProfiling())
94 return false;
95
96 // Have a default DILocation, if we find instructions with memops that don't
97 // have any debug info.
98 const DILocation *ReferenceDI =
99 DILocation::get(Context&: FDI->getContext(), Line: FDI->getLine(), Column: 0, Scope: FDI);
100 assert(ReferenceDI && "ReferenceDI should not be nullptr");
101 DenseMap<Location, unsigned> MemOpDiscriminators;
102 MemOpDiscriminators[diToLocation(Loc: ReferenceDI)] = 0;
103
104 // Figure out the largest discriminator issued for each Location. When we
105 // issue new discriminators, we can thus avoid issuing discriminators
106 // belonging to instructions that don't have memops. This isn't a requirement
107 // for the goals of this pass, however, it avoids unnecessary ambiguity.
108 for (auto &MBB : MF) {
109 for (auto &MI : MBB) {
110 const auto &DI = MI.getDebugLoc();
111 if (!DI)
112 continue;
113 if (BypassPrefetchInstructions && IsPrefetchOpcode(Opcode: MI.getDesc().Opcode))
114 continue;
115 Location Loc = diToLocation(Loc: DI);
116 unsigned &Disc = MemOpDiscriminators[Loc];
117 Disc = std::max(a: Disc, b: DI->getBaseDiscriminator());
118 }
119 }
120
121 // Keep track of the discriminators seen at each Location. If an instruction's
122 // DebugInfo has a Location and discriminator we've already seen, replace its
123 // discriminator with a new one, to guarantee uniqueness.
124 DenseMap<Location, DenseSet<unsigned>> Seen;
125
126 bool Changed = false;
127 for (auto &MBB : MF) {
128 for (auto &MI : MBB) {
129 if (X86II::getMemoryOperandNo(TSFlags: MI.getDesc().TSFlags) < 0)
130 continue;
131 if (BypassPrefetchInstructions && IsPrefetchOpcode(Opcode: MI.getDesc().Opcode))
132 continue;
133 const DILocation *DI = MI.getDebugLoc();
134 bool HasDebug = DI;
135 if (!HasDebug) {
136 DI = ReferenceDI;
137 }
138 Location L = diToLocation(Loc: DI);
139 DenseSet<unsigned> &Set = Seen[L];
140 const std::pair<DenseSet<unsigned>::iterator, bool> TryInsert =
141 Set.insert(V: DI->getBaseDiscriminator());
142 if (!TryInsert.second || !HasDebug) {
143 unsigned BF, DF, CI = 0;
144 DILocation::decodeDiscriminator(D: DI->getDiscriminator(), BD&: BF, DF, CI);
145 std::optional<unsigned> EncodedDiscriminator =
146 DILocation::encodeDiscriminator(BD: MemOpDiscriminators[L] + 1, DF, CI);
147
148 if (!EncodedDiscriminator) {
149 // FIXME(mtrofin): The assumption is that this scenario is infrequent/OK
150 // not to support. If evidence points otherwise, we can explore synthesizeing
151 // unique DIs by adding fake line numbers, or by constructing 64 bit
152 // discriminators.
153 LLVM_DEBUG(dbgs() << "Unable to create a unique discriminator "
154 "for instruction with memory operand in: "
155 << DI->getFilename() << " Line: " << DI->getLine()
156 << " Column: " << DI->getColumn()
157 << ". This is likely due to a large macro expansion. \n");
158 continue;
159 }
160 // Since we were able to encode, bump the MemOpDiscriminators.
161 ++MemOpDiscriminators[L];
162 DI = DI->cloneWithDiscriminator(Discriminator: *EncodedDiscriminator);
163 assert(DI && "DI should not be nullptr");
164 updateDebugInfo(MI: &MI, Loc: DI);
165 Changed = true;
166 std::pair<DenseSet<unsigned>::iterator, bool> MustInsert =
167 Set.insert(V: DI->getBaseDiscriminator());
168 (void)MustInsert; // Silence warning in release build.
169 assert(MustInsert.second && "New discriminator shouldn't be present in set");
170 }
171
172 // Bump the reference DI to avoid cramming discriminators on line 0.
173 // FIXME(mtrofin): pin ReferenceDI on blocks or first instruction with DI
174 // in a block. It's more consistent than just relying on the last memop
175 // instruction we happened to see.
176 ReferenceDI = DI;
177 }
178 }
179 return Changed;
180}
181
182FunctionPass *llvm::createX86DiscriminateMemOpsPass() {
183 return new X86DiscriminateMemOps();
184}
185