| //===-- StackFrameLayoutAnalysisPass.cpp | |
| //------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // StackFrameLayoutAnalysisPass implementation. Outputs information about the | |
| // layout of the stack frame, using the remarks interface. On the CLI it prints | |
| // a textual representation of the stack frame. When possible it prints the | |
| // values that occupy a stack slot using any available debug information. Since | |
| // output is remarks based, it is also available in a machine readable file | |
| // format, such as YAML. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/CodeGen/StackFrameLayoutAnalysisPass.h" | |
| #include "llvm/ADT/SetVector.h" | |
| #include "llvm/Analysis/OptimizationRemarkEmitter.h" | |
| #include "llvm/CodeGen/MachineFrameInfo.h" | |
| #include "llvm/CodeGen/MachineFunction.h" | |
| #include "llvm/CodeGen/MachineFunctionPass.h" | |
| #include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h" | |
| #include "llvm/CodeGen/Passes.h" | |
| #include "llvm/CodeGen/SlotIndexes.h" | |
| #include "llvm/CodeGen/StackProtector.h" | |
| #include "llvm/CodeGen/TargetFrameLowering.h" | |
| #include "llvm/CodeGen/TargetSubtargetInfo.h" | |
| #include "llvm/IR/DebugInfoMetadata.h" | |
| #include "llvm/IR/PrintPasses.h" | |
| #include "llvm/InitializePasses.h" | |
| #include "llvm/Support/Debug.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| using namespace llvm; | |
| #define DEBUG_TYPE "stack-frame-layout" | |
| namespace { | |
| /// StackFrameLayoutAnalysisPass - This is a pass to dump the stack frame of a | |
| /// MachineFunction. | |
| /// | |
| struct StackFrameLayoutAnalysis { | |
| using SlotDbgMap = SmallDenseMap<int, SetVector<const DILocalVariable *>>; | |
| MachineOptimizationRemarkEmitter &ORE; | |
| StackFrameLayoutAnalysis(MachineOptimizationRemarkEmitter &ORE) : ORE(ORE) {} | |
| enum SlotType { | |
| Spill, // a Spill slot | |
| Fixed, // a Fixed slot (e.g. arguments passed on the stack) | |
| VariableSized, // a variable sized object | |
| StackProtector, // Stack Protector slot | |
| Variable, // a slot used to store a local data (could be a tmp) | |
| Invalid // It's an error for a slot to have this type | |
| }; | |
| struct SlotData { | |
| int Slot; | |
| int Size; | |
| int Align; | |
| StackOffset Offset; | |
| SlotType SlotTy; | |
| bool Scalable; | |
| SlotData(const MachineFrameInfo &MFI, const StackOffset Offset, | |
| const int Idx) | |
| : Slot(Idx), Size(MFI.getObjectSize(Idx)), | |
| Align(MFI.getObjectAlign(Idx).value()), Offset(Offset), | |
| SlotTy(Invalid), Scalable(false) { | |
| Scalable = MFI.hasScalableStackID(Idx); | |
| if (MFI.isSpillSlotObjectIndex(Idx)) | |
| SlotTy = SlotType::Spill; | |
| else if (MFI.isFixedObjectIndex(Idx)) | |
| SlotTy = SlotType::Fixed; | |
| else if (MFI.isVariableSizedObjectIndex(Idx)) | |
| SlotTy = SlotType::VariableSized; | |
| else if (MFI.hasStackProtectorIndex() && | |
| Idx == MFI.getStackProtectorIndex()) | |
| SlotTy = SlotType::StackProtector; | |
| else | |
| SlotTy = SlotType::Variable; | |
| } | |
| bool isVarSize() const { return SlotTy == SlotType::VariableSized; } | |
| // We use this to sort in reverse order, so that the layout is displayed | |
| // correctly. Variable sized slots are sorted to the end of the list, as | |
| // offsets are currently incorrect for these but they reside at the end of | |
| // the stack frame. The Slot index is used to ensure deterministic order | |
| // when offsets are equal. | |
| bool operator<(const SlotData &Rhs) const { | |
| return std::make_tuple(!isVarSize(), | |
| Offset.getFixed() + Offset.getScalable(), Slot) > | |
| std::make_tuple(!Rhs.isVarSize(), | |
| Rhs.Offset.getFixed() + Rhs.Offset.getScalable(), | |
| Rhs.Slot); | |
| } | |
| }; | |
| bool run(MachineFunction &MF) { | |
| // TODO: We should implement a similar filter for remarks: | |
| // -Rpass-func-filter=<regex> | |
| if (!isFunctionInPrintList(MF.getName())) | |
| return false; | |
| LLVMContext &Ctx = MF.getFunction().getContext(); | |
| if (!Ctx.getDiagHandlerPtr()->isAnalysisRemarkEnabled(DEBUG_TYPE)) | |
| return false; | |
| MachineOptimizationRemarkAnalysis Rem(DEBUG_TYPE, "StackLayout", | |
| MF.getFunction().getSubprogram(), | |
| &MF.front()); | |
| Rem << ("\nFunction: " + MF.getName()).str(); | |
| emitStackFrameLayoutRemarks(MF, Rem); | |
| ORE.emit(Rem); | |
| return false; | |
| } | |
| std::string getTypeString(SlotType Ty) { | |
| switch (Ty) { | |
| case SlotType::Spill: | |
| return "Spill"; | |
| case SlotType::Fixed: | |
| return "Fixed"; | |
| case SlotType::VariableSized: | |
| return "VariableSized"; | |
| case SlotType::StackProtector: | |
| return "Protector"; | |
| case SlotType::Variable: | |
| return "Variable"; | |
| default: | |
| llvm_unreachable("bad slot type for stack layout"); | |
| } | |
| } | |
| void emitStackSlotRemark(const MachineFunction &MF, const SlotData &D, | |
| MachineOptimizationRemarkAnalysis &Rem) { | |
| // To make it easy to understand the stack layout from the CLI, we want to | |
| // print each slot like the following: | |
| // | |
| // Offset: [SP+8], Type: Spill, Align: 8, Size: 16 | |
| // foo @ /path/to/file.c:25 | |
| // bar @ /path/to/file.c:35 | |
| // | |
| // Which prints the size, alignment, and offset from the SP at function | |
| // entry. | |
| // | |
| // But we also want the machine readable remarks data to be nicely | |
| // organized. So we print some additional data as strings for the CLI | |
| // output, but maintain more structured data for the YAML. | |
| // | |
| // For example we store the Offset in YAML as: | |
| // ... | |
| // - Offset: -8 | |
| // - ScalableOffset: -16 | |
| // Note: the ScalableOffset entries are added only for slots with non-zero | |
| // scalable offsets. | |
| // | |
| // But we print it to the CLI as: | |
| // Offset: [SP-8] | |
| // | |
| // Or with non-zero scalable offset: | |
| // Offset: [SP-8-16 x vscale] | |
| // Negative offsets will print a leading `-`, so only add `+` | |
| std::string Prefix = | |
| formatv("\nOffset: [SP{0}", (D.Offset.getFixed() < 0) ? "" : "+").str(); | |
| Rem << Prefix << ore::NV("Offset", D.Offset.getFixed()); | |
| if (D.Offset.getScalable()) { | |
| Rem << ((D.Offset.getScalable() < 0) ? "" : "+") | |
| << ore::NV("ScalableOffset", D.Offset.getScalable()) << " x vscale"; | |
| } | |
| Rem << "], Type: " << ore::NV("Type", getTypeString(D.SlotTy)) | |
| << ", Align: " << ore::NV("Align", D.Align) | |
| << ", Size: " << ore::NV("Size", ElementCount::get(D.Size, D.Scalable)); | |
| } | |
| void emitSourceLocRemark(const MachineFunction &MF, const DILocalVariable *N, | |
| MachineOptimizationRemarkAnalysis &Rem) { | |
| std::string Loc = | |
| formatv("{0} @ {1}:{2}", N->getName(), N->getFilename(), N->getLine()) | |
| .str(); | |
| Rem << "\n " << ore::NV("DataLoc", Loc); | |
| } | |
| StackOffset getStackOffset(const MachineFunction &MF, | |
| const MachineFrameInfo &MFI, | |
| const TargetFrameLowering *FI, int FrameIdx) { | |
| if (!FI) | |
| return StackOffset::getFixed(MFI.getObjectOffset(FrameIdx)); | |
| return FI->getFrameIndexReferenceFromSP(MF, FrameIdx); | |
| } | |
| void emitStackFrameLayoutRemarks(MachineFunction &MF, | |
| MachineOptimizationRemarkAnalysis &Rem) { | |
| const MachineFrameInfo &MFI = MF.getFrameInfo(); | |
| if (!MFI.hasStackObjects()) | |
| return; | |
| const TargetFrameLowering *FI = MF.getSubtarget().getFrameLowering(); | |
| LLVM_DEBUG(dbgs() << "getStackProtectorIndex ==" | |
| << MFI.getStackProtectorIndex() << "\n"); | |
| std::vector<SlotData> SlotInfo; | |
| const unsigned int NumObj = MFI.getNumObjects(); | |
| SlotInfo.reserve(NumObj); | |
| // initialize slot info | |
| for (int Idx = MFI.getObjectIndexBegin(), EndIdx = MFI.getObjectIndexEnd(); | |
| Idx != EndIdx; ++Idx) { | |
| if (MFI.isDeadObjectIndex(Idx)) | |
| continue; | |
| SlotInfo.emplace_back(MFI, getStackOffset(MF, MFI, FI, Idx), Idx); | |
| } | |
| // sort the ordering, to match the actual layout in memory | |
| llvm::sort(SlotInfo); | |
| SlotDbgMap SlotMap = genSlotDbgMapping(MF); | |
| for (const SlotData &Info : SlotInfo) { | |
| emitStackSlotRemark(MF, Info, Rem); | |
| for (const DILocalVariable *N : SlotMap[Info.Slot]) | |
| emitSourceLocRemark(MF, N, Rem); | |
| } | |
| } | |
| // We need to generate a mapping of slots to the values that are stored to | |
| // them. This information is lost by the time we need to print out the frame, | |
| // so we reconstruct it here by walking the CFG, and generating the mapping. | |
| SlotDbgMap genSlotDbgMapping(MachineFunction &MF) { | |
| SlotDbgMap SlotDebugMap; | |
| // add variables to the map | |
| for (MachineFunction::VariableDbgInfo &DI : | |
| MF.getInStackSlotVariableDbgInfo()) | |
| SlotDebugMap[DI.getStackSlot()].insert(DI.Var); | |
| // Then add all the spills that have debug data | |
| for (MachineBasicBlock &MBB : MF) { | |
| for (MachineInstr &MI : MBB) { | |
| for (MachineMemOperand *MO : MI.memoperands()) { | |
| if (!MO->isStore()) | |
| continue; | |
| auto *FI = dyn_cast_or_null<FixedStackPseudoSourceValue>( | |
| MO->getPseudoValue()); | |
| if (!FI) | |
| continue; | |
| int FrameIdx = FI->getFrameIndex(); | |
| SmallVector<MachineInstr *> Dbg; | |
| MI.collectDebugValues(Dbg); | |
| for (MachineInstr *MI : Dbg) | |
| SlotDebugMap[FrameIdx].insert(MI->getDebugVariable()); | |
| } | |
| } | |
| } | |
| return SlotDebugMap; | |
| } | |
| }; | |
| class StackFrameLayoutAnalysisLegacy : public MachineFunctionPass { | |
| public: | |
| static char ID; | |
| StackFrameLayoutAnalysisLegacy() : MachineFunctionPass(ID) {} | |
| StringRef getPassName() const override { | |
| return "Stack Frame Layout Analysis"; | |
| } | |
| void getAnalysisUsage(AnalysisUsage &AU) const override { | |
| AU.setPreservesAll(); | |
| MachineFunctionPass::getAnalysisUsage(AU); | |
| AU.addRequired<MachineOptimizationRemarkEmitterPass>(); | |
| } | |
| bool runOnMachineFunction(MachineFunction &MF) override { | |
| auto &ORE = getAnalysis<MachineOptimizationRemarkEmitterPass>().getORE(); | |
| return StackFrameLayoutAnalysis(ORE).run(MF); | |
| } | |
| }; | |
| char StackFrameLayoutAnalysisLegacy::ID = 0; | |
| } // namespace | |
| PreservedAnalyses | |
| llvm::StackFrameLayoutAnalysisPass::run(MachineFunction &MF, | |
| MachineFunctionAnalysisManager &MFAM) { | |
| auto &ORE = MFAM.getResult<MachineOptimizationRemarkEmitterAnalysis>(MF); | |
| StackFrameLayoutAnalysis(ORE).run(MF); | |
| return PreservedAnalyses::all(); | |
| } | |
| char &llvm::StackFrameLayoutAnalysisPassID = StackFrameLayoutAnalysisLegacy::ID; | |
| INITIALIZE_PASS(StackFrameLayoutAnalysisLegacy, "stack-frame-layout", | |
| "Stack Frame Layout", false, false) | |
| /// Returns a newly-created StackFrameLayout pass. | |
| MachineFunctionPass *llvm::createStackFrameLayoutAnalysisPass() { | |
| return new StackFrameLayoutAnalysisLegacy(); | |
| } |