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->();
}

Reply via email to