Skip to content

Commit d062aa3

Browse files
committed
Implement lexical => local lowering.
1 parent 44181ad commit d062aa3

File tree

1 file changed

+146
-11
lines changed

1 file changed

+146
-11
lines changed

src/NQP/Optimizer.nqp

Lines changed: 146 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,99 @@ class NQP::Optimizer {
1515
# immediate block or a declaration block.
1616
has %!usages_inner;
1717

18+
# If lowering is, for some reason, poisened.
19+
has $!poisoned;
20+
1821
method add_decl($var) {
1922
%!decls{$var.name} := $var;
2023
}
2124

2225
method add_usage($var) {
23-
my @usages := %!usages_flat{$var.name};
26+
my $name := $var.name;
27+
my @usages := %!usages_flat{$name};
2428
unless @usages {
2529
@usages := [];
26-
%!usages_flat{$var.name} := @usages;
30+
%!usages_flat{$name} := @usages;
2731
}
2832
nqp::push(@usages, $var);
2933
}
34+
35+
method poison_lowering() { $!poisoned := 1; }
36+
37+
method get_decls() { %!decls }
38+
39+
method get_usages_flat() { %!usages_flat }
40+
41+
method get_usages_inner() { %!usages_inner }
42+
43+
method is_flattenable() {
44+
for %!decls {
45+
return 0 if $_.value.scope eq 'lexical';
46+
return 0 if $_.value.decl eq 'param';
47+
}
48+
1
49+
}
50+
51+
method incorporate_inner($vars_info, $flattened) {
52+
# We'll exclude anything that the inner or flattened thing has as
53+
# a declaration, since those are its own.
54+
my %decls := $vars_info.get_decls;
55+
56+
# Inner ones always go into our inners set.
57+
add_to_set(%!usages_inner, $vars_info.get_usages_inner, %decls);
58+
59+
# Flat ones depend on if we flattened this block into ourself.
60+
add_to_set($flattened ?? %!usages_flat !! %!usages_inner,
61+
$vars_info.get_usages_flat, %decls);
62+
63+
sub add_to_set(%set, %to_add, %exclude) {
64+
for %to_add {
65+
my $name := $_.key;
66+
next if nqp::existskey(%exclude, $name);
67+
my @existing := %set{$name};
68+
if @existing {
69+
for $_.value { nqp::push(@existing, $_) }
70+
#nqp::splice(@existing, $_.value, 0, 0);
71+
}
72+
else {
73+
%set{$name} := $_.value;
74+
}
75+
}
76+
}
77+
}
78+
79+
method lexicals_to_locals() {
80+
return 0 if $!poisoned;
81+
for %!decls {
82+
# We're looking for lexical var or param decls.
83+
my $qast := $_.value;
84+
my str $scope := $qast.scope;
85+
next unless $scope eq 'lexical';
86+
my str $decl := $qast.decl;
87+
next unless $decl eq 'param' || $decl eq 'var';
88+
89+
# Consider name. Can't lower if it's used by any nested blocks.
90+
my str $name := $_.key;
91+
unless nqp::existskey(%!usages_inner, $name) {
92+
# Lowerable if it's a normal variable.
93+
next if nqp::chars($name) < 2;
94+
my str $sigil := nqp::substr($name, 0, 1);
95+
next unless $sigil eq '$' || $sigil eq '@' || $sigil eq '%';
96+
next unless nqp::iscclass(nqp::const::CCLASS_ALPHABETIC, $name, 1);
97+
98+
# Seems good; lower it.
99+
my $new_name := $qast.unique('__lowered_lex');
100+
$qast.scope('local');
101+
$qast.name($new_name);
102+
if %!usages_flat{$name} {
103+
for %!usages_flat{$name} {
104+
$_.scope('local');
105+
$_.name($new_name);
106+
}
107+
}
108+
}
109+
}
110+
}
30111
}
31112

32113
has @!block_stack;
@@ -42,11 +123,39 @@ class NQP::Optimizer {
42123
}
43124

