Implementing closures (was: Re: 3.9 vs. 3.10 : Closures, fixTemps)

Andreas Raab andreas.raab at gmx.de
Thu Dec 20 04:19:33 UTC 2007


Mathieu Suen wrote:
> Well so propose us your solution.

Okay, I promised not to invent a solution but it's one of "those" 
problems that I've thought about for a while in the past so let's see. 
Here is how I would address the problem of implementing full closures in 
Squeak:

1) First, I would fix the "attempt to evaluate a block that is already 
evaluated" problem. That's about ten lines of code in the VM - if you 
look at primitiveValue[withArgs] you'll find that we fail if the 
CallerIndex is non-nil. Instead of failing, I'd clone the original 
context and continue as usual.

At this point you'd be able to execute code like:

   | v |
   fac := [v > 0 ifTrue:[1] ifFalse:[v := v-1. fac value * (v+1)]].
   fac value: 10.

which allows recursive evaluation but without block args (since they get 
hacked in some terrible ways).

2) Next, I'd deal with block variables (temps and args). Instead of 
sharing the method contexts, blocks would store their own temps and 
access them via tempAt:. So that for example code like here:

   self do:[:v| | v2 | v2 := v squared. v2].

would be compiled into to:

   self do:[:v|
     thisContext push: nil. "allocate v2"
     thisContext tempAt: 2 "v2" put: (thisContext tempAt: 1 "v") squared.
     thisContext tempAt: 2.
   ].

This would be slower than what we have today but obviously, the pattern 
"thisContext tempAt:" is a prime candidate for optimization. At this 
point we'd be able to run the following correctly:

fac [:v| v = 0 ifTrue:[1] ifFalse:[ v * (fac value: v-1)].

The implementation would be pretty straightforward too, since all you'd 
have to do is to add BlockVariableNode that generates tempAt: messages 
(plus some initialization). Very, very straightforward.

3) Next, I would deal with "outer" state like here:

   self do:[:x|
     x do:[:y| x * y]
   ].

This is probably the hardest part since dealing with "non-method 
closed-over state" can be tricky due to life-time issues. Anyway, for 
starters I would just add a hidden temporary "outerContext" for all 
blocks that is just a temp like any other such that the compiler can 
generate code like:

   self do:[:x|
     x do:[:y|
       ((thisContext tempAt: 1) "outerContext" tempAt: 1 "x") *
         (thisContext tempAt: 2) "y"
     ].
   ].

At this point (I think - please correct me if I'm missing something) we 
have *complete* closure semantics in a straightforward way. What remains 
is optimization.

4) Lastly, optimization. I would start with the obvious 
push/popBlockTemp bytecodes which would bring all of the above up to par 
with current Squeak (except (3) which could still be slower). Finally, 
one might look at the life-time of closed-over state - it is certainly 
not necessary to preserve the entire outer context so there may be other 
things that could be done to fix it.

In any case, the above looks pretty simple and straightforward. Wouldn't 
you agree?

Cheers,
   - Andreas



More information about the Squeak-dev mailing list