Zalo DS Blog

Sunday, December 04, 2016

Game Boy Development - Tips and Tricks (III)

This is the 3rd post on the series Game Boy Development - Tips and Tricks

7. SDCC Issues


No matter how you look at it, the SDCC that comes with the GBDK has some serious issues. Here I am going to talk a little bit about some of them that I have been able to isolate an reproduce 100% and the workarounds I was forced to use.

1. Minus unary operator (-) doesn't work with numbers bigger than 130

The minus operator used for negating a number either gives you a random value or crashes. You can easily test this with the next code: 

void Test0()  {
INT16 n   = 1000;
INT16 tmp = -n;
//Printing -n crashes
printf("%d neg: %d", n, tmp);
}

For some reason it only fails with numbers bigger than 130, so you can safely use if with 8 bits values (because the biggest 8 bit signed is 127). You shouldn't use it with unsigned values for obvious reasons.
The workaround is simple but annoying, use the next macro that negates a two's complement number

#define NEG(A) (0xFFFF - A + 1)
void Test0()  {
INT16 n   = 1000;
INT16 tmp = -n;
//Workaround
printf("%d neg: %d\n", n, NEG(n));
}

2. Extra cast is required for adding  an 8-bit value to an unsigned 16-bit value

I have to admit I wasn't sure at first it this was a bug or not. Mixing signed and unsigned values is always complicated but when you start using unsigned values is going to happen sooner or later. I just wrote a similar code on Visual Studio using shorts and ints and it seem to be a bug according to that.

Calling the next function with v0=100 and v1=-50 outputs 306 instead of 50

void Test1(UINT16 v0, INT8 v1) {
printf("%d ", v0 + v1);
}

At least for this one there is an explanation. The compiler is converting v1  to unsigned (256 - 50 = 206) and adding that value wich gives that result.

If Instead of writing the test as a function with two parameters we declare the vars as local:

void Test1() {
UINT16 v0 = 100;
INT8 v1 = -50;
printf("%d ", v0 + v1);
}

then it outputs 50. This is probably resolved by the compiler but because of the lack of consistency there is definitely something wrong going on here 

The workaround is simple (but once again annoying), you just have to force a cast like this:

void Test1(UINT16 v0, INT8 v1) {
printf("%d", v0 + (INT16)v1);
}

3. Right Shift operator (>>) doesn't work with 16-bit values

Similar to the unary minus, the >> operator either crashes or gives you a random value. The next code crashes:

void Test2() {
UINT16 v16 = 50;
UINT16 n = 2;
printf("%d ", v16 >> n);
}

Also 50 >> n or v16 >> 2 will crash but if you write 50 >> 2 that works, I think the compiler is resolving that on compilation time. The workaroud I found here is a big surprising since it uses the operator itself. You just need to use the next function:

INT16 DespRight(INT16 a, INT8 b) {
return a >> b;
}

void Test2() {
UINT16 v16 = 50;
UINT16 n = 2;
printf("%d \n", DespRight(v16, n));
}

that seems to stop some kind of optimization that at the end is breaking up everything... strange
The Left Shift operator (<<) seems to work without problems

4. Comparing 8 bit value from struct with another 8 bit value fails


This one was causing a very odd behaviour on my engine making some sprites not properly being spawned. Calling Test3 here with a value of 5 will output 1 (TRUE)

struct StructTest {
UINT8 v8;
};

void Test3(UINT8 v8_0) {
struct StructTest test = {0};

printf("%d ", (UINT16)(test.v8 == v8_0));
}

This won't happen if you use 16-bit values instead. I found a couple of workarounds here, you can either change the order of the comparison:

void Test3_fix(UINT8 v8_0) {
struct StructTest test = {0};
printf("%d ", (UINT16)(v8_0 == test.v8));
}

or cast the values to 16-bit

void Test3_fix(UINT8 v8_0) {
struct StructTest test = {0};
printf("%d\n", ((UINT16)v8_0 == (UINT16)test.v8));
}

Something interesting is that if you write the code followed by the workarounds like this:

void Test3(UINT8 v8_0) {
struct StructTest test = {0};

printf("%d ", (UINT16)(test.v8 == v8_0));
printf("%d ", (UINT16)(v8_0 == test.v8)); 
printf("%d\n", ((UINT16)v8_0 == (UINT16)test.v8));
}

then it outputs the right value (¿?¿?) in the 3 cases

That's all for now. I have created a public repository wit the tests here, feel free to contribute and if you find any mistake on anything I said please let me know. I have tried to compile all this using gbdk-n which works with the latest version of sdcc but since it doesn't support printf I haven't been able to test it. Also gbdk-n doesn't have proper support for banks yet and that is a big problem.