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/8] [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/8] [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/8] [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/8] [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/8] [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, >From 7a0072136dbd2f249513ab3d148253241b274f0e Mon Sep 17 00:00:00 2001 From: Henrich Lauko <[email protected]> Date: Thu, 29 Oct 2026 11:26:45 +0000 Subject: [PATCH 6/8] [CIR] Drop a leftover blank line in CIR_FPBinaryOp Assisted-by: Claude Code (Opus 5.5). --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index cb86ff58b76589..01f6b2f791391f 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -3024,7 +3024,6 @@ class CIR_FPBinaryOp<string mnemonic, list<Trait> traits = [], }]; let constrainedLLVMIntrinsic = mnemonic; - } //===----------------------------------------------------------------------===// >From 2adc69d19eac9937b917106eabbe9235b2b04e4c Mon Sep 17 00:00:00 2001 From: Henrich Lauko <[email protected]> Date: Fri, 30 Oct 2026 21:20:47 +0000 Subject: [PATCH 7/8] [CIR] Update fenv docs for the default-valued attribute Assisted-by: Claude Code (Opus 5.5). --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 3 ++- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 5 ++-- clang/include/clang/CIR/Dialect/IR/CIROps.td | 23 ++++++++++--------- clang/test/CIR/Lowering/fenv.cir | 2 +- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 489ed57695d299..a9d1aa0592f5af 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -279,7 +279,8 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { /// environment currently in effect. This is attached to floating-point /// operations that support it to capture the rounding and exception /// behavior. Returns a null attribute when constrained floating-point is not - /// enabled, in which case no attribute should be attached. + /// enabled, in which case the operation gets the default, unconstrained + /// `#cir.fenv<>`. cir::FenvAttr getConstrainedFPAttr() { if (!isFPConstrained) return {}; diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 1e54c4b67bb6f2..965256234f2d59 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -896,9 +896,10 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> { parameters are optional. - `dynamic_rounding_mode`: the known dynamic rounding mode at the point the - instruction is executed. Otherwise the behavior is undefined. + instruction is executed. If the actual mode differs, the behavior is + undefined. - `except_mode`: the known exception mode at the point the instruction is - executed. Otherwise the behavior is undefined. + executed. If the actual mode differs, the behavior is undefined. - `strict_except`: if `false`, any case that would produce an FP exception produces it non-deterministically instead (i.e. it may or may not occur). This means the FP status is written non-deterministically, and, if diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 01f6b2f791391f..4728cdf1285793 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -331,8 +331,9 @@ def CIR_CastOp : CIR_Op<"cast", !listconcat([ - `bool_to_float` - The optional `fenv` attribute describes constraints on the floating-point - handling of the operation. It is only valid for floating-point cast kinds: + The `fenv` attribute describes constraints on the floating-point handling + of the operation. A non-empty `fenv` is only valid for floating-point cast + kinds: `floating`, `int_to_float`, `float_to_int`, `float_to_bool`, `bool_to_float`, `float_to_complex`, `float_complex_to_real`, `float_complex_to_bool`, `float_complex`, `float_complex_to_int_complex`, @@ -2672,8 +2673,8 @@ def CIR_CmpOp : CIR_Op<"cmp", 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 + The `fenv` attribute describes constraints on the floating-point handling + of the operation. A non-empty `fenv` is only valid for floating-point comparisons. ``` @@ -3008,8 +3009,8 @@ def CIR_RemOp : CIR_BinaryOp<"rem", CIR_AnyIntOrVecOfIntType> { // Base class for floating-point binary arithmetic operations. The lhs, rhs, // and result must all be the same floating-point scalar or vector type. // -// The optional `fenv` attribute describes constraints on the floating-point -// handling of the operation. The optional `fastmath_flags` attribute holds the +// The `fenv` attribute describes constraints on the floating-point handling +// of the operation. The optional `fastmath_flags` attribute holds the // fast-math flags of the operation. class CIR_FPBinaryOp<string mnemonic, list<Trait> traits = [], Type type = CIR_AnyFloatOrVecOfFloatType> @@ -6534,9 +6535,9 @@ def CIR_VecCmpOp : CIR_Op<"vec.cmp", 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. + The `fenv` attribute describes constraints on the floating-point handling + of the operation. A non-empty `fenv` 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> @@ -8258,8 +8259,8 @@ def CIR_PtrDiffOp : CIR_Op<"ptr_diff", [Pure, SameTypeOperands]> { // Floating Point Ops //===----------------------------------------------------------------------===// -// The optional `fenv` attribute describes constraints on the floating-point -// handling of the operation. +// The `fenv` attribute describes constraints on the floating-point handling +// of the operation. class CIR_UnaryFPToFPBuiltinOp<string mnemonic, string llvmOpName> : CIR_Op<mnemonic, !listconcat([SameOperandsAndResultType], CIR_FenvOpTraits)> diff --git a/clang/test/CIR/Lowering/fenv.cir b/clang/test/CIR/Lowering/fenv.cir index e49daf6dda4924..400daa02c88a61 100644 --- a/clang/test/CIR/Lowering/fenv.cir +++ b/clang/test/CIR/Lowering/fenv.cir @@ -15,7 +15,7 @@ module { %1 = cir.fadd %a, %b : !cir.float // The empty fenv attribute does not constrain the operation. - // CHECK: llvm.fadd %{{.*}}, %{{.*}} : f32 + // CHECK-NEXT: llvm.fadd %{{.*}}, %{{.*}} : f32 %2 = cir.fadd %a, %b : !cir.float fenv<> // CHECK: %[[RM2:.*]] = llvm.mlir.metadata_as_value #llvm.md_string<"round.tonearest"> >From c93e82bd17bb5e5d8827fbc6785356eb20ac76c1 Mon Sep 17 00:00:00 2001 From: Henrich Lauko <[email protected]> Date: Fri, 30 Oct 2026 21:48:47 +0000 Subject: [PATCH 8/8] [CIR] Make fenv lowering follow the attribute's defaults An absent fenv parameter now takes its value in the default FP environment (tonearest, masked, non-strict), so the empty attribute is the unconstrained default. The lowering reads the parameters through the OrDefault helpers, as speculation does, and checks strict_except before the exception mode: strict exceptions keep the FP status exact even when masked. Clang codegen output and its lowering are unchanged. Assisted-by: Claude Code (Opus 5.5). --- clang/include/clang/CIR/Dialect/IR/CIRAttrs.td | 8 ++++---- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 18 +++++++----------- clang/test/CIR/Lowering/fenv.cir | 10 +++++++++- clang/unittests/CIR/FenvOpTest.cpp | 2 +- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 965256234f2d59..91f48f8bd6a97c 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -905,9 +905,9 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> { This means the FP status is written non-deterministically, and, if exceptions are unmasked, the instruction traps non-deterministically. - An absent `dynamic_rounding_mode` defaults to `unknown`, an absent - `except_mode` defaults to `masked`, and an absent `strict_except` defaults - to `false`. + An absent parameter takes its value in the default floating-point + environment: `dynamic_rounding_mode` defaults to `tonearest`, `except_mode` + to `masked`, and `strict_except` 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` @@ -940,7 +940,7 @@ def CIR_FenvAttr : CIR_Attr<"Fenv", "fenv"> { static constexpr cir::FPDynamicRoundingMode getDefaultDynamicRoundingMode() { - return cir::FPDynamicRoundingMode::Unknown; + return cir::FPDynamicRoundingMode::ToNearest; } static constexpr cir::FPExceptionMode getDefaultExceptionMode() { diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 3d5617bb2cde83..c74b222cc7c9d2 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -600,11 +600,7 @@ static mlir::LLVM::CallIntrinsicOp replaceOpWithCallLLVMIntrinsicOp( } static llvm::StringRef getConstrainedRoundingMetadata(cir::FenvAttr fenv) { - std::optional<cir::FPDynamicRoundingMode> rounding = - fenv.getDynamicRoundingMode(); - if (!rounding) - return "round.tonearest"; - switch (*rounding) { + switch (fenv.getDynamicRoundingModeOrDefault()) { case cir::FPDynamicRoundingMode::ToNearest: return "round.tonearest"; case cir::FPDynamicRoundingMode::Downward: @@ -621,14 +617,14 @@ static llvm::StringRef getConstrainedRoundingMetadata(cir::FenvAttr fenv) { llvm_unreachable("unknown FP dynamic rounding mode"); } +// Strict exceptions keep the FP status exact even when exceptions are masked. +// Otherwise, masked exceptions cannot trap, so they can be ignored. static llvm::StringRef getConstrainedExceptMetadata(cir::FenvAttr fenv) { - std::optional<cir::FPExceptionMode> exceptMode = fenv.getExceptMode(); - if (exceptMode == cir::FPExceptionMode::Masked) - return "fpexcept.ignore"; - mlir::BoolAttr strictExcept = fenv.getStrictExcept(); - if (!strictExcept) + if (fenv.getStrictExceptOrDefault()) + return "fpexcept.strict"; + if (fenv.getExceptionModeOrDefault() == cir::FPExceptionMode::Masked) return "fpexcept.ignore"; - return strictExcept.getValue() ? "fpexcept.strict" : "fpexcept.maytrap"; + return "fpexcept.maytrap"; } static mlir::Value diff --git a/clang/test/CIR/Lowering/fenv.cir b/clang/test/CIR/Lowering/fenv.cir index 400daa02c88a61..e8d08ce3afa716 100644 --- a/clang/test/CIR/Lowering/fenv.cir +++ b/clang/test/CIR/Lowering/fenv.cir @@ -55,13 +55,21 @@ module { %0 = cir.fadd %a, %b : !cir.float fenv<strict_except = true> // CHECK: llvm.mlir.metadata_as_value #llvm.md_string<"fpexcept.maytrap"> // CHECK: llvm.call_intrinsic "llvm.experimental.constrained.fadd" - %1 = cir.fadd %a, %b : !cir.float fenv<strict_except = false> + %1 = cir.fadd %a, %b : !cir.float fenv<except_mode = unknown, strict_except = false> // CHECK: llvm.mlir.metadata_as_value #llvm.md_string<"fpexcept.ignore"> // CHECK: llvm.call_intrinsic "llvm.experimental.constrained.fadd" %2 = cir.fadd %a, %b : !cir.float fenv<except_mode = masked, strict_except = false> // CHECK: llvm.mlir.metadata_as_value #llvm.md_string<"fpexcept.ignore"> // CHECK: llvm.call_intrinsic "llvm.experimental.constrained.fadd" %3 = cir.fadd %a, %b : !cir.float fenv<dynamic_rounding_mode = tonearest> + // An absent except_mode is masked. + // CHECK: llvm.mlir.metadata_as_value #llvm.md_string<"fpexcept.ignore"> + // CHECK: llvm.call_intrinsic "llvm.experimental.constrained.fadd" + %4 = cir.fadd %a, %b : !cir.float fenv<strict_except = false> + // Strict exceptions stay strict when masked. + // CHECK: llvm.mlir.metadata_as_value #llvm.md_string<"fpexcept.strict"> + // CHECK: llvm.call_intrinsic "llvm.experimental.constrained.fadd" + %5 = cir.fadd %a, %b : !cir.float fenv<except_mode = masked, strict_except = true> cir.return } diff --git a/clang/unittests/CIR/FenvOpTest.cpp b/clang/unittests/CIR/FenvOpTest.cpp index df19617c1bcac2..244778341bd6fd 100644 --- a/clang/unittests/CIR/FenvOpTest.cpp +++ b/clang/unittests/CIR/FenvOpTest.cpp @@ -170,7 +170,7 @@ TEST_F(CIRFenvOpTest, Speculatability) { EXPECT_TRUE(getEffects(ops[1]).empty()); cir::FenvAttr fenv = fenvOp.getFenv(); EXPECT_EQ(fenv.getDynamicRoundingModeOrDefault(), - cir::FPDynamicRoundingMode::Unknown); + cir::FPDynamicRoundingMode::ToNearest); EXPECT_EQ(fenv.getExceptionModeOrDefault(), cir::FPExceptionMode::Masked); EXPECT_FALSE(fenv.getStrictExceptOrDefault()); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
