From: "Matt Diephouse via RT" <[EMAIL PROTECTED]> Date: Sat, 04 Aug 2007 00:26:05 -0700
. . . Again, I believe this is what Bob was saying: it's not possible to be faithful to the original p5 source without creating a separate subroutine for every loop body. Exactly. And there are reasons why that's a bad idea (even if that's what they are in Perl 6): - It introduces extra overhead in terms of the compiled code - It's extra work for the compiler ? introducing an extra pass for lexical analysis I am discovering that this is especially cumbersome for languages that have "goto." FWIW, I believe that in Lisp they are restricted in such a way that it is possible (1) to identify the scopes that might need to be broken out into separate subs, and (2) to optimize them away in most of the cases where this is possible without needing to do too much control flow analysis. It still requires an extra pass and more runtime overhead, though. But languages with less restricted "goto" are hairier still. Consider the test-closures-4.pl script (first attachment) which rewrites the loop to use an explicit "goto", and adds a few extra lexical variables and control flow quirks. Ostensibly, one would translate this to PIR by splitting the inner block at each label, and implementing the block fragments as subs (attached as test-closures-4.pir). Because the scopes are nested, the blocks must be nested as well, i.e. the sub for "push_it" must reference the sub for "before_c" as its :outer sub, and similarly the sub for the start of the block (let's call it "block_start") must have "before_c" as its :outer. First off, note that I have finessed the implementation of "goto again" by using normal sub return, and moving this goto into the outermost sub. To handle situations where we are jumping to a lexically earlier label more generally, the compiler could create a continuation in the outer sub and stuff it in a new lexical variable so that the inner sub can call it if needed. The fully general technique would be to split the "again" label body out into a sub and just call it. Note that tailcalling here makes little difference, because all of the various contexts are kept alive by the closures we create, and are not destroyed until Parrot exits. But the "goto push_it if $b == 4;" part is even more problematic, as it skips the before_c block fragment entirely, and goes straight from block_start to push_it. To my surprise, it is possible to create a closure of push_it from block_start and then call it, even though block_start is not push_it's :outer sub (isn't this a bug?). But of course the resulting closure lacks $c; it quite properly says "Lexical '$c' not found" when called. My reading of perlsub is that $c ought to be in scope, because we're still in the same block as the "my", but the value should be "undef" because we jumped over the initialization code. Interestingly, this is not what Perl 5 does; it reports that $c is bound to 9, the same value as for the *next* closure, which seems very odd. Regardless, a better PIR solution would be to call an alternative skip_before_c sub that creates the necessary lexical environment before calling push_it. But after a day of goto madness, I don't have the stomach for it. In sum, this all seems pretty ugly, and unworthy of Parrot. To compile an HLL "goto" using explicit pad manipulation, by contrast, the compiler just has to emit code to massage the pad state so that it conforms to what the destination requires, and then emit a PIR "goto". The alternative (which Bob is suggesting) is to reintroduce push_pad/pop_pad so you can create new lexical scopes without using subroutines for loop bodies. His suggestion seems reasonable to me. We won't run into this particular problem with Tcl, but I think most languages will. So we've now heard from three fans of explicit pad manipulation, but nothing from The Powers That Be. Allison? Chip? -- Bob
## Return n closures, each with lexical references to "I" and "N'. .sub make_closures_loop .param pmc n .lex "$n", n .lex "$i", $P42 .local pmc result, counter .lex "$counter", counter .lex "@result", result result = new .FixedPMCArray $I1 = n result = $I1 counter = new .Integer counter = 0 .local pmc block_start, bs .const .Sub bs = 'block_start' newclosure block_start, bs again: $I0 = counter if $I0 < $I1 goto do_block .return (result) do_block: block_start() goto again .end .sub block_start :outer ('make_closures_loop') .lex "$i", $P42 find_lex $P43, "$n" .local pmc counter, result find_lex counter, "$counter" find_lex result, "@result" ## bind $i $I42 = counter inc counter $P42 = new .Integer $P42 = $I42 inc $P42 ## bind $b .local pmc b .lex "$b", b $P33 = new .Integer $P33 = 2 b = n_mul $P33, $P42 ## if b == 4 goto call_push_it ## Note that we have to create and call closures explicitly, because the ## "autoclose" feature doesn't cut it for more than one level of :outer. .local pmc before_c, bc .const .Sub bc = 'before_c' newclosure before_c, bc .return before_c() call_push_it: ## call push_it .local pmc push_it, pi_sub .const .Sub $P99 = 'push_it' if_null $P99, oops newclosure push_it, $P99 .return push_it() oops: print "oops\n" .end .sub before_c :outer('block_start') .local pmc i find_lex i, "$i" .local pmc b find_lex b, "$b" before_c_restart: ## bind $c .local pmc c .lex "$c", c $P33 = new .Integer $P33 = 2 c = n_add i, b ## push if odd. $I1 = c $I2 = 1 $I3 = $I1 & $I2 if $I3 goto call_push_it ## else $b++ and try again inc b goto before_c_restart call_push_it: ## call push_it .local pmc push_it, pi .const .Sub pi = 'push_it' newclosure push_it, pi .return push_it() .end .sub push_it :outer('before_c') .local pmc counter, result find_lex counter, "$counter" find_lex result, "@result" $I1 = counter dec $I1 .const .Sub $P53 = 'internal_make_closures_loop_0' newclosure $P52, $P53 result[$I1] = $P52 .end .sub internal_make_closures_loop_0 :outer('push_it') find_lex $P42, "$i" find_lex $P43, "$n" find_lex $P44, "$b" .local pmc c find_lex c, "$c" print "Called sub " print $P42 print " out of " print $P43 print ": $b = " print $P44 print ", $c = " print c print ".\n" .end ## Make three closures and call them in turn. .sub test_closures :main .local pmc closures $I1 = 3 debug 0x80 closures = make_closures_loop($I1) $I0 = 0 next: if $I0 >= $I1 goto done $P52 = closures[$I0] $P52() inc $I0 goto next done: .end
#! /usr/bin/perl -w use strict; use warnings; sub make_closures_loop { # Return n closures, each with lexical references to $i and $n. my $n = shift; my @result; my $counter = 0; again: return @result if $counter >= $n; { # Without these braces, we don't get a new lexical scope. my $i = ++$counter; my $b = 2*$i; goto push_it # Note that this will leave $c undefined. if $b == 4; before_c: my $c = $i+$b; goto push_it if $c & 1; $b++; goto before_c; push_it: push(@result, sub { print("Called sub $i out of $n: ", "\$b = $b, \$c = $c.\n") }); goto again; } } # Make three closures and call them in turn. for my $c (make_closures_loop(3)) { $c->(); }