Am 26.08.26 um 09:43 schrieb Konstantinos Eleftheriou:
From: Philipp Tomsich <[email protected]>

lower_long_mul_high_chain lowered every recognized high-part chain the
target cannot form a narrow high part for, including chains whose wide
multiply expands just fine. On AVR without hardware multiply this turned
a single __mulsi3 call into four __mulhi3 calls plus carry propagation
at -Os.

Lower only where the inline longhand is the better code. Optimizing for
size a single wide multiply is smaller either way, so never lower. With
a multiply insn for the wide mode the target has asked for the wide form
even when optimizing for speed: targets without a hardware multiply

Hi Konstantinos,

just the fact that the target provides an insn doesn't mean it is asking
for something, or that the insn should be preferred over different but
equivalent expansions.

It just means that the target is better of providing the insn than not
providing it.  That's all.

For example, the avr libgcc implements many helper function in asm,
and the compiler provides insns that model the exact register footprint.
This is profitable since many asm functions don't clobber all callee
clobbered registers, and the insn is there to express the reduced
register footprint.  For example, a mulhi3 may look like:

(define_insn "*mulhi3_call"
  [(set (reg:HI 24)
        (mult:HI (reg:HI 24)
                 (reg:HI 22)))
   (clobber (reg:HI 22))
   (clobber (reg:QI 21))
   (clobber (reg:CC REG_CC))]
  "!AVR_HAVE_MUL && reload_completed"
  "%~call __mulhi3"
  [(set_attr "type" "xcall")])

This is exactly what a vanilla libcall insn would do, just with
a reduced register footprint.  (Missed tail calls are optimized
by linker relaxation.)

In order to select which of many alternative representations of some
arithmetic should be used, the targets are providing cost functions
like targetm.rtx_costs / TARGET_RTX_COSTS.

To make thinks even worse, the back end has to hack around madness
like https://gcc.gnu.org/PR118012 from the tree optimizers that
use a multiplication -- even on targets that don't have MUL -- to
perform a single bit test!  avr tries to hack around that my providing
combiner patterns that try to "roll back" such completely crazy code.
Just search for 118012 in the avr back end.

Johann

expose call-bundle patterns (AVR's mulsi3) precisely to route such
multiplies to their hand-written helpers. What is left is a libcall at
speed, where inlining saves only the call, since the callee performs the
same longhand.

A wide mode with no expansion path at all is unaffected: such a chain
has to be lowered at every optimization level, or it reaches expand as
a multiply the target cannot expand.

Bootstrapped and regtested on AArch64, x86-64 and ARM.
Regtested on AVR.

        PR tree-optimization/127028

gcc/ChangeLog:

        * tree-ssa-math-opts.cc (long_mul_lowering_profitable_p): New.
        (lower_long_mul_high_chain): Call it when the wide mode can
        expand.

gcc/testsuite/ChangeLog:

        * gcc.target/avr/pr127028.c: New test.

Co-authored-by: Konstantinos Eleftheriou <[email protected]>
---
  gcc/testsuite/gcc.target/avr/pr127028.c | 12 ++++++++++
  gcc/tree-ssa-math-opts.cc               | 29 +++++++++++++++++++++++++
  2 files changed, 41 insertions(+)
  create mode 100644 gcc/testsuite/gcc.target/avr/pr127028.c

diff --git a/gcc/testsuite/gcc.target/avr/pr127028.c 
b/gcc/testsuite/gcc.target/avr/pr127028.c
new file mode 100644
index 000000000000..e183d02f052d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr127028.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os" } */
+
+typedef __UINT16_TYPE__ uint16_t;
+typedef __UINT32_TYPE__ uint32_t;
+
+uint16_t wmul16 (uint16_t x)
+{
+  return ((uint32_t) x * 0xaaab) >> 16;
+}
+
+/* { dg-final { scan-assembler-not "__mulhi3" } } */
diff --git a/gcc/tree-ssa-math-opts.cc b/gcc/tree-ssa-math-opts.cc
index ebc3300ae332..8288fbead751 100644
--- a/gcc/tree-ssa-math-opts.cc
+++ b/gcc/tree-ssa-math-opts.cc
@@ -7101,6 +7101,18 @@ narrow_long_mul_halves (gimple *stmt)
    return true;
  }
+/* True when the inline longhand beats expanding the WIDE_MODE multiply.
+   At size the wide multiply is smaller either way.  At speed, a multiply
+   pattern is the target's own expansion -- AVR defines mulsi3 even on
+   cores with no multiply at all -- while a libcall is the generic fallback
+   whose body is that same longhand, so inlining saves only the call.  */
+
+static bool
+long_mul_lowering_profitable_p (scalar_int_mode wide_mode, bool speed)
+{
+  return speed && optab_handler (smul_optab, wide_mode) == CODE_FOR_nothing;
+}
+
  /* Match.pd recognizer for the long-multiply recognizer's high-part
     emit chain.  */
@@ -7148,6 +7160,23 @@ lower_long_mul_high_chain (gimple_stmt_iterator *gsi)
    if (can_mult_highpart_p (narrow_mode, true))
      return false;
+ /* Where the 2N multiply expands, lowering is a trade, not a rescue, so
+     leave the chain alone unless the longhand is the better code.  A 2N
+     mode with no expansion path must be lowered regardless.  */
+  tree wide_type = TREE_TYPE (gimple_assign_lhs (mult_stmt));
+  scalar_int_mode wide_mode;
+  bool speed = optimize_bb_for_speed_p (gimple_bb (trunc_stmt));
+  if (is_a <scalar_int_mode> (TYPE_MODE (wide_type), &wide_mode)
+      && targetm.scalar_mode_supported_p (wide_mode)
+      && !long_mul_lowering_profitable_p (wide_mode, speed))
+    {
+      if (dump_file && (dump_flags & TDF_DETAILS))
+       fprintf (dump_file, "Not lowering long-mul chain: %s.\n",
+                speed ? "the target expands the wide multiply"
+                      : "the wide multiply is smaller");
+      return false;
+    }
+
    location_t loc = gimple_location (trunc_stmt);
    gimple_seq seq = NULL;

Reply via email to