[llvm] [RISCV64] liveness analysis (PR #167454)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Nov 17 20:25:05 PST 2025
================
@@ -0,0 +1,524 @@
+//===- RISCVLiveVariables.cpp - Live Variable Analysis for RISC-V --------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements a live variable analysis pass for the RISC-V backend.
+// The pass computes liveness information for virtual and physical registers
+// in RISC-V machine functions, optimized for RV64 (64-bit RISC-V architecture).
+//
+// The analysis performs a backward dataflow analysis to compute
+// liveness information. Also updates the kill flags on register operands.
+// There is also a verification step to ensure consistency with MBB live-ins.
+//
+//===----------------------------------------------------------------------===//
+
+#include "RISCV.h"
+#include "RISCVInstrInfo.h"
+#include "RISCVSubtarget.h"
+#include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/PostOrderIterator.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/CodeGen/MachineBasicBlock.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineInstr.h"
+#include "llvm/CodeGen/MachineRegisterInfo.h"
+#include "llvm/CodeGen/TargetRegisterInfo.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <set>
+#include <unordered_map>
+
+using namespace llvm;
+
+#define DEBUG_TYPE "riscv-live-variables"
+#define RISCV_LIVE_VARIABLES_NAME "RISC-V Live Variable Analysis"
+
+STATISTIC(NumLiveRegsAtEntry, "Number of registers live at function entry");
+STATISTIC(NumLiveRegsTotal, "Total number of live registers across all blocks");
+
+static cl::opt<bool> UpdateKills("riscv-liveness-update-kills",
+ cl::desc("Update kill flags"), cl::init(false),
+ cl::Hidden);
+
+static cl::opt<unsigned> MaxVRegs("riscv-liveness-max-vregs",
+ cl::desc("Maximum VRegs to track"),
+ cl::init(1024), cl::Hidden);
+
+namespace {
+
+/// LivenessInfo - Stores liveness information for a basic block
+/// TODO: Optimize storage using BitVectors for large register sets.
+struct LivenessInfo {
+ /// Registers that are live into this block
+ /// LiveIn[B] = Use[B] U (LiveOut[B] - Def[B])
+ std::set<Register> LiveIn;
+
+ /// Registers that are live out of this block.
+ /// LiveOut[B] = U LiveIns[∀ Succ(B)].
+ std::set<Register> LiveOut;
+
+ /// Registers that are defined in this block
+ std::set<Register> Gen;
+
+ /// Registers that are used in this block before being defined (if at all).
+ std::set<Register> Use;
+};
+
+class RISCVLiveVariables : public MachineFunctionPass {
+public:
+ static char ID;
+
+ RISCVLiveVariables(bool PreRegAlloc)
+ : MachineFunctionPass(ID), PreRegAlloc(PreRegAlloc) {
+ initializeRISCVLiveVariablesPass(*PassRegistry::getPassRegistry());
+ }
+
+ bool runOnMachineFunction(MachineFunction &MF) override;
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ MachineFunctionPass::getAnalysisUsage(AU);
+ }
+
+ StringRef getPassName() const override { return RISCV_LIVE_VARIABLES_NAME; }
+
+ /// Returns the set of registers live at the entry of the given basic block
+ const std::set<Register> &getLiveInSet(const MachineBasicBlock *MBB) const {
+ auto It = BlockLiveness.find(MBB);
+ assert(It != BlockLiveness.end() && "Block not analyzed");
+ return It->second.LiveIn;
+ }
+
+ /// Returns the set of registers live at the exit of the given basic block
+ const std::set<Register> &getLiveOutSet(const MachineBasicBlock *MBB) const {
+ auto It = BlockLiveness.find(MBB);
+ assert(It != BlockLiveness.end() && "Block not analyzed");
+ return It->second.LiveOut;
+ }
+
+ /// Check if a register is live at a specific instruction
+ bool isLiveAt(Register Reg, const MachineInstr &MI) const;
+
+ /// Print liveness information for debugging
+ void print(raw_ostream &OS, const Module *M = nullptr) const override;
+
+ void verifyLiveness(MachineFunction &MF) const;
+
+ /// Mark operands that kill a register
+ void markKills(MachineFunction &MF);
+
+private:
+ /// Compute local liveness information (Use and Def sets) for each block
+ void computeLocalLiveness(MachineFunction &MF);
+
+ /// Compute global liveness information (LiveIn and LiveOut sets)
+ void computeGlobalLiveness(MachineFunction &MF);
+
+ /// Process a single instruction to extract def/use information
+ void processInstruction(const MachineInstr &MI, LivenessInfo &Info,
+ const TargetRegisterInfo *TRI);
+
+ /// Check if a register is allocatable (relevant for liveness tracking)
+ bool isTrackableRegister(Register Reg, const TargetRegisterInfo *TRI,
+ const MachineRegisterInfo *MRI) const;
+
+ bool PreRegAlloc;
+ unsigned RegCounter = 0;
+
+ // PreRA can have large number of registers and basic block
+ // level liveness may be expensive without a bitvector representation.
+ std::unordered_map<unsigned, unsigned> TrackedRegisters;
+
+ /// Liveness information for each basic block
+ DenseMap<const MachineBasicBlock *, LivenessInfo> BlockLiveness;
+
+ /// Cached pointer to MachineRegisterInfo
+ const MachineRegisterInfo *MRI;
+
+ /// Cached pointer to TargetRegisterInfo
+ const TargetRegisterInfo *TRI;
+};
+
+} // end anonymous namespace
+
+char RISCVLiveVariables::ID = 0;
+
+INITIALIZE_PASS(RISCVLiveVariables, DEBUG_TYPE, RISCV_LIVE_VARIABLES_NAME,
+ false, true)
+
+FunctionPass *llvm::createRISCVLiveVariablesPass(bool PreRegAlloc) {
+ return new RISCVLiveVariables(PreRegAlloc);
+}
+
+bool RISCVLiveVariables::isTrackableRegister(
+ Register Reg, const TargetRegisterInfo *TRI,
+ const MachineRegisterInfo *MRI) const {
+ // Track all virtual registers but only allocatable physical registers.
+ // 1. General purpose registers (X0-X31)
+ // 2. Floating point registers (F0-F31)
+ // 3. Vector registers if present
+
+ if (Reg.isVirtual())
+ return true;
+
+ if (Reg.isPhysical())
+ return TRI->isInAllocatableClass(Reg);
+
+ return false;
+}
+
+void RISCVLiveVariables::processInstruction(const MachineInstr &MI,
+ LivenessInfo &Info,
+ const TargetRegisterInfo *TRI) {
+ std::vector<Register> GenVec;
+ for (const MachineOperand &MO : MI.operands()) {
+ if (!MO.isReg() || !MO.getReg())
+ continue;
+
+ Register Reg = MO.getReg();
+
+ TrackedRegisters.insert(std::pair(Reg, RegCounter++));
+
+ // Skip non-trackable registers
+ if (!isTrackableRegister(Reg, TRI, MRI))
+ continue;
+
+ if (MO.isUse()) {
+ // Only add to Use set if not already defined in this block.
+ if (Info.Gen.find(Reg) == Info.Gen.end()) {
+ Info.Use.insert(Reg);
+
+ // Also handle sub-registers for physical registers
+ if (Reg.isPhysical()) {
+ for (MCSubRegIterator SubRegs(Reg, TRI, /*IncludeSelf=*/false);
+ SubRegs.isValid(); ++SubRegs) {
+ if (Info.Gen.find(*SubRegs) == Info.Gen.end()) {
+ Info.Use.insert(*SubRegs);
+ }
+ }
+ }
+ }
+ }
+
+ // Handle implicit operands (like condition codes, stack pointer updates)
+ if (MO.isImplicit() && MO.isUse() && Reg.isPhysical()) {
+ if (Info.Gen.find(Reg) == Info.Gen.end()) {
+ Info.Use.insert(Reg);
+ }
+ }
+
+ if (MO.isDef()) // Collect defs for later processing.
+ GenVec.push_back(Reg);
+ }
+
+ for (auto Reg : GenVec) {
+ Info.Gen.insert(Reg);
+ if (Reg.isPhysical()) {
+ for (MCSubRegIterator SubRegs(Reg, TRI, /*IncludeSelf=*/false);
+ SubRegs.isValid(); ++SubRegs) {
+ Info.Gen.insert(*SubRegs);
+ }
+ }
+ }
+
+ // Handle RegMasks (from calls) - they kill all non-preserved registers
+ for (const MachineOperand &MO : MI.operands()) {
+ if (MO.isRegMask()) {
+ const uint32_t *RegMask = MO.getRegMask();
+
+ // Iterate through all physical registers
+ for (unsigned PhysReg = 1; PhysReg < TRI->getNumRegs(); ++PhysReg) {
+ // If the register is not preserved by this mask, it's clobbered
+ if (!MachineOperand::clobbersPhysReg(RegMask, PhysReg))
+ continue;
+
+ // Mark as defined (clobbered)
+ if (isTrackableRegister(Register(PhysReg), TRI, MRI)) {
+ Info.Gen.insert(Register(PhysReg));
+ }
+ }
+ }
+ }
+}
+
+void RISCVLiveVariables::computeLocalLiveness(MachineFunction &MF) {
+ LLVM_DEBUG(dbgs() << "Computing local liveness for " << MF.getName() << "\n");
+
+ // Process each basic block
+ for (MachineBasicBlock &MBB : MF) {
+ LivenessInfo &Info = BlockLiveness[&MBB];
+ Info.Gen.clear();
+ Info.Use.clear();
+
+ // Process instructions in forward order to build Use and Def sets
+ for (const MachineInstr &MI : MBB) {
+ // Skip debug instructions and meta instructions
+ if (MI.isDebugInstr() || MI.isMetaInstruction())
+ continue;
+
+ processInstruction(MI, Info, TRI);
+ }
+
+ LLVM_DEBUG({
+ dbgs() << "Block " << MBB.getName() << ":\n";
+ dbgs() << " Use: ";
+ for (Register Reg : Info.Use)
+ dbgs() << printReg(Reg, TRI) << " ";
+ dbgs() << "\n Def: ";
+ for (Register Reg : Info.Gen)
+ dbgs() << printReg(Reg, TRI) << " ";
+ dbgs() << "\n";
+ });
+ }
+}
+
+void RISCVLiveVariables::computeGlobalLiveness(MachineFunction &MF) {
+ LLVM_DEBUG(dbgs() << "Computing global liveness (fixed-point iteration)\n");
+
+ bool Changed = true;
+ [[maybe_unused]] unsigned Iterations = 0;
+
+ // Iterate until we reach a fixed point
+ // Live-out[B] = Union of Live-in[S] for all successors S of B
+ // Live-in[B] = Use[B] Union (Live-out[B] - Def[B])
+ while (Changed) {
+ Changed = false;
+ ++Iterations;
+
+ for (MachineBasicBlock *MBB : post_order(&MF)) {
+ LivenessInfo &Info = BlockLiveness[MBB];
+ std::set<Register> OldLiveIn = Info.LiveIn;
+ std::set<Register> NewLiveOut;
+
+ // Compute Live-out: Union of Live-in of all successors
+ for (MachineBasicBlock *Succ : MBB->successors()) {
+ LivenessInfo &SuccInfo = BlockLiveness[Succ];
+ NewLiveOut.insert(SuccInfo.LiveIn.begin(), SuccInfo.LiveIn.end());
+ }
+
+ Info.LiveOut = NewLiveOut;
+
+ // Compute Live-in: Use Union (Live-out - Def)
+ std::set<Register> NewLiveIn = Info.Use;
+
+ for (Register Reg : Info.LiveOut) {
+ if (Info.Gen.find(Reg) == Info.Gen.end()) {
+ NewLiveIn.insert(Reg);
+ }
+ }
+
+ Info.LiveIn = NewLiveIn;
+
+ // Check if anything changed
+ if (Info.LiveIn != OldLiveIn) {
+ Changed = true;
+ }
+ }
+ }
+
+ LLVM_DEBUG(dbgs() << "Global liveness converged in " << Iterations
+ << " iterations\n");
+
+ // Update statistics
+ for (auto &Entry : BlockLiveness) {
+ NumLiveRegsTotal += Entry.second.LiveIn.size();
+ }
+
+ // Count live registers at function entry
+ if (!MF.empty()) {
+ const MachineBasicBlock &EntryBB = MF.front();
+ NumLiveRegsAtEntry += BlockLiveness[&EntryBB].LiveIn.size();
+ }
+}
+
+bool RISCVLiveVariables::isLiveAt(Register Reg, const MachineInstr &MI) const {
+ const MachineBasicBlock *MBB = MI.getParent();
+ auto It = BlockLiveness.find(MBB);
+
+ if (It == BlockLiveness.end())
+ return false;
+
+ const LivenessInfo &Info = It->second;
+
+ // A register is live at an instruction if:
+ // 1. It's in the live-in set of the block, OR
+ // 2. It's defined before this instruction and used after (not yet killed)
+
+ // Check if it's live-in to the block
+ if (Info.LiveIn.count(Reg))
+ return true;
+
+ // For a more precise answer, we'd need to track instruction-level liveness
+ // For now, conservatively return true if it's in live-out and not killed yet
+ if (Info.LiveOut.count(Reg))
+ return true;
+
+ return false;
+}
+
+void RISCVLiveVariables::verifyLiveness(MachineFunction &MF) const {
+ for (auto &BB : MF) {
+ auto BBLiveness = BlockLiveness.find(&BB);
+ assert(BBLiveness != BlockLiveness.end() && "Missing Liveness");
+ auto &ComputedLivein = BBLiveness->second.LiveIn;
+ for (auto &LI : BB.getLiveIns()) {
+ if (!ComputedLivein.count(LI.PhysReg)) {
+ LLVM_DEBUG(dbgs() << "Warning: Live-in register "
+ << printReg(LI.PhysReg, TRI)
+ << " missing from computed live-in set of block "
+ << BB.getName() << "\n");
+ llvm_unreachable("Computed live-in set is inconsistent with MBB.");
+ }
+ }
+ }
+}
+
+void RISCVLiveVariables::markKills(MachineFunction &MF) {
+ auto KillSetSize = PreRegAlloc ? RegCounter : TRI->getNumRegs();
+ for (MachineBasicBlock *MBB : post_order(&MF)) {
+ // Set all the registers that are not live-out of the block.
+ // Since the global liveness is available (even though a bit conservative),
+ // this initialization is safe.
+ llvm::BitVector KillSet(KillSetSize, true);
+ LivenessInfo &Info = BlockLiveness[MBB];
+
+ for (Register Reg : Info.LiveOut) {
+ auto RegIdx = PreRegAlloc ? TrackedRegisters[Reg] : Reg.asMCReg().id();
+ KillSet.reset(RegIdx);
+ }
+
+ for (MachineInstr &MI : reverse(*MBB)) {
+ for (MachineOperand &MO : MI.all_defs()) {
+ Register Reg = MO.getReg();
+ // Does not track physical registers pre-regalloc.
+ if ((PreRegAlloc && Reg.isPhysical()) ||
+ !isTrackableRegister(Reg, TRI, MRI))
+ continue;
+
+ assert(TrackedRegisters.find(Reg) != TrackedRegisters.end() &&
+ "Register not tracked");
+ auto RegIdx = PreRegAlloc ? TrackedRegisters[Reg] : Reg.asMCReg().id();
+
+ KillSet.set(RegIdx);
+
+ // Also handle sub-registers for physical registers
+ if (!PreRegAlloc && Reg.isPhysical()) {
+ for (MCRegAliasIterator RA(Reg, TRI, true); RA.isValid(); ++RA)
+ KillSet.set(*RA);
+ }
+ }
+
+ for (MachineOperand &MO : MI.all_uses()) {
+ Register Reg = MO.getReg();
+ // Does not track physical registers pre-regalloc.
+ if ((PreRegAlloc && Reg.isPhysical()) ||
+ !isTrackableRegister(Reg, TRI, MRI))
+ continue;
+
+ assert(TrackedRegisters.find(Reg) != TrackedRegisters.end() &&
+ "Register not tracked");
+ auto RegIdx = PreRegAlloc ? TrackedRegisters[Reg] : Reg.asMCReg().id();
+
+ if (KillSet[RegIdx]) {
+ if (!MO.isKill() && !MI.isPHI())
+ MO.setIsKill(true);
+ LLVM_DEBUG(dbgs() << "Marking kill of " << printReg(Reg, TRI)
+ << " at instruction: " << MI);
+ KillSet.reset(RegIdx);
+ if (Reg.isPhysical()) {
+ for (MCRegAliasIterator RA(Reg, TRI, true); RA.isValid(); ++RA)
+ KillSet.reset(*RA);
+ }
+ }
+ }
+ }
+ }
+}
+
+bool RISCVLiveVariables::runOnMachineFunction(MachineFunction &MF) {
+ if (skipFunction(MF.getFunction()) || MF.empty())
+ return false;
+
+ const RISCVSubtarget &Subtarget = MF.getSubtarget<RISCVSubtarget>();
+
+ // Verify we're targeting RV64
+ if (!Subtarget.is64Bit()) {
+ LLVM_DEBUG(dbgs() << "Warning: RISCVLiveVariables optimized for RV64, "
+ << "but running on RV32\n");
+ return false;
+ }
+
+ MRI = &MF.getRegInfo();
+ TRI = Subtarget.getRegisterInfo();
+
+ LLVM_DEBUG(dbgs() << "***** RISC-V Live Variable Analysis *****\n");
+ LLVM_DEBUG(dbgs() << "Function: " << MF.getName() << "\n");
+
+ // Clear any previous analysis
+ BlockLiveness.clear();
+
+ // Step 1: Compute local liveness (Use and Def sets)
+ computeLocalLiveness(MF);
+
+ // Step 2: Compute global liveness (LiveIn and LiveOut sets)
+ computeGlobalLiveness(MF);
+
+ // TODO: Update live-in/live-out sets of MBBs
+
+ // Step 3: Mark kill flags on operands
+ if (UpdateKills && MaxVRegs >= RegCounter)
+ markKills(MF);
+
+ LLVM_DEBUG({
+ dbgs() << "\n***** Final Liveness Information *****\n";
+ print(dbgs());
+ });
+
+ verifyLiveness(MF);
+ // This is an analysis pass, it doesn't modify the function
+ return false;
----------------
akmysti wrote:
Done.
https://github.com/llvm/llvm-project/pull/167454
More information about the llvm-commits
mailing list