[llvm] [ARM][Thumb1] Verify EstimateFunctionSizeInBytes every time (PR #203319)

Simon Tatham via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 07:16:47 PDT 2026


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

>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 01/16] [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 cde8d0ad4324d7eec35e50eb9a46211ded2ce965 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Mon, 8 Jun 2026 10:43:13 +0100
Subject: [PATCH 02/16] [ARM][Thumb1] Verify EstimateFunctionSizeInBytes every
 time

EstimateFunctionSizeInBytes is supposed to err on the side of
overestimating the function size, but historically, has sometimes
underestimated, leading to a code generation failure in
ARMConstantIslandPass in the rare case where the underestimate led
frame setup to fail to stack LR to permit intra-function BL.

To try to catch any remaining issues of this kind more reliably, this
patch arranges that the result of EstimateFunctionSizeInBytes is saved
in the ARMFunctionInfo, and in ARMConstantIslandPass, the estimate is
checked against the true function size _unconditionally_. So
underestimates should be detected even when they don't lead to an
actual problem. Moreover, enough debug output is now generated to
understand the basis of the estimate, and the true function size, and
compare them to see why the estimate wasn't big enough.

No extra tests are added in this commit, because the existing tests of
Thumb1 code generation in `llvm/test/CodeGen/Thumb` were already very
good at catching failures of this check, and pointed out most of the
detailed issues fixed in the previous patch.
---
 llvm/lib/Target/ARM/ARMBasicBlockInfo.h       |  4 +++
 llvm/lib/Target/ARM/ARMConstantIslandPass.cpp | 30 +++++++++++++++++++
 llvm/lib/Target/ARM/ARMFrameLowering.cpp      | 21 +++++++++++--
 3 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMBasicBlockInfo.h b/llvm/lib/Target/ARM/ARMBasicBlockInfo.h
index daf8f9b4b8361..de4198b92e812 100644
--- a/llvm/lib/Target/ARM/ARMBasicBlockInfo.h
+++ b/llvm/lib/Target/ARM/ARMBasicBlockInfo.h
@@ -135,6 +135,10 @@ class ARMBasicBlockUtils {
     return BBInfo[MBB->getNumber()].Offset;
   }
 
+  unsigned getFunctionSize() const {
+    return BBInfo[MF.getNumBlockIDs() - 1].postOffset();
+  }
+
   void adjustBBOffsetsAfter(MachineBasicBlock *MBB);
 
   void adjustBBSize(MachineBasicBlock *MBB, int Size) {
diff --git a/llvm/lib/Target/ARM/ARMConstantIslandPass.cpp b/llvm/lib/Target/ARM/ARMConstantIslandPass.cpp
index 97db51129cff2..27f89d9475eae 100644
--- a/llvm/lib/Target/ARM/ARMConstantIslandPass.cpp
+++ b/llvm/lib/Target/ARM/ARMConstantIslandPass.cpp
@@ -511,6 +511,36 @@ bool ARMConstantIslands::runOnMachineFunction(MachineFunction &mf) {
 
   LLVM_DEBUG(dbgs() << '\n'; dumpBBs());
 
+  if (auto Estimate = AFI->getEstimatedFunctionSizeInBytes()) {
+    auto RealSize = BBUtils->getFunctionSize();
+    if (RealSize > *Estimate) {
+      LLVM_DEBUG({
+        dbgs() << "ARMConstantIslandsPass output for " << mf.getName()
+               << " with sizes:\n";
+        for (MachineBasicBlock &MBB : mf) {
+          unsigned Offset = BBUtils->getOffsetOf(&MBB);
+          unsigned End = BBUtils->getBBInfo()[MBB.getNumber()].postOffset();
+          dbgs() << printMBBReference(MBB) << ": // offset "
+                 << Twine::utohexstr(Offset) << "\n";
+          for (MachineInstr &MI : MBB) {
+            unsigned InstSize = TII->getInstSizeInBytes(MI);
+            LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(Offset) << " +"
+                              << InstSize << ": " << MI);
+            Offset += InstSize;
+          }
+          if (Offset < End) {
+            LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(Offset) << " +"
+                              << (End - Offset) << ": extra\n");
+          }
+        }
+      });
+      report_fatal_error(
+          Twine("Underestimated size of function ") + mf.getName() +
+          ": estimate = " + Twine(*Estimate) +
+          ", size after ARMConstantIslandsPass = " + Twine(RealSize));
+    }
+  }
+
   BBUtils->clear();
   WaterList.clear();
   CPUsers.clear();
diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index a282359e39dc6..b08bf45e6e8e5 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2351,10 +2351,14 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
                                             bool BigFrameOffsets) {
   unsigned FnSize = 0;
 
+  LLVM_DEBUG(dbgs() << "EstimateFunctionSizeInBytes(" << MF.getName()
+                    << "):\n");
+
   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.
+    LLVM_DEBUG(dbgs() << "  +0x24 bytes for split stack\n");
     FnSize += 0x24;
   }
 
