Last updated at 3:31 am UTC on 2 October 2006
New compiler and true block closures for standard Squeak image and VM.
Version: 19 July 2004 for Squeak-3.7
by Anthony Hannan
- ClosureCompiler2-ajh.zip - Block closures and new compiler. Unzip and follow install instructions in enclosed README file.
- ClosureCompilerAssignCapturedArgsFix-ajh.cs - a fix to file-in after loading compiler.
This package adds a closure compiler to the standard Squeak image without replacing the existing compiler. A new 'general' preference called #compileBlocksAsClosures determines which compiler to use for NEW methods only. When true the closure compiler is used, when false the existing compiler is used (default). Block contexts will still be created by existing methods.
Etoy scripts/tiles do not work yet with the closure compiler so be sure to use them with the above preference set to false.
The closure compiler is a total rewrite of the Smalltalk compiler using the SmaCC parser generator and the Refactory abstract systax tree (www.refactory.com). A special thanks goes to John Brant and Don Roberts for creating both the Refactory Browser and Smacc, and to Daniel Vainsencher and Markus Gaelli for porting them, respectively, to Squeak.
For more details on the compiler organization itself see the Compiler class comment. Below is a description of how the compiler creates block closures.
Each non-inlined blocks has its own CompiledMethod. These block methods are held in the literals of the home method and sent the #createBlock: message at runtime to create BlockClosures. Home method temps captured by inner blocks are placed inside a ClosureEnvironment when the home method is started. This environment is supplied as the argument to each #createBlock:. When #value... is sent to a block closure, its method is executed in a new MethodContext with its closure environment as the receiver. The block method accesses its free variables (captured home temps) via this environment.
Closure environments are nested mirroring the nesting of blocks. Each environment points to its parent environment (the top method environment has no parent). However, for efficiency, environments that have no captured temps are skipped (never created). For example, an environment's parent may actually be its grand-parent. There is no special parent variable in ClosureEnvironment, it is just another named variable such as 'self' or 'parent env' (special var with space so it can't be referenced by user code), or it may not be their at all.
A block closure that returns to its home context does so by finding the thisContext sender that owns the top environment. A return inside a block forces the home environment to be created even if it has no captured temps. Each context holds its local environment (which holds its captured temps) in its #myEnv instance variable (previously the unused #receiverMap variable). Code that references captured temps goes through the #myEnv context variable.
Block closures are totally separate from their home context. They are reentrant and each activation has its own block-local temps. So except for the thisContext psuedo-variable, contexts are now LIFO (assuming we get rid of old block contexts and recompile the whole image).
While block contexts reuse the same context on each activation, block closures create a new context each time. This makes block closures slower, until we speed up general activation. For an example of its slowness (and general slowness of context activation) execute '[(1 to: 1000000) do: [:i | i]] timeToRun' under each compiler (toggle the preference). On my Pentium II its about 50% slower (after prim 189 wass added, ie. no args array created on each block call). This pathological case should not discourage you. It does nothing in the block, so the context creation dominates. In practice, real work will be done inside each activation, and also, blocks are not as common as straight methods. Running macro-benchmarks you will not see such a noticable difference.
Since blocks access fields inside environments it would be nice to have bytecodes that can access any object's instance/index variables intead of just the receiver's (or be forced to send #at: or #instVarAt:). See PotentialNewBytecodes.txt for recommended additional bytecodes that would reduce code size and potentially improve execution speed.
Thanks to Boris Gaertner and Rob Withers for writing closure test cases (BlockClosuresTestCase).
Question: has the debugger been updated to understand the new encoding of captured variables?