https://github.com/AmrDeveloper created https://github.com/llvm/llvm-project/pull/224851
Add support for experimental strict floating-point for Complex Add & Sub ops >From a5ec0eb6f809f018e4cb424a92857a16c2fc0de6 Mon Sep 17 00:00:00 2001 From: Amr Hesham <[email protected]> Date: Sat, 19 Sep 2026 19:43:04 +0200 Subject: [PATCH] [CIR] Support strict floating-point in Complex Add & Sub --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 18 ++++ clang/include/clang/CIR/Dialect/IR/CIROps.td | 13 ++- clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp | 4 +- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 70 +++++++++--- clang/test/CIR/CodeGen/complex-strict-fp.cpp | 100 ++++++++++++++++++ 5 files changed, 187 insertions(+), 18 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 3a52960000a14..66b199e8b5ca7 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -326,6 +326,24 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return cir::ComplexImagOp::create(*this, loc, resultType, operand); } + mlir::Value createComplexAdd(mlir::Location loc, mlir::Value lhs, + mlir::Value rhs) { + cir::FenvAttr fenv; + if (isAnyFloatingPointType( + mlir::cast<cir::ComplexType>(lhs.getType()).getElementType())) + fenv = getConstrainedFPAttr(); + return cir::ComplexAddOp::create(*this, loc, lhs, rhs, fenv); + } + + mlir::Value createComplexSub(mlir::Location loc, mlir::Value lhs, + mlir::Value rhs) { + cir::FenvAttr fenv; + if (isAnyFloatingPointType( + mlir::cast<cir::ComplexType>(lhs.getType()).getElementType())) + fenv = getConstrainedFPAttr(); + return cir::ComplexSubOp::create(*this, loc, lhs, rhs, fenv); + } + mlir::Value createComplexConj(mlir::Location loc, mlir::Value operand) { return cir::ComplexConjOp::create(*this, loc, operand.getType(), operand); } diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index d5fe9882e4ec8..4340e59697d11 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -6598,12 +6598,23 @@ def CIR_ComplexImagPtrOp : CIR_ComplexPartPtrOp<"complex.imag_ptr"> { class CIR_ComplexBinOp<string mnemonic> : CIR_Op<mnemonic, [Pure, SameOperandsAndResultType]> { - let arguments = (ins CIR_ComplexType:$lhs, CIR_ComplexType:$rhs); + let arguments = (ins + CIR_ComplexType:$lhs, + CIR_ComplexType:$rhs, + OptionalAttr<CIR_FenvAttr>:$fenv + ); + let results = (outs CIR_ComplexType:$result); let assemblyFormat = [{ $lhs `,` $rhs `:` qualified(type($result)) attr-dict }]; + + let builders = [ + OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{ + build($_builder, $_state, lhs, rhs, cir::FenvAttr{}); + }]> + ]; } def CIR_ComplexAddOp : CIR_ComplexBinOp<"complex.add"> { diff --git a/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp b/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp index 1fab2352b10ce..78f848733f33c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp @@ -623,7 +623,7 @@ mlir::Value ComplexExprEmitter::emitBinAdd(const BinOpInfo &op) { if (mlir::isa<cir::ComplexType>(op.lhs.getType()) && mlir::isa<cir::ComplexType>(op.rhs.getType())) - return cir::ComplexAddOp::create(builder, op.loc, op.lhs, op.rhs); + return builder.createComplexAdd(op.loc, op.lhs, op.rhs); auto createAdd = [&](mlir::Location loc, mlir::Value a, mlir::Value b) { return cir::isFPOrVectorOfFPType(a.getType()) @@ -651,7 +651,7 @@ mlir::Value ComplexExprEmitter::emitBinSub(const BinOpInfo &op) { if (mlir::isa<cir::ComplexType>(op.lhs.getType()) && mlir::isa<cir::ComplexType>(op.rhs.getType())) - return cir::ComplexSubOp::create(builder, op.loc, op.lhs, op.rhs); + return builder.createComplexSub(op.loc, op.lhs, op.rhs); auto createSub = [&](mlir::Location loc, mlir::Value a, mlir::Value b) { return cir::isFPOrVectorOfFPType(a.getType()) diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 50adaad64a763..98ad6da10c09b 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -560,6 +560,24 @@ 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, @@ -5207,11 +5225,22 @@ mlir::LogicalResult CIRToLLVMComplexAddOpLowering::matchAndRewrite( rhsImag); } else { assert(!cir::MissingFeatures::fastMathFlags()); - assert(!cir::MissingFeatures::fpConstraints()); - newReal = mlir::LLVM::FAddOp::create(rewriter, loc, complexElemTy, lhsReal, - rhsReal); - newImag = mlir::LLVM::FAddOp::create(rewriter, loc, complexElemTy, lhsImag, - rhsImag); + 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 = @@ -5290,11 +5319,22 @@ mlir::LogicalResult CIRToLLVMComplexSubOpLowering::matchAndRewrite( rhsImag); } else { assert(!cir::MissingFeatures::fastMathFlags()); - assert(!cir::MissingFeatures::fpConstraints()); - newReal = mlir::LLVM::FSubOp::create(rewriter, loc, complexElemTy, lhsReal, - rhsReal); - newImag = mlir::LLVM::FSubOp::create(rewriter, loc, complexElemTy, lhsImag, - rhsImag); + 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 = @@ -5665,9 +5705,9 @@ mlir::LogicalResult CIRToLLVMIndirectBrOpLowering::matchAndRewrite( // If the poison attribute is set, use llvm.mlir.poison as the address. // This happens when the block has no predecessors and is essentially // unreachable. Do NOT erase the block argument directly, as that violates - // the MLIR dialect conversion framework contract (the framework tracks block - // arguments and will clean them up). A block with no predecessors simply - // produces no PHI node. + // the MLIR dialect conversion framework contract (the framework tracks + // block arguments and will clean them up). A block with no predecessors + // simply produces no PHI node. if (op.getPoison()) { auto llvmPtrType = mlir::LLVM::LLVMPointerType::get(rewriter.getContext()); targetAddr = @@ -5786,8 +5826,8 @@ mlir::LogicalResult CIRToLLVMMemChrOpLowering::matchAndRewrite( return mlir::success(); } -// Function to do the clear-padding operation. This is a faithful translation of -// CGBuiltin.cpp's ClearPadding function. +// Function to do the clear-padding operation. This is a faithful +// translation of CGBuiltin.cpp's ClearPadding function. static void clearPadding(mlir::ConversionPatternRewriter &rewriter, mlir::Location loc, mlir::Value inputPtr, uint64_t baseAlignment, 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 0000000000000..1b2af0ba1664c --- /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.add %[[TMP_A]], %[[TMP_B]] : !cir.complex<!cir.float> {fenv = #cir.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.sub %[[TMP_A]], %[[TMP_B]] : !cir.complex<!cir.float> {fenv = #cir.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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
