[llvm] [ARM] Fix Machine Outliner crash when tBLXr uses non-tcGPR register (PR #200684)
Deepak Shirke via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 1 11:54:17 PDT 2026
https://github.com/deepakshirkem updated https://github.com/llvm/llvm-project/pull/200684
>From cdc9226156f6d751bafaa107cf73e8c3ec16ca84 Mon Sep 17 00:00:00 2001
From: deepakshirkem <deepakshirke509 at gmail.com>
Date: Mon, 1 Jun 2026 00:46:41 +0530
Subject: [PATCH 1/3] [ARM] Fix Machine Outliner crash when tBLXr uses
non-tcGPR register on Thumb
When the Machine Outliner selects MachineOutlinerThunk mode for a
sequence ending in tBLXr/tBLXr_noip, it converts the indirect call
to tTAILJMPr in buildOutlinedFrame. However tTAILJMPr requires its
operand to be in tcGPR (R0-R3, R12), while tBLXr accepts any GPR.
If the register is callee-saved (e.g. r4), the Machine Verifier
crashes with 'Illegal physical register for instruction'.
Fixes https://github.com/llvm/llvm-project/issues/188076
---
llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp | 8 +++--
.../ARM/machine-outliner-thunk-tcgpr.ll | 33 +++++++++++++++++++
2 files changed, 38 insertions(+), 3 deletions(-)
create mode 100644 llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.ll
diff --git a/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp b/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
index 3b1b8673e56a0..01a1b4288d569 100644
--- a/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
+++ b/llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
@@ -5827,9 +5827,11 @@ ARMBaseInstrInfo::getOutliningCandidateInfo(
SetCandidateCallInfo(MachineOutlinerTailCall, Costs.CallTailCall);
} else if (LastInstrOpcode == ARM::BL || LastInstrOpcode == ARM::BLX ||
LastInstrOpcode == ARM::BLX_noip || LastInstrOpcode == ARM::tBL ||
- LastInstrOpcode == ARM::tBLXr ||
- LastInstrOpcode == ARM::tBLXr_noip ||
- LastInstrOpcode == ARM::tBLXi) {
+ LastInstrOpcode == ARM::tBLXi ||
+ ((LastInstrOpcode == ARM::tBLXr ||
+ LastInstrOpcode == ARM::tBLXr_noip) &&
+ ARM::tcGPRRegClass.contains(
+ RepeatedSequenceLocs[0].back().getOperand(2).getReg()))) {
FrameID = MachineOutlinerThunk;
NumBytesToCreateFrame = Costs.FrameThunk;
SetCandidateCallInfo(MachineOutlinerThunk, Costs.CallThunk);
diff --git a/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.ll b/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.ll
new file mode 100644
index 0000000000000..830d1599153ca
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.ll
@@ -0,0 +1,33 @@
+; RUN: llc -enable-machine-outliner -verify-machineinstrs \
+; RUN: -mtriple=thumbv7m-unknown-none-eabihf < %s | FileCheck %s
+; RUN: llc -enable-machine-outliner -verify-machineinstrs \
+; RUN: -mtriple=thumbv7m-unknown-none-eabihf \
+; RUN: --stop-after=machine-outliner < %s | FileCheck %s
+
+; Verify that the Machine Outliner does not emit tTAILJMPr with a non-tcGPR register on Thumb.
+
+; CHECK-NOT: tTAILJMPr {{.*}}$r4
+
+target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
+target triple = "thumbv7m-unknown-none-eabihf"
+
+define i32 @__f_vfprintf(i1 %or.cond1640, i1 %tobool326.not) #0 {
+entry:
+ %0 = load ptr, ptr null, align 4
+ br i1 %or.cond1640, label %if.then220, label %while.cond1098
+if.then220:
+ br i1 %tobool326.not, label %if.else391, label %if.then327
+if.then327:
+ %conv332 = select i1 false, i32 0, i32 0
+ %call345 = call i32 %0(i8 0, ptr null)
+ %call1217 = call i32 %0(i8 0, ptr null)
+ ret i32 %call1217
+if.else391:
+ %call491 = call i32 %0(i8 0, ptr null)
+ unreachable
+while.cond1098:
+ %call1104 = call i32 %0(i8 0, ptr null)
+ br label %while.cond1098
+}
+
+attributes #0 = { minsize }
\ No newline at end of file
>From 41e9c5aee676a51809f2ebff35f8badb3361e60e Mon Sep 17 00:00:00 2001
From: deepakshirkem <deepakshirke509 at gmail.com>
Date: Tue, 2 Jun 2026 00:02:13 +0530
Subject: [PATCH 2/3] Replace .ll test with .mir for precise register control
---
.../ARM/machine-outliner-thunk-tcgpr.ll | 33 ---------------
.../ARM/machine-outliner-thunk-tcgpr.mir | 40 +++++++++++++++++++
2 files changed, 40 insertions(+), 33 deletions(-)
delete mode 100644 llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.ll
create mode 100644 llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.mir
diff --git a/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.ll b/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.ll
deleted file mode 100644
index 830d1599153ca..0000000000000
--- a/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.ll
+++ /dev/null
@@ -1,33 +0,0 @@
-; RUN: llc -enable-machine-outliner -verify-machineinstrs \
-; RUN: -mtriple=thumbv7m-unknown-none-eabihf < %s | FileCheck %s
-; RUN: llc -enable-machine-outliner -verify-machineinstrs \
-; RUN: -mtriple=thumbv7m-unknown-none-eabihf \
-; RUN: --stop-after=machine-outliner < %s | FileCheck %s
-
-; Verify that the Machine Outliner does not emit tTAILJMPr with a non-tcGPR register on Thumb.
-
-; CHECK-NOT: tTAILJMPr {{.*}}$r4
-
-target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
-target triple = "thumbv7m-unknown-none-eabihf"
-
-define i32 @__f_vfprintf(i1 %or.cond1640, i1 %tobool326.not) #0 {
-entry:
- %0 = load ptr, ptr null, align 4
- br i1 %or.cond1640, label %if.then220, label %while.cond1098
-if.then220:
- br i1 %tobool326.not, label %if.else391, label %if.then327
-if.then327:
- %conv332 = select i1 false, i32 0, i32 0
- %call345 = call i32 %0(i8 0, ptr null)
- %call1217 = call i32 %0(i8 0, ptr null)
- ret i32 %call1217
-if.else391:
- %call491 = call i32 %0(i8 0, ptr null)
- unreachable
-while.cond1098:
- %call1104 = call i32 %0(i8 0, ptr null)
- br label %while.cond1098
-}
-
-attributes #0 = { minsize }
\ No newline at end of file
diff --git a/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.mir b/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.mir
new file mode 100644
index 0000000000000..b2638a7ff300d
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/machine-outliner-thunk-tcgpr.mir
@@ -0,0 +1,40 @@
+# 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"
+ target triple = "thumbv7m-unknown-none-eabihf"
+
+ define void @test1() #0 { ret void }
+ define void @test2() #0 { ret void }
+
+ attributes #0 = { minsize }
+...
+---
+name: test1
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $r4, $lr
+ $r0, dead $cpsr = tMOVi8 0, 14, $noreg
+ $r1, dead $cpsr = tMOVi8 0, 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
+ tBLXr 14, $noreg, $r4, implicit-def dead $lr, implicit $sp
+ tBX_RET 14, $noreg
+...
+---
+name: test2
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $r4, $lr
+ $r0, dead $cpsr = tMOVi8 0, 14, $noreg
+ $r1, dead $cpsr = tMOVi8 0, 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
+ tBLXr 14, $noreg, $r4, implicit-def dead $lr, implicit $sp
+ tBX_RET 14, $noreg
+...
\ No newline at end of file
>From 1407962e61e8fe04cc49776c44900a2fddd81e68 Mon Sep 17 00:00:00 2001
From: deepakshirkem <deepakshirke509 at gmail.com>
Date: Tue, 2 Jun 2026 00:24:01 +0530
Subject: [PATCH 3/3] Fix tcGPR comment and replace .ll test with .mir
---
llvm/lib/Target/ARM/ARMRegisterInfo.td | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/ARM/ARMRegisterInfo.td b/llvm/lib/Target/ARM/ARMRegisterInfo.td
index de4219577c78c..e8c2b3b822e65 100644
--- a/llvm/lib/Target/ARM/ARMRegisterInfo.td
+++ b/llvm/lib/Target/ARM/ARMRegisterInfo.td
@@ -366,8 +366,9 @@ def hGPR : RegisterClass<"ARM", [i32], 32, (sub GPR, tGPR)> {
let DiagnosticString = "operand must be a register in range [r8, r15]";
}
-// For tail calls, we can't use callee-saved registers, as they are restored
-// to the saved value before the tail call, which would clobber a call address.
+// For tail calls, we can't use callee-saved registers. When the frame
+// is destroyed before the tail call, callee-saved registers are restored
+// to the caller's values, clobbering any call address stored in them.
// Note, getMinimalPhysRegClass(R0) returns tGPR because of the names of
// this class and the preceding one(!) This is what we want.
def tcGPR : RegisterClass<"ARM", [i32], 32, (add R0, R1, R2, R3, R12)> {
More information about the llvm-commits
mailing list