Skip to main content.

Friday, March 21, 2008

In the "funny / peculiar" meaning. I am developing the SDL module, and with that I am reinforcing the app-falcon integration mechanisms. I wrote a new item type called "membuf", which allows to do interact directly with binary data coming from SDL images through Falcon VM statements. But that's not the point here.

The point is that, running a funny test of this feature on a test that already loaded and displayed an image (our project logo :-) I run sporadically on this error:

RangeError RT0047 at sdl_logo.__main__:37(PC:272): Access array out of bounds (STV)
Traceback:
sdl_logo.__main__:37(PC:272)


The code generating this was...

pixels = screen.pixels

for y = 0 to 479
for x = 0 to 639
n = x + y * 640
pixels[ n ] = 0xFFFFFFFF
end
end

There was no memory corruption; just, at a spot the value of N went mad. Further investigation shown that inspecting its value cleared the error.
I started thinking there was a problem in nested fors; in fact, using a for-in on range cured the problem. I wanted to remove the for loops for a long time now, and since we have ranges that may store 3 values now, that is one thing I am loosely considering. However, before removing this statement, I wanted to be sure there wasn't another error hidden somewhere else.

I discovered that changing the patterns of garbage collection cleaned the error too. In the beginning, I thought the GC was ripping too much memory, or messing it up. But the thing seemed so clean. Then, I had a look at the VM code:

PUSH 639
PUSH 0
FORI _label_304, $*x, 0
.line 36
_label_240:
MUL $*y, 640
ADD $*x, A
LD $*n, A
.line 37
STV $*pixels, $*n, 268435455
FORN _label_240, $*x


The LD $*n, A instruction is the one that does the trick, moving the value of the pixel index in the variable that is then used to store the data. I started to think that the GC collection loop destroyed the A register and put something in that. A fast check proved I was right; there was an object instance in A by the time STV was called. Also, saving A before the GC call and restoring it right after did the trick.

But, the Garbage Collector is not supposed to change registers, and if it did it, then the whole thing would have blown out. I had a suspect, but to confirm it I added a data watchpoint to the A register right before entering the GC and... I got it.

GC scans object properties using CoreObject?.getProperty(). SDLScreen is a reflective class, and this means its UserData.getProperty() method gets called instead. Surface UserData creates a small object to store the rectangle in a property. Apart the fact that the object was re-created when not needed, the function doing this small step was...

CoreObject *MakeRectInst( VMachine *vm, const ::SDL_Rect &rect )
{
Item *cls = vm->findWKI( "SDLRect" );
fassert( cls != 0 );
CoreObject *obj = cls->asClass()->createInstance();
RectToObject( rect, obj );
vm->retval( obj );
}

And vm->retval() changes the A register. I am so accustomed to do falcon extension functions (that are void and return data to VM through this mechanism) that I didn't realize I wrote that code instead of the correct return obj. Moreover, the compiler (g++4.x) didn't even warn me about this non-void function not returning. The thing worked normally as, after this function returns, normally, the value that it returns is really stored in A and then passed to the script. Just, when this happened in the GC loop this turned out in a disaster.

Someone may think this means that Falcon code is frail (well, no VM can be handed without care) but none of this would have happened if just the compiler told me I wasn't returning anything.

Comments

No comments yet

Add Comment

This item is closed, it's not possible to add new comments to it or to vote on it