[llvm] [BOLT][RISCV] Support frame optimization (PR #214145)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 5 00:19:31 PDT 2026


https://github.com/WangJee created https://github.com/llvm/llvm-project/pull/214145

Implement the RISC-V MCPlusBuilder hooks used by frame analysis and shrink wrapping, including stack-pointer tracking, stack accesses, register moves, immediate adjustment, and spill/reload construction.

Make the initial SP-to-CFA offset target-dependent, account for registers clobbered by call instructions, and keep adjacent stack adjustments separate when the combined immediate cannot be encoded.

Add tests for shrink-wrapped spills, CFI reconstruction, and large stack frames.

>From bef7cc5c523ecf388e24dcbe8bd1d924897ca1f7 Mon Sep 17 00:00:00 2001
From: "wangjue.wangjue" <wangjue.wangjue at alibaba-inc.com>
Date: Wed, 5 Aug 2026 16:12:57 +0900
Subject: [PATCH] [BOLT][RISCV] Support frame optimization

Implement the RISC-V MCPlusBuilder hooks used by frame analysis and shrink wrapping, including stack-pointer tracking, stack accesses, register moves, immediate adjustment, and spill/reload construction.

Make the initial SP-to-CFA offset target-dependent, account for registers clobbered by call instructions, and keep adjacent stack adjustments separate when the combined immediate cannot be encoded.

Add tests for shrink-wrapped spills, CFI reconstruction, and large stack frames.
---
 bolt/include/bolt/Core/MCPlusBuilder.h        |   3 +
 .../bolt/Passes/StackPointerTracking.h        |   5 +-
 bolt/lib/Passes/AllocCombiner.cpp             |   5 +-
 bolt/lib/Passes/FrameAnalysis.cpp             |   9 +-
 bolt/lib/Passes/FrameOptimizer.cpp            |   5 +-
 bolt/lib/Passes/RegAnalysis.cpp               |   5 +
 bolt/lib/Passes/ShrinkWrapping.cpp            |   2 +-
 bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp  | 466 ++++++++++++++++++
 bolt/test/RISCV/alloc-combiner-large-stack.s  |  53 ++
 bolt/test/RISCV/frame-opt-basic.s             |  47 ++
 10 files changed, 591 insertions(+), 9 deletions(-)
 create mode 100644 bolt/test/RISCV/alloc-combiner-large-stack.s
 create mode 100644 bolt/test/RISCV/frame-opt-basic.s

diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index aae520d5afe54..276dc1c5ffc70 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -1286,6 +1286,9 @@ class MCPlusBuilder {
     return 0;
   }
 
+  /// Return the stack pointer offset from CFA at function entry.
+  virtual int64_t getInitialStackPointerOffset() const { return -8; }
+
   virtual MCPhysReg getFramePointer() const {
     llvm_unreachable("not implemented");
     return 0;
diff --git a/bolt/include/bolt/Passes/StackPointerTracking.h b/bolt/include/bolt/Passes/StackPointerTracking.h
index cbb4f88302bc6..333157b563e29 100644
--- a/bolt/include/bolt/Passes/StackPointerTracking.h
+++ b/bolt/include/bolt/Passes/StackPointerTracking.h
@@ -32,10 +32,11 @@ class StackPointerTrackingBase
   int getEmpty() { return EMPTY; }
 
   std::pair<int, int> getStartingStateAtBB(const BinaryBasicBlock &BB) {
-    // Entry BB start with offset 8 from CFA.
+    // The entry block starts at the target ABI's initial offset from CFA.
     // All others start with EMPTY (meaning we don't know anything).
     if (BB.isEntryPoint())
-      return std::make_pair(-8, getEmpty());
+      return std::make_pair(this->BC.MIB->getInitialStackPointerOffset(),
+                            getEmpty());
     return std::make_pair(getEmpty(), getEmpty());
   }
 
diff --git a/bolt/lib/Passes/AllocCombiner.cpp b/bolt/lib/Passes/AllocCombiner.cpp
index ea620bb82184d..e59b42bddb253 100644
--- a/bolt/lib/Passes/AllocCombiner.cpp
+++ b/bolt/lib/Passes/AllocCombiner.cpp
@@ -88,7 +88,10 @@ void AllocCombinerPass::combineAdjustments(BinaryFunction &BF) {
       if (BC.MIB->isSUB(Inst))
         Adjustment = -Adjustment;
 
-      BC.MIB->addToImm(Inst, Adjustment, BC.Ctx.get());
+      if (!BC.MIB->addToImm(Inst, Adjustment, BC.Ctx.get())) {
+        Prev = &Inst;
+        continue;
+      }
 
       LLVM_DEBUG({
         dbgs() << "After adjustment:\n";
diff --git a/bolt/lib/Passes/FrameAnalysis.cpp b/bolt/lib/Passes/FrameAnalysis.cpp
index 0b26da3371234..eda08aa5f6727 100644
--- a/bolt/lib/Passes/FrameAnalysis.cpp
+++ b/bolt/lib/Passes/FrameAnalysis.cpp
@@ -100,8 +100,8 @@ class FrameAccessAnalysis {
   // is used in this function
   int SPOffset{0};
   int FPOffset{0};
-  int64_t CfaOffset{-8};
-  uint16_t CfaReg{7};
+  int64_t CfaOffset;
+  uint16_t CfaReg;
   std::stack<std::pair<int64_t, uint16_t>> CFIStack;
   /// Our pointer to access SPT info
   const MCInst *Prev{nullptr};
@@ -163,7 +163,10 @@ class FrameAccessAnalysis {
 
 public:
   FrameAccessAnalysis(BinaryFunction &BF, StackPointerTracking &SPT)
-      : SPT(SPT), BC(BF.getBinaryContext()), BF(BF) {}
+      : SPT(SPT), BC(BF.getBinaryContext()), BF(BF),
+        CfaOffset(BC.MIB->getInitialStackPointerOffset()),
+        CfaReg(BC.MRI->getDwarfRegNum(BC.MIB->getStackPointer(),
+                                      /*isEH=*/false)) {}
 
   void enterNewBB() { Prev = nullptr; }
   const FrameIndexEntry &getFIE() const { return FIE; }
diff --git a/bolt/lib/Passes/FrameOptimizer.cpp b/bolt/lib/Passes/FrameOptimizer.cpp
index b0b7207feac01..74707acbd8116 100644
--- a/bolt/lib/Passes/FrameOptimizer.cpp
+++ b/bolt/lib/Passes/FrameOptimizer.cpp
@@ -224,8 +224,9 @@ Error FrameOptimizerPass::runOnFunctions(BinaryContext &BC) {
   if (opts::FrameOptimization == FOP_NONE)
     return Error::success();
 
-  if (!BC.isX86()) {
-    BC.errs() << "BOLT-ERROR: " << getName() << " is supported only on X86\n";
+  if (!BC.isX86() && !BC.isRISCV()) {
+    BC.errs() << "BOLT-ERROR: " << getName()
+              << " is supported only on X86 and RISC-V\n";
     exit(1);
   }
 
diff --git a/bolt/lib/Passes/RegAnalysis.cpp b/bolt/lib/Passes/RegAnalysis.cpp
index 66ca6800b1a5f..524d5e3f475c3 100644
--- a/bolt/lib/Passes/RegAnalysis.cpp
+++ b/bolt/lib/Passes/RegAnalysis.cpp
@@ -150,6 +150,11 @@ void RegAnalysis::getInstUsedRegsList(const MCInst &Inst, BitVector &RegSet,
     return;
   }
 
+  // Account for registers clobbered by the call instruction itself, such as
+  // the link register on RISC-V and AArch64.
+  if (GetClobbers)
+    BC.MIB->getClobberedRegs(Inst, RegSet);
+
   // If no call graph supplied...
   if (RegsKilledMap.size() == 0) {
     beConservative(RegSet);
diff --git a/bolt/lib/Passes/ShrinkWrapping.cpp b/bolt/lib/Passes/ShrinkWrapping.cpp
index 5f0af2d68182f..23a3285e0fedf 100644
--- a/bolt/lib/Passes/ShrinkWrapping.cpp
+++ b/bolt/lib/Passes/ShrinkWrapping.cpp
@@ -1601,7 +1601,7 @@ void ShrinkWrapping::rebuildCFIForSP() {
     }
   }
 
-  int PrevSPVal = -8;
+  int PrevSPVal = BC.MIB->getInitialStackPointerOffset();
   BinaryBasicBlock *PrevBB = nullptr;
   StackPointerTracking &SPT = Info.getStackPointerTracking();
   for (BinaryBasicBlock *BB : BF.getLayout().blocks()) {
diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
index d1a0572277874..ace6795fa4bae 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -19,6 +19,7 @@
 #include "llvm/MC/MCInstBuilder.h"
 #include "llvm/MC/MCSubtargetInfo.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
 
 #define DEBUG_TYPE "mcplus"
 
@@ -39,6 +40,17 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
 public:
   using MCPlusBuilder::MCPlusBuilder;
 
+  BitVector getRegsUsedAsParams() const override {
+    BitVector Regs(RegInfo->getNumRegs(), false);
+    for (MCPhysReg Reg :
+         {RISCV::X10, RISCV::X11, RISCV::X12, RISCV::X13, RISCV::X14,
+          RISCV::X15, RISCV::X16, RISCV::X17, RISCV::F10_D, RISCV::F11_D,
+          RISCV::F12_D, RISCV::F13_D, RISCV::F14_D, RISCV::F15_D, RISCV::F16_D,
+          RISCV::F17_D})
+      Regs |= getAliases(Reg);
+    return Regs;
+  }
+
   bool equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B,
               CompFuncTy Comp) const override {
     const auto &RISCVExprA = cast<MCSpecifierExpr>(A);
@@ -66,6 +78,460 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
     Regs |= getAliases(RISCV::X27);
   }
 
+  MCPhysReg getStackPointer() const override { return RISCV::X2; }
+
+  int64_t getInitialStackPointerOffset() const override { return 0; }
+
+  MCPhysReg getFramePointer() const override { return RISCV::X8; }
+
+  MCPhysReg getFlagsReg() const override { return RISCV::NoRegister; }
+
+  void getDefaultLiveOut(BitVector &Regs) const override {
+    Regs |= getAliases(RISCV::X10);
+    Regs |= getAliases(RISCV::X11);
+    Regs |= getAliases(RISCV::F10_D);
+    Regs |= getAliases(RISCV::F11_D);
+  }
+
+  bool isPush(const MCInst &Inst) const override { return false; }
+
+  int getPushSize(const MCInst &Inst) const override { return 0; }
+
+  int getPopSize(const MCInst &Inst) const override { return 0; }
+
+  bool isSUB(const MCInst &Inst) const override {
+    return Inst.getOpcode() == RISCV::SUB;
+  }
+
+  bool requiresAlignedAddress(const MCInst &Inst) const override {
+    return false;
+  }
+
+  bool isPacked(const MCInst &Inst) const override { return false; }
+
+  bool isCleanRegXOR(const MCInst &Inst) const override { return false; }
+
+  bool isRegToRegMove(const MCInst &Inst, MCPhysReg &From,
+                      MCPhysReg &To) const override {
+    switch (Inst.getOpcode()) {
+    default:
+      return false;
+    case RISCV::ADDI:
+      if (!Inst.getOperand(2).isImm() || Inst.getOperand(2).getImm() != 0)
+        return false;
+      To = Inst.getOperand(0).getReg();
+      From = Inst.getOperand(1).getReg();
+      return true;
+    case RISCV::C_MV:
+      To = Inst.getOperand(0).getReg();
+      From = Inst.getOperand(1).getReg();
+      return true;
+    }
+  }
+
+  bool isRedundantMove(const MCInst &Inst) const override {
+    MCPhysReg From, To;
+    return isRegToRegMove(Inst, From, To) && From == To;
+  }
+
+  bool replaceMemOperandWithReg(MCInst &Inst, MCPhysReg RegNum) const override {
+    if (Inst.getNumOperands() == 0 || !Inst.getOperand(0).isReg())
+      return false;
+    const MCPhysReg DestReg = Inst.getOperand(0).getReg();
+    if (getRISCVMCRegisterClass(RISCV::GPRRegClassID).contains(DestReg) &&
+        getRISCVMCRegisterClass(RISCV::GPRRegClassID).contains(RegNum)) {
+      Inst =
+          MCInstBuilder(RISCV::ADDI).addReg(DestReg).addReg(RegNum).addImm(0);
+      return true;
+    }
+
+    unsigned Opcode;
+    switch (Inst.getOpcode()) {
+    default:
+      return false;
+    case RISCV::FLW:
+    case RISCV::C_FLW:
+    case RISCV::C_FLWSP:
+      Opcode = RISCV::FSGNJ_S;
+      break;
+    case RISCV::FLD:
+    case RISCV::C_FLD:
+    case RISCV::C_FLDSP:
+      Opcode = RISCV::FSGNJ_D;
+      break;
+    }
+    Inst = MCInstBuilder(Opcode).addReg(DestReg).addReg(RegNum).addReg(RegNum);
+    return true;
+  }
+
+  bool replaceMemOperandWithImm(MCInst &Inst, StringRef ConstantData,
+                                uint64_t Offset) const override {
+    return false;
+  }
+
+  bool isStackAdjustment(const MCInst &Inst) const override {
+    switch (Inst.getOpcode()) {
+    default:
+      return false;
+    case RISCV::ADDI:
+    case RISCV::C_ADDI:
+    case RISCV::C_ADDI16SP:
+      return Inst.getNumOperands() >= 3 && Inst.getOperand(0).isReg() &&
+             Inst.getOperand(0).getReg() == RISCV::X2 &&
+             Inst.getOperand(1).isReg() &&
+             Inst.getOperand(1).getReg() == RISCV::X2 &&
+             Inst.getOperand(2).isImm();
+    }
+  }
+
+  bool
+  evaluateStackOffsetExpr(const MCInst &Inst, int64_t &Output,
+                          std::pair<MCPhysReg, int64_t> Input1,
+                          std::pair<MCPhysReg, int64_t> Input2) const override {
+    auto getInput = [&](MCPhysReg Reg) -> std::optional<int64_t> {
+      if (Reg == Input1.first)
+        return Input1.second;
+      if (Reg == Input2.first)
+        return Input2.second;
+      return std::nullopt;
+    };
+
+    switch (Inst.getOpcode()) {
+    default:
+      return false;
+    case RISCV::ADDI:
+    case RISCV::C_ADDI:
+    case RISCV::C_ADDI16SP: {
+      if (Inst.getNumOperands() < 3 || !Inst.getOperand(1).isReg() ||
+          !Inst.getOperand(2).isImm())
+        return false;
+      std::optional<int64_t> Input = getInput(Inst.getOperand(1).getReg());
+      if (!Input)
+        return false;
+      Output = *Input + Inst.getOperand(2).getImm();
+      return true;
+    }
+    case RISCV::C_ADDI4SPN: {
+      if (Inst.getNumOperands() < 3 || !Inst.getOperand(1).isReg() ||
+          !Inst.getOperand(2).isImm())
+        return false;
+      std::optional<int64_t> Input = getInput(Inst.getOperand(1).getReg());
+      if (!Input)
+        return false;
+      Output = *Input + Inst.getOperand(2).getImm();
+      return true;
+    }
+    case RISCV::C_MV: {
+      if (Inst.getNumOperands() < 2 || !Inst.getOperand(1).isReg())
+        return false;
+      std::optional<int64_t> Input = getInput(Inst.getOperand(1).getReg());
+      if (!Input)
+        return false;
+      Output = *Input;
+      return true;
+    }
+    }
+  }
+
+  bool isStackAccess(const MCInst &Inst, bool &IsLoad, bool &IsStore,
+                     bool &IsStoreFromReg, MCPhysReg &Reg, int32_t &SrcImm,
+                     uint16_t &StackPtrReg, int64_t &StackOffset, uint8_t &Size,
+                     bool &IsSimple, bool &IsIndexed) const override {
+    IsLoad = false;
+    IsStore = false;
+    IsStoreFromReg = false;
+    Reg = 0;
+    SrcImm = 0;
+    StackPtrReg = 0;
+    StackOffset = 0;
+    Size = 0;
+    IsSimple = false;
+    IsIndexed = false;
+
+    struct StackAccessInfo {
+      uint8_t Size;
+      bool IsLoad;
+    };
+    std::optional<StackAccessInfo> Access;
+    switch (Inst.getOpcode()) {
+    default:
+      return false;
+    case RISCV::LB:
+    case RISCV::LBU:
+      Access = StackAccessInfo{1, true};
+      break;
+    case RISCV::LH:
+    case RISCV::LHU:
+      Access = StackAccessInfo{2, true};
+      break;
+    case RISCV::LW:
+    case RISCV::LWU:
+    case RISCV::FLW:
+    case RISCV::C_LW:
+    case RISCV::C_FLW:
+    case RISCV::C_LWSP:
+    case RISCV::C_FLWSP:
+      Access = StackAccessInfo{4, true};
+      break;
+    case RISCV::LD:
+    case RISCV::FLD:
+    case RISCV::C_LD:
+    case RISCV::C_FLD:
+    case RISCV::C_LDSP:
+    case RISCV::C_FLDSP:
+      Access = StackAccessInfo{8, true};
+      break;
+    case RISCV::SB:
+      Access = StackAccessInfo{1, false};
+      break;
+    case RISCV::SH:
+      Access = StackAccessInfo{2, false};
+      break;
+    case RISCV::SW:
+    case RISCV::FSW:
+    case RISCV::C_SW:
+    case RISCV::C_FSW:
+    case RISCV::C_SWSP:
+    case RISCV::C_FSWSP:
+      Access = StackAccessInfo{4, false};
+      break;
+    case RISCV::SD:
+    case RISCV::FSD:
+    case RISCV::C_SD:
+    case RISCV::C_FSD:
+    case RISCV::C_SDSP:
+    case RISCV::C_FSDSP:
+      Access = StackAccessInfo{8, false};
+      break;
+    }
+
+    if (Inst.getNumOperands() < 3 || !Inst.getOperand(0).isReg() ||
+        !Inst.getOperand(1).isReg() || !Inst.getOperand(2).isImm())
+      return false;
+
+    MCPhysReg Base = Inst.getOperand(1).getReg();
+    if (Base != getStackPointer() && Base != getFramePointer())
+      return false;
+
+    IsLoad = Access->IsLoad;
+    IsStore = !Access->IsLoad;
+    IsStoreFromReg = IsStore;
+    Reg = Inst.getOperand(0).getReg();
+    StackPtrReg = Base;
+    StackOffset = Inst.getOperand(2).getImm();
+    Size = Access->Size;
+    IsSimple = true;
+    return true;
+  }
+
+  bool escapesVariable(const MCInst &Inst,
+                       bool HasFramePointer) const override {
+    bool IsLoad, IsStore, IsStoreFromReg, IsSimple, IsIndexed;
+    MCPhysReg Reg;
+    int32_t SrcImm;
+    uint16_t StackPtrReg;
+    int64_t StackOffset;
+    uint8_t Size;
+    if (isStackAccess(Inst, IsLoad, IsStore, IsStoreFromReg, Reg, SrcImm,
+                      StackPtrReg, StackOffset, Size, IsSimple, IsIndexed))
+      return false;
+
+    const MCInstrDesc &Desc = Info->get(Inst.getOpcode());
+    const unsigned NumDefs = Desc.getNumDefs();
+    bool UsesFrameAddress = false;
+    for (unsigned I = NumDefs, E = MCPlus::getNumPrimeOperands(Inst); I != E;
+         ++I) {
+      const MCOperand &Operand = Inst.getOperand(I);
+      if (!Operand.isReg())
+        continue;
+      const MCPhysReg UsedReg = Operand.getReg();
+      if (UsedReg == getStackPointer() ||
+          (HasFramePointer && UsedReg == getFramePointer())) {
+        UsesFrameAddress = true;
+        break;
+      }
+    }
+    if (!UsesFrameAddress)
+      return false;
+
+    return !any_of(defOperands(Inst), [&](const MCOperand &Operand) {
+      if (!Operand.isReg())
+        return false;
+      const MCPhysReg DefReg = Operand.getReg();
+      return DefReg == getStackPointer() ||
+             (HasFramePointer && DefReg == getFramePointer());
+    });
+  }
+
+  static bool isValidCompressedOffset(unsigned Opcode, int64_t Offset) {
+    auto isShiftedUInt = [&](unsigned Bits, unsigned Alignment) {
+      return Offset >= 0 && isUIntN(Bits, Offset) && Offset % Alignment == 0;
+    };
+
+    switch (Opcode) {
+    default:
+      return false;
+    case RISCV::C_LW:
+    case RISCV::C_FLW:
+    case RISCV::C_SW:
+    case RISCV::C_FSW:
+      return isShiftedUInt(7, 4);
+    case RISCV::C_LD:
+    case RISCV::C_FLD:
+    case RISCV::C_SD:
+    case RISCV::C_FSD:
+      return isShiftedUInt(8, 8);
+    case RISCV::C_LWSP:
+    case RISCV::C_FLWSP:
+    case RISCV::C_SWSP:
+    case RISCV::C_FSWSP:
+      return isShiftedUInt(8, 4);
+    case RISCV::C_LDSP:
+    case RISCV::C_FLDSP:
+    case RISCV::C_SDSP:
+    case RISCV::C_FSDSP:
+      return isShiftedUInt(9, 8);
+    }
+  }
+
+  bool addToImm(MCInst &Inst, int64_t &Amt, MCContext *Ctx) const override {
+    if (Inst.getNumOperands() < 3 || !Inst.getOperand(2).isImm())
+      return false;
+
+    const int64_t NewImm = Inst.getOperand(2).getImm() + Amt;
+    switch (Inst.getOpcode()) {
+    default:
+      return false;
+    case RISCV::ADDI:
+    case RISCV::LB:
+    case RISCV::LBU:
+    case RISCV::LH:
+    case RISCV::LHU:
+    case RISCV::LW:
+    case RISCV::LWU:
+    case RISCV::LD:
+    case RISCV::FLW:
+    case RISCV::FLD:
+    case RISCV::SB:
+    case RISCV::SH:
+    case RISCV::SW:
+    case RISCV::SD:
+    case RISCV::FSW:
+    case RISCV::FSD:
+      if (!isInt<12>(NewImm))
+        return false;
+      break;
+    case RISCV::C_ADDI:
+      if (!isInt<6>(NewImm))
+        return false;
+      break;
+    case RISCV::C_ADDI16SP:
+      if (!isInt<10>(NewImm) || NewImm == 0 || NewImm % 16 != 0)
+        return false;
+      break;
+    case RISCV::C_ADDI4SPN:
+      if (NewImm <= 0 || !isUInt<10>(NewImm) || NewImm % 4 != 0)
+        return false;
+      break;
+    case RISCV::C_LW:
+    case RISCV::C_FLW:
+    case RISCV::C_SW:
+    case RISCV::C_FSW:
+    case RISCV::C_LD:
+    case RISCV::C_FLD:
+    case RISCV::C_SD:
+    case RISCV::C_FSD:
+    case RISCV::C_LWSP:
+    case RISCV::C_FLWSP:
+    case RISCV::C_SWSP:
+    case RISCV::C_FSWSP:
+    case RISCV::C_LDSP:
+    case RISCV::C_FLDSP:
+    case RISCV::C_SDSP:
+    case RISCV::C_FSDSP:
+      if (!isValidCompressedOffset(Inst.getOpcode(), NewImm))
+        return false;
+      break;
+    }
+
+    Amt = NewImm;
+    Inst.getOperand(2).setImm(NewImm);
+    return true;
+  }
+
+  void createSaveToStack(MCInst &Inst, const MCPhysReg &StackReg, int Offset,
+                         const MCPhysReg &SrcReg, int Size) const override {
+    unsigned Opcode;
+    const bool IsGPR =
+        getRISCVMCRegisterClass(RISCV::GPRRegClassID).contains(SrcReg);
+    if (IsGPR) {
+      switch (Size) {
+      default:
+        llvm_unreachable("Invalid GPR spill size");
+      case 1:
+        Opcode = RISCV::SB;
+        break;
+      case 2:
+        Opcode = RISCV::SH;
+        break;
+      case 4:
+        Opcode = RISCV::SW;
+        break;
+      case 8:
+        Opcode = RISCV::SD;
+        break;
+      }
+    } else {
+      if (!getRISCVMCRegisterClass(RISCV::FPR64RegClassID).contains(SrcReg))
+        llvm_unreachable("Unsupported spill register class");
+      if (Size == 4)
+        Opcode = RISCV::FSW;
+      else if (Size == 8)
+        Opcode = RISCV::FSD;
+      else
+        llvm_unreachable("Invalid FPR spill size");
+    }
+    assert(isInt<12>(Offset) && "Stack spill offset is out of range");
+    Inst = MCInstBuilder(Opcode).addReg(SrcReg).addReg(StackReg).addImm(Offset);
+  }
+
+  void createRestoreFromStack(MCInst &Inst, const MCPhysReg &StackReg,
+                              int Offset, const MCPhysReg &DstReg,
+                              int Size) const override {
+    unsigned Opcode;
+    const bool IsGPR =
+        getRISCVMCRegisterClass(RISCV::GPRRegClassID).contains(DstReg);
+    if (IsGPR) {
+      switch (Size) {
+      default:
+        llvm_unreachable("Invalid GPR reload size");
+      case 1:
+        Opcode = RISCV::LB;
+        break;
+      case 2:
+        Opcode = RISCV::LH;
+        break;
+      case 4:
+        Opcode = RISCV::LW;
+        break;
+      case 8:
+        Opcode = RISCV::LD;
+        break;
+      }
+    } else {
+      if (!getRISCVMCRegisterClass(RISCV::FPR64RegClassID).contains(DstReg))
+        llvm_unreachable("Unsupported reload register class");
+      if (Size == 4)
+        Opcode = RISCV::FLW;
+      else if (Size == 8)
+        Opcode = RISCV::FLD;
+      else
+        llvm_unreachable("Invalid FPR reload size");
+    }
+    assert(isInt<12>(Offset) && "Stack reload offset is out of range");
+    Inst = MCInstBuilder(Opcode).addReg(DstReg).addReg(StackReg).addImm(Offset);
+  }
+
   bool shouldRecordCodeRelocation(uint32_t RelType) const override {
     switch (RelType) {
     case ELF::R_RISCV_JAL:
diff --git a/bolt/test/RISCV/alloc-combiner-large-stack.s b/bolt/test/RISCV/alloc-combiner-large-stack.s
new file mode 100644
index 0000000000000..65b36b7ae7ae4
--- /dev/null
+++ b/bolt/test/RISCV/alloc-combiner-large-stack.s
@@ -0,0 +1,53 @@
+# REQUIRES: system-linux
+
+# RUN: %clang %cflags64 -march=rv64gc %s -o %t.exe
+# RUN: link_fdata --no-lbr --nmtool llvm-nm %s %t.exe %t.fdata
+# RUN: llvm-bolt %t.exe -relocs -o %t.out -data %t.fdata \
+# RUN:   -frame-opt=all -simplify-conditional-tail-calls=false \
+# RUN:   -eliminate-unreachable=false
+# RUN: llvm-objdump -d --no-show-raw-insn %t.out | \
+# RUN:   FileCheck %s
+
+  .text
+  .reloc 0, R_RISCV_NONE
+  .globl _start
+  .type _start, @function
+_start:
+  .cfi_startproc
+# FDATA: 1 _start #_start# 100
+  addi sp, sp, -0x1f0
+  .cfi_adjust_cfa_offset 0x1f0
+  sd ra, 0x1e8(sp)
+  .cfi_offset ra, -8
+  addi sp, sp, -0x800
+  .cfi_adjust_cfa_offset 0x800
+  addi sp, sp, -0x6f0
+  .cfi_adjust_cfa_offset 0x6f0
+  sd a0, 0(sp)
+  call callee
+  ld a0, 0(sp)
+  addi sp, sp, 0x7f0
+  .cfi_adjust_cfa_offset -0x7f0
+  addi sp, sp, 0x700
+  .cfi_adjust_cfa_offset -0x700
+  ld ra, 0x1e8(sp)
+  .cfi_restore ra
+  addi sp, sp, 0x1f0
+  .cfi_adjust_cfa_offset -0x1f0
+  ret
+  .cfi_endproc
+  .size _start, .-_start
+
+  .type callee, @function
+callee:
+  ret
+  .size callee, .-callee
+
+# CHECK-LABEL: <_start>:
+# CHECK:       addi sp, sp, -0x1f0
+# CHECK:       addi sp, sp, -0x800
+# CHECK-NEXT:  addi sp, sp, -0x6f0
+# CHECK:       addi sp, sp, 0x7f0
+# CHECK-NEXT:  addi sp, sp, 0x700
+# CHECK:       ld ra, 0x1e8(sp)
+# CHECK:       addi sp, sp, 0x1f0
diff --git a/bolt/test/RISCV/frame-opt-basic.s b/bolt/test/RISCV/frame-opt-basic.s
new file mode 100644
index 0000000000000..e7c4bfbaab914
--- /dev/null
+++ b/bolt/test/RISCV/frame-opt-basic.s
@@ -0,0 +1,47 @@
+# REQUIRES: system-linux
+
+# RUN: %clang %cflags64 -march=rv64gc %s -o %t.exe
+# RUN: link_fdata --no-lbr --nmtool llvm-nm %s %t.exe %t.fdata
+# RUN: llvm-bolt %t.exe -relocs -o %t.out -data %t.fdata \
+# RUN:   -frame-opt=all -simplify-conditional-tail-calls=false \
+# RUN:   -eliminate-unreachable=false | FileCheck %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t.out | \
+# RUN:   FileCheck --check-prefix=CHECK-OBJDUMP %s
+# RUN: llvm-readelf --unwind %t.out | FileCheck --check-prefix=CHECK-CFI %s
+
+  .text
+  .reloc 0, R_RISCV_NONE
+  .globl _start
+  .type _start, @function
+_start:
+  .cfi_startproc
+# FDATA: 1 _start #_start# 100
+  addi sp, sp, -16
+  .cfi_def_cfa sp, 16
+  sd s1, 8(sp)
+  .cfi_offset s1, -8
+.Lbranch:
+  beqz a0, .Lcold
+.Lhot:
+  addi s1, s1, 1
+  addi a0, s1, 0
+.Lexit:
+  ld s1, 8(sp)
+  .cfi_restore s1
+  addi sp, sp, 16
+  .cfi_def_cfa sp, 0
+  ret
+.Lcold:
+  addi a0, a0, 2
+  j .Lexit
+  .cfi_endproc
+  .size _start, .-_start
+
+# CHECK: BOLT-INFO: FOP optimized
+# CHECK: BOLT-INFO: FRAME ANALYSIS: 0 function(s) {{.*}} could not have its frame indices restored.
+# CHECK: BOLT-INFO: Shrink wrapping moved 1 spills inserting load/stores
+# CHECK-OBJDUMP: <_start>:
+# CHECK-OBJDUMP: beqz
+# CHECK-OBJDUMP-NEXT: sd s1, 0x8(sp)
+# CHECK-CFI: DW_CFA_def_cfa: reg2 +0
+# CHECK-CFI: DW_CFA_offset: reg9 -8



More information about the llvm-commits mailing list