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