Good methods for trouble shooting code?

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

Good methods for trouble shooting code?

garamor
Hello everyone! I have been working away, building my OS for the last few days and I am coming to the end of this project. A lot of the challenging bugs have started to pile up, they are not syntactical errors which results in the compiler not finding them, and so they end up cropping up when I am running the VMEmulator. I then am forced to spend hours grinding away, going line by line through the VM Code and keeping careful track of all of the values in memory and cross referencing them with the original jack program to see what the values should be. Does anyone have any suggestions on how I might go about finding these bugs in a more efficient manner?
Reply | Threaded
Open this post in threaded view
|

Re: Good methods for trouble shooting code?

WBahn
Administrator
Are these bugs in the Jack OS code, or in your Jack compiler?

Regardless, when developing software one of the best strategies is incremental development with thorough testing.

Don't try to implement, say, a line drawing function that does everything before trying to run it. Develop a function that can draw a horizontal line. Test it. If it doesn't work for that, then it's almost certainly not going to work for any other kind of line. But the code at this point is small and easy to work through, so get it working for this case. Then add the ability to draw a vertical line. Only after that is also working, do you worry about being able to draw a line at a shallow positive slope and test it. Then a steep positive slope. Then a negative slope. Then a forty-five degree slope. Then make sure that it can handle the two end points being in either order.

Be sure to pick end points that are at each end and withing the interior of a sixteen-pixel word in the Screen RAM.

The key is to test as many corner cases as you can think of. Go out of your way to break the code in as many ways as you can come up with.

Otherwise, you will end up missing mistakes in a function that is then used in other functions and when those functions don't work, you are stuck trying to figure out if it's the code in that function, or in one of the functions that are being called by it.

Don't skimp on testing. Too many people try one or two tests and if that doesn't break it, they move one.
Reply | Threaded
Open this post in threaded view
|

Re: Good methods for trouble shooting code?

garamor
Thanks for the help! I think I have been pretty good about doing incremental changes to my work, and testing each step of the way. The challenging bugs though are the ones that don't even through any errors, and just output something unexpected. here is an example:

I have written and tested each of the libraries in the OS using the tests that I got in the project 12 folder. As one final test I tried to run the Pong game that was shown in project 11 using my OS, and it seemed to be mostly working however there are a couple bugs. I have ruled out my compiler as the issue.

One bug is that the paddle leaves an after image, and has white lines running through it. Here is the second frame of the pong game running using my OS:



The paddle is moving right, and as you can see, it has left behind a black line and acquired a white one inside of it. Here is the code for my screen library where I would guess the bug is occurring:

