sdcc

Is this a possible bug in SDCC?


The following code works as expected when compiled with gcc, however when compiled with sdcc (4.2.0) the conditional test (x2 + y2 >= 800) doesn't ever appear to be evaluated as true (as only spaces are printed).

#include <stdio.h>

void main()
{
   const char* a[8] = {"\x1b[0m", "\x1b[0;41m", "\x1b[0;42m", "\x1b[0;43m", 
      "\x1b[0;44m", "\x1b[0;45m", "\x1b[0;46m", "\x1b[0;47m"};
   
   int x, y;
   int x2, y2, x0, y0, i, n;
   char c;

   int xa = -500;
   int xb = 300;
   int ya = 240;
   int yb = -250;
   int xd = 7;
   int yd= 15;
   int m = 20;
   
   y0 = ya;
   while (y0 > yb)
   {
      x0 = xa;
      while (x0 < xb)
      {
         y = 0;
         x = 0;
         c = ' ';
         n = 0;
         i = 0;
         while (i < m)
         {
            x2 = (x * x) / 200;
            y2 = (y * y) / 200;
            if ((x2 + y2) >= 800)
            {
               if (i > 9) c = '0'; else c = '0' + i;
               if (i > 7) n = 7; else n = i;
               i = m;
            }
            y = (x * y / 100 + y0);
            x = (x2 - y2 + x0);
            /* if ((x > 16383) || (y > 16383)) printf(" %d %d ", x ,y); */
            i++;
         }
         printf("%s%c", a[n], c);
         x0 = x0 + xd;
      }
      printf("%s\n", a[0]);
      y0 = y0 - yd;
   }
}

This behaviour is consistent when using sdcc (4.2.0) on Debian (bookworm) and sdcc 4.0.0 on Debian(bulseye).

$ sdcc -mz80 --no-std-crt0 --data-loc 0 sdc-crt0-args.rel sdc-cpm.rel sdc-mandlebrot.c 
$ sdobjcopy -Iihex -Obinary --gap-fill 0 sdc-mandlebrot.ihx sdc-mandlebrot.com

However if I change int x, y; to long x, y; it works, even though neither x or y ever exceed 16K.

Am I doing something wrong, or is there a bug with sdcc?


Solution

  • SDCC uses 16 bits for an int, that much you found out. However, you have an overflow in your code.

    This is the relevant excerpt, all variables are int:

                x2 = (x * x) / 200;
                y2 = (y * y) / 200;
                if ((x2 + y2) >= 800)
    

    Because of the products being actually squares, x2 and y2 can never be negative.

    To get a sum of at least 800, we can differentiate these corner cases:

    1. x2 is 800 or more, y2 is 0.
    2. x2 is 0, y2 is 800 or more.
    3. x2 is 400 or more, y2 is 400 or more.

    For any of x2 or y2 to be 400 or more, the square of x or y, respectively, needs to be 400 * 200 = 80000 or more.

    This is clearly more than an int can represent. The highest value is 32767, divided by 200 will result in 163. So the maximum of x2 + y2 is 2 * 163 = 326.


    Why does it work, if you declare x and y as long?

    Well, by integer promotion the squares are long, too, and the quotient will be correct. The assignments to x2 and y2 silently cast down to int, but the result is small enough.

    This gives a hint how to have an intermediate square in long. For this, at least one of the operands needs to be long. But to make it crystal clear to future readers, use explicit casts instead of implicit casts, and don't forget the constants.

                x2 = (int)(((long)x * (long)x) / 200L);
                y2 = (int)(((long)y * (long)y) / 200L);
    

    Pro-tip: These expressions look ugly. You might want to try the compiler's optimization capability to inline. For this, define a local helper function. You might want to comment the function extensively.

    static int scaled_square(int value) {
        return (int)(((long)value * (long)value) / 200L);
    }
    
    /* ... */
    
                x2 = scaled_square(x);
                y2 = scaled_square(y);
    

    Final note: While SDCC is a project driven by voluntaries, and may therefore have some bugs, it is a matured system built by experienced developers. So this case is not a bug in SDCC, but a bug in your code.

    I found some compiler bugs in my career, even in expensive and validated closed source compilers, but only every so many years. The vast majority of bugs were my own, more than 99,99%. So I never assume a compiler bug, if something does not work as expected, but always a user bug. You should develop that mind set, too.

    Only if you can prove by the generated assembly code, you can point to the compiler.