[Vm-dev] Fwd: Question about closure

Eliot Miranda eliot.miranda at gmail.com
Sun Apr 22 21:05:35 UTC 2012

On Sun, Apr 22, 2012 at 11:48 AM, stephane ducasse <
stephane.ducasse at gmail.com> wrote:

> Hi eliot
> I'm rereading your blog. I understand that you create an array so that you
> are sure that
> local of the activation context of the method containing a block are not
> modified, just the
> array elements. so this means in particular that since the array is not
> allocated on the heap
> you guarantee that captured variables of the closure environment outlive
> the method activation.
> I hope I got it correctly.

The array *is* allocated on the heap.  I don't guarantee that captured
variables of the closure environment outlive the method activation.  That
depends on whether a reference to the block is captured.  If some object
takes a reference to the block (stores it in an inst var) then the block
can outlive its enclosing method activation.  If this happens, if the
variables closed-over by the block live on the stack of the method
activation then the method activation must persist too.  And that's a
problem if the activation has been mapped to a stack ftrame, because we've
exited the stack frame on returning from the method activation and so must
update the state of the context from the stack frame at return time, and
that's what we're trying to avoid. So instead any shared closed-over
variables (variables that can't be copied) get allocated in a
heap-allocated indirection vector (the array).

> Now I have a question about the following snippet representing what the
> compiler is doing.
> Where indirectTempsCopy is initialized and to what?
> inject: thisValue into: binaryBlock
>        | indirectTemps |
>        indirectTemps := Array new: 1.
>        indirectTemps at: 1 put: thisValue.
>        self do: (thisContext
>                closureCopy:
>                        [:each |
>                                | binaryBlockCopy indirectTempsCopy |
>                                indirectTempsCopy
>                                        at:1
>                                        put: (binaryBlockCopy value:
> (indirectTempsCopy at: 1) value: each)]
>                                copiedValues: (Array with: binaryBlock
> with: indirectTemps)).
>        ^indirectTemps at: 1
> I thought that it should be like and I was wondering after if and why the
> indirectTemps should be copied.
> Because we create one context for each
>                [:each |
>                                | binaryBlockCopy indirectTempsCopy |
>                                indirectTempsCopy := indirectTemps
>                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>                                indirectTempsCopy
>                                        at:1
>                                        put: (binaryBlockCopy value:
> (indirectTempsCopy at: 1) value: each)]
>                                copiedValues: (Array with: binaryBlock
> with: indirectTemps)).
>        ^indirectTemps at: 1

That's right.  But the indirectTempsCopy assignment happens in the block
value primitive.  The first place the indirection vector ends up is on the
stack frame of the method activation:

17 <8A 01> push: (Array new: 1)
19 <6A> popIntoTemp: 2

The second place it ends up is in the block:

25 <11> pushTemp: 1    << pushes the binaryBlock argument to inject:into:
26 <12> pushTemp: 2    << pushes the indirection vector
27 <8F 21 00 0A> closureNumCopied: 2 numArgs: 1 bytes 31 to 40  <<= creates
the block within inject:into: that takes a copy of the two arguments on the

The third place it sends up is at stack offset 2 in the inner block.  In
the inner block the argument nextValue is at offset 0, binaryBlock is at
offset 1, and the indirection vector is at offset 2.  So the line
:= indirectTemps" is implicit in the value:value: primitive that activates
the context.

i.e. in the value:value: primitive you'll see a simulation of this which is
disabled (in the false ifTrue: arm):

BlockClosure>>value: firstArg value: secondArg
"Activate the receiver, creating a closure activation (MethodContext)
 whose closure is the receiver and whose caller is the sender of this
 message. Supply the arguments and copied values to the activation
 as its arguments and copied temps. Primitive. Essential."
<primitive: 203>
| newContext |
numArgs ~= 2 ifTrue:
[self numArgsError: 2].
ifTrue: "Old code to simulate the closure value primitive on VMs that lack
[newContext := *self asContextWithSender: thisContext sender*.
*newContext at: 1 put: firstArg.*
* newContext at: 2 put: secondArg.*
thisContext privSender: newContext]
ifFalse: [self primitiveFailed]

BlockClosure>>asContextWithSender: aContext
"Inner private support method for evaluation.  Do not use unless you know
what you're doing."

^((MethodContext newForMethod: outerContext method)
setSender: aContext
receiver: outerContext receiver
method: outerContext method
closure: self
startpc: startpc) *privRefresh*

"Reinitialize the receiver so that it is in the state it was at its

[pc := closureOrNil startpc.
self stackp: *closureOrNil numArgs + closureOrNil numCopiedValues*.
*1 to: closureOrNil numCopiedValues* do:
[:i | self tempAt: *closureOrNil numArgs + i* put: (closureOrNil at: i)]]
[pc := method initialPC.
self stackp: method numTemps.
method numArgs+1 to: method numTemps do:
[:i | self tempAt: i put: nil]]

Make sense now?

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/vm-dev/attachments/20120422/9d737567/attachment.htm

More information about the Vm-dev mailing list