Why is gcc behaving this way?
So why? Let's have a look at the cost computation in
rtx op0 = XEXP (x, 0);
rtx op1 = XEXP (x, 1);
else if (register_operand (op0, HImode)
&& (u8_operand (op1, HImode)
|| s8_operand (op1, HImode)))
*total = COSTS_N_INSNS (!speed ? 6 : 9);
This is the cost for multiplication of a 16-bit integral value with an 8-bit compile-time constant (signed or unsigned). The costs are:
- 9 (in unity of ticks) when compiling for speed,
- 6 (in units of words) otherwise, e.g. when compiling for size.
Such a sequence would read something like
ldi R_ten, 10 ; 1
mul R_in.lo8, R_ten ; 2
movw R_OUT, R0 ; 1
mul R_IN.hi8, R_ten ; 2
add R_OUT.hi8, R0 ; 1
clr __zero_reg__ ; 1
where the output register must not overlap with any of the input registers. The actual costs are 6 words and 8 ticks, and to me it is not completely clear why avr-gcc is using 9 ticks in the cost model. Such penalty would usually account for costs that are not visible at that stage, like high register pressure or additional moves forced by early-clobber. And indeed, the real sequence has a superfluous move instruction and actually costs 9 ticks...
Where are the costs of the shift+add sequence? They are not visible here. The costs are used during instruction selection when lowering tree-SSA to RTL. The middle-end knows some algorithms to expand mul-with-const and will add costs for the required operations: (3 * shift + 1 * add) * 2 bytes = 8 ticks.
So shift+add appears cheaper than MUL. Tree-SSA → RTL lowering takes place3 in pass 234 out of 316, and costs due to register allocation (like spills, reloads or moves) are not available because reg alloc takes place later, at pass 280 / 316.
and how can I tell it to use hardware multiplication instructions?
There is no such option. Instruction selection follows from the device familiy, which follows from
-mmcu=.... If you insist on using
MUL, you'd have to use inline assembly like for example:
static inline uint16_t mul_u16_u8 (uint16_t a, uint8_t b)
#if defined __AVR_HAVE_MUL__
__asm ("mul %A[in16], %[in8]" "\n\t"
"movw %[res], r0" "\n\t"
"mul %B[in16], %[in8]" "\n\t"
"add %B[res], r0" "\n\t"
: [res] "=&r" (result)
: [in16] "r" (a), [in8] "r" (b));
return a * b;
uint16_t use_mul_u16_u8 (uint16_t x)
return mul_u16_u8 (x, 10);
The inline squence takes 7 ticks, and setting
b = 10 takes one more tick. Assuming no other instructions are required2, it consumes 8 ticks. The final
CLR is required due to avr-gcc ABI, see this answer.
What does not work though is something like
static inline uint16_t mul_Os (uint16_t a, uint8_t b)
return a * b;
uint16_t use_mul_Os (uint16_t x)
return mul_Os (x, 10);
and compile with
-O2. Inlining takes place prior to instructio selection, and even though GCC annotates every RTL insn whether to optimize for size or speed, it doesn't work out here (dunno if that could be considered a GCC problem).
2For the test case, the assumption does not hold because the output operand of the asm is early clobber, and both result and 1st input will occupy R25:R24, hence at least one more
MOVW is needed.
3Using pass numbers from avr-gcc v8.