[llvm] [BOLT][AArch64] Support FEAT_CMPBR branch instructions. (PR #174972)

Alexandros Lamprineas via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 8 05:08:41 PST 2026


https://github.com/labrinea created https://github.com/llvm/llvm-project/pull/174972

The Armv9.6-A compare-and-branch instructions use a short range 9-bit immediate value. They do not have a corresponding relocation type in the ABI. That means they can only be used either in compact code model, or in non-relocation mode. With this patch I am adding support for them in BOLT.

>From 9ae12a45f96d2e2fac8439dfb838331492a41336 Mon Sep 17 00:00:00 2001
From: Alexandros Lamprineas <alexandros.lamprineas at arm.com>
Date: Fri, 14 Nov 2025 11:43:47 +0000
Subject: [PATCH] [BOLT][AArch64] Support FEAT_CMPBR branch instructions.

The Armv9.6-A compare-and-branch instructions use a short range 9-bit
immediate value. They do not have a corresponding relocation type in
the ABI. That means they can only be used either in compact code model,
or in non-relocation mode. With this patch I am adding support for
them in BOLT.
---
 .../Target/AArch64/AArch64MCPlusBuilder.cpp   | 155 ++++++++++++++++--
 bolt/test/AArch64/compare-and-branch.S        | 127 ++++++++++++++
 2 files changed, 271 insertions(+), 11 deletions(-)
 create mode 100644 bolt/test/AArch64/compare-and-branch.S

diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index 03fb4ddc2f238..1d9e4cbce118a 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -716,6 +716,51 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
     return Insts;
   }
 
+  bool isCompAndBranch(const MCInst &Inst) const {
+    const unsigned opcode = Inst.getOpcode();
+    switch (opcode) {
+    case AArch64::CBBEQWrr:
+    case AArch64::CBBGEWrr:
+    case AArch64::CBBGTWrr:
+    case AArch64::CBBHIWrr:
+    case AArch64::CBBHSWrr:
+    case AArch64::CBBNEWrr:
+    case AArch64::CBHEQWrr:
+    case AArch64::CBHGEWrr:
+    case AArch64::CBHGTWrr:
+    case AArch64::CBHHIWrr:
+    case AArch64::CBHHSWrr:
+    case AArch64::CBHNEWrr:
+    case AArch64::CBHIWrr:
+    case AArch64::CBHIXrr:
+    case AArch64::CBHSWrr:
+    case AArch64::CBHSXrr:
+    case AArch64::CBNEWrr:
+    case AArch64::CBNEXrr:
+    case AArch64::CBEQWrr:
+    case AArch64::CBEQXrr:
+    case AArch64::CBGEWrr:
+    case AArch64::CBGEXrr:
+    case AArch64::CBGTWrr:
+    case AArch64::CBGTXrr:
+    case AArch64::CBEQWri:
+    case AArch64::CBEQXri:
+    case AArch64::CBGTWri:
+    case AArch64::CBGTXri:
+    case AArch64::CBHIWri:
+    case AArch64::CBHIXri:
+    case AArch64::CBLOWri:
+    case AArch64::CBLOXri:
+    case AArch64::CBLTWri:
+    case AArch64::CBLTXri:
+    case AArch64::CBNEWri:
+    case AArch64::CBNEXri:
+      return true;
+    default:
+      return false;
+    }
+  }
+
   bool isTB(const MCInst &Inst) const {
     return (Inst.getOpcode() == AArch64::TBNZW ||
             Inst.getOpcode() == AArch64::TBNZX ||
@@ -1260,7 +1305,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
       if (isConditionalBranch(Inst) || isADR(Inst) || isADRP(Inst) ||
           isMOVW(Inst))
         OpNum = 1;
-      if (isTB(Inst) || isAddXri(Inst))
+      if (isTB(Inst) || isAddXri(Inst) || isCompAndBranch(Inst))
         OpNum = 2;
     }
 
@@ -1329,7 +1374,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
       ++OI;
     }
 
