This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Make sub () { 0; 3 } inlinable once more
[perl5.git] / t / op / const-optree.t
1 #!perl
2
3 # Test the various op trees that turn sub () { ... } into a constant, and
4 # some variants that don’t.
5
6 BEGIN {
7     chdir 't';
8     require './test.pl';
9     @INC = '../lib';
10 }
11 plan 141;
12
13 # @tests is an array of hash refs, each of which can have various keys:
14 #
15 #   nickname    - name of the sub to use in test names
16 #   generator   - a sub returning a code ref to test
17 #   finally     - sub to run after the tests
18 #
19 # Each of the following gives expected test results.  If the key is
20 # omitted, the test is skipped:
21 #
22 #   retval      - the returned code ref’s return value
23 #   same_retval - whether the same scalar is returned each time
24 #   inlinable   - whether the sub is inlinable
25 #   deprecated  - whether the sub returning a code ref will emit a depreca-
26 #                 tion warning when called
27 #   method      - whether the sub has the :method attribute
28
29 # [perl #63540] Don’t treat sub { if(){.....}; "constant" } as a constant
30 sub blonk { ++$blonk_was_called }
31 push @tests, {
32   nickname    => 'sub with null+kids (if-block), then constant',
33   generator   => sub {
34     # This used to turn into a constant with the value of $x
35     my $x = 7;
36     sub() { if($x){ () = "tralala"; blonk() }; 0 }
37   },
38   retval      => 0,
39   same_retval => 0,
40   inlinable   => 0,
41   deprecated  => 0,
42   method      => 0,
43   finally     => sub { ok($blonk_was_called, 'RT #63540'); },
44 };
45
46 # [perl #79908]
47 push @tests, {
48   nickname    => 'sub with simple lexical modified elsewhere',
49   generator   => sub { my $x = 5; my $ret = sub(){$x}; $x = 7; $ret },
50   retval      => 5, # change to 7 when the deprecation cycle is over
51   same_retval => 0,
52   inlinable   => 1,
53   deprecated  => 1,
54   method      => 0,
55 };
56
57 push @tests, {
58   nickname    => 'sub with simple lexical unmodified elsewhere',
59   generator   => sub { my $x = 5; sub(){$x} },
60   retval      => 5,
61   same_retval => 0,
62   inlinable   => 1,
63   deprecated  => 0,
64   method      => 0,
65 };
66
67 push @tests, {
68   nickname    => 'return $variable modified elsewhere',
69   generator   => sub { my $x=5; my $ret = sub(){return $x}; $x = 7; $ret },
70   retval      => 7,
71   same_retval => 0,
72   inlinable   => 0,
73   deprecated  => 0,
74   method      => 0,
75 };
76
77 push @tests, {
78   nickname    => 'return $variable unmodified elsewhere',
79   generator   => sub { my $x = 5; sub(){return $x} },
80   retval      => 5,
81   same_retval => 0,
82   inlinable   => 0,
83   deprecated  => 0,
84   method      => 0,
85 };
86
87 push @tests, {
88   nickname    => 'sub () { 0; $x } with $x modified elsewhere',
89   generator   => sub { my $x = 5; my $ret = sub(){0;$x}; $x = 8; $ret },
90   retval      => 8,
91   same_retval => 0,
92   inlinable   => 0,
93   deprecated  => 0,
94   method      => 0,
95 };
96
97 push @tests, {
98   nickname    => 'sub () { 0; $x } with $x unmodified elsewhere',
99   generator   => sub { my $x = 5; my $y = $x; sub(){0;$x} },
100   retval      => 5,
101   same_retval => 0,
102   inlinable   => 1,
103   deprecated  => 0,
104   method      => 0,
105 };
106
107 # Explicit return after optimised statement, not at end of sub
108 push @tests, {
109   nickname    => 'sub () { 0; return $x; ... }',
110   generator   => sub { my $x = 5; sub () { 0; return $x; ... } },
111   retval      => 5,
112   same_retval => 0,
113   inlinable   => 0,
114   deprecated  => 0,
115   method      => 0,
116 };
117
118 # Explicit return after optimised statement, at end of sub [perl #123092]
119 push @tests, {
120   nickname    => 'sub () { 0; return $x }',
121   generator   => sub { my $x = 5; sub () { 0; return $x } },
122   retval      => 5,
123   same_retval => 0,
124   inlinable   => 0,
125   deprecated  => 0,
126   method      => 0,
127 };
128
129 # Multiple closure tests
130 push @tests, {
131   nickname    => 'simple lexical after another closure and no lvalue',
132   generator   => sub {
133     my $x = 5;
134     # This closure prevents inlining, though theoretically it shouldn’t
135     # have to.  If you change the behaviour, just change the test.  This
136     # fails the refcount check in op.c:op_const_sv, which is necessary for
137     # the sake of \(my $x = 1) (tested below).
138     my $sub1 = sub () { () = $x };
139     sub () { $x };
140   },
141   retval      => 5,
142   same_retval => 0,
143   inlinable   => 0,
144   deprecated  => 0,
145   method      => 0,
146 };
147 push @tests, {
148   nickname    => 'simple lexical before another closure and no lvalue',
149   generator   => sub {
150     my $x = 5;
151     my $ret = sub () { $x };
152     # This does not prevent inlining and never has.
153     my $sub1 = sub () { () = $x };
154     $ret;
155   },
156   retval      => 5,
157   same_retval => 0,
158   inlinable   => 1,
159   deprecated  => 0,
160   method      => 0,
161 };
162 push @tests, {
163   nickname    => 'simple lexical after an lvalue closure',
164   generator   => sub {
165     my $x = 5;
166     # This has always prevented inlining
167     my $sub1 = sub () { $x++ };
168     sub () { $x };
169   },
170   retval      => 5,
171   same_retval => 0,
172   inlinable   => 0,
173   deprecated  => 0,
174   method      => 0,
175 };
176 push @tests, {
177   nickname    => 'simple lexical before an lvalue closure',
178   generator   => sub {
179     my $x = 5;
180     my $ret = sub () { $x };  # <-- simple lexical op tree
181     # Traditionally this has not prevented inlining, though it should.  But
182     # since $ret has a simple lexical op tree, we preserve backward-compat-
183     # ibility, but deprecate it.
184     my $sub1 = sub () { $x++ };
185     $ret;
186   },
187   retval      => 5,
188   same_retval => 0,
189   inlinable   => 1,
190   deprecated  => 1,
191   method      => 0,
192 };
193 push @tests, {
194   nickname    => 'complex lexical op tree before an lvalue closure',
195   generator   => sub {
196     my $x = 5;
197     my $ret = sub () { 0; $x };  # <-- more than just a lexical
198     # This used not to prevent inlining, though it should, and now does.
199     my $sub1 = sub () { $x++ };
200     $ret;
201   },
202   retval      => 5,
203   same_retval => 0,
204   inlinable   => 0,
205   deprecated  => 0,
206   method      => 0,
207 };
208 push @tests, {
209   nickname    => 'complex lexical op tree before a nested lvalue closure',
210   generator   => sub {
211     my $x = 5;
212     my $ret = sub () { 0; $x };  # <-- more than just a lexical
213     # This used not to prevent inlining, though it should, and now does.
214     my $sub1 = sub () { sub () { $x++ } }; # nested
215     $ret;
216   },
217   retval      => 5,
218   same_retval => 0,
219   inlinable   => 0,
220   deprecated  => 0,
221   method      => 0,
222 };
223
224 use feature 'state', 'lexical_subs';
225 no warnings 'experimental::lexical_subs';
226
227 # Constant constants
228 push @tests, {
229   nickname    => 'sub with constant',
230   generator   => sub { sub () { 8 } },
231   retval      => 8,
232   same_retval => 0,
233   inlinable   => 1,
234   deprecated  => 0,
235   method      => 0,
236 };
237 push @tests, {
238   nickname    => 'sub with constant and return',
239   generator   => sub { sub () { return 8 } },
240   retval      => 8,
241   same_retval => 0,
242   inlinable   => 0,
243   deprecated  => 0,
244   method      => 0,
245 };
246 push @tests, {
247   nickname    => 'sub with optimised statement and constant',
248   generator   => sub { sub () { 0; 8 } },
249   retval      => 8,
250   same_retval => 0,
251   inlinable   => 1,
252   deprecated  => 0,
253   method      => 0,
254 };
255 push @tests, {
256   nickname    => 'sub with optimised statement, constant and return',
257   generator   => sub { sub () { 0; return 8 } },
258   retval      => 8,
259   same_retval => 0,
260   inlinable   => 0,
261   deprecated  => 0,
262   method      => 0,
263 };
264 push @tests, {
265   nickname    => 'my sub with constant',
266   generator   => sub { my sub x () { 8 } \&x },
267   retval      => 8,
268   same_retval => 0,
269   inlinable   => 1,
270   deprecated  => 0,
271   method      => 0,
272 };
273 push @tests, {
274   nickname    => 'my sub with constant and return',
275   generator   => sub { my sub x () { return 8 } \&x },
276   retval      => 8,
277   same_retval => 0,
278   inlinable   => 0,
279   deprecated  => 0,
280   method      => 0,
281 };
282 push @tests, {
283   nickname    => 'my sub with optimised statement and constant',
284   generator   => sub { my sub x () { 0; 8 } \&x },
285   retval      => 8,
286   same_retval => 0,
287   inlinable   => 1,
288   deprecated  => 0,
289   method      => 0,
290 };
291 push @tests, {
292   nickname    => 'my sub with optimised statement, constant and return',
293   generator   => sub { my sub x () { 0; return 8 } \&x },
294   retval      => 8,
295   same_retval => 0,
296   inlinable   => 0,
297   deprecated  => 0,
298   method      => 0,
299 };
300
301 push @tests, {
302   nickname    => 'sub () { my $x; state sub z { $x } $outer }',
303   generator   => sub {
304     my $outer = 43;
305     sub () { my $x; state sub z { $x } $outer }
306   },
307   retval      => 43,
308   same_retval => 0,
309   inlinable   => 0,
310   deprecated  => 0,
311   method      => 0,
312 };
313
314 push @tests, {
315   nickname    => 'closure after \(my $x=1)',
316   generator   => sub {
317     $y = \(my $x = 1);
318     my $ret = sub () { $x };
319     $$y += 7;
320     $ret;
321   },
322   retval      => 8,
323   same_retval => 0,
324   inlinable   => 0,
325   deprecated  => 0,
326   method      => 0,
327 };
328
329 push @tests, {
330   nickname    => 'sub:method with simple lexical',
331   generator   => sub { my $y; sub():method{$y} },
332   retval      => undef,
333   same_retval => 0,
334   inlinable   => 1,
335   deprecated  => 0,
336   method      => 1,
337 };
338 push @tests, {
339   nickname    => 'sub:method with constant',
340   generator   => sub { sub():method{3} },
341   retval      => 3,
342   same_retval => 0,
343   inlinable   => 1,
344   deprecated  => 0,
345   method      => 1,
346 };
347 push @tests, {
348   nickname    => 'my sub:method with constant',
349   generator   => sub { my sub x ():method{3} \&x },
350   retval      => 3,
351   same_retval => 0,
352   inlinable   => 1,
353   deprecated  => 0,
354   method      => 1,
355 };
356
357
358 use feature 'refaliasing';
359 no warnings 'experimental::refaliasing';
360 for \%_ (@tests) {
361     my $nickname = $_{nickname};
362     my $w;
363     local $SIG{__WARN__} = sub { $w = shift };
364     my $sub = &{$_{generator}};
365     if (exists $_{deprecated}) {
366         if ($_{deprecated}) {
367             like $w, qr/^Constants from lexical variables potentially (?x:
368                        )modified elsewhere are deprecated at /,
369                 "$nickname is deprecated";
370         }
371         else {
372             is $w, undef, "$nickname is not deprecated";
373         }
374     }
375     if (exists $_{retval}) {
376         is &$sub, $_{retval}, "retval of $nickname";
377     }
378     if (exists $_{same_retval}) {
379         my $same = $_{same_retval} ? "same" : "different";
380         &{$_{same_retval} ? \&is : \&isnt}(
381             \scalar &$sub(), \scalar &$sub(),
382             "$nickname gives $same retval each call"
383         );
384     }
385     if (exists $_{inlinable}) {
386         local *temp_inlinability_test = $sub;
387         $w = undef;
388         use warnings 'redefine';
389         *temp_inlinability_test = sub (){};
390         my $S = $_{inlinable} ? "Constant s" : "S";
391         my $not = " not" x! $_{inlinable};
392         like $w, qr/^${S}ubroutine .* redefined at /,
393                 "$nickname is$not inlinable";
394     }
395     if (exists $_{method}) {
396         local *time = $sub;
397         $w = undef;
398         use warnings 'ambiguous';
399         eval "()=time";
400         if ($_{method}) {
401             is $w, undef, "$nickname has :method attribute";
402         }
403         else {
404             like $w, qr/^Ambiguous call resolved as CORE::time\(\), (?x:
405                         )qualify as such or use & at /,
406                 "$nickname has no :method attribute";
407         }
408     }
409
410     &{$_{finally} or next}
411 }