Skip to content

Commit

Permalink
Work towards more aggressive lexical lowering
Browse files Browse the repository at this point in the history
This starts us down the path of turning "normal" variables into
locals, which will not only be a little faster for lookup, but give
dynamic optimizers a good bit more to work with too. It will also
enable more block flattening.

This gets the transformation essentially working, however there's
quite a bit of spectest fallout that needs to be investigated yet
(most likely with just a few root causes).
  • Loading branch information
jnthn committed Jan 2, 2019
1 parent c869a6e commit e73853a
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 9 deletions.
67 changes: 58 additions & 9 deletions src/Perl6/Optimizer.nqp
Expand Up @@ -438,6 +438,12 @@ my class BlockVarOptimizer {
# If p6bindsig is used. # If p6bindsig is used.
has int $!uses_bindsig; has int $!uses_bindsig;


# If we're currently inside a handler argument of an nqp::handle. These
# are code-gen'd with an implicit block around them, so we mustn't lower
# lexicals referenced in them to locals.
has int $!in_handle_handler;
has %!used_in_handle_handler;

method add_decl($var) { method add_decl($var) {
my str $scope := $var.scope; my str $scope := $var.scope;
if $scope eq 'lexical' || $scope eq 'lexicalref' { if $scope eq 'lexical' || $scope eq 'lexicalref' {
Expand All @@ -455,6 +461,9 @@ my class BlockVarOptimizer {
%!usages_flat{$name} := @usages; %!usages_flat{$name} := @usages;
} }
nqp::push(@usages, $var); nqp::push(@usages, $var);
if $!in_handle_handler {
%!used_in_handle_handler{$name} := 1;
}
} }
} }


