This weekend, I rewrote large chunks of LanguageKit, simplifying a few things and upgrading a few other things. If you are using LanguageKit / Smalltalk, make sure up update both LanguageKit and SmalltalkKit - they contain interrelated changes.
The changes involved the way in which local variables are allocated on the stack. Every stack frame now contains a StackContext object. All of the local variables are now stored as instance variables in this object. The BlockClosure object is now stateless - all of the state information is stored in the context hierarchy.
This simplified the symbol table a lot, and meant that a lot of ugly code related to closures has gone away. The most obvious side effect of this is that accesses to variables from blocks now works correctly. In addition to contexts, blocs are also now allocated on the stack. This should speed things up a bit and have the nice side-effect that it gets rid of a potential memory leak that used to exist.
The other side effect is that it greatly simplified the task of implementing upward funargs. If you want to keep a block around for longer than the duration of the parent scope, then you just need to send it a -
retain message (assigning it to an instance variable in Smalltalk does this for you). When you do this, you get a copy of the block, rather than original (you don't want to store pointers to things that are on the stack - bad things happen if you do).
The following program illustrates this:
NSObject subclass: SmalltalkTool [ | block | run [ self setBlock. self callBlock. ] setBlock [ | a | a := 'Local variable'. block := [ a log. ]. ] callBlock [ 'Testing retained block:' log. block value. ] ]
When you run this, you get the following output:
$ edlc -f retainblock.st 2008-12-06 17:27:36.530 edlc Testing retained block: 2008-12-06 17:27:36.566 edlc Local variable
As you can see, the block is stored in an instance variable and is still able to access the variable
a from the
setBlock method, even after that method has exited.
The block you acquire still contains a pointer to the context on the stack, however. This means that if you use it before the enclosing function, method, or block, returns, you will still get any changes to other variables propagated back up the stack.
When a copy of a block is made, it calls -
retainWithPointer: on its scope. This method stores the address of the pointer which references the block and swizzles the block's
isa pointer so that it becomes a RetainedContext object. At the end of any LanguageKit-generated block or method, a simple test is performed on the context to see if it is a RetainedContext. If it is, it is sent a -
promote message. This then copies it to the heap and updates all of the pointers referencing it.
As you can imagine, this is quite an expensive procedure. The good news is that it's rarely needed, and only incurs one test at the end of each method or block when it isn't. Since blocks are now allocated on the stack, I expect that the new version should be faster than the old one (although I haven't actually benchmarked it).
The new version is in trunk now, so please test it. People watching svn will have noticed that the BlockContext object currently has an unused instance variable:
char **symbolTable. This will be set to an array of the variable names for every variable in the context, which will allow contexts to be introspected.