Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this code being generated by avr-gcc and how does it work?

This is a snippet of disassembled AVR code from a C project I'm working on. I noticed this curious code being generated, and I can't understand how it works. I'm assuming it's some sort of ridiculous optimization...

What is the explanation?

92:         ticks++;         // unsigned char ticks;
+0000009F:   91900104    LDS       R25,0x0104     Load direct from data space
+000000A1:   5F9F        SUBI      R25,0xFF       Subtract immediate
+000000A2:   93900104    STS       0x0104,R25     Store direct to data space
95:         if (ticks == 0) {
+000000A4:   2399        TST       R25            Test for Zero or Minus
+000000A5:   F009        BREQ      PC+0x02        Branch if equal
+000000A6:   C067        RJMP      PC+0x0068      Relative jump

Specifically, why does the second instruction subtract 0xFF from R25 instead of just INC R25?

like image 925
Mark Renouf Avatar asked Aug 26 '09 22:08

Mark Renouf


2 Answers

The SUBI instruction can be used to add/subtract any 8 bit constant to/from an 8 bit value. It has the same cost as INC, i.e. instruction size and execution time. So SUBI is preferred by the compiler because it is more general. There is no corresponding ADDI instruction, probably because it would be redundant.

like image 61
starblue Avatar answered Oct 10 '22 09:10

starblue


tl;dr the compiler was designed to use the more portable, efficient & general solution here.

The SUBI instruction sets C (carry) and H (half-carry) CPU flags for use with subsequent instructions (there is no ADDI in 8-bit AVR BTW, so to add an immediate value of x we subtract -x from it), whereas INC does not. Since both SUBI & INC have 2 bytes of length and execute during 1 clock cycle, you lose nothing by using SUBI - OTOH, if you use a 8-bit-sized counter, you can then easily detect if it has rolled over (by BRCC/BRCS), and if you'd have a 16- or 32-bit-sized counter, it allows you to increment it in a very simple way - with just INC, 0x00FF would get increased to 0x0000, so you'd have to check if the lowest byte is 0xFF before INCing. OTOH, with SUBI you just SUBI -1 the lowest byte, and then ADC 0 for the following bytes, assuring all the potential carry bits has been accounted for.

Further reading:

https://lists.gnu.org/archive/html/avr-gcc-list/2008-11/msg00029.html

http://avr-gcc-list.nongnu.narkive.com/SMMzdBkW/foo-subi-vs-inc

like image 40
Greg Hewgill Avatar answered Oct 10 '22 10:10

Greg Hewgill