Expand All @@ -480,6 +489,10 @@ my class BlockVarOptimizer {


method uses_bindsig() { $!uses_bindsig := 1; } method uses_bindsig() { $!uses_bindsig := 1; }


method entering_handle_handler() { $!in_handle_handler++; }

method leaving_handle_handler() { $!in_handle_handler--; }

method get_decls() { %!decls } method get_decls() { %!decls }


method get_usages_flat() { %!usages_flat } method get_usages_flat() { %!usages_flat }
Expand Down Expand Up @@ -609,23 +622,32 @@ my class BlockVarOptimizer {
return 0 if $!poisoned || $!uses_bindsig; return 0 if $!poisoned || $!uses_bindsig;
return 0 unless nqp::istype($block[0], QAST::Stmts); return 0 unless nqp::istype($block[0], QAST::Stmts);
for %!decls { for %!decls {
# We're looking for lexical var decls; these have no magical # We're looking for lexical var/contvar decls.
# vivification lexical behavior and so are safe to lower.
my $qast := $_.value; my $qast := $_.value;
my str $decl := $qast.decl;
next unless $decl eq 'var';
my str $scope := $qast.scope; my str $scope := $qast.scope;
next unless $scope eq 'lexical'; next unless $scope eq 'lexical';
my str $decl := $qast.decl;
my int $is_contvar := $decl eq 'contvar';
next unless $is_contvar || $decl eq 'var';


# Also ensure not dynamic. # Also ensure not dynamic.
my $qv := $qast.value; my $qv := $qast.value;
my $dynamic := nqp::isconcrete_nd($qv) && my $dynamic := nqp::isconcrete_nd($qv) &&
try nqp::getattr($qv, nqp::what_nd($qv), '$!descriptor').dynamic; try nqp::getattr($qv, nqp::what_nd($qv), '$!descriptor').dynamic;
next if $dynamic; next if $dynamic;


# Consider name. Can't lower if it's used by any nested blocks. # If it's a contvar, then the value should be a concrete P6opaque
# for us to lower its initialization.
if $is_contvar {
next unless nqp::isconcrete_nd($qv) &&
nqp::reprname(nqp::what_nd($qv)) eq 'P6opaque';
}

# Consider name. Can't lower if it's used by any nested blocks or
# in an nqp::handlers handler.
my str $name := $_.key; my str $name := $_.key;
unless nqp::existskey(%!usages_inner, $name) { unless nqp::existskey(%!usages_inner, $name) ||
nqp::existskey(%!used_in_handle_handler, $name) {
# Lowerable if it's a normal variable. # Lowerable if it's a normal variable.
next if nqp::chars($name) < 1; next if nqp::chars($name) < 1;
unless nqp::iscclass(nqp::const::CCLASS_ALPHABETIC, $name, 0) { unless nqp::iscclass(nqp::const::CCLASS_ALPHABETIC, $name, 0) {
Expand All @@ -648,12 +670,32 @@ my class BlockVarOptimizer {
next if $ref'd; next if $ref'd;


# Seems good; lower it. Note we need to retain a lexical in # Seems good; lower it. Note we need to retain a lexical in
# case of binder failover to generate errors. # case of binder failover to generate errors. (TODO: only
# retain them for parameters.)
my $new_name := $qast.unique('__lowered_lex'); my $new_name := $qast.unique('__lowered_lex');
$block[0].unshift(QAST::Var.new( :name($qast.name), :scope('lexical'), $block[0].unshift(QAST::Var.new( :name($qast.name), :scope('lexical'),
:decl('var'), :returns($qast.returns) )); :decl('var'), :returns($qast.returns) ));
$qast.name($new_name); $qast.name($new_name);
$qast.scope('local'); $qast.scope('local');
if $is_contvar {
# Instead of the vivify on first read, we instead set up
# the variable's container. The naive way to do that would
# be a clone of the prototype value, but to explicitly
# bindattr is much more analyzable by VM-level optimizers,
# such as MoarVM's spesh. We skip this for the `our` case,
# as it is bound immediately.
$qast.decl('var');
unless $qast.ann('our_decl') {
$block[0].push(QAST::Op.new(
:op('bind'),
QAST::Var.new( :name($new_name), :scope('local') ),
QAST::Op.new(
:op('clone_nd'),
QAST::WVal.new( :value($qv) )
)
));
}
}
if %!usages_flat{$name} { if %!usages_flat{$name} {
for %!usages_flat{$name} { for %!usages_flat{$name} {
$_.scope('local'); $_.scope('local');
Expand Down Expand Up @@ -2375,7 +2417,7 @@ class Perl6::Optimizer {
method visit_handle($op) { method visit_handle($op) {
my int $orig_void := $!void_context; my int $orig_void := $!void_context;
$!void_context := 0; $!void_context := 0;
self.visit_children($op, :skip_selectors); self.visit_children($op, :skip_selectors, :handle);
$!void_context := $orig_void; $!void_context := $orig_void;
$op $op
} }
Expand Down Expand Up @@ -2613,7 +2655,8 @@ class Perl6::Optimizer {
} }


# Visits all of a node's children, and dispatches appropriately. # Visits all of a node's children, and dispatches appropriately.
method visit_children($node, :$skip_selectors, :$resultchild, :$first, :$void_default) { method visit_children($node, :$skip_selectors, :$resultchild, :$first, :$void_default,
:$handle) {
note("method visit_children $!void_context\n" ~ $node.dump) if $!debug; note("method visit_children $!void_context\n" ~ $node.dump) if $!debug;
my int $r := $resultchild // -1; my int $r := $resultchild // -1;
my int $i := 0; my int $i := 0;
Expand All @@ -2636,6 +2679,9 @@ class Perl6::Optimizer {
else { else {
note("Non-QAST node visited " ~ $visit.HOW.name($visit)) if $!debug; note("Non-QAST node visited " ~ $visit.HOW.name($visit)) if $!debug;
} }
if $handle && $i > 0 {
@!block_var_stack[nqp::elems(@!block_var_stack) - 1].entering_handle_handler();
}
if nqp::istype($visit, QAST::Op) { if nqp::istype($visit, QAST::Op) {
$node[$i] := self.visit_op($visit) $node[$i] := self.visit_op($visit)
} }
Expand Down Expand Up @@ -2711,6 +2757,9 @@ class Perl6::Optimizer {
else { else {
note("Weird node visited: " ~ $visit.HOW.name($visit)) if $!debug; note("Weird node visited: " ~ $visit.HOW.name($visit)) if $!debug;
} }
if $handle && $i > 0 {
@!block_var_stack[nqp::elems(@!block_var_stack) - 1].leaving_handle_handler();
}
} }
$i := $first ?? $n !! $i + 1; $i := $first ?? $n !! $i + 1;
$!void_context := $outer_void; $!void_context := $outer_void;
Expand Down
1 change: 1 addition & 0 deletions src/Perl6/World.nqp
Expand Up @@ -1625,6 +1625,7 @@ class Perl6::World is HLL::World {
:scope('lexical'), :name($name), :decl('var'), :scope('lexical'), :name($name), :decl('var'),
:returns(%cont_info<bind_constraint>) :returns(%cont_info<bind_constraint>)
); );
$var.annotate('our_decl', 1) if $scope eq 'our';
$block[0].unshift($var); $block[0].unshift($var);
} }
$block.symbol($name, :scope('lexical'), :type(%cont_info<bind_constraint>), :descriptor($descriptor)); $block.symbol($name, :scope('lexical'), :type(%cont_info<bind_constraint>), :descriptor($descriptor));
Expand Down

0 comments on commit e73853a

Please sign in to comment.