On Thu, Aug 27, 2026 at 2:30 PM Konstantinos Eleftheriou
<[email protected]> wrote:
>
> Hi all, thanks for the feedback!
>
> In case of a libcall we can't calculate the cost of the emitted instructions,
> so I think that we should favour the inline expansion when optimizing
> for speed, as it at least avoids the call.
>
> In case that there is an optab, we could compare its cost against our
> inline expansion rather than preferring it outright.
>
> How does that sound?

The question is how does this detect that AVR expands to a libcall that
isn't a libcall ...

> On Wed, Aug 26, 2026 at 5:01 PM Richard Biener <[email protected]> 
> wrote:
>>
>> On Wed, Aug 26, 2026 at 11:42 AM Georg-Johann Lay <[email protected]> wrote:
>> >
>> > 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.
>>
>> That's true.  And for AVR it's kind-of extreme, for good enough reasons.
>>
>> > 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.
>>
>> Checking rtx_cost on GIMPLE is a bit elaborate (see IVOPTs), so
>> short of applying heuristics (> word_mode operations are unlikely cheap),
>> we might want to provide a helper computing optab cost for some mode(s)
>> and that applies some caching (because creating scratch RTL for costing
>> is costly).  We for example have lshift_cheap_p which does this for
>> a single optab (with a FIXME).
>>
>> > 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