class Screen {
    static boolean color;
    static int newBit;
    static Array powers_of_two, screenMem;
    function void init() {
        let screenMem = 16384;
        let color = true;
        let powers_of_two = Array.new(16);
        let powers_of_two[0] = 1;
        let powers_of_two[1] = 2;
        let powers_of_two[2] = 4;
        let powers_of_two[3] = 8;
        let powers_of_two[4] = 16;
        let powers_of_two[5] = 32;
        let powers_of_two[6] = 64;
        let powers_of_two[7] = 128;
        let powers_of_two[8] = 256;
        let powers_of_two[9] = 512;
        let powers_of_two[10] = 1024;
        let powers_of_two[11] = 2048;
        let powers_of_two[12] = 4096;
        let powers_of_two[13] = 8192;
        let powers_of_two[14] = 16384;
        let powers_of_two[15] = 16384+16384;
        return;
    }
    function void clearScreen() {
        var int screenCell;
        let screenCell = 8192;
        while (screenCell) {
            let screenCell = screenCell - 1;
            let screenMem[screenCell] = 0;
        }
        return;
    }
    function void setColor(boolean newColor) {
        let color = newColor;
        return;
    }
    function void drawPixel(int x, int y) {
        var int address, pixelBit;
        let address = (y * 32) + (x / 16);
        let pixelBit = powers_of_two[x & 15];
        if (color) {let screenMem[address] = screenMem[address] | pixelBit;}
        else       {let screenMem[address] = screenMem[address] & ~pixelBit;}
        return;
    }
    function void drawLine(int x1, int y1, int x2, int y2) {
        var int dx, dy, a, b, diff, temp, address, pixelBit;

        if (x1 > x2) {
            let temp = x1;
            let x1 = x2;
            let x2 = temp;
            let temp = y1;
            let y1 = y2;
            let y2 = temp;
        }

        let dx = x2 - x1;
        let dy = y2 - y1;

        if (dx = 0) {
            if (y1 > y2) {
                let temp = y1;
                let y1 = y2;
                let y2 = temp;
            }

            let address = (y1 * 32) + (x1 / 16);
            let pixelBit = powers_of_two[x1 & 15];
            let y2 = y2 + 1;
            while (y1 < y2) {
                if (color) {let screenMem[address] = screenMem[address] | pixelBit;}
                else       {let screenMem[address] = screenMem[address] & ~pixelBit;}
                let address = address + 32;
                let y1 = y1 + 1;
            }
        } else { if (dy = 0) {
            do Screen.drawHorizontalLine(x1, x2, y1);
        } else {
            if (y1 < y2) {
                while (~(a > dx) & ~(b > dy)) {
                    do Screen.drawPixel(x1 + a, y1 + b);
                    if (diff < 0) {let a = a + 1; let diff = diff + dy;}
                    else          {let b = b + 1; let diff = diff - dx;}
                }
            } else {
                while (~(a > dx) & ~(b < dy)) {
                    do Screen.drawPixel(x1 + a, y1 + b);
                    if (diff < 0) {let a = a + 1; let diff = diff - dy;}
                    else          {let b = b - 1; let diff = diff - dx;}
                }
            }
        }}
        return;
    }
    function void drawRectangle(int x1, int y1, int x2, int y2) {
        var int dx, dy, temp;
        if (x1 > x2) {
            let temp = x2;
            let x2 = x1;
            let x1 = temp;
        }
        if (y1 > y2) {
            let temp = y2;
            let y2 = y1;
            let y1 = temp;
        }
        let dx = x2 - x1;
        let dy = y2 - y1;
        while (dy > 0) {
            do Screen.drawHorizontalLine(x1, x2, y1 + dy);
            let dy = dy - 1;
        }
        do Screen.drawHorizontalLine(x1, x2, y1);
        return;
    }
    function void drawCircle(int x, int y, int radius) {
        var int dx, dy, radiusPlusOne;
        let dy = -radius;
        let radiusPlusOne = radius + 1;
        while (dy < radiusPlusOne) {
            let dx = Math.sqrt((radius * radius) - (dy * dy));
            do Screen.drawHorizontalLine(x - dx, x + dx, y + dy);
            let dy = dy + 1;
        }
        return;
    }

// LOCAL FUNCTIONS
    function void drawHorizontalLine(int x1, int x2, int y) {
        var int temp, leftAddress, rightAddress, yCell, leftEndOfLine, rightEndOfLine;

        if (x1 > x2) {
            let temp = x1;
            let x1 = x2;
            let x2 = temp;
        }

        let yCell = (y * 32);
        let leftAddress = yCell + (x1 / 16);
        let rightAddress = yCell + (x2 / 16);

        let leftEndOfLine = ~(powers_of_two[x1 & 15] - 1);
        let rightEndOfLine = powers_of_two[x2 & 15] - 1;

        if (leftAddress = rightAddress) {
            if (color) {let screenMem[leftAddress] = screenMem[leftAddress] | (leftEndOfLine & rightEndOfLine);}
            else       {let screenMem[leftAddress] = screenMem[leftAddress] & ~(leftEndOfLine & rightEndOfLine);}
        }
        else {
            if (color) {
                let screenMem[leftAddress] = screenMem[leftAddress] | leftEndOfLine;
                let screenMem[rightAddress] = screenMem[rightAddress] | rightEndOfLine;
            }
            else {
                let screenMem[leftAddress] = screenMem[leftAddress] & ~leftEndOfLine;
                let screenMem[rightAddress] = screenMem[rightAddress] & ~rightEndOfLine;
            }
            if ((rightAddress - leftAddress) > 1) {
                let leftAddress = leftAddress + 1;
                while (leftAddress < rightAddress) {
                    let screenMem[leftAddress] = color;
                    let leftAddress = leftAddress + 1;
                }
            }
        }
        return;
    }
}


If you could help my figure this out it would be greatly appreciated. Thanks! :)
Reply | Threaded
Open this post in threaded view
|

