[Vm-dev] [squeak-dev] Bug whith contextOn:do:
Tobias Pape
Das.Linux at gmx.de
Thu May 28 11:36:19 UTC 2015
Hi all
Picking up an old discussion, I'd like to include both of Eliot fixes
regarding contextOn:do: and ensure into trunk before release.
Any vetoes?
Best regards
-Tobias
On 31.03.2015, at 23:10, Eliot Miranda <eliot.miranda at gmail.com> wrote:
> Hi Tobias,
>
> first let me sympathize. This is such horrible code :-(. Ovr the years, getting this code to work with Cog's context-to-stack-mapping machinery has given me headaches :-).
>
>
> On Tue, Mar 31, 2015 at 9:01 AM, Tobias Pape <Das.Linux at gmx.de> wrote:
> Hey fellow Squeakers
>
> [Attention: low level lengthy stuff]
>
> I today encountered a strange behavior.
> When running the Startup code, somewhen ContextPart>>#complete is called,
> which, in the process issues #contextOn:do:, which subsequently somewhere interanlly
> does a jump:
>
> contextOn: exceptionClass do: block
> "Create an #on:do: context that is ready to return from executing its receiver"
>
> | ctxt chain |
> ctxt := thisContext.
> [chain := thisContext sender cut: ctxt. ctxt jump] on: exceptionClass do: block.
> "jump above will resume here without unwinding chain"
> ^ chain
>
> The idea is that we end up right before '^ chain' as the comment indicates.
> so what happens is that ctxt is the context of #contextOn:do: itself and it gets
> send #jump in the closure.
>
> Jump now does the following:
>
> jump
> "Abandon thisContext and resume self instead (using the same current process). You may want to save thisContext's sender before calling this so you can jump back to it.
> Self MUST BE a top context (ie. a suspended context or a abandoned context that was jumped out of). A top context already has its return value on its stack (see Interpreter>>primitiveSuspend and other suspending primitives).
> thisContext's sender is converted to a top context (by pushing a nil return value on its stack) so it can be jump back to."
>
> | top |
> "Make abandoned context a top context (has return value (nil)) so it can be jumped back to"
> thisContext sender push: nil.
>
> "Pop self return value then return it to self (since we jump to self by returning to it)"
> stackp = 0 ifTrue: [self stepToSendOrReturn].
> stackp = 0 ifTrue: [self push: nil]. "must be quick return self/constant"
> top := self pop.
> thisContext privSender: self.
> ^ top
>
> So. bytecode for #contextOn:do: is:
>
> 29 <8A 01> push: (Array new: 1)
> 31 <6B> popIntoTemp: 3
> 32 <89> pushThisContext:
> 33 <6A> popIntoTemp: 2
> 34 <12> pushTemp: 2
> 35 <13> pushTemp: 3
> 36 <8F 20 00 0A> closureNumCopied: 2 numArgs: 0 bytes 40 to 49
> 40 <89> pushThisContext:
> 41 <D2> send: sender
> 42 <10> pushTemp: 0
> 43 <E1> send: cut:
> 44 <8E 00 01> popIntoTemp: 0 inVectorAt: 1
> 47 <10> pushTemp: 0
> 48 <D3> send: jump
> 49 <7D> blockReturn
> 50 <10> pushTemp: 0
> 51 <11> pushTemp: 1
> 52 <F0> send: on:do:
> 53 <87> pop
> 54 <8C 00 03> pushTemp: 0 inVectorAt: 3
> 57 <7C> returnTop
>
>
> The jump lands right at 53 and does a pop.
> HOWEVER, at this point the stack of this context is empty and the pop actually pops the 3rd temp
> from the temps that 'just happens' to be right under the stack. This should be fatal.
> HOWEVER again, Squeak actually does not pop but only decrement the SP so the temp access still
> works(this _could_ be fine but some implementations (Eg, RSqueak) tried to separate temps and
> stack; which is not possible currently).
>
> What could be the problem here?
> - are the 'stackp = 0'-checks in #jump wrong and they actually should check for the actual stack depth _after_ temps?
>
> It does look like it. I would have expected this to be more correct:
>
> jump
> "Abandon thisContext and resume self instead (using the same current process).
> You may want to save thisContext's sender before calling this so you can jump back to it.
> Self MUST BE a top context (ie. a suspended context or a abandoned context that was jumped
> out of). A top context already has its return value on its stack (see Interpreter>>primitiveSuspend
> and other suspending primitives). thisContext's sender is converted to a top context (by pushing a
> nil return value on its stack) so it can be jump back to."
>
> | top |
> "Make abandoned context a top context (has return value (nil)) so it can be jumped back to"
> thisContext sender push: nil.
>
> "Pop self return value then return it to self (since we jump to self by returning to it)"
> stackp <= self numTemps ifTrue: [self stepToSendOrReturn].
> (stackp <= self numTemps
> and: [self willJustPop]) ifTrue: [self push: nil]. "must be quick return self/constant"
>
> top := self pop.
> thisContext privSender: self.
> ^top
>
> - should we put in a "sacrificial anode" in #contextOn:do: so that the pop does not pop the empty stack? (like this:
>
> contextOn: exceptionClass do: block
> "Create an #on:do: context that is ready to return from executing its receiver"
>
> | ctxt chain |
> ctxt := thisContext.
> [chain := thisContext sender cut: ctxt.
> ctxt push: nil. "sacrifical anode"
> ctxt jump
> ] on: exceptionClass do: block.
> "jump above will resume here without unwinding chain"
> ^ chain
>
> That looks right. Once the on:do: is sent the thisContext of contextOn:do:'s stack contains only exceptionClass, block, context and the indirection vector containing chain. So it is in the stack = self numTemps case.
>
>
> - Or is there an even better way?
>
> I'm not sure the other ways are any better. The way to transfer to a context without disturbing its stack is to do a process switch. So you do something equivalent to
>
> jump
> "Abandon thisContext and resume self instead (using the same current process)."
>
> | process semaphore |
> process := Processor activeProcess.
> semaphore := Semaphore new.
>
> [process suspendedContext unwindTo: self.
> process suspendedContext: self.
> semaphore signal] fork.
>
> semaphore wait
>
> This way no bizarre stack manipulations are going on, and no return value is pushed, because there isn't a return. One may get away with:
>
> jump
> "Abandon thisContext and resume self instead (using the same current process)."
>
> [| process |
> process := Processor activeProcess.
> process suspendedContext unwindTo: self.
> process suspendedContext: self] fork.
>
> Processor yield
>
> I'd be interested in your experience using either of these. One of the advantages the process switch versions have is in not updating the receiving context sp there's a chance the context-to-stack mapping machinery won't flush the context to the heap. In the end it might actually be faster.
> --
> best,
> Eliot
More information about the Squeak-dev
mailing list
|