[llvm] d7750af - ARM: Track CPSR liveness in Thumb2SizeReduction without kill flags (#223068)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 01:35:41 PDT 2026


Author: Matt Arsenault
Date: 2026-09-22T10:35:36+02:00
New Revision: d7750af92b0b0df0eb4e77f82b47f9e93f29b7b1

URL: https://github.com/llvm/llvm-project/commit/d7750af92b0b0df0eb4e77f82b47f9e93f29b7b1
DIFF: https://github.com/llvm/llvm-project/commit/d7750af92b0b0df0eb4e77f82b47f9e93f29b7b1.diff

LOG: ARM: Track CPSR liveness in Thumb2SizeReduction without kill flags (#223068)

Thumb2SizeReduction narrows a 32-bit instruction to its flag-setting
16-bit form only when CPSR is dead afterwards. It determined this from
kill flags on CPSR operands. Kill flags have been semi-deprecated
for over a decade, so avoid relying on them. Liveness should be
evaluated as a reverse walk over a block.

The existing forward walk over the block is still necessary as a
separate step for the different A9 avoidCPSRPartialUpdate optimization.

Co-Authored-By: Claude claude-opus-4.8 <noreply at anthropic.com>

Added: 
    llvm/test/CodeGen/Thumb2/thumb2-reduce-size-cpsr.mir

Modified: 
    llvm/lib/Target/ARM/Thumb2SizeReduction.cpp
    llvm/test/CodeGen/ARM/cmse-harden-call-returned-values.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Target/ARM/Thumb2SizeReduction.cpp b/llvm/lib/Target/ARM/Thumb2SizeReduction.cpp
index a0df59297abd8..96bc986f02ab3 100644
--- a/llvm/lib/Target/ARM/Thumb2SizeReduction.cpp
+++ b/llvm/lib/Target/ARM/Thumb2SizeReduction.cpp
@@ -980,38 +980,6 @@ Thumb2SizeReduce::ReduceToNarrow(MachineBasicBlock &MBB, MachineInstr *MI,
   return true;
 }
 
-static bool UpdateCPSRDef(MachineInstr &MI, bool LiveCPSR, bool &DefCPSR) {
-  bool HasDef = false;
-  for (const MachineOperand &MO : MI.operands()) {
-    if (!MO.isReg() || MO.isUndef() || MO.isUse())
-      continue;
-    if (MO.getReg() != ARM::CPSR)
-      continue;
-
-    DefCPSR = true;
-    if (!MO.isDead())
-      HasDef = true;
-  }
-
-  return HasDef || LiveCPSR;
-}
-
-static bool UpdateCPSRUse(MachineInstr &MI, bool LiveCPSR) {
-  for (const MachineOperand &MO : MI.operands()) {
-    if (!MO.isReg() || MO.isUndef() || MO.isDef())
-      continue;
-    if (MO.getReg() != ARM::CPSR)
-      continue;
-    assert(LiveCPSR && "CPSR liveness tracking is wrong!");
-    if (MO.isKill()) {
-      LiveCPSR = false;
-      break;
-    }
-  }
-
-  return LiveCPSR;
-}
-
 bool Thumb2SizeReduce::ReduceMI(MachineBasicBlock &MBB, MachineInstr *MI,
                                 bool LiveCPSR, bool IsSelfLoop,
                                 bool SkipPrologueEpilogue) {
@@ -1045,9 +1013,22 @@ bool Thumb2SizeReduce::ReduceMBB(MachineBasicBlock &MBB,
                                  bool SkipPrologueEpilogue) {
   bool Modified = false;
 
-  // Yes, CPSR could be livein.
-  bool LiveCPSR = MBB.isLiveIn(ARM::CPSR);
-  MachineInstr *BundleMI = nullptr;
+  // Narrowing to a flag-setting form is only legal where CPSR is dead.
+  bool LiveCPSR = any_of(MBB.successors(), [](const MachineBasicBlock *Succ) {
+    return Succ->isLiveIn(ARM::CPSR);
+  });
+
+  DenseMap<const MachineInstr *, bool> CPSRLiveAfter;
+  for (MachineInstr &MI : reverse(MBB.instrs())) {
+    if (MI.isBundle() || MI.isDebugInstr())
+      continue;
+    if (ReduceOpcodeMap.contains(MI.getOpcode()))
+      CPSRLiveAfter[&MI] = LiveCPSR;
+    if (MI.definesRegister(ARM::CPSR, /*TRI=*/nullptr))
+      LiveCPSR = false;
+    if (MI.readsRegister(ARM::CPSR, /*TRI=*/nullptr))
+      LiveCPSR = true;
+  }
 
   CPSRDef = nullptr;
   HighLatencyCPSR = false;
@@ -1059,6 +1040,7 @@ bool Thumb2SizeReduce::ReduceMBB(MachineBasicBlock &MBB,
       // Since blocks are visited in RPO, this must be a back-edge.
       continue;
     }
+
     if (PInfo.HighLatencyCPSR) {
       HighLatencyCPSR = true;
       break;
@@ -1074,14 +1056,10 @@ bool Thumb2SizeReduce::ReduceMBB(MachineBasicBlock &MBB,
     NextMII = std::next(MII);
 
     MachineInstr *MI = &*MII;
-    if (MI->isBundle()) {
-      BundleMI = MI;
-      continue;
-    }
-    if (MI->isDebugInstr())
+    if (MI->isBundle() || MI->isDebugInstr())
       continue;
 
-    LiveCPSR = UpdateCPSRUse(*MI, LiveCPSR);
+    LiveCPSR = CPSRLiveAfter.lookup(MI);
 
     // Does NextMII belong to the same bundle as MI?
     bool NextInSameBundle = NextMII != E && NextMII->isBundledWithPred();
@@ -1096,30 +1074,15 @@ bool Thumb2SizeReduce::ReduceMBB(MachineBasicBlock &MBB,
         NextMII->bundleWithPred();
     }
 
-    if (BundleMI && !NextInSameBundle && MI->isInsideBundle()) {
-      // FIXME: Since post-ra scheduler operates on bundles, the CPSR kill
-      // marker is only on the BUNDLE instruction. Process the BUNDLE
-      // instruction as we finish with the bundled instruction to work around
-      // the inconsistency.
-      if (BundleMI->killsRegister(ARM::CPSR, /*TRI=*/nullptr))
-        LiveCPSR = false;
-      MachineOperand *MO =
-          BundleMI->findRegisterDefOperand(ARM::CPSR, /*TRI=*/nullptr);
-      if (MO && !MO->isDead())
-        LiveCPSR = true;
-      MO = BundleMI->findRegisterUseOperand(ARM::CPSR, /*TRI=*/nullptr);
-      if (MO && !MO->isKill())
-        LiveCPSR = true;
-    }
-
-    bool DefCPSR = false;
-    LiveCPSR = UpdateCPSRDef(*MI, LiveCPSR, DefCPSR);
+    // Maintain CPSRDef as the most recent CPSR-defining instruction in program
+    // order, so canAddPseudoFlagDep can consult it. MI is inspected after
+    // reduction, so a candidate just narrowed to a flag-setting form counts.
     if (MI->isCall()) {
       // Calls don't really set CPSR.
       CPSRDef = nullptr;
       HighLatencyCPSR = false;
       IsSelfLoop = false;
-    } else if (DefCPSR) {
+    } else if (MI->definesRegister(ARM::CPSR, /*TRI=*/nullptr)) {
       // This is the last CPSR defining instruction.
       CPSRDef = MI;
       HighLatencyCPSR = isHighLatencyCPSR(CPSRDef);

diff  --git a/llvm/test/CodeGen/ARM/cmse-harden-call-returned-values.ll b/llvm/test/CodeGen/ARM/cmse-harden-call-returned-values.ll
index 58eef443c25e6..b08740efe3ee9 100644
--- a/llvm/test/CodeGen/ARM/cmse-harden-call-returned-values.ll
+++ b/llvm/test/CodeGen/ARM/cmse-harden-call-returned-values.ll
@@ -474,7 +474,7 @@ define i32 @access_i33(ptr %f) {
 ; V8M-COMMON-NEXT:    pop.w {r4, r5, r6, r7, r8, r9, r10, r11}
 ; V8M-LE-NEXT:        and r0, r1, #1
 ; V8M-BE-NEXT:        and r0, r0, #1
-; V8M-COMMON-NEXT:    rsb.w r0, r0, #0
+; V8M-COMMON-NEXT:    rsbs r0, r0, #0
 ; V8M-COMMON-NEXT:    pop {r7, pc}
 ;
 ; V81M-COMMON-LABEL: access_i33:
@@ -491,7 +491,7 @@ define i32 @access_i33(ptr %f) {
 ; V81M-COMMON-NEXT:    pop.w {r4, r5, r6, r7, r8, r9, r10, r11}
 ; V81M-LE-NEXT:        and r0, r1, #1
 ; V81M-BE-NEXT:        and r0, r0, #1
-; V81M-COMMON-NEXT:    rsb.w r0, r0, #0
+; V81M-COMMON-NEXT:    rsbs r0, r0, #0
 ; V81M-COMMON-NEXT:    pop {r7, pc}
 entry:
   %call = tail call i33 %f() "cmse_nonsecure_call"

diff  --git a/llvm/test/CodeGen/Thumb2/thumb2-reduce-size-cpsr.mir b/llvm/test/CodeGen/Thumb2/thumb2-reduce-size-cpsr.mir
new file mode 100644
index 0000000000000..7c159286301e4
--- /dev/null
+++ b/llvm/test/CodeGen/Thumb2/thumb2-reduce-size-cpsr.mir
@@ -0,0 +1,122 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -mtriple=thumbv7-apple-darwin -mcpu=cortex-a9 -run-pass=thumb2-reduce-size -o - %s | FileCheck %s
+
+# Thumb2SizeReduction narrows a 32-bit instruction to its flag-setting
+# 16-bit form only when CPSR is dead afterwards. This should be
+# derived from block liveness without depending on a kill flag.
+
+--- |
+  declare void @g()
+  define void @cpsr_dead_no_kill_flag() { ret void }
+  define void @cpsr_use_in_block_no_kill_flag() { ret void }
+  define void @cpsr_live_out_no_kill_flag() { ret void }
+  define i32 @mul_after_call(i32 %a, i32 %b) { ret i32 0 }
+...
+---
+name:            cpsr_dead_no_kill_flag
+tracksRegLiveness: true
+body:             |
+  ; $r2 reads CPSR without a kill flag, but CPSR is not live out, so t2ANDrr
+  ; narrows to the flag-setting tAND from block liveness.
+  bb.0:
+    liveins: $r0, $r1, $r2, $cpsr
+    ; CHECK-LABEL: name: cpsr_dead_no_kill_flag
+    ; CHECK: liveins: $r0, $r1, $r2, $cpsr
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $r2 = t2MOVCCi renamable $r2, 7, 1 /* CC::ne */, $cpsr
+    ; CHECK-NEXT: renamable $r0, dead $cpsr = tAND killed renamable $r0, killed renamable $r1, 14 /* CC::al */, $noreg
+    ; CHECK-NEXT: tBX_RET 14 /* CC::al */, $noreg, implicit $r0, implicit $r2
+    renamable $r2 = t2MOVCCi renamable $r2, 7, 1, $cpsr
+    renamable $r0 = t2ANDrr killed renamable $r0, killed renamable $r1, 14, $noreg, $noreg
+    tBX_RET 14, $noreg, implicit $r0, implicit $r2
+...
+---
+name:            cpsr_use_in_block_no_kill_flag
+tracksRegLiveness: true
+body:             |
+  ; CPSR is live across the AND to an in-block conditional branch (no kill
+  ; flag present), so the reduction is rejected from block liveness.
+  ; CHECK-LABEL: name: cpsr_use_in_block_no_kill_flag
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $r0, $r1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   tCMPi8 renamable $r0, 0, 14 /* CC::al */, $noreg, implicit-def $cpsr
+  ; CHECK-NEXT:   renamable $r0 = t2ANDrr killed renamable $r0, killed renamable $r1, 14 /* CC::al */, $noreg, $noreg
+  ; CHECK-NEXT:   t2Bcc %bb.1, 0 /* CC::eq */, $cpsr
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   liveins: $r0
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   tBX_RET 14 /* CC::al */, $noreg, implicit $r0
+  bb.0:
+    successors: %bb.1
+    liveins: $r0, $r1
+    t2CMPri renamable $r0, 0, 14, $noreg, implicit-def $cpsr
+    renamable $r0 = t2ANDrr killed renamable $r0, killed renamable $r1, 14, $noreg, $noreg
+    t2Bcc %bb.1, 0, $cpsr
+
+  bb.1:
+    liveins: $r0
+    tBX_RET 14, $noreg, implicit $r0
+...
+---
+name:            cpsr_live_out_no_kill_flag
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: cpsr_live_out_no_kill_flag
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $r0, $r1, $r2, $cpsr
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   renamable $r2 = t2MOVCCi renamable $r2, 7, 1 /* CC::ne */, $cpsr
+  ; CHECK-NEXT:   renamable $r0 = t2ANDrr killed renamable $r0, killed renamable $r1, 14 /* CC::al */, $noreg, $noreg
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT:   liveins: $r0, $cpsr
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   t2Bcc %bb.2, 0 /* CC::eq */, $cpsr
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   liveins: $r0
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   tBX_RET 14 /* CC::al */, $noreg, implicit $r0
+  ; CPSR is live into the successor (no kill flag anywhere). Narrowing to the
+  ; flag-setting tAND would clobber it, so the reduction is rejected.
+  bb.0:
+    successors: %bb.1
+    liveins: $r0, $r1, $r2, $cpsr
+    renamable $r2 = t2MOVCCi renamable $r2, 7, 1, $cpsr
+    renamable $r0 = t2ANDrr killed renamable $r0, killed renamable $r1, 14, $noreg, $noreg
+
+  bb.1:
+    liveins: $r0, $cpsr
+    t2Bcc %bb.2, 0, $cpsr
+
+  bb.2:
+    liveins: $r0
+    tBX_RET 14, $noreg, implicit $r0
+...
+---
+name:            mul_after_call
+tracksRegLiveness: true
+body:             |
+  ; A call resets CPSR tracking in the avoidCPSRPartialUpdate heuristic, so a
+  ; partial-flag reduction candidate after a call still narrows to its 16-bit
+  ; flag-setting form: the call is not treated as a preceding CPSR def to depend
+  ; on, even though its regmask clobbers CPSR.
+  bb.0:
+    liveins: $r4, $r5, $lr
+    ; CHECK-LABEL: name: mul_after_call
+    ; CHECK: liveins: $r4, $r5, $lr
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: tBL 14 /* CC::al */, $noreg, @g, csr_aapcs, implicit-def dead $lr, implicit $sp, implicit-def $sp
+    ; CHECK-NEXT: renamable $r4, dead $cpsr = tMUL killed renamable $r5, killed renamable $r4, 14 /* CC::al */, $noreg
+    ; CHECK-NEXT: $r0 = COPY killed $r4
+    ; CHECK-NEXT: tBX_RET 14 /* CC::al */, $noreg, implicit $r0
+    tBL 14, $noreg, @g, csr_aapcs, implicit-def dead $lr, implicit $sp, implicit-def $sp
+    renamable $r4 = t2MUL killed renamable $r4, killed renamable $r5, 14, $noreg
+    $r0 = COPY killed $r4
+    tBX_RET 14, $noreg, implicit $r0
+...


        


More information about the llvm-commits mailing list