Last active
March 1, 2018 02:11
-
-
Save skids/21eaaad37969c5123ce7e9c48be35274 to your computer and use it in GitHub Desktop.
Investigating topic behavior on bare blocks, conditionals, and conditional loops
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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