https://gcc.gnu.org/g:2d2e355b89367711f549b1277a537e2c0a4de58d
commit r17-4493-g2d2e355b89367711f549b1277a537e2c0a4de58d Author: Philipp Tomsich <[email protected]> Date: Mon Aug 24 17:13:40 2026 +0200 widening_mul: Emit zero high-half partials for operands that fit [PR127042] build_long_mul_partials splits both operands into halves regardless of what is known about them. An operand provably within the half width still gets a shift and two partial products against its high half, which is zero. They reach expand, and the carry arithmetic built on top of them survives into the final code. Reuse the recognizer's fits test: a provably-fitting operand keeps its low half unmasked and contributes literal zeros as high-half partials, so the cross sum, carry test and accumulate fold as the sequence is built. The u16-operand high part comes out as the two multiplies it needs. Bootstrapped and regtested on AArch64, x86-64 and ARM. Regtested on AVR. PR tree-optimization/127042 gcc/ChangeLog: * tree-ssa-math-opts.cc (build_long_mul_partials): Emit zero high-half partial products, and skip the high-half split and the low-half mask, for an operand that provably fits the half width. gcc/testsuite/ChangeLog: * gcc.target/arm/pr127042.c: New test. * gcc.target/avr/mmcu/pr127042.c: New test. Co-authored-by: Konstantinos Eleftheriou <[email protected]> Diff: --- gcc/testsuite/gcc.target/arm/pr127042.c | 23 ++++++++++++++++ gcc/testsuite/gcc.target/avr/mmcu/pr127042.c | 22 ++++++++++++++++ gcc/tree-ssa-math-opts.cc | 39 +++++++++++++++++++++------- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/gcc/testsuite/gcc.target/arm/pr127042.c b/gcc/testsuite/gcc.target/arm/pr127042.c new file mode 100644 index 000000000000..66368c8bb349 --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/pr127042.c @@ -0,0 +1,23 @@ +/* PR tree-optimization/127042 */ +/* { dg-do compile } */ +/* { dg-require-effective-target arm_thumb1_ok } */ +/* { dg-options "-O2 -mthumb -mcpu=cortex-m0 -fdump-tree-widening_mul" } */ + +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; + +/* x fits 16 bits: only x * 0xaaab and x * 0xaaaa remain. */ +uint16_t fits (uint16_t x) +{ + return ((uint64_t) x * 0xaaaaaaab) >> 32; +} + +/* Neither operand fits: the full four-partial longhand stays. */ +uint32_t nofit (uint32_t x, uint32_t y) +{ + return ((uint64_t) x * (uint64_t) y) >> 32; +} + +/* Two multiplies from fits, four from nofit. */ +/* { dg-final { scan-tree-dump-times " \\* " 6 "widening_mul" } } */ diff --git a/gcc/testsuite/gcc.target/avr/mmcu/pr127042.c b/gcc/testsuite/gcc.target/avr/mmcu/pr127042.c new file mode 100644 index 000000000000..b047ce92aa69 --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/mmcu/pr127042.c @@ -0,0 +1,22 @@ +/* PR tree-optimization/127042 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -mmcu=avr2 -fdump-tree-widening_mul" } */ + +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; + +/* x fits 16 bits: only x * 0xaaab and x * 0xaaaa remain. */ +uint16_t fits (uint16_t x) +{ + return ((uint64_t) x * 0xaaaaaaab) >> 32; +} + +/* Neither operand fits: the full four-partial longhand stays. */ +uint32_t nofit (uint32_t x, uint32_t y) +{ + return ((uint64_t) x * (uint64_t) y) >> 32; +} + +/* Two multiplies from fits, four from nofit. */ +/* { dg-final { scan-tree-dump-times " \\* " 6 "widening_mul" } } */ diff --git a/gcc/tree-ssa-math-opts.cc b/gcc/tree-ssa-math-opts.cc index 2efd70f27f64..450116add1d3 100644 --- a/gcc/tree-ssa-math-opts.cc +++ b/gcc/tree-ssa-math-opts.cc @@ -6569,12 +6569,16 @@ can_widen_to_narrow_p (scalar_int_mode narrow_mode, unsigned int half_width, != CODE_FOR_nothing; } +static bool long_mul_op_fits_p (tree, unsigned, bitmap); + /* Append to *SEQ the operand split and partial products for an unsigned long multiply of OP1 by OP2 at the precision of TREE_TYPE (OP1). HALF_TYPE is the (N/2)-bit unsigned type; HALF_AMT is the integer-typed shift constant equal to N/2. Outputs the four partial products via *LOLO, *HILO, *LOHI, *HIHI. + An operand provably within N/2 bits contributes literal-zero high + partials. USE_WIDEN selects the partial-product form: true - cast halves to HALF_TYPE and use WIDEN_MULT_EXPR (needs @@ -6590,8 +6594,16 @@ build_long_mul_partials (gimple_seq *seq, location_t loc, tree op1, tree op2, bool use_widen) { tree acc_type = TREE_TYPE (op1); - tree op1_hi = gimple_build (seq, loc, RSHIFT_EXPR, acc_type, op1, half_amt); - tree op2_hi = gimple_build (seq, loc, RSHIFT_EXPR, acc_type, op2, half_amt); + unsigned int half_prec = TYPE_PRECISION (half_type); + auto_bitmap phi_seen1, phi_seen2; + bool op1_fits = long_mul_op_fits_p (op1, half_prec, phi_seen1); + bool op2_fits = long_mul_op_fits_p (op2, half_prec, phi_seen2); + tree zero = build_zero_cst (acc_type); + tree op1_hi = NULL_TREE, op2_hi = NULL_TREE; + if (!op1_fits) + op1_hi = gimple_build (seq, loc, RSHIFT_EXPR, acc_type, op1, half_amt); + if (!op2_fits) + op2_hi = gimple_build (seq, loc, RSHIFT_EXPR, acc_type, op2, half_amt); tree op1_lo, op2_lo; tree_code mul_code; @@ -6599,24 +6611,31 @@ build_long_mul_partials (gimple_seq *seq, location_t loc, tree op1, tree op2, { op1_lo = gimple_build (seq, loc, NOP_EXPR, half_type, op1); op2_lo = gimple_build (seq, loc, NOP_EXPR, half_type, op2); - op1_hi = gimple_build (seq, loc, NOP_EXPR, half_type, op1_hi); - op2_hi = gimple_build (seq, loc, NOP_EXPR, half_type, op2_hi); + if (op1_hi) + op1_hi = gimple_build (seq, loc, NOP_EXPR, half_type, op1_hi); + if (op2_hi) + op2_hi = gimple_build (seq, loc, NOP_EXPR, half_type, op2_hi); mul_code = WIDEN_MULT_EXPR; } else { tree mask = wide_int_to_tree (acc_type, - wi::mask (TYPE_PRECISION (half_type), false, + wi::mask (half_prec, false, TYPE_PRECISION (acc_type))); - op1_lo = gimple_build (seq, loc, BIT_AND_EXPR, acc_type, op1, mask); - op2_lo = gimple_build (seq, loc, BIT_AND_EXPR, acc_type, op2, mask); + op1_lo = op1_fits ? op1 + : gimple_build (seq, loc, BIT_AND_EXPR, acc_type, op1, mask); + op2_lo = op2_fits ? op2 + : gimple_build (seq, loc, BIT_AND_EXPR, acc_type, op2, mask); mul_code = MULT_EXPR; } *lolo = gimple_build (seq, loc, mul_code, acc_type, op1_lo, op2_lo); - *hilo = gimple_build (seq, loc, mul_code, acc_type, op1_hi, op2_lo); - *lohi = gimple_build (seq, loc, mul_code, acc_type, op1_lo, op2_hi); - *hihi = gimple_build (seq, loc, mul_code, acc_type, op1_hi, op2_hi); + *hilo = op1_fits ? zero + : gimple_build (seq, loc, mul_code, acc_type, op1_hi, op2_lo); + *lohi = op2_fits ? zero + : gimple_build (seq, loc, mul_code, acc_type, op1_lo, op2_hi); + *hihi = op1_fits || op2_fits ? zero + : gimple_build (seq, loc, mul_code, acc_type, op1_hi, op2_hi); } /* Emit into *SEQ the high N bits of the unsigned product A * B, where A and B
