Bug In SDCC __critical Makes Compiler Lose Track of Stack Top
Back in the distant past when I was just learning C, I often convinced myself that the compiler had a bug and my C 101 code was correct. Of course, I was wrong 100% of the time.
Well, today, I found one. This one took a while to find because my computer doesn’t have communication yet. I have to debug by loading code and then looking at waveforms on a 2 channel scope.
To aid in my debugging I created a new function:
This drops GPIO2 (which must be made an output elsewhere) and then outputs a byte with a leading 0xa nibble and a trailing 0x5 nibble. I did this because I need one probe on GPIO 2 and the other on the data, so I can’t see the clock line. (I might be able to use the REF feature of the scope though…) Anyway, with the prototype extern void ShowByte(uint8_t); I can output a byte that the scope can see. Now, main() calls putchar() which calls send(). And the argument seemed to be getting corrupted at send(). Why? I looked at the uart.asm file and found the reason:
This was generated by the compiler. And it’s wrong wrong wrong!
The first three instructions handle the __critical part. They store the interrupt register into a, disable interrupts, and push af onto the stack so that the interrupts can be restored at the end of the call.
The next part tries to get the argument. In theory, the argument should be at the top of the stack, just on the other side of the return address. Or sp+2. And indeed we see hl gets 2 plus sp and then we load that into a, push af, inc the stack pointer because it’s just a byte, and then calls ShowByte.
But, this function is __critical. The stack isn’t the 8 bit argument followed by the 16 bit return address. It’s the 8 bit argument, followed by the return address, followed by 16 bits of interrupt restore data. I’m not getting my argument–I’m getting the lower part of the return address!
Wow! I need to rethink how I do critical sections! I took off the __critical and rebuilt. Here is that snippet:
Except for the missing interrupt disable, it’s the same–it gets the byte 2 bytes back. This one works in my testing. I’m getting the right byte on my scope.
I’m wondering if that +0 is supposed to account for the push af but it’s failing to update… I gotta figure out a game plan. And then submit a bug report if one isn’t already there on this.