Re: Good methods for trouble shooting code?

dolomiti7
Your code contains already some optimizations which makes it more difficult to find bugs. First I would try to implement your drawHorizontalLine with a simple loop that calls drawPixel and see if that works. It will be slow, but you can narrow down if the bug is in that routine. Then you can continue to optimize.

However, in your code the bitmask for the right end doesn't look correct to me. Go through it for the 16 cases and see if it really returns what you expect.
let rightEndOfLine = powers_of_two[x2 & 15] - 1; // looks incorrect

Finally - when everything works - I would suggest using >= or <= comparisons in your loops which avoids repetitive code like:
while (dy > 0) {
  do Screen.drawHorizontalLine(x1, x2, y1 + dy);
  let dy = dy - 1;
}
do Screen.drawHorizontalLine(x1, x2, y1);


e.g. dy >= 0 is equivalent to ~(dy < 0)

Reply | Threaded
Open this post in threaded view
|

Re: Good methods for trouble shooting code?

garamor
This post was updated on .
I have thoroughly tested the drawHorizontalLine function and it seems to be working as intended (Edit: I now notice that there was 1 pixel missing from the right side of the line). Not only that but drawRectangle does too. If I draw rectangle on the screen in black, and then draw one in white using the same coordinates the original rectangle, it is erased with nothing left behind, also as intended.

As far as the rightEndOfLine bitmask goes if we change it to powers_of_to[(x2 & 15) + 1] - 1 I think it should work correctly.

The reason I am not using ~(dy < 0) is because this involves an additional not operation for each line of my rectangle. Because adding one extra call to drawHorizontalLine was such a miner addition, I just thought I might as well.

Edit: Swapped left and right when I was walking through the rightEndOfLine bitmask and started using the other equation half way through on accident.

Update: powers_of_to[(x2 & 15) + 1] - 1 fixed the issue! Hooray! :D

Update2: All OS test have been completed! N2T IS DONE!!! HOORAY!!! Time to finish my funky Space Ship Game.
Reply | Threaded
Open this post in threaded view
|

Re: Good methods for trouble shooting code?

dolomiti7
Glad that it's working now. I presume you have extended the powers of two array to size 17 as well. Otherwise it is pure luck if it operates correctly for x&15==15 coordinates, since you would access the array out of bounds ([16]).

Keep in mind that Pong isn't a good test case for correctness, and probably doesn't catch that since the paddle is updated only every few pixels.

Generally, I would recommend to avoid premature optimizations. Having an additional NOT command in the VM code is just a representation and doesn't necessarily mean that the final program will be slower. In fact this can be optimized away at zero costs by the VM translator. Trying to optimize this in the Jack source code reduces readability and increases the risk of introducing more bugs. Not to mention that it also increases code size which is in many cases a critical issue on the Hack platform.
Reply | Threaded
Open this post in threaded view
|

Re: Good methods for trouble shooting code?

garamor
I am not sure what the problem is with the powers_of_two array because it has a 15th element that would be accessed in the case of x&15==15. Even if I tried to add the next power of two to the array it would result in an overflow to zero, so I don't even see how it is possible. Am I missing something?

Thanks for really hammering home the part about Premature optimization. I think I could do to keep that in mind more often. What I was thinking when I decided to optimize the drawLine function immediately was "I could start with a completely different and simpler method for drawing lines and then do the more complected technique or I could just learn the more complex one from the get go. Either way I am going to have to learn it.".
Reply | Threaded
Open this post in threaded view
|

Re: Good methods for trouble shooting code?

dolomiti7
Update: powers_of_to[(x2 & 15) + 1] - 1 fixed the issue! Hooray! :D

let's assume x2=15, then you are accessing:
powers_of_to[(15 & 15) + 1]
->
powers_of_to[16]

But your array only has size 16, from [0] to [15]. So you are out of bounds.

You are right that the next power of 2 would overflow to 0, however if you think about it, that should just give you what you need in this context:
rightEnd = powers_of_to[16] - 1
->
-1 // all bits are set in the mask, just what you want if x is at the right end of the cell, e.g. x2&15=15
Reply | Threaded
Open this post in threaded view
|

Re: Good methods for trouble shooting code?

garamor
Ah, that makes sense. Thanks for answering all my questions, it's been quite helpful.