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

Klaus D. Witzel klaus.witzel at cobss.com
Thu Dec 20 10:53:53 UTC 2007


On Thu, 20 Dec 2007 05:19:33 +0100, Andreas Raab wrote:

> 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

Did you mean

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

I confess that I copied the idea from Self's view on method activation (a  
clone of a template :)

But then, how'd that be on Smalltalks other than Squeak, i.e. if a  
Seasider creates+tests such code with Squeak and later runs it on another  
Seaside-capable VM? Looks like opening the cross-Smalltalk Hunting Season  
:(

> (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.
>    ].

Sure, that'd be an easy one for the compiler+decompiler guild.

> This would be slower than what we have today but obviously, the pattern  
> "thisContext tempAt:" is a prime candidate for optimization.

AFAIK there's no bytecode which pushes/pops temps in *activeContext*, only  
in *homeContext* (and so the main reason behind this discussion). That  
looks like a major change to VM.

> At this point we'd be able to run the following correctly:
>
> fac [:v| v = 0 ifTrue:[1] ifFalse:[ v * (fac value: v-1)].

This statement does not parse. Can you reformulate without syntax error.

> 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"
>      ].
>    ].

Sort of "outerContext" is already part of the BlockClosure handling with  
NewCompiler, there it's an instance of ClosureEnvironment.

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

I would like to see comment(s) on the above from Marcus, but he's  
currently not subscribed to squeak-dev (cc'ed him anyways).

> What remains is optimization.

Bryce & Marcus & Math & my discussed optimizing BlockClosure+bytecodes  
some time ago, if that's of interest

- http://www.google.com/search?&q=Newcompiler+bytecode+recycling+closure

> 4) Lastly, optimization.

During the discussion we came to the point that context recycling, which  
is interrupted by explicit and implicit #pushActiveContext, would no  
longer be hindered because BlockClosure seems to be a complete replacement  
for BlockContext. By that time it looked like a very promising speed-up.

/Klaus

> 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