[Apologies if some if this is obvious to many of you - kjx]
As some of you may know, I'm trying to write a program visualisation system in Self for a doctoral thesis. This is based upon an mechanism for generating events whenever something interesting happens in a Self program (the events are fed to other Self objects to produce the visualisations - papers available on request :-). Events can be easily generated when messages are received by an object, when methods return normally. Currently, I can't handle non-local method completion, (that is the "^" operator) - these message completions are ignored. This has only been a theoretical problem so far, however, gradually scaling up the size of programs I'm trying to visualise, I seem to have reached a point where I need to be able to handle non-local returns in the event system.
Now several other languages incorporating non-local exits (Smalltalk, Lisp and even VAX Pascal) include an "unwind protect" construct which would trap non-local returns, allowing some action to be taken "on the way up the stack" so to speak. Something like this would allow me to generate the appropriate events and hopefully visualise the programs currently giving me trouble.
So while I try to find ways to avoid the non-local exits in the code in question, this is another plaintive cry asking if anyone has ideas about incorporating unwind-protect into Self. Given that the VM source code is now available, I suppose I could try that - does anyone have any idea how difficult it would be? (in passing - how have people outside the Self group found the VM code to work with). Another alternative would be to move the whole event mechanism inside the VM - but I'm sure that would be even more work.
Another related question, (discovered when poking around these things) is what happens to blocks that perform a non-local return where the method returned to is not in the running process. (Yes, I was attempting a work-around where the protected code would be run in another Self process). However, a non-local return in this case seems to terminate the process without a return value.
Some example code is appended below.
Thanks
James
"what's happening here"
lobby _AddSlots: (| foo = (| parent* = traits oddball.
a = ( 'foo a called' printLine.
'foo a stores magic block' printLine. blk: [ 'blk called' printLine. ^'magic-return'.].
'foo a stores process number' printLine. prc: process this.
'foo a suspends' printLine. process this suspend.
'foo a invokes block' printLine. blk value.
'foo a returns' printLine.).
blk. "holds block" prc. "holds process" |) |)
"Now, run these commands from the prompt"
" foo a
[foo blk value. ^'where does this go?'] value
foo prc resume "
R James Noble writes:
So while I try to find ways to avoid the non-local exits in the code in question, this is another plaintive cry asking if anyone has ideas about incorporating unwind-protect into Self. Given that the VM source code is now available, I suppose I could try that - does anyone have any idea how difficult it would be?
We've been planning to put unwinf protect into the system for a while, but I guess so far it just hasn't gotten to the top of our wish list yet. There's lots of code in the system that is vulnerable to accidental NLRs. For example, the following code fragment doesn't release the semaphore if someCondition holds:
aSemaphore protect: [ someCondition ifTrue: [^ something]]
A possible solution is to introduce two new primitives:
- aBlock _UnwindProtect: protectBlock exececutes the block (i.e. sends 'value' to it) and invokes the protectBlock if aBlock does a non-local return. A more general primitive could take arguments for aBlock, but that would be a little harder to implement. - _ContinueUnwind continues the last non-local return caught by _UnwindProtect. Presumably, the last statement in the protectBlock would call _ContinueUnwind (if continuation is wanted).
While not completely general, this primitive pair would probably cover most cases and would be relatively simple to implement as long as super-high performance is not the main goal. Would these primitives be ok for you apllication?
Another related question, (discovered when poking around these things) is what happens to blocks that perform a non-local return where the method returned to is not in the running process. (Yes, I was attempting a work-around where the protected code would be run in another Self process). However, a non-local return in this case seems to terminate the process without a return value.
Gee, I though nobody would ever discover this :-) You're right, NLRs do not work across processes, and it's not quite clear what the correct semantics would be. The most consistent semantics would be to abort the process invoking the block (as today) because the NLR completely unwinds the process' stack, and then (unlike today) continue the NLR in the block's "home process". However, this has some weird side effects. For example, the block's home process is suspended when the NLR occurs, and it would appear to return non-locally from the _Yield primitive. To handle this, the scheduler would have to use unwind protection so it could clean up its queues before resuming the process.
To make it short, the current implementation does not handle non-local returns across process boundaries, but I would guess that the "better" semantics outlined above wouldn't help you.
-Urs
self-interest@lists.selflanguage.org