https://github.com/koparasy updated https://github.com/llvm/llvm-project/pull/215382
>From 92c7b5cb675bf8330f061b76097d1015800054cf Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Mon, 10 Aug 2026 11:52:06 -0700 Subject: [PATCH 1/4] [CIR][CodeGen] Emit cir.fmuladd for FP-contracted mul+add/sub Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 89 ++++++ .../CIR/CodeGen/fp-contract-on-pragma.cpp | 122 +++++++++ clang/test/CIR/CodeGen/fp-contract-pragma.cpp | 258 ++++++++++++++++++ clang/test/CIR/CodeGen/fp-contract.c | 86 ++++++ 4 files changed, 555 insertions(+) create mode 100644 clang/test/CIR/CodeGen/fp-contract-on-pragma.cpp create mode 100644 clang/test/CIR/CodeGen/fp-contract-pragma.cpp create mode 100644 clang/test/CIR/CodeGen/fp-contract.c diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 8d660a0a2c721..f3e8e646c3c07 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -1948,6 +1948,89 @@ static bool isIntegerVectorBinOp(mlir::Type ty) { return vecTy && mlir::isa<cir::IntType>(vecTy.getElementType()); } +// Construct a cir.fmuladd op to represent a fused mul-add of `mulOp` and +// `addend`. Use negMul and negAdd to negate the first operand of the mul or +// the addend respectively. This allows fmuladd to represent a*b-c, or c-a*b. +// Patterns in LLVM should catch the negated forms and translate them to +// efficient operations. +static mlir::Value buildFMulAdd(cir::FMulOp mulOp, mlir::Value addend, + CIRGenBuilderTy &builder, bool negMul, + bool negAdd) { + const mlir::Location loc = mulOp.getLoc(); + mlir::Value mulOp0 = mulOp.getLhs(); + mlir::Value mulOp1 = mulOp.getRhs(); + if (negMul) + mulOp0 = builder.createFNeg(loc, mulOp0); + if (negAdd) + addend = builder.createFNeg(loc, addend); + + mlir::Value fmuladd = + cir::FMulAddOp::create(builder, loc, addend.getType(), mulOp0, mulOp1, + addend, builder.getConstrainedFPAttr()); + mulOp.erase(); + return fmuladd; +} + +// Check whether it would be legal to emit a cir.fmuladd op to represent op +// and if so, build it. +// +// Checks that (a) the operation is fusable, and (b) -ffp-contract=on. +// Does NOT check the type of the operation - it's assumed that this function +// will be called from contexts where it's known that the type is contractable. +static mlir::Value tryEmitFMulAdd(const BinOpInfo &op, CIRGenBuilderTy &builder, + bool isSub = false) { + assert((op.opcode == BO_Add || op.opcode == BO_AddAssign || + op.opcode == BO_Sub || op.opcode == BO_SubAssign) && + "Only fadd/fsub can be the root of an fmuladd."); + + // Check whether this op is marked as fusable. + if (!op.fpFeatures.allowFPContractWithinStatement()) + return nullptr; + + mlir::Value lhs = op.lhs; + mlir::Value rhs = op.rhs; + + // Peek through fneg to look for fmul. Make sure the fneg has no other users, + // and that it is the only use of its operand. + bool negLHS = false; + if (auto lhsNeg = lhs.getDefiningOp<cir::FNegOp>()) { + if (lhsNeg.getResult().use_empty() && lhsNeg.getInput().hasOneUse()) { + lhs = lhsNeg.getInput(); + negLHS = true; + } + } + + bool negRHS = false; + if (auto rhsNeg = rhs.getDefiningOp<cir::FNegOp>()) { + if (rhsNeg.getResult().use_empty() && rhsNeg.getInput().hasOneUse()) { + rhs = rhsNeg.getInput(); + negRHS = true; + } + } + + // We have a potentially fusable op. Look for a mul on one of the operands. + // Also make sure that the mul result isn't used directly. In that case, + // there's no point creating a muladd operation. + if (auto lhsMul = lhs.getDefiningOp<cir::FMulOp>()) { + if (lhsMul.getResult().use_empty() || negLHS) { + // If we looked through fneg, erase it. + if (negLHS) + op.lhs.getDefiningOp<cir::FNegOp>().erase(); + return buildFMulAdd(lhsMul, op.rhs, builder, negLHS, isSub); + } + } + if (auto rhsMul = rhs.getDefiningOp<cir::FMulOp>()) { + if (rhsMul.getResult().use_empty() || negRHS) { + // If we looked through fneg, erase it. + if (negRHS) + op.rhs.getDefiningOp<cir::FNegOp>().erase(); + return buildFMulAdd(rhsMul, op.lhs, builder, isSub ^ negRHS, false); + } + } + + return nullptr; +} + mlir::Value ScalarExprEmitter::emitMul(const BinOpInfo &ops) { const mlir::Location loc = cgf.getLoc(ops.loc); if (!isIntegerVectorBinOp(ops.lhs.getType()) && @@ -2047,6 +2130,9 @@ mlir::Value ScalarExprEmitter::emitAdd(const BinOpInfo &ops) { if (cir::isFPOrVectorOfFPType(ops.lhs.getType())) { CIRGenFunction::CIRGenFPOptionsRAII FPOptsRAII(cgf, ops.fpFeatures); + // Try to form an fmuladd. + if (mlir::Value fmuladd = tryEmitFMulAdd(ops, builder)) + return fmuladd; return builder.createFAdd(loc, ops.lhs, ops.rhs); } @@ -2095,6 +2181,9 @@ mlir::Value ScalarExprEmitter::emitSub(const BinOpInfo &ops) { if (cir::isFPOrVectorOfFPType(ops.lhs.getType())) { CIRGenFunction::CIRGenFPOptionsRAII FPOptsRAII(cgf, ops.fpFeatures); + // Try to form an fmuladd. + if (mlir::Value fmuladd = tryEmitFMulAdd(ops, builder, /*isSub=*/true)) + return fmuladd; return builder.createFSub(loc, ops.lhs, ops.rhs); } diff --git a/clang/test/CIR/CodeGen/fp-contract-on-pragma.cpp b/clang/test/CIR/CodeGen/fp-contract-on-pragma.cpp new file mode 100644 index 0000000000000..2c04dcfd6afc3 --- /dev/null +++ b/clang/test/CIR/CodeGen/fp-contract-on-pragma.cpp @@ -0,0 +1,122 @@ +// ClangIR port of clang/test/CodeGen/fp-contract-on-pragma.cpp. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -fclangir -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -emit-llvm %s -o %t-ogcg.ll +// RUN: FileCheck --input-file=%t-ogcg.ll %s -check-prefix=OGCG + +// Is FP_CONTRACT honored in a simple case? +float fp_contract_1(float a, float b, float c) { +#pragma clang fp contract(on) + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_1fff +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmul +// LLVM-LABEL: @_Z13fp_contract_1fff +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z13fp_contract_1fff +// OGCG: call float @llvm.fmuladd.f32 + +// Is FP_CONTRACT state cleared on exiting compound statements? +float fp_contract_2(float a, float b, float c) { + { +#pragma clang fp contract(on) + } + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_2fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z13fp_contract_2fff +// LLVM: %[[M:.*]] = fmul float +// LLVM: fadd float %[[M]], +// OGCG-LABEL: @_Z13fp_contract_2fff +// OGCG: %[[M:.*]] = fmul float +// OGCG: fadd float %[[M]], + +// Does FP_CONTRACT survive template instantiation? +class Foo {}; +Foo operator+(Foo, Foo); + +template <typename T> +T template_muladd(T a, T b, T c) { +#pragma clang fp contract(on) + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z15template_muladdIfET_S0_S0_S0_ +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_Z15template_muladdIfET_S0_S0_S0_ +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z15template_muladdIfET_S0_S0_S0_ +// OGCG: call {{.*}}float @llvm.fmuladd.f32 + +// fp_contract_3 is just a caller; the fused op lives in the instantiated +// template_muladd checked above. It is emitted in a different order under the +// classic CodeGen path, so it carries no checks of its own here. +float fp_contract_3(float a, float b, float c) { + return template_muladd<float>(a, b, c); +} + +template <typename T> +class fp_contract_4 { + float method(float a, float b, float c) { +#pragma clang fp contract(on) + return a * b + c; + } +}; +template class fp_contract_4<int>; +// CIR-LABEL: cir.func {{.*}}@_ZN13fp_contract_4IiE6methodEfff +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_ZN13fp_contract_4IiE6methodEfff +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_ZN13fp_contract_4IiE6methodEfff +// OGCG: call float @llvm.fmuladd.f32 + +// Check file-scoped FP_CONTRACT +#pragma clang fp contract(on) +float fp_contract_5(float a, float b, float c) { + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_5fff +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_Z13fp_contract_5fff +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z13fp_contract_5fff +// OGCG: call float @llvm.fmuladd.f32 + +#pragma clang fp contract(off) +float fp_contract_6(float a, float b, float c) { + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_6fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z13fp_contract_6fff +// LLVM: %[[M:.*]] = fmul float +// LLVM: fadd float %[[M]], +// OGCG-LABEL: @_Z13fp_contract_6fff +// OGCG: %[[M:.*]] = fmul float +// OGCG: fadd float %[[M]], + +// If the multiply has multiple uses, don't produce fmuladd. +// This used to assert (PR25719): +// https://llvm.org/bugs/show_bug.cgi?id=25719 +float fp_contract_7(float a, float b, float c) { +#pragma clang fp contract(on) + return (a = 2 * b) - c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_7fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fsub %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z13fp_contract_7fff +// LLVM: %[[M:.*]] = fmul float +// LLVM: fsub float %[[M]], +// OGCG-LABEL: @_Z13fp_contract_7fff +// OGCG: %[[M:.*]] = fmul float +// OGCG: fsub float %[[M]], diff --git a/clang/test/CIR/CodeGen/fp-contract-pragma.cpp b/clang/test/CIR/CodeGen/fp-contract-pragma.cpp new file mode 100644 index 0000000000000..6819f150205fe --- /dev/null +++ b/clang/test/CIR/CodeGen/fp-contract-pragma.cpp @@ -0,0 +1,258 @@ +// ClangIR port of clang/test/CodeGen/fp-contract-pragma.cpp. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -fclangir -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -emit-llvm %s -o %t-ogcg.ll +// RUN: FileCheck --input-file=%t-ogcg.ll %s -check-prefix=OGCG + +// Is FP_CONTRACT honored in a simple case? +float fp_contract_1(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_1fff +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmul +// LLVM-LABEL: @_Z13fp_contract_1fff +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z13fp_contract_1fff +// OGCG: call float @llvm.fmuladd.f32 + +// Is FP_CONTRACT state cleared on exiting compound statements? +float fp_contract_2(float a, float b, float c) { + { + #pragma STDC FP_CONTRACT ON + } + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_2fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z13fp_contract_2fff +// LLVM: %[[M:.*]] = fmul float +// LLVM: fadd float %[[M]], +// OGCG-LABEL: @_Z13fp_contract_2fff +// OGCG: %[[M:.*]] = fmul float +// OGCG: fadd float %[[M]], + +// Does FP_CONTRACT survive template instantiation? +class Foo {}; +Foo operator+(Foo, Foo); + +template <typename T> +T template_muladd(T a, T b, T c) { + #pragma STDC FP_CONTRACT ON + return a * b + c; +} +// The fmuladd is emitted in the instantiated template body. +// CIR-LABEL: cir.func {{.*}}@_Z15template_muladdIfET_S0_S0_S0_ +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_Z15template_muladdIfET_S0_S0_S0_ +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z15template_muladdIfET_S0_S0_S0_ +// OGCG: call {{.*}}float @llvm.fmuladd.f32 + +// fp_contract_3 is just a caller; the fused op lives in the instantiated +// template_muladd checked above. It is emitted in a different order under the +// classic CodeGen path, so it carries no checks of its own here. +float fp_contract_3(float a, float b, float c) { + return template_muladd<float>(a, b, c); +} + +template<typename T> class fp_contract_4 { + float method(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return a * b + c; + } +}; +template class fp_contract_4<int>; +// CIR-LABEL: cir.func {{.*}}@_ZN13fp_contract_4IiE6methodEfff +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_ZN13fp_contract_4IiE6methodEfff +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_ZN13fp_contract_4IiE6methodEfff +// OGCG: call float @llvm.fmuladd.f32 + +// Check file-scoped FP_CONTRACT +#pragma STDC FP_CONTRACT ON +float fp_contract_5(float a, float b, float c) { + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_5fff +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_Z13fp_contract_5fff +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z13fp_contract_5fff +// OGCG: call float @llvm.fmuladd.f32 + +#pragma STDC FP_CONTRACT OFF +float fp_contract_6(float a, float b, float c) { + return a * b + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_6fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z13fp_contract_6fff +// LLVM: %[[M:.*]] = fmul float +// LLVM: fadd float %[[M]], +// OGCG-LABEL: @_Z13fp_contract_6fff +// OGCG: %[[M:.*]] = fmul float +// OGCG: fadd float %[[M]], + +// If the multiply has multiple uses, don't produce fmuladd. +// This used to assert (PR25719): +// https://llvm.org/bugs/show_bug.cgi?id=25719 +float fp_contract_7(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return (a = 2 * b) - c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_7fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fsub %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z13fp_contract_7fff +// LLVM: %[[M:.*]] = fmul float +// LLVM: fsub float %[[M]], +// OGCG-LABEL: @_Z13fp_contract_7fff +// OGCG: %[[M:.*]] = fmul float +// OGCG: fsub float %[[M]], + +// a * b - c => fmuladd(a, b, -c) +float fp_contract_8(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return a * b - c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_8fff +// CIR: cir.fneg %{{.*}} : !cir.float +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_Z13fp_contract_8fff +// LLVM: fneg float +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z13fp_contract_8fff +// OGCG: fneg float +// OGCG: call float @llvm.fmuladd.f32 + +// c - a * b => fmuladd(-a, b, c) (mul on the RHS of a subtraction) +float fp_contract_9(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return c - a * b; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_9fff +// CIR: cir.fneg %{{.*}} : !cir.float +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_Z13fp_contract_9fff +// LLVM: fneg float +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z13fp_contract_9fff +// OGCG: fneg float +// OGCG: call float @llvm.fmuladd.f32 + +// -(a * b) + c => fmuladd(-a, b, c) (peek through fneg on the LHS) +float fp_contract_10(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return -(a * b) + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z14fp_contract_10fff +// CIR: cir.fneg %{{.*}} : !cir.float +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fadd +// LLVM-LABEL: @_Z14fp_contract_10fff +// LLVM: fneg float +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z14fp_contract_10fff +// OGCG: fneg float +// OGCG: call float @llvm.fmuladd.f32 + +// -(a * b) - c => fmuladd(-a, b, -c) (fneg both the mul operand and addend) +float fp_contract_11(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return -(a * b) - c; +} +// CIR-LABEL: cir.func {{.*}}@_Z14fp_contract_11fff +// CIR: cir.fneg %{{.*}} : !cir.float +// CIR: cir.fneg %{{.*}} : !cir.float +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_Z14fp_contract_11fff +// LLVM: fneg float +// LLVM: fneg float +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z14fp_contract_11fff +// OGCG: fneg float +// OGCG: fneg float +// OGCG: call float @llvm.fmuladd.f32 + +// c + -(a * b) => fmuladd(-a, b, c) (peek through fneg on the RHS) +float fp_contract_12(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return c + -(a * b); +} +// CIR-LABEL: cir.func {{.*}}@_Z14fp_contract_12fff +// CIR: cir.fneg %{{.*}} : !cir.float +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fadd +// LLVM-LABEL: @_Z14fp_contract_12fff +// LLVM: fneg float +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z14fp_contract_12fff +// OGCG: fneg float +// OGCG: call float @llvm.fmuladd.f32 + +// c - -(a * b) => fmuladd(a, b, c) (the two negations cancel; no fneg) +float fp_contract_13(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + return c - -(a * b); +} +// CIR-LABEL: cir.func {{.*}}@_Z14fp_contract_13fff +// CIR-NOT: cir.fneg +// CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// LLVM-LABEL: @_Z14fp_contract_13fff +// LLVM-NOT: fneg float +// LLVM: call float @llvm.fmuladd.f32 +// OGCG-LABEL: @_Z14fp_contract_13fff +// OGCG-NOT: fneg float +// OGCG: call float @llvm.fmuladd.f32 + +// Mul reused by the assignment, so no fusion. At -O0 the negation stays an +// fneg+fadd instead of the fsub the -O3 original test expects. +float fp_contract_14(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + float d; + return (d = -(a * b)) + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z14fp_contract_14fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fneg %{{.*}} : !cir.float +// CIR: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z14fp_contract_14fff +// LLVM: fmul float +// LLVM: fneg float +// LLVM: fadd float +// OGCG-LABEL: @_Z14fp_contract_14fff +// OGCG: fmul float +// OGCG: fneg float +// OGCG: fadd float + +// Same as above, with the negation applied to the assignment result. +float fp_contract_15(float a, float b, float c) { + #pragma STDC FP_CONTRACT ON + float d; + return -(d = (a * b)) + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z14fp_contract_15fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fneg %{{.*}} : !cir.float +// CIR: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z14fp_contract_15fff +// LLVM: fmul float +// LLVM: fneg float +// LLVM: fadd float +// OGCG-LABEL: @_Z14fp_contract_15fff +// OGCG: fmul float +// OGCG: fneg float +// OGCG: fadd float diff --git a/clang/test/CIR/CodeGen/fp-contract.c b/clang/test/CIR/CodeGen/fp-contract.c new file mode 100644 index 0000000000000..a27ead3d127d3 --- /dev/null +++ b/clang/test/CIR/CodeGen/fp-contract.c @@ -0,0 +1,86 @@ +// Test that -ffp-contract=on fuses a*b+c / a*b-c into cir.fmuladd and that +// -ffp-contract=off does not. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -ffp-contract=on -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR-ON +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -ffp-contract=off -emit-cir %s -o %t-off.cir +// RUN: FileCheck --input-file=%t-off.cir %s -check-prefix=CIR-OFF + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -ffp-contract=on -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM-ON +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -ffp-contract=off -emit-llvm %s -o %t-off.ll +// RUN: FileCheck --input-file=%t-off.ll %s -check-prefix=LLVM-OFF + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -ffp-contract=on -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG-ON +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -ffp-contract=off -emit-llvm %s -o %t-off.ll +// RUN: FileCheck --input-file=%t-off.ll %s -check-prefix=OGCG-OFF + +// a * b + c => fmuladd(a, b, c) +float fmuladd_add(float a, float b, float c) { + return a * b + c; +} +// CIR-ON-LABEL: cir.func {{.*}}@fmuladd_add +// CIR-ON: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float +// CIR-ON-NOT: cir.fmul + +// CIR-OFF-LABEL: cir.func {{.*}}@fmuladd_add +// CIR-OFF: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR-OFF: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-OFF-NOT: cir.fmuladd + +// LLVM-ON-LABEL: @fmuladd_add +// LLVM-ON: call float @llvm.fmuladd.f32 +// LLVM-OFF-LABEL: @fmuladd_add +// LLVM-OFF: fmul float +// LLVM-OFF: fadd float + +// OGCG-ON-LABEL: @fmuladd_add +// OGCG-ON: call float @llvm.fmuladd.f32 +// OGCG-OFF-LABEL: @fmuladd_add +// OGCG-OFF: fmul float +// OGCG-OFF: fadd float + +// c + a * b => fmuladd(a, b, c) (mul on the RHS) +float fmuladd_add_rhs(float a, float b, float c) { + return c + a * b; +} +// CIR-ON-LABEL: cir.func {{.*}}@fmuladd_add_rhs +// CIR-ON: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float + +// LLVM-ON-LABEL: @fmuladd_add_rhs +// LLVM-ON: call float @llvm.fmuladd.f32 + +// a * b - c => fmuladd(a, b, -c) +float fmuladd_sub(float a, float b, float c) { + return a * b - c; +} +// CIR-ON-LABEL: cir.func {{.*}}@fmuladd_sub +// CIR-ON: %[[NEG:.*]] = cir.fneg %{{.*}} : !cir.float +// CIR-ON: cir.fmuladd %{{.*}}, %{{.*}}, %[[NEG]] : !cir.float + +// LLVM-ON-LABEL: @fmuladd_sub +// LLVM-ON: %[[NEG:.*]] = fneg float +// LLVM-ON: call float @llvm.fmuladd.f32(float %{{.*}}, float %{{.*}}, float %[[NEG]]) + +// If the mul result is used elsewhere, it must NOT be fused. +float no_fmuladd_reused_mul(float a, float b, float c, float *p) { + float m = a * b; + *p = m; + return m + c; +} +// CIR-ON-LABEL: cir.func {{.*}}@no_fmuladd_reused_mul +// CIR-ON: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR-ON: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-ON-NOT: cir.fmuladd + +// Vector: a * b + c => fmuladd on the vector type. +typedef float float4 __attribute__((ext_vector_type(4))); +float4 fmuladd_vec(float4 a, float4 b, float4 c) { + return a * b + c; +} +// CIR-ON-LABEL: cir.func {{.*}}@fmuladd_vec +// CIR-ON: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.vector<4 x !cir.float> + +// LLVM-ON-LABEL: @fmuladd_vec +// LLVM-ON: call <4 x float> @llvm.fmuladd.v4f32 >From 4d0ac875c52ee79abdc73507e8accc03d3ce7f7a Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Tue, 11 Aug 2026 14:28:01 -0700 Subject: [PATCH 2/4] Address comments --- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 32 +++++++++++--------- clang/test/CIR/CodeGen/fp-contract.c | 34 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index f3e8e646c3c07..0025ab5b94088 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -1953,10 +1953,9 @@ static bool isIntegerVectorBinOp(mlir::Type ty) { // the addend respectively. This allows fmuladd to represent a*b-c, or c-a*b. // Patterns in LLVM should catch the negated forms and translate them to // efficient operations. -static mlir::Value buildFMulAdd(cir::FMulOp mulOp, mlir::Value addend, - CIRGenBuilderTy &builder, bool negMul, - bool negAdd) { - const mlir::Location loc = mulOp.getLoc(); +static mlir::Value buildFMulAdd(mlir::Location loc, cir::FMulOp mulOp, + mlir::Value addend, CIRGenBuilderTy &builder, + bool negMul, bool negAdd) { mlir::Value mulOp0 = mulOp.getLhs(); mlir::Value mulOp1 = mulOp.getRhs(); if (negMul) @@ -1964,9 +1963,11 @@ static mlir::Value buildFMulAdd(cir::FMulOp mulOp, mlir::Value addend, if (negAdd) addend = builder.createFNeg(loc, addend); - mlir::Value fmuladd = - cir::FMulAddOp::create(builder, loc, addend.getType(), mulOp0, mulOp1, - addend, builder.getConstrainedFPAttr()); + // Carry the mul's fenv attribute so a constrained fmul yields a constrained + // fmuladd; the builder is under the add's FP options, not the mul's. + mlir::Value fmuladd = cir::FMulAddOp::create( + builder, loc, addend.getType(), mulOp0, mulOp1, addend, + mulOp.getFenvAttr()); mulOp.erase(); return fmuladd; } @@ -1977,13 +1978,16 @@ static mlir::Value buildFMulAdd(cir::FMulOp mulOp, mlir::Value addend, // Checks that (a) the operation is fusable, and (b) -ffp-contract=on. // Does NOT check the type of the operation - it's assumed that this function // will be called from contexts where it's known that the type is contractable. -static mlir::Value tryEmitFMulAdd(const BinOpInfo &op, CIRGenBuilderTy &builder, - bool isSub = false) { +static mlir::Value tryEmitFMulAdd(mlir::Location loc, const BinOpInfo &op, + CIRGenBuilderTy &builder, bool isSub = false) { assert((op.opcode == BO_Add || op.opcode == BO_AddAssign || op.opcode == BO_Sub || op.opcode == BO_SubAssign) && "Only fadd/fsub can be the root of an fmuladd."); - // Check whether this op is marked as fusable. + // Check whether this op is fusable, i.e. -ffp-contract=on. -ffp-contract=fast + // needs fast-math flags on the fmul/fadd, which CIR does not model yet, so it + // fuses nowhere for now. + assert(!cir::MissingFeatures::fastMathFlags()); if (!op.fpFeatures.allowFPContractWithinStatement()) return nullptr; @@ -2016,7 +2020,7 @@ static mlir::Value tryEmitFMulAdd(const BinOpInfo &op, CIRGenBuilderTy &builder, // If we looked through fneg, erase it. if (negLHS) op.lhs.getDefiningOp<cir::FNegOp>().erase(); - return buildFMulAdd(lhsMul, op.rhs, builder, negLHS, isSub); + return buildFMulAdd(loc, lhsMul, op.rhs, builder, negLHS, isSub); } } if (auto rhsMul = rhs.getDefiningOp<cir::FMulOp>()) { @@ -2024,7 +2028,7 @@ static mlir::Value tryEmitFMulAdd(const BinOpInfo &op, CIRGenBuilderTy &builder, // If we looked through fneg, erase it. if (negRHS) op.rhs.getDefiningOp<cir::FNegOp>().erase(); - return buildFMulAdd(rhsMul, op.lhs, builder, isSub ^ negRHS, false); + return buildFMulAdd(loc, rhsMul, op.lhs, builder, isSub ^ negRHS, false); } } @@ -2131,7 +2135,7 @@ mlir::Value ScalarExprEmitter::emitAdd(const BinOpInfo &ops) { if (cir::isFPOrVectorOfFPType(ops.lhs.getType())) { CIRGenFunction::CIRGenFPOptionsRAII FPOptsRAII(cgf, ops.fpFeatures); // Try to form an fmuladd. - if (mlir::Value fmuladd = tryEmitFMulAdd(ops, builder)) + if (mlir::Value fmuladd = tryEmitFMulAdd(loc, ops, builder)) return fmuladd; return builder.createFAdd(loc, ops.lhs, ops.rhs); } @@ -2182,7 +2186,7 @@ mlir::Value ScalarExprEmitter::emitSub(const BinOpInfo &ops) { if (cir::isFPOrVectorOfFPType(ops.lhs.getType())) { CIRGenFunction::CIRGenFPOptionsRAII FPOptsRAII(cgf, ops.fpFeatures); // Try to form an fmuladd. - if (mlir::Value fmuladd = tryEmitFMulAdd(ops, builder, /*isSub=*/true)) + if (mlir::Value fmuladd = tryEmitFMulAdd(loc, ops, builder, /*isSub=*/true)) return fmuladd; return builder.createFSub(loc, ops.lhs, ops.rhs); } diff --git a/clang/test/CIR/CodeGen/fp-contract.c b/clang/test/CIR/CodeGen/fp-contract.c index a27ead3d127d3..c752ca997f445 100644 --- a/clang/test/CIR/CodeGen/fp-contract.c +++ b/clang/test/CIR/CodeGen/fp-contract.c @@ -16,6 +16,15 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -ffp-contract=off -emit-llvm %s -o %t-off.ll // RUN: FileCheck --input-file=%t-off.ll %s -check-prefix=OGCG-OFF +// Under strict FP the fused op carries an fenv attribute and lowers to the +// constrained fmuladd intrinsic. +// 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-STRICT +// 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-STRICT +// 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-STRICT + // a * b + c => fmuladd(a, b, c) float fmuladd_add(float a, float b, float c) { return a * b + c; @@ -50,6 +59,8 @@ float fmuladd_add_rhs(float a, float b, float c) { // LLVM-ON-LABEL: @fmuladd_add_rhs // LLVM-ON: call float @llvm.fmuladd.f32 +// OGCG-ON-LABEL: @fmuladd_add_rhs +// OGCG-ON: call float @llvm.fmuladd.f32 // a * b - c => fmuladd(a, b, -c) float fmuladd_sub(float a, float b, float c) { @@ -74,6 +85,15 @@ float no_fmuladd_reused_mul(float a, float b, float c, float *p) { // CIR-ON: cir.fadd %{{.*}}, %{{.*}} : !cir.float // CIR-ON-NOT: cir.fmuladd +// LLVM-ON-LABEL: @no_fmuladd_reused_mul +// LLVM-ON: fmul float +// LLVM-ON: fadd float +// LLVM-ON-NOT: call float @llvm.fmuladd.f32 +// OGCG-ON-LABEL: @no_fmuladd_reused_mul +// OGCG-ON: fmul float +// OGCG-ON: fadd float +// OGCG-ON-NOT: call float @llvm.fmuladd.f32 + // Vector: a * b + c => fmuladd on the vector type. typedef float float4 __attribute__((ext_vector_type(4))); float4 fmuladd_vec(float4 a, float4 b, float4 c) { @@ -84,3 +104,17 @@ float4 fmuladd_vec(float4 a, float4 b, float4 c) { // LLVM-ON-LABEL: @fmuladd_vec // LLVM-ON: call <4 x float> @llvm.fmuladd.v4f32 +// OGCG-ON-LABEL: @fmuladd_vec +// OGCG-ON: call <4 x float> @llvm.fmuladd.v4f32 + +// Strict FP: fused op carries an fenv attr, lowering to the constrained +// fmuladd intrinsic. +float fmuladd_strict(float a, float b, float c) { + return a * b + c; +} +// CIR-STRICT-LABEL: cir.func {{.*}}@fmuladd_strict +// CIR-STRICT: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float {fenv = #cir.fenv<{{.*}}strict_except = true>} +// LLVM-STRICT-LABEL: @fmuladd_strict +// LLVM-STRICT: call float @llvm.experimental.constrained.fmuladd.f32 +// OGCG-STRICT-LABEL: @fmuladd_strict +// OGCG-STRICT: call float @llvm.experimental.constrained.fmuladd.f32 >From 5b90237f793453556c3e6e0e7de6a87a8a1d25a6 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Tue, 11 Aug 2026 14:38:13 -0700 Subject: [PATCH 3/4] Format --- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 0025ab5b94088..74f524e92eec7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -1965,9 +1965,9 @@ static mlir::Value buildFMulAdd(mlir::Location loc, cir::FMulOp mulOp, // Carry the mul's fenv attribute so a constrained fmul yields a constrained // fmuladd; the builder is under the add's FP options, not the mul's. - mlir::Value fmuladd = cir::FMulAddOp::create( - builder, loc, addend.getType(), mulOp0, mulOp1, addend, - mulOp.getFenvAttr()); + mlir::Value fmuladd = + cir::FMulAddOp::create(builder, loc, addend.getType(), mulOp0, mulOp1, + addend, mulOp.getFenvAttr()); mulOp.erase(); return fmuladd; } @@ -1979,7 +1979,8 @@ static mlir::Value buildFMulAdd(mlir::Location loc, cir::FMulOp mulOp, // Does NOT check the type of the operation - it's assumed that this function // will be called from contexts where it's known that the type is contractable. static mlir::Value tryEmitFMulAdd(mlir::Location loc, const BinOpInfo &op, - CIRGenBuilderTy &builder, bool isSub = false) { + CIRGenBuilderTy &builder, + bool isSub = false) { assert((op.opcode == BO_Add || op.opcode == BO_AddAssign || op.opcode == BO_Sub || op.opcode == BO_SubAssign) && "Only fadd/fsub can be the root of an fmuladd."); @@ -2186,7 +2187,8 @@ mlir::Value ScalarExprEmitter::emitSub(const BinOpInfo &ops) { if (cir::isFPOrVectorOfFPType(ops.lhs.getType())) { CIRGenFunction::CIRGenFPOptionsRAII FPOptsRAII(cgf, ops.fpFeatures); // Try to form an fmuladd. - if (mlir::Value fmuladd = tryEmitFMulAdd(loc, ops, builder, /*isSub=*/true)) + if (mlir::Value fmuladd = + tryEmitFMulAdd(loc, ops, builder, /*isSub=*/true)) return fmuladd; return builder.createFSub(loc, ops.lhs, ops.rhs); } >From 53f68f437e43b0f284d2c2f7ab3078a87977b183 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Wed, 12 Aug 2026 11:11:17 -0700 Subject: [PATCH 4/4] Address comments --- .../CIR/CodeGen/fp-contract-on-pragma.cpp | 38 +++++++------- clang/test/CIR/CodeGen/fp-contract-pragma.cpp | 49 ++----------------- clang/test/CIR/CodeGen/fp-contract.c | 24 ++------- 3 files changed, 27 insertions(+), 84 deletions(-) diff --git a/clang/test/CIR/CodeGen/fp-contract-on-pragma.cpp b/clang/test/CIR/CodeGen/fp-contract-on-pragma.cpp index 2c04dcfd6afc3..1996896edd26b 100644 --- a/clang/test/CIR/CodeGen/fp-contract-on-pragma.cpp +++ b/clang/test/CIR/CodeGen/fp-contract-on-pragma.cpp @@ -1,11 +1,12 @@ // ClangIR port of clang/test/CodeGen/fp-contract-on-pragma.cpp. +// The CIR-lowered and classic CodeGen LLVM IR match here, so both feed LLVM. // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -fclangir -emit-cir %s -o %t.cir // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -fclangir -emit-llvm %s -o %t.ll // RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -emit-llvm %s -o %t-ogcg.ll -// RUN: FileCheck --input-file=%t-ogcg.ll %s -check-prefix=OGCG +// RUN: FileCheck --input-file=%t-ogcg.ll %s -check-prefix=LLVM // Is FP_CONTRACT honored in a simple case? float fp_contract_1(float a, float b, float c) { @@ -17,8 +18,6 @@ float fp_contract_1(float a, float b, float c) { // CIR-NOT: cir.fmul // LLVM-LABEL: @_Z13fp_contract_1fff // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z13fp_contract_1fff -// OGCG: call float @llvm.fmuladd.f32 // Is FP_CONTRACT state cleared on exiting compound statements? float fp_contract_2(float a, float b, float c) { @@ -34,9 +33,6 @@ float fp_contract_2(float a, float b, float c) { // LLVM-LABEL: @_Z13fp_contract_2fff // LLVM: %[[M:.*]] = fmul float // LLVM: fadd float %[[M]], -// OGCG-LABEL: @_Z13fp_contract_2fff -// OGCG: %[[M:.*]] = fmul float -// OGCG: fadd float %[[M]], // Does FP_CONTRACT survive template instantiation? class Foo {}; @@ -50,9 +46,7 @@ T template_muladd(T a, T b, T c) { // CIR-LABEL: cir.func {{.*}}@_Z15template_muladdIfET_S0_S0_S0_ // CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float // LLVM-LABEL: @_Z15template_muladdIfET_S0_S0_S0_ -// LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z15template_muladdIfET_S0_S0_S0_ -// OGCG: call {{.*}}float @llvm.fmuladd.f32 +// LLVM: call {{.*}}float @llvm.fmuladd.f32 // fp_contract_3 is just a caller; the fused op lives in the instantiated // template_muladd checked above. It is emitted in a different order under the @@ -73,8 +67,6 @@ template class fp_contract_4<int>; // CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float // LLVM-LABEL: @_ZN13fp_contract_4IiE6methodEfff // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_ZN13fp_contract_4IiE6methodEfff -// OGCG: call float @llvm.fmuladd.f32 // Check file-scoped FP_CONTRACT #pragma clang fp contract(on) @@ -85,8 +77,6 @@ float fp_contract_5(float a, float b, float c) { // CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float // LLVM-LABEL: @_Z13fp_contract_5fff // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z13fp_contract_5fff -// OGCG: call float @llvm.fmuladd.f32 #pragma clang fp contract(off) float fp_contract_6(float a, float b, float c) { @@ -99,9 +89,6 @@ float fp_contract_6(float a, float b, float c) { // LLVM-LABEL: @_Z13fp_contract_6fff // LLVM: %[[M:.*]] = fmul float // LLVM: fadd float %[[M]], -// OGCG-LABEL: @_Z13fp_contract_6fff -// OGCG: %[[M:.*]] = fmul float -// OGCG: fadd float %[[M]], // If the multiply has multiple uses, don't produce fmuladd. // This used to assert (PR25719): @@ -117,6 +104,19 @@ float fp_contract_7(float a, float b, float c) { // LLVM-LABEL: @_Z13fp_contract_7fff // LLVM: %[[M:.*]] = fmul float // LLVM: fsub float %[[M]], -// OGCG-LABEL: @_Z13fp_contract_7fff -// OGCG: %[[M:.*]] = fmul float -// OGCG: fsub float %[[M]], + +// contract(on) only fuses within a statement: a mul and add in separate +// statements are not contracted. +float fp_contract_8(float a, float b, float c) { +#pragma clang fp contract(on) + float t = a * b; + return t + c; +} +// CIR-LABEL: cir.func {{.*}}@_Z13fp_contract_8fff +// CIR: cir.fmul %{{.*}}, %{{.*}} : !cir.float +// CIR: cir.fadd %{{.*}}, %{{.*}} : !cir.float +// CIR-NOT: cir.fmuladd +// LLVM-LABEL: @_Z13fp_contract_8fff +// LLVM: fmul float +// LLVM: fadd float +// LLVM-NOT: call float @llvm.fmuladd.f32 diff --git a/clang/test/CIR/CodeGen/fp-contract-pragma.cpp b/clang/test/CIR/CodeGen/fp-contract-pragma.cpp index 6819f150205fe..f5f0c0884dc52 100644 --- a/clang/test/CIR/CodeGen/fp-contract-pragma.cpp +++ b/clang/test/CIR/CodeGen/fp-contract-pragma.cpp @@ -1,11 +1,12 @@ // ClangIR port of clang/test/CodeGen/fp-contract-pragma.cpp. +// The CIR-lowered and classic CodeGen LLVM IR match here, so both feed LLVM. // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -fclangir -emit-cir %s -o %t.cir // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -fclangir -emit-llvm %s -o %t.ll // RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -Wno-unused-value -emit-llvm %s -o %t-ogcg.ll -// RUN: FileCheck --input-file=%t-ogcg.ll %s -check-prefix=OGCG +// RUN: FileCheck --input-file=%t-ogcg.ll %s -check-prefix=LLVM // Is FP_CONTRACT honored in a simple case? float fp_contract_1(float a, float b, float c) { @@ -17,8 +18,6 @@ float fp_contract_1(float a, float b, float c) { // CIR-NOT: cir.fmul // LLVM-LABEL: @_Z13fp_contract_1fff // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z13fp_contract_1fff -// OGCG: call float @llvm.fmuladd.f32 // Is FP_CONTRACT state cleared on exiting compound statements? float fp_contract_2(float a, float b, float c) { @@ -34,9 +33,6 @@ float fp_contract_2(float a, float b, float c) { // LLVM-LABEL: @_Z13fp_contract_2fff // LLVM: %[[M:.*]] = fmul float // LLVM: fadd float %[[M]], -// OGCG-LABEL: @_Z13fp_contract_2fff -// OGCG: %[[M:.*]] = fmul float -// OGCG: fadd float %[[M]], // Does FP_CONTRACT survive template instantiation? class Foo {}; @@ -51,9 +47,7 @@ T template_muladd(T a, T b, T c) { // CIR-LABEL: cir.func {{.*}}@_Z15template_muladdIfET_S0_S0_S0_ // CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float // LLVM-LABEL: @_Z15template_muladdIfET_S0_S0_S0_ -// LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z15template_muladdIfET_S0_S0_S0_ -// OGCG: call {{.*}}float @llvm.fmuladd.f32 +// LLVM: call {{.*}}float @llvm.fmuladd.f32 // fp_contract_3 is just a caller; the fused op lives in the instantiated // template_muladd checked above. It is emitted in a different order under the @@ -73,8 +67,6 @@ template class fp_contract_4<int>; // CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float // LLVM-LABEL: @_ZN13fp_contract_4IiE6methodEfff // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_ZN13fp_contract_4IiE6methodEfff -// OGCG: call float @llvm.fmuladd.f32 // Check file-scoped FP_CONTRACT #pragma STDC FP_CONTRACT ON @@ -85,8 +77,6 @@ float fp_contract_5(float a, float b, float c) { // CIR: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float // LLVM-LABEL: @_Z13fp_contract_5fff // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z13fp_contract_5fff -// OGCG: call float @llvm.fmuladd.f32 #pragma STDC FP_CONTRACT OFF float fp_contract_6(float a, float b, float c) { @@ -99,9 +89,6 @@ float fp_contract_6(float a, float b, float c) { // LLVM-LABEL: @_Z13fp_contract_6fff // LLVM: %[[M:.*]] = fmul float // LLVM: fadd float %[[M]], -// OGCG-LABEL: @_Z13fp_contract_6fff -// OGCG: %[[M:.*]] = fmul float -// OGCG: fadd float %[[M]], // If the multiply has multiple uses, don't produce fmuladd. // This used to assert (PR25719): @@ -117,9 +104,6 @@ float fp_contract_7(float a, float b, float c) { // LLVM-LABEL: @_Z13fp_contract_7fff // LLVM: %[[M:.*]] = fmul float // LLVM: fsub float %[[M]], -// OGCG-LABEL: @_Z13fp_contract_7fff -// OGCG: %[[M:.*]] = fmul float -// OGCG: fsub float %[[M]], // a * b - c => fmuladd(a, b, -c) float fp_contract_8(float a, float b, float c) { @@ -132,9 +116,6 @@ float fp_contract_8(float a, float b, float c) { // LLVM-LABEL: @_Z13fp_contract_8fff // LLVM: fneg float // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z13fp_contract_8fff -// OGCG: fneg float -// OGCG: call float @llvm.fmuladd.f32 // c - a * b => fmuladd(-a, b, c) (mul on the RHS of a subtraction) float fp_contract_9(float a, float b, float c) { @@ -147,9 +128,6 @@ float fp_contract_9(float a, float b, float c) { // LLVM-LABEL: @_Z13fp_contract_9fff // LLVM: fneg float // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z13fp_contract_9fff -// OGCG: fneg float -// OGCG: call float @llvm.fmuladd.f32 // -(a * b) + c => fmuladd(-a, b, c) (peek through fneg on the LHS) float fp_contract_10(float a, float b, float c) { @@ -163,9 +141,6 @@ float fp_contract_10(float a, float b, float c) { // LLVM-LABEL: @_Z14fp_contract_10fff // LLVM: fneg float // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z14fp_contract_10fff -// OGCG: fneg float -// OGCG: call float @llvm.fmuladd.f32 // -(a * b) - c => fmuladd(-a, b, -c) (fneg both the mul operand and addend) float fp_contract_11(float a, float b, float c) { @@ -180,10 +155,6 @@ float fp_contract_11(float a, float b, float c) { // LLVM: fneg float // LLVM: fneg float // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z14fp_contract_11fff -// OGCG: fneg float -// OGCG: fneg float -// OGCG: call float @llvm.fmuladd.f32 // c + -(a * b) => fmuladd(-a, b, c) (peek through fneg on the RHS) float fp_contract_12(float a, float b, float c) { @@ -197,9 +168,6 @@ float fp_contract_12(float a, float b, float c) { // LLVM-LABEL: @_Z14fp_contract_12fff // LLVM: fneg float // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z14fp_contract_12fff -// OGCG: fneg float -// OGCG: call float @llvm.fmuladd.f32 // c - -(a * b) => fmuladd(a, b, c) (the two negations cancel; no fneg) float fp_contract_13(float a, float b, float c) { @@ -212,9 +180,6 @@ float fp_contract_13(float a, float b, float c) { // LLVM-LABEL: @_Z14fp_contract_13fff // LLVM-NOT: fneg float // LLVM: call float @llvm.fmuladd.f32 -// OGCG-LABEL: @_Z14fp_contract_13fff -// OGCG-NOT: fneg float -// OGCG: call float @llvm.fmuladd.f32 // Mul reused by the assignment, so no fusion. At -O0 the negation stays an // fneg+fadd instead of the fsub the -O3 original test expects. @@ -232,10 +197,6 @@ float fp_contract_14(float a, float b, float c) { // LLVM: fmul float // LLVM: fneg float // LLVM: fadd float -// OGCG-LABEL: @_Z14fp_contract_14fff -// OGCG: fmul float -// OGCG: fneg float -// OGCG: fadd float // Same as above, with the negation applied to the assignment result. float fp_contract_15(float a, float b, float c) { @@ -252,7 +213,3 @@ float fp_contract_15(float a, float b, float c) { // LLVM: fmul float // LLVM: fneg float // LLVM: fadd float -// OGCG-LABEL: @_Z14fp_contract_15fff -// OGCG: fmul float -// OGCG: fneg float -// OGCG: fadd float diff --git a/clang/test/CIR/CodeGen/fp-contract.c b/clang/test/CIR/CodeGen/fp-contract.c index c752ca997f445..297655a6f4a80 100644 --- a/clang/test/CIR/CodeGen/fp-contract.c +++ b/clang/test/CIR/CodeGen/fp-contract.c @@ -1,5 +1,6 @@ // Test that -ffp-contract=on fuses a*b+c / a*b-c into cir.fmuladd and that -// -ffp-contract=off does not. +// -ffp-contract=off does not. The CIR-lowered and classic CodeGen LLVM IR +// match here, so both feed the LLVM-* prefixes. // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -ffp-contract=on -emit-cir %s -o %t.cir // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR-ON @@ -12,9 +13,9 @@ // RUN: FileCheck --input-file=%t-off.ll %s -check-prefix=LLVM-OFF // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -ffp-contract=on -emit-llvm %s -o %t.ll -// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG-ON +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM-ON // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -ffp-contract=off -emit-llvm %s -o %t-off.ll -// RUN: FileCheck --input-file=%t-off.ll %s -check-prefix=OGCG-OFF +// RUN: FileCheck --input-file=%t-off.ll %s -check-prefix=LLVM-OFF // Under strict FP the fused op carries an fenv attribute and lowers to the // constrained fmuladd intrinsic. @@ -23,7 +24,7 @@ // 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-STRICT // 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-STRICT +// RUN: FileCheck --input-file=%t-strict-ogcg.ll %s -check-prefix=LLVM-STRICT // a * b + c => fmuladd(a, b, c) float fmuladd_add(float a, float b, float c) { @@ -44,11 +45,6 @@ float fmuladd_add(float a, float b, float c) { // LLVM-OFF: fmul float // LLVM-OFF: fadd float -// OGCG-ON-LABEL: @fmuladd_add -// OGCG-ON: call float @llvm.fmuladd.f32 -// OGCG-OFF-LABEL: @fmuladd_add -// OGCG-OFF: fmul float -// OGCG-OFF: fadd float // c + a * b => fmuladd(a, b, c) (mul on the RHS) float fmuladd_add_rhs(float a, float b, float c) { @@ -59,8 +55,6 @@ float fmuladd_add_rhs(float a, float b, float c) { // LLVM-ON-LABEL: @fmuladd_add_rhs // LLVM-ON: call float @llvm.fmuladd.f32 -// OGCG-ON-LABEL: @fmuladd_add_rhs -// OGCG-ON: call float @llvm.fmuladd.f32 // a * b - c => fmuladd(a, b, -c) float fmuladd_sub(float a, float b, float c) { @@ -89,10 +83,6 @@ float no_fmuladd_reused_mul(float a, float b, float c, float *p) { // LLVM-ON: fmul float // LLVM-ON: fadd float // LLVM-ON-NOT: call float @llvm.fmuladd.f32 -// OGCG-ON-LABEL: @no_fmuladd_reused_mul -// OGCG-ON: fmul float -// OGCG-ON: fadd float -// OGCG-ON-NOT: call float @llvm.fmuladd.f32 // Vector: a * b + c => fmuladd on the vector type. typedef float float4 __attribute__((ext_vector_type(4))); @@ -104,8 +94,6 @@ float4 fmuladd_vec(float4 a, float4 b, float4 c) { // LLVM-ON-LABEL: @fmuladd_vec // LLVM-ON: call <4 x float> @llvm.fmuladd.v4f32 -// OGCG-ON-LABEL: @fmuladd_vec -// OGCG-ON: call <4 x float> @llvm.fmuladd.v4f32 // Strict FP: fused op carries an fenv attr, lowering to the constrained // fmuladd intrinsic. @@ -116,5 +104,3 @@ float fmuladd_strict(float a, float b, float c) { // CIR-STRICT: cir.fmuladd %{{.*}}, %{{.*}}, %{{.*}} : !cir.float {fenv = #cir.fenv<{{.*}}strict_except = true>} // LLVM-STRICT-LABEL: @fmuladd_strict // LLVM-STRICT: call float @llvm.experimental.constrained.fmuladd.f32 -// OGCG-STRICT-LABEL: @fmuladd_strict -// OGCG-STRICT: call float @llvm.experimental.constrained.fmuladd.f32 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