-    if (isTB(Inst)) {
+    if (isTB(Inst) || isCompAndBranch(Inst)) {
       assert(MCPlus::getNumPrimeOperands(Inst) >= 3 &&
              "Invalid number of operands");
       OI = Inst.begin() + 2;
@@ -1682,6 +1727,42 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
     case AArch64::CBZX:     return AArch64::CBNZX;
     case AArch64::CBNZW:    return AArch64::CBZW;
     case AArch64::CBNZX:    return AArch64::CBZX;
+    case AArch64::CBGTWrr:  return AArch64::CBGEWrr;
+    case AArch64::CBGTXrr:  return AArch64::CBGEXrr;
+    case AArch64::CBGEWrr:  return AArch64::CBGTWrr;
+    case AArch64::CBGEXrr:  return AArch64::CBGTXrr;
+    case AArch64::CBHIWrr:  return AArch64::CBHSWrr;
+    case AArch64::CBHIXrr:  return AArch64::CBHSXrr;
+    case AArch64::CBHSWrr:  return AArch64::CBHIWrr;
+    case AArch64::CBHSXrr:  return AArch64::CBHIXrr;
+    case AArch64::CBEQWrr:  return AArch64::CBNEWrr;
+    case AArch64::CBEQXrr:  return AArch64::CBNEXrr;
+    case AArch64::CBNEWrr:  return AArch64::CBEQWrr;
+    case AArch64::CBNEXrr:  return AArch64::CBEQXrr;
+    case AArch64::CBHGTWrr: return AArch64::CBHGEWrr;
+    case AArch64::CBHGEWrr: return AArch64::CBHGTWrr;
+    case AArch64::CBHHIWrr: return AArch64::CBHHSWrr;
+    case AArch64::CBHHSWrr: return AArch64::CBHHIWrr;
+    case AArch64::CBHEQWrr: return AArch64::CBHNEWrr;
+    case AArch64::CBHNEWrr: return AArch64::CBHEQWrr;
+    case AArch64::CBBGTWrr: return AArch64::CBBGEWrr;
+    case AArch64::CBBGEWrr: return AArch64::CBBGTWrr;
+    case AArch64::CBBHIWrr: return AArch64::CBBHSWrr;
+    case AArch64::CBBHSWrr: return AArch64::CBBHIWrr;
+    case AArch64::CBBEQWrr: return AArch64::CBBNEWrr;
+    case AArch64::CBBNEWrr: return AArch64::CBBEQWrr;
+    case AArch64::CBGTWri:  return AArch64::CBLTWri;
+    case AArch64::CBGTXri:  return AArch64::CBLTXri;
+    case AArch64::CBLTWri:  return AArch64::CBGTWri;
+    case AArch64::CBLTXri:  return AArch64::CBGTXri;
+    case AArch64::CBHIWri:  return AArch64::CBLOWri;
+    case AArch64::CBHIXri:  return AArch64::CBLOXri;
+    case AArch64::CBLOWri:  return AArch64::CBHIWri;
+    case AArch64::CBLOXri:  return AArch64::CBHIXri;
+    case AArch64::CBEQWri:  return AArch64::CBNEWri;
+    case AArch64::CBEQXri:  return AArch64::CBNEXri;
+    case AArch64::CBNEWri:  return AArch64::CBEQWri;
+    case AArch64::CBNEXri:  return AArch64::CBEQXri;
     }
   }
 
@@ -1702,9 +1783,63 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
     }
   }
 
