[Vm-dev] Question about closure

stephane ducasse stephane.ducasse at gmail.com
Mon Apr 23 05:15:28 UTC 2012


Thanks I will digest your answer (when I will have a calm moment and not sleepy brain).

Stef
On Apr 22, 2012, at 11:05 PM, Eliot Miranda wrote:

> 
> 
> 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 stack
> 
> 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 "indirectTempsCopy := 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].
> 	false
> 		ifTrue: "Old code to simulate the closure value primitive on VMs that lack it."
> 			[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
> 
> MethodContext>>privRefresh
> 	"Reinitialize the receiver so that it is in the state it was at its creation."
> 
> 	closureOrNil
> 		ifNotNil:
> 			[pc := closureOrNil startpc.
> 			self stackp: closureOrNil numArgs + closureOrNil numCopiedValues.
> 			1 to: closureOrNil numCopiedValues do:
> 				[:i | self tempAt: closureOrNil numArgs + i put: (closureOrNil at: i)]]
> 		ifNil:
> 			[pc := method initialPC.
> 			self stackp: method numTemps.
> 			method numArgs+1 to: method numTemps do:
> 				[:i | self tempAt: i put: nil]]
> 
> Make sense now?
> 
> -- 
> best,
> Eliot
> 



More information about the Vm-dev mailing list