On Sat, Apr 21, 2012 at 12:51 AM, Stéphane Ducasse <
[email protected]> wrote:
> Thanks eliot
>
> I'm refreshing my fuzzy understanding of closure implementation details (I
> would love to avoid to have students, kids, bosses and … around to get some
> calm time :).
>
> I will start rereading your blog.
>
> > tempAt: in Method context uses <primitive: 210>, which is failing.
> Maybe there is a new primitive we are not aware of for Blocks...
> >
> > No, 210 is what is needed. The issue is my implementation of temporary
> initialization in blocks, which simply uses pushNil to allocate and
> initialize each temp. So if one inspects [|a|a:=2] and sends it self method
> symbolic you get:
> >
> > 13 <8F 00 00 05> closureNumCopied: 0 numArgs: 0 bytes 17 to 21
> > 17 <73> pushConstant: nil
> > 18 <77> pushConstant: 2
> > 19 <81 40> storeIntoTemp: 0
> > 21 <7D> blockReturn
> > 22 <7C> returnTop
> >
> > And when you say self asContext pc you get 17, which is *before* the nil
> is pushed. So you'll want to modify tempNamed: to compare the index of the
> temporary with the stack pointer and answer nil if the index is beyond the
> stack pointer.
>
> Is it correct? I think so but I'm learning ContextPart
>
> tempNamed: aName
> "Returns the value of the temporaries, aName."
> "Implementation notes:
> temporary initialization in blocks simply uses pushNil to allocate
> and initialize each temp. So if one inspects [|a|a:=2] and sends it self
> method symbolic you get:
>
> 13 <8F 00 00 05> closureNumCopied: 0 numArgs: 0 bytes 17 to 21
> 17 <73> pushConstant: nil
> 18 <77> pushConstant: 2
> 19 <81 40> storeIntoTemp: 0
> 21 <7D> blockReturn
> 22 <7C> returnTop
>
> And when we check self asContext pc we get 17, which is *before* the nil
> is pushed. Therefore we should pay attention when querying a temporary if
> the temporary allocation was executed."
>
> | index |
> index := (self tempNames indexOf: aName).
> ^ index >= stackp
> ifTrue: [ nil]
> ifFalse: [self tempAt: (self tempNames indexOf: aName)]
>
>
>
> >
> > The same bug exists in Squeak, e.g. the below gave an error until I
> fixed DebuggerMethodMap>>namedTempAt:in:
>
>
> I checked and this method is the same in pharo and latest squeak
> namedTempAt: index in: aContext
> "Answer the value of the temp at index in aContext where index is
> relative
> to the array of temp names answered by tempNamesForContext:"
> ^self
> privateTempAt: index
> in: aContext
> startpcsToBlockExtents: aContext method
> startpcsToBlockExtents
>
>
>
> > | b |
> > b := [ |a| a:=2 ].
> > { b asContext tempNames. b method debuggerMap tempsAndValuesForContext:
> b asContext }
> > #(#('b' 'a') 'b: [closure] in UndefinedObject>>DoIt
> > a: nil
> > ')
> >
> >
> > Why did I use pushNil to allocate temps? It was not a good choice; it
> means for example that to determine how many temps a block has one has to
> parse all the bytecodes and see where the stack pointer is at the end
> (since initial pushNils are ambiguous; an initial pushNil might be
> producing a parameter or result or initializing a temp). But not using
> pushNil would have meant a more complex bytecode for closure creation plus
> another inst var in BlockClosure. I made another similar misstep in not
> storing the method in a BlockClosure, hence preventing simple
> implementation of clean blocks (one needs to synthesize an outerContext
> also). We live and learn.
>
> Do you mean that the closure cannot access directly method but should do
> go to its outer context to access the methods.
> When this is needed to set the instruction pointer?
>
>
>
> Another thoughts while writing the chapter on block execution..
>
> Am I correct to say: we need contexts (to me contexts are activation
> objects by opposition to bindings which could be used to represent scope)
> to query local temps of a closure because they will be allocated at
> execution while
> the temporaries that are shared are defined in their defining environment
> (now the trick is that in Smalltalk environment is represented as a
> context).
>
> I get why I'm often slow because I did not implemented closure in
> Smalltalk only in lisp
>
> foo
>
> |b|
> b := 3.
> ^ [ |a | a :=2. a]
>
> In my stupid lisp implementation an environment is represented by bindings
> and closure points to their own environment
> so
> [ |a | a :=2. a]
> points to
> (b.3) (toplevel) (and not top-level)
>
> and for closure execution we just added temporaly a binding for a to the
> closure environment.
> eval a:=2. a in (a) (b.3) (toplevel)
>
This is the wrong example. You need to consider how to implement this
example:
Object subclass: #AbstractSuperclass
AbstractSuperclass>>computeV
| v |
v := 0.
self maybeCapture: [v := v + 1. v].
^self maybeBlock: v
AbstractSuperclass subclass: #CaptureBlock
instanceVariableNames: 'block'
CaptureBlock>>maybeCapture: aBlock
block := aBlock
CaptureBlock>>maybeBlock: value
^block
AbstractSubclass subclass: #DontCaptureBlock
DontCaptureBlock>>maybeCapture: aBlock
DontCaptureBlock>>maybeBlock: value
^value
Then
| thingOne thingTwo |
thingOne := CaptureBlock computeV.
thingTwo := DontCaptureBlock computeV.
(1 to: 10) collect: [:ignore| { thingOne value. thingTwo value }]
should answer
#((1 0) (2 0) (3 0) ... (10 0))
right? So where does the l-value for v in computeV live? Iy must be an
l-value since it is assigned to in the block in computeV. It must outlive
the activation of computeV since CaptureBlock>maybeCapture: captures the
block and CaptureBlock>>maybeBlock: answers it and the doit evaluates it.
If it lives on the stack of the activation of computeV then there is no
problem in a Context VM, but in a VM that maps activations to stack frames
something special (and slow) has to happen when returning from computeV.
However, if the l-value lives in a separate Array (as it does in my
VisualWorks and Squeak closure implementations, and as happens in some Lisp
implementations), nothing special has to happen. The block refers to the
Array, *not* to the activation of computeV.
Make sense now?
--
best,
Eliot