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