Am 28.08.26 um 15:49 schrieb Richard Biener:
On Fri, Aug 28, 2026 at 3:26 PM Philipp Tomsich
<[email protected]> wrote:
On Fri, 28 Aug 2026 at 15:12, Richard Biener <[email protected]> wrote:
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 ...
Does if even need to?
avr_rtx_costs already provides the information: For mult:SI at speed it returns
COSTS_N_INSNS (300) when !AVR_HAVE_MUL, against COSTS_N_INSNS (25) when the
multiplier exists. So the gate should drop the optab test entirely and compare
costs. I believe that this is Johann's point, that TARGET_RTX_COSTS is the
mechanism for choosing between equivalent forms.
Well, OK. So yes, this sounds good. But then if it's libcall vs.
inline exapand
with large cost as you say you cannot cost the libcall, so you can't see that
it might be N "AVR-style libcalls" vs. one "regular libcall". Unless
there's some
hook that allows to determine sth like "libcall cost"?
As far as I know, these libcalls are introduced at expand / rtl
lowering. The lowering routines query the backend for rtx costs and
chose the arithmetic according to the costs (provided there is more than
one way). When that arithmetic is expanded, it may be represented as a
call or as (a sequence of) insn(s).
So the backend never sees a libcall in rtx_costs but only the very
arithmetic it will be performing. The backend knows whether some
operation will be emit as libcall or not, and knows the costs.
So the tree optimizers don't need to bother about how expand will
express some arithmetic.
Getting rtx costs in tree passes might be expensive and requires extra
attention of the author, but that's true for many other optimizations:
There's no free lunch.
As an aside, it's still beyond me why tree code that uses 300 cycles to
test a single bit (PR118012 etc) is still there and nothing being done
about it (except flawed attempts to hack around it in affected
backends). Or just reverted such code that contributes to bad GCC
reputation.
From my reading, AVR only prices SI/DI multiplies at speed; QI/HI return false
and fall back to the generic factor * factor * COSTS_N_INSNS(5).
This looks cheap even though *mulhi3_call is a call too. @Johann, is that
deliberate?
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;