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:
1 .area _CODE
2
3 _ShowByte::
4 ld iy,#2
5 add iy,sp
6 in a, (#0x80)
7 res 2, a
8 out (#0x80), a
9 ld a,0 (iy)
10 rlca
11 rlca
12 rlca
13 rlca
14 and a,#0x0F
15 or a, #0xA0
16 out (#0x83), a
17 ld a, #0x28
18 out (#0x84), a
19 ld a,0 (iy)
20 rlca
21 rlca
22 rlca
23 rlca
24 and a,#0xf0
25 or a, #0x05
26 out (#0x83), a
27 ld a, #0x28
28 out (#0x84), a
29 in a, (#0x80)
30 nop
31 nop
32 nop
33 set 2, a
34 out (#0x80), a
35 ret
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:
1 ;uart.c:151: bool send(uint8_t b) __critical
2 ; ---------------------------------
3 ; Function send
4 ; ---------------------------------
5 _send::
6 ld a,i
7 di
8 push af
9 ;uart.c:153: ShowByte(b);
10 ld hl, #2+0
11 add hl, sp
12 ld a, (hl)
13 push af
14 inc sp
15 call _ShowByte
16 inc sp
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:
1 ; ---------------------------------
2 ; Function send
3 ; ---------------------------------
4 _send::
5 ;uart.c:153: ShowByte(b);
6 ld hl, #2+0
7 add hl, sp
8 ld a, (hl)
9 push af
10 inc sp
11 call _ShowByte
12 inc sp
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.