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

Reply via email to