From: Leopold Toetsch <[EMAIL PROTECTED]>
   Date: Mon, 21 Feb 2005 11:40:49 +0100

   Bob Rogers <[EMAIL PROTECTED]> wrote:
   >    In situations where A calls B and B tail-calls C, and C produces some
   > arbitrary number of return values, I would like to be able to generate
   > code for B without having to care how many values A expects, how many C
   > produces, or even whether these numbers are fixed at compile-time.

   I'd pass one argument array around. With C<foldup/.flatten_arg) you are
   doing that anyway.

Well, the example I showed you was a bit contrived.  I'm working on
implementing Common Lisp call/return semantics, where functions often
take variable numbers of arguments, and can return zero or more values,
and it's the caller's responsibility to sort out what it got back.  In
fact, CL multiple return values are quite similar to optional arguments
by design.  Furthermore, there are numerous places in the spec where it
says that "expression X returns all the values of its final subform" --
hence my interest in tail calling.  (Just like Perl5, as a matter of
fact, but without context propagation.)

   So the Parrot calling convention seemed marvelously well suited to
all that, especially given the symmetry between call and return, and it
would be a shame to throw that all away by passing/returning arrays,
never mind the extra overhead.

   . . .

   >    4.  The return must be just before .end; it doesn't work if I invert
   > the sense of the test and swap the "bad_func" and "doit" blocks.  (Is
   > this a bug in tail-call optimization?)

   You could call it a bug, yes. This is the only case, where tailcall
   optimization is done.

It had seemed otherwise, looking at imcc/pcc.c yesterday, but I must not
have been looking closely enough.  Is that what the "&& !tmp->next" in
line 583 does (CVS 1.86)?  Is there a rationale behind limiting it this
way?

   But you can always branch to the end of the function and have just
   one return statement at the very end.

That would be awkward to do in general.  The default assumption in
Common Lisp is to pass back all values of a call that is in tail
position (whether or not tail-call optimization is done).  So any
function of any size is likely to have numerous such calls, each
potentially calling different functions with different arguments, and
having different numbers of return values.

   >    The key problem is that this code works only if compiled with
   > tail-merging (tail-call optimization) enabled, which strikes me as
   > wrong; correct code should not have to depend on an optimization,
   > certainly not a non-default one.

   Your code is lying about the count of returned values. You can't expect
   that to work.

Exactly; I'd much rather emit "honest" code.  ;-}

   >    So I would like to be able to tell IMCC explicitly that I am doing a
   > tail call which should pass all returned values back to my caller.  This
   > should be regardless of whether tail-merging is enabled, but should
   > definitely be tail-merged if so.

   As said, I'd just pass exactly one array around.

If this is going to be the dominant idiom for compilers targeting
Parrot, then I may need to get on the bandwagon some day, if only for
the sake of interoperability.  Still, it seems like a waste of such an
elegant mechanism . . .

   >    I thought I might be able to do this via .pcc_call with an explicit
   > continuation, but I don't see how to get to the continuation that
   > returncc would use.  Is this possible?

     include "interpinfo.pasm"
     the_cont = interpinfo .INTERPINFO_CURRENT_CONT

Great; thank you.

   >    Better still would be an explicit way to say "call and return all
   > values."  Here's one possible syntax for the example above:

   >    doit:
   >            .pcc_begin prototyped
   >            .flatten_arg argv
   >            .pcc_tail_call function
   >            .pcc_end
   >    .end

   > The ".pcc_tail_call function" would be an alternative to the "pcc_call
   > opt_label pcc_results" sequence in the "pcc_sub_call" production.  It
   > seems to me that this could generate a normal call followed by returncc
   > (without changing I0-4) if tail-call optimization is off.

   You could write in in PASM.

I was hoping to avoid that; like I say, I'll need it frequently . . .

   But yep. Such a construct should be there. And additionally maybe

         .tail_call (rets) = func(...)

Actually, if that *is* a tail call, then you can't possibly be
interested in "(rets)".  But this syntax is clearly much easier to use
for cases that don't require .flatten_arg or other .pcc_begin/.pcc_end
magic.  So how about

         .tail_call func(...)

for something that *must* use the tailcall op, and

         .return_call func(...)

as a shorthand for other calls in tail position that return all values?
Having two separate constructions helps to avoid confusion between
required semantics and permitted optimization.  For consistency, there
must be two .pcc_* constructs:  .pcc_tail_call and .pcc_return_call,
with corresponding semantics.

   The extra syntax also provides detailed control over tail-call
optimization.  If you restrict the optimization only to .tail_call and
.pcc_tail_call constructs, then the upstream compiler can specify
whether tail-calling may, must, or must not be done, for each call
individually.  (And check_tail_call won't have to work so hard.)

   . . .

   Hope that helps and welcome,

   leo

It does indeed; thanks particularly for the design help.  I don't know
when (or even if) I'll be able to get around to it -- C is not my strong
suit -- but this has the feel of The Right Thing, so there's hope.

                                        -- Bob

Reply via email to