Re: Poor handling of non-power-of-2 multiplication on ATtiny

Georg-Johann Lay <[email protected]>
Newsgroups gmane.comp.hardware.avr.gcc
Message-ID <[email protected]>
Paul "LeoNerd" Evans schrieb:
> I'm writing code for an ATtiny (tiny84 specifically).
> 
> I currently have an array and a function defined thus:
> 
>   static struct {
>     uint8_t state;
>     uint8_t v1;
>   } leds[3];
> 
>   uint8_t led_state(uint8_t i) { return leds[i].state; }
> 
> Elements of this array are 2 bytes long, and this function compiles to
> some fairly sensible looking code:
> 
> 000003b8 <led_state>:
>  3b8:	e8 2f       	mov	r30, r24
>  3ba:	f0 e0       	ldi	r31, 0x00	; 0
>  3bc:	ee 0f       	add	r30, r30
>  3be:	ff 1f       	adc	r31, r31
>  3c0:	e1 57       	subi	r30, 0x71	; 113
>  3c2:	ff 4f       	sbci	r31, 0xFF	; 255
>  3c4:	80 81       	ld	r24, Z
>  3c6:	08 95       	ret
> 
> The instruction at 3bc adds r30 to itself, effectively causing it to
> contain 2*i, which is the offset in bytes required to be added to the
> base address of the `leds` array to find the required field.
> 
> If instead I define my array thus:
> 
>   static struct {
>     uint8_t state;
>     uint16_t v1;
>   } leds[3];
> 
> I get the most horrible:
> 
> 000003be <led_state>:
>  3be:	90 e0       	ldi	r25, 0x00	; 0
>  3c0:	63 e0       	ldi	r22, 0x03	; 3
>  3c2:	70 e0       	ldi	r23, 0x00	; 0
>  3c4:	2d d3       	rcall	.+1626   	; 0xa20 <__mulhi3>
>  3c6:	81 57       	subi	r24,0x71	; 113
>  3c8:	9f 4f       	sbci	r25, 0xFF	; 255
>  3ca:	fc 01       	movw	r30, r24
>  3cc:	80 81       	ld	r24, Z
>  3ce:	08 95       	ret
> 
> Here, gcc has given up trying to perform a non power-of-two
> multiplication and just inlined a call to the generic __mulhi3 function
> instead.

The code of __mulhi3 is not inlined, it's a library call.  GCC has 
currently no means to determine the accumulated costs of library calls 
like the one emit in your example.  Presumably you compiled with -Os and 
the sequence above is actually shorter than expanding *3 inline.


> This is somewhat disappointing, as if I further expand my array with a
> dummy field for padding, I now get:
> 
> 000003be <led_state>:
>  3be:	e8 2f       	mov	r30, r24
>  3c0:	f0 e0       	ldi	r31, 0x00	; 0
>  3c2:	ee 0f       	add	r30, r30
>  3c4:	ff 1f       	adc	r31, r31
>  3c6:	ee 0f       	add	r30, r30
>  3c8:	ff 1f       	adc	r31, r31
>  3ca:	e1 57       	subi	r30, 0x71	; 113
>  3cc:	ff 4f       	sbci	r31, 0xFF	; 255
>  3ce:	80 81       	ld	r24, Z
>  3d0:	08 95       	ret
> 
> because it has neatly managed to perform that power-of-two
> multiplication, this time of 4*i.
> 
> I feel this is a situation that could be resolved better; for instance,
> a multiplication by 3 in the middle case could have been performed by
> 
>   mov r30, r24
>   ldi r31, 0x00    ; Z == i
>   add r30, r30
>   adc r31, r31     ; Z == 2*i
>   add r30, r24
>   adc r31, r1      ; Z == 2*i + i == 3*i
> 
> in just as many instructions as the 4*i case, without my having to

That sequence is not as short as calling __mulhi3.  If you prefer faster 
code as indicated, try optimizing for speed with -O2.

> waste extra bytes of precious RAM just to provide that dummy padding.
> Furthermore, I care a lot about instruction counts to access because
> this LED management code runs inside a timer match interrupt to
> implement software PWM, so it has to be fast and tight.
> 
> 
> As a workaround for now, I can restructure my code into two separate
> arrays of integers, instead of one array of two-int structs, and thus
> ensure that all arrays contain only a power-of-two number of bytes, so
> all accesses will be efficient. However, it would be nice if gcc could
> manage these multiplications a little nicer.
> 
> -----
> 
> As another side note, consider the massive difference between
> 
>   uint8_t mul10(uint8_t x) { return x * 10; }
> 
>  3be:	6a e0       	ldi	r22, 0x0A	; 10
>  3c0:	29 d3       	rcall	.+1618   	; 0xa14 <__mulqi3>
>  3c2:	08 95       	ret
> 
> vs. something like
> 
>   mov r0, r24   ; r0 = x
>   add r24, r24  ; r24 = 2*x
>   add r24, r24  ; r24 = 4*x
>   add r24, r0   ; r24 = 5*x
>   add r24, r24  ; r24 = 10*x
>   ret

Again, try -O2.


> Is there currently underway an attempt to provide better optimisation
> of these sorts of cases? If not, where/how could I contribute such?

None that I know of.  Maybe adding a small penalty to the rtx costs for 
such shifts will do the trick.  The avr backend currently uses the 
shortest sequence for -Os, maybe that could be relaxed for not-so-tiny 
devices even in presence of -Os so that saving 1 instruction does not 
blow up the execution time to the x-fold.

The compiler has just a very coarse understanding of flash size: it is 
only aware of the architecture like -mmcu=avr4 so that adding libcall 
penalty could be added if, say, flash >= 16 KiB, i.e. if there is a JMP 
instruction (AVR_HAVE_JMP_CALL from avr.h).


Johann
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.