https://github.com/xlauko updated 
https://github.com/llvm/llvm-project/pull/236704

>From 6e17cb3b41d9660d404524ce17478b9e82b424fd Mon Sep 17 00:00:00 2001
From: Henrich Lauko <[email protected]>
Date: Thu, 29 Oct 2026 09:55:43 +0000
Subject: [PATCH 1/5] [CIR] Default the fenv attribute and elide it when empty

Floating-point operations carried `fenv` as an optional attribute, so an
attached `#cir.fenv<>` that set no field still printed as `fenv<>` and
lowered to a constrained intrinsic. Make `fenv` a default-valued attribute
whose default is the empty `#cir.fenv<>`: it does not constrain the
operation and is not printed. Codegen always sets every field of the fenv
it attaches, so its output is unchanged. Verifiers, folding, memory effects
and lowering now use the new `getConstrainedFenv()` interface method, which
returns null for the empty attribute. The custom builders that only passed
an empty fenv are dropped; the generated ones default it.

Assisted-by: Claude Code (Opus 5.5).
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  | 25 +++++++
 .../include/clang/CIR/Dialect/IR/CIRDialect.h |  2 +-
 clang/include/clang/CIR/Dialect/IR/CIROps.td  | 67 ++++++-------------
 .../clang/CIR/Interfaces/CIROpInterfaces.td   | 27 ++++----
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 10 +--
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 18 ++---
 .../CIR/Analysis/alias-analysis-modref.cir    |  4 +-
 clang/test/CIR/IR/fenv.cir                    | 21 +++---
 clang/test/CIR/IR/invalid-cast.cir            | 10 +--
 clang/test/CIR/IR/invalid-cmp.cir             |  6 +-
 clang/test/CIR/Lowering/fenv.cir              |  8 ++-
 clang/utils/TableGen/CIRLoweringEmitter.cpp   |  8 +--
 12 files changed, 106 insertions(+), 100 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index e04ac4bd9b8b84..9d2ec98042ce28 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -908,6 +908,10 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> {
     `except_mode` defaults to `masked`, and an absent `strict_except` defaults
     to `false`.
 
+    The empty `#cir.fenv<>`, with no parameter set, does not constrain the
+    operation that carries it. It is the default value of the `fenv`
+    attribute of floating-point operations, which omit it when printing.
+
     Example:
     ```
     #cir.fenv<dynamic_rounding_mode = tonearest, except_mode = unknown,
@@ -925,7 +929,20 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> {
     `<` struct($dynamic_rounding_mode, $except_mode, $strict_except) `>`
   }];
 
+  let builders = [
+    AttrBuilder<(ins), [{
+      return $_get($_ctxt, std::nullopt, std::nullopt, mlir::BoolAttr{});
+    }]>
+  ];
+
   let extraClassDeclaration = [{
+    /// Whether this is the empty attribute, which does not constrain the
+    /// operation that carries it.
+    bool isEmpty() const {
+      return !getDynamicRoundingMode() && !getExceptMode() &&
+             !getStrictExcept();
+    }
+
     static constexpr cir::FPDynamicRoundingMode
     getDefaultDynamicRoundingMode() {
       return cir::FPDynamicRoundingMode::Unknown;
@@ -950,9 +967,17 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> {
     }
   }];
 
+  // Used by `CIR_DefaultValuedFenvAttr`, whose default value is a full builder
+  // expression.
+  let constBuilderCall = "$0";
+
   let canHaveIllegalCXXABIType = 0;
 }
 
+def CIR_DefaultValuedFenvAttr
+    : DefaultValuedAttr<CIR_FenvAttr,
+                        "cir::FenvAttr::get($_builder.getContext())">;
+
 
//===----------------------------------------------------------------------===//
 // CIR_FlagParam
 
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
index d9018e93628b69..6f695e6403e49c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
@@ -67,7 +67,7 @@ class FenvOpTrait : public 
mlir::OpTrait::TraitBase<ConcreteType, FenvOpTrait> {
 
   void getEffects(
       llvm::SmallVectorImpl<mlir::MemoryEffects::EffectInstance> &effects) {
-    if (!getFenvOp().getFenvAttr())
+    if (!getFenvOp().getConstrainedFenv())
       return;
     effects.emplace_back(mlir::MemoryEffects::Read::get(),
                          FloatingPointEnvironmentResource::get());
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index bd762aae3e1d7e..fec2b11b8ffb68 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -75,10 +75,11 @@ include "mlir/Interfaces/SideEffectInterfaces.td"
 // `llvmOp` field from your CIR operation definition.
 //
 // The `constrainedLLVMIntrinsic` field enables a specialized variant of this
-// generated lowering for floating-point operations that carry an optional
-// `fenv` attribute. When it is set and the operation has an `fenv` attribute
-// the operation is lowering to a call to the corresponding constrained fp
-// intrinsic. If `constrainedLLVMIntrinsic` is set `llvmOp` must be set as 
well.
+// generated lowering for floating-point operations that carry an `fenv`
+// attribute. When it is set and the operation's `fenv` attribute is not the
+// empty `#cir.fenv<>`, the operation is lowered to a call to the corresponding
+// constrained fp intrinsic. If `constrainedLLVMIntrinsic` is set `llvmOp` must
+// be set as well.
 //
 // `constrainedLLVMIntrinsicHasRoundingMode` indicates whether the constrained
 // intrinsic takes a rounding-mode metadata argument in addition to the
@@ -344,12 +345,13 @@ def CIR_CastOp : CIR_Op<"cast", !listconcat([
     ...
     %x = cir.cast array_to_ptrdecay %0 
        : !cir.ptr<!cir.array<!s32i x 10>> -> !cir.ptr<!s32i>
-    %y = cir.cast floating %f : !cir.double -> !cir.float fenv<>
+    %y = cir.cast floating %f : !cir.double -> !cir.float
+       fenv<except_mode = unknown, strict_except = true>
     ```
   }];
 
   let arguments = (ins CIR_CastKindAttr:$kind, CIR_AnyType:$src,
-                       OptionalAttr<CIR_FenvAttr>:$fenv);
+                       CIR_DefaultValuedFenvAttr:$fenv);
   let results = (outs CIR_AnyType:$result);
 
   let assemblyFormat = [{
@@ -2677,7 +2679,8 @@ def CIR_CmpOp : CIR_Op<"cmp",
     ```
     %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<>
+    %2 = cir.cmp lt %x, %y : !cir.float
+         fenv<except_mode = unknown, strict_except = true>
     ```
   }];
 
@@ -2685,7 +2688,7 @@ def CIR_CmpOp : CIR_Op<"cmp",
     CIR_CmpOpKindAttr:$kind,
     CIR_ComparableType:$lhs,
     CIR_ComparableType:$rhs,
-    OptionalAttr<CIR_FenvAttr>:$fenv
+    CIR_DefaultValuedFenvAttr:$fenv
   );
 
   let results = (outs CIR_BoolType:$result);
@@ -3014,7 +3017,7 @@ class CIR_FPBinaryOp<string mnemonic, list<Trait> traits 
= [],
                    !listconcat(CIR_FenvOpTraits, traits),
                    CIR_DynamicMemoryEffects> {
   let arguments = !con(commonArgs, (ins
-      OptionalAttr<CIR_FenvAttr>:$fenv,
+      CIR_DefaultValuedFenvAttr:$fenv,
       OptionalAttr<CIR_FastMathFlagsAttr>:$fastmath_flags));
   let assemblyFormat = [{
     $lhs `,` $rhs `:` type($lhs) (`fenv` `` $fenv^)? prop-dict attr-dict
@@ -3022,12 +3025,6 @@ class CIR_FPBinaryOp<string mnemonic, list<Trait> traits 
= [],
 
   let constrainedLLVMIntrinsic = mnemonic;
 
-  let builders = [
-    OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
-      build($_builder, $_state, lhs, rhs, cir::FenvAttr{},
-            cir::FastMathFlagsAttr{});
-    }]>
-  ];
 }
 
 
//===----------------------------------------------------------------------===//
@@ -6545,7 +6542,8 @@ def CIR_VecCmpOp : CIR_Op<"vec.cmp",
     ```
     %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<>
+    %gt = cir.vec.cmp(gt, %va, %vb) : !cir.vector<4 x !cir.float>, 
!cir.vector<4 x !s32i>
+        fenv<except_mode = unknown, strict_except = true>
     ```
   }];
 
@@ -6553,7 +6551,7 @@ def CIR_VecCmpOp : CIR_Op<"vec.cmp",
     CIR_CmpOpKindAttr:$kind,
     CIR_VectorType:$lhs,
     CIR_VectorType:$rhs,
-    OptionalAttr<CIR_FenvAttr>:$fenv
+    CIR_DefaultValuedFenvAttr:$fenv
   );
 
   let results = (outs CIR_VectorType:$result);
@@ -7446,7 +7444,7 @@ class CIR_ComplexFPBinOp<string mnemonic>
   let arguments = (ins
     CIR_ComplexOfFloatType:$lhs,
     CIR_ComplexOfFloatType:$rhs,
-    OptionalAttr<CIR_FenvAttr>:$fenv
+    CIR_DefaultValuedFenvAttr:$fenv
   );
 
   let results = (outs CIR_ComplexOfFloatType:$result);
@@ -8266,17 +8264,11 @@ class CIR_UnaryFPToFPBuiltinOp<string mnemonic, string 
llvmOpName>
              !listconcat([SameOperandsAndResultType], CIR_FenvOpTraits)>
 {
   let arguments = (ins CIR_AnyFloatOrVecOfFloatType:$src,
-                       OptionalAttr<CIR_FenvAttr>:$fenv);
+                       CIR_DefaultValuedFenvAttr:$fenv);
   let results = (outs CIR_AnyFloatOrVecOfFloatType:$result);
 
   let assemblyFormat = "$src `:` type($src) (`fenv` `` $fenv^)? attr-dict";
 
-  let builders = [
-    OpBuilder<(ins "mlir::Value":$src), [{
-      build($_builder, $_state, src, cir::FenvAttr{});
-    }]>
-  ];
-
   let llvmOp = llvmOpName;
   let constrainedLLVMIntrinsic = mnemonic;
 }
@@ -8588,7 +8580,7 @@ class CIR_UnaryFPToIntBuiltinOp<string mnemonic, string 
llvmOpName>
     : CIR_Op<mnemonic, CIR_FenvOpTraits>
 {
   let arguments = (ins CIR_AnyFloatType:$src,
-                       OptionalAttr<CIR_FenvAttr>:$fenv);
+                       CIR_DefaultValuedFenvAttr:$fenv);
   let results = (outs CIR_IntType:$result);
 
   let summary = [{
@@ -8601,12 +8593,6 @@ class CIR_UnaryFPToIntBuiltinOp<string mnemonic, string 
llvmOpName>
     (`fenv` `` $fenv^)? attr-dict
   }];
 
-  let builders = [
-    OpBuilder<(ins "mlir::Type":$result, "mlir::Value":$src), [{
-      build($_builder, $_state, result, src, cir::FenvAttr{});
-    }]>
-  ];
-
   let llvmOp = llvmOpName;
   let constrainedLLVMIntrinsic = mnemonic;
 }
@@ -8658,7 +8644,7 @@ class CIR_BinaryFPToFPBuiltinOp<string mnemonic, string 
llvmOpName>
   let arguments = (ins
     CIR_AnyFloatOrVecOfFloatType:$lhs,
     CIR_AnyFloatOrVecOfFloatType:$rhs,
-    OptionalAttr<CIR_FenvAttr>:$fenv
+    CIR_DefaultValuedFenvAttr:$fenv
   );
 
   let results = (outs  CIR_AnyFloatOrVecOfFloatType:$result);
@@ -8668,12 +8654,6 @@ class CIR_BinaryFPToFPBuiltinOp<string mnemonic, string 
llvmOpName>
     (`fenv` `` $fenv^)? attr-dict
   }];
 
-  let builders = [
-    OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
-      build($_builder, $_state, lhs, rhs, cir::FenvAttr{});
-    }]>
-  ];
-
   let llvmOp = llvmOpName;
 }
 
@@ -8810,7 +8790,7 @@ class CIR_TernaryFPToFPBuiltinOp<string mnemonic, string 
llvmOpName>
     CIR_AnyFloatOrVecOfFloatType:$a,
     CIR_AnyFloatOrVecOfFloatType:$b,
     CIR_AnyFloatOrVecOfFloatType:$c,
-    OptionalAttr<CIR_FenvAttr>:$fenv
+    CIR_DefaultValuedFenvAttr:$fenv
   );
 
   let results = (outs CIR_AnyFloatOrVecOfFloatType:$result);
@@ -8819,13 +8799,6 @@ class CIR_TernaryFPToFPBuiltinOp<string mnemonic, string 
llvmOpName>
     $a `,` $b `,` $c `:` type($a) (`fenv` `` $fenv^)? attr-dict
   }];
 
-  let builders = [
-    OpBuilder<(ins "mlir::Type":$result, "mlir::Value":$a, "mlir::Value":$b,
-                   "mlir::Value":$c), [{
-      build($_builder, $_state, result, a, b, c, cir::FenvAttr{});
-    }]>
-  ];
-
   let llvmOp = llvmOpName;
   let constrainedLLVMIntrinsic = mnemonic;
 }
diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td 
b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
index 1563fc5aa00da3..12aca794cc4b50 100644
--- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
@@ -267,40 +267,39 @@ let cppNamespace = "::cir" in {
   def CIR_FPEnvConstrainedOpInterface
       : OpInterface<"FPEnvConstrainedOpInterface"> {
     let description = [{
-      Interface for floating-point operations that carry an optional
-      `#cir.fenv` attribute. It provides effective values for each field,
-      substituting the floating-point environment defaults for absent fields.
+      Interface for floating-point operations that carry a `#cir.fenv`
+      attribute. The attribute defaults to the empty `#cir.fenv<>`, which does
+      not constrain the operation. The interface provides effective values for
+      each field, substituting the floating-point environment defaults for
+      absent fields.
     }];
 
     let methods = [
       InterfaceMethod<
-        "Return the `#cir.fenv` attribute, or a null attribute if absent.",
-        "cir::FenvAttr", "getFenvAttr", (ins), [{}],
+        "Return the `#cir.fenv` attribute if it constrains the operation, or "
+        "a null attribute if it is the empty `#cir.fenv<>`.",
+        "cir::FenvAttr", "getConstrainedFenv", (ins), [{}],
         /*defaultImplementation=*/[{
-          return $_op.getFenvAttr();
+          cir::FenvAttr fenv = $_op.getFenv();
+          return fenv.isEmpty() ? cir::FenvAttr{} : fenv;
         }]>,
       InterfaceMethod<
         "Return the effective dynamic rounding mode.",
         "cir::FPDynamicRoundingMode", "getFenvDynamicRoundingMode", (ins),
         [{}], /*defaultImplementation=*/[{
-          cir::FenvAttr fenv = $_op.getFenvAttr();
-          return fenv ? fenv.getDynamicRoundingModeOrDefault()
-                      : cir::FenvAttr::getDefaultDynamicRoundingMode();
+          return $_op.getFenv().getDynamicRoundingModeOrDefault();
         }]>,
       InterfaceMethod<
         "Return the effective floating-point exception mode.",
         "cir::FPExceptionMode", "getFenvExceptionMode", (ins), [{}],
         /*defaultImplementation=*/[{
-          cir::FenvAttr fenv = $_op.getFenvAttr();
-          return fenv ? fenv.getExceptionModeOrDefault()
-                      : cir::FenvAttr::getDefaultExceptionMode();
+          return $_op.getFenv().getExceptionModeOrDefault();
         }]>,
       InterfaceMethod<
         "Return the effective strict-exception flag.",
         "bool", "getFenvStrictExcept", (ins), [{}],
         /*defaultImplementation=*/[{
-          cir::FenvAttr fenv = $_op.getFenvAttr();
-          return fenv ? fenv.getStrictExceptOrDefault() : false;
+          return $_op.getFenv().getStrictExceptOrDefault();
         }]>,
     ];
   }
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 15f5ccaef130b7..1b120e9442f2aa 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -953,7 +953,7 @@ LogicalResult cir::CastOp::verify() {
   mlir::Type srcType = getSrc().getType();
   cir::CastKind kind = getKind();
 
-  if (getFenvAttr() && !isFloatingPointCastKind(kind))
+  if (getConstrainedFenv() && !isFloatingPointCastKind(kind))
     return emitOpError()
            << "'fenv' is only valid for floating-point cast kinds";
 
@@ -4267,7 +4267,7 @@ OpFoldResult cir::VecExtractOp::fold(FoldAdaptor adaptor) 
{
 
//===----------------------------------------------------------------------===//
 
 LogicalResult cir::CmpOp::verify() {
-  if (getFenvAttr() && !cir::isAnyFloatingPointType(getLhs().getType()))
+  if (getConstrainedFenv() && !cir::isAnyFloatingPointType(getLhs().getType()))
     return emitOpError()
            << "'fenv' is only valid for floating-point comparisons";
   return success();
@@ -4278,19 +4278,19 @@ LogicalResult cir::CmpOp::verify() {
 
//===----------------------------------------------------------------------===//
 
 LogicalResult cir::VecCmpOp::verify() {
-  if (getFenvAttr() && !cir::isFPOrVectorOfFPType(getLhs().getType()))
+  if (getConstrainedFenv() && !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.
+  // Do not fold when the floating-point environment is constrained.
   // 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())
+  if (getConstrainedFenv())
     return {};
 
   auto lhsVecAttr =
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 39ecb6fa7c746c..1b0a66dad40f25 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1991,7 +1991,7 @@ mlir::LogicalResult 
CIRToLLVMCastOpLowering::matchAndRewrite(
     };
 
     bool isTrunc = getFloatWidth(srcTy) > getFloatWidth(dstTy);
-    if (cir::FenvAttr fenv = castOp.getFenvAttr()) {
+    if (cir::FenvAttr fenv = castOp.getConstrainedFenv()) {
       // fptrunc takes rounding mode + exception behavior; fpext takes only
       // exception behavior.
       return lowerToConstrainedFPIntrinsic(
@@ -2072,7 +2072,7 @@ mlir::LogicalResult 
CIRToLLVMCastOpLowering::matchAndRewrite(
     bool isSigned = mlir::cast<cir::IntType>(
                         elementTypeIfVectorOrMatrix(castOp.getSrc().getType()))
                         .isSigned();
-    if (cir::FenvAttr fenv = castOp.getFenvAttr()) {
+    if (cir::FenvAttr fenv = castOp.getConstrainedFenv()) {
       return lowerToConstrainedFPIntrinsic(
           castOp, llvmSrcVal, fenv, llvmDstTy, rewriter,
           isSigned ? "sitofp" : "uitofp", /*hasRoundingMode=*/true);
@@ -2092,7 +2092,7 @@ mlir::LogicalResult 
CIRToLLVMCastOpLowering::matchAndRewrite(
     bool isSigned =
         mlir::cast<cir::IntType>(elementTypeIfVectorOrMatrix(castOp.getType()))
             .isSigned();
-    if (cir::FenvAttr fenv = castOp.getFenvAttr()) {
+    if (cir::FenvAttr fenv = castOp.getConstrainedFenv()) {
       return lowerToConstrainedFPIntrinsic(
           castOp, llvmSrcVal, fenv, llvmDstTy, rewriter,
           isSigned ? "fptosi" : "fptoui", /*hasRoundingMode=*/false);
@@ -2370,7 +2370,7 @@ mlir::LogicalResult 
CIRToLLVMFMaxNumOpLowering::matchAndRewrite(
     cir::FMaxNumOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
   mlir::Type resTy = typeConverter->convertType(op.getType());
-  if (cir::FenvAttr fenv = op.getFenvAttr())
+  if (cir::FenvAttr fenv = op.getConstrainedFenv())
     return lowerToConstrainedFPIntrinsic(
         op, adaptor.getOperands(), fenv, resTy, rewriter, "maxnum",
         /*hasRoundingMode=*/false, mlir::LLVM::FastmathFlags::nsz);
@@ -2384,7 +2384,7 @@ mlir::LogicalResult 
CIRToLLVMFMinNumOpLowering::matchAndRewrite(
     cir::FMinNumOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
   mlir::Type resTy = typeConverter->convertType(op.getType());
-  if (cir::FenvAttr fenv = op.getFenvAttr())
+  if (cir::FenvAttr fenv = op.getConstrainedFenv())
     return lowerToConstrainedFPIntrinsic(
         op, adaptor.getOperands(), fenv, resTy, rewriter, "minnum",
         /*hasRoundingMode=*/false, mlir::LLVM::FastmathFlags::nsz);
@@ -4019,7 +4019,7 @@ mlir::LogicalResult 
CIRToLLVMCmpOpLowering::matchAndRewrite(
 
   if (mlir::isa<cir::FPTypeInterface>(type)) {
     mlir::Type llvmResTy = getTypeConverter()->convertType(cmpOp.getType());
-    if (cir::FenvAttr fenv = cmpOp.getFenvAttr()) {
+    if (cir::FenvAttr fenv = cmpOp.getConstrainedFenv()) {
       mlir::LLVM::CallIntrinsicOp call = createConstrainedFCmpCall(
           rewriter, cmpOp.getLoc(), adaptor.getLhs(), adaptor.getRhs(),
           cmpOp.getKind(), fenv, llvmResTy);
@@ -5521,7 +5521,7 @@ mlir::LogicalResult 
CIRToLLVMVecCmpOpLowering::matchAndRewrite(
         convertCmpKindToICmpPredicate(op.getKind(), /*isSigned=*/false),
         adaptor.getLhs(), adaptor.getRhs());
   } else if (mlir::isa<cir::FPTypeInterface>(elementType)) {
-    if (cir::FenvAttr fenv = op.getFenvAttr()) {
+    if (cir::FenvAttr fenv = op.getConstrainedFenv()) {
       llvm::ElementCount size = op.getLhs().getType().getSize();
       auto i1VecTy = mlir::VectorType::get(
           size.getKnownMinValue(), rewriter.getI1Type(), {size.isScalable()});
@@ -5979,7 +5979,7 @@ mlir::LogicalResult 
CIRToLLVMComplexFAddOpLowering::matchAndRewrite(
   assert(!cir::MissingFeatures::fastMathFlags());
   mlir::Value newReal;
   mlir::Value newImag;
-  if (cir::FenvAttr fenv = op.getFenvAttr()) {
+  if (cir::FenvAttr fenv = op.getFenv(); !fenv.isEmpty()) {
     newReal = createConstrainedFPIntrinsicCall(
         rewriter, loc, {lhsReal, rhsReal}, complexElemTy, fenv,
         /*constrainedMnemonic=*/"fadd",
@@ -6032,7 +6032,7 @@ mlir::LogicalResult 
CIRToLLVMComplexFSubOpLowering::matchAndRewrite(
   assert(!cir::MissingFeatures::fastMathFlags());
   mlir::Value newReal;
   mlir::Value newImag;
-  if (cir::FenvAttr fenv = op.getFenvAttr()) {
+  if (cir::FenvAttr fenv = op.getFenv(); !fenv.isEmpty()) {
     newReal = createConstrainedFPIntrinsicCall(
         rewriter, loc, {lhsReal, rhsReal}, complexElemTy, fenv,
         /*constrainedMnemonic=*/"fsub",
diff --git a/clang/test/CIR/Analysis/alias-analysis-modref.cir 
b/clang/test/CIR/Analysis/alias-analysis-modref.cir
index 781d3bd3baa60f..015fb1ee1600d9 100644
--- a/clang/test/CIR/Analysis/alias-analysis-modref.cir
+++ b/clang/test/CIR/Analysis/alias-analysis-modref.cir
@@ -39,7 +39,7 @@ cir.func @modref_load() {
 
 // -----
 
-// A cir.fadd with an `fenv` attribute has Read/Write effects on the
+// A cir.fadd with a non-empty `fenv` attribute has Read/Write effects on the
 // non-addressable FloatingPointEnvironment resource and no associated
 // Value. Such effects can never alias a pointer-based location, so the
 // result is NoModRef regardless of the queried location.
@@ -51,6 +51,6 @@ cir.func @modref_load() {
 !s32i = !cir.int<s, 32>
 cir.func @modref_fenv(%a: !cir.float, %b: !cir.float) {
   %x = cir.alloca "x" align(4) : !cir.ptr<!s32i> {test.ptr = "x"}
-  %0 = cir.fadd %a, %b : !cir.float fenv<> {test.ptr = "fadd_op"}
+  %0 = cir.fadd %a, %b : !cir.float fenv<strict_except = true> {test.ptr = 
"fadd_op"}
   cir.return
 }
diff --git a/clang/test/CIR/IR/fenv.cir b/clang/test/CIR/IR/fenv.cir
index 2c938c2c74c70b..4ca91d243a621b 100644
--- a/clang/test/CIR/IR/fenv.cir
+++ b/clang/test/CIR/IR/fenv.cir
@@ -13,9 +13,14 @@ cir.func @fadd_fenv(%a: !cir.float, %b: !cir.float) {
   // CHECK-NOT: fenv
   %1 = cir.fadd %a, %b : !cir.float
 
-  // Empty fenv attribute.
-  // CHECK: cir.fadd %{{.*}}, %{{.*}} : !cir.float fenv<>
+  // The empty fenv attribute is the default and is not printed.
+  // CHECK: cir.fadd %{{.*}}, %{{.*}} : !cir.float
+  // CHECK-NOT: fenv
   %2 = cir.fadd %a, %b : !cir.float fenv<>
+
+  // Fields set to their default value are kept.
+  // CHECK: cir.fadd %{{.*}}, %{{.*}} : !cir.float fenv<dynamic_rounding_mode 
= unknown, except_mode = masked, strict_except = false>
+  %3 = cir.fadd %a, %b : !cir.float fenv<dynamic_rounding_mode = unknown, 
except_mode = masked, strict_except = false>
   cir.return
 }
 
@@ -73,8 +78,8 @@ cir.func @unary_fp_to_int_builtin_fenv(%a: !cir.float) -> 
!s32i {
   %0 = cir.lround %a : !cir.float -> !s32i fenv<dynamic_rounding_mode = 
tonearest>
   // CHECK: cir.llround %{{.*}} : !cir.float -> !s32i fenv<except_mode = 
unmasked>
   %1 = cir.llround %a : !cir.float -> !s32i fenv<except_mode = unmasked>
-  // CHECK: cir.lrint %{{.*}} : !cir.float -> !s32i fenv<>
-  %2 = cir.lrint %a : !cir.float -> !s32i fenv<>
+  // CHECK: cir.lrint %{{.*}} : !cir.float -> !s32i fenv<except_mode = unknown>
+  %2 = cir.lrint %a : !cir.float -> !s32i fenv<except_mode = unknown>
   // CHECK: cir.llrint %{{.*}} : !cir.float -> !s32i
   // CHECK-NOT: fenv
   %3 = cir.llrint %a : !cir.float -> !s32i
@@ -110,8 +115,8 @@ cir.func @ternary_fp_builtin_fenv(%a: !cir.float, %b: 
!cir.float, %c: !cir.float
 cir.func @cast_fenv(%a: !cir.float, %b: !cir.double, %i: !s32i) {
   // CHECK: cir.cast floating %{{.*}} : !cir.double -> !cir.float 
fenv<dynamic_rounding_mode = tonearest>
   %0 = cir.cast floating %b : !cir.double -> !cir.float 
fenv<dynamic_rounding_mode = tonearest>
-  // CHECK: cir.cast int_to_float %{{.*}} : !s32i -> !cir.float fenv<>
-  %1 = cir.cast int_to_float %i : !s32i -> !cir.float fenv<>
+  // CHECK: cir.cast int_to_float %{{.*}} : !s32i -> !cir.float 
fenv<except_mode = unknown>
+  %1 = cir.cast int_to_float %i : !s32i -> !cir.float fenv<except_mode = 
unknown>
   // CHECK: cir.cast float_to_int %{{.*}} : !cir.float -> !s32i 
fenv<except_mode = unmasked, strict_except = true>
   %2 = cir.cast float_to_int %a : !cir.float -> !s32i fenv<except_mode = 
unmasked, strict_except = true>
   // CHECK: cir.cast float_to_bool %{{.*}} : !cir.float -> !cir.bool
@@ -126,8 +131,8 @@ cir.func @cmp_fenv(%a: !cir.float, %b: !cir.float,
                     %vb: !cir.vector<4 x !cir.float>) {
   // CHECK: cir.cmp lt %{{.*}}, %{{.*}} : !cir.float fenv<except_mode = 
unmasked>
   %0 = cir.cmp lt %a, %b : !cir.float fenv<except_mode = unmasked>
-  // CHECK: cir.cmp eq %{{.*}}, %{{.*}} : !cir.float fenv<>
-  %1 = cir.cmp eq %a, %b : !cir.float fenv<>
+  // CHECK: cir.cmp eq %{{.*}}, %{{.*}} : !cir.float fenv<except_mode = 
unknown>
+  %1 = cir.cmp eq %a, %b : !cir.float fenv<except_mode = unknown>
   // CHECK: cir.vec.cmp(gt, %{{.*}}, %{{.*}}) : !cir.vector<4 x !cir.float>, 
!cir.vector<4 x !s32i> fenv<strict_except = true>
   %2 = cir.vec.cmp(gt, %va, %vb) : !cir.vector<4 x !cir.float>, !cir.vector<4 
x !s32i>
        fenv<strict_except = true>
diff --git a/clang/test/CIR/IR/invalid-cast.cir 
b/clang/test/CIR/IR/invalid-cast.cir
index 67e3dbe68306e6..62cd36e30dcea0 100644
--- a/clang/test/CIR/IR/invalid-cast.cir
+++ b/clang/test/CIR/IR/invalid-cast.cir
@@ -45,7 +45,7 @@ module {
 module {
   cir.func @fenv_on_integral(%i: !s32i) {
     // expected-error@+1 {{'fenv' is only valid for floating-point cast kinds}}
-    %0 = cir.cast integral %i : !s32i -> !s32i fenv<>
+    %0 = cir.cast integral %i : !s32i -> !s32i fenv<strict_except = true>
     cir.return
   }
 }
@@ -57,7 +57,7 @@ module {
 module {
   cir.func @fenv_on_int_to_bool(%i: !s32i) {
     // expected-error@+1 {{'fenv' is only valid for floating-point cast kinds}}
-    %0 = cir.cast int_to_bool %i : !s32i -> !cir.bool fenv<>
+    %0 = cir.cast int_to_bool %i : !s32i -> !cir.bool fenv<strict_except = 
true>
     cir.return
   }
 }
@@ -69,7 +69,7 @@ module {
 module {
   cir.func @fenv_on_ptr_to_int(%p: !cir.ptr<!s32i>) {
     // expected-error@+1 {{'fenv' is only valid for floating-point cast kinds}}
-    %0 = cir.cast ptr_to_int %p : !cir.ptr<!s32i> -> !s32i fenv<>
+    %0 = cir.cast ptr_to_int %p : !cir.ptr<!s32i> -> !s32i fenv<strict_except 
= true>
     cir.return
   }
 }
@@ -81,7 +81,7 @@ module {
 module {
   cir.func @fenv_on_bitcast(%p: !cir.ptr<!s32i>) {
     // expected-error@+1 {{'fenv' is only valid for floating-point cast kinds}}
-    %0 = cir.cast bitcast %p : !cir.ptr<!s32i> -> !cir.ptr<!cir.float> fenv<>
+    %0 = cir.cast bitcast %p : !cir.ptr<!s32i> -> !cir.ptr<!cir.float> 
fenv<strict_except = true>
     cir.return
   }
 }
@@ -93,7 +93,7 @@ module {
 module {
   cir.func @fenv_on_int_complex(%c: !cir.complex<!s32i>) {
     // expected-error@+1 {{'fenv' is only valid for floating-point cast kinds}}
-    %0 = cir.cast int_complex %c : !cir.complex<!s32i> -> !cir.complex<!s32i> 
fenv<>
+    %0 = cir.cast int_complex %c : !cir.complex<!s32i> -> !cir.complex<!s32i> 
fenv<strict_except = true>
     cir.return
   }
 }
diff --git a/clang/test/CIR/IR/invalid-cmp.cir 
b/clang/test/CIR/IR/invalid-cmp.cir
index 25e09700c2d72f..337becafc1b525 100644
--- a/clang/test/CIR/IR/invalid-cmp.cir
+++ b/clang/test/CIR/IR/invalid-cmp.cir
@@ -5,7 +5,7 @@
 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<>
+    %0 = cir.cmp eq %a, %b : !s32i fenv<strict_except = true>
     cir.return
   }
 }
@@ -17,7 +17,7 @@ module {
 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<>
+    %0 = cir.cmp ne %a, %b : !cir.ptr<!s32i> fenv<strict_except = true>
     cir.return
   }
 }
@@ -31,7 +31,7 @@ module {
                                 %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.vector<4 x !s32i> fenv<strict_except = 
true>
     cir.return
   }
 }
diff --git a/clang/test/CIR/Lowering/fenv.cir b/clang/test/CIR/Lowering/fenv.cir
index 27f3455c16ea37..e49daf6dda4924 100644
--- a/clang/test/CIR/Lowering/fenv.cir
+++ b/clang/test/CIR/Lowering/fenv.cir
@@ -14,10 +14,14 @@ module {
     // CHECK: llvm.fadd %{{.*}}, %{{.*}} : f32
     %1 = cir.fadd %a, %b : !cir.float
 
+    // The empty fenv attribute does not constrain the operation.
+    // CHECK: llvm.fadd %{{.*}}, %{{.*}} : f32
+    %2 = cir.fadd %a, %b : !cir.float fenv<>
+
     // CHECK: %[[RM2:.*]] = llvm.mlir.metadata_as_value 
#llvm.md_string<"round.tonearest">
     // CHECK: %[[EB2:.*]] = llvm.mlir.metadata_as_value 
#llvm.md_string<"fpexcept.ignore">
     // CHECK: llvm.call_intrinsic 
"llvm.experimental.constrained.fadd"(%{{.*}}, %{{.*}}, %[[RM2]], %[[EB2]]) : 
(f32, f32, !llvm.metadata, !llvm.metadata) -> f32
-    %2 = cir.fadd %a, %b : !cir.float fenv<>
+    %3 = cir.fadd %a, %b : !cir.float fenv<dynamic_rounding_mode = tonearest, 
except_mode = masked, strict_except = false>
     cir.return
   }
 
@@ -255,7 +259,7 @@ module {
     // CHECK: llvm.fmul %{{.*}}, %{{.*}} fastmath<contract> : f32
     %0 = cir.fmul %a, %b : !cir.float <fastmath_flags = [contract]>
     // CHECK: llvm.call_intrinsic 
"llvm.experimental.constrained.fadd"(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) 
{fastmathFlags = #llvm.fastmath<contract>} : (f32, f32, !llvm.metadata, 
!llvm.metadata) -> f32
-    %1 = cir.fadd %0, %b : !cir.float fenv<> <fastmath_flags = [contract]>
+    %1 = cir.fadd %0, %b : !cir.float fenv<except_mode = masked> 
<fastmath_flags = [contract]>
     cir.return
   }
 }
diff --git a/clang/utils/TableGen/CIRLoweringEmitter.cpp 
b/clang/utils/TableGen/CIRLoweringEmitter.cpp
index 46a8dd8bd6ba0d..00ee5f12d9942b 100644
--- a/clang/utils/TableGen/CIRLoweringEmitter.cpp
+++ b/clang/utils/TableGen/CIRLoweringEmitter.cpp
@@ -203,9 +203,9 @@ void GenerateLLVMLoweringPattern(llvm::StringRef OpName,
 
   if (!ConstrainedLLVMIntrinsic.empty()) {
     // Generate a matchAndRewrite body for a floating-point operation that
-    // carries an optional `fenv` attribute. The shared 
`lowerConstrainableFPOp`
-    // helper lowers to the plain `llvmOp` when no `fenv` attribute is present
-    // and to a call to the constrained floating-point intrinsic when one is.
+    // carries an `fenv` attribute. The shared `lowerConstrainableFPOp` helper
+    // lowers to the plain `llvmOp` when `fenv` is the empty `#cir.fenv<>` and
+    // to a call to the constrained floating-point intrinsic otherwise.
     assert(!LLVMOp.empty() && "constrainedLLVMIntrinsic requires llvmOp");
     Code
         << "  mlir::LogicalResult matchAndRewrite(cir::" << OpName
@@ -213,7 +213,7 @@ void GenerateLLVMLoweringPattern(llvm::StringRef OpName,
            "const override {\n";
     Code << "    return lowerConstrainableFPOp<mlir::LLVM::" << LLVMOp
          << ">(\n";
-    Code << "        op, adaptor.getOperands(), op.getFenvAttr(),\n";
+    Code << "        op, adaptor.getOperands(), op.getConstrainedFenv(),\n";
     Code << "        "
          << (HasFastMathFlags ? "op.getFastmathFlagsAttr()"
                               : "cir::FastMathFlagsAttr{}")

>From da5b9d9d0696a44439fbca4cf01aa2dd76c46b6d Mon Sep 17 00:00:00 2001
From: Henrich Lauko <[email protected]>
Date: Thu, 29 Oct 2026 10:18:51 +0000
Subject: [PATCH 2/5] [CIR] Drop comment on FenvAttr constBuilderCall

Assisted-by: Claude Code (Opus 5.5).
---
 clang/include/clang/CIR/Dialect/IR/CIRAttrs.td | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 9d2ec98042ce28..b470ba5b0847a8 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -967,8 +967,6 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> {
     }
   }];
 
-  // Used by `CIR_DefaultValuedFenvAttr`, whose default value is a full builder
-  // expression.
   let constBuilderCall = "$0";
 
   let canHaveIllegalCXXABIType = 0;

>From 25121ed62527c45f102213690523bae43b8fc012 Mon Sep 17 00:00:00 2001
From: Henrich Lauko <[email protected]>
Date: Thu, 29 Oct 2026 10:24:19 +0000
Subject: [PATCH 3/5] [CIR] Simplify the fenv default and use the interface for
 complex ops

Spell the empty fenv inline in the default instead of adding a no-argument 
builder, and give the complex FP binary ops the fenv interface so their 
lowering uses getConstrainedFenv() like every other op.

Assisted-by: Claude Code (Opus 5.5).
---
 clang/include/clang/CIR/Dialect/IR/CIRAttrs.td      | 11 ++---------
 clang/include/clang/CIR/Dialect/IR/CIROps.td        |  3 ++-
 clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |  4 ++--
 3 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index b470ba5b0847a8..7db49e83cb423b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -929,12 +929,6 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> {
     `<` struct($dynamic_rounding_mode, $except_mode, $strict_except) `>`
   }];
 
-  let builders = [
-    AttrBuilder<(ins), [{
-      return $_get($_ctxt, std::nullopt, std::nullopt, mlir::BoolAttr{});
-    }]>
-  ];
-
   let extraClassDeclaration = [{
     /// Whether this is the empty attribute, which does not constrain the
     /// operation that carries it.
@@ -972,9 +966,8 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> {
   let canHaveIllegalCXXABIType = 0;
 }
 
-def CIR_DefaultValuedFenvAttr
-    : DefaultValuedAttr<CIR_FenvAttr,
-                        "cir::FenvAttr::get($_builder.getContext())">;
+def CIR_DefaultValuedFenvAttr : DefaultValuedAttr<CIR_FenvAttr,
+    "cir::FenvAttr::get($_builder.getContext(), {}, {}, {})">;
 
 
//===----------------------------------------------------------------------===//
 // CIR_FlagParam
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index fec2b11b8ffb68..135260073d59e4 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -7440,7 +7440,8 @@ def CIR_ComplexDivOp : CIR_ComplexBinOp<"complex.div"> {
 }
 
 class CIR_ComplexFPBinOp<string mnemonic>
-    : CIR_Op<mnemonic, [Pure, SameOperandsAndResultType]> {
+    : CIR_Op<mnemonic, [Pure, SameOperandsAndResultType,
+                        CIR_FPEnvConstrainedOpInterface]> {
   let arguments = (ins
     CIR_ComplexOfFloatType:$lhs,
     CIR_ComplexOfFloatType:$rhs,
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 1b0a66dad40f25..3d5617bb2cde83 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -5979,7 +5979,7 @@ mlir::LogicalResult 
CIRToLLVMComplexFAddOpLowering::matchAndRewrite(
   assert(!cir::MissingFeatures::fastMathFlags());
   mlir::Value newReal;
   mlir::Value newImag;
-  if (cir::FenvAttr fenv = op.getFenv(); !fenv.isEmpty()) {
+  if (cir::FenvAttr fenv = op.getConstrainedFenv()) {
     newReal = createConstrainedFPIntrinsicCall(
         rewriter, loc, {lhsReal, rhsReal}, complexElemTy, fenv,
         /*constrainedMnemonic=*/"fadd",
@@ -6032,7 +6032,7 @@ mlir::LogicalResult 
CIRToLLVMComplexFSubOpLowering::matchAndRewrite(
   assert(!cir::MissingFeatures::fastMathFlags());
   mlir::Value newReal;
   mlir::Value newImag;
-  if (cir::FenvAttr fenv = op.getFenv(); !fenv.isEmpty()) {
+  if (cir::FenvAttr fenv = op.getConstrainedFenv()) {
     newReal = createConstrainedFPIntrinsicCall(
         rewriter, loc, {lhsReal, rhsReal}, complexElemTy, fenv,
         /*constrainedMnemonic=*/"fsub",

>From d166cd9742363162d82be6f7885b88e11315d6e7 Mon Sep 17 00:00:00 2001
From: Henrich Lauko <[email protected]>
Date: Thu, 29 Oct 2026 10:34:27 +0000
Subject: [PATCH 4/5] [CIR] Reduce the fenv interface to an accessor and fix
 the unit test

The interface now declares only getFenv() and shares getConstrainedFenv(); 
callers read effective field values through the FenvAttr helpers. The identity 
constBuilderCall moves onto CIR_DefaultValuedFenvAttr, the only place that 
needs it. FenvOpTest used fenv<> to mean constrained; it now sets a field and 
checks that the empty fenv has no effects.

Assisted-by: Claude Code (Opus 5.5).
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  |  6 +--
 .../include/clang/CIR/Dialect/IR/CIRDialect.h |  6 +--
 .../clang/CIR/Interfaces/CIROpInterfaces.td   | 41 ++++++-------------
 clang/unittests/CIR/FenvOpTest.cpp            | 26 +++++++-----
 4 files changed, 33 insertions(+), 46 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 7db49e83cb423b..1e54c4b67bb6f2 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -961,13 +961,13 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> {
     }
   }];
 
-  let constBuilderCall = "$0";
-
   let canHaveIllegalCXXABIType = 0;
 }
 
 def CIR_DefaultValuedFenvAttr : DefaultValuedAttr<CIR_FenvAttr,
-    "cir::FenvAttr::get($_builder.getContext(), {}, {}, {})">;
+    "cir::FenvAttr::get($_builder.getContext(), {}, {}, {})"> {
+  let constBuilderCall = "$0";
+}
 
 
//===----------------------------------------------------------------------===//
 // CIR_FlagParam
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
index 6f695e6403e49c..ac3adbed3af3d8 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
@@ -57,9 +57,9 @@ class FenvOpTrait : public 
mlir::OpTrait::TraitBase<ConcreteType, FenvOpTrait> {
   mlir::Speculation::Speculatability getSpeculatability() {
     // Masked exceptions cannot trap. When strict_except is false, exception
     // side effects are non-deterministic, so speculation is still safe.
-    FPEnvConstrainedOpInterface fenvOp = getFenvOp();
-    if (fenvOp.getFenvExceptionMode() == cir::FPExceptionMode::Masked &&
-        !fenvOp.getFenvStrictExcept())
+    cir::FenvAttr fenv = getFenvOp().getFenv();
+    if (fenv.getExceptionModeOrDefault() == cir::FPExceptionMode::Masked &&
+        !fenv.getStrictExceptOrDefault())
       return mlir::Speculation::Speculatable;
 
     return mlir::Speculation::NotSpeculatable;
diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td 
b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
index 12aca794cc4b50..35603ab63f9586 100644
--- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
@@ -269,39 +269,22 @@ let cppNamespace = "::cir" in {
     let description = [{
       Interface for floating-point operations that carry a `#cir.fenv`
       attribute. The attribute defaults to the empty `#cir.fenv<>`, which does
-      not constrain the operation. The interface provides effective values for
-      each field, substituting the floating-point environment defaults for
-      absent fields.
+      not constrain the operation.
     }];
 
     let methods = [
-      InterfaceMethod<
-        "Return the `#cir.fenv` attribute if it constrains the operation, or "
-        "a null attribute if it is the empty `#cir.fenv<>`.",
-        "cir::FenvAttr", "getConstrainedFenv", (ins), [{}],
-        /*defaultImplementation=*/[{
-          cir::FenvAttr fenv = $_op.getFenv();
-          return fenv.isEmpty() ? cir::FenvAttr{} : fenv;
-        }]>,
-      InterfaceMethod<
-        "Return the effective dynamic rounding mode.",
-        "cir::FPDynamicRoundingMode", "getFenvDynamicRoundingMode", (ins),
-        [{}], /*defaultImplementation=*/[{
-          return $_op.getFenv().getDynamicRoundingModeOrDefault();
-        }]>,
-      InterfaceMethod<
-        "Return the effective floating-point exception mode.",
-        "cir::FPExceptionMode", "getFenvExceptionMode", (ins), [{}],
-        /*defaultImplementation=*/[{
-          return $_op.getFenv().getExceptionModeOrDefault();
-        }]>,
-      InterfaceMethod<
-        "Return the effective strict-exception flag.",
-        "bool", "getFenvStrictExcept", (ins), [{}],
-        /*defaultImplementation=*/[{
-          return $_op.getFenv().getStrictExceptOrDefault();
-        }]>,
+      InterfaceMethod<"Return the `#cir.fenv` attribute.",
+        "cir::FenvAttr", "getFenv">,
     ];
+
+    let extraSharedClassDeclaration = [{
+      /// Return the `#cir.fenv` attribute if it constrains the operation, or
+      /// a null attribute if it is the empty `#cir.fenv<>`.
+      cir::FenvAttr getConstrainedFenv() {
+        cir::FenvAttr fenv = $_op.getFenv();
+        return fenv.isEmpty() ? cir::FenvAttr{} : fenv;
+      }
+    }];
   }
 
   def CIR_BinaryOpInterface : OpInterface<"BinaryOpInterface"> {
diff --git a/clang/unittests/CIR/FenvOpTest.cpp 
b/clang/unittests/CIR/FenvOpTest.cpp
index b0ff67b9167010..df19617c1bcac2 100644
--- a/clang/unittests/CIR/FenvOpTest.cpp
+++ b/clang/unittests/CIR/FenvOpTest.cpp
@@ -68,19 +68,19 @@ TEST_F(CIRFenvOpTest, MemoryEffects) {
                 %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<>
-      %2 = cir.sqrt %a : !cir.float fenv<>
-      %3 = cir.pow %a, %b : !cir.float fenv<>
+      %1 = cir.fadd %a, %b : !cir.float fenv<strict_except = true>
+      %2 = cir.sqrt %a : !cir.float fenv<strict_except = true>
+      %3 = cir.pow %a, %b : !cir.float fenv<strict_except = true>
       %4 = cir.fma %a, %b, %c : !cir.float
-      %5 = cir.fma %a, %b, %c : !cir.float fenv<>
+      %5 = cir.fma %a, %b, %c : !cir.float fenv<strict_except = true>
       %6 = cir.lround %a : !cir.float -> !s32i
-      %7 = cir.lround %a : !cir.float -> !s32i fenv<>
+      %7 = cir.lround %a : !cir.float -> !s32i fenv<strict_except = true>
       %8 = cir.cast floating %a : !cir.float -> !cir.double
-      %9 = cir.cast floating %a : !cir.float -> !cir.double fenv<>
+      %9 = cir.cast floating %a : !cir.float -> !cir.double fenv<strict_except 
= true>
       %10 = cir.cmp lt %a, %b : !cir.float
-      %11 = cir.cmp lt %a, %b : !cir.float fenv<>
+      %11 = cir.cmp lt %a, %b : !cir.float fenv<strict_except = true>
       %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<>
+      %13 = cir.vec.cmp(eq, %va, %vb) : !cir.vector<4 x !cir.float>, 
!cir.vector<4 x !s32i> fenv<strict_except = true>
       cir.return
     }
   )CIR");
@@ -163,12 +163,16 @@ TEST_F(CIRFenvOpTest, Speculatability) {
   for (unsigned i = 4; i != ops.size(); ++i)
     EXPECT_FALSE(isSpeculatable(ops[i])) << "operation " << i;
 
+  // The empty fenv is the default and does not constrain the operation.
   cir::FPEnvConstrainedOpInterface fenvOp =
       cast<cir::FPEnvConstrainedOpInterface>(ops[1].getOperation());
-  EXPECT_EQ(fenvOp.getFenvDynamicRoundingMode(),
+  EXPECT_FALSE(fenvOp.getConstrainedFenv());
+  EXPECT_TRUE(getEffects(ops[1]).empty());
+  cir::FenvAttr fenv = fenvOp.getFenv();
+  EXPECT_EQ(fenv.getDynamicRoundingModeOrDefault(),
             cir::FPDynamicRoundingMode::Unknown);
-  EXPECT_EQ(fenvOp.getFenvExceptionMode(), cir::FPExceptionMode::Masked);
-  EXPECT_FALSE(fenvOp.getFenvStrictExcept());
+  EXPECT_EQ(fenv.getExceptionModeOrDefault(), cir::FPExceptionMode::Masked);
+  EXPECT_FALSE(fenv.getStrictExceptOrDefault());
 }
 
 } // namespace

>From da29c6b57c314b5e814ff1d8e466b7ffd7ce7e43 Mon Sep 17 00:00:00 2001
From: Henrich Lauko <[email protected]>
Date: Thu, 29 Oct 2026 11:08:14 +0000
Subject: [PATCH 5/5] [CIR] Append the fenv interface to complex FP op traits

Use let append traits instead of extending the base trait list.

Assisted-by: Claude Code (Opus 5.5).
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 135260073d59e4..cb86ff58b76589 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -7440,8 +7440,9 @@ def CIR_ComplexDivOp : CIR_ComplexBinOp<"complex.div"> {
 }
 
 class CIR_ComplexFPBinOp<string mnemonic>
-    : CIR_Op<mnemonic, [Pure, SameOperandsAndResultType,
-                        CIR_FPEnvConstrainedOpInterface]> {
+    : CIR_Op<mnemonic, [Pure, SameOperandsAndResultType]> {
+  let append traits = [CIR_FPEnvConstrainedOpInterface];
+
   let arguments = (ins
     CIR_ComplexOfFloatType:$lhs,
     CIR_ComplexOfFloatType:$rhs,

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to