[clang] [CIR] Allow boolean operands in cir.cmp (PR #206846)

Adam Smith via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 29 13:55:24 PDT 2026


https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/206846

>From d82d949c2bad4f896c62e2e687bb8137e37358fe Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 30 Jun 2026 14:53:53 -0700
Subject: [PATCH] [CIR] Allow boolean operands in cir.cmp

A scoped enum with a boolean underlying type (enum class E : bool) is
compared without integral promotion, so its value, represented in CIR as
!cir.bool, reaches cir.cmp directly.  The op's CIR_ComparableType
constraint excluded bool, so module verification fails with "'cir.cmp' op
operand #0 must be comparable type, but got '!cir.bool'" before the
CIR-to-CIR passes.

Add bool to CIR_ComparableType and lower a bool cir.cmp through the same
unsigned icmp the pointer and vptr cases already build, matching classic
CodeGen, which compares these enums as i1.  Plain bool comparisons are
unaffected: they still carry the AST integral promotion and compare as
i32.

Found building GROMACS, where enum class : bool flags are pervasive
(decidegpuusage, mdrun/runner, domdec topology).
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |   5 +-
 .../CIR/Dialect/IR/CIRTypeConstraints.td      |   4 +-
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |  17 +--
 clang/test/CIR/CodeGen/enum-bool.cpp          | 100 +++++++++++++++++-
 clang/test/CIR/IR/cmp.cir                     |  68 ++++++++++++
 5 files changed, 173 insertions(+), 21 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index b0f654a54bacd..d0851cbe98c3b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2424,8 +2424,9 @@ def CIR_CmpOp : CIR_Op<"cmp", [Pure, SameTypeOperands]> {
   let summary = "Compare two values and produce a boolean result";
   let description = [{
     The `cir.cmp` operation compares two operands of the same type and produces
-    a `!cir.bool` result. It supports integral, floating-point, and pointer
-    types.
+    a `!cir.bool` result. It supports integral, boolean, floating-point, and
+    pointer types.  Booleans (including enums with a boolean underlying type)
+    are compared as unsigned integers.
 
     The following comparison predicates are available:
 
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
index 6ab739b1c3035..c8f3eff18ce5c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
@@ -402,8 +402,8 @@ def CIR_AnyScalarType : AnyTypeOf<CIR_ScalarTypes, "cir scalar type"> {
 def CIR_AnyMethodType : CIR_TypeBase<"::cir::MethodType", "method type">;
 
 def CIR_ComparableType
-    : AnyTypeOf<[CIR_AnyIntType, CIR_AnyFloatType, CIR_AnyPtrType,
-                 CIR_AnyComplexType, CIR_AnyVPtrType,
+    : AnyTypeOf<[CIR_AnyIntType, CIR_AnyBoolType, CIR_AnyFloatType,
+                 CIR_AnyPtrType, CIR_AnyComplexType, CIR_AnyVPtrType,
                  CIR_AnyDataMemberType, CIR_AnyMethodType],
                 "comparable type"> {
   let cppFunctionName = "isComparableType";
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 979aed513f595..c7e8f97f3a68f 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -3176,19 +3176,12 @@ mlir::LogicalResult CIRToLLVMCmpOpLowering::matchAndRewrite(
     return mlir::success();
   }
 
-  if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(type)) {
+  if (mlir::isa<cir::BoolType, cir::PointerType, cir::VPtrType>(type)) {
+    // Booleans, including enums with a boolean underlying type, compare as
+    // unsigned integers, as do pointers and !cir.vptr, which lowers to a
+    // pointer.
     mlir::LLVM::ICmpPredicate kind =
-        convertCmpKindToICmpPredicate(cmpOp.getKind(),
-                                      /* isSigned=*/false);
-    rewriter.replaceOpWithNewOp<mlir::LLVM::ICmpOp>(
-        cmpOp, kind, adaptor.getLhs(), adaptor.getRhs());
-    return mlir::success();
-  }
-
-  if (auto vptrTy = mlir::dyn_cast<cir::VPtrType>(type)) {
-    // !cir.vptr is a special case, but it's just a pointer to LLVM.
-    auto kind = convertCmpKindToICmpPredicate(cmpOp.getKind(),
-                                              /* isSigned=*/false);
+        convertCmpKindToICmpPredicate(cmpOp.getKind(), /*isSigned=*/false);
     rewriter.replaceOpWithNewOp<mlir::LLVM::ICmpOp>(
         cmpOp, kind, adaptor.getLhs(), adaptor.getRhs());
     return mlir::success();
diff --git a/clang/test/CIR/CodeGen/enum-bool.cpp b/clang/test/CIR/CodeGen/enum-bool.cpp
index aa6740f6803b4..67fe1396cbd4d 100644
--- a/clang/test/CIR/CodeGen/enum-bool.cpp
+++ b/clang/test/CIR/CodeGen/enum-bool.cpp
@@ -1,9 +1,9 @@
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
 // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
-// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: FileCheck --check-prefixes=LLVM,LLVMCIR --input-file=%t-cir.ll %s
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
-// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+// RUN: FileCheck --check-prefixes=LLVM,OGCG --input-file=%t.ll %s
 
 enum BoolEnum : bool { False, True };
 
@@ -16,7 +16,8 @@ BoolEnum loadEnum(BoolEnum *p) { return *p; }
 // CIR:         %[[V:.*]] = cir.load {{.*}}%[[PV]] : !cir.ptr<!cir.bool>, !cir.bool
 // CIR:         cir.return
 
-// LLVM-LABEL: define dso_local noundef {{(zeroext )?}}i1 @_Z8loadEnumP8BoolEnum(ptr noundef %{{.*}})
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z8loadEnumP8BoolEnum(ptr noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z8loadEnumP8BoolEnum(ptr noundef %{{.*}})
 // LLVM:         load i8, ptr %{{.*}}, align 1
 // LLVM:         ret i1 %{{.*}}
 
@@ -28,7 +29,8 @@ void storeEnum(BoolEnum *p, BoolEnum v) { *p = v; }
 // CIR:         %[[P:.*]] = cir.load deref {{.*}} : !cir.ptr<!cir.ptr<!cir.bool>>, !cir.ptr<!cir.bool>
 // CIR:         cir.store {{.*}}%[[V]], %[[P]] : !cir.bool, !cir.ptr<!cir.bool>
 
-// LLVM-LABEL: define dso_local void @_Z9storeEnumP8BoolEnumS_(ptr noundef %{{.*}}, i1 noundef {{(zeroext )?}}%{{.*}})
+// LLVMCIR-LABEL: define dso_local void @_Z9storeEnumP8BoolEnumS_(ptr noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local void @_Z9storeEnumP8BoolEnumS_(ptr noundef %{{.*}}, i1 noundef zeroext %{{.*}})
 // LLVM:         zext i1 %{{.*}} to i8
 // LLVM:         store i8 %{{.*}}, ptr %{{.*}}, align 1
 // LLVM:         load i8, ptr %{{.*}}, align 1
@@ -42,5 +44,93 @@ bool toBool(BoolEnum e) { return static_cast<bool>(e); }
 // CIR-NOT:     cir.cast int_to_bool
 // CIR:         cir.return
 
-// LLVM-LABEL: define dso_local noundef {{(zeroext )?}}i1 @_Z6toBool8BoolEnum(i1 noundef {{(zeroext )?}}%{{.*}})
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z6toBool8BoolEnum(i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z6toBool8BoolEnum(i1 noundef zeroext %{{.*}})
 // LLVM:         ret i1 %{{.*}}
+
+// An unscoped enum is integer-promoted before the comparison, so cir.cmp sees
+// !s32i operands.
+bool ltUnscopedEnum(BoolEnum a, BoolEnum b) { return a < b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z14ltUnscopedEnum8BoolEnumS_
+// CIR:         %[[A:.*]] = cir.cast bool_to_int %{{.*}} : !cir.bool -> !s32i
+// CIR:         %[[B:.*]] = cir.cast bool_to_int %{{.*}} : !cir.bool -> !s32i
+// CIR:         cir.cmp lt %[[A]], %[[B]] : !s32i
+
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z14ltUnscopedEnum8BoolEnumS_(i1 noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z14ltUnscopedEnum8BoolEnumS_(i1 noundef zeroext %{{.*}}, i1 noundef zeroext %{{.*}})
+// LLVM:         zext i1 %{{.*}} to i32
+// LLVM:         zext i1 %{{.*}} to i32
+// LLVM:         icmp slt i32 %{{.*}}, %{{.*}}
+
+// Plain bool is promoted the same way.
+bool ltPlainBool(bool a, bool b) { return a < b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z11ltPlainBoolbb
+// CIR:         %[[A:.*]] = cir.cast bool_to_int %{{.*}} : !cir.bool -> !s32i
+// CIR:         %[[B:.*]] = cir.cast bool_to_int %{{.*}} : !cir.bool -> !s32i
+// CIR:         cir.cmp lt %[[A]], %[[B]] : !s32i
+
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z11ltPlainBoolbb(i1 noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z11ltPlainBoolbb(i1 noundef zeroext %{{.*}}, i1 noundef zeroext %{{.*}})
+// LLVM:         zext i1 %{{.*}} to i32
+// LLVM:         zext i1 %{{.*}} to i32
+// LLVM:         icmp slt i32 %{{.*}}, %{{.*}}
+
+// A scoped enum with a boolean underlying type is compared directly (no
+// integral promotion), so cir.cmp must accept !cir.bool operands.
+enum class ScopedBoolEnum : bool { No, Yes };
+
+bool eqEnum(ScopedBoolEnum a, ScopedBoolEnum b) { return a == b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z6eqEnum14ScopedBoolEnumS_
+// CIR:         cir.cmp eq %{{.*}}, %{{.*}} : !cir.bool
+
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z6eqEnum14ScopedBoolEnumS_(i1 noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z6eqEnum14ScopedBoolEnumS_(i1 noundef zeroext %{{.*}}, i1 noundef zeroext %{{.*}})
+// LLVM:         icmp eq i1 %{{.*}}, %{{.*}}
+
+bool neEnum(ScopedBoolEnum a, ScopedBoolEnum b) { return a != b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z6neEnum14ScopedBoolEnumS_
+// CIR:         cir.cmp ne %{{.*}}, %{{.*}} : !cir.bool
+
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z6neEnum14ScopedBoolEnumS_(i1 noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z6neEnum14ScopedBoolEnumS_(i1 noundef zeroext %{{.*}}, i1 noundef zeroext %{{.*}})
+// LLVM:         icmp ne i1 %{{.*}}, %{{.*}}
+
+bool ltEnum(ScopedBoolEnum a, ScopedBoolEnum b) { return a < b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z6ltEnum14ScopedBoolEnumS_
+// CIR:         cir.cmp lt %{{.*}}, %{{.*}} : !cir.bool
+
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z6ltEnum14ScopedBoolEnumS_(i1 noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z6ltEnum14ScopedBoolEnumS_(i1 noundef zeroext %{{.*}}, i1 noundef zeroext %{{.*}})
+// LLVM:         icmp ult i1 %{{.*}}, %{{.*}}
+
+bool leEnum(ScopedBoolEnum a, ScopedBoolEnum b) { return a <= b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z6leEnum14ScopedBoolEnumS_
+// CIR:         cir.cmp le %{{.*}}, %{{.*}} : !cir.bool
+
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z6leEnum14ScopedBoolEnumS_(i1 noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z6leEnum14ScopedBoolEnumS_(i1 noundef zeroext %{{.*}}, i1 noundef zeroext %{{.*}})
+// LLVM:         icmp ule i1 %{{.*}}, %{{.*}}
+
+bool gtEnum(ScopedBoolEnum a, ScopedBoolEnum b) { return a > b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z6gtEnum14ScopedBoolEnumS_
+// CIR:         cir.cmp gt %{{.*}}, %{{.*}} : !cir.bool
+
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z6gtEnum14ScopedBoolEnumS_(i1 noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z6gtEnum14ScopedBoolEnumS_(i1 noundef zeroext %{{.*}}, i1 noundef zeroext %{{.*}})
+// LLVM:         icmp ugt i1 %{{.*}}, %{{.*}}
+
+bool geEnum(ScopedBoolEnum a, ScopedBoolEnum b) { return a >= b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z6geEnum14ScopedBoolEnumS_
+// CIR:         cir.cmp ge %{{.*}}, %{{.*}} : !cir.bool
+
+// LLVMCIR-LABEL: define dso_local noundef i1 @_Z6geEnum14ScopedBoolEnumS_(i1 noundef %{{.*}}, i1 noundef %{{.*}})
+// OGCG-LABEL: define dso_local noundef zeroext i1 @_Z6geEnum14ScopedBoolEnumS_(i1 noundef zeroext %{{.*}}, i1 noundef zeroext %{{.*}})
+// LLVM:         icmp uge i1 %{{.*}}, %{{.*}}
diff --git a/clang/test/CIR/IR/cmp.cir b/clang/test/CIR/IR/cmp.cir
index 3f8d189ff1e68..0daa71b36cbcf 100644
--- a/clang/test/CIR/IR/cmp.cir
+++ b/clang/test/CIR/IR/cmp.cir
@@ -356,4 +356,72 @@ module {
   // CHECK-NEXT:   cir.store %32, %2 : !cir.bool, !cir.ptr<!cir.bool>
   // CHECK-NEXT:   cir.return
   // CHECK-NEXT: }
+
+  // A bool that was not integer-promoted, as with a scoped enum whose
+  // underlying type is bool.
+  cir.func @unpromoted_bool_cmp(%arg0: !cir.bool, %arg1: !cir.bool) {
+    %0 = cir.alloca "a" align(1) init : !cir.ptr<!cir.bool>
+    %1 = cir.alloca "b" align(1) init : !cir.ptr<!cir.bool>
+    %2 = cir.alloca "x" align(1) init : !cir.ptr<!cir.bool>
+    cir.store %arg0, %0 : !cir.bool, !cir.ptr<!cir.bool>
+    cir.store %arg1, %1 : !cir.bool, !cir.ptr<!cir.bool>
+    %3 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+    %4 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+    %5 = cir.cmp gt %3, %4 : !cir.bool
+    cir.store %5, %2 : !cir.bool, !cir.ptr<!cir.bool>
+    %6 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+    %7 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+    %8 = cir.cmp lt %6, %7 : !cir.bool
+    cir.store %8, %2 : !cir.bool, !cir.ptr<!cir.bool>
+    %9 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+    %10 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+    %11 = cir.cmp ge %9, %10 : !cir.bool
+    cir.store %11, %2 : !cir.bool, !cir.ptr<!cir.bool>
+    %12 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+    %13 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+    %14 = cir.cmp le %12, %13 : !cir.bool
+    cir.store %14, %2 : !cir.bool, !cir.ptr<!cir.bool>
+    %15 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+    %16 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+    %17 = cir.cmp eq %15, %16 : !cir.bool
+    cir.store %17, %2 : !cir.bool, !cir.ptr<!cir.bool>
+    %18 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+    %19 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+    %20 = cir.cmp ne %18, %19 : !cir.bool
+    cir.store %20, %2 : !cir.bool, !cir.ptr<!cir.bool>
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @unpromoted_bool_cmp(%arg0: !cir.bool, %arg1: !cir.bool) {
+  // CHECK-NEXT:   %0 = cir.alloca "a" align(1) init : !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   %1 = cir.alloca "b" align(1) init : !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   %2 = cir.alloca "x" align(1) init : !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   cir.store %arg0, %0 : !cir.bool, !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   cir.store %arg1, %1 : !cir.bool, !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   %3 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %4 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %5 = cir.cmp gt %3, %4 : !cir.bool
+  // CHECK-NEXT:   cir.store %5, %2 : !cir.bool, !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   %6 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %7 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %8 = cir.cmp lt %6, %7 : !cir.bool
+  // CHECK-NEXT:   cir.store %8, %2 : !cir.bool, !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   %9 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %10 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %11 = cir.cmp ge %9, %10 : !cir.bool
+  // CHECK-NEXT:   cir.store %11, %2 : !cir.bool, !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   %12 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %13 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %14 = cir.cmp le %12, %13 : !cir.bool
+  // CHECK-NEXT:   cir.store %14, %2 : !cir.bool, !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   %15 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %16 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %17 = cir.cmp eq %15, %16 : !cir.bool
+  // CHECK-NEXT:   cir.store %17, %2 : !cir.bool, !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   %18 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %19 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+  // CHECK-NEXT:   %20 = cir.cmp ne %18, %19 : !cir.bool
+  // CHECK-NEXT:   cir.store %20, %2 : !cir.bool, !cir.ptr<!cir.bool>
+  // CHECK-NEXT:   cir.return
+  // CHECK-NEXT: }
 }



More information about the cfe-commits mailing list