@@ -2365,6 +2369,7 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
   // + stack update (up to 6 bytes)
   // + stack realignment (8)
   // + make base pointer (2).
+  LLVM_DEBUG(dbgs() << "  +0x38 bytes for prologue\n");
   FnSize += 0x38;
 
   // Size of a large epilogue:
@@ -2374,9 +2379,11 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
   // + 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)
+  LLVM_DEBUG(dbgs() << "  +0x1e bytes for epilogue\n");
   FnSize += 0x1e;
 
   for (auto &MBB : MF) {
+    LLVM_DEBUG(dbgs() << "  " << printMBBReference(MBB) << ":\n");
     bool seenBranch = false, seenConstantLoad = false;
     for (auto &MI : MBB) {
       unsigned InstSize;
@@ -2450,6 +2457,8 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
         InstSize = TII.getInstSizeInBytes(MI);
         break;
       }
+      LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(FnSize) << " +"
+                        << InstSize << ": " << MI);
 
       FnSize += InstSize;
 
@@ -2461,6 +2470,8 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
                 MO->getPointerInfo().V);
         if (PSV && PSV->kind() == PseudoSourceValue::ConstantPool) {
           unsigned ConstSize = MO->getType().getSizeInBytes();
+          LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(FnSize) << " +"
+                            << ConstSize << ": constant pool entry\n");
           FnSize += ConstSize;
           seenConstantLoad = true;
         }
@@ -2479,13 +2490,18 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
     // We might have to realign at the end of a basic block.
     FnSize += 2;
   }
+  LLVM_DEBUG(dbgs() << "  trailers:\n");
   if (MF.getJumpTableInfo()) {
     for (auto &Table : MF.getJumpTableInfo()->getJumpTables()) {
       unsigned TableLen = Table.MBBs.size();
       unsigned TableSizeBytes = TableLen * 4;
+      LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(FnSize) << " +"
+                        << TableSizeBytes << ": jump table\n");
       FnSize += TableSizeBytes;
     }
   }
+  LLVM_DEBUG(dbgs() << "Estimated function size for " << MF.getName() << " = "
+                    << FnSize << " bytes\n");
   return FnSize;
 }
 
@@ -2930,11 +2946,12 @@ void ARMFrameLowering::determineCalleeSaves(MachineFunction &MF,
                     << "; EstimatedFPStack: " << MaxFixedOffset - MaxFPOffset
                     << "; BigFrameOffsets: " << BigFrameOffsets << "\n");
 
