Extend the long-multiply fold to a LOW_PART shape that recovers the
lower 2N bits as a plain unsigned sum of the xl*yl product and the
shifted cross-product sum:
xl*yl + (cross_sum << N)
cross_sum = xh*yl + xl*yh
This shape also appears as the low half of a two-carry long-multiply,
where an unsigned overflow compare against one of the PLUS operands is
the low-carry term consumed by the matching high-part fold. Folding
to mul_lo here would destroy (cross_sum << N), which both the compare
and the high-part match still need; an extra check defers the LOW
fold when any GT/LT/GE/LE use shares an operand with the PLUS, so the
high-part fold runs first. After it does, the compare is dead and
the surviving lolo + (cross_sum << N) is picked up by this row in the
next forwprop instance.
gcc/ChangeLog:
* tree-ssa-forwprop.cc (long_mul_check_low_plus_defer): New
helper, defers the fold when any GT/LT/GE/LE use shares an
operand with the PLUS.
(long_mul_table): Add the LOW_PART PLUS_EXPR row gated on
long_mul_check_low_plus_defer.
gcc/testsuite/ChangeLog:
* gcc.dg/tree-ssa/long-mul-low-plus.c: New test.
* gcc.dg/tree-ssa/long-mul-two-carry.c: Add a forwprop4 LOW
fold expectation for full_mul_two_carry, deferred from
forwprop3 by long_mul_check_low_plus_defer.
Signed-off-by: Konstantinos Eleftheriou <[email protected]>
---
(no changes since v1)
.../gcc.dg/tree-ssa/long-mul-low-plus.c | 54 +++++++++++++++++++
.../gcc.dg/tree-ssa/long-mul-two-carry.c | 11 ++--
gcc/tree-ssa-forwprop.cc | 54 +++++++++++++++++++
3 files changed, 114 insertions(+), 5 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/long-mul-low-plus.c
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/long-mul-low-plus.c
b/gcc/testsuite/gcc.dg/tree-ssa/long-mul-low-plus.c
new file mode 100644
index 000000000000..37c0193ece09
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/long-mul-low-plus.c
@@ -0,0 +1,54 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fdump-tree-forwprop-details" } */
+
+typedef __UINT32_TYPE__ uint32_t;
+typedef __UINT64_TYPE__ uint64_t;
+
+/* Low part via PLUS form: lolo + (cross_sum << halfwidth).
+ No GT/LT comparison on the result, so long_mul_check_low_plus_defer
+ should fold without deferring. */
+uint32_t mul_low_plus_32 (uint32_t x, uint32_t y)
+{
+ uint32_t x_hi = x >> 16;
+ uint32_t x_lo = x & 0xFFFF;
+ uint32_t y_hi = y >> 16;
+ uint32_t y_lo = y & 0xFFFF;
+ uint32_t lolo = x_lo * y_lo;
+ uint32_t hilo = x_hi * y_lo;
+ uint32_t lohi = x_lo * y_hi;
+ uint32_t cross_sum = hilo + lohi;
+ uint32_t cross_shifted = cross_sum << 16;
+ return lolo + cross_shifted;
+}
+
+/* 64-bit variant. */
+uint64_t mul_low_plus_64 (uint64_t x, uint64_t y)
+{
+ uint64_t x_hi = x >> 32;
+ uint64_t x_lo = x & 0xFFFFFFFFUL;
+ uint64_t y_hi = y >> 32;
+ uint64_t y_lo = y & 0xFFFFFFFFUL;
+ uint64_t lolo = x_lo * y_lo;
+ uint64_t hilo = x_hi * y_lo;
+ uint64_t lohi = x_lo * y_hi;
+ uint64_t cross_sum = hilo + lohi;
+ uint64_t cross_shifted = cross_sum << 32;
+ return lolo + cross_shifted;
+}
+
+/* Commuted operand order. */
+uint32_t mul_low_plus_comm (uint32_t x, uint32_t y)
+{
+ uint32_t x_hi = x >> 16;
+ uint32_t x_lo = x & 0xFFFF;
+ uint32_t y_hi = y >> 16;
+ uint32_t y_lo = y & 0xFFFF;
+ uint32_t lolo = y_lo * x_lo;
+ uint32_t hilo = y_lo * x_hi;
+ uint32_t lohi = y_hi * x_lo;
+ uint32_t cross_sum = lohi + hilo;
+ uint32_t cross_shifted = cross_sum << 16;
+ return cross_shifted + lolo;
+}
+
+/* { dg-final { scan-tree-dump-times "Long multiplication low part folded." 3
"forwprop1" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/long-mul-two-carry.c
b/gcc/testsuite/gcc.dg/tree-ssa/long-mul-two-carry.c
index 288363429dd9..662ad77748e5 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/long-mul-two-carry.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/long-mul-two-carry.c
@@ -105,8 +105,9 @@ uint64_t full_mul_two_carry (uint64_t x, uint64_t y,
uint64_t *lo)
return high;
}
-/* Folds land in forwprop3 because the LT_EXPR low-carry compare is
- only canonicalized into the gt:c-plus-overflow shape that
- mul_carry_low matches by an earlier pass between forwprop1 and
- forwprop3. */
-/* { dg-final { scan-tree-dump-times "Long multiplication high part folded." 4
"forwprop3" } } */
\ No newline at end of file
+/* The LOW fold for full_mul_two_carry lands in forwprop4 because
+ `lolo + cross_shifted' also feeds the HIGH fold's low-carry
+ compare; long_mul_check_low_plus_defer holds it until the HIGH
+ fold has consumed the compare. */
+/* { dg-final { scan-tree-dump-times "Long multiplication high part folded." 4
"forwprop3" } } */
+/* { dg-final { scan-tree-dump-times "Long multiplication low part folded." 1
"forwprop4" } } */
diff --git a/gcc/tree-ssa-forwprop.cc b/gcc/tree-ssa-forwprop.cc
index f9c0f1a9854d..30f3a0310d0f 100644
--- a/gcc/tree-ssa-forwprop.cc
+++ b/gcc/tree-ssa-forwprop.cc
@@ -4272,6 +4272,55 @@ long_mul_check_two_carries (const vec<long_mul_summand>
&summands,
return true;
}
+/* The lolo + cross_shifted shape is also the low half of a two-carry
+ long-multiply, where an unsigned overflow compare against one of
+ the PLUS operands is the low-carry term consumed by the matching
+ high-part fold. Folding to mul_lo here destroys cross_shifted,
+ which both the compare and the high-part match still need; defer
+ so the high-part fold runs first. After it does, the compare is
+ dead and the surviving lolo + cross_shifted is picked up by this
+ row in the next forwprop instance. Returns false to defer. */
+
+static bool
+long_mul_check_low_plus_defer (const vec<long_mul_summand> &, gimple *stmt)
+{
+ if (!is_gimple_assign (stmt))
+ return false;
+
+ tree lhs = gimple_assign_lhs (stmt);
+ tree rhs1 = gimple_assign_rhs1 (stmt);
+ tree rhs2 = gimple_assign_rhs2 (stmt);
+
+ imm_use_iterator iter;
+ gimple *use_stmt;
+ FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs)
+ {
+ tree cmp_op1 = NULL_TREE, cmp_op2 = NULL_TREE;
+ enum tree_code use_code = ERROR_MARK;
+ if (is_gimple_assign (use_stmt))
+ {
+ use_code = gimple_assign_rhs_code (use_stmt);
+ cmp_op1 = gimple_assign_rhs1 (use_stmt);
+ cmp_op2 = gimple_assign_rhs2 (use_stmt);
+ }
+ else if (gcond *cond = dyn_cast<gcond *> (use_stmt))
+ {
+ use_code = gimple_cond_code (cond);
+ cmp_op1 = gimple_cond_lhs (cond);
+ cmp_op2 = gimple_cond_rhs (cond);
+ }
+ if (use_code == GT_EXPR || use_code == LT_EXPR
+ || use_code == GE_EXPR || use_code == LE_EXPR)
+ {
+ tree other = (cmp_op1 == lhs) ? cmp_op2
+ : (cmp_op2 == lhs) ? cmp_op1 : NULL_TREE;
+ if (other && (other == rhs1 || other == rhs2))
+ return false;
+ }
+ }
+ return true;
+}
+
/* Long-multiply variant table. Each row enumerates the multiset of
(kind, extract) summands that compose one long-multiply form. Rows
are sorted by long_mul_summand_compare, matching the input summands'
@@ -4335,6 +4384,11 @@ static const long_mul_row long_mul_table[] = {
NULL },
/* LOW-PART folds. Recover the lower 2N bits from xl*yl plus a
shifted cross-half term. */
+ /* xl*yl + (cross_sum << N). */
+ { long_mul_row::LOW_PART, PLUS_EXPR, 2,
+ { { LMK_MUL_LOLO, LMX_NONE },
+ { LMK_CROSS_SUM, LMX_SHL_N } },
+ long_mul_check_low_plus_defer },
/* (xl*yl & mask) | (low_accum << N),
low_accum = (xl*yl >> N) + (cross_sum & mask). */
{ long_mul_row::LOW_PART, BIT_IOR_EXPR, 2,
--
2.55.0