Skip to content

Instantly share code, notes, and snippets.

@skids
Last active March 1, 2018 02:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skids/21eaaad37969c5123ce7e9c48be35274 to your computer and use it in GitHub Desktop.
Save skids/21eaaad37969c5123ce7e9c48be35274 to your computer and use it in GitHub Desktop.
Investigating topic behavior on bare blocks, conditionals, and conditional loops
Update: the patch to eliminate most topic ensconses on inlined
immediate blocks turned out to be pretty simple. It is at
https://github.com/skids/rakudo/tree/inline_topic. With those
gome there is little reason to do optimizations specifically
on while loops.
...with the caveat that there may be some way for a topic bind
to appear in inlined immediate block which I am not aware of.
But... it does at least pass all spectests, including the ones
I added (see below) to cover special cases.
On the other hand, it does not show much of an improvement in
test suite performance (no penalty either) or a few benchmarks.
So the question is whether allowing core code to turn nqp::if
and nqp::while back into perl6 control constructs with no
speed penalty is worth the patch. Personally I think so,
given those two particular constructs mess up the look and
feel of the code much more than other nqp ops.
I'll leave this subject open to comment for a while before
possibly submitting that as a PR.
(end of update)
Apparently having gotten in the good habit of not abusing $_
has left me ignorant of some of the subtleties surrounding it.
Notes here to aid in figuring out issues/questions raised in
https://irclog.perlgeek.de/perl6/2018-02-24#i_15852854
1) We already knew this, but to note the history, S04 is where it
is stated that if/while and relatives do not implicitly topicalize:
```
use of C<$_> with a conditional or conditionally repeating
statement's block is I<not> considered sufficiently explicit to turn a 0-ary
block into a 1-ary function
```
...but that doesn't say anything about localizing.
2) S06 has this:
```
Non-routine code C<Block>s,
declared with C<< -> >> or with bare curlies, are born only with C<$_>,
which is aliased to its OUTER::<$_> unless bound as a parameter.
A block generally uses the C<$!> and C<$/> defined by the innermost
enclosing routine, unless C<$!> or C<$/> is explicitly declared in
the block.
A thunk is a piece of code that may not execute immediately, for instance
because it is part of a conditional operator, or a default initialization of
an attribute. It has no scope of its own, so any new variables defined in
a thunk, will leak to the scope that they're in. Note however that
any and all lazy constructs, whether block-based or thunk-based,
such as gather or start or C<< ==> >> should declare their own C<$/>
and C<$!> so that the user's values for those variables cannot be
clobbered asynchronously.
```
...which basically explains that we can close around outer $_ and that
binding is how that is achieved if I am taking the right meaning of
the word "aliased". Also we know the litmus test for a thunk
which is handy to know, and we can rule out that blocks of ifs and whiles
are being considered thunks.
So if the above is intended to apply to bare blocks, which would appear to
be the case, then there's a reason for binding on both sides when inlining...
they would be able to rebind it to another variable, and we can't let them
rebind the outer $_, because they are supposed to have their own. But it is
fine for them to assign to it. That explains the Optimizer's inlining code.
The real spec is the tests, and the Synopsis don't always get updated.
So what is tested?
In S04-statements/while.t we have this test which passes, but warns.
# RT #125876
lives-ok { EVAL 'while 0 { my $_ }' }, 'Can declare $_ in a loop body';
In S02-magicals/dollar-underscore.t we have this test which, while it is testing
a for loop's behavior, also establishes the behavior of assigning to $_ inside
a conditional block.
$_ = 1;
my $tracker = '';
for 11,12 -> $a {
if $_ == 1 { $tracker ~= "1 : $_|"; $_ = 2; }
else { $tracker ~= "* : $_" }
}
is $tracker, '1 : 1|* : 2',
'Two iterations of a loop share the same $_ if it is not a formal parameter';
Nothing in roast tries to '$_ :=' at all. That's a coverage hole that should probably
be filled. (edit: I added https://github.com/perl6/roast/commit/c227876274 for this)
So, as far as I can tell this is expected behavior:
```
$ perl6 -e '$_ = 22; { $_ := 44; } ; $_.say'
22
$ perl6 -e '$_ = 22; { $_ = 44; } ; $_.say'
44
$ perl6 -e '$_ = 22; if 1 { $_ := 44; } ; $_.say'
22
$ perl6 -e '$_ = 22; if 1 { $_ = 44; } ; $_.say'
44
```
... this might be a bit dubious but it also A) already warns
and B) is a silly thing to do:
```
$ perl6 -e '$_ = 22; { my $_ = 44; } ; $_.say'
Potential difficulties:
Redeclaration of symbol '$_'
at -e:1
------> $_ = 22; { my $_⏏ = 44; } ; $_.say
44
```
This apparentlyy should "work" but whether the pseudo-namespace
is so magical as to perform a bind on the caller's variable
name and not on just the slot in the pseudo namespace, I dunno.
If it did, it would be a reason to have the protections for the inline
whenever the block contains calls:
```
$ perl6 -e 'my $a = 42; sub settopica { CALLERS::<$_> := 44 } ; $_ = 22; { settopica } ; $_.say'
This case of binding is not yet implemented
in sub settopica at -e line 1
in block <unit> at -e line 1
```
Now, as far as if/while is concerned, moving the protection outside the
loop does two wrong things (the optimize=99 is using custom code , see previous
gist https://gist.github.com/skids/0745bfc48eac0c0f8ddf99fd81bf50ed):
It prevents rebinding the outer $_ from inside the conditional:
```
$ perl6 -e '$_ = 22; my $a = 2; while $_ := --$a { $_.say }; $_.say'
1
0
$ perl6 --optimize=99 -e '$_ = 22; my $a = 2; while $_ := --$a { $_.say }; $_.say'
1
22
```
... and it allows the loop body to rebind the conditional's $_:
```
$ perl6 -e '$_ = 22; my $a = 2; while $_.say && --$a { $_ := 42 }; $_.say'
22
22
22
bri@atlas:~/git/specs$ perl6 --optimize=99 -e '$_ = 22; my $a = 2; while $_.say && --$a { $_ := 42 }; $_.say'
22
42
22
```
The question then becomes, for simple conditionals (or just immediate blocks) that
do not involve a call, is it possible for the static optimizer to tell that
nothing binds $_ inside the conditional, or if something may bind $_ in the body,
that the conditional won't care. Assigning $_, however, is OK.
Currently, the protections are present even in cases where it is trivially obvious
that nothing will care:
$ perl6 --target=optimize -e 'if 1 { }'
...
- QAST::Op(if) <sunk> :statement_id<1> 1 { }
- QAST::Want <wanted> 1
- QAST::WVal(Int)
- Ii
- QAST::IVal(1)
- QAST::Stmts(:resultchild(1)))
- QAST::Op(bind)
- QAST::Var(local pres_topic__1)
- QAST::Var(lexical $_)
- QAST::WVal(Nil)
- QAST::Op(bind)
- QAST::Var(lexical $_)
- QAST::Var(local pres_topic__1)
- QAST::Op(null)
...
Ideally if nothing may bind $_ in the body, the bind ops would not be added
at all, and if something in the body can bind $_ but the conditional
would not care, the binds may be moved outside the loop body to only
run once.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment