[llvm] [ARM][Thumb1] Improve EstimateFunctionSizeInBytes accuracy (PR #203318)

Simon Tatham via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 12 02:02:14 PDT 2026


https://github.com/statham-arm updated https://github.com/llvm/llvm-project/pull/203318

>From 9d3e378f3e7d28b36bf94a3c04ca386203a58da8 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Mon, 8 Jun 2026 10:42:34 +0100
Subject: [PATCH 1/2] [ARM][Thumb1] Improve EstimateFunctionSizeInBytes
 accuracy

The estimate of a function size now includes (what I hope are) upper
bounds on the size of the prologue and epilogue; adds size estimates
for some pseudo-instructions that were previously counted as 0; and
increases the estimates for things that were previously optimistic,
such as assuming no constant pool entry ever needs to be duplicated.
The estimation function is also passed extra information to use for
adjusting its estimates, such as the BigFrameOffsets flag which makes
some pseudos have much longer expansions.

Background:

EstimateFunctionSizeInBytes is supposed to estimate how large a Thumb1
function will end up, in advance of actually doing the full code
generation. It must overestimate rather than underestimating, because
large functions need a special precaution (namely, making sure LR is
stacked, so that BL can be used for an intra-function long branch). In
multiple cases recently it has underestimated, leading to a crash
later in code generation, when ARMConstantIslandsPass needs to insert
an intra-function BL and finds that it isn't safe to corrupt LR.

Discussion on Discourse suggested that it's OK to overestimate by
quite a large factor, because any significantly large function won't
be badly impacted by a push of LR that turns out to be unnecessary.
The main place where we want to leave out those unnecessary pushes is
_really_ small leaf functions, where the extra push and pop might be a
significant fraction of the whole call.
---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp     | 170 ++++++++++++++++---
 llvm/lib/Target/ARM/ARMMachineFunctionInfo.h |  11 ++
 2 files changed, 162 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index 7ccd2ad4aa8c9..a282359e39dc6 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2346,18 +2346,146 @@ bool ARMFrameLowering::restoreCalleeSavedRegisters(
 
 // FIXME: Make generic?
 static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
-                                            const ARMBaseInstrInfo &TII) {
+                                            const ARMBaseInstrInfo &TII,
+                                            const ARMSubtarget &STI,
+                                            bool BigFrameOffsets) {
   unsigned FnSize = 0;
+
+  if (MF.shouldSplitStack()) {
+    // Split stack prologue saves r4,r5; makes a copy of sp and loads
+    // a literal; compares the two, and if sp < literal, pushes
+    // further registers and calls __morestack.
+    FnSize += 0x24;
+  }
+
+  // Size of a particularly large Thumb1 stack setup prologue:
+  // update sp for variadic functions (2 bytes)
+  // + push registers (maybe high ones by copying them down, up to 14 bytes)
+  // + frame pointer (might use r11, requiring pushing it first, 6 bytes)
+  // + stack update (up to 6 bytes)
+  // + stack realignment (8)
+  // + make base pointer (2).
+  FnSize += 0x38;
+
+  // Size of a large epilogue:
+  // restore sp from frame pointer (6 bytes if it's in r11)
+  // + pop registers (up to 14 bytes, as above)
+  // + pop r11 if it was saved to make frame pointer (4 bytes)
+  // + pop return address into a low reg (2 bytes)
+  // + update sp to undo variadic function setup (2 bytes)
+  // + BX to where you popped the return address (2 bytes)
+  FnSize += 0x1e;
+
   for (auto &MBB : MF) {
-    for (auto &MI : MBB)
-      FnSize += TII.getInstSizeInBytes(MI);
-  }
-  if (MF.getJumpTableInfo())
-    for (auto &Table: MF.getJumpTableInfo()->getJumpTables())
-      FnSize += Table.MBBs.size() * 4;
-  FnSize += MF.getConstantPool()->getConstants().size() * 4;
-  LLVM_DEBUG(dbgs() << "Estimated function size for " << MF.getName() << " = "
-                    << FnSize << " bytes\n");
+    bool seenBranch = false, seenConstantLoad = false;
+    for (auto &MI : MBB) {
+      unsigned InstSize;
+      switch (MI.getOpcode()) {
+      case ARM::tADDframe:
+        if (BigFrameOffsets) {
+          // We might need two ADD instructions, or even a constant
+          // load. In the latter case we must count the constant as
+          // well as the load instruction and the addition, for 8
+          // bytes total.
+          InstSize = 8;
+        } else {
+          InstSize = 2;
+        }
+        break;
+      case ARM::tLDRspi:
+      case ARM::tSTRspi:
+        if (BigFrameOffsets) {
+          // In a really nasty case, accessing a stack slot might
+          // require saving and restoring a scratch register (4 bytes)
+          // to make space to load (2 bytes) a constant (4 bytes) to
+          // add to SP or FP (2 bytes) and then do the load/store to
+          // the resulting register (2 bytes).
+          InstSize = 14;
+        } else {
+          InstSize = 2;
+        }
+        break;
+      case TargetOpcode::COPY:
+        // In some situations, COPY has to go via a high register, to
+        // avoid corrupting the PSR flags: Thumb moves between low and
+        // high registers don't write the PSR, whereas low/low moves
+        // do.
+        InstSize = 4; // may have to go via a high reg
+        break;
+      case ARM::MEMCPY:
+        InstSize = 4; // becomes one LDMIA_UPD + STMIA_UPD pair
+        break;
+
+      case ARM::Int_eh_sjlj_dispatchsetup:
+        // Worst case is 6 bytes, loading a constant from a literal pool.
+        InstSize = 6;
+        break;
+
+      case ARM::tLDRpci_pic:
+        InstSize = 4; // ordinary LDRpci + add to pc
+        break;
+
+      case ARM::ADJCALLSTACKDOWN:
+      case ARM::ADJCALLSTACKUP:
+        InstSize = 2;
+        break;
+
+      case TargetOpcode::LOAD_STACK_GUARD:
+        if (STI.genExecuteOnly()) {
+          // In execute-only code generation, it costs seven 2-byte
+          // instructions (MOV + 3 ADD + 3 LSL) to load an arbitrary
+          // 32-bit constant, plus two 4-byte MSRs to save/restore the
+          // flags those instructions clobber. Then we load from the
+          // resulting address with one more 2-byte instruction.
+          InstSize = 7 * 2 + 2 * 4 + 8;
+        } else {
+          // If we're not generating execute-only code, the constant
+          // just costs an LDR and a literal, and then another LDR is
+          // needed to load from that address.
+          InstSize = 2 * 2 + 4;
+        }
+        break;
+
+      default:
+        InstSize = TII.getInstSizeInBytes(MI);
+        break;
+      }
+
+      FnSize += InstSize;
+
+      // If the instruction loads a constant, score the value of the
+      // constant, in case it can't be shared with other basic blocks.
+      for (MachineMemOperand *MO : MI.memoperands()) {
+        const PseudoSourceValue *PSV =
+            dyn_cast_if_present<const PseudoSourceValue *>(
+                MO->getPointerInfo().V);
+        if (PSV && PSV->kind() == PseudoSourceValue::ConstantPool) {
+          unsigned ConstSize = MO->getType().getSizeInBytes();
+          FnSize += ConstSize;
+          seenConstantLoad = true;
+        }
+      }
+
+      if (MI.isBranch())
+        seenBranch = true;
+    }
+
+    // If there's no branch instruction in the block and we saw a
+    // constant, count a branch + alignment in case we have to branch
+    // round it.
+    if (seenConstantLoad && !seenBranch)
+      FnSize += 4;
+
+    // We might have to realign at the end of a basic block.
+    FnSize += 2;
+  }
+  if (MF.getJumpTableInfo()) {
+    for (auto &Table : MF.getJumpTableInfo()->getJumpTables()) {
+      unsigned TableLen = Table.MBBs.size();
+      unsigned TableSizeBytes = TableLen * 4;
+      FnSize += TableSizeBytes;
+    }
+  }
   return FnSize;
 }
 
@@ -2706,15 +2834,6 @@ void ARMFrameLowering::determineCalleeSaves(MachineFunction &MF,
   }
 
   bool ForceLRSpill = false;
-  if (!LRSpilled && AFI->isThumb1OnlyFunction()) {
-    unsigned FnSize = EstimateFunctionSizeInBytes(MF, TII);
-    // Force LR to be spilled if the Thumb function size is > 2048. This enables
-    // use of BL to implement far jump.
-    if (FnSize >= (1 << 11)) {
-      CanEliminateFrame = false;
-      ForceLRSpill = true;
-    }
-  }
 
   // If any of the stack slot references may be out of range of an immediate
   // offset, make sure a register (or a spill slot) is available for the
@@ -2810,6 +2929,19 @@ void ARMFrameLowering::determineCalleeSaves(MachineFunction &MF,
                     << "; EstimatedStack: " << EstimatedStackSize
                     << "; EstimatedFPStack: " << MaxFixedOffset - MaxFPOffset
                     << "; BigFrameOffsets: " << BigFrameOffsets << "\n");
+
+  if (!LRSpilled && AFI->isThumb1OnlyFunction()) {
+    unsigned FnSize =
+        EstimateFunctionSizeInBytes(MF, TII, STI, BigFrameOffsets);
+
+    if (FnSize >= (1 << 11)) {
+      // Force LR to be spilled if the Thumb function size is > 2048. This
+      // enables use of BL to implement far jump.
+      CanEliminateFrame = false;
+      ForceLRSpill = true;
+    }
+  }
+
   if (BigFrameOffsets ||
       !CanEliminateFrame || RegInfo->cannotEliminateFrame(MF)) {
     AFI->setHasStackFrame(true);
diff --git a/llvm/lib/Target/ARM/ARMMachineFunctionInfo.h b/llvm/lib/Target/ARM/ARMMachineFunctionInfo.h
index 59d328783a37b..29dd5fdb61a0f 100644
--- a/llvm/lib/Target/ARM/ARMMachineFunctionInfo.h
+++ b/llvm/lib/Target/ARM/ARMMachineFunctionInfo.h
@@ -155,6 +155,10 @@ class ARMFunctionInfo : public MachineFunctionInfo {
   /// destinations.
   bool BranchTargetEnforcement = false;
 
+  /// The result of EstimateFunctionSizeInBytes, if that was run during frame
+  /// lowering. Used to check later that the estimate was conservative.
+  std::optional<unsigned> EstimatedFunctionSizeInBytes;
+
 public:
   ARMFunctionInfo() = default;
 
@@ -225,6 +229,13 @@ class ARMFunctionInfo : public MachineFunctionInfo {
   unsigned getArgumentStackToRestore() const { return ArgumentStackToRestore; }
   void setArgumentStackToRestore(unsigned v) { ArgumentStackToRestore = v; }
 
+  std::optional<unsigned> getEstimatedFunctionSizeInBytes() const {
+    return EstimatedFunctionSizeInBytes;
+  }
+  void setEstimatedFunctionSizeInBytes(unsigned v) {
+    EstimatedFunctionSizeInBytes = v;
+  }
+
   void initPICLabelUId(unsigned UId) {
     PICLabelUId = UId;
   }

>From 48eaec299503ffbac0ba99f08394579901d82ac0 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Fri, 12 Jun 2026 10:01:21 +0100
Subject: [PATCH 2/2] Reinstate previous diagnostic and patch failing test

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp     | 2 ++
 llvm/test/CodeGen/ARM/estimate-size-copy.mir | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index a282359e39dc6..5b440a098182f 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2486,6 +2486,8 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
       FnSize += TableSizeBytes;
     }
   }
+  LLVM_DEBUG(dbgs() << "Estimated function size for " << MF.getName() << " = "
+                    << FnSize << " bytes\n");
   return FnSize;
 }
 
diff --git a/llvm/test/CodeGen/ARM/estimate-size-copy.mir b/llvm/test/CodeGen/ARM/estimate-size-copy.mir
index 154117d1743d1..bb98a94e581fc 100644
--- a/llvm/test/CodeGen/ARM/estimate-size-copy.mir
+++ b/llvm/test/CodeGen/ARM/estimate-size-copy.mir
@@ -4,7 +4,7 @@
 # RUN:     FileCheck %s --check-prefix=OUTPUT
 # RUN: FileCheck %s --check-prefix=DEBUG < %t
 #
-# DEBUG: Estimated function size for f = 4 bytes
+# DEBUG: Estimated function size for f = 94 bytes
 #
 # OUTPUT:  mov r0, r1
 # OUTPUT:  bx lr



More information about the llvm-commits mailing list