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

via cfe-commits cfe-commits at lists.llvm.org
Tue Jun 30 14:56:08 PDT 2026


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

GROMACS uses scoped enums with a boolean underlying type as type-safe
flags (`enum class EmulateGpuNonbonded : bool`), and compares them with
`==` / `!=` / `<`. A scoped enum is not integer-promoted before the
comparison, so the operand reaches `cir.cmp` as a `!cir.bool` (CIR already
represents a boolean-underlying enum with `!cir.bool`). The `cir.cmp`
operand constraint `CIR_ComparableType` does not list bool, so module
verification fails before the CIR-to-CIR passes with:

```
'cir.cmp' op operand #0 must be comparable type, but got '!cir.bool'
```

Add bool to `CIR_ComparableType` and give `CmpOp` lowering a bool branch
that emits an unsigned `icmp` (`ult` for `<`), matching classic CodeGen
(compared as `i1`). The constraint now admits any `cir.cmp` on `!cir.bool`;
in practice that is the scoped `enum class : bool` path above. Plain `bool`
comparisons are unchanged -- they still carry the AST integral promotion
and compare as `i32` -- and unscoped `enum : bool` still promotes too.

Six translation units in a minimal GROMACS build hit this
(`taskassignment/decidegpuusage`, `mdrun/runner`, the three `domdec`
topology files, and a gtest-based test); they compile under `-fclangir`
with the fix, and `enum-bool.cpp` pins the CIR/LLVM parity for scoped
`enum class : bool`.


>From 58ca7bcad539c0367787eb98d864378586602c5a 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 to an unsigned
icmp, 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 | 10 +++++++
 clang/test/CIR/CodeGen/enum-bool.cpp          | 28 +++++++++++++++++++
 4 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 0a54a7edf85fd..4e8fdf76a6966 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2342,8 +2342,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 a8643ec21af80..cf73b89294040 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
@@ -390,8 +390,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 e0fc9e58ed4b7..d7409544bd31a 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2949,6 +2949,16 @@ mlir::LogicalResult CIRToLLVMCmpOpLowering::matchAndRewrite(
     return mlir::success();
   }
 
+  if (mlir::isa<cir::BoolType>(type)) {
+    // Booleans (and enums with a boolean underlying type) compare as
+    // unsigned integers.
+    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 ptrTy = mlir::dyn_cast<cir::PointerType>(type)) {
     mlir::LLVM::ICmpPredicate kind =
         convertCmpKindToICmpPredicate(cmpOp.getKind(),
diff --git a/clang/test/CIR/CodeGen/enum-bool.cpp b/clang/test/CIR/CodeGen/enum-bool.cpp
index c8a7a0c1ebb79..3050d446e9a16 100644
--- a/clang/test/CIR/CodeGen/enum-bool.cpp
+++ b/clang/test/CIR/CodeGen/enum-bool.cpp
@@ -33,3 +33,31 @@ void storeEnum(BoolEnum *p, BoolEnum v) { *p = v; }
 // LLVM:         store i8 %{{.*}}, ptr %{{.*}}, align 1
 // LLVM:         load i8, ptr %{{.*}}, align 1
 // LLVM:         store i8 %{{.*}}, ptr %{{.*}}, align 1
+
+// 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
+
+// LLVM-LABEL: define dso_local noundef {{(zeroext )?}}i1 @_Z6eqEnum14ScopedBoolEnumS_
+// LLVM:         icmp eq i1 %{{.*}}, %{{.*}}
+
+bool neEnum(ScopedBoolEnum a, ScopedBoolEnum b) { return a != b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z6neEnum14ScopedBoolEnumS_
+// CIR:         cir.cmp ne %{{.*}}, %{{.*}} : !cir.bool
+
+// LLVM-LABEL: define dso_local noundef {{(zeroext )?}}i1 @_Z6neEnum14ScopedBoolEnumS_
+// LLVM:         icmp ne i1 %{{.*}}, %{{.*}}
+
+bool ltEnum(ScopedBoolEnum a, ScopedBoolEnum b) { return a < b; }
+
+// CIR-LABEL: cir.func{{.*}} @_Z6ltEnum14ScopedBoolEnumS_
+// CIR:         cir.cmp lt %{{.*}}, %{{.*}} : !cir.bool
+
+// LLVM-LABEL: define dso_local noundef {{(zeroext )?}}i1 @_Z6ltEnum14ScopedBoolEnumS_
+// LLVM:         icmp ult i1 %{{.*}}, %{{.*}}



More information about the cfe-commits mailing list