[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