Hello. I was working on STM8 code using SDCC and I encountered a code generation bug that caused the firmware to malfunction.
Here is a simplified version of the code I was running:
#include <stdint.h>
uint16_t total = 0;
uint8_t prev = 255;
void main() {
uint8_t now = 13;
total += (uint8_t)(now - prev);
while (1);
}
I compiled the code using the following command:
sdcc -mstm8 test.c -o test.hex
I first encountered the code in SDCC 4.1.0 #12072, but for the purposes of this bug report I am using the latest release of SDCC:
$ sdcc -v
SDCC : mcs51/z80/z180/r2k/r2ka/r3ka/sm83/tlcs90/ez80_z80/z80n/ds390/pic16/pic14/TININative/ds400/hc08/s08/stm8/pdk13/pdk14/pdk15/mos6502 4.2.0 #13081 (MINGW64)
published under GNU General Public License (GPL)
The result of subtracting 255 from 13 should be an int
with value -242. When we cast that result to a uint8_t
we should get 14, and I believe this is guaranteed by the C standard. This is equivalent to clearing the most-significant byte of the 16-bit integer, changing 0xFF0E to 0x0E.
Unfortunately, the assembly generated by SDCC 4.2.0 performs a 16-bit subtraction, never clears the most significant byte of the result, and then adds the full result to total
, resulting in incorrect behavior. Here is the relevant excerpt from test.rst:
97 ; test.c: 8: total += (uint8_t)(now - prev);
008029 C6 00 03 [ 1] 98 ld a, _prev+0
00802C 6B 02 [ 1] 99 ld (0x02, sp), a
00802E 0F 01 [ 1] 100 clr (0x01, sp)
008030 90 AE 00 0D [ 2] 101 ldw y, #0x000d
008034 72 F2 01 [ 2] 102 subw y, (0x01, sp)
008037 CE 00 01 [ 2] 103 ldw x, _total+0
00803A 17 01 [ 2] 104 ldw (0x01, sp), y
00803C 72 FB 01 [ 2] 105 addw x, (0x01, sp)
00803F CF 00 01 [ 2] 106 ldw _total+0, x
Note that subw
, ldw
, and addw
are all 16-bit instructions. The 16-bit subtraction result is stored in the 16-bit register Y, later moved to the stack, then it is added to the 16-bit register X that contains the old value of global variable total
, and then the final result is stored back in total
. There is no masking/clearing of the most-significant byte.
It is pretty easy to work around this bug if you notice it. In my project, I worked around it by storing the subtraction result in a local uint8_t
variable before adding it to total
.
I can reproduce the issue on my Debian GNU/Linux testing on amd64 system.
The bug can already be seen in the dumpraw0 output from --dump-i-code and in the missing cast from the --dump-ast output. So this is a frontend bug.
Fixed in [r13782] a week ago.
Related
Commit: [r13782]
Great, thank you!