[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