1 | //===-- StackFrameLayoutAnalysisPass.cpp |
2 | //------------------------------------===// |
3 | // |
4 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
5 | // See https://llvm.org/LICENSE.txt for license information. |
6 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
7 | // |
8 | //===----------------------------------------------------------------------===// |
9 | // |
10 | // StackFrameLayoutAnalysisPass implementation. Outputs information about the |
11 | // layout of the stack frame, using the remarks interface. On the CLI it prints |
12 | // a textual representation of the stack frame. When possible it prints the |
13 | // values that occupy a stack slot using any available debug information. Since |
14 | // output is remarks based, it is also available in a machine readable file |
15 | // format, such as YAML. |
16 | // |
17 | //===----------------------------------------------------------------------===// |
18 | |
19 | #include "llvm/ADT/SetVector.h" |
20 | #include "llvm/Analysis/OptimizationRemarkEmitter.h" |
21 | #include "llvm/CodeGen/MachineFrameInfo.h" |
22 | #include "llvm/CodeGen/MachineFunction.h" |
23 | #include "llvm/CodeGen/MachineFunctionPass.h" |
24 | #include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h" |
25 | #include "llvm/CodeGen/Passes.h" |
26 | #include "llvm/CodeGen/SlotIndexes.h" |
27 | #include "llvm/CodeGen/StackProtector.h" |
28 | #include "llvm/CodeGen/TargetFrameLowering.h" |
29 | #include "llvm/CodeGen/TargetSubtargetInfo.h" |
30 | #include "llvm/IR/DebugInfoMetadata.h" |
31 | #include "llvm/IR/PrintPasses.h" |
32 | #include "llvm/InitializePasses.h" |
33 | #include "llvm/Support/Debug.h" |
34 | #include "llvm/Support/FormatVariadic.h" |
35 | #include "llvm/Support/raw_ostream.h" |
36 | |
37 | #include <sstream> |
38 | |
39 | using namespace llvm; |
40 | |
41 | #define DEBUG_TYPE "stack-frame-layout" |
42 | |
43 | namespace { |
44 | |
45 | /// StackFrameLayoutAnalysisPass - This is a pass to dump the stack frame of a |
46 | /// MachineFunction. |
47 | /// |
48 | struct StackFrameLayoutAnalysisPass : public MachineFunctionPass { |
49 | using SlotDbgMap = SmallDenseMap<int, SetVector<const DILocalVariable *>>; |
50 | static char ID; |
51 | |
52 | enum SlotType { |
53 | Spill, // a Spill slot |
54 | Fixed, // a Fixed slot (e.g. arguments passed on the stack) |
55 | VariableSized, // a variable sized object |
56 | StackProtector, // Stack Protector slot |
57 | Variable, // a slot used to store a local data (could be a tmp) |
58 | Invalid // It's an error for a slot to have this type |
59 | }; |
60 | |
61 | struct SlotData { |
62 | int Slot; |
63 | int Size; |
64 | int Align; |
65 | StackOffset Offset; |
66 | SlotType SlotTy; |
67 | bool Scalable; |
68 | |
69 | SlotData(const MachineFrameInfo &MFI, const StackOffset Offset, |
70 | const int Idx) |
71 | : Slot(Idx), Size(MFI.getObjectSize(ObjectIdx: Idx)), |
72 | Align(MFI.getObjectAlign(ObjectIdx: Idx).value()), Offset(Offset), |
73 | SlotTy(Invalid), Scalable(false) { |
74 | Scalable = MFI.getStackID(ObjectIdx: Idx) == TargetStackID::ScalableVector; |
75 | if (MFI.isSpillSlotObjectIndex(ObjectIdx: Idx)) |
76 | SlotTy = SlotType::Spill; |
77 | else if (MFI.isFixedObjectIndex(ObjectIdx: Idx)) |
78 | SlotTy = SlotType::Fixed; |
79 | else if (MFI.isVariableSizedObjectIndex(ObjectIdx: Idx)) |
80 | SlotTy = SlotType::VariableSized; |
81 | else if (MFI.hasStackProtectorIndex() && |
82 | Idx == MFI.getStackProtectorIndex()) |
83 | SlotTy = SlotType::StackProtector; |
84 | else |
85 | SlotTy = SlotType::Variable; |
86 | } |
87 | |
88 | bool isVarSize() const { return SlotTy == SlotType::VariableSized; } |
89 | |
90 | // We use this to sort in reverse order, so that the layout is displayed |
91 | // correctly. Variable sized slots are sorted to the end of the list, as |
92 | // offsets are currently incorrect for these but they reside at the end of |
93 | // the stack frame. The Slot index is used to ensure deterministic order |
94 | // when offsets are equal. |
95 | bool operator<(const SlotData &Rhs) const { |
96 | return std::make_tuple(args: !isVarSize(), |
97 | args: Offset.getFixed() + Offset.getScalable(), args: Slot) > |
98 | std::make_tuple(args: !Rhs.isVarSize(), |
99 | args: Rhs.Offset.getFixed() + Rhs.Offset.getScalable(), |
100 | args: Rhs.Slot); |
101 | } |
102 | }; |
103 | |
104 | StackFrameLayoutAnalysisPass() : MachineFunctionPass(ID) {} |
105 | |
106 | StringRef getPassName() const override { |
107 | return "Stack Frame Layout Analysis" ; |
108 | } |
109 | |
110 | void getAnalysisUsage(AnalysisUsage &AU) const override { |
111 | AU.setPreservesAll(); |
112 | MachineFunctionPass::getAnalysisUsage(AU); |
113 | AU.addRequired<MachineOptimizationRemarkEmitterPass>(); |
114 | } |
115 | |
116 | bool runOnMachineFunction(MachineFunction &MF) override { |
117 | // TODO: We should implement a similar filter for remarks: |
118 | // -Rpass-func-filter=<regex> |
119 | if (!isFunctionInPrintList(FunctionName: MF.getName())) |
120 | return false; |
121 | |
122 | LLVMContext &Ctx = MF.getFunction().getContext(); |
123 | if (!Ctx.getDiagHandlerPtr()->isAnalysisRemarkEnabled(DEBUG_TYPE)) |
124 | return false; |
125 | |
126 | MachineOptimizationRemarkAnalysis Rem(DEBUG_TYPE, "StackLayout" , |
127 | MF.getFunction().getSubprogram(), |
128 | &MF.front()); |
129 | Rem << ("\nFunction: " + MF.getName()).str(); |
130 | emitStackFrameLayoutRemarks(MF, Rem); |
131 | getAnalysis<MachineOptimizationRemarkEmitterPass>().getORE().emit(OptDiag&: Rem); |
132 | return false; |
133 | } |
134 | |
135 | std::string getTypeString(SlotType Ty) { |
136 | switch (Ty) { |
137 | case SlotType::Spill: |
138 | return "Spill" ; |
139 | case SlotType::Fixed: |
140 | return "Fixed" ; |
141 | case SlotType::VariableSized: |
142 | return "VariableSized" ; |
143 | case SlotType::StackProtector: |
144 | return "Protector" ; |
145 | case SlotType::Variable: |
146 | return "Variable" ; |
147 | default: |
148 | llvm_unreachable("bad slot type for stack layout" ); |
149 | } |
150 | } |
151 | |
152 | void (const MachineFunction &MF, const SlotData &D, |
153 | MachineOptimizationRemarkAnalysis &Rem) { |
154 | // To make it easy to understand the stack layout from the CLI, we want to |
155 | // print each slot like the following: |
156 | // |
157 | // Offset: [SP+8], Type: Spill, Align: 8, Size: 16 |
158 | // foo @ /path/to/file.c:25 |
159 | // bar @ /path/to/file.c:35 |
160 | // |
161 | // Which prints the size, alignment, and offset from the SP at function |
162 | // entry. |
163 | // |
164 | // But we also want the machine readable remarks data to be nicely |
165 | // organized. So we print some additional data as strings for the CLI |
166 | // output, but maintain more structured data for the YAML. |
167 | // |
168 | // For example we store the Offset in YAML as: |
169 | // ... |
170 | // - Offset: -8 |
171 | // - ScalableOffset: -16 |
172 | // Note: the ScalableOffset entries are added only for slots with non-zero |
173 | // scalable offsets. |
174 | // |
175 | // But we print it to the CLI as: |
176 | // Offset: [SP-8] |
177 | // |
178 | // Or with non-zero scalable offset: |
179 | // Offset: [SP-8-16 x vscale] |
180 | |
181 | // Negative offsets will print a leading `-`, so only add `+` |
182 | std::string Prefix = |
183 | formatv(Fmt: "\nOffset: [SP{0}" , Vals: (D.Offset.getFixed() < 0) ? "" : "+" ).str(); |
184 | Rem << Prefix << ore::NV("Offset" , D.Offset.getFixed()); |
185 | |
186 | if (D.Offset.getScalable()) { |
187 | Rem << ((D.Offset.getScalable() < 0) ? "" : "+" ) |
188 | << ore::NV("ScalableOffset" , D.Offset.getScalable()) << " x vscale" ; |
189 | } |
190 | |
191 | Rem << "], Type: " << ore::NV("Type" , getTypeString(Ty: D.SlotTy)) |
192 | << ", Align: " << ore::NV("Align" , D.Align) |
193 | << ", Size: " << ore::NV("Size" , ElementCount::get(MinVal: D.Size, Scalable: D.Scalable)); |
194 | } |
195 | |
196 | void (const MachineFunction &MF, const DILocalVariable *N, |
197 | MachineOptimizationRemarkAnalysis &Rem) { |
198 | std::string Loc = |
199 | formatv(Fmt: "{0} @ {1}:{2}" , Vals: N->getName(), Vals: N->getFilename(), Vals: N->getLine()) |
200 | .str(); |
201 | Rem << "\n " << ore::NV("DataLoc" , Loc); |
202 | } |
203 | |
204 | StackOffset getStackOffset(const MachineFunction &MF, |
205 | const MachineFrameInfo &MFI, |
206 | const TargetFrameLowering *FI, int FrameIdx) { |
207 | if (!FI) |
208 | return StackOffset::getFixed(Fixed: MFI.getObjectOffset(ObjectIdx: FrameIdx)); |
209 | |
210 | return FI->getFrameIndexReferenceFromSP(MF, FI: FrameIdx); |
211 | } |
212 | |
213 | void (MachineFunction &MF, |
214 | MachineOptimizationRemarkAnalysis &Rem) { |
215 | const MachineFrameInfo &MFI = MF.getFrameInfo(); |
216 | if (!MFI.hasStackObjects()) |
217 | return; |
218 | |
219 | const TargetFrameLowering *FI = MF.getSubtarget().getFrameLowering(); |
220 | |
221 | LLVM_DEBUG(dbgs() << "getStackProtectorIndex ==" |
222 | << MFI.getStackProtectorIndex() << "\n" ); |
223 | |
224 | std::vector<SlotData> SlotInfo; |
225 | |
226 | const unsigned int NumObj = MFI.getNumObjects(); |
227 | SlotInfo.reserve(n: NumObj); |
228 | // initialize slot info |
229 | for (int Idx = MFI.getObjectIndexBegin(), EndIdx = MFI.getObjectIndexEnd(); |
230 | Idx != EndIdx; ++Idx) { |
231 | if (MFI.isDeadObjectIndex(ObjectIdx: Idx)) |
232 | continue; |
233 | SlotInfo.emplace_back(args: MFI, args: getStackOffset(MF, MFI, FI, FrameIdx: Idx), args&: Idx); |
234 | } |
235 | |
236 | // sort the ordering, to match the actual layout in memory |
237 | llvm::sort(C&: SlotInfo); |
238 | |
239 | SlotDbgMap SlotMap = genSlotDbgMapping(MF); |
240 | |
241 | for (const SlotData &Info : SlotInfo) { |
242 | emitStackSlotRemark(MF, D: Info, Rem); |
243 | for (const DILocalVariable *N : SlotMap[Info.Slot]) |
244 | emitSourceLocRemark(MF, N, Rem); |
245 | } |
246 | } |
247 | |
248 | // We need to generate a mapping of slots to the values that are stored to |
249 | // them. This information is lost by the time we need to print out the frame, |
250 | // so we reconstruct it here by walking the CFG, and generating the mapping. |
251 | SlotDbgMap genSlotDbgMapping(MachineFunction &MF) { |
252 | SlotDbgMap SlotDebugMap; |
253 | |
254 | // add variables to the map |
255 | for (MachineFunction::VariableDbgInfo &DI : |
256 | MF.getInStackSlotVariableDbgInfo()) |
257 | SlotDebugMap[DI.getStackSlot()].insert(X: DI.Var); |
258 | |
259 | // Then add all the spills that have debug data |
260 | for (MachineBasicBlock &MBB : MF) { |
261 | for (MachineInstr &MI : MBB) { |
262 | for (MachineMemOperand *MO : MI.memoperands()) { |
263 | if (!MO->isStore()) |
264 | continue; |
265 | auto *FI = dyn_cast_or_null<FixedStackPseudoSourceValue>( |
266 | Val: MO->getPseudoValue()); |
267 | if (!FI) |
268 | continue; |
269 | int FrameIdx = FI->getFrameIndex(); |
270 | SmallVector<MachineInstr *> Dbg; |
271 | MI.collectDebugValues(DbgValues&: Dbg); |
272 | |
273 | for (MachineInstr *MI : Dbg) |
274 | SlotDebugMap[FrameIdx].insert(X: MI->getDebugVariable()); |
275 | } |
276 | } |
277 | } |
278 | |
279 | return SlotDebugMap; |
280 | } |
281 | }; |
282 | |
283 | char StackFrameLayoutAnalysisPass::ID = 0; |
284 | } // namespace |
285 | |
286 | char &llvm::StackFrameLayoutAnalysisPassID = StackFrameLayoutAnalysisPass::ID; |
287 | INITIALIZE_PASS(StackFrameLayoutAnalysisPass, "stack-frame-layout" , |
288 | "Stack Frame Layout" , false, false) |
289 | |
290 | namespace llvm { |
291 | /// Returns a newly-created StackFrameLayout pass. |
292 | MachineFunctionPass *createStackFrameLayoutAnalysisPass() { |
293 | return new StackFrameLayoutAnalysisPass(); |
294 | } |
295 | |
296 | } // namespace llvm |
297 | |