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 <[email protected]> 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: } } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
