[flang-commits] [clang] [flang] [Flang][Lower] Add complex sum reassociation (PR #214775)
Tom Eccles via flang-commits
flang-commits at lists.llvm.org
Fri Aug 7 08:48:59 PDT 2026
https://github.com/tblah created https://github.com/llvm/llvm-project/pull/214775
Third part of generalisations requested in #207377.
Extend the experimental split-sum transformation to complex addition and subtraction chains. Share the signed-term builder across explicitly supported real and complex categories while keeping integer expressions excluded.
There are no known effect on benchmarks as a result of this patch.
Assisted-by: Codex
>From 7dd15ee0cd04457d95cd9b03bba10a6c9c96ca6b Mon Sep 17 00:00:00 2001
From: Tom Eccles <tom.eccles at arm.com>
Date: Tue, 4 Aug 2026 14:18:23 +0100
Subject: [PATCH] [Flang][Lower] Add complex sum reassociation
Third part of generalisations requested in #207377.
Extend the experimental split-sum transformation to complex addition
and subtraction chains. Share the signed-term builder across explicitly
supported real and complex categories while keeping integer expressions
excluded.
There are no known effect on benchmarks as a result of this patch.
Assisted-by: Codex
---
clang/include/clang/Options/FlangOptions.td | 14 +--
flang/include/flang/Evaluate/tools.h | 6 +-
flang/lib/Evaluate/tools.cpp | 98 ++++++++++---------
flang/test/Driver/driver-help.f90 | 4 +-
.../split-sum-expression-tree-lowering.f90 | 88 +++++++++++++++++
5 files changed, 152 insertions(+), 58 deletions(-)
diff --git a/clang/include/clang/Options/FlangOptions.td b/clang/include/clang/Options/FlangOptions.td
index bafc063663fe2..3950847251828 100644
--- a/clang/include/clang/Options/FlangOptions.td
+++ b/clang/include/clang/Options/FlangOptions.td
@@ -315,16 +315,16 @@ defm real_sum_reassociation
"f", "real-sum-reassociation",
PosFlag<SetTrue, [], [],
"Enable Fortran-standard compliant reassociation within "
- "individual REAL sum expressions. This may change exact "
- "floating-point results">,
+ "individual REAL and COMPLEX sum expressions. This may "
+ "change exact floating-point results">,
NegFlag<SetFalse, [], [],
- "Disable reassociation within individual REAL sum "
- "expressions">>,
+ "Disable reassociation within individual REAL and COMPLEX "
+ "sum expressions">>,
DocBrief<[{
Enable Fortran-standard compliant reassociation within individual
- ``REAL`` sum expressions. This can improve optimization opportunities
- and may change exact floating-point results while preserving
- standard-conforming Fortran semantics.
+ ``REAL`` and ``COMPLEX`` sum expressions. This can improve optimization
+ opportunities and may change exact floating-point results while
+ preserving standard-conforming Fortran semantics.
}]>;
defm init_global_zero : BoolOptionWithoutMarshalling<"f", "init-global-zero",
diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index cce2325b33da7..c877ec5f5705b 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -1126,12 +1126,12 @@ bool HasProcedureRef(const Expr<SomeType> &expr);
// Predicate: does an expression contain a VOLATILE or ASYNCHRONOUS symbol?
bool HasVolatileOrAsynchronousSymbol(const Expr<SomeType> &expr);
-// Can a scalar real RHS expression in an assignment be rewritten as a split
-// sum expression tree?
+// Can a scalar real or complex RHS expression in an assignment be rewritten
+// as a split sum expression tree?
bool CanBuildSplitSumExpressionTree(
const Expr<SomeType> &lhs, const Expr<SomeType> &rhs);
-// Try to rewrite a scalar real sum as a split sum expression tree.
+// Try to rewrite a scalar real or complex sum as a split sum expression tree.
std::optional<Expr<SomeType>> TryBuildSplitSumExpressionTree(
const Expr<SomeType> &expr);
diff --git a/flang/lib/Evaluate/tools.cpp b/flang/lib/Evaluate/tools.cpp
index 34deaaea289c1..589aab5132a65 100644
--- a/flang/lib/Evaluate/tools.cpp
+++ b/flang/lib/Evaluate/tools.cpp
@@ -1382,76 +1382,79 @@ bool HasVolatileOrAsynchronousSymbol(const Expr<SomeType> &expr) {
namespace {
-template <int KIND> using Real = Type<common::TypeCategory::Real, KIND>;
+template <common::TypeCategory CAT, int KIND> using Numeric = Type<CAT, KIND>;
-template <int KIND> using RealExpr = Expr<Real<KIND>>;
+template <common::TypeCategory CAT, int KIND>
+using NumericExpr = Expr<Numeric<CAT, KIND>>;
-template <int KIND> struct SignedRealTerm {
- RealExpr<KIND> expr;
+template <common::TypeCategory CAT, int KIND> struct SignedNumericTerm {
+ NumericExpr<CAT, KIND> expr;
bool isPositive;
};
-template <int KIND> struct SignedRealExpr {
- RealExpr<KIND> expr;
+template <common::TypeCategory CAT, int KIND> struct SignedNumericExpr {
+ NumericExpr<CAT, KIND> expr;
bool isPositive;
};
-template <int KIND>
-static void flattenTopLevelAddSubtract(const RealExpr<KIND> &expr,
- llvm::SmallVectorImpl<SignedRealTerm<KIND>> &terms,
+template <common::TypeCategory CAT, int KIND>
+static void flattenTopLevelAddSubtract(const NumericExpr<CAT, KIND> &expr,
+ llvm::SmallVectorImpl<SignedNumericTerm<CAT, KIND>> &terms,
bool isPositive = true) {
- // Only flatten real Add and Subtract nodes. Every other node, including
+ // Only flatten Add and Subtract nodes. Every other node, including
// Parentheses, is one opaque signed term whose tree is preserved.
- if (const auto *add = std::get_if<Add<Real<KIND>>>(&expr.u)) {
+ if (const auto *add = std::get_if<Add<Numeric<CAT, KIND>>>(&expr.u)) {
flattenTopLevelAddSubtract(add->left(), terms, isPositive);
flattenTopLevelAddSubtract(add->right(), terms, isPositive);
return;
}
- if (const auto *subtract = std::get_if<Subtract<Real<KIND>>>(&expr.u)) {
+ if (const auto *subtract =
+ std::get_if<Subtract<Numeric<CAT, KIND>>>(&expr.u)) {
flattenTopLevelAddSubtract(subtract->left(), terms, isPositive);
flattenTopLevelAddSubtract(subtract->right(), terms, !isPositive);
return;
}
- terms.push_back(SignedRealTerm<KIND>{expr, isPositive});
+ terms.push_back(SignedNumericTerm<CAT, KIND>{expr, isPositive});
}
-template <int KIND>
-static SignedRealExpr<KIND> buildRightAssociatedSignedFold(
- llvm::MutableArrayRef<SignedRealTerm<KIND>> terms) {
+template <common::TypeCategory CAT, int KIND>
+static SignedNumericExpr<CAT, KIND> buildRightAssociatedSignedFold(
+ llvm::MutableArrayRef<SignedNumericTerm<CAT, KIND>> terms) {
assert(!terms.empty() && "cannot build empty signed fold");
const bool isPositive{terms.front().isPositive};
- RealExpr<KIND> result{std::move(terms.back().expr)};
+ NumericExpr<CAT, KIND> result{std::move(terms.back().expr)};
for (std::size_t i{terms.size() - 1}; i > 0; --i) {
- SignedRealTerm<KIND> &term{terms[i - 1]};
+ SignedNumericTerm<CAT, KIND> &term{terms[i - 1]};
const bool useAdd{term.isPositive == terms[i].isPositive};
if (useAdd)
- result = RealExpr<KIND>{
- Add<Real<KIND>>{std::move(term.expr), std::move(result)}};
+ result = NumericExpr<CAT, KIND>{
+ Add<Numeric<CAT, KIND>>{std::move(term.expr), std::move(result)}};
else
- result = RealExpr<KIND>{
- Subtract<Real<KIND>>{std::move(term.expr), std::move(result)}};
+ result = NumericExpr<CAT, KIND>{Subtract<Numeric<CAT, KIND>>{
+ std::move(term.expr), std::move(result)}};
}
- return SignedRealExpr<KIND>{std::move(result), isPositive};
+ return SignedNumericExpr<CAT, KIND>{std::move(result), isPositive};
}
-template <int KIND>
-static SignedRealExpr<KIND> buildSignedAdd(
- SignedRealExpr<KIND> left, SignedRealExpr<KIND> right) {
+template <common::TypeCategory CAT, int KIND>
+static SignedNumericExpr<CAT, KIND> buildSignedAdd(
+ SignedNumericExpr<CAT, KIND> left, SignedNumericExpr<CAT, KIND> right) {
if (left.isPositive == right.isPositive) {
- return SignedRealExpr<KIND>{
- RealExpr<KIND>{
- Add<Real<KIND>>{std::move(left.expr), std::move(right.expr)}},
+ return SignedNumericExpr<CAT, KIND>{
+ NumericExpr<CAT, KIND>{Add<Numeric<CAT, KIND>>{
+ std::move(left.expr), std::move(right.expr)}},
left.isPositive};
}
if (left.isPositive) {
- return SignedRealExpr<KIND>{
- RealExpr<KIND>{
- Subtract<Real<KIND>>{std::move(left.expr), std::move(right.expr)}},
+ return SignedNumericExpr<CAT, KIND>{
+ NumericExpr<CAT, KIND>{Subtract<Numeric<CAT, KIND>>{
+ std::move(left.expr), std::move(right.expr)}},
true};
}
// Prefer Y-X to introducing a unary negation for -X+Y.
- return SignedRealExpr<KIND>{RealExpr<KIND>{Subtract<Real<KIND>>{
- std::move(right.expr), std::move(left.expr)}},
+ return SignedNumericExpr<CAT, KIND>{
+ NumericExpr<CAT, KIND>{Subtract<Numeric<CAT, KIND>>{
+ std::move(right.expr), std::move(left.expr)}},
true};
}
@@ -1460,25 +1463,25 @@ static std::optional<Expr<SomeType>> tryBuildSplitSumExpressionTree(const T &) {
return std::nullopt;
}
-template <int KIND>
+template <common::TypeCategory CAT, int KIND>
static std::optional<Expr<SomeType>> tryBuildSplitSumExpressionTree(
- const RealExpr<KIND> &expr) {
- if (!std::get_if<Add<Real<KIND>>>(&expr.u) &&
- !std::get_if<Subtract<Real<KIND>>>(&expr.u))
+ const NumericExpr<CAT, KIND> &expr) {
+ if (!std::get_if<Add<Numeric<CAT, KIND>>>(&expr.u) &&
+ !std::get_if<Subtract<Numeric<CAT, KIND>>>(&expr.u))
return std::nullopt;
- llvm::SmallVector<SignedRealTerm<KIND>, 8> terms;
+ llvm::SmallVector<SignedNumericTerm<CAT, KIND>, 8> terms;
flattenTopLevelAddSubtract(expr, terms);
if (terms.size() <= 2)
return std::nullopt;
- llvm::MutableArrayRef<SignedRealTerm<KIND>> head{terms.data(), 2};
- llvm::MutableArrayRef<SignedRealTerm<KIND>> tail{
+ llvm::MutableArrayRef<SignedNumericTerm<CAT, KIND>> head{terms.data(), 2};
+ llvm::MutableArrayRef<SignedNumericTerm<CAT, KIND>> tail{
terms.data() + 2, terms.size() - 2};
- SignedRealExpr<KIND> headExpr = buildRightAssociatedSignedFold<KIND>(head);
- SignedRealExpr<KIND> tailExpr = buildRightAssociatedSignedFold<KIND>(tail);
- SignedRealExpr<KIND> result =
- buildSignedAdd<KIND>(std::move(tailExpr), std::move(headExpr));
+ SignedNumericExpr<CAT, KIND> headExpr = buildRightAssociatedSignedFold(head);
+ SignedNumericExpr<CAT, KIND> tailExpr = buildRightAssociatedSignedFold(tail);
+ SignedNumericExpr<CAT, KIND> result =
+ buildSignedAdd(std::move(tailExpr), std::move(headExpr));
assert(result.isPositive &&
"the first flattened term and therefore the split sum are positive");
return Expr<SomeType>{std::move(result.expr)};
@@ -1487,7 +1490,10 @@ static std::optional<Expr<SomeType>> tryBuildSplitSumExpressionTree(
template <common::TypeCategory CAT>
static std::optional<Expr<SomeType>> tryBuildSplitSumExpressionTree(
const Expr<SomeKind<CAT>> &expr) {
- if constexpr (CAT == common::TypeCategory::Real) {
+ // Keep the supported categories explicit: integer reassociation requires a
+ // separate intermediate-range policy.
+ if constexpr (CAT == common::TypeCategory::Real ||
+ CAT == common::TypeCategory::Complex) {
return common::visit(
[&](const auto &typedExpr) -> std::optional<Expr<SomeType>> {
return tryBuildSplitSumExpressionTree(typedExpr);
diff --git a/flang/test/Driver/driver-help.f90 b/flang/test/Driver/driver-help.f90
index e77fc460850b2..c7c9ef887c594 100644
--- a/flang/test/Driver/driver-help.f90
+++ b/flang/test/Driver/driver-help.f90
@@ -8,14 +8,14 @@
! HELP-EMPTY:
! HELP-NEXT:OPTIONS:
! HELP: -freal-sum-reassociation
-! HELP: Enable Fortran-standard compliant reassociation within individual REAL sum expressions
+! HELP: Enable Fortran-standard compliant reassociation within individual REAL and COMPLEX sum expressions
! HELP: may change exact floating-point results
! HELP-FC1:USAGE: flang
! HELP-FC1-EMPTY:
! HELP-FC1-NEXT:OPTIONS:
! HELP-FC1: -freal-sum-reassociation
-! HELP-FC1: Enable Fortran-standard compliant reassociation within individual REAL sum expressions
+! HELP-FC1: Enable Fortran-standard compliant reassociation within individual REAL and COMPLEX sum expressions
! HELP-FC1: may change exact floating-point results
! ERROR: error: unknown argument '-helps'; did you mean '-help'
diff --git a/flang/test/Lower/split-sum-expression-tree-lowering.f90 b/flang/test/Lower/split-sum-expression-tree-lowering.f90
index 1b1400f756177..93bb4396348c7 100644
--- a/flang/test/Lower/split-sum-expression-tree-lowering.f90
+++ b/flang/test/Lower/split-sum-expression-tree-lowering.f90
@@ -587,6 +587,94 @@ subroutine eligible_nested_unparenthesized_subtraction(x,a,b,c,d)
! DEFAULT: %[[RES:.*]] = arith.addf %[[XABC]], %[[DV]]
! DEFAULT: hlfir.assign %[[RES]] to %[[X]]#0
+! Complex addition and subtraction use the same signed-term split. The
+! parenthesized c-d remains one opaque no_reassoc value.
+! Default: (((x - a) + b) - (c-d))
+! Rewritten: (b - (c-d)) + (x - a)
+subroutine eligible_complex_signed_parenthesized(x,a,b,c,d)
+ complex(4) :: x,a,b,c,d
+ x = x - a + b - (c-d)
+end
+
+! SPLIT-LABEL: func.func @_QPeligible_complex_signed_parenthesized
+! SPLIT-DAG: %[[A:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEa"}
+! SPLIT-DAG: %[[B:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEb"}
+! SPLIT-DAG: %[[C:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEc"}
+! SPLIT-DAG: %[[D:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEd"}
+! SPLIT-DAG: %[[X:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEx"}
+! SPLIT: %[[BV:.*]] = fir.load %[[B]]#0
+! SPLIT: %[[CV:.*]] = fir.load %[[C]]#0
+! SPLIT: %[[DV:.*]] = fir.load %[[D]]#0
+! SPLIT: %[[CD_SUB:.*]] = fir.subc %[[CV]], %[[DV]] {{.*}} : complex<f32>
+! SPLIT: %[[CD:.*]] = hlfir.no_reassoc %[[CD_SUB]] : complex<f32>
+! SPLIT: %[[TAIL:.*]] = fir.subc %[[BV]], %[[CD]] {{.*}} : complex<f32>
+! SPLIT: %[[XV:.*]] = fir.load %[[X]]#0
+! SPLIT: %[[AV:.*]] = fir.load %[[A]]#0
+! SPLIT: %[[HEAD:.*]] = fir.subc %[[XV]], %[[AV]] {{.*}} : complex<f32>
+! SPLIT: %[[RES:.*]] = fir.addc %[[TAIL]], %[[HEAD]] {{.*}} : complex<f32>
+! SPLIT: hlfir.assign %[[RES]] to %[[X]]#0
+
+! DEFAULT-LABEL: func.func @_QPeligible_complex_signed_parenthesized
+! DEFAULT-DAG: %[[A:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEa"}
+! DEFAULT-DAG: %[[B:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEb"}
+! DEFAULT-DAG: %[[C:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEc"}
+! DEFAULT-DAG: %[[D:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEd"}
+! DEFAULT-DAG: %[[X:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_signed_parenthesizedEx"}
+! DEFAULT: %[[XV:.*]] = fir.load %[[X]]#0
+! DEFAULT: %[[AV:.*]] = fir.load %[[A]]#0
+! DEFAULT: %[[XA:.*]] = fir.subc %[[XV]], %[[AV]] {{.*}} : complex<f32>
+! DEFAULT: %[[BV:.*]] = fir.load %[[B]]#0
+! DEFAULT: %[[XAB:.*]] = fir.addc %[[XA]], %[[BV]] {{.*}} : complex<f32>
+! DEFAULT: %[[CV:.*]] = fir.load %[[C]]#0
+! DEFAULT: %[[DV:.*]] = fir.load %[[D]]#0
+! DEFAULT: %[[CD_SUB:.*]] = fir.subc %[[CV]], %[[DV]] {{.*}} : complex<f32>
+! DEFAULT: %[[CD:.*]] = hlfir.no_reassoc %[[CD_SUB]] : complex<f32>
+! DEFAULT: %[[RES:.*]] = fir.subc %[[XAB]], %[[CD]] {{.*}} : complex<f32>
+! DEFAULT: hlfir.assign %[[RES]] to %[[X]]#0
+
+! A second complex kind exercises category dispatch independently of kind.
+! Default: (((x + a) + b) + c)
+! Rewritten: (b + c) + (x + a)
+subroutine eligible_complex_kind8(x,a,b,c)
+ complex(8) :: x,a,b,c
+ x = x + a + b + c
+end
+
+! SPLIT-LABEL: func.func @_QPeligible_complex_kind8
+! SPLIT-DAG: %[[A:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_kind8Ea"}
+! SPLIT-DAG: %[[B:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_kind8Eb"}
+! SPLIT-DAG: %[[C:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_kind8Ec"}
+! SPLIT-DAG: %[[X:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFeligible_complex_kind8Ex"}
+! SPLIT: %[[BV:.*]] = fir.load %[[B]]#0
+! SPLIT: %[[CV:.*]] = fir.load %[[C]]#0
+! SPLIT: %[[TAIL:.*]] = fir.addc %[[BV]], %[[CV]] {{.*}} : complex<f64>
+! SPLIT: %[[XV:.*]] = fir.load %[[X]]#0
+! SPLIT: %[[AV:.*]] = fir.load %[[A]]#0
+! SPLIT: %[[HEAD:.*]] = fir.addc %[[XV]], %[[AV]] {{.*}} : complex<f64>
+! SPLIT: %[[RES:.*]] = fir.addc %[[TAIL]], %[[HEAD]] {{.*}} : complex<f64>
+! SPLIT: hlfir.assign %[[RES]] to %[[X]]#0
+
+! It isn't as useful to re-write integer expressions because the middle-end can
+! already re-associate them somewhat (within the bounds of avoiding overflow).
+subroutine guard_integer(x,a,b,c)
+ integer :: x,a,b,c
+ x = x + a - b + c
+end
+
+! NO-REWRITE-LABEL: func.func @_QPguard_integer
+! NO-REWRITE-DAG: %[[A:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFguard_integerEa"}
+! NO-REWRITE-DAG: %[[B:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFguard_integerEb"}
+! NO-REWRITE-DAG: %[[C:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFguard_integerEc"}
+! NO-REWRITE-DAG: %[[X:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFguard_integerEx"}
+! NO-REWRITE: %[[XV:.*]] = fir.load %[[X]]#0
+! NO-REWRITE: %[[AV:.*]] = fir.load %[[A]]#0
+! NO-REWRITE: %[[XA:.*]] = arith.addi %[[XV]], %[[AV]]
+! NO-REWRITE: %[[BV:.*]] = fir.load %[[B]]#0
+! NO-REWRITE: %[[XAB:.*]] = arith.subi %[[XA]], %[[BV]]
+! NO-REWRITE: %[[CV:.*]] = fir.load %[[C]]#0
+! NO-REWRITE: %[[RES:.*]] = arith.addi %[[XAB]], %[[CV]]
+! NO-REWRITE: hlfir.assign %[[RES]] to %[[X]]#0
+
! Subtraction immediately outside a parenthesized term changes the term's
! outer sign, but the parenthesized b-c remains one opaque no_reassoc value.
! Default: (((x + a) - (b-c)) + d)
More information about the flang-commits
mailing list