[Vm-dev] Incomprehendable behaviour of ContextPart class >>#contextOn:do:

Eliot Miranda eliot.miranda at gmail.com
Sun May 19 17:29:13 UTC 2013


On Sat, May 18, 2013 at 3:49 PM, Lars <lars.wassermann at googlemail.com>wrote:

>
>  Hi Eliot,
> thank you for the analysis.
> The question is why the code works with the VM, but not if we step through
> it. What does the VM do that makes this behave correctly, although the
> temps-array is removed from the stack prior to returning from
> #contextOn:do:?
>

the vm doesn't do anything special (* with one exception) because even
though the image code pops off the temps array the return pushes it back
almost immediately, before the temps array is used.

* the exception is that when a context is created for a stack frame the
code miust handle the fact that the stack may be empty and so the proxy
context must have a nil where the missing stack entry is, not an
uninitialized field.  See the comment
in updateStateOfSpouseContextForFrame:WithSP:.

I ask because I need to recreate that behaviour in case it also appears in
> other places. The general  project is creating a squeak research VM. That
> is also why I try to refrain from changing the image, retaining
> compatibility with Pharo/Squeak trunk.
>
> Thank you, Lars
>
> 2013/05/17 8:08 pm Eliot Miranda <eliot.miranda at gmail.com><eliot.miranda at gmail.com>
> :
>
>
>
> Hi Lars,
>
> On Fri, May 17, 2013 at 9:36 AM, Lars Wassermann <
> lars.wassermann at googlemail.com> wrote:
>
>>
>> Hello VM-dev,
>> I habe problems understanding what exactly happens when I step through:
>> (ContextPart contextOn: Error do: []) halt
>> in a workspace.
>> When I step over the parenthesis in the debugger, everything works kind
>> of like expected (the context of contextOn:do: misses one temporary but the
>> returned value is what the methods description promised). When I step into
>> or through the code, an error is raised when executing bytecode 54 of
>> #contextOn:do:, because the temps-array illegally has been poped (by
>> bytecode 53).
>>
>> My question now is:
>> Why does the normal vm not only break when runing this code, but also
>> return the correct value (the first element of the last temporary).
>> I appended a graphic trying to visualize what happens up to the miracle
>> of three skipped bytecodes.
>>
>
>  Ugh.  I hate this method.   Actually I hate ContextPart>>jump, which is
> the really evil method.
>
>  So let's take a look at the methods:
>
>  ContextPasrt class>>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
>
>  answers a context that is an activation of on:do: ready to catch the
> supplied exception.  It is used by ContextPart>>runUntilErrorOrReturnFrom:
> to insert an exception handler for UnhandledError immediately beneath the
> current context being executed by the debugger.
>
>  The on:do: argument is a little unclear.  It is easier to understand if
> it reads
>
>  ContextPasrt class>>contextOn: exceptionClass do: block
>  "Create an #on:do: context that is ready to return from executing its
> receiver"
>
>  | ctxt onDoContext |
>  ctxt := thisContext.
>  [onDoContext := thisContext sender.
>   onDoContext cut: ctxt.
>   ctxt jump]
>  on: exceptionClass
>  do: block.
>  "jump above will resume here without unwinding chain"
>  ^ onDoContext
>
>  So the idea is to run until in the on:do:'s receiver (the [onDoContext
> := ...] block), grab the sender (the on:do: activation), remove intervening
> contexts (onDoContext cut: ctxt) so that on:do:'s handlerActive doesn't get
> set, resume executing in the contextOn:do: context (ctxt jump) and answer
> the onDoContext.
>
>  The horror is in ContextPart>>jump:
>
>  ContextPart>>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
>
>  This works by returning to self (a MethodContext), but return pushes a
> result.  So the code pops the top thing off the stack and returns it.  This
> is evil.  If the stack is empty then this will pop the receiver, return it
> and push it back in the return (!!).  If the stack contains only temps then
> the last temp is popped and pushed by the return (!!).  This is what you're
> seeing.  The stack's last temp is an indirection vector.  It gets popped
> off the stack by
>  top := self pop.
> at which point any attempt to look at self's temps in a debugger will
> cause an error because the indirection vector is no longer there.  It then
> gets pushed back by=
>  ^ top
>
>  Horrible.  It all depends on being able to create a temporary invalid
> execution state in a context.
>
>  A better way to do this is by process switch, avoiding the return.  For
> example, in my basic block profiler I handle an unknownBytecode error
> caused by executing the unknownBytecode that overwrites the bytecode at the
> beginning of each basic block, replaces the unknownBytecode with the
> correct bytecode, and continues:
>
>  ContextPart>>unusedBytecode
>  "Handle unusedBytecode by replacing the bytecode with the
>   correct one found in the coverage property and continuing.
>   Continue via wait/signal since return would push a result."
>  | coverage semaphore process |
>  self assert: (method at: pc) = method encoderClass unusedBytecode.
>  coverage := method propertyValueAt: #coverage.
>  self assert: coverage notNil.
>  self assert: (coverage includesKey: pc).
>  semaphore := Semaphore new.
>  process := Processor activeProcess.
>
>  [method
>  at: pc
>  put: (coverage removeKey: pc).
>   process suspendedContext unwindTo: self.
>   process suspendedContext: self.
>   semaphore signal] fork.
>
>  semaphore wait
>
>  So that could become something like
>
>  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)."
>  | semaphore process |
>  semaphore := Semaphore new.
>  process := Processor activeProcess.
>
>  [process suspendedContext unwindTo: self.
>   process suspendedContext: self.
>   semaphore signal] fork.
>
>  semaphore wait
>
>  but perhaps the process switch will introduce other problems.  I don't
> know.
>
>  Hope this helps.
>
>
>> I replicated the behavior in Squeak 4.0 and 4.4 images on interpreter VMs
>> 3.7.7, 4.10, and a newer cog VM.
>>
>>
>> A fix to the problem of stepping and comprehension as well as the missing
>> temp in the frame would be changing the method to:
>>
>> 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. ctxt jump] on: exceptionClass do: block.
>>         "jump above will resume here without unwinding chain"
>>         ^ chain
>>
>> The difference is that after cutting off ctxt, we ensure that it is a top
>> context by pushing a meaningless value. But this still doesn't help me
>> understanding the original behaviour.
>>
>> Thank you and all the best
>> Lars
>>
>>
> --
> best,
> Eliot
>
> --
best,
Eliot
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/vm-dev/attachments/20130519/4d3d6559/attachment.htm


More information about the Vm-dev mailing list