[llvm] 05f9c66 - [AArch64] Normalize (bool CC 1) to (bool NewCC 0) in LowerBR_CC (#189380)

via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 7 13:03:15 PDT 2026


Author: Valeriy Savchenko
Date: 2026-04-07T21:03:10+01:00
New Revision: 05f9c66c38c646b4795268a9c6c3aa479c793a8c

URL: https://github.com/llvm/llvm-project/commit/05f9c66c38c646b4795268a9c6c3aa479c793a8c
DIFF: https://github.com/llvm/llvm-project/commit/05f9c66c38c646b4795268a9c6c3aa479c793a8c.diff

LOG: [AArch64] Normalize (bool CC 1) to (bool NewCC 0) in LowerBR_CC (#189380)

Added: 
    llvm/test/CodeGen/AArch64/branch-on-bool.ll

Modified: 
    llvm/lib/Target/AArch64/AArch64ISelLowering.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 3a782fe31a590..165541ba089c8 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -11710,9 +11710,50 @@ SDValue AArch64TargetLowering::LowerBR_CC(SDValue Op, SelectionDAG &DAG) const {
     assert((LHS.getValueType() == RHS.getValueType()) &&
            (LHS.getValueType() == MVT::i32 || LHS.getValueType() == MVT::i64));
 
+    // Normalize (LHS CC 1) -> (LHS NewCC 0) when LHS is known to be 0 or 1.
+    // This enables the CBZ/CBNZ matching below.
+    const ConstantSDNode *RHSC = dyn_cast<ConstantSDNode>(RHS);
+    if (RHSC && RHSC->getZExtValue() == 1 && ProduceNonFlagSettingCondBr &&
+        // Don't do this when LHS is an overflow/carry result (resNo == 1)
+        // because we can fold cset + cmp #1 + b.cc into a direct
+        // flag-consuming branch, which CBZ/CBNZ would prevent.
+        LHS.getResNo() == 0 &&
+        // This is true only when we somehow know that it's either 0 or 1.
+        DAG.computeKnownBits(LHS).getMaxValue().ule(1)) {
+      // Output params unused; we only care whether it returns true.
+      bool CanNegate, MustBeFirst, PreferFirst;
+      // Also skip when LHS is a conjunction tree (AND/OR of SETCCs) --
+      // emitConjunction will lower it as a CCMP chain, which is better
+      // than materializing the boolean for CBZ.
+      if (!canEmitConjunction(DAG, LHS, CanNegate, MustBeFirst, PreferFirst,
+                              false)) {
+        ISD::CondCode NewCC = ISD::SETCC_INVALID;
+        switch (CC) {
+        // SETLT/SETGE are canonicalized away before reaching here, but
+        // handle them defensively.
+        case ISD::SETNE:
+        case ISD::SETULT:
+        case ISD::SETLT:
+          NewCC = ISD::SETEQ;
+          break;
+        case ISD::SETEQ:
+        case ISD::SETUGE:
+        case ISD::SETGE:
+          NewCC = ISD::SETNE;
+          break;
+        default:
+          break;
+        }
+        if (NewCC != ISD::SETCC_INVALID) {
+          CC = NewCC;
+          RHS = DAG.getConstant(0, DL, LHS.getValueType());
+          RHSC = cast<ConstantSDNode>(RHS);
+        }
+      }
+    }
+
     // If the RHS of the comparison is zero, we can potentially fold this
     // to a specialized branch.
-    const ConstantSDNode *RHSC = dyn_cast<ConstantSDNode>(RHS);
     if (RHSC && RHSC->getZExtValue() == 0 && ProduceNonFlagSettingCondBr) {
       if (CC == ISD::SETEQ) {
         if (SDValue Result =

diff  --git a/llvm/test/CodeGen/AArch64/branch-on-bool.ll b/llvm/test/CodeGen/AArch64/branch-on-bool.ll
new file mode 100644
index 0000000000000..8bdb7c6754395
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/branch-on-bool.ll
@@ -0,0 +1,202 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -mtriple=aarch64 < %s | FileCheck %s
+
+define void @bool_ne_1(ptr %p) {
+; CHECK-LABEL: bool_ne_1:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    ldrb w8, [x0]
+; CHECK-NEXT:    cbnz w8, .LBB0_2
+; CHECK-NEXT:  // %bb.1: // %if.else
+; CHECK-NEXT:    b bar
+; CHECK-NEXT:  .LBB0_2: // %if.then
+; CHECK-NEXT:    b foo
+  %v = load i8, ptr %p, align 1, !range !0, !noundef !1
+  %cmp = icmp ne i8 %v, 1
+  br i1 %cmp, label %if.else, label %if.then
+
+if.then:
+  tail call void @foo()
+  ret void
+
+if.else:
+  tail call void @bar()
+  ret void
+}
+
+define void @bool_ne_1_with_memcpy(ptr %this, ptr %v) {
+; CHECK-LABEL: bool_ne_1_with_memcpy:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    ldrb w8, [x0, #40]
+; CHECK-NEXT:    ldr q0, [x1]
+; CHECK-NEXT:    str q0, [x0]
+; CHECK-NEXT:    cbz w8, .LBB1_2
+; CHECK-NEXT:  // %bb.1: // %if.then
+; CHECK-NEXT:    b foo
+; CHECK-NEXT:  .LBB1_2: // %if.else
+; CHECK-NEXT:    b bar
+  %engaged = getelementptr inbounds nuw i8, ptr %this, i64 40
+  %val = load i8, ptr %engaged, align 8, !range !0, !noundef !1
+  %loadedv = trunc nuw i8 %val to i1
+  tail call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(40) %this, ptr noundef nonnull align 8 dereferenceable(40) %v, i64 16, i1 false)
+  br i1 %loadedv, label %if.then, label %if.else
+
+if.then:
+  tail call void @foo()
+  ret void
+
+if.else:
+  tail call void @bar()
+  ret void
+}
+
+define void @bool_eq_1(ptr %p) {
+; CHECK-LABEL: bool_eq_1:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    ldrb w8, [x0]
+; CHECK-NEXT:    cbz w8, .LBB2_2
+; CHECK-NEXT:  // %bb.1: // %if.then
+; CHECK-NEXT:    b foo
+; CHECK-NEXT:  .LBB2_2: // %if.else
+; CHECK-NEXT:    b bar
+  %v = load i8, ptr %p, align 1, !range !0, !noundef !1
+  %cmp = icmp eq i8 %v, 1
+  br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+  tail call void @foo()
+  ret void
+
+if.else:
+  tail call void @bar()
+  ret void
+}
+
+define void @bool_ne_1_i32(ptr %p) {
+; CHECK-LABEL: bool_ne_1_i32:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    ldr w8, [x0]
+; CHECK-NEXT:    cbnz w8, .LBB3_2
+; CHECK-NEXT:  // %bb.1: // %if.else
+; CHECK-NEXT:    b bar
+; CHECK-NEXT:  .LBB3_2: // %if.then
+; CHECK-NEXT:    b foo
+  %v = load i32, ptr %p, align 4, !range !2, !noundef !1
+  %cmp = icmp ne i32 %v, 1
+  br i1 %cmp, label %if.else, label %if.then
+
+if.then:
+  tail call void @foo()
+  ret void
+
+if.else:
+  tail call void @bar()
+  ret void
+}
+
+define void @bool_eq_1_i32(ptr %p) {
+; CHECK-LABEL: bool_eq_1_i32:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    ldr w8, [x0]
+; CHECK-NEXT:    cbz w8, .LBB4_2
+; CHECK-NEXT:  // %bb.1: // %if.then
+; CHECK-NEXT:    b foo
+; CHECK-NEXT:  .LBB4_2: // %if.else
+; CHECK-NEXT:    b bar
+  %v = load i32, ptr %p, align 4, !range !2, !noundef !1
+  %cmp = icmp eq i32 %v, 1
+  br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+  tail call void @foo()
+  ret void
+
+if.else:
+  tail call void @bar()
+  ret void
+}
+
+; Negative test: value not in [0, 1]
+define void @not_bool_ne_1(ptr %p) {
+; CHECK-LABEL: not_bool_ne_1:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    ldrb w8, [x0]
+; CHECK-NEXT:    cmp w8, #1
+; CHECK-NEXT:    b.eq .LBB5_2
+; CHECK-NEXT:  // %bb.1: // %if.then
+; CHECK-NEXT:    b foo
+; CHECK-NEXT:  .LBB5_2: // %if.else
+; CHECK-NEXT:    b bar
+  %v = load i8, ptr %p, align 1
+  %cmp = icmp ne i8 %v, 1
+  br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+  tail call void @foo()
+  ret void
+
+if.else:
+  tail call void @bar()
+  ret void
+}
+
+; Negative test: conjunction tree should use CCMP chain, not CBZ.
+define void @conjunction_ccmp(ptr %x_ptr, ptr %y_ptr, ptr %z_ptr) {
+; CHECK-LABEL: conjunction_ccmp:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    ldrh w8, [x1]
+; CHECK-NEXT:    ldr w9, [x0]
+; CHECK-NEXT:    cmp w8, #10
+; CHECK-NEXT:    ccmp w9, #0, #0, ne
+; CHECK-NEXT:    ccmp w8, #14, #4, ne
+; CHECK-NEXT:    b.eq .LBB6_2
+; CHECK-NEXT:  // %bb.1: // %if.then
+; CHECK-NEXT:    str wzr, [x2]
+; CHECK-NEXT:  .LBB6_2: // %common.ret
+; CHECK-NEXT:    ret
+  %x = load i32, ptr %x_ptr, align 4
+  %cmp1 = icmp ne i32 %x, 0
+  %y = load i16, ptr %y_ptr, align 2
+  %cmp2 = icmp eq i16 %y, 10
+  %or1 = select i1 %cmp1, i1 true, i1 %cmp2
+  %cmp3 = icmp ne i16 %y, 14
+  %or2 = select i1 %or1, i1 %cmp3, i1 false
+  br i1 %or2, label %if.then, label %if.else
+
+if.then:
+  store i32 0, ptr %z_ptr, align 8
+  ret void
+
+if.else:
+  ret void
+}
+
+; Negative test: overflow result should use direct flag-consuming branch
+define void @overflow_bool(i32 %a, i32 %b) {
+; CHECK-LABEL: overflow_bool:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmn w0, w1
+; CHECK-NEXT:    b.vc .LBB7_2
+; CHECK-NEXT:  // %bb.1: // %if.then
+; CHECK-NEXT:    b foo
+; CHECK-NEXT:  .LBB7_2: // %if.else
+; CHECK-NEXT:    b bar
+  %res = call {i32, i1} @llvm.sadd.with.overflow.i32(i32 %a, i32 %b)
+  %ov = extractvalue {i32, i1} %res, 1
+  br i1 %ov, label %if.then, label %if.else
+
+if.then:
+  tail call void @foo()
+  ret void
+
+if.else:
+  tail call void @bar()
+  ret void
+}
+
+declare void @foo()
+declare void @bar()
+declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1)
+
+!0 = !{i8 0, i8 2}
+!1 = !{}
+!2 = !{i32 0, i32 2}


        


More information about the llvm-commits mailing list