[llvm] [ARM] Fix outlining suitability of tBLXr instructions (PR #206104)
Victor Campos via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 26 08:10:17 PDT 2026
https://github.com/vhscampos updated https://github.com/llvm/llvm-project/pull/206104
>From 0b695ace9eb6bf902d5938d46cb88a2a45ccacac Mon Sep 17 00:00:00 2001
From: Victor Campos <victor.campos at arm.com>
Date: Fri, 26 Jun 2026 14:14:27 +0100
Subject: [PATCH 1/2] [ARM] Fix outlining suitability of tBLXr instructions
In the machine outliner logic for ARM, call instructions that terminate
a code sequence are only outlinable if they can be transformed into a
tail call.
Before this patch, tBLXr (Thumb branch-link-and-exchange with a register
operand) was unconditionally considered ok for outlining. However, this
is false in the case where the operand register is not compatible with
tail calls. Therefore, the instruction in question would be marked as
suitable, but would not be changed into a tail call. The resulting
outlined function might end up wrong if the callee accesses incoming
stack lots, for example if there were arguments passed in the stack.
Assisted-by: codex. It was used to help me create the tests.
---
llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp | 28 +++---
.../ARM/machine-outliner-thunk-tcgpr.mir | 95 +++++++++++++++++--
2 files changed, 103 insertions(+), 20 deletions(-)
diff --git a/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp b/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
index 01a1b4288d569..8ef34c447ee2a 100644
--- a/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
+++ b/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
@@ -5675,6 +5675,7 @@ static bool isLRAvailable(const TargetRegisterInfo &TRI,
const MachineInstr &MI = *I;
// Check defs of LR.
+
if (MI.modifiesRegister(ARM::LR, &TRI))
Live = false;
@@ -5694,6 +5695,19 @@ static bool isLRAvailable(const TargetRegisterInfo &TRI,
return !Live;
}
+/// Return true if \p MI is a call instruction that the outliner can rewrite as
+/// a tail call.
+///
+/// Register-indirect Thumb BLX instructions are only eligible when their target
+/// register is in the tail-call GPR register class.
+static bool CanTransformInstrIntoTailCall(const MachineInstr &MI) {
+ auto Opcode = MI.getOpcode();
+ return (Opcode == ARM::BL || Opcode == ARM::BLX || Opcode == ARM::BLX_noip ||
+ Opcode == ARM::tBL || Opcode == ARM::tBLXi ||
+ ((Opcode == ARM::tBLXr || Opcode == ARM::tBLXr_noip) &&
+ ARM::tcGPRRegClass.contains(MI.getOperand(2).getReg())));
+}
+
std::optional<std::unique_ptr<outliner::OutlinedFunction>>
ARMBaseInstrInfo::getOutliningCandidateInfo(
const MachineModuleInfo &MMI,
@@ -5788,8 +5802,6 @@ ARMBaseInstrInfo::getOutliningCandidateInfo(
// At this point, we have only "safe" candidates to outline. Figure out
// frame + call instruction information.
- unsigned LastInstrOpcode = RepeatedSequenceLocs[0].back().getOpcode();
-
// Helper lambda which sets call information for every candidate.
auto SetCandidateCallInfo =
[&RepeatedSequenceLocs](unsigned CallID, unsigned NumBytesForCall) {
@@ -5825,13 +5837,7 @@ ARMBaseInstrInfo::getOutliningCandidateInfo(
FrameID = MachineOutlinerTailCall;
NumBytesToCreateFrame = Costs.FrameTailCall;
SetCandidateCallInfo(MachineOutlinerTailCall, Costs.CallTailCall);
- } else if (LastInstrOpcode == ARM::BL || LastInstrOpcode == ARM::BLX ||
- LastInstrOpcode == ARM::BLX_noip || LastInstrOpcode == ARM::tBL ||
- LastInstrOpcode == ARM::tBLXi ||
- ((LastInstrOpcode == ARM::tBLXr ||
- LastInstrOpcode == ARM::tBLXr_noip) &&
- ARM::tcGPRRegClass.contains(
- RepeatedSequenceLocs[0].back().getOperand(2).getReg()))) {
+ } else if (CanTransformInstrIntoTailCall(RepeatedSequenceLocs[0].back())) {
FrameID = MachineOutlinerThunk;
NumBytesToCreateFrame = Costs.FrameThunk;
SetCandidateCallInfo(MachineOutlinerThunk, Costs.CallThunk);
@@ -6171,9 +6177,7 @@ ARMBaseInstrInfo::getOutliningTypeImpl(const MachineModuleInfo &MMI,
// as a tail-call. Explicitly list the call instructions we know about so
// we don't get unexpected results with call pseudo-instructions.
auto UnknownCallOutlineType = outliner::InstrType::Illegal;
- if (Opc == ARM::BL || Opc == ARM::tBL || Opc == ARM::BLX ||
- Opc == ARM::BLX_noip || Opc == ARM::tBLXr || Opc == ARM::tBLXr_noip ||
- Opc == ARM::tBLXi)
+ if (CanTransformInstrIntoTailCall(MI))
UnknownCallOutlineType = outliner::InstrType::LegalTerminator;
if (!Callee)
diff --git a/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.mir b/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.mir
index b2638a7ff300d..18037bf0fa5fa 100644
--- a/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.mir
+++ b/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.mir
@@ -1,5 +1,4 @@
# RUN: llc -run-pass=machine-outliner -verify-machineinstrs %s -o - | FileCheck %s
-# CHECK-NOT: tTAILJMPr {{.*}}$r4
--- |
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
@@ -7,6 +6,8 @@
define void @test1() #0 { ret void }
define void @test2() #0 { ret void }
+ define void @test3() #0 { ret void }
+ define void @test4() #0 { ret void }
attributes #0 = { minsize }
...
@@ -14,13 +15,23 @@
name: test1
tracksRegLiveness: true
body: |
+ ; CHECK-LABEL: name: test1
+ ; CHECK: tBL 14 /* CC::al */, $noreg, @[[R4_OUTLINED:OUTLINED_FUNCTION_[0-9]+]]
+ ; CHECK-NEXT: tBLXr 14 /* CC::al */, $noreg, $r4
+ ; CHECK: tBL 14 /* CC::al */, $noreg, @[[R4_OUTLINED]]
+ ; CHECK-NEXT: tBLXr 14 /* CC::al */, $noreg, $r4
bb.0:
- liveins: $r4, $lr
+ liveins: $r4, $r9, $r10, $lr
+
$r0, dead $cpsr = tMOVi8 0, 14, $noreg
- $r1, dead $cpsr = tMOVi8 0, 14, $noreg
+ $r2 = t2MVNi 99, 14, $noreg, $noreg
+ $r3 = t2MOVi -1, 14, $noreg, $noreg
+ t2STRDi8 $r10, $r9, $sp, 8, 14, $noreg
tBLXr 14, $noreg, $r4, implicit-def dead $lr, implicit $sp
$r0, dead $cpsr = tMOVi8 0, 14, $noreg
- $r1, dead $cpsr = tMOVi8 0, 14, $noreg
+ $r2 = t2MVNi 99, 14, $noreg, $noreg
+ $r3 = t2MOVi -1, 14, $noreg, $noreg
+ t2STRDi8 $r10, $r9, $sp, 8, 14, $noreg
tBLXr 14, $noreg, $r4, implicit-def dead $lr, implicit $sp
tBX_RET 14, $noreg
...
@@ -28,13 +39,81 @@ body: |
name: test2
tracksRegLiveness: true
body: |
+ ; CHECK-LABEL: name: test2
+ ; CHECK: tBL 14 /* CC::al */, $noreg, @[[R4_OUTLINED]]
+ ; CHECK-NEXT: tBLXr 14 /* CC::al */, $noreg, $r4
+ ; CHECK: tBL 14 /* CC::al */, $noreg, @[[R4_OUTLINED]]
+ ; CHECK-NEXT: tBLXr 14 /* CC::al */, $noreg, $r4
bb.0:
- liveins: $r4, $lr
+ liveins: $r4, $r9, $r10, $lr
+
$r0, dead $cpsr = tMOVi8 0, 14, $noreg
- $r1, dead $cpsr = tMOVi8 0, 14, $noreg
+ $r2 = t2MVNi 99, 14, $noreg, $noreg
+ $r3 = t2MOVi -1, 14, $noreg, $noreg
+ t2STRDi8 $r10, $r9, $sp, 8, 14, $noreg
tBLXr 14, $noreg, $r4, implicit-def dead $lr, implicit $sp
$r0, dead $cpsr = tMOVi8 0, 14, $noreg
- $r1, dead $cpsr = tMOVi8 0, 14, $noreg
+ $r2 = t2MVNi 99, 14, $noreg, $noreg
+ $r3 = t2MOVi -1, 14, $noreg, $noreg
+ t2STRDi8 $r10, $r9, $sp, 8, 14, $noreg
tBLXr 14, $noreg, $r4, implicit-def dead $lr, implicit $sp
tBX_RET 14, $noreg
-...
\ No newline at end of file
+...
+---
+name: test3
+tracksRegLiveness: true
+body: |
+ ; CHECK-LABEL: name: test3
+ ; CHECK: tBL 14 /* CC::al */, $noreg, @[[R0_OUTLINED:OUTLINED_FUNCTION_[0-9]+]]
+ ; CHECK-NEXT: tBL 14 /* CC::al */, $noreg, @[[R0_OUTLINED]]
+ bb.0:
+ liveins: $r0, $r8, $r11, $lr
+
+ $r1, dead $cpsr = tMOVi8 13, 14, $noreg
+ $r2 = t2MVNi 88, 14, $noreg, $noreg
+ $r3 = t2MOVi -2, 14, $noreg, $noreg
+ t2STRDi8 $r11, $r8, $sp, 12, 14, $noreg
+ tBLXr 14, $noreg, $r0, implicit-def dead $lr, implicit $sp
+ $r1, dead $cpsr = tMOVi8 13, 14, $noreg
+ $r2 = t2MVNi 88, 14, $noreg, $noreg
+ $r3 = t2MOVi -2, 14, $noreg, $noreg
+ t2STRDi8 $r11, $r8, $sp, 12, 14, $noreg
+ tBLXr 14, $noreg, $r0, implicit-def dead $lr, implicit $sp
+ tBX_RET 14, $noreg
+...
+---
+name: test4
+tracksRegLiveness: true
+body: |
+ ; CHECK-LABEL: name: test4
+ ; CHECK: tBL 14 /* CC::al */, $noreg, @[[R0_OUTLINED]]
+ ; CHECK-NEXT: tBL 14 /* CC::al */, $noreg, @[[R0_OUTLINED]]
+ bb.0:
+ liveins: $r0, $r8, $r11, $lr
+
+ $r1, dead $cpsr = tMOVi8 13, 14, $noreg
+ $r2 = t2MVNi 88, 14, $noreg, $noreg
+ $r3 = t2MOVi -2, 14, $noreg, $noreg
+ t2STRDi8 $r11, $r8, $sp, 12, 14, $noreg
+ tBLXr 14, $noreg, $r0, implicit-def dead $lr, implicit $sp
+ $r1, dead $cpsr = tMOVi8 13, 14, $noreg
+ $r2 = t2MVNi 88, 14, $noreg, $noreg
+ $r3 = t2MOVi -2, 14, $noreg, $noreg
+ t2STRDi8 $r11, $r8, $sp, 12, 14, $noreg
+ tBLXr 14, $noreg, $r0, implicit-def dead $lr, implicit $sp
+ tBX_RET 14, $noreg
+...
+
+# CHECK-LABEL: name: OUTLINED_FUNCTION_0
+# CHECK: $r1, dead $cpsr = tMOVi8 13
+# CHECK-NEXT: $r2 = t2MVNi 88
+# CHECK-NEXT: $r3 = t2MOVi -2
+# CHECK-NEXT: t2STRDi8 $r11, $r8, $sp, 12
+# CHECK-NEXT: tTAILJMPr $r0
+
+# CHECK-LABEL: name: OUTLINED_FUNCTION_1
+# CHECK: $r2 = t2MVNi 99
+# CHECK-NEXT: $r3 = t2MOVi -1
+# CHECK-NEXT: t2STRDi8 $r10, $r9, $sp, 8
+# CHECK-NOT: tBLXr
+# CHECK: tBX_RET
>From e6b5c5b93ac5bc2db9adf28d0e09b6ae9c44d994 Mon Sep 17 00:00:00 2001
From: Victor Campos <victor.campos at arm.com>
Date: Fri, 26 Jun 2026 16:09:52 +0100
Subject: [PATCH 2/2] Remove rogue newline
---
llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp b/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
index 8ef34c447ee2a..e0ed4253f5490 100644
--- a/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
+++ b/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
@@ -5675,7 +5675,6 @@ static bool isLRAvailable(const TargetRegisterInfo &TRI,
const MachineInstr &MI = *I;
// Check defs of LR.
-
if (MI.modifiesRegister(ARM::LR, &TRI))
Live = false;
More information about the llvm-commits
mailing list