Menu

#3504 STM8: Cast from int to uint8_t on right side of += malfunctions

closed-fixed
None
Front-end
9
2023-01-07
2022-10-12
No

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.

Related

Wiki: NGI0-SDCC
Wiki: SDCC 4.3.0 Release

Discussion

  • Philipp Klaus Krause

    • Category: other --> Front-end
     
  • Philipp Klaus Krause

    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.

     
  • Philipp Klaus Krause

    • status: open --> closed-fixed
    • assigned_to: Philipp Klaus Krause
     
  • Philipp Klaus Krause

    Fixed in [r13782] a week ago.

     

    Related

    Commit: [r13782]

  • David Grayson

    David Grayson - 2023-01-07

    Great, thank you!

     

Log in to post a comment.