[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