[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