@@ -15,18 +15,99 @@ class NQP::Optimizer {
15
15
# immediate block or a declaration block.
16
16
has % ! usages_inner ;
17
17
18
+ # If lowering is, for some reason, poisened.
19
+ has $ ! poisoned ;
20
+
18
21
method add_decl ($ var ) {
19
22
% ! decls {$ var . name } := $ var ;
20
23
}
21
24
22
25
method add_usage ($ var ) {
23
- my @ usages := % ! usages_flat {$ var . name };
26
+ my $ name := $ var . name ;
27
+ my @ usages := % ! usages_flat {$ name };
24
28
unless @ usages {
25
29
@ usages := [];
26
- % ! usages_flat {$ var . name } := @ usages ;
30
+ % ! usages_flat {$ name } := @ usages ;
27
31
}
28
32
nqp :: push (@ usages , $ var );
29
33
}
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
+ }
30
111
}
31
112
32
113
has @ ! block_stack ;
@@ -42,11 +123,39 @@ class NQP::Optimizer {
42
123
}
43
124
44
125
method visit_block ($ block ) {
126
+ # Push block and a new block vars tracking block.
45
127
@ ! block_stack . push ($ block );
46
128
@ ! block_var_stack . push (BlockVars. new );
129
+
130
+ # Visit all children, which includes nested blocks.
47
131
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.
48
139
@ ! 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
+
50
159
$ block ;
51
160
}
52
161
@@ -59,8 +168,22 @@ class NQP::Optimizer {
59
168
return self . visit_handle($ op );
60
169
}
61
170
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
+ }
64
187
65
188
# Consider numeric ops we can simplify.
66
189
my $ typeinfo := nqp :: chars ($ opname ) > 2
@@ -147,12 +270,17 @@ class NQP::Optimizer {
147
270
}
148
271
149
272
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
+ }
156
284
}
157
285
}
158
286
@@ -171,6 +299,7 @@ class NQP::Optimizer {
171
299
} elsif nqp ::istype($ visit , QAST ::Want) {
172
300
self . visit_children($ visit , : skip_selectors)
173
301
} elsif nqp ::istype($ visit , QAST ::Regex) {
302
+ self . poison_lowering();
174
303
QRegex::Optimizer. new (). optimize($ visit , @ ! block_stack [+ @ ! block_stack - 1 ], | % ! adverbs );
175
304
} else {
176
305
self . visit_children($ visit );
@@ -202,4 +331,10 @@ class NQP::Optimizer {
202
331
nqp ::die(" No compile-time value for $ name" );
203
332
}
204
333
}
334
+
335
+ method poison_lowering () {
336
+ for @ ! block_var_stack {
337
+ $ _ . poison_lowering();
338
+ }
339
+ }
205
340
}
0 commit comments