The 'invoke the current return continuation' op apparently got lost
in the blowup. That needs to go in. "return" is a simple, if
unimaginative name, and that works for me.
The wording of PDD 03 implies that some of the registers can be
populated by the op making the invocation, rather than explicitly
setting them. In this case specifically the method/sub PMC, the
object, and the return continuation. Leo brought this up but it got
lost in the firestorm. We've already got "invoke Px" and "invokecc
Px" along with the plain invoke and invokecc, but method calls aren't
as well-rounded, and tail-call versions would be useful as well.
With that out of the way, we should address exceptions.
There's some support in now, but it's primitive, and needs updating.
Exceptions are fairly straightforward. Setting an exception handler
creates a exception continuation and pushes it onto the control
stack. If an exception is thrown the control stack is walked looking
for an exception handler and, when one is found, its continuation is
invoked, at which point we transfer control to the exception handler.
The following parameters are passed into the handler (and this I'm up
for discussion on):
*) Language throwing exception
*) The subsystem throwing the exception
*) The exception severity
*) The exception code
*) An object which the exception handler can do whatever it wants with
Exceptions are not, by default, resumable. That's problematic with
some classes of exception, so we aren't doing that. (Not, mind, that
you can't throw a continuation to resume to as part of your object
you throw at the exception handler)
I'd like pushing exception handlers to remain simple -- the current
system is almost OK. What I'd like it to change to is:
push_eh label
with popping the top exception handler being:
pop_eh
I'm up for better names, too. When pushing an exception handler we
generate a return continuation with the resume address being the
label rather than the next op.
This is more restrictive than passing in a generic invokable object
as the exception handler, but given that all the exception handling
schemes in all our languages of interest do lexically scoped
exceptions I think we're better off with the restriction. Allowing
random invokables as exception handlers feels like it's likely to
have some subtle security or consistency problems associated with it,
though I can't give a good example at the moment. (Plus it can be
overridden by a language that actually cares by having the exception
handler invoke a random continuation) Still, forcing all exception
objects to be created by parrot gives us some guarantees of control,
so that's fine.
Throwing exceptions should be:
throw language, subsystem, severity, code, object
And I'm definitely unsure what types language and subsystem should
be. (I can see either ints or strings here) severity and code should
both be ints, and object is a PMC.
Rethrowing the current exception status should be done with the
rethrow op, which takes no parameters.
As part of this I want to reserve one language identifier for
parrot-internal exceptions which can *not* be thrown by bytecode. The
only way for these to propagate is if C code throws them or they're
rethrown by an exception handler which can't handle them. This'll be
used for exceptions we don't want to be fakeable. (I'm not sure we'll
need any, but if we do I want the option) If bytecode does throw them
it becomes a security exception instead, and we may well up and kill
the interpreter.
With pushing and popping exception handlers, we're going to want to
pop off several exception handlers at once, in some cases an unknown
number of handlers. As such I want to reintroduce something we talked
about a very long time ago: stack marks. This allows us to do:
pushmark 12
.
. random code here
.
popmark 12
where everything put on the control stack after the "pushmark
identifier" is popped off by the corresponding "popmark identifier".
Marks can nest, and it's the code's responsibility to not reuse
labels. popmark will *not* cross routine boundaries, so the control
stack will never be cleared out past the point where it was when the
sub/method/whatever was entered. (Note that exception handlers don't
count as subs here) That can be an exception in itself if we want,
and probably ought to do.
Now, with this, there's one other thing we need to talk about, and
that's scope exit actions. We promised this a number of years back
(and now I feel very old) and need to deliver on them. A scope exit
action is put in place on the control stack with:
pushaction Psub
and when the action is popped off the stack because of a popmark,
walking the stack back looking for exception handlers, a popaction
op, or returning from a subroutine the sub is invoked with a *single*
integer parameter that indicates whether the action is being called
as part of a normal or exception-based stack unwind. (a status of 0
means the action was caused by normal code, a 1 because the action
was popped off as parrot walked backwards looking for an exception
handler)
If folks want to argue that the action should get the exception
information if called as part of an exception unwind I'm OK with
that. Go for it, I'm unsure of that one.
Most of the stack unwinding stuff's straightforward -- it's called
because code actually *did* something to the stack, or pitched a fit.
The one place we need to do some stuff with is on subroutine exit.
With CPS we don't truly have subroutine exits, since we just invoke
continuations and bop around. There's no 'exit' as such. And, of
course, nobody actually *cares* that we do this -- our convenience
isn't really that interesting. That means we need to simulate exiting
under normal circumstances. So, parrot will automatically clear out
the control stack and invoke any actions on it if:
*) the current return continuation is invoked with the return op
*) a tail call is made with either the tailcall or tailmethodcall ops
Note that the unwinding is *not* inherent in the invocation of the
return continuation! That means that if someone fetches the return
continuation out of the interpreter structure and then explicitly
invokes it the control stack isn't walked for action handlers. (Which
would give a quick escape if code wanted to skip them for some
reason, though we'd probably be better off with variant
return/tailcall ops instead since it'd be cheaper than fully
instantiating the return continuation)
It's also important for people writing these things to take into
account the possibility that their exit actions may potentially be
triggered multiple times, courtesy of the joys of continuations.
So. Simple, right? Make sense to everyone? (You may all commence
ripping this to shreds...)
--
Dan
--------------------------------------it's like this-------------------
Dan Sugalski even samurai
d...@sidhe.org have teddy bears and even
teddy bears get drunk
> I'd like pushing exception handlers to remain simple -- the current
> system is almost OK. What I'd like it to change to is:
>
> push_eh label
>
> with popping the top exception handler being:
>
> pop_eh
>
> I'm up for better names, too.
The "push_" is okay but "eh" is meh. push_handler seems better, though
"handler" is terribly generic. If the documentation and comments use it
consistently only for exceptions, though, it could work.
-- c
Throw....catch, so
push_catcher ?
I guess the HLL compiler needs to ensure that for every push the
control flow will always pass through a matching pop.
Otherwise that'll be another hard to debug situation.
Couldn't (doesn't) parrot have a stronger concept of a 'scope' (as
in perl5's scope.c) so that scope-exit cleanup can be automated and
reliable?
Tim [ignore me if I'm talking nonsense]
> It's also important for people writing these things to take into
> account the possibility that their exit actions may potentially be
> triggered multiple times, courtesy of the joys of continuations.
Hmm, the first thing to take into the account is that return
continuations can be promoted to the fully blown continuations. This
should affect the handlers in the same way - so exception handlers could
have become arbitrary invokable objects at the point when the exception
is thrown.
Miro
> I guess the HLL compiler needs to ensure that for every push the
> control flow will always pass through a matching pop.
Not necessarily. The handler is pushed onto the control stack. During a
context change (e.g. from a subroutine return), the previous context
with the according control stack top is copied into the interpreter.
So effectively the handler is discarded.
OTOH if several handlers are involved the emitted code ought to take
care of it, yes.
> Couldn't (doesn't) parrot have a stronger concept of a 'scope' (as
> in perl5's scope.c) so that scope-exit cleanup can be automated and
> reliable?
AFAIK a scope is either a lexical closure or optimized (maybe) to a
region of code surrounded by C<new_pad> / C<pop_pad> opcodes. Doing the
rught thing on scope exit is up to the emitted code.
> Tim [ignore me if I'm talking nonsense]
leo
> The 'invoke the current return continuation' op apparently got lost
> in the blowup. That needs to go in.
Its in and named C<returncc> since yesterday "return with current
continuation".
> I'd like pushing exception handlers to remain simple -- the current
> system is almost OK. What I'd like it to change to is:
> push_eh label
Shouldn't that be:
push_eh handler, label
> This is more restrictive than passing in a generic invokable object
> as the exception handler, but given that all the exception handling
> schemes in all our languages of interest do lexically scoped
> exceptions I think we're better off with the restriction.
Good.
> ...Allowing
> random invokables as exception handlers feels like it's likely to
> have some subtle security or consistency problems associated with it,
> though I can't give a good example at the moment.
Yep, it smells like that.
> Throwing exceptions should be:
> throw language, subsystem, severity, code, object
Allowing just one additional object doesn't properly support Python,
which has two optional expressions for the C<raise> statement and Python
attaches a traceback object to the exception.
OTOH (again from Python's view) raising just a C<StopIteration> or a
C<KeyErrror> ought to be fast as its used heavily.
And Python has an hierarchy of exception *objects*, with inheritance. So
I think that for maximum flexibility we should just have exception
objects, with some mandatory attributes (the exception class might
already provide enough information) and additional information the HLL
might stuff in.
How will Perl6 do it?
> And I'm definitely unsure what types language and subsystem should
> be.
C<language> - I presume - is defining the HLL that emitted the code,
where the exception occured. I think this information has to be in the
interpreter context as we should eventually be able to execute mixed
languages code. So C<language> should probably end up in the traceback
object, passed along with the exception.
BTW - how do we set C<language>?
> pushmark 12
> .
> . random code here
> .
> popmark 12
> where everything put on the control stack after the "pushmark
> identifier" is popped off by the corresponding "popmark identifier".
> Marks can nest, and it's the code's responsibility to not reuse
> labels. popmark will *not* cross routine boundaries, so the control
> stack will never be cleared out past the point where it was when the
> sub/method/whatever was entered.
This is intended for cleanup of a bunch of pushed handlers inside one
routine?
> Now, with this, there's one other thing we need to talk about, and
> that's scope exit actions. We promised this a number of years back
> (and now I feel very old) and need to deliver on them. A scope exit
> action is put in place on the control stack with:
> pushaction Psub
> and when the action is popped off the stack because of a popmark,
> walking the stack back looking for exception handlers, a popaction
> op, or returning from a subroutine the sub is invoked with a *single*
> integer parameter that indicates whether the action is being called
> as part of a normal or exception-based stack unwind. (a status of 0
> means the action was caused by normal code, a 1 because the action
> was popped off as parrot walked backwards looking for an exception
> handler)
> If folks want to argue that the action should get the exception
> information if called as part of an exception unwind I'm OK with
> that. Go for it, I'm unsure of that one.
Python has "try ... finally". It preserves (possibly) existing
exceptions around the finally block. But if in the "finally" clause
another exception occurs (or a "return" of "break" is executed), the
saved exception is lost.
This could mean that the exception object is needed, e.g. to nullify it
in that case.
> .... So, parrot will automatically clear out
> the control stack and invoke any actions on it if:
> *) the current return continuation is invoked with the return op
> *) a tail call is made with either the tailcall or tailmethodcall ops
How does the latter look like for recursive tail calls?
> So. Simple, right? Make sense to everyone?
Yep. A lot.
A final question: classifying the exception in the handler is still up
to the HLL, I presume?
And one remark WRT internally thrown exceptions: in case of a
MemoryError exception (Python speak), we are not allowed to use any more
resources, so we probably need to preconstruct a few needed items for
such a case.
leo
> Hmm, the first thing to take into the account is that return
> continuations can be promoted to the fully blown continuations.
Yes. But an exception handler is not a RetContinuation object. It's an
Exception_Handler object (also derived from Continuatio), behaving more
like a RetContinuation then a full one.
Creating a full continuation involves the C<clone> vtable of that
object, which can do just the right thing. With some few missing patches
there isn't any visible P1 or such object around. So the described
behavior will work.
> Miro
leo
I'd rather not, if we can avoid it. Assumptions on control flow and
such are likely to be very different, and I can't think of a good
reason to treat an error handler as a normal sub. (If you have one
then we can think on it some)
Hopefully not. This is partially handled by the push/popmark stuff,
partially handled by stack unwinding on 'returns', and partially
handled by the sub-private nature of stacks.
Within a sub (or method, whatever) generated code can use the
pushmark/popmark ops to manage control stack elements. I expect
this'll be done mostly for entering and leaving scope -- code like:
foo: {
bar: {
}
}
outsidefoo:
would push a mark for foo when that block is entered, then push one
for bar when *that* block is entered. When bar's left the exit code
does a popmark bar, while exiting the foo block would exit a popmark
foo. If code in bar did a goto outsidefoo, the compiler would see
that it was exiting both the bar and foo blocks and just do a popmark
foo (since that'd pop all bar's entries too)
Each sub in parrot basically has its own private set of stacks, and
doesn't directly connect to its invoker's stacks. (Those are only
available from the return continuation tucked away in the interpreter
structure, so parrot can get to it easily enough) If code invokes a
continuation then the current stacks all go away (more or less --
unless there's a continuation holding on to them, but even then
they're out of view) and the stacks in the invoked continuation get
put into play, so any exception handlers that were in effect no
longer are. Or, rather, the ones that were in scope when the
continuation was taken are put back in scope as part of switching
over to the control stack that's held in the continuation.
Finally, when a 'return' op is invoked (basically one that puts the
return continuation back in play either through a real return or a
tail call) the op itself walks back the current control chain (which,
remember, is just for the current sub so it should be short) and
virtually pops all the elements off it, which means any element that
does something when popped will get a chance to do its thing.
Now it's distinctly possible there are more things that should go on
the control stack besides exception handlers, marks, and "run my code
when I'm popped" entries, so we can add those in as we need.
>Tim [ignore me if I'm talking nonsense]
Nope, not nonsense. Things are just a bit fuzzy, so some
explanation's in order. :)
>> Hmm, the first thing to take into the account is that return
>> continuations can be promoted to the fully blown continuations. This
>> should affect the handlers in the same way - so exception handlers
>> could have become arbitrary invokable objects at the point when the
>> exception is thrown.
>
>
> I'd rather not, if we can avoid it. Assumptions on control flow and
> such are likely to be very different, and I can't think of a good
> reason to treat an error handler as a normal sub. (If you have one
> then we can think on it some)
While that's not what I commented on, sure. Common LISP and TOM both
invoke error handler -before- unwinding the stack (then the handler
explicitely unwinds if it can't recover). I still don't think it's
something Parrot should care about, as their (hypothetical) compilers
can install their own error handling, and other languages don't expect
their own throw to ever return - so I wouldn't call this a good reason. :)
Miro
> Exceptions are not, by default, resumable.
Are there non-default resumable exceptions?
leo
Hrm. The name's not right, since there's no current continuation
involved. (At least we shouldn't be passing in the current
continuation on return -- instead the return continuation that was in
effect when the code we're returning to was active should be the
return continuation)
> > I'd like pushing exception handlers to remain simple -- the current
>> system is almost OK. What I'd like it to change to is:
>
>> push_eh label
>
>Shouldn't that be:
>
> push_eh handler, label
Nope. The only thing that push_eh (or whatever we name it) needs is
the address of the code to jump to if an exception's thrown. The
exception pushing code should take care of building whatever
structure's needed to call into it. (Though other than making a
return continuation and changing the address to the address used in
the push_eh instruction I'm not sure we need to do anything else)
>
>> Throwing exceptions should be:
>
>> throw language, subsystem, severity, code, object
>
>Allowing just one additional object doesn't properly support Python,
>which has two optional expressions for the C<raise> statement and Python
>attaches a traceback object to the exception.
Hrm. Well, the traceback object could just be the interpreter
structure at the time of the the exception. That'd be cheap enough to
pass in. I'm fine with adding that as a second PMC parameter.
>OTOH (again from Python's view) raising just a C<StopIteration> or a
>C<KeyErrror> ought to be fast as its used heavily.
Yeah. That's partly the reason for the non-PMC parameters. The other
reason for them is that we may be throwing an exception at a point
where it's not possible to actually create a PMC. We can always stuff
in constant strings and integers, though.
>And Python has an hierarchy of exception *objects*, with inheritance. So
>I think that for maximum flexibility we should just have exception
>objects, with some mandatory attributes (the exception class might
>already provide enough information) and additional information the HLL
>might stuff in.
Python code can always pass in an exception object in the PMC
parameter slot -- that's fine. Perl 5 throws plain strings or
arbitrary PMCs as exceptional things, and the internals may throw
exceptions with non-pmc parameters if for some reason a PMC couldn't
be created.
> > And I'm definitely unsure what types language and subsystem should
>> be.
>
>C<language> - I presume - is defining the HLL that emitted the code,
>where the exception occured.
Yep.
>I think this information has to be in the
>interpreter context as we should eventually be able to execute mixed
>languages code. So C<language> should probably end up in the traceback
>object, passed along with the exception.
Possibly, but C code may well be throwing exceptions too, but still
want to attach a language to it.
>BTW - how do we set C<language>?
In general, I'd figured it was an attribute placed at compile-time on
a sub PMC.
> > pushmark 12
>> .
>> . random code here
>> .
>> popmark 12
>
>> where everything put on the control stack after the "pushmark
>> identifier" is popped off by the corresponding "popmark identifier".
>> Marks can nest, and it's the code's responsibility to not reuse
>> labels. popmark will *not* cross routine boundaries, so the control
>> stack will never be cleared out past the point where it was when the
>> sub/method/whatever was entered.
>
>This is intended for cleanup of a bunch of pushed handlers inside one
>routine?
Yep.
> > Now, with this, there's one other thing we need to talk about, and
>> that's scope exit actions. We promised this a number of years back
>> (and now I feel very old) and need to deliver on them. A scope exit
>> action is put in place on the control stack with:
>
>> pushaction Psub
>
>> and when the action is popped off the stack because of a popmark,
>> walking the stack back looking for exception handlers, a popaction
>> op, or returning from a subroutine the sub is invoked with a *single*
>> integer parameter that indicates whether the action is being called
>> as part of a normal or exception-based stack unwind. (a status of 0
>> means the action was caused by normal code, a 1 because the action
>> was popped off as parrot walked backwards looking for an exception
>> handler)
>
>> If folks want to argue that the action should get the exception
>> information if called as part of an exception unwind I'm OK with
>> that. Go for it, I'm unsure of that one.
>
>Python has "try ... finally". It preserves (possibly) existing
>exceptions around the finally block. But if in the "finally" clause
>another exception occurs (or a "return" of "break" is executed), the
>saved exception is lost.
>This could mean that the exception object is needed, e.g. to nullify it
>in that case.
I think that the python compiler will be able to manage things
sufficiently to make this work out OK without us doing anything else.
(Well, other than passing in the interpreter struct for traceback)
> > .... So, parrot will automatically clear out
>> the control stack and invoke any actions on it if:
>
>> *) the current return continuation is invoked with the return op
>> *) a tail call is made with either the tailcall or tailmethodcall ops
>
>How does the latter look like for recursive tail calls?
The same as non-recursive calls -- we clear out the stack and use the
return continuation we were passed in as the return continuation for
the sub we're calling. That it's the current sub's just a detail. :)
> > So. Simple, right? Make sense to everyone?
>
>Yep. A lot.
>
>A final question: classifying the exception in the handler is still up
>to the HLL, I presume?
Yeah. We provide the information, but past that it's other people's problem.
>And one remark WRT internally thrown exceptions: in case of a
>MemoryError exception (Python speak), we are not allowed to use any more
>resources, so we probably need to preconstruct a few needed items for
>such a case.
Yeah, that was the reason for the non-pmc parameters. That way we can
pass on at least a minimal amount of information and possibly do
something with it.
>>Its in and named C<returncc> since yesterday "return with current
>>continuation".
> Hrm. The name's not right,
I've proposed ret_cc and returncc, about two weeks ago the first time.
I've asked for names of the opcode. As no answer arrived I just used
that name.
> ... since there's no current continuation
> involved.
Well, "current" in the sense of context, not P1. So the comment is
better: "return with continuation in context" or such.
>> push_eh handler, label
> Nope. The only thing that push_eh (or whatever we name it) needs is
> the address of the code to jump to if an exception's thrown. The
> exception pushing code should take care of building whatever
> structure's needed to call into it.
Ah, ok. That makes sense.
>>Allowing just one additional object doesn't properly support Python,
>>which has two optional expressions for the C<raise> statement and Python
>>attaches a traceback object to the exception.
> Hrm. Well, the traceback object could just be the interpreter
> structure at the time of the the exception. That'd be cheap enough to
> pass in. I'm fine with adding that as a second PMC parameter.
I don't think that this works. The handler continuation restores the
context, which changes the interpreter context. During unwinding the
control stack we should probably fill a (preconstructed) array-ish
object with pointers to contexts up to the handler conext.
leo
And Perl 6 would like control exceptions to be fast. We won't use
ordinary exceptions as heavily as Python does for normal control flow,
since we believe in undef. Or rather, we'll generate exceptions,
but instead of throwing them we'll often just stuff them into an
undef and return that.
: And Python has an hierarchy of exception *objects*, with inheritance. So
: I think that for maximum flexibility we should just have exception
: objects, with some mandatory attributes (the exception class might
: already provide enough information) and additional information the HLL
: might stuff in.
:
: How will Perl6 do it?
Same as Python, to the first approximation. See
http://dev.perl.org/perl6/synopsis/S04.html
which is up-to-date as of Saturday.
In fact, all the synopses on dev.perl.org are pretty much up-to-date
at this point. Any implementor that cares to should probably
take a pass through them all (if they haven't done so recently)
and look for any show-stoppingly unimplementable ideas, or even any
ideas that punish the innocent with the guilty, performance-wise.
(Ignoring the bullets we've already irrevocably bit, like MMD.)
Larry
Sure. Anything that throws an exception is more than welcome to pass
along a resume continuation if it wants, and I'm OK with documenting
this as a perfectly acceptable option for folks writing code.
Fair enough. The cc's going to imply current continuation, which is
going to confuse folks.
Maybe we should name it invoke_return.
> >>Allowing just one additional object doesn't properly support Python,
>>>which has two optional expressions for the C<raise> statement and Python
>>>attaches a traceback object to the exception.
>
>> Hrm. Well, the traceback object could just be the interpreter
>> structure at the time of the the exception. That'd be cheap enough to
>> pass in. I'm fine with adding that as a second PMC parameter.
>
>I don't think that this works. The handler continuation restores the
>context, which changes the interpreter context. During unwinding the
>control stack we should probably fill a (preconstructed) array-ish
>object with pointers to contexts up to the handler conext.
We'd talked at one point about swapping interpreter structures as
part of sub invocation, though there wasn't any resolution to what
the right way to do that was. It got tabled as part of the flareup.
Right now, I'm fine mandating that a non-invokable continuation is
passed as the traceback object. If benchmarks show that we're going
to require doing the swapping interpreter stuff to get non-sucky
performance, or we have to do it to get reliable traceback info in
the face of some exception scenarios, then we'll do it at that point.
(I expect we're going to have to, but I'm fine with waiting on it for
now, since it'll be transparent to bytecode and nearly all the C code)
> Maybe we should name it invoke_return.
Ok. If someone grep's through the tree and just changes all, it's done.
$ find . -type f | xargs grep -w returncc
> We'd talked at one point about swapping interpreter structures as
> part of sub invocation, though there wasn't any resolution to what
> the right way to do that was. It got tabled as part of the flareup.
I've tried that scheme and tossed it due to performance reasons.
Creating / copying whole interpreter structures is just more expensive
then what we got now. It wouldn't really help for creating tracebacks
enyway.
> Right now, I'm fine mandating that a non-invokable continuation is
> passed as the traceback object.
Not all languages need a traceback object. It's a Python feature. But
the non-invokable continuation sounds good, albeit it smells like
"context preserving overhead", as this traceback object can extract info
about the call frames, so they have to persist.
IMHO the throw as opcode scheme with all these arguments isn't flexible
enough to cover all HLL semnatics plus user thrawables.
What about:
$P0 = getclass "Exception"
e = $P0."new_extended"("KeyError")
# or
e = $P0."new_extended"("Borked", "Perl6", .Severe, whatever, ...)
...
throw e
Actually this isn't a method call, it's the new_extended opcode with a
variable amount of arguments, passed in according to pdd03.
leo
> pushmark 12
> popmark 12
> pushaction Psub
I've now implemented these bits. I hope it's correct, specifically, if a
return continuation in only captured, the action handler is not run.
See t/pmc/exceptions.t
Still missing is the throw opcode. Or better that exists, just exception
creation and the extended attributes like language is missing.
I'm still voting for a more object-ish exception constructor to better
accomodate HLLs different exception usage.
E.g.
e = new PyKeyError # presumably a constant singleton
throw e
That ought to be enough for heavily used exception and for Perl6
control exceptions.
OTOH
e = new Exception
setattribute e, "message", Pmsg
setattribute e, "language", PLang
...
throw e
construct a full exception object.
Currently it is:
e["_message"] = "foo"
e["_error"]
e["_severity"]
...
And it could be even something like:
cl = getclass "Exception"
e = cl."instantiate"("foo", "Perl", .error, .severity, ...)
leo
> ... A scope exit
> action is put in place on the control stack with:
> pushaction Psub
* What is the intended usage of the action handler?
* Specifically is this also ment for lazy DOD runs?
* How is the relationship to the C<pop_pad> opcode?
Thanks,
leo
The action handler is there to provide the languages as a way to do
something when scopes are left. It's a generic 'out' for stuff that
we've not thought about. Most scope exit stuff is cleanup which we'd
rather be done via the DOD/GC system (otherwise things go Horribly
Wrong in the face of continuations) but there may well be things that
need doing.
The one thing that I figure *will* be done is that languages will
push a sweep or collect op in their scope cleanup in those cases
where the language knows that there's potentially easy temp cleanup
that needs doing, for example filehandles that should be closed when
the scope's exited if there are no outstanding references to them.
(And I know we've got the aggressive GC system for things like that,
but in most cases languages can use something a bit less aggressive
-- do a full sweep to clean up anything that's actually dead, and
anything that escapes scope can be picked up later, since it's
lifetime's probably gone nondeterministic)
As far as pop_pad goes, I think maybe we need to revisit the control
stack ops to see if some of them can go. (There are a fair number of
rough draft things in the pad handling design that need editing)
Possibly push_pad too, which could be handled with pushaction, or
come up with a lighter-weight scheme to do something similar. Or it
may be that there are few enough things that it's worth keeping
push/pop_pad around. Not quite sure right now, but we should nail
that one down.
>>* What is the intended usage of the action handler?
>>* Specifically is this also ment for lazy DOD runs?
>>* How is the relationship to the C<pop_pad> opcode?
> The one thing that I figure *will* be done is that languages will
> push a sweep or collect op in their scope cleanup in those cases
> where the language knows that there's potentially easy temp cleanup
> that needs doing, for example filehandles that should be closed when
> the scope's exited if there are no outstanding references to them.
That'll not be really easy:
[ subroutine frame ]
|
|
[ cleanup handler subroutine frame ]
If the cleanup handler is a plain subroutine, the previous one, which
should be cleaned, is alive. A lazy DOD will find the filehandle in the
lexicals and likely in registers.
It seems that we have to do kind of a tailcall to the cleanup handler
and that we've to nullify PMC registers of the subroutine that called
the cleanup handler. But that would make the cleanup handler useless for
other tasks, like actively closing some resources.
Doing this right needs a precise definition of the semantics of such a
cleanup handler.
leo
Which does argue that it ought not be a sub, I suppose, but something
simpler. A plain bsr sort of thing.
>If the cleanup handler is a plain subroutine, the previous one, which
>should be cleaned, is alive. A lazy DOD will find the filehandle in the
>lexicals and likely in registers.
Well, maybe. Subs are going to have multiple scopes in them (which
argues for a faster-than-sub cleanup handler dispatch) so it's more
than just a 'cleanup before leaving the sub' sort of thing. There's
an awful lot of code around that looks like:
sub foo {
# Insert code here
foreach (@some_array) {
}
{
# Some code that needs its own block
}
if (foo) {
} else {
}
}
Besides cleaning up on sub exit, there's also a potential cleanup
when the foreach is left, the bare block is left, and each of the
legs of the if are left. (Potentially once on each foreach iteration,
I suppose)
Also, since the compilers are in control of when things get
established when entering a sub, if they've noted that there's a
reason for a cleanup handler they can push one before establishing
the lexical pad it needs to clean up after so that pad'll be disposed
of before the cleanup handler runs. (Or, I suppose, two can be
pushed, one for before and one for after, if it's noted that's needed)
> Which does argue that it ought not be a sub, I suppose, but something
> simpler. A plain bsr sort of thing.
A bsr doesn't change anything. It has to return to the caller. That
thing, where it's returning to, is alive.
>>If the cleanup handler is a plain subroutine, the previous one, which
>>should be cleaned, is alive. A lazy DOD will find the filehandle in the
>>lexicals and likely in registers.
> Well, maybe. Subs are going to have multiple scopes in them (which
> argues for a faster-than-sub cleanup handler dispatch) so it's more
> than just a 'cleanup before leaving the sub' sort of thing. There's
> an awful lot of code around that looks like:
> sub foo {
> # Insert code here
> foreach (@some_array) {
> }
> {
> # Some code that needs its own block
> }
> if (foo) {
> } else {
> }
> }
> Besides cleaning up on sub exit, there's also a potential cleanup
> when the foreach is left, the bare block is left, and each of the
> legs of the if are left. (Potentially once on each foreach iteration,
> I suppose)
Yes. I'll presume that the first Perl6 compiler will just emit closures
for each block. But if no handle on this closure is kept they'll likely
end up as nested scopes. For both we need a sequence:
pop_pad
sweep 0
this could be one opcode (the pop_pad isn't needed, if it's a sub exit)
scope_exit
But that still doesn't solve the problem that a file-handle (after
cleaning lexicals) is still in a PMC register, when the C<sweep 0>
opcode is run.
leo
I'm only concerned about overhead here. Live-ness is a separate
issue. (An important one, but separate. That's dealt with below)
> >>If the cleanup handler is a plain subroutine, the previous one, which
>>>should be cleaned, is alive. A lazy DOD will find the filehandle in the
>>>lexicals and likely in registers.
>
>> Well, maybe. Subs are going to have multiple scopes in them (which
>> argues for a faster-than-sub cleanup handler dispatch) so it's more
>> than just a 'cleanup before leaving the sub' sort of thing. There's
>> an awful lot of code around that looks like:
>
>> sub foo {
>> # Insert code here
>> foreach (@some_array) {
>> }
>
>> {
>> # Some code that needs its own block
>> }
>> if (foo) {
>> } else {
>> }
>> }
>
>> Besides cleaning up on sub exit, there's also a potential cleanup
>> when the foreach is left, the bare block is left, and each of the
>> legs of the if are left. (Potentially once on each foreach iteration,
>> I suppose)
>
>Yes. I'll presume that the first Perl6 compiler will just emit closures
>for each block.
Ah, I hope not. I *really* hope not. (Paying attention Patrick? :)
That'd be rather slower than necessary in most cases.
[Snippage]
>But that still doesn't solve the problem that a file-handle (after
>cleaning lexicals) is still in a PMC register, when the C<sweep 0>
>opcode is run.
True but, and this is the good part, that's not our problem. It is, I
think, safe to assume that language compilers that want timely
destruction will make sure to clean up after themselves sufficiently
to make that timely destruction possible. It's our job to provide the
mechanisms they need, and leave it to them to use them as needed.
In other words, we punt it to someone else. :)
Yup, I'm paying attention. :) While we might just emit closures
for each block in the earliest development versions of the Perl6
compiler just to get things going, I wouldn't expect it to stay that
way for very long. Certainly I don't think we'll be long past that
by the time we reach a 6.0.0 release of the compiler.
> It is, I
> think, safe to assume that language compilers that want timely
> destruction will make sure to clean up after themselves sufficiently
> to make that timely destruction possible. It's our job to provide the
> mechanisms they need, and leave it to them to use them as needed.
Agreed, with the caveat that we may discover we need some additional
mechanisms as work progresses. But it's a little early to try to
get it perfect right now -- we'll just cross our bridges when we
get to them. The important thing will for the compiler writers and
Parrot implementers to be able to adapt quickly when we find the
things we all overlooked.
Pm
>>But that still doesn't solve the problem that a file-handle (after
>>cleaning lexicals) is still in a PMC register, when the C<sweep 0>
>>opcode is run.
> True but, and this is the good part, that's not our problem. It is, I
> think, safe to assume that language compilers that want timely
> destruction will make sure to clean up after themselves sufficiently
> to make that timely destruction possible. It's our job to provide the
> mechanisms they need, and leave it to them to use them as needed.
> In other words, we punt it to someone else. :)
I'd prefer such a solution too. But:
{
my $fh = open(..);
foo(1, $h);
}
which could be some PIR like:
new_pad -1
$P0 = Pio."open"()
store_lex -1, "$fh", $P0
foo(1, $P0);
pop_pad
sweep 0
with some corresponding PASM (pdd03 call registers ignored for brevity)
new_pad -1
set P2, Px
callmethodcc "open"
set P16, P5
store_lex -1, "$fh", P16
set I5, 1
set P6, P16
set_p_pc P0, "foo"
invokecc
pop_pad
sweep 0
Now we have the filehandle scattered and duplicated in P registers. There
is no chance for a HLL to emit "null Px" to clear involved registers as
the information, where each temp is located, isn't available.
At scope exit the filehandle is alive in registers and not closed.
leo