+  bool needsRegSwap(unsigned Opcode) const {
+    switch (Opcode) {
+    default:
+      return false;
+    case AArch64::CBGTWrr:
+    case AArch64::CBGTXrr:
+    case AArch64::CBGEWrr:
+    case AArch64::CBGEXrr:
+    case AArch64::CBHIWrr:
+    case AArch64::CBHIXrr:
+    case AArch64::CBHSWrr:
+    case AArch64::CBHSXrr:
+    case AArch64::CBHGTWrr:
+    case AArch64::CBHGEWrr:
+    case AArch64::CBHHIWrr:
+    case AArch64::CBHHSWrr:
+    case AArch64::CBBGTWrr:
+    case AArch64::CBBGEWrr:
+    case AArch64::CBBHIWrr:
+    case AArch64::CBBHSWrr:
+      return true;
+    }
+  }
+
+  bool needsImmDec(unsigned Opcode) const {
+    switch (Opcode) {
+    default:
+      return false;
+    case AArch64::CBLTWri:
+    case AArch64::CBLTXri:
+    case AArch64::CBLOWri:
+    case AArch64::CBLOXri:
+      return true;
+    }
+  }
+
+  bool needsImmInc(unsigned Opcode) const {
+    switch (Opcode) {
+    default:
+      return false;
+    case AArch64::CBGTWri:
+    case AArch64::CBGTXri:
+    case AArch64::CBHIWri:
+    case AArch64::CBHIXri:
+      return true;
+    }
+  }
+
   void reverseBranchCondition(MCInst &Inst, const MCSymbol *TBB,
                               MCContext *Ctx) const override {
-    if (isTB(Inst) || isCB(Inst)) {
+    if (isTB(Inst) || isCB(Inst) || isCompAndBranch(Inst)) {
+      if (needsRegSwap(Inst.getOpcode()))
+        std::swap(Inst.getOperand(0), Inst.getOperand(1));
+      else if (needsImmDec(Inst.getOpcode()))
+        Inst.getOperand(1).setImm(Inst.getOperand(1).getImm() - 1);
+      else if (needsImmInc(Inst.getOpcode()))
+        Inst.getOperand(1).setImm(Inst.getOperand(1).getImm() + 1);
       Inst.setOpcode(getInvertedBranchOpcode(Inst.getOpcode()));
       assert(Inst.getOpcode() != 0 && "Invalid branch instruction");
     } else if (Inst.getOpcode() == AArch64::Bcc) {
@@ -1721,18 +1856,16 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
   }
 
   int getPCRelEncodingSize(const MCInst &Inst) const override {
+    if (isCompAndBranch(Inst))
+      return 11;
+    if (isTB(Inst))
+      return 16;
+    if (isCB(Inst))
+      return 21;
     switch (Inst.getOpcode()) {
     default:
       llvm_unreachable("Failed to get pcrel encoding size");
       return 0;
-    case AArch64::TBZW:     return 16;
-    case AArch64::TBZX:     return 16;
-    case AArch64::TBNZW:    return 16;
-    case AArch64::TBNZX:    return 16;
-    case AArch64::CBZW:     return 21;
-    case AArch64::CBZX:     return 21;
-    case AArch64::CBNZW:    return 21;
-    case AArch64::CBNZX:    return 21;
     case AArch64::B:        return 28;
     case AArch64::BL:       return 28;
     case AArch64::Bcc:      return 21;
diff --git a/bolt/test/AArch64/compare-and-branch.S b/bolt/test/AArch64/compare-and-branch.S
new file mode 100644
index 0000000000000..374541c9bd6a0
--- /dev/null
+++ b/bolt/test/AArch64/compare-and-branch.S
@@ -0,0 +1,127 @@
+# This test checks that splitting functions which contain short range
+# conditional branches works in compact code model without relying on
+# relocations. Also checks that splitting works in non-relocation mode,
+# in order to test the branch inversion on those instructions.
+
+# REQUIRES: system-linux, asserts
+
+# RUN: %clang %cflags -march=armv9-a+cmpbr -Wl,-q %s -o %t -DRELOCATION_MODE=1
+# RUN: link_fdata --no-lbr %s %t %t.fdata
+# RUN: llvm-bolt %t -o %t.bolt --data %t.fdata -split-functions --compact-code-model
+# RUN: llvm-objdump -r %t | FileCheck %s --check-prefix=CHECK-RELOCS
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s --check-prefix=RELOC-MODE
+
+# RUN: %clang %cflags -march=armv9-a+cmpbr -Wl,-q %s -o %t -DRELOCATION_MODE=0
+# RUN: link_fdata --no-lbr %s %t %t.fdata
+# RUN: llvm-bolt %t -o %t.bolt --data %t.fdata -split-functions
+# RUN: llvm-objdump -r %t | FileCheck %s --check-prefix=CHECK-NO-RELOCS
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s --check-prefix=NON-RELOC-MODE
+
+  .globl  foo
+  .type foo, %function
+foo:
+# FDATA: 1 foo #.entry_foo# 10
+.entry_foo:
+## Test immediate increment when inverting the branch.
+    cbgt x0, #0, .Lcold_foo
+    mov x0, #1
+.Lcold_foo:
+    ret
+
+  .globl  bar
+  .type bar, %function
+bar:
+# FDATA: 1 bar #.entry_bar# 10
+.entry_bar:
+## Test immediate decrement when inverting the branch.
+    cblo x0, #1, .Lcold_bar
+    mov x0, #2
+.Lcold_bar:
+    ret
+
+  .globl  baz
+  .type baz, %function
+baz:
+# FDATA: 1 baz #.entry_baz# 10
+.entry_baz:
+## Test register swap when inverting the branch.
+    cbge x0, x1, .Lcold_baz
+    mov x0, #3
+.Lcold_baz:
+    ret
+
+## Force relocation mode.
+.if RELOCATION_MODE
+.reloc 0, R_AARCH64_NONE
+.endif
+
+
+# CHECK-RELOCS: R_AARCH64_NONE *ABS*
+# CHECK-RELOCS-NOT: R_AARCH64_
+
+
+# CHECK-NO-RELOCS-NOT: R_AARCH64_
+
+
+# RELOC-MODE: Disassembly of section .text:
+
+# RELOC-MODE: <foo>:
+# RELOC-MODE-NEXT:            {{.*}} cbgt x0, #0x0, 0x[[ADDR0:[0-9a-f]+]] <{{.*}}>
+# RELOC-MODE-NEXT:            {{.*}} b              0x[[ADDR1:[0-9a-f]+]] <{{.*}}>
+# RELOC-MODE-NEXT: [[ADDR0]]: {{.*}} b              0x[[ADDR2:[0-9a-f]+]] <{{.*}}>
+
+# RELOC-MODE: <bar>:
+# RELOC-MODE-NEXT:            {{.*}} cblo x0, #0x1, 0x[[ADDR3:[0-9a-f]+]] <{{.*}}>
+# RELOC-MODE-NEXT:            {{.*}} b              0x[[ADDR4:[0-9a-f]+]] <{{.*}}>
+# RELOC-MODE-NEXT: [[ADDR3]]: {{.*}} b              0x[[ADDR5:[0-9a-f]+]] <{{.*}}>
+
+# RELOC-MODE: <baz>:
+# RELOC-MODE-NEXT:            {{.*}} cbge x0, x1, 0x[[ADDR6:[0-9a-f]+]] <{{.*}}>
+# RELOC-MODE-NEXT:            {{.*}} b            0x[[ADDR7:[0-9a-f]+]] <{{.*}}>
+# RELOC-MODE-NEXT: [[ADDR6]]: {{.*}} b            0x[[ADDR8:[0-9a-f]+]] <{{.*}}>
+
+# RELOC-MODE: Disassembly of section .text.cold:
+
+# RELOC-MODE: <foo.cold.0>:
+# RELOC-MODE-NEXT: [[ADDR1]]: {{.*}} mov x0, #0x1 // =1
+# RELOC-MODE-NEXT: [[ADDR2]]: {{.*}} ret
+
+# RELOC-MODE: <bar.cold.0>:
+# RELOC-MODE-NEXT: [[ADDR4]]: {{.*}} mov x0, #0x2 // =2
+# RELOC-MODE-NEXT: [[ADDR5]]: {{.*}} ret
+
+# RELOC-MODE: <baz.cold.0>:
+# RELOC-MODE-NEXT: [[ADDR7]]: {{.*}} mov x0, #0x3 // =3
+# RELOC-MODE-NEXT: [[ADDR8]]: {{.*}} ret
+
+
+# NON-RELOC-MODE: Disassembly of section .text:
+
+# NON-RELOC-MODE: <foo>:
+# NON-RELOC-MODE-NEXT:            {{.*}} cblt x0, #0x1, 0x[[ADDR0:[0-9a-f]+]] <{{.*}}>
+# NON-RELOC-MODE-NEXT:            {{.*}} b              0x[[ADDR1:[0-9a-f]+]] <{{.*}}>
+# NON-RELOC-MODE-NEXT: [[ADDR0]]: {{.*}} b              0x[[ADDR2:[0-9a-f]+]] <{{.*}}>
+
+# NON-RELOC-MODE: <bar>:
+# NON-RELOC-MODE-NEXT:            {{.*}} cbhi x0, #0x0, 0x[[ADDR3:[0-9a-f]+]] <{{.*}}>
+# NON-RELOC-MODE-NEXT:            {{.*}} b              0x[[ADDR4:[0-9a-f]+]] <{{.*}}>
+# NON-RELOC-MODE-NEXT: [[ADDR3]]: {{.*}} b              0x[[ADDR5:[0-9a-f]+]] <{{.*}}>
+
+# NON-RELOC-MODE: <baz>:
+# NON-RELOC-MODE-NEXT:            {{.*}} cbgt x1, x0, 0x[[ADDR6:[0-9a-f]+]] <{{.*}}>
+# NON-RELOC-MODE-NEXT:            {{.*}} b            0x[[ADDR7:[0-9a-f]+]] <{{.*}}>
+# NON-RELOC-MODE-NEXT: [[ADDR6]]: {{.*}} b            0x[[ADDR8:[0-9a-f]+]] <{{.*}}>
+
+# NON-RELOC-MODE: Disassembly of section .bolt.text:
+
+# NON-RELOC-MODE: <foo.cold.0>:
+# NON-RELOC-MODE-NEXT: [[ADDR2]]: {{.*}} mov x0, #0x1 // =1
+# NON-RELOC-MODE-NEXT: [[ADDR1]]: {{.*}} ret
+
+# NON-RELOC-MODE: <bar.cold.0>:
+# NON-RELOC-MODE-NEXT: [[ADDR5]]: {{.*}} mov x0, #0x2 // =2
+# NON-RELOC-MODE-NEXT: [[ADDR4]]: {{.*}} ret
+
+# NON-RELOC-MODE: <baz.cold.0>:
+# NON-RELOC-MODE-NEXT: [[ADDR8]]: {{.*}} mov x0, #0x3 // =3
+# NON-RELOC-MODE-NEXT: [[ADDR7]]: {{.*}} ret



More information about the llvm-commits mailing list