[clang] [CIR] Support strict floating-point in Complex Add & Sub (PR #224851)
Amr Hesham via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 13 07:55:34 PDT 2026
https://github.com/AmrDeveloper updated https://github.com/llvm/llvm-project/pull/224851
>From 5ceb7fafd8b6eb758d170875bddb5ef317ec949f Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
Date: Thu, 8 Oct 2026 20:18:55 +0200
Subject: [PATCH] [CIR] Support strict floating-point in Complex Add & Sub
---
.../CIR/Dialect/Builder/CIRBaseBuilder.h | 12 ++-
clang/include/clang/CIR/Dialect/IR/CIROps.td | 9 +-
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 65 ++++++++++--
clang/test/CIR/CodeGen/complex-strict-fp.cpp | 100 ++++++++++++++++++
4 files changed, 170 insertions(+), 16 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/complex-strict-fp.cpp
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 24c98752bfd640..dde2e729dc8c2d 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -331,16 +331,20 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
mlir::Value createComplexAdd(mlir::Location loc, mlir::Value lhs,
mlir::Value rhs) {
auto complexTy = mlir::cast<cir::ComplexType>(lhs.getType());
- if (cir::isAnyFloatingPointType(complexTy.getElementType()))
- return cir::ComplexFAddOp::create(*this, loc, lhs, rhs);
+ if (cir::isAnyFloatingPointType(complexTy.getElementType())) {
+ cir::FenvAttr fenv = getConstrainedFPAttr();
+ return cir::ComplexFAddOp::create(*this, loc, lhs, rhs, fenv);
+ }
return cir::ComplexAddOp::create(*this, loc, lhs, rhs);
}
mlir::Value createComplexSub(mlir::Location loc, mlir::Value lhs,
mlir::Value rhs) {
auto complexTy = mlir::cast<cir::ComplexType>(lhs.getType());
- if (cir::isAnyFloatingPointType(complexTy.getElementType()))
- return cir::ComplexFSubOp::create(*this, loc, lhs, rhs);
+ if (cir::isAnyFloatingPointType(complexTy.getElementType())) {
+ cir::FenvAttr fenv = getConstrainedFPAttr();
+ return cir::ComplexFSubOp::create(*this, loc, lhs, rhs, fenv);
+ }
return cir::ComplexSubOp::create(*this, loc, lhs, rhs);
}
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index d46b9135e618cf..50fde1239f7ff4 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -6954,11 +6954,16 @@ def CIR_ComplexDivOp : CIR_ComplexBinOp<"complex.div"> {
class CIR_ComplexFPBinOp<string mnemonic>
: CIR_Op<mnemonic, [Pure, SameOperandsAndResultType]> {
- let arguments = (ins CIR_ComplexOfFloatType:$lhs, CIR_ComplexOfFloatType:$rhs);
+ let arguments = (ins
+ CIR_ComplexOfFloatType:$lhs,
+ CIR_ComplexOfFloatType:$rhs,
+ OptionalAttr<CIR_FenvAttr>:$fenv
+ );
+
let results = (outs CIR_ComplexOfFloatType:$result);
let assemblyFormat = [{
- $lhs `,` $rhs `:` qualified(type($result)) attr-dict
+ $lhs `,` $rhs `:` qualified(type($result)) (`fenv` `` $fenv^)? attr-dict
}];
}
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index a04aca6b63b431..a6bed93cd7ebf3 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -594,6 +594,25 @@ mlir::LogicalResult lowerToConstrainedFPIntrinsic(
return mlir::success();
}
+static mlir::Value createConstrainedFPIntrinsicCall(
+ mlir::ConversionPatternRewriter &rewriter, mlir::Location loc,
+ mlir::ValueRange operands, mlir::Type llvmResTy, cir::FenvAttr fenv,
+ llvm::StringRef constrainedMnemonic, bool hasRoundingMode,
+ mlir::LLVM::FastmathFlags fastmathFlags = {}) {
+ llvm::SmallVector<mlir::Value> callOperands(operands.begin(), operands.end());
+ if (hasRoundingMode)
+ callOperands.push_back(createFenvMetadataValue(
+ rewriter, loc, getConstrainedRoundingMetadata(fenv)));
+
+ callOperands.push_back(createFenvMetadataValue(
+ rewriter, loc, getConstrainedExceptMetadata(fenv)));
+
+ mlir::LLVM::CallIntrinsicOp intrinsic = createCallLLVMIntrinsicOp(
+ rewriter, loc, "llvm.experimental.constrained." + constrainedMnemonic,
+ llvmResTy, callOperands, fastmathFlags);
+ return intrinsic->getResult(0);
+}
+
template <typename LLVMOp>
mlir::LogicalResult lowerConstrainableFPOp(
mlir::Operation *op, mlir::ValueRange operands, cir::FenvAttr fenv,
@@ -5500,11 +5519,24 @@ mlir::LogicalResult CIRToLLVMComplexFAddOpLowering::matchAndRewrite(
rewriter, loc, complexElemTy, rhs, ArrayRef(int64_t{1}));
assert(!cir::MissingFeatures::fastMathFlags());
- assert(!cir::MissingFeatures::fpConstraints());
- mlir::Value newReal = mlir::LLVM::FAddOp::create(rewriter, loc, complexElemTy,
- lhsReal, rhsReal);
- mlir::Value newImag = mlir::LLVM::FAddOp::create(rewriter, loc, complexElemTy,
- lhsImag, rhsImag);
+ mlir::Value newReal;
+ mlir::Value newImag;
+ if (cir::FenvAttr fenv = op.getFenvAttr()) {
+ newReal = createConstrainedFPIntrinsicCall(
+ rewriter, loc, {lhsReal, rhsReal}, complexElemTy, fenv,
+ /*constrainedMnemonic=*/"fadd",
+ /*hasRoundingMode=*/true);
+
+ newImag = createConstrainedFPIntrinsicCall(
+ rewriter, loc, {lhsImag, rhsImag}, complexElemTy, fenv,
+ /*constrainedMnemonic=*/"fadd",
+ /*hasRoundingMode=*/true);
+ } else {
+ newReal = mlir::LLVM::FAddOp::create(rewriter, loc, complexElemTy, lhsReal,
+ rhsReal);
+ newImag = mlir::LLVM::FAddOp::create(rewriter, loc, complexElemTy, lhsImag,
+ rhsImag);
+ }
mlir::Type complexLLVMTy =
getTypeConverter()->convertType(op.getResult().getType());
@@ -5540,11 +5572,24 @@ mlir::LogicalResult CIRToLLVMComplexFSubOpLowering::matchAndRewrite(
rewriter, loc, complexElemTy, rhs, ArrayRef(int64_t{1}));
assert(!cir::MissingFeatures::fastMathFlags());
- assert(!cir::MissingFeatures::fpConstraints());
- mlir::Value newReal = mlir::LLVM::FSubOp::create(rewriter, loc, complexElemTy,
- lhsReal, rhsReal);
- mlir::Value newImag = mlir::LLVM::FSubOp::create(rewriter, loc, complexElemTy,
- lhsImag, rhsImag);
+ mlir::Value newReal;
+ mlir::Value newImag;
+ if (cir::FenvAttr fenv = op.getFenvAttr()) {
+ newReal = createConstrainedFPIntrinsicCall(
+ rewriter, loc, {lhsReal, rhsReal}, complexElemTy, fenv,
+ /*constrainedMnemonic=*/"fsub",
+ /*hasRoundingMode=*/true);
+
+ newImag = createConstrainedFPIntrinsicCall(
+ rewriter, loc, {lhsImag, rhsImag}, complexElemTy, fenv,
+ /*constrainedMnemonic=*/"fsub",
+ /*hasRoundingMode=*/true);
+ } else {
+ newReal = mlir::LLVM::FSubOp::create(rewriter, loc, complexElemTy, lhsReal,
+ rhsReal);
+ newImag = mlir::LLVM::FSubOp::create(rewriter, loc, complexElemTy, lhsImag,
+ rhsImag);
+ }
mlir::Type complexLLVMTy =
getTypeConverter()->convertType(op.getResult().getType());
diff --git a/clang/test/CIR/CodeGen/complex-strict-fp.cpp b/clang/test/CIR/CodeGen/complex-strict-fp.cpp
new file mode 100644
index 00000000000000..9a7f0a145b28be
--- /dev/null
+++ b/clang/test/CIR/CodeGen/complex-strict-fp.cpp
@@ -0,0 +1,100 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -ffp-contract=on -fexperimental-strict-floating-point -ffp-exception-behavior=strict -emit-cir %s -o %t-strict.cir
+// RUN: FileCheck --input-file=%t-strict.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -ffp-contract=on -fexperimental-strict-floating-point -ffp-exception-behavior=strict -emit-llvm %s -o %t-strict.ll
+// RUN: FileCheck --input-file=%t-strict.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -ffp-contract=on -fexperimental-strict-floating-point -ffp-exception-behavior=strict -emit-llvm %s -o %t-strict-ogcg.ll
+// RUN: FileCheck --input-file=%t-strict-ogcg.ll %s -check-prefix=OGCG
+
+void complex_add() {
+ _Complex float a;
+ _Complex float b;
+ _Complex float c = a + b;
+}
+
+// CIR: %[[A_ADDR:.*]] = cir.alloca "a" {{.*}} : !cir.ptr<!cir.complex<!cir.float>>
+// CIR: %[[B_ADDR:.*]] = cir.alloca "b" {{.*}} : !cir.ptr<!cir.complex<!cir.float>>
+// CIR: %[[C_ADDR:.*]] = cir.alloca "c" {{.*}} init : !cir.ptr<!cir.complex<!cir.float>>
+// CIR: %[[TMP_A:.*]] = cir.load {{.*}} %[[A_ADDR]] : !cir.ptr<!cir.complex<!cir.float>>, !cir.complex<!cir.float>
+// CIR: %[[TMP_B:.*]] = cir.load {{.*}} %[[B_ADDR]] : !cir.ptr<!cir.complex<!cir.float>>, !cir.complex<!cir.float>
+// CIR: %[[RESULT:.*]] = cir.complex.fadd %[[TMP_A]], %[[TMP_B]] : !cir.complex<!cir.float> fenv<dynamic_rounding_mode = tonearest, except_mode = unknown, strict_except = true>
+// CIR: cir.store {{.*}} %[[RESULT]], %[[C_ADDR]] : !cir.complex<!cir.float>, !cir.ptr<!cir.complex<!cir.float>>
+
+// LLVM: %[[A_ADDR:.*]] = alloca { float, float }, align 4
+// LLVM: %[[B_ADDR:.*]] = alloca { float, float }, align 4
+// LLVM: %[[C_ADDR:.*]] = alloca { float, float }, align 4
+// LLVM: %[[TMP_A:.*]] = load { float, float }, ptr %[[A_ADDR]], align 4
+// LLVM: %[[TMP_B:.*]] = load { float, float }, ptr %[[B_ADDR]], align 4
+// LLVM: %[[A_REAL:.*]] = extractvalue { float, float } %[[TMP_A]], 0
+// LLVM: %[[A_IMAG:.*]] = extractvalue { float, float } %[[TMP_A]], 1
+// LLVM: %[[B_REAL:.*]] = extractvalue { float, float } %[[TMP_B]], 0
+// LLVM: %[[B_IMAG:.*]] = extractvalue { float, float } %[[TMP_B]], 1
+// LLVM: %[[RESULT_REAL:.*]] = call float @llvm.experimental.constrained.fadd.f32(float %[[A_REAL]], float %[[B_REAL]], metadata !"round.tonearest", metadata !"fpexcept.strict")
+// LLVM: %[[RESULT_IMAG:.*]] = call float @llvm.experimental.constrained.fadd.f32(float %[[A_IMAG]], float %[[B_IMAG]], metadata !"round.tonearest", metadata !"fpexcept.strict")
+// LLVM: %[[TMP_RESULT:.*]] = insertvalue { float, float } poison, float %[[RESULT_REAL]], 0
+// LLVM: %[[RESULT:.*]] = insertvalue { float, float } %[[TMP_RESULT]], float %[[RESULT_IMAG]], 1
+// LLVM: store { float, float } %[[RESULT]], ptr %[[C_ADDR]], align 4
+
+// OGCG: %[[A_ADDR:.*]] = alloca { float, float }, align 4
+// OGCG: %[[B_ADDR:.*]] = alloca { float, float }, align 4
+// OGCG: %[[C_ADDR:.*]] = alloca { float, float }, align 4
+// OGCG: %[[A_REAL_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[A_ADDR]], i32 0, i32 0
+// OGCG: %[[A_REAL:.*]] = load float, ptr %[[A_REAL_PTR]], align 4
+// OGCG: %[[A_IMAG_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[A_ADDR]], i32 0, i32 1
+// OGCG: %[[A_IMAG:.*]] = load float, ptr %[[A_IMAG_PTR]], align 4
+// OGCG: %[[B_REAL_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[B_ADDR]], i32 0, i32 0
+// OGCG: %[[B_REAL:.*]] = load float, ptr %[[B_REAL_PTR]], align 4
+// OGCG: %[[B_IMAG_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[B_ADDR]], i32 0, i32 1
+// OGCG: %[[B_IMAG:.*]] = load float, ptr %[[B_IMAG_PTR]], align 4
+// OGCG: %[[RESULT_REAL:.*]] = call float @llvm.experimental.constrained.fadd.f32(float %[[A_REAL]], float %[[B_REAL]], metadata !"round.tonearest", metadata !"fpexcept.strict") #2
+// OGCG: %[[RESULT_IMAG:.*]] = call float @llvm.experimental.constrained.fadd.f32(float %[[A_IMAG]], float %[[B_IMAG]], metadata !"round.tonearest", metadata !"fpexcept.strict") #2
+// OGCG: %[[C_REAL_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[C_ADDR]], i32 0, i32 0
+// OGCG: %[[C_IMAG_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[C_ADDR]], i32 0, i32 1
+// OGCG: store float %[[RESULT_REAL]], ptr %[[C_REAL_PTR]], align 4
+// OGCG: store float %[[RESULT_IMAG]], ptr %[[C_IMAG_PTR]], align 4
+
+void complex_sub() {
+ _Complex float a;
+ _Complex float b;
+ _Complex float c = a - b;
+}
+
+// CIR: %[[A_ADDR:.*]] = cir.alloca "a" {{.*}} : !cir.ptr<!cir.complex<!cir.float>>
+// CIR: %[[B_ADDR:.*]] = cir.alloca "b" {{.*}} : !cir.ptr<!cir.complex<!cir.float>>
+// CIR: %[[C_ADDR:.*]] = cir.alloca "c" {{.*}} init : !cir.ptr<!cir.complex<!cir.float>>
+// CIR: %[[TMP_A:.*]] = cir.load {{.*}} %[[A_ADDR]] : !cir.ptr<!cir.complex<!cir.float>>, !cir.complex<!cir.float>
+// CIR: %[[TMP_B:.*]] = cir.load {{.*}} %[[B_ADDR]] : !cir.ptr<!cir.complex<!cir.float>>, !cir.complex<!cir.float>
+// CIR: %[[RESULT:.*]] = cir.complex.fsub %[[TMP_A]], %[[TMP_B]] : !cir.complex<!cir.float> fenv<dynamic_rounding_mode = tonearest, except_mode = unknown, strict_except = true>
+// CIR: cir.store {{.*}} %[[RESULT]], %[[C_ADDR]] : !cir.complex<!cir.float>, !cir.ptr<!cir.complex<!cir.float>>
+
+// LLVM: %[[A_ADDR:.*]] = alloca { float, float }, align 4
+// LLVM: %[[B_ADDR:.*]] = alloca { float, float }, align 4
+// LLVM: %[[C_ADDR:.*]] = alloca { float, float }, align 4
+// LLVM: %[[TMP_A:.*]] = load { float, float }, ptr %[[A_ADDR]], align 4
+// LLVM: %[[TMP_B:.*]] = load { float, float }, ptr %[[B_ADDR]], align 4
+// LLVM: %[[A_REAL:.*]] = extractvalue { float, float } %[[TMP_A]], 0
+// LLVM: %[[A_IMAG:.*]] = extractvalue { float, float } %[[TMP_A]], 1
+// LLVM: %[[B_REAL:.*]] = extractvalue { float, float } %[[TMP_B]], 0
+// LLVM: %[[B_IMAG:.*]] = extractvalue { float, float } %[[TMP_B]], 1
+// LLVM: %[[RESULT_REAL:.*]] = call float @llvm.experimental.constrained.fsub.f32(float %[[A_REAL]], float %[[B_REAL]], metadata !"round.tonearest", metadata !"fpexcept.strict")
+// LLVM: %[[RESULT_IMAG:.*]] = call float @llvm.experimental.constrained.fsub.f32(float %[[A_IMAG]], float %[[B_IMAG]], metadata !"round.tonearest", metadata !"fpexcept.strict")
+// LLVM: %[[TMP_RESULT:.*]] = insertvalue { float, float } poison, float %[[RESULT_REAL]], 0
+// LLVM: %[[RESULT:.*]] = insertvalue { float, float } %[[TMP_RESULT]], float %[[RESULT_IMAG]], 1
+// LLVM: store { float, float } %[[RESULT]], ptr %[[C_ADDR]], align 4
+
+// OGCG: %[[A_ADDR:.*]] = alloca { float, float }, align 4
+// OGCG: %[[B_ADDR:.*]] = alloca { float, float }, align 4
+// OGCG: %[[C_ADDR:.*]] = alloca { float, float }, align 4
+// OGCG: %[[A_REAL_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[A_ADDR]], i32 0, i32 0
+// OGCG: %[[A_REAL:.*]] = load float, ptr %[[A_REAL_PTR]], align 4
+// OGCG: %[[A_IMAG_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[A_ADDR]], i32 0, i32 1
+// OGCG: %[[A_IMAG:.*]] = load float, ptr %[[A_IMAG_PTR]], align 4
+// OGCG: %[[B_REAL_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[B_ADDR]], i32 0, i32 0
+// OGCG: %[[B_REAL:.*]] = load float, ptr %[[B_REAL_PTR]], align 4
+// OGCG: %[[B_IMAG_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[B_ADDR]], i32 0, i32 1
+// OGCG: %[[B_IMAG:.*]] = load float, ptr %[[B_IMAG_PTR]], align 4
+// OGCG: %[[RESULT_REAL:.*]] = call float @llvm.experimental.constrained.fsub.f32(float %[[A_REAL]], float %[[B_REAL]], metadata !"round.tonearest", metadata !"fpexcept.strict") #2
+// OGCG: %[[RESULT_IMAG:.*]] = call float @llvm.experimental.constrained.fsub.f32(float %[[A_IMAG]], float %[[B_IMAG]], metadata !"round.tonearest", metadata !"fpexcept.strict") #2
+// OGCG: %[[C_REAL_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[C_ADDR]], i32 0, i32 0
+// OGCG: %[[C_IMAG_PTR:.*]] = getelementptr inbounds nuw { float, float }, ptr %[[C_ADDR]], i32 0, i32 1
+// OGCG: store float %[[RESULT_REAL]], ptr %[[C_REAL_PTR]], align 4
+// OGCG: store float %[[RESULT_IMAG]], ptr %[[C_IMAG_PTR]], align 4
More information about the cfe-commits
mailing list