# Avr-gcc does not compile correctly without optimizations, but works (badly) with -Os

I am developing software for attiny88 with avr-gcc toolchain. This is a cheap microcontroller with 8kB program memory, 512B SRAM and 64B non-volatile EEPROM data memory. This is all sufficient for the task it is supposed to handle. I do not have a debugger for this chip.

The device acts as SPI slave and allows the master to read/program the EEPROM and to read the device state (state of some analog inputs and digital outputs). The protocol is simple - first databyte carries the instruction where highest two bits encode required action (00-nothing, 01-write eeprom, 10-read eeprom, 11-read state) and the rest is address. Second byte is always zero, third byte is the value to read or write, fourth byte is always zero.

Problem is that I am getting weird behavior from the compiler. It is difficult to put a finger on what is going on, so I will just give a few examples. Most striking is that without -Os optimization the device does not respond over SPI. This is not for any of the obvious reasons - the program fits the memory and the stack should not run into .bss/.data sections (the program has ~700B, SRAM is mapped between 0x100 and 0x2ff, where .bss_end is at 0x109; the heap is empty; there are no nested function calls, neither nested interrupts).

If I turn on the -Os optimization, then the program responds as intended. Here is the working code to handle the ISR:

unsigned char state[8];
volatile unsigned char data;

ISR(SPI_STC_vect)
{
switch(data>>6) {
case 0:
data = SPDR;
break;
case 1:           /* write eeprom */
while(EECR & (1<<EEPE));
EECR = (0<<EEPM1)|(0<<EEPM0);
EEARL = 0;
EEDR = SPDR;
EECR |= (1<<EEMPE);
EECR |= (1<<EEPE);
data = 0;
break;
case 2:               /* read eeprom */
EEARL = data & 0x1f;    /* with 0x3f stops working (???) */
EECR |= (1<<EERE);
SPDR = EEDR;
data = 0;
break;
case 3:           /* read state */
SPDR = state[data&7];
data = 0;
break;
}
}


However, the program gets broken when written in semantically different way:

• If I change the line "EEARL = data & 0x1f;" to "EEARL = data & 0x3f;", which is desirable as it would allow to address whole EEPROM address space, the ERPROM writing/reading stops working (I do not need full 64B, so I left it as is)

• State reading (case 3) gets broken if I replace the line "SPDR = state[data&7];" with switch-case construct that returns value of PORTD/PORTB register when address is 0 and 1 respectively (the current workaround is to keep state[0] and state[1] synchronized with PORTB/PORTD in the main loop).

Am I missing something important? It seems to me that the compiler messes up, but I haven't found any bug reports for avr-gcc or avr-libc (newlib?) that would fit the bill.

The toolchain was installed from current gcc-avr package in aptitude repositories. The makefile is pretty basic:

all: main.hex main.s

main.elf: main.o
avr-gcc -g -mmcu=attiny88 -o main.elf main.o

main.s: main.elf
avr-objdump -d main.elf > main.s

main.hex: main.elf
avr-objcopy -O ihex  main.elf main.hex

main.o: main.c
avr-gcc -mmcu=attiny88 -Os -c main.c -Wall


UPDATE: Still the same result with latest binutils, gcc and avr-libc (recompiled from sources)

• Try reading/comparing the generated machine code. Aug 17 '16 at 8:54
• Where do you initialize the data variable? Are you relying on static initialization to give you "zero-out"? That's always a bad idea, since microcontrollers often deviate from the C standard there, in order to give faster start-up. Aug 17 '16 at 9:08
• That being said, the most common reason by far why programs stop working between different levels of optimization, is that somewhere in your program, you have some manner of undefined behavior. That is, you probably have a dormant bug which surfaced when you changed the build. Not necessarily in the code posted. Aug 17 '16 at 9:13
• @PlasmaHH Thanks for the suggestion, it occurred to me that I can diff the disassembly when I just change one constant from 0x1f to 0x3f as discussed in the question. The differences are vast - practically whole program is different. Seems wrong though - for example cli (disable interrupts) instruction disappeared from the interrupt handler routine Aug 17 '16 at 9:19
• Doing time taking things (like writing EEPROM ) inside an interrupt handler (recalled each SPI receiving event ) definitevely brings rotten luck. Aug 17 '16 at 9:59

You'll need to inspect the output of avr-objdump to see what exact instructions were generated for your code. Incidentally, it would be helpful to include your C code in the disassembly via avr-objdump -S main.elf > main.s. I doubt that the whole program becomes different when you replace the 0x1F constant by 0x3F, isolating differences in the listing and analyzing them carefully would be your next step.
• @student try to produce assembler listings from compiler, i.e. avr-gcc -mmcu=attiny88 -Os -S main.c. That should leave address literals in symbolic form (label_0001 etc.), so producing a diff should be easier. Aug 17 '16 at 12:52