-  if (!LRSpilled && AFI->isThumb1OnlyFunction()) {
+  if (AFI->isThumb1OnlyFunction()) {
     unsigned FnSize =
         EstimateFunctionSizeInBytes(MF, TII, STI, BigFrameOffsets);
+    AFI->setEstimatedFunctionSizeInBytes(FnSize);
 
-    if (FnSize >= (1 << 11)) {
+    if (!LRSpilled && 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;

>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 03/16] 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

>From 6b078e995152939d35dea3ad13ecb4a4c00b2aa0 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Fri, 12 Jun 2026 10:17:26 +0100
Subject: [PATCH 04/16] Update an existing test for new more precise
 diagnostics

---
 llvm/test/CodeGen/ARM/estimate-size-copy.mir | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/CodeGen/ARM/estimate-size-copy.mir b/llvm/test/CodeGen/ARM/estimate-size-copy.mir
index 154117d1743d1..3665493b644d5 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: 0x{{[0-9a-f]+}} +4: renamable $r0 = COPY $r1
 #
 # OUTPUT:  mov r0, r1
 # OUTPUT:  bx lr

>From 74450b1e0332a797b94fe05936f2b4233db466e0 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 2 Jul 2026 09:19:28 +0100
Subject: [PATCH 05/16] Add emergency option to turn off verification

---
 llvm/lib/Target/ARM/ARMConstantIslandPass.cpp | 59 +++++++++++--------
 1 file changed, 33 insertions(+), 26 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMConstantIslandPass.cpp b/llvm/lib/Target/ARM/ARMConstantIslandPass.cpp
index 27f89d9475eae..c189d1623b35a 100644
--- a/llvm/lib/Target/ARM/ARMConstantIslandPass.cpp
+++ b/llvm/lib/Target/ARM/ARMConstantIslandPass.cpp
@@ -84,6 +84,10 @@ static cl::opt<bool> SynthesizeThumb1TBB(
     cl::desc("Use compressed jump tables in Thumb-1 by synthesizing an "
              "equivalent to the TBB/TBH instructions"));
 
+static cl::opt<bool>
+    Thumb1EstimateCheck("arm-thumb1-estimate-check", cl::Hidden, cl::init(true),
+                        cl::desc("Verify estimates of Thumb1 function size"));
+
 namespace {
 
   /// ARMConstantIslands - Due to limited PC-relative displacements, ARM
@@ -511,33 +515,36 @@ bool ARMConstantIslands::runOnMachineFunction(MachineFunction &mf) {
 
   LLVM_DEBUG(dbgs() << '\n'; dumpBBs());
 
-  if (auto Estimate = AFI->getEstimatedFunctionSizeInBytes()) {
-    auto RealSize = BBUtils->getFunctionSize();
-    if (RealSize > *Estimate) {
-      LLVM_DEBUG({
-        dbgs() << "ARMConstantIslandsPass output for " << mf.getName()
-               << " with sizes:\n";
-        for (MachineBasicBlock &MBB : mf) {
-          unsigned Offset = BBUtils->getOffsetOf(&MBB);
-          unsigned End = BBUtils->getBBInfo()[MBB.getNumber()].postOffset();
-          dbgs() << printMBBReference(MBB) << ": // offset "
-                 << Twine::utohexstr(Offset) << "\n";
-          for (MachineInstr &MI : MBB) {
-            unsigned InstSize = TII->getInstSizeInBytes(MI);
-            LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(Offset) << " +"
-                              << InstSize << ": " << MI);
-            Offset += InstSize;
-          }
-          if (Offset < End) {
-            LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(Offset) << " +"
-                              << (End - Offset) << ": extra\n");
+  if (Thumb1EstimateCheck) {
+    if (auto Estimate = AFI->getEstimatedFunctionSizeInBytes()) {
+      auto RealSize = BBUtils->getFunctionSize();
+      if (RealSize > *Estimate) {
+        LLVM_DEBUG({
+          dbgs() << "ARMConstantIslandsPass output for " << mf.getName()
+                 << " with sizes:\n";
+          for (MachineBasicBlock &MBB : mf) {
+            unsigned Offset = BBUtils->getOffsetOf(&MBB);
+            unsigned End = BBUtils->getBBInfo()[MBB.getNumber()].postOffset();
+            dbgs() << printMBBReference(MBB) << ": // offset "
+                   << Twine::utohexstr(Offset) << "\n";
+            for (MachineInstr &MI : MBB) {
+              unsigned InstSize = TII->getInstSizeInBytes(MI);
+              LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(Offset) << " +"
+                                << InstSize << ": " << MI);
+              Offset += InstSize;
+            }
+            if (Offset < End) {
+              LLVM_DEBUG(dbgs() << "    0x" << Twine::utohexstr(Offset) << " +"
+                                << (End - Offset) << ": extra\n");
+            }
           }
-        }
-      });
-      report_fatal_error(
-          Twine("Underestimated size of function ") + mf.getName() +
-          ": estimate = " + Twine(*Estimate) +
-          ", size after ARMConstantIslandsPass = " + Twine(RealSize));
+        });
+        report_fatal_error(
+            Twine("Underestimated size of function ") + mf.getName() +
+            ": estimate = " + Twine(*Estimate) +
+            ", size after ARMConstantIslandsPass = " + Twine(RealSize) +
+            " (use --arm-thumb1-estimate-check=0 to inhibit this check)");
+      }
     }
   }
 

>From ed0a0cff79e2714a466932282a9782e6e63ce794 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 2 Jul 2026 13:19:52 +0100
Subject: [PATCH 06/16] Remove braces

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index 5b440a098182f..e597a0739e9fd 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2382,28 +2382,26 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
       unsigned InstSize;
       switch (MI.getOpcode()) {
       case ARM::tADDframe:
-        if (BigFrameOffsets) {
+        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 {
+        else
           InstSize = 2;
-        }
         break;
       case ARM::tLDRspi:
       case ARM::tSTRspi:
-        if (BigFrameOffsets) {
+        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 {
+        else
           InstSize = 2;
-        }
         break;
       case TargetOpcode::COPY:
         // In some situations, COPY has to go via a high register, to
@@ -2431,19 +2429,18 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
         break;
 
       case TargetOpcode::LOAD_STACK_GUARD:
-        if (STI.genExecuteOnly()) {
+        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 {
+        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:

>From 5fc77e56603191a1868ab1d519ab6be062ca6449 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 9 Jul 2026 09:36:31 +0100
Subject: [PATCH 07/16] Epilogue per return

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 25 ++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index e597a0739e9fd..fc59aff429662 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2367,15 +2367,6 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
   // + 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) {
     bool seenBranch = false, seenConstantLoad = false;
     for (auto &MI : MBB) {
@@ -2450,7 +2441,7 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
 
       FnSize += InstSize;
 
-      // If the instruction loads a constant, score the value of the
+      // If the instruction loads a constant, score the size of the
       // constant, in case it can't be shared with other basic blocks.
       for (MachineMemOperand *MO : MI.memoperands()) {
         const PseudoSourceValue *PSV =
@@ -2463,6 +2454,20 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
         }
       }
 
+      // If the instruction is a return or a tailcall, count the size
+      // of an epilogue. (We do this for each return, in case the
+      // epilogue must be duplicated.)
+      if (MI.isReturn() || TII.isTailCall(MI)) {
+        // 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;
+      }
+
       if (MI.isBranch())
         seenBranch = true;
     }

>From 3f4b8b624d066843b201066eb9d3661c4da7dde0 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 9 Jul 2026 09:47:08 +0100
Subject: [PATCH 08/16] Only count unconditional branches for seenBranch

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index fc59aff429662..de1f2b4e6593d 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2468,7 +2468,7 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
         FnSize += 0x1e;
       }
 
-      if (MI.isBranch())
+      if (MI.isUnconditionalBranch())
         seenBranch = true;
     }
 

>From 5200deb0f26c754632fb35704b3e681401a297a7 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 9 Jul 2026 12:46:10 +0100
Subject: [PATCH 09/16] Account for a constant load during stack setup

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index de1f2b4e6593d..8cdcc6f6989f6 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2362,10 +2362,11 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
   // 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 update (up to 12 bytes if a constant load is needed and a branch
+  //   required later to skip the constant)
   // + stack realignment (8)
   // + make base pointer (2).
-  FnSize += 0x38;
+  FnSize += 0x2c;
 
   for (auto &MBB : MF) {
     bool seenBranch = false, seenConstantLoad = false;

>From d90b0e00df63df7993491dba82a1a7aa7c2d17a2 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 9 Jul 2026 13:32:11 +0100
Subject: [PATCH 10/16] Check saved high registers in prologue/epilogue

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 33 ++++++++++++++++--------
 1 file changed, 22 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index 8cdcc6f6989f6..c8382ce85b2eb 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2348,6 +2348,7 @@ bool ARMFrameLowering::restoreCalleeSavedRegisters(
 static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
                                             const ARMBaseInstrInfo &TII,
                                             const ARMSubtarget &STI,
+                                            BitVector &SavedRegs,
                                             bool BigFrameOffsets) {
   unsigned FnSize = 0;
 
@@ -2358,15 +2359,32 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
     FnSize += 0x24;
   }
 
+  // Count the number of saved high registers, which take more effort
+  // to push and pop in the prologue and epilogue.
+  unsigned SavedHighRegs = 0;
+  for (auto Reg : {ARM::R8, ARM::R9, ARM::R10, ARM::R11, ARM::R12})
+    if (SavedRegs.test(Reg))
+      ++SavedHighRegs;
+
   // 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)
+  // + push registers (2 bytes for PUSH {low regs} + 4 bytes per high register
+  //   that needs to be copied into a low reg and then pushed)
   // + frame pointer (might use r11, requiring pushing it first, 6 bytes)
   // + stack update (up to 12 bytes if a constant load is needed and a branch
   //   required later to skip the constant)
   // + stack realignment (8)
   // + make base pointer (2).
-  FnSize += 0x2c;
+  FnSize += 2 + 4 * SavedHighRegs + 6 + 12 + 8 + 2;
+
+  // Size of a large epilogue:
+  // restore sp from frame pointer (6 bytes if it's in r11)
+  // + pop registers (2 bytes + 4 per high register, 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)
+  unsigned EpilogueSize = 6 + 2 + 4 * SavedHighRegs + 4 + 2 + 2;
 
   for (auto &MBB : MF) {
     bool seenBranch = false, seenConstantLoad = false;
@@ -2459,14 +2477,7 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
       // of an epilogue. (We do this for each return, in case the
       // epilogue must be duplicated.)
       if (MI.isReturn() || TII.isTailCall(MI)) {
-        // 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;
+        FnSize += EpilogueSize;
       }
 
       if (MI.isUnconditionalBranch())
@@ -2937,7 +2948,7 @@ void ARMFrameLowering::determineCalleeSaves(MachineFunction &MF,
 
   if (!LRSpilled && AFI->isThumb1OnlyFunction()) {
     unsigned FnSize =
-        EstimateFunctionSizeInBytes(MF, TII, STI, BigFrameOffsets);
+        EstimateFunctionSizeInBytes(MF, TII, STI, SavedRegs, BigFrameOffsets);
 
     if (FnSize >= (1 << 11)) {
       // Force LR to be spilled if the Thumb function size is > 2048. This

>From 2bf38b4b04f6000e477490d281debd4d1c5e3d67 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 9 Jul 2026 15:50:23 +0100
Subject: [PATCH 11/16] Check stack realignment in prologue

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index c8382ce85b2eb..1e975e77e8735 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2348,6 +2348,7 @@ bool ARMFrameLowering::restoreCalleeSavedRegisters(
 static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
                                             const ARMBaseInstrInfo &TII,
                                             const ARMSubtarget &STI,
+                                            const ARMBaseRegisterInfo *RegInfo,
                                             BitVector &SavedRegs,
                                             bool BigFrameOffsets) {
   unsigned FnSize = 0;
@@ -2373,9 +2374,12 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
   // + frame pointer (might use r11, requiring pushing it first, 6 bytes)
   // + stack update (up to 12 bytes if a constant load is needed and a branch
   //   required later to skip the constant)
-  // + stack realignment (8)
+  // + stack realignment (8, if needed)
   // + make base pointer (2).
-  FnSize += 2 + 4 * SavedHighRegs + 6 + 12 + 8 + 2;
+  unsigned PrologueSize = 2 + 4 * SavedHighRegs + 6 + 12 + 2;
+  if (RegInfo->hasStackRealignment(MF))
+    PrologueSize += 8;
+  FnSize += PrologueSize;
 
   // Size of a large epilogue:
   // restore sp from frame pointer (6 bytes if it's in r11)
@@ -2947,8 +2951,8 @@ void ARMFrameLowering::determineCalleeSaves(MachineFunction &MF,
                     << "; BigFrameOffsets: " << BigFrameOffsets << "\n");
 
   if (!LRSpilled && AFI->isThumb1OnlyFunction()) {
-    unsigned FnSize =
-        EstimateFunctionSizeInBytes(MF, TII, STI, SavedRegs, BigFrameOffsets);
+    unsigned FnSize = EstimateFunctionSizeInBytes(MF, TII, STI, RegInfo,
+                                                  SavedRegs, BigFrameOffsets);
 
     if (FnSize >= (1 << 11)) {
       // Force LR to be spilled if the Thumb function size is > 2048. This

>From adb40c7dfea0b8273d05a66976364cc261695fbe Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 9 Jul 2026 13:55:19 +0100
Subject: [PATCH 12/16] Check MachineBasicBlock alignments

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index 1e975e77e8735..d9513ccaff36d 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2390,7 +2390,14 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
   // + BX to where you popped the return address (2 bytes)
   unsigned EpilogueSize = 6 + 2 + 4 * SavedHighRegs + 4 + 2 + 2;
 
+  bool FirstBlock = true;
   for (auto &MBB : MF) {
+    if (!FirstBlock) {
+      // We might have to insert padding to align the start of this basic
+      // block.
+      unsigned Alignment = MBB.getMaxBytesForAlignment();
+      FnSize += Alignment;
+    }
     bool seenBranch = false, seenConstantLoad = false;
     for (auto &MI : MBB) {
       unsigned InstSize;
@@ -2488,14 +2495,11 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
         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 there's no branch instruction in the block and we saw a constant,
+    // count a branch + realignment to 4 bytes, 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()) {

>From 93126772c69c4e5e97811b6fdc2647d5673d4a08 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 9 Jul 2026 15:36:23 +0100
Subject: [PATCH 13/16] Account for potential block splitting

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index d9513ccaff36d..055a440bdff55 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2398,6 +2398,9 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
       unsigned Alignment = MBB.getMaxBytesForAlignment();
       FnSize += Alignment;
     }
+
+    unsigned SizeBeforeThisBB = FnSize;
+
     bool seenBranch = false, seenConstantLoad = false;
     for (auto &MI : MBB) {
       unsigned InstSize;
@@ -2498,8 +2501,19 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
     // If there's no branch instruction in the block and we saw a constant,
     // count a branch + realignment to 4 bytes, in case we have to branch round
     // it.
-    if (seenConstantLoad && !seenBranch)
+    if (seenConstantLoad && !seenBranch) {
       FnSize += 4;
+    }
+
+    // Also, if the block is really, really big, then count an extra 4 bytes
+    // (again branch + realignment) for potentially splitting it in order to
+    // put constants in the middle, avoiding the problem of an LDR not being
+    // able to reach all the way to the end. The LDR offset limit is 1024
+    // bytes; the splitting itself adds some cost, but since any function this
+    // large is likely to have already gone over the "must stack LR" limit, we
+    // can keep things simple by assuming we split at half that rate.
+    unsigned BBSize = FnSize - SizeBeforeThisBB;
+    FnSize += 4 * BBSize / 512;
   }
   if (MF.getJumpTableInfo()) {
     for (auto &Table : MF.getJumpTableInfo()->getJumpTables()) {

>From b684f1fe61a73820504c0251a6222eee383c8f9a Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Thu, 9 Jul 2026 16:39:00 +0100
Subject: [PATCH 14/16] Fix expected function size in one test

I forgot to re-test llvm/test/CodeGen/ARM as well as /Thumb, oops.
---
 llvm/test/CodeGen/ARM/estimate-size-copy.mir | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/CodeGen/ARM/estimate-size-copy.mir b/llvm/test/CodeGen/ARM/estimate-size-copy.mir
index bb98a94e581fc..577cd8d1d18c7 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 = 94 bytes
+# DEBUG: Estimated function size for f = 44 bytes
 #
 # OUTPUT:  mov r0, r1
 # OUTPUT:  bx lr

>From 5d088a102355f7900160cc8c10eff61a6bcd6753 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Fri, 10 Jul 2026 11:46:00 +0100
Subject: [PATCH 15/16] Add size estimates for CMSE calls and returns

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 30 ++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index 055a440bdff55..b35a4de57dccf 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -2452,6 +2452,36 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
         InstSize = 2;
         break;
 
+      case ARM::tBLXNS_CALL:
+        // A call across a CMSE trust boundary involves a lot of work. We must
+        // clear all the registers that might contain our own secrets, which
+        // means pushing them first. So first push low registers, then move
+        // four high regs into them and push those too.
+        InstSize = 2 + 2 * 4 + 2;
+        // Then we must clear the low bit of the target register, because
+        // instead of indicating Arm/Thumb it indicates the CMSE security
+        // status. That takes two instructions.
+        InstSize += 4;
+        // Now actually clear all the registers, via a MOV for each one. This
+        // includes argument registers as well as saved regs, so it can be
+        // r1-r12 inclusive.
+        InstSize += 2 * 12;
+        // Clear the PSR flags (a wide instruction even in Armv8-M Baseline).
+        InstSize += 4;
+        // Perform the BLXNS call itself.
+        InstSize += 2;
+        // Now pop all the saved registers, which takes the same amount of
+        // effort as pushing them did.
+        InstSize += 2 + 2 * 4 + 2;
+        break;
+
+      case ARM::tBXNS_RET:
+        // When returning across a CMSE boundary, we must potentially clear all
+        // the registers that we haven't restored to the caller's value: r0-r3
+        // and r12. We also clear the PSR flags, and finally return via BXNS.
+        InstSize = 5 * 2 + 4 + 2;
+        break;
+
       case TargetOpcode::LOAD_STACK_GUARD:
         if (STI.genExecuteOnly())
           // In execute-only code generation, it costs seven 2-byte

>From 34b2fbe4064afe22bb717789f1c4b0c79ca5724f Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Fri, 10 Jul 2026 12:51:42 +0100
Subject: [PATCH 16/16] Account for a KCFI check before call instructions

---
 llvm/lib/Target/ARM/ARMFrameLowering.cpp | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index b35a4de57dccf..553e4ed6e03d5 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -139,6 +139,7 @@
 #include "llvm/IR/CallingConv.h"
 #include "llvm/IR/DebugLoc.h"
 #include "llvm/IR/Function.h"
+#include "llvm/IR/Module.h"
 #include "llvm/MC/MCAsmInfo.h"
 #include "llvm/MC/MCInstrDesc.h"
 #include "llvm/Support/CodeGen.h"
@@ -2353,6 +2354,8 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
                                             bool BigFrameOffsets) {
   unsigned FnSize = 0;
 
+  bool KCFI = MF.getFunction().getParent()->getModuleFlag("kcfi");
+
   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
@@ -2524,6 +2527,13 @@ static unsigned EstimateFunctionSizeInBytes(const MachineFunction &MF,
         FnSize += EpilogueSize;
       }
 
+      // If the instruction is a call, and KCFI is enabled, then count the
+      // cost of a KCFI_CHECK_Thumb1 pseudo.
+      if (KCFI && MI.isCall() && MI.getCFIType()) {
+        const MCInstrDesc &MCID = TII.get(ARM::KCFI_CHECK_Thumb1);
+        FnSize += MCID.getSize();
+      }
+
       if (MI.isUnconditionalBranch())
         seenBranch = true;
     }



More information about the llvm-commits mailing list