Callbacks, simplified.

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
12 messages Options
Reply | Threaded
Open this post in threaded view
|

Callbacks, simplified.

Andreas.Raab
Folks -

Another one of the issues I really need a solution for is simple
callback support into the interpreter. For me, the simplest thing to do
that can potentially support all the relevant use cases is to merely
provide an interface that (via setjmp/longjmp) ensures that the C
callstack remains in order, e.g., prevents returns "through" other
callback frames.

I'm thinking about an interface like here:

int interpretEnter(int *cbID);
     - (Re-)Enters the interpreter. Different from interpret() in such
that it creates an environment (via setjmp) to return to. Fills in the
cbID with an ID that is used in the return call.

int interpretExit(int cbID);
     - Returns from a previous call to interpretEnter() with the
provided callback ID.

Both of the above functions may fail - interpretEnter() would fail if
for some reason the interpreter cannot be entered (no setjmp/longjmp
support, or failure to allocate jmpbuf). interpretExit() would fail if
the callback ID is not the last "matching" ID.

Note that the above does NOT make any assumptions on the means by which
the VM gets notified about the occurrence of the callback itself nor any
arguments etc. It is up to the plugin to provide that information by its
idiosyncratic means.

Question: Does anyone have a problem with the above? I'm trying to
design "just enough" so that projects like wxSqueak or others can
cooperate without knowing too much about each other.

Cheers,
   - Andreas

Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

timrowledge
Last time I used setjmp/longjmp was probably 1990. Back then it was  
considered a seriously tacky thing to do and a cause of lost  
performance.  I think BrouHaHa got non-trivially faster after I beat  
Eliot up to get him to stop using it in the interpreter code.

Is it less of an issue these days?

tim
--
tim Rowledge; [hidden email]; http://www.rowledge.org/tim
Strange OpCodes: TOAC: Turn Off Air Conditioner


Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

Andreas.Raab
Hi Tim -

