This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Allow sub():method{CONSTANT} to be inlined
[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 101;
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 push @tests, {
228   nickname    => 'sub () { my $x; state sub z { $x } $outer }',
229   generator   => sub {
230     my $outer = 43;
231     sub () { my $x; state sub z { $x } $outer }
232   },
233   retval      => 43,
234   same_retval => 0,
235   inlinable   => 0,
236   deprecated  => 0,
237   method      => 0,
238 };
239
240 push @tests, {
241   nickname    => 'closure after \(my $x=1)',
242   generator   => sub {
243     $y = \(my $x = 1);
244     my $ret = sub () { $x };
245     $$y += 7;
246     $ret;
247   },
248   retval      => 8,
249   same_retval => 0,
250   inlinable   => 0,
251   deprecated  => 0,
252   method      => 0,
253 };
254
255 push @tests, {
256   nickname    => 'sub:method with simple lexical',
257   generator   => sub { my $y; sub():method{$y} },
258   retval      => undef,
259   same_retval => 0,
260   inlinable   => 1,
261   deprecated  => 0,
262   method      => 1,
263 };
264 push @tests, {
265   nickname    => 'sub:method with constant',
266   generator   => sub { sub():method{3} },
267   retval      => 3,
268   same_retval => 0,
269   inlinable   => 1,
270   deprecated  => 0,
271   method      => 1,
272 };
273 push @tests, {
274   nickname    => 'my sub:method with constant',
275   generator   => sub { my sub x ():method{3} \&x },
276   retval      => 3,
277   same_retval => 0,
278   inlinable   => 1,
279   deprecated  => 0,
280   method      => 1,
281 };
282
283
284 use feature 'refaliasing';
285 no warnings 'experimental::refaliasing';
286 for \%_ (@tests) {
287     my $nickname = $_{nickname};
288     my $w;
289     local $SIG{__WARN__} = sub { $w = shift };
290     my $sub = &{$_{generator}};
291     if (exists $_{deprecated}) {
292         if ($_{deprecated}) {
293             like $w, qr/^Constants from lexical variables potentially (?x:
294                        )modified elsewhere are deprecated at /,
295                 "$nickname is deprecated";
296         }
297         else {
298             is $w, undef, "$nickname is not deprecated";
299         }
300     }
301     if (exists $_{retval}) {
302         is &$sub, $_{retval}, "retval of $nickname";
303     }
304     if (exists $_{same_retval}) {
305         my $same = $_{same_retval} ? "same" : "different";
306         &{$_{same_retval} ? \&is : \&isnt}(
307             \scalar &$sub(), \scalar &$sub(),
308             "$nickname gives $same retval each call"
309         );
310     }
311     if (exists $_{inlinable}) {
312         local *temp_inlinability_test = $sub;
313         $w = undef;
314         use warnings 'redefine';
315         *temp_inlinability_test = sub (){};
316         my $S = $_{inlinable} ? "Constant s" : "S";
317         my $not = " not" x! $_{inlinable};
318         like $w, qr/^${S}ubroutine .* redefined at /,
319                 "$nickname is$not inlinable";
320     }
321     if (exists $_{method}) {
322         local *time = $sub;
323         $w = undef;
324         use warnings 'ambiguous';
325         eval "()=time";
326         if ($_{method}) {
327             is $w, undef, "$nickname has :method attribute";
328         }
329         else {
330             like $w, qr/^Ambiguous call resolved as CORE::time\(\), (?x:
331                         )qualify as such or use & at /,
332                 "$nickname has no :method attribute";
333         }
334     }
335
336     &{$_{finally} or next}
337 }