From: "Patrick R. Michaud" <[EMAIL PROTECTED]> Date: Wed, 9 Jul 2008 08:25:52 -0500
On Wed, Jul 09, 2008 at 01:27:29AM -0400, Bob Rogers wrote: > What "foo" should do is create a closure and call that: > > .const .Sub inner = 'inner' > inner = newclosure inner > inner() If I understand what you're saying, and then take it to what I see as its ultimate conclusion, you're basically claiming that every call to a subroutine involving lexicals requires a newclosure op. Yes; I believe that's a cleaner design. Doing otherwise relies on runtime heuristics to decide what context ought to have been closed over, which strikes me as much less reliable. Of course, I don't *need* the cleaner design, since I can just ignore the runtime heuristics -- as long as they don't break the newclosure case. Consider: { my $x = 1; sub foo() { say $x; } foo(); } In effect this is little different from having 'foo' as an immediate block. If we now say that a newclosure op is required to invoke 'foo', then that means that the 'foo()' HLL subroutine has to be generated as: .local pmc $P0 $P0 = find_name_not_null 'foo' $P0 = newclosure $P0 $P0() . . . Not true. The compiler always knows when it's compiling a closure. So if, at the point of definition, the emitted code does: foo_closure = newclosure foo_sub set_hll_global 'foo', foo_closure then the compiler doesn't have to treat calls to "foo" specially. Of course, this means that there is a different closure installed in the namespace after each call to the outer sub, which may have ramifications for threading. But IMHO this is a tad cleaner than the current situation (i.e. after Jonathan's fix) where the same closure is shared between threads, and could therefore leak the outer context from one to the other. (I also detect a design smell, but I am not certain it's coming from where you think it is. ;-) It all gets worse if I have something like: .sub 'abc' :multi('Integer') ... .end .sub 'abc' :multi('String') :outer('xyz') ... .end One of these is a Sub PMC, the other is a Closure PMC, and the thing that is stored under symbol 'abc' is neither -- that's a MultiSub PMC. I'm fairly certain I can't do "newclosure" on the MultiSub PMC . . . I think you're right, but that is probably for good reason; I don't think it makes sense. Instead, you should do newclosure on the method sub, and then define the closure as a method on the MultiSub. And if you can't do that, I claim that is a shortcoming of the Parrot MOP. I would like to see some HLL code for this. In Lisp, this winds up doing a newclosure on the method before stuffing it into the generic function, as outlined above -- and this particular method is not defined until then. IOW, there's no way to call this method that doesn't require having installed it via newclosure first. And the nature of the closure (i.e. what context is closed over) doesn't change unless the method is redefined explicitly. So the fact that you can say this in PIR doesn't necessary mean that it ought to be meaningful. In fact, if "xyz" is called in multiple threads, I suspect you need multiple MultiSub objects for "abc" anyway, which requires explicit runtime mangling to ensure that the right method closure is installed in the right context. (This is distinct from the case of lexically-scoped methods, which definitely require a new MultiSub object for each scope, and so the problem doesn't arise.) Ultimately I think that Closure PMCs need to be smart enough to take a newclosure if they need one at the time they're invoked, rather than trying to place that burden on the caller. (Note that I'm not arguing that the current implementation is correct -- I'm just describing what I think a correct implementation will need to do.) That's good; we semi-agree. But I'm arguing further that making parrot guess the right context is also broken, certainly in some if not all situations. I would like at least to persuade you that your badlex.pir case is incorrect (but will settle for only semi-agreeing. ;-) I also think we probably still need to have a newclosure opcode available to allow PIR to explicitly say when to take a closure . . . We *definitely* need newclosure; otherwise there can only ever be one closure at a time. Except perhaps by cloning the closure, and hoping that the runtime magic does the right thing. But I claim that compilers can always emit code that knows what the right context is; why should parrot *ever* have to guess? > [...] > > It may be possible to rescue Patrick's desired behavior by keeping > track of whether the outer_sub was initialized by newclosure or not. My 'desired behavior' is simply that we have something that works. :-) But I suspect that this means that closure PMCs will have to keep track of whether they've already been newclosured. Not clobbering the result of an explicit newclosure would certainly work for me. I question whether the other cases can be made to DTRT. But that's your can of worms to dive into; you're welcome to it. ;-} > Personally, I'd prefer if it were always an error to call a closure sub > directly; implicitly initializing the outer context is bad enough, but > re-initializing it is worse. (But we've had this discussion before [1], > and I was not persuasive.) Given the examples I listed above, I think we _have_ to provide a way for Closure PMCs to be invoked directly from PIR, and then either the Closure PMC vtable_invoke method or IMCC performs whatever context initialization and/or fixups that are needed to have everything work. These are good examples, but AFAICS they don't pose any problems that aren't better solved by taking an explicit newclosure, and (re-)installing it in the namspace or MultiSub (modulo MOP issues). Perl is the only language with the odd ability to call what ought to be a closure before calling the outer sub, as in perl-closure-test.pl (attached). (This is AFAIK; in all other languages I know, you can't even talk about such a sub, much less call it, without delving into the implementation. If anyone knows of any other languages with this capability, I would love to learn of them.) So that could be a valid ecological niche for "autoclose" -- except that autoclose can't even be made to work in this case (see perl-closure-test.bad.pir, also attached), because the "outer" context doesn't exist yet, so no amount of magic will find it. Along those lines, it occurs to me that badlex.pir doesn't solve the general case for the Perl 6 example you posted. What should happen if I add "inner()" in main before the first "foo" call? (In Perl 5, $x is undef; I'm hoping it's an "undefind sub" error in Perl 6.) What would you have to do to badlex.pir to get it to reflect this? Conceivably, it could be fixed in C by having the PBC thawing code initialize the outer sub to an empty context -- but that would definitely be taking a walk on the Dark Side. (Also, a reminder that whatever we do to fix this ticket has to keep RT #56184 working as well. :-) Pm Fine by me. And, since it sounds like you think that recursive-lex.pir also ought to work (and assuming no one else objects), I will add it as a "todo" case, so we can be sure that *that* also continues to work. -- Bob
#!/usr/bin/perl use strict; use warnings; sub outer { my $x = shift; print "outer foo $x\n"; sub inner { print "inner foo $x\n"; } } # Note that it is not illegal to call inner before outer. inner(); outer(42); inner();
.sub outer .param pmc x .lex '$x', x print "outer foo " print x print "\n" .end .sub inner :outer('outer') .local pmc x x = find_lex '$x' print "inner foo " print x print "\n" .end .sub 'main' :main inner() outer(42) inner() .end