There's a bunch of information we need to save off when we call
another function or method. We need to save the current lexical chain
head, the current global chain head, the current opcode function
table, and the current set of security credentials. (We'll get to
those later) The called function, on the other hand, needs to make
sure that the stacks are in the same shape they were when it was
invoked. Parrot, finally, needs to make sure that if some sort of
security boundary is crossed that the appropriate credentials,
quotas, privs, and whatnot are in place. And, more importantly, that
they don't leak out.
Anyway, the current thinking is "push all the bits we care about onto
the system stack, make the call, and pop them off when we get back.
The called function puts the stack back in the shape they were when
it was called." Or so I was thinking. But...
The problem with this is that it puts the onus on the compilers to
spew out the right sequence of save and restore ops. It also puts the
onus on the compilers to generate the right sequence of ops so as not
to corrupt the interpreter state. And finally it puts the onus on the
interpreter to make sure that the state isn't corrupted when we're
running in one of the restricted modes where we have to assume that
the code we're running is nasty, brutish, and short, not to mention
vicious and malicious.
That's way too many onuses. (Onii? Something like that) Besides, it
also may have problems if we decide to later add in more state that
may need to be saved across sub calls. (Since we can't save state we
don't know about, because it doesn't exist yet, but may later) So,
time for Plan B.
We've got one thing going for us here--we don't use the stacks for
anything in the calling conventions. (Though we do want to save their
states) In fact, at the point we're making the call, we know
*everything* about the state we want the interpreter when the
function returns, including the location we want to go to. So, let's
use that. What we're going to do is build an object that has all the
information we need to save, including the current stack state and
return address. We'll pass that into the called function, and the
called function can then use it to return.
We're already doing something like this, but rather than pushing the
return address on the stack, we'll pass in the context object, and
rather than doing a return which pops the address off the stack,
we'll just invoke the context object. Since it's got all the stack
pointers, return address, and bits of context info, we don't even
need to bother popping things off the stacks. And as an added bonus,
if we're in a situation where we're doing a tail call, we can just
pass the context object the function got passed to it to the function
its calling, and skip building the context object in the first place.
So, I'd like to add a "makecontext Px" op to create the context
object. The object will go in P1 as part of the calling contventions.
(Which it already does, of sorts) "return Px" will invoke the context
object in register X, while a plain "returnsub" will invoke the
context object in P1. The various call ops won't change, and will
assume that user code has put a context object in P1 already. callcc
will automatically generate a new context object and put it in P1.
This make sense to everyone? It should ultimately make things
simpler--rather than a whole mass of 'savethis, savethat,
savetheother' ops followed by a restore of all that stuff, we'll just
go with a "makecontext P1, call" (or just callcc if we're being
short). Plus it should make dealing with security later.
--
Dan
--------------------------------------"it's like this"-------------------
Dan Sugalski even samurai
d...@sidhe.org have teddy bears and even
teddy bears get drunk
Klaas-Jan
Is there any difference at all between this kind of "calling context"
object you're suggesting, and a continuation? From the "callcc"
comment,
I *think* I can assume that they're the same, but I'm not entirely sure.
--
$a=24;split//,240513;s/\B/ => /for@@=qw(ac ab bc ba cb ca
);{push(@b,$a),($a-=6)^=1 for 2..$a/6x--$|;print "$@[$a%6
]\n";((6<=($a-=6))?$a+=$_[$a%6]-$a%6:($a=pop @b))&&redo;}
[ snip ]
> ... we'll pass in the context object, and
> rather than doing a return which pops the address off the stack,
> we'll just invoke the context object.
[ snap ]
> So, I'd like to add a "makecontext Px" op to create the context
> object.
It seems that we alread have such a context object
new Px, .Coroutine
Invoking this object branches to the sub. Invoking it again returns.
At least, it would only be a slight variation of this object - a second
invocation of the Coroutine object continues, where it yielded.
leo
Hey, it can already do OO stuff, why not encapsulation? :)
Where , Where ... I can't see any OO !
Gopal
/me reminds Dan again of someone waiting around in the darkness with
a C# and Java compiler in hand and a flexible backend codegen which
already has a stub for imcc generation ...
--
The difference between insanity and genius is measured by success
Yep, they're the same. Well, almost, as I'm figuring these
continuation objects will be single use, and for upward calling only.
(Saves having to mark the stack as COW, and we need to unwind lexical
stuff immediately on scope exit, respectively)
> At 4:40 PM -0400 5/13/03, Benjamin Goldberg wrote:
>>Dan Sugalski wrote:
>>[snip]
>>> This make sense to everyone? It should ultimately make things
>>> simpler--rather than a whole mass of 'savethis, savethat,
>>> savetheother' ops followed by a restore of all that stuff, we'll just
>>> go with a "makecontext P1, call" (or just callcc if we're being
>>> short). Plus it should make dealing with security later.
>>
>>Is there any difference at all between this kind of "calling context"
>>object you're suggesting, and a continuation? From the "callcc"
>>comment,
>>I *think* I can assume that they're the same, but I'm not entirely sure.
>
> Yep, they're the same. Well, almost, as I'm figuring these
> continuation objects will be single use, and for upward calling
> only. (Saves having to mark the stack as COW, and we need to unwind
> lexical stuff immediately on scope exit, respectively)
Presumably there'll be a way to make a 'real' continuation from the
continuation in P0? (ie, one that doesn't have to go upwards...)
--
Piers
Ah, damn, I thought some changes made it in when they haven't. The
can, is, and does opcodes are in, but the method call one isn't. I'll
go fix that.
>/me reminds Dan again of someone waiting around in the darkness with
> a C# and Java compiler in hand and a flexible backend codegen which
> already has a stub for imcc generation ...
Okay, I admit it, the temptation is too strong! I shall have to get
the darned method call stuff finalized, in the vtables, and in the
ops file. I think we're 90% there, it's just a matter of nailing the
rest down.
The interesting issue will be class metadata in the bytecode file,
along with the runtime inheritance stuff. That may take a few more
days to nail down.
Almost, yes, though it ought to be
new Px, .Continuation
Though there needs to be more information than just that in there--we
need to store the return address at least. (Which argues for the "new
Px, .Continuation" form being insufficient as well) I'd prefer these
to be upwards-only, but I just realized that won't work. We also have
some interesting issues with temporization and continuations that we
need to work out.
There may be some interesting issues, and as such I think we'd
probably best stick with a separate PMC type, at least for now--it
may turn otu that they're the same, but for now I'm not sure, so I'd
rather hold off.
> Almost, yes, though it ought to be
>
> new Px, .Continuation
or maybe
new Px, .LinearContinuation
> Though there needs to be more information than just that in there--we
> need to store the return address at least. (Which argues for the "new
> Px, .Continuation" form being insufficient as well) I'd prefer these
> to be upwards-only, but I just realized that won't work. We also have
> some interesting issues with temporization and continuations that we
> need to work out.
Continuation's can already hold a return address. What do you mean by
temporization issues?
--
Jonathan Sillito
Well, one of the things that perl 6 is bringing in is the concept of
transactions, of a sort. These are things that look like:
$foo = 12;
try {
temp $foo;
$foo = 18;
maybe_die;
# Stuff happens here
}
If you'll pardon the syntax, which is just wrong. Anyway, what
happens with that temp is that the value of $foo is made local to the
block, and any changes to it only stick if the block is exited
normally. If it's exited abnormally the changes are undone. Now, this
is relatively easy to handle, but lets assume that, in addition to
maybe or maybe not dying, maybe_die takes a continuation. Then
something later invokes that continuation. Do we re-temporize the
value of $foo? If so, what value gets temporized? If not, what
happens to the code in that block that assumes the value is
temporizable? In either case, what happens to variables that has an
active assignment routine?
It gets... interesting. Plain CPS continuations are easy, since the
user code never sees them and the rules are pretty restricted, but
it's the language level continuations where things get nasty. Or so I
think, but I might just not have thought things through yet.
It's not that wrong. The keyword is C<let> (C<temp> is P5's
C<local>). You don't need C<try>, but I don't think it hurts. It
just keeps the exception from propigating through into the next scope.
> Anyway, what happens with that temp is that the value of $foo is
> made local to the block, and any changes to it only stick if the
> block is exited normally. If it's exited abnormally the changes are
> undone. Now, this is relatively easy to handle, but lets assume
> that, in addition to maybe or maybe not dying, maybe_die takes a
> continuation. Then something later invokes that continuation. Do we
> re-temporize the value of $foo? If so, what value gets temporized?
> If not, what happens to the code in that block that assumes the
> value is temporizable? In either case, what happens to variables
> that has an active assignment routine?
I think of hypotheticalization as a sort of stack in each name. The
old value is right below the new value. If the block exits
abnormally, the top of the stack is just popped. If it exits
normally, we replace the second-to-top with the top. Perhaps, then,
continuations make a closure on this stack, so when it's re-invoked,
you get the same hypothetical state, with references and such working
right.
Another way to think of hypotheticals is just that their declaration
adds an appropriate UNDO block to the scope in which they're
declared. For instance:
{ let $foo = 18; ... }
Gets transformed into:
{ $old_foo := $foo; $foo = 18; ...; UNDO { $foo := $old_foo } }
This seems somehow less elegant than the stack method, but it may in
fact be exactly the same. It's obvious how continuations work with
this method.
I think we need some examples of where this would happen to really
work out what should happen. Unfortunately, I have surprisingly few
examples that have to do with continuations whatsover.
Luke
That's what I thought originally, and what I really wanted it to be,
but it isn't. Consider the obvious case--you have a tied variable
with a store action and, more importantly, a temp and untemp action.
(They can have these, though I'm not sure if Larry's said so publicly
yet) We have to call the temp when the block starts, and the untemp
when it ends. Should we, then, call the temp when we invoke a
continuation into the block? If not, should we invoke the untemp when
we leave?
Another issue occurs to me. The calling convention states that return values
are passed via registers. Your proposed approach means that the context
(which includes registers, I assume?) is restored before the calling code is
resumed. So the "returned" values are lost.
To be more concrete:
new P0, .Sub
# set up the sub
# ...
set I5, 12
callcc P0 # puts continuation in P1 and invokes P0
# ****
# I5 is 12 again !!!!
end
sub:
# ...
set I5, 4 # this method is returning 4
return P1 # restores the context and goes to **** above
Is this a real problem or am I horribly confused?
Jonathan Sillito
Nope, not registers. Those are left alone, otherwise the problem you
note would definitely be an issue.
> At 12:44 PM -0600 5/16/03, Luke Palmer wrote:
>>I think of hypotheticalization as a sort of stack in each name.
>
> That's what I thought originally, and what I really wanted it to be,
> but it isn't. Consider the obvious case--you have a tied variable with
> a store action and, more importantly, a temp and untemp action. (They
> can have these, though I'm not sure if Larry's said so publicly yet)
> We have to call the temp when the block starts, and the untemp when it
> ends. Should we, then, call the temp when we invoke a continuation
> into the block?
No, you already entered or how did you make the continuation?
> If not, should we invoke the untemp when we leave?
Yes, if the thing in question needs to balance entry/exits it can do
it itself.
--
Piers
So the caller still needs to saveall/restoreall, and the callee still needs
to make sure that the stacks are left the same as they were when it was
called? In this case I am not sure I see the point of using CPS?
On the other hand, I could imagine something like:
typedef struct Parrot_Context {
struct IReg int_reg;
struct NReg num_reg;
struct SReg string_reg;
struct PReg pmc_reg;
struct Parrot_Lexicals lex;
} * parrot_context_t;
typedef struct Parrot_Linear_Continuation {
struct Parrot_Context context;
opcode_t resume;
} * parrot_linear_continuation_t;
i.e no stacks needed. This is possible (ignoring the "losing return values"
issue raised in my previous email) if every sub is called with the correct
context to "return" to (this of course makes for an implicit stack). In this
case there is no need to saveall/restoreall and the callee does not have
stacks to worry about ...
Does this make any sense?
Jonathan Sillito
Yes and no, respectively. Taking the continuation snapshots the
stack, so the pushed registers will be there for restoration on
return, if the caller wants to restore them. The called function, on
the other hand, doesn't have to bother with the stack state at all,
since no matter what state they're in, invoking the continuation will
put them back the way the caller had them when the call was made.