44125
method visit_block($block) {
126+
# Push block and a new block vars tracking block.
45127
@!block_stack.push($block);
46128
@!block_var_stack.push(BlockVars.new);
129+
130+
# Visit all children, which includes nested blocks.
47131
self.visit_children($block);
132+
133+
# Methods with late-bound names poison lowering.
134+
if nqp::substr($block.name, 0, 12) eq '!!LATENAME!!' {
135+
self.poison_lowering();
136+
}
137+
138+
# Pop the block and the vars info.
48139
@!block_stack.pop();
49-
@!block_var_stack.pop();
140+
my $vars_info := @!block_var_stack.pop();
141+
142+
# Lower any declarations we can.
143+
$vars_info.lexicals_to_locals();
144+
145+
# If the block has no lexical declarations remaining, and it was an
146+
# immediate block, then flatten it in.
147+
my int $flattened := 0;
148+
if $block.blocktype eq 'immediate' || $block.blocktype eq 'immediate_static' {
149+
if $vars_info.is_flattenable {
150+
my @innards := $block.list;
151+
$block := QAST::Stmts.new( |@innards );
152+
$flattened := 1;
153+
}
154+
}
155+
156+
# Incorporate this block's info into outer block's info.
157+
@!block_var_stack[nqp::elems(@!block_var_stack) - 1].incorporate_inner($vars_info, $flattened);
158+
50159
$block;
51160
}
52161

@@ -59,8 +168,22 @@ class NQP::Optimizer {
59168
return self.visit_handle($op);
60169
}
61170

62-
# Visit children first.
63-
self.visit_children($op);
171+
# A for loop must have its block treated as a declaration; besides
172+
# that, visit children as normal.
173+
if $opname eq 'for' {
174+
my $orig := $op[1].blocktype;
175+
$op[1].blocktype('declaration');
176+
self.visit_children($op);
177+
$op[1].blocktype($orig);
178+
}
179+
else {
180+
self.visit_children($op);
181+
}
182+
183+
# nqp::ctx and nqp::curlexpad capture the current context and so poisons lowering
184+
if $opname eq 'ctx' || $opname eq 'curlexpad' {
185+
self.poison_lowering();
186+
}
64187

65188
# Consider numeric ops we can simplify.
66189
my $typeinfo := nqp::chars($opname) > 2
@@ -147,12 +270,17 @@ class NQP::Optimizer {
147270
}
148271

149272
method visit_var($var) {
150-
my int $top := nqp::elems(@!block_var_stack) - 1;
151-
if $var.decl {
152-
@!block_var_stack[$top].add_decl($var);
153-
}
154-
else {
155-
@!block_var_stack[$top].add_usage($var);
273+
my str $scope := $var.scope;
274+
if $scope eq 'positional' || $scope eq 'associative' {
275+
self.visit_children($var);
276+
} else {
277+
my int $top := nqp::elems(@!block_var_stack) - 1;
278+
if $var.decl {
279+
@!block_var_stack[$top].add_decl($var);
280+
}
281+
else {
282+
@!block_var_stack[$top].add_usage($var);
283+
}
156284
}
157285
}
158286

@@ -171,6 +299,7 @@ class NQP::Optimizer {
171299
} elsif nqp::istype($visit, QAST::Want) {
172300
self.visit_children($visit, :skip_selectors)
173301
} elsif nqp::istype($visit, QAST::Regex) {
302+
self.poison_lowering();
174303
QRegex::Optimizer.new().optimize($visit, @!block_stack[+@!block_stack - 1], |%!adverbs);
175304
} else {
176305
self.visit_children($visit);
@@ -202,4 +331,10 @@ class NQP::Optimizer {
202331
nqp::die("No compile-time value for $name");
203332
}
204333
}
334+
335+
method poison_lowering() {
336+
for @!block_var_stack {
337+
$_.poison_lowering();
338+
}
339+
}
205340
}

0 commit comments

Comments
 (0)