setjmp/longjmp are probably slow as hell but the real issue is whether
we want to keep changes minimalistic and local (I do) or whether we're
up for some serious rework of other parts (I'm not).

An alternative I have pondered was to give interpret() an argument,
namely a pointer to a variable that external code can set when it's time
to return. This would solve the problem without doing setjmp/longjmp,
BUT it would come at the cost of a pointer-dereferencing and test at
every single bytecode (at the very least at each primitiveResponse but
there are gotchas with that) and would potentially defeat the dispatch
optimization in gnuification.

Knowing you guys (and knowing myself too) those tradeoffs would be
completely unacceptable for the VM in general - if you want to use
callbacks and you're willing to pay a price for that then you really
should pay the price when you use the callback and not elsewhere. That's
why I'm opting for a setjmp/longjmp solution.

Besides, given the simplistic interface if another implementation
becomes available it should be possible to implement an interface as
trivial as the one proposed here. Since the interface doesn't expose
anything about its implementation (e.g., all you get is an opaque
callback ID) it's quite literally up to the VM how it wants to implement
the semantics properly. It could even do this via threading and IPC if
that were of interest.

Cheers,
   - Andreas

tim Rowledge wrote:

> Last time I used setjmp/longjmp was probably 1990. Back then it was
> considered a seriously tacky thing to do and a cause of lost
> performance.  I think BrouHaHa got non-trivially faster after I beat
> Eliot up to get him to stop using it in the interpreter code.
>
> Is it less of an issue these days?
>
> tim
> --
> tim Rowledge; [hidden email]; http://www.rowledge.org/tim
> Strange OpCodes: TOAC: Turn Off Air Conditioner
>
>
>
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

Dave Mason-4
> An alternative I have pondered was to give interpret() an argument,
> namely a pointer to a variable that external code can set when it's
> time to return. This would solve the problem without doing
> setjmp/longjmp, BUT it would come at the cost of a
> pointer-dereferencing and test at every single bytecode (at the very
> least at each primitiveResponse but there are gotchas with that) and
> would potentially defeat the dispatch optimization in gnuification.

I don't know the vm internals at all, but a standard place to put such
tests in other contexts is on backward-jumps and method-returns (maybe
that's what primitiveResponse is).  The cost of that wouldn't be too
significant.

../Dave
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

timrowledge

On 6-Jun-06, at 12:59 PM, Dave Mason wrote:

>> An alternative I have pondered was to give interpret() an argument,
>> namely a pointer to a variable that external code can set when it's
>> time to return. This would solve the problem without doing
>> setjmp/longjmp, BUT it would come at the cost of a
>> pointer-dereferencing and test at every single bytecode (at the very
>> least at each primitiveResponse but there are gotchas with that) and
>> would potentially defeat the dispatch optimization in gnuification.
>
> I don't know the vm internals at all, but a standard place to put such
> tests in other contexts is on backward-jumps and method-returns (maybe
> that's what primitiveResponse is).  The cost of that wouldn't be too
> significant.

That's where the interruptCheck stuff is done Dave; and if it is  
plausible to hang off that (?) then all that would need doing is to  
call forceInterruptCheck() from the plugin to get a fuller check  
ASAP. IF (big if in glowing capitals) handling callbacks could be  
dealt with within the typical interrupt checking work it would be a  
bit less onerous than at arbitrary points.

How are you proposing to deal with the callback at the higher levels?  
Is the plugin going to be allowed to cons up a message and 'send' it?  
Or.....?

tim
--
tim Rowledge; [hidden email]; http://www.rowledge.org/tim
Useful random insult:- Settled some during shipping and handling.


Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

johnmci
The older (way older) macintosh os-8.x based netscape browser plugin  
would run the interpreter loop for N instructions (milliseconds) I  
think, then exit the loop, thus halting the envrionment and return  
control to the browser, on the next call from the browser we would  
reenter the interpreter loop and process the next bytecode as if  
nothing happened...
I believe stubs for that logic still exist.

On 6-Jun-06, at 1:10 PM, tim Rowledge wrote:

>
> On 6-Jun-06, at 12:59 PM, Dave Mason wrote:
>
>>> An alternative I have pondered was to give interpret() an argument,
>>> namely a pointer to a variable that external code can set when it's
>>> time to return. This would solve the problem without doing
>>> setjmp/longjmp, BUT it would come at the cost of a
>>> pointer-dereferencing and test at every single bytecode (at the very
>>> least at each primitiveResponse but there are gotchas with that) and
>>> would potentially defeat the dispatch optimization in gnuification.
>>
>> I don't know the vm internals at all, but a standard place to put  
>> such
>> tests in other contexts is on backward-jumps and method-returns  
>> (maybe
>> that's what primitiveResponse is).  The cost of that wouldn't be too
>> significant.
>
> That's where the interruptCheck stuff is done Dave; and if it is  
> plausible to hang off that (?) then all that would need doing is to  
> call forceInterruptCheck() from the plugin to get a fuller check  
> ASAP. IF (big if in glowing capitals) handling callbacks could be  
> dealt with within the typical interrupt checking work it would be a  
> bit less onerous than at arbitrary points.
>
> How are you proposing to deal with the callback at the higher  
> levels? Is the plugin going to be allowed to cons up a message and  
> 'send' it? Or.....?
>
> tim
> --
> tim Rowledge; [hidden email]; http://www.rowledge.org/tim
> Useful random insult:- Settled some during shipping and handling.
>
>

--
========================================================================
===
John M. McIntosh <[hidden email]> 1-800-477-2659
Corporate Smalltalk Consulting Ltd.  http://www.smalltalkconsulting.com
========================================================================
===

Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

Andreas.Raab
In reply to this post by timrowledge
tim Rowledge wrote:
> How are you proposing to deal with the callback at the higher levels? Is
> the plugin going to be allowed to cons up a message and 'send' it? Or.....?

This should be up to the plugin. I can't think of a good interface right
now given that we don't have enough examples to look at and learn from.
Personally, I'm just signaling a semaphore before the callback and have
the image "pull up" the arguments via primitives. This avoids the need
to do too much at the primitive level.

Cheers,
   - Andreas
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

timrowledge

On 6-Jun-06, at 5:00 PM, Andreas Raab wrote:

> tim Rowledge wrote:
>> How are you proposing to deal with the callback at the higher  
>> levels? Is the plugin going to be allowed to cons up a message and  
>> 'send' it? Or.....?
>
> This should be up to the plugin. I can't think of a good interface  
> right now given that we don't have enough examples to look at and  
> learn from. Personally, I'm just signaling a semaphore before the  
> callback and have the image "pull up" the arguments via primitives.  
> This avoids the need to do too much at the primitive level.

So would simply using the normal semaphore signalling be ok? We  
already have external semaphore capability so it would certainly be  
simple to provide! If we implemented callbacks by taking a block (or  
a MessageSend?), making a Process that will use it, make the process  
wait on a Semaphore and pass the semaphore index  down to the plugin,  
then signalling the semaphore would run the block/whatever and  
presumably ask the plugin for relevant arguments. As long as it can  
be non-synchronous that should work for a lot of cases, surely?


tim
--
tim Rowledge; [hidden email]; http://www.rowledge.org/tim
Klingon Code Warrior:- 9) "A TRUE Klingon warrior does not comment  
his code!"


Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

Rob Gayvert
tim Rowledge wrote:

>
> On 6-Jun-06, at 5:00 PM, Andreas Raab wrote:
>
>> tim Rowledge wrote:
>>
>>> How are you proposing to deal with the callback at the higher  
>>> levels? Is the plugin going to be allowed to cons up a message and  
>>> 'send' it? Or.....?
>>
>>
>> This should be up to the plugin. I can't think of a good interface  
>> right now given that we don't have enough examples to look at and  
>> learn from. Personally, I'm just signaling a semaphore before the  
>> callback and have the image "pull up" the arguments via primitives.  
>> This avoids the need to do too much at the primitive level.
>
>
> So would simply using the normal semaphore signalling be ok? We  already
> have external semaphore capability so it would certainly be  simple to
> provide! If we implemented callbacks by taking a block (or  a
> MessageSend?), making a Process that will use it, make the process  wait
> on a Semaphore and pass the semaphore index  down to the plugin,  then
> signalling the semaphore would run the block/whatever and  presumably
> ask the plugin for relevant arguments. As long as it can  be
> non-synchronous that should work for a lot of cases, surely?
>

Hi guys,

This is basically what I'm currently doing in wxSqueak. I have two
different flavors of callbacks, one for event loops and another for
other callbacks. The only real difference between these two is that the
non-event callbacks have to handle a variety of arguments. In both cases
the callbacks must appear to be handled synchronously from the viewpoint
of the (wx) calling routine. The only serious difficulty I've
encountered with this scheme is with nested callbacks. I have a number
of cases where a callback handler triggers another callback. The problem
is that when the second callback is handled, the process in which the
first primitive was called may resume prematurely, causing the first
primitive to get an incorrect return value.

The only way I found to reliably handle this scenario is to have the
first primitive pass in another semaphore and a pointer in which to
store the return value, and have the primitive signal the semphore when
it's done. It's a bit ugly, but it does work. It would be much better if
the processes involved could be controlled by the framework.

Do you have any thoughts on how this could be handled better?

.. Rob





Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

Andreas.Raab
 > Do you have any thoughts on how this could be handled better?

How about we just suspend the currently active process when we re-enter
the interpreter? As long as the callback is synchronous (it better be)
this should work just fine, right?

Cheers,
   - Andreas

Rob Gayvert wrote:

> tim Rowledge wrote:
>>
>> On 6-Jun-06, at 5:00 PM, Andreas Raab wrote:
>>
>>> tim Rowledge wrote:
>>>
>>>> How are you proposing to deal with the callback at the higher  
>>>> levels? Is the plugin going to be allowed to cons up a message and  
>>>> 'send' it? Or.....?
>>>
>>>
>>> This should be up to the plugin. I can't think of a good interface  
>>> right now given that we don't have enough examples to look at and  
>>> learn from. Personally, I'm just signaling a semaphore before the  
>>> callback and have the image "pull up" the arguments via primitives.  
>>> This avoids the need to do too much at the primitive level.
>>
>>
>> So would simply using the normal semaphore signalling be ok? We  
>> already have external semaphore capability so it would certainly be  
>> simple to provide! If we implemented callbacks by taking a block (or  
>> a MessageSend?), making a Process that will use it, make the process  
>> wait on a Semaphore and pass the semaphore index  down to the plugin,  
>> then signalling the semaphore would run the block/whatever and  
>> presumably ask the plugin for relevant arguments. As long as it can  
>> be non-synchronous that should work for a lot of cases, surely?
>>
>
> Hi guys,
>
> This is basically what I'm currently doing in wxSqueak. I have two
> different flavors of callbacks, one for event loops and another for
> other callbacks. The only real difference between these two is that the
> non-event callbacks have to handle a variety of arguments. In both cases
> the callbacks must appear to be handled synchronously from the viewpoint
> of the (wx) calling routine. The only serious difficulty I've
> encountered with this scheme is with nested callbacks. I have a number
> of cases where a callback handler triggers another callback. The problem
> is that when the second callback is handled, the process in which the
> first primitive was called may resume prematurely, causing the first
> primitive to get an incorrect return value.
>
> The only way I found to reliably handle this scenario is to have the
> first primitive pass in another semaphore and a pointer in which to
> store the return value, and have the primitive signal the semphore when
> it's done. It's a bit ugly, but it does work. It would be much better if
> the processes involved could be controlled by the framework.
>
> Do you have any thoughts on how this could be handled better?
>
> .. Rob
>
>
>
>
>
>
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

Rob Gayvert
Andreas Raab wrote:
>  > Do you have any thoughts on how this could be handled better?
>
> How about we just suspend the currently active process when we re-enter
> the interpreter? As long as the callback is synchronous (it better be)
> this should work just fine, right?

Yes, that should do the trick. I tried doing something like this awhile
back, but couldn't get it to work reliably. I suspect the problem was
that the process oop might move during a callback, in which case trying
to resume it would cause a crash. It sounds like your other changes to
keep oops across primitives would solve this.

.. Rob


>
> Cheers,
>   - Andreas
>
> Rob Gayvert wrote:
>
>> tim Rowledge wrote:
>>
>>>
>>> On 6-Jun-06, at 5:00 PM, Andreas Raab wrote:
>>>
>>>> tim Rowledge wrote:
>>>>
>>>>> How are you proposing to deal with the callback at the higher  
>>>>> levels? Is the plugin going to be allowed to cons up a message and  
>>>>> 'send' it? Or.....?
>>>>
>>>>
>>>>
>>>> This should be up to the plugin. I can't think of a good interface  
>>>> right now given that we don't have enough examples to look at and  
>>>> learn from. Personally, I'm just signaling a semaphore before the  
>>>> callback and have the image "pull up" the arguments via primitives.  
>>>> This avoids the need to do too much at the primitive level.
>>>
>>>
>>>
>>> So would simply using the normal semaphore signalling be ok? We  
>>> already have external semaphore capability so it would certainly be  
>>> simple to provide! If we implemented callbacks by taking a block (or  
>>> a MessageSend?), making a Process that will use it, make the process  
>>> wait on a Semaphore and pass the semaphore index  down to the
>>> plugin,  then signalling the semaphore would run the block/whatever
>>> and  presumably ask the plugin for relevant arguments. As long as it
>>> can  be non-synchronous that should work for a lot of cases, surely?
>>>
>>
>> Hi guys,
>>
>> This is basically what I'm currently doing in wxSqueak. I have two
>> different flavors of callbacks, one for event loops and another for
>> other callbacks. The only real difference between these two is that
>> the non-event callbacks have to handle a variety of arguments. In both
>> cases the callbacks must appear to be handled synchronously from the
>> viewpoint of the (wx) calling routine. The only serious difficulty
>> I've encountered with this scheme is with nested callbacks. I have a
>> number of cases where a callback handler triggers another callback.
>> The problem is that when the second callback is handled, the process
>> in which the first primitive was called may resume prematurely,
>> causing the first primitive to get an incorrect return value.
>>
>> The only way I found to reliably handle this scenario is to have the
>> first primitive pass in another semaphore and a pointer in which to
>> store the return value, and have the primitive signal the semphore
>> when it's done. It's a bit ugly, but it does work. It would be much
>> better if the processes involved could be controlled by the framework.
>>
>> Do you have any thoughts on how this could be handled better?
>>
>> .. Rob
>>
>>
>>
>>
>>
>>
>

Reply | Threaded
Open this post in threaded view
|

Re: Callbacks, simplified.

Andreas.Raab
Hi Rob -

Rob Gayvert wrote:

> Andreas Raab wrote:
>>  > Do you have any thoughts on how this could be handled better?
>>
>> How about we just suspend the currently active process when we
>> re-enter the interpreter? As long as the callback is synchronous (it
>> better be) this should work just fine, right?
>
> Yes, that should do the trick. I tried doing something like this awhile
> back, but couldn't get it to work reliably. I suspect the problem was
> that the process oop might move during a callback, in which case trying
> to resume it would cause a crash. It sounds like your other changes to
> keep oops across primitives would solve this.

Yes, although (if you look at the changes) I decided to keep this
internal in the VM because it is also necessary to ensure proper
resumption upon callback return (otherwise the return value will be
inaccurate). So the ultimate solution goes like this:

Upon entering the callback, suspend the active process forcefully. Upon
returning from the callback resume the active process forcefully(!) so
that the "returning primitive" (e.g., the primitive which invoked the
callback) can set the proper return value.

As far as I am aware that solves the trick completely (check out the
code) at the cost of requiring that callbacks are only entered during
the execution of primitives (meaning that the interpreter's state is
externalized).

Cheers,
   - Andreas