https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/213072
This adds the #cir.fenv attribute to the cir.cmp and cir.vec.cmp and adds verfiers to enforce that the attribute is only present when the comparison involves floating-point values. The cir.vec.cmp operation has an existing folder which is skipped in this change when the fenv attribute is present. In most circumstance, this folding could still be done, but the implementation is intentionally conservative at this point. Proper checking of the conditions under which folding would be legal will be added later. The cir.cmp operation does not currently have a folder. Assisted-by: Cursor / various models >From 881df16266df440629bbdaede93feb750b2eb52e Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Wed, 29 Jul 2026 15:52:02 -0700 Subject: [PATCH] [CIR] Add fenv attribute to compare operations This adds the #cir.fenv attribute to the cir.cmp and cir.vec.cmp and adds verfiers to enforce that the attribute is only present when the comparison involves floating-point values. The cir.vec.cmp operation has an existing folder which is skipped in this change when the fenv attribute is present. In most circumstance, this folding could still be done, but the implementation is intentionally conservative at this point. Proper checking of the conditions under which folding would be legal will be added later. The cir.cmp operation does not currently have a folder. Assisted-by: Cursor / various models --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 44 +++++++++++++++++-- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 26 +++++++++++ clang/test/CIR/IR/fenv.cir | 21 +++++++++ clang/test/CIR/IR/invalid-cmp.cir | 39 ++++++++++++++++ clang/test/CIR/Transforms/vector-cmp-fold.cir | 31 +++++++++++++ clang/unittests/CIR/FenvOpTest.cpp | 24 +++++++++- 6 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 clang/test/CIR/IR/invalid-cmp.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 40c3542e32172..cd20eca64e700 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -2488,7 +2488,8 @@ def CIR_CmpOpKind : CIR_I32EnumAttr<"CmpOpKind", "compare operation kind", [ I32EnumAttrCase<"uno", 7> ]>; -def CIR_CmpOp : CIR_Op<"cmp", [Pure, SameTypeOperands]> { +def CIR_CmpOp : CIR_Op<"cmp", + !listconcat([SameTypeOperands], CIR_FenvOpTraits)> { 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 @@ -2512,16 +2513,22 @@ def CIR_CmpOp : CIR_Op<"cmp", [Pure, SameTypeOperands]> { The `one` and `uno` predicates are floating-point specific: `one` is ordered not-equal (false for NaN), `uno` tests if either operand is NaN. + The optional `fenv` attribute describes constraints on the floating-point + handling of the operation. It is only valid for floating-point + comparisons. + ``` %0 = cir.cmp gt %1, %2 : !s32i %1 = cir.cmp eq %a, %b : !cir.ptr<!u8i> + %2 = cir.cmp lt %x, %y : !cir.float {fenv = #cir.fenv<>} ``` }]; let arguments = (ins CIR_CmpOpKind:$kind, CIR_ComparableType:$lhs, - CIR_ComparableType:$rhs + CIR_ComparableType:$rhs, + OptionalAttr<CIR_FenvAttr>:$fenv ); let results = (outs CIR_BoolType:$result); @@ -2530,6 +2537,18 @@ def CIR_CmpOp : CIR_Op<"cmp", [Pure, SameTypeOperands]> { $kind $lhs `,` $rhs `:` type($lhs) attr-dict }]; + let builders = [ + OpBuilder<(ins "cir::CmpOpKind":$kind, "mlir::Value":$lhs, + "mlir::Value":$rhs), [{ + build($_builder, $_state, kind, lhs, rhs, cir::FenvAttr{}); + }]>, + OpBuilder<(ins "mlir::Type":$result, "cir::CmpOpKind":$kind, + "mlir::Value":$lhs, "mlir::Value":$rhs), [{ + build($_builder, $_state, result, kind, lhs, rhs, cir::FenvAttr{}); + }]> + ]; + + let hasVerifier = 1; let isLLVMLoweringRecursive = true; let hasCXXABILowering = true; } @@ -5702,7 +5721,8 @@ def CIR_VecExtractOp : CIR_Op<"vec.extract", [ // VecCmpOp //===----------------------------------------------------------------------===// -def CIR_VecCmpOp : CIR_Op<"vec.cmp", [Pure, SameTypeOperands]> { +def CIR_VecCmpOp : CIR_Op<"vec.cmp", + !listconcat([SameTypeOperands], CIR_FenvOpTraits)> { let summary = "Compare two vectors"; let description = [{ The `cir.vec.cmp` operation does an element-wise comparison of two vectors @@ -5710,16 +5730,24 @@ def CIR_VecCmpOp : CIR_Op<"vec.cmp", [Pure, SameTypeOperands]> { whose element type is the signed integral type that is the same size as the element type of the operands. The values in the result are 0 or -1. + The optional `fenv` attribute describes constraints on the floating-point + handling of the operation. It is only valid for floating-point vector + comparisons. + ``` %eq = cir.vec.cmp(eq, %vec_a, %vec_b) : !cir.vector<4 x !s32i>, !cir.vector<4 x !s32i> %lt = cir.vec.cmp(lt, %vec_a, %vec_b) : !cir.vector<4 x !s32i>, !cir.vector<4 x !s32i> + %gt = cir.vec.cmp(gt, %va, %vb) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> { + fenv = #cir.fenv<> + } ``` }]; let arguments = (ins CIR_CmpOpKind:$kind, CIR_VectorType:$lhs, - CIR_VectorType:$rhs + CIR_VectorType:$rhs, + OptionalAttr<CIR_FenvAttr>:$fenv ); let results = (outs CIR_VectorType:$result); @@ -5729,6 +5757,14 @@ def CIR_VecCmpOp : CIR_Op<"vec.cmp", [Pure, SameTypeOperands]> { qualified(type($result)) attr-dict }]; + let builders = [ + OpBuilder<(ins "mlir::Type":$result, "cir::CmpOpKind":$kind, + "mlir::Value":$lhs, "mlir::Value":$rhs), [{ + build($_builder, $_state, result, kind, lhs, rhs, cir::FenvAttr{}); + }]> + ]; + + let hasVerifier = 1; let hasFolder = 1; } diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index c54957a84768a..c8f4e640c3f73 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -3507,11 +3507,37 @@ OpFoldResult cir::VecExtractOp::fold(FoldAdaptor adaptor) { return elements[index]; } +//===----------------------------------------------------------------------===// +// CmpOp +//===----------------------------------------------------------------------===// + +LogicalResult cir::CmpOp::verify() { + if (getFenvAttr() && !cir::isAnyFloatingPointType(getLhs().getType())) + return emitOpError() + << "'fenv' is only valid for floating-point comparisons"; + return success(); +} + //===----------------------------------------------------------------------===// // VecCmpOp //===----------------------------------------------------------------------===// +LogicalResult cir::VecCmpOp::verify() { + if (getFenvAttr() && !cir::isFPOrVectorOfFPType(getLhs().getType())) + return emitOpError() + << "'fenv' is only valid for floating-point comparisons"; + return success(); +} + OpFoldResult cir::VecCmpOp::fold(FoldAdaptor adaptor) { + // Do not fold when fenv is present. + // TODO(cir): This is overly conservative. We could fold comparisons as long + // as we can prove that the operation wouldn't raise exceptions or + // when the fenv attribute does not require strict exception + // semantics. + if (getFenvAttr()) + return {}; + auto lhsVecAttr = mlir::dyn_cast_if_present<cir::ConstVectorAttr>(adaptor.getLhs()); auto rhsVecAttr = diff --git a/clang/test/CIR/IR/fenv.cir b/clang/test/CIR/IR/fenv.cir index 6047ccf1af3c7..5602212ea1de1 100644 --- a/clang/test/CIR/IR/fenv.cir +++ b/clang/test/CIR/IR/fenv.cir @@ -104,3 +104,24 @@ cir.func @cast_fenv(%a: !cir.float, %b: !cir.double, %i: !s32i) { %3 = cir.cast float_to_bool %a : !cir.float -> !cir.bool cir.return } + +// CHECK-LABEL: cir.func @cmp_fenv +cir.func @cmp_fenv(%a: !cir.float, %b: !cir.float, + %va: !cir.vector<4 x !cir.float>, + %vb: !cir.vector<4 x !cir.float>) { + // CHECK: cir.cmp lt %{{.*}}, %{{.*}} : !cir.float {fenv = #cir.fenv<except_mode = unmasked>} + %0 = cir.cmp lt %a, %b : !cir.float {fenv = #cir.fenv<except_mode = unmasked>} + // CHECK: cir.cmp eq %{{.*}}, %{{.*}} : !cir.float {fenv = #cir.fenv<>} + %1 = cir.cmp eq %a, %b : !cir.float {fenv = #cir.fenv<>} + // CHECK: cir.vec.cmp(gt, %{{.*}}, %{{.*}}) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> {fenv = #cir.fenv<strict_except = true>} + %2 = cir.vec.cmp(gt, %va, %vb) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> { + fenv = #cir.fenv<strict_except = true> + } + // CHECK: cir.cmp ne %{{.*}}, %{{.*}} : !cir.float + // CHECK-NOT: fenv + %3 = cir.cmp ne %a, %b : !cir.float + // CHECK: cir.vec.cmp(le, %{{.*}}, %{{.*}}) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> + // CHECK-NOT: fenv + %4 = cir.vec.cmp(le, %va, %vb) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> + cir.return +} diff --git a/clang/test/CIR/IR/invalid-cmp.cir b/clang/test/CIR/IR/invalid-cmp.cir new file mode 100644 index 0000000000000..2b5185f8977eb --- /dev/null +++ b/clang/test/CIR/IR/invalid-cmp.cir @@ -0,0 +1,39 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file + +!s32i = !cir.int<s, 32> + +module { + cir.func @fenv_on_int_cmp(%a: !s32i, %b: !s32i) { + // expected-error@+1 {{'fenv' is only valid for floating-point comparisons}} + %0 = cir.cmp eq %a, %b : !s32i {fenv = #cir.fenv<>} + cir.return + } +} + +// ----- + +!s32i = !cir.int<s, 32> + +module { + cir.func @fenv_on_ptr_cmp(%a: !cir.ptr<!s32i>, %b: !cir.ptr<!s32i>) { + // expected-error@+1 {{'fenv' is only valid for floating-point comparisons}} + %0 = cir.cmp ne %a, %b : !cir.ptr<!s32i> {fenv = #cir.fenv<>} + cir.return + } +} + +// ----- + +!s32i = !cir.int<s, 32> + +module { + cir.func @fenv_on_int_vec_cmp(%a: !cir.vector<4 x !s32i>, + %b: !cir.vector<4 x !s32i>) { + // expected-error@+1 {{'fenv' is only valid for floating-point comparisons}} + %0 = cir.vec.cmp(eq, %a, %b) : !cir.vector<4 x !s32i>, + !cir.vector<4 x !s32i> { + fenv = #cir.fenv<> + } + cir.return + } +} diff --git a/clang/test/CIR/Transforms/vector-cmp-fold.cir b/clang/test/CIR/Transforms/vector-cmp-fold.cir index 9198db396c1e8..4b130a3671a44 100644 --- a/clang/test/CIR/Transforms/vector-cmp-fold.cir +++ b/clang/test/CIR/Transforms/vector-cmp-fold.cir @@ -292,3 +292,34 @@ module { // CHECK-SAME: #cir.int<-1> : !cir.int<s, 1>, #cir.int<-1> : !cir.int<s, 1>]> : !cir.vector<4 x !cir.int<s, 1>> // CHECK-NEXT: cir.return %[[RES]] : !cir.vector<4 x !cir.int<s, 1>> } + +// ----- + +!s32i = !cir.int<s, 32> + +module { + cir.func @no_fold_cmp_vector_with_fenv() -> !cir.vector<4 x !s32i> { + %vec_1 = cir.const #cir.const_vector<[ + #cir.fp<0x7FC00000> : !cir.float, + #cir.fp<2.000000e+00> : !cir.float, + #cir.fp<3.000000e+00> : !cir.float, + #cir.fp<4.000000e+00> : !cir.float + ]> : !cir.vector<4 x !cir.float> + %vec_2 = cir.const #cir.const_vector<[ + #cir.fp<5.000000e+00> : !cir.float, + #cir.fp<6.000000e+00> : !cir.float, + #cir.fp<7.000000e+00> : !cir.float, + #cir.fp<8.000000e+00> : !cir.float + ]> : !cir.vector<4 x !cir.float> + %new_vec = cir.vec.cmp(lt, %vec_1, %vec_2) : + !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> { + fenv = #cir.fenv<strict_except = true> + } + cir.return %new_vec : !cir.vector<4 x !s32i> + } + + // CHECK-LABEL: cir.func{{.*}} @no_fold_cmp_vector_with_fenv + // CHECK: cir.vec.cmp(lt, %{{.*}}, %{{.*}}) : + // CHECK-SAME: !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> + // CHECK-SAME: {fenv = #cir.fenv<strict_except = true>} +} diff --git a/clang/unittests/CIR/FenvOpTest.cpp b/clang/unittests/CIR/FenvOpTest.cpp index 293c94563cbf7..9e989ed1ff8a5 100644 --- a/clang/unittests/CIR/FenvOpTest.cpp +++ b/clang/unittests/CIR/FenvOpTest.cpp @@ -64,7 +64,9 @@ class CIRFenvOpTest : public ::testing::Test { TEST_F(CIRFenvOpTest, MemoryEffects) { OwningOpRef<ModuleOp> module = parse(R"CIR( !s32i = !cir.int<s, 32> - cir.func @f(%a: !cir.float, %b: !cir.float, %c: !cir.float) { + cir.func @f(%a: !cir.float, %b: !cir.float, %c: !cir.float, + %va: !cir.vector<4 x !cir.float>, + %vb: !cir.vector<4 x !cir.float>) { %0 = cir.fadd %a, %b : !cir.float %1 = cir.fadd %a, %b : !cir.float {fenv = #cir.fenv<>} %2 = cir.sqrt %a : !cir.float {fenv = #cir.fenv<>} @@ -75,6 +77,12 @@ TEST_F(CIRFenvOpTest, MemoryEffects) { %7 = cir.lround %a : !cir.float -> !s32i {fenv = #cir.fenv<>} %8 = cir.cast floating %a : !cir.float -> !cir.double %9 = cir.cast floating %a : !cir.float -> !cir.double {fenv = #cir.fenv<>} + %10 = cir.cmp lt %a, %b : !cir.float + %11 = cir.cmp lt %a, %b : !cir.float {fenv = #cir.fenv<>} + %12 = cir.vec.cmp(eq, %va, %vb) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> + %13 = cir.vec.cmp(eq, %va, %vb) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> { + fenv = #cir.fenv<> + } cir.return } )CIR"); @@ -115,6 +123,20 @@ TEST_F(CIRFenvOpTest, MemoryEffects) { EXPECT_TRUE(isMemoryEffectFree(castOps[0])); expectFenvReadAndWrite(castOps[1]); EXPECT_FALSE(isMemoryEffectFree(castOps[1])); + + SmallVector<cir::CmpOp> cmpOps = findOps<cir::CmpOp>(*module); + ASSERT_EQ(cmpOps.size(), 2u); + EXPECT_TRUE(getEffects(cmpOps[0]).empty()); + EXPECT_TRUE(isMemoryEffectFree(cmpOps[0])); + expectFenvReadAndWrite(cmpOps[1]); + EXPECT_FALSE(isMemoryEffectFree(cmpOps[1])); + + SmallVector<cir::VecCmpOp> vecCmpOps = findOps<cir::VecCmpOp>(*module); + ASSERT_EQ(vecCmpOps.size(), 2u); + EXPECT_TRUE(getEffects(vecCmpOps[0]).empty()); + EXPECT_TRUE(isMemoryEffectFree(vecCmpOps[0])); + expectFenvReadAndWrite(vecCmpOps[1]); + EXPECT_FALSE(isMemoryEffectFree(vecCmpOps[1])); } TEST_F(CIRFenvOpTest, Speculatability) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
