1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2018-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(beam_ssa_SUITE).
21
22-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
23	 init_per_group/2,end_per_group/2,
24         calls/1,tuple_matching/1,recv/1,maps/1,
25         cover_ssa_dead/1,combine_sw/1,share_opt/1,
26         beam_ssa_dead_crash/1,stack_init/1,grab_bag/1]).
27
28suite() -> [{ct_hooks,[ts_install_cth]}].
29
30all() ->
31    [{group,p}].
32
33groups() ->
34    [{p,test_lib:parallel(),
35      [tuple_matching,
36       calls,
37       recv,
38       maps,
39       cover_ssa_dead,
40       combine_sw,
41       share_opt,
42       beam_ssa_dead_crash,
43       stack_init,
44       grab_bag
45      ]}].
46
47init_per_suite(Config) ->
48    test_lib:recompile(?MODULE),
49    Config.
50
51end_per_suite(_Config) ->
52    ok.
53
54init_per_group(_GroupName, Config) ->
55    Config.
56
57end_per_group(_GroupName, Config) ->
58    Config.
59
60calls(Config) ->
61    Ret = {return,value,Config},
62    Ret = fun_call(fun(42) -> ok end, Ret),
63    Ret = apply_fun(fun(a, b) -> ok end, [a,b], Ret),
64    Ret = apply_mfa(test_lib, id, [anything], Ret),
65    {'EXIT',{badarg,_}} = (catch call_error()),
66    {'EXIT',{badarg,_}} = (catch call_error(42)),
67    5 = start_it([erlang,length,1,2,3,4,5]),
68    ok.
69
70fun_call(Fun, X0) ->
71    X = id(X0),
72    Fun(42),
73    X.
74
75apply_fun(Fun, Args, X0) ->
76    X = id(X0),
77    apply(Fun, Args),
78    X.
79
80apply_mfa(Mod, Name, Args, X0) ->
81    X = id(X0),
82    apply(Mod, Name, Args),
83    X.
84
85call_error() ->
86    error(badarg),
87    ok.
88
89call_error(I) ->
90    <<I:(-8)>>,
91    ok.
92
93start_it([_|_]=MFA) ->
94    case MFA of
95	[M,F|Args] -> M:F(Args)
96    end.
97
98tuple_matching(_Config) ->
99    do_tuple_matching({tag,42}),
100
101    true = is_two_tuple({a,b}),
102    false = is_two_tuple({a,b,c}),
103    false = is_two_tuple(atom),
104
105    ok.
106
107do_tuple_matching(Arg) ->
108    Res = do_tuple_matching_1(Arg),
109    Res = do_tuple_matching_2(Arg),
110    Res = do_tuple_matching_3(Arg),
111    Res.
112
113do_tuple_matching_1({tag,V}) ->
114    {ok,V}.
115
116do_tuple_matching_2(Tuple) when is_tuple(Tuple) ->
117    Size = tuple_size(Tuple),
118    if
119        Size =:= 2 ->
120            {ok,element(2, Tuple)}
121    end.
122
123do_tuple_matching_3(Tuple) when is_tuple(Tuple) ->
124    Size = tuple_size(Tuple),
125    if
126        Size =:= 2 ->
127            2 = id(Size),
128            {ok,element(2, Tuple)}
129    end.
130
131is_two_tuple(Arg) ->
132    case is_tuple(Arg) of
133        false -> false;
134        true -> tuple_size(Arg) == 2
135    end.
136
137-record(reporter_state, {res,run_config}).
138-record(run_config, {report_interval=0}).
139
140recv(_Config) ->
141    Parent = self(),
142
143    %% Test sync_wait_mon/2.
144    Succ = fun() -> Parent ! {ack,self(),{result,42}} end,
145    {result,42} = sync_wait_mon(spawn_monitor(Succ), infinity),
146
147    Down = fun() -> exit(down) end,
148    {error,down} = sync_wait_mon(spawn_monitor(Down), infinity),
149
150    Exit = fun() ->
151                   Self = self(),
152                   spawn(fun() -> exit(Self, kill_me) end),
153                   receive _ -> ok end
154           end,
155    {error,kill_me} = sync_wait_mon(spawn_monitor(Exit), infinity),
156
157    Timeout = fun() -> receive _ -> ok end end,
158    {error,timeout} = sync_wait_mon(spawn_monitor(Timeout), 0),
159
160    %% Test reporter_loop/1.
161    {a,Parent} = reporter_loop(#reporter_state{res={a,Parent},
162                                               run_config=#run_config{}}),
163
164    %% Test bad_sink/0.
165    bad_sink(),
166
167    %% Test tricky_recv_1/0.
168    self() ! 1,
169    a = tricky_recv_1(),
170    self() ! 2,
171    b = tricky_recv_1(),
172
173    %% Test tricky_recv_2/0.
174    self() ! 1,
175    {1,yes} = tricky_recv_2(),
176    self() ! 2,
177    {2,maybe} = tricky_recv_2(),
178
179    %% Test 'receive after infinity' in try/catch.
180    Pid = spawn(fun recv_after_inf_in_try/0),
181    exit(Pid, done),
182
183    %% Test tricky_recv_3().
184    self() ! {{self(),r0},{1,42,"name"}},
185    {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_3(),
186    self() ! {{self(),r1},{2,99,<<"data">>}},
187    {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_3(),
188
189    %% Test tricky_recv_4().
190    self() ! {[self(),r0],{1,42,"name"}},
191    {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_4(),
192    self() ! {[self(),r1],{2,99,<<"data">>}},
193    {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_4(),
194
195    %% Test tricky_recv_5/0.
196    self() ! 1,
197    a = tricky_recv_5(),
198    self() ! 2,
199    b = tricky_recv_5(),
200
201    %% Test tricky_recv_5a/0.
202    self() ! 1,
203    a = tricky_recv_5a(),
204    self() ! 2,
205    b = tricky_recv_5a(),
206    self() ! any,
207    b = tricky_recv_5a(),
208
209    %% tricky_recv_6/0 is a compile-time error.
210    tricky_recv_6(),
211
212    ok.
213
214sync_wait_mon({Pid, Ref}, Timeout) ->
215    receive
216	{ack,Pid,Return} ->
217	    erlang:demonitor(Ref, [flush]),
218	    Return;
219	{'DOWN',Ref,_Type,Pid,Reason} ->
220	    {error,Reason};
221	{'EXIT',Pid,Reason} ->
222	    erlang:demonitor(Ref, [flush]),
223	    {error,Reason}
224    after Timeout ->
225            erlang:demonitor(Ref, [flush]),
226            exit(Pid, kill),
227            {error,timeout}
228    end.
229
230reporter_loop(State) ->
231    RC = State#reporter_state.run_config,
232    receive after RC#run_config.report_interval ->
233                    State#reporter_state.res
234    end.
235
236bad_sink() ->
237    {ok,Pid} = my_spawn(self()),
238    %% The get_tuple_element instruction for the matching
239    %% above was sinked into the receive loop. That will
240    %% not work (and would be bad for performance if it
241    %% would work).
242    receive
243        {ok,Pid} ->
244            ok;
245        error ->
246            exit(failed)
247    end,
248    exit(Pid, kill).
249
250my_spawn(Parent) ->
251    Pid = spawn(fun() ->
252                        Parent ! {ok,self()},
253                        receive _ -> ok end
254                end),
255    {ok,Pid}.
256
257tricky_recv_1() ->
258    receive
259        X=1 ->
260            id(42),
261            a;
262        X=2 ->
263            b
264    end,
265    case X of
266        1 -> a;
267        2 -> b
268    end.
269
270tricky_recv_2() ->
271    receive
272        X=1 ->
273            Y = case id(X) of
274                    1 -> yes;
275                    _ -> no
276                end,
277            a;
278        X=2 ->
279            Y = maybe,
280            b
281    end,
282    {X,Y}.
283
284recv_after_inf_in_try() ->
285    try
286        %% Used to crash beam_kernel_to_ssa.
287        receive after infinity -> ok end
288    catch
289	_A:_B ->
290	    receive after infinity -> ok end
291    end.
292
293tricky_recv_3() ->
294    {Pid, R, Request} =
295	receive
296	    {{Pid0,R0}, {1, Proto0, Name0}} ->
297		{Pid0, R0,
298		 [<<1:32, 1:8, Proto0:8>>,Name0,0]};
299	    {{Pid1,R1}, {2, Proto1, Data1}}  ->
300		{Pid1, R1,
301		 <<1:32, 2:8, Proto1:8, Data1/binary>>}
302	end,
303    id({Pid,R,Request}).
304
305tricky_recv_4() ->
306    {Pid, R, Request} =
307	receive
308	    {[Pid0,R0], {1, Proto0, Name0}} ->
309		{Pid0, R0,
310		 [<<1:32, 1:8, Proto0:8>>,Name0,0]};
311	    {[Pid1,R1], {2, Proto1, Data1}}  ->
312		{Pid1, R1,
313		 <<1:32, 2:8, Proto1:8, Data1/binary>>}
314	end,
315    id({Pid,R,Request}).
316
317%% beam_ssa_pre_codegen would accidentally create phi nodes on critical edges
318%% when fixing up receives; the call to id/2 can either succeed or land in the
319%% catch block, and we added a phi node to its immediate successor.
320tricky_recv_5() ->
321    try
322        receive
323            X=1 ->
324                id(42),
325                a;
326            X=2 ->
327                b
328        end,
329        case X of
330            1 -> a;
331            2 -> b
332        end
333    catch
334        _:_ -> c
335    end.
336
337%% beam_ssa_pre_codegen would find the wrong exit block when fixing up
338%% receives.
339tricky_recv_5a() ->
340    try
341        receive
342            X=1 ->
343                id(42),
344                a;
345            X=_ ->
346                b
347        end,
348        %% The following is the code in the common exit block.
349        if X =:= 1 -> a;
350           true -> b
351        end
352    catch
353        %% But this code with the landingpad instruction was found,
354        %% because it happened to occur before the true exit block
355        %% in the reverse post order.
356        _:_ -> c
357    end.
358
359
360%% When fixing tricky_recv_5, we introduced a compiler crash when the common
361%% exit block was ?BADARG_BLOCK and floats were in the picture.
362tricky_recv_6() ->
363    RefA = make_ref(),
364    RefB = make_ref(),
365    receive
366        {RefA, Number} -> Number + 1.0;
367        {RefB, Number} -> Number + 2.0
368    after 0 ->
369        ok
370    end.
371
372maps(_Config) ->
373    {'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)),
374    ok.
375
376maps_1(K) ->
377    _ = id(42),
378    #{K:=V} = #{},
379    V.
380
381-record(wx_ref, {type=any_type,ref=any_ref}).
382
383cover_ssa_dead(_Config) ->
384    str = format_str(str, escapable, [], true),
385    [iolist,str] = format_str(str, escapable, iolist, true),
386    bad = format_str(str, not_escapable, [], true),
387    bad = format_str(str, not_escapable, iolist, true),
388    bad = format_str(str, escapable, [], false),
389    bad = format_str(str, escapable, [], bad),
390
391    DefWxRef = #wx_ref{},
392    {DefWxRef,77,9999,[]} = contains(#wx_ref{}, 77, 9999),
393    {DefWxRef,77.0,9999,[]} = contains(#wx_ref{}, 77.0, 9999),
394    {DefWxRef,77,9999.0,[]} = contains(#wx_ref{}, 77, 9999.0),
395    {DefWxRef,77.0,9999.0,[]} = contains(#wx_ref{}, 77.0, 9999.0),
396    {any_type,any_ref,42,43,[option]} = contains(#wx_ref{}, {42,43}, [option]),
397    {any_type,any_ref,42,43,[]} = contains(#wx_ref{}, {42,43}, []),
398    {any_type,any_ref,42.0,43,[]} = contains(#wx_ref{}, {42.0,43}, []),
399    {any_type,any_ref,42,43.0,[]} = contains(#wx_ref{}, {42,43.0}, []),
400    {any_type,any_ref,42.0,43.0,[]} = contains(#wx_ref{}, {42.0,43.0}, []),
401
402    nope = conv_alub(false, '=:='),
403    ok = conv_alub(true, '=:='),
404    ok = conv_alub(true, none),
405    error = conv_alub(false, none),
406
407    {false,false} = eval_alu(false, false, false),
408    {true,false}  = eval_alu(false, false, true),
409    {false,true}  = eval_alu(false, true, false),
410    {false,false} = eval_alu(false, true, true),
411    {false,true}  = eval_alu(true, false, false),
412    {false,false} = eval_alu(true, false, true),
413    {true,true}   = eval_alu(true, true, false),
414    {false,true}  = eval_alu(true, true, true),
415
416    100.0 = percentage(1.0, 0.0),
417    100.0 = percentage(1, 0),
418    0.0 = percentage(0, 0),
419    0.0 = percentage(0.0, 0.0),
420    40.0 = percentage(4.0, 10.0),
421    60.0 = percentage(6, 10),
422
423    %% Cover '=:=', followed by '=/='.
424    false = 'cover__=:=__=/='(41),
425    true = 'cover__=:=__=/='(42),
426    false = 'cover__=:=__=/='(43),
427
428    %% Cover '<', followed by '=/='.
429    true = 'cover__<__=/='(41),
430    false = 'cover__<__=/='(42),
431    false = 'cover__<__=/='(43),
432
433    %% Cover '=<', followed by '=/='.
434    true = 'cover__=<__=/='(41),
435    true = 'cover__=<__=/='(42),
436    false = 'cover__=<__=/='(43),
437
438    %% Cover '>=', followed by '=/='.
439    false = 'cover__>=__=/='(41),
440    true = 'cover__>=__=/='(42),
441    true = 'cover__>=__=/='(43),
442
443    %% Cover '>', followed by '=/='.
444    false = 'cover__>__=/='(41),
445    false = 'cover__>__=/='(42),
446    true = 'cover__>__=/='(43),
447
448    ok.
449
450'cover__=:=__=/='(X) when X =:= 42 -> X =/= 43;
451'cover__=:=__=/='(_) -> false.
452
453'cover__<__=/='(X) when X < 42 -> X =/= 42;
454'cover__<__=/='(_) -> false.
455
456'cover__=<__=/='(X) when X =< 42 -> X =/= 43;
457'cover__=<__=/='(_) -> false.
458
459'cover__>=__=/='(X) when X >= 42 -> X =/= 41;
460'cover__>=__=/='(_) -> false.
461
462'cover__>__=/='(X) when X > 42 -> X =/= 42;
463'cover__>__=/='(_) -> false.
464
465format_str(Str, FormatData, IoList, EscChars) ->
466    Escapable = FormatData =:= escapable,
467    case id(Str) of
468        IoStr when Escapable, EscChars, IoList == [] ->
469            id(IoStr);
470        IoStr when Escapable, EscChars ->
471            [IoList,id(IoStr)];
472        _ ->
473            bad
474    end.
475
476contains(This, X, Y) when is_record(This, wx_ref), is_number(X), is_number(Y) ->
477    {This,X,Y,[]};
478contains(#wx_ref{type=ThisT,ref=ThisRef}, {CX,CY}, Options)
479  when is_number(CX), is_number(CY), is_list(Options) ->
480    {ThisT,ThisRef,CX,CY,Options}.
481
482conv_alub(HasDst, CmpOp) ->
483    case (not HasDst) andalso CmpOp =/= none of
484        true -> nope;
485        false ->
486            case HasDst of
487                false -> error;
488                true -> ok
489            end
490    end.
491
492eval_alu(Sign1, Sign2, N) ->
493    V = (Sign1 andalso Sign2 andalso (not N))
494        or ((not Sign1) andalso (not Sign2) andalso N),
495    C = (Sign1 andalso Sign2)
496          or ((not N) andalso (Sign1 orelse Sign2)),
497    {V,C}.
498
499percentage(Divident, Divisor) ->
500    if Divisor == 0 andalso Divident /= 0 ->
501            100.0;
502       Divisor == 0 ->
503            0.0;
504       true ->
505            Divident / Divisor * 100
506    end.
507
508combine_sw(_Config) ->
509    [a] = do_comb_sw_1(a),
510    [b,b] = do_comb_sw_1(b),
511    [c] = do_comb_sw_1(c),
512    [c] = do_comb_sw_1(c),
513    [] = do_comb_sw_1(z),
514
515    [a] = do_comb_sw_2(a),
516    [b2,b1] = do_comb_sw_2(b),
517    [c] = do_comb_sw_2(c),
518    [c] = do_comb_sw_2(c),
519    [] = do_comb_sw_2(z),
520
521    ok.
522
523do_comb_sw_1(X) ->
524    put(?MODULE, []),
525    if
526        X == a; X == b ->
527            put(?MODULE, [X|get(?MODULE)]);
528        true ->
529            ok
530    end,
531    if
532        X == b; X == c ->
533            put(?MODULE, [X|get(?MODULE)]);
534        true ->
535            ok
536    end,
537    erase(?MODULE).
538
539do_comb_sw_2(X) ->
540    put(?MODULE, []),
541    case X of
542        a ->
543            put(?MODULE, [a|get(?MODULE)]);
544        b ->
545            put(?MODULE, [b1|get(?MODULE)]);
546        _ ->
547            ok
548    end,
549    case X of
550        b ->
551            put(?MODULE, [b2|get(?MODULE)]);
552        c ->
553            put(?MODULE, [c|get(?MODULE)]);
554        _ ->
555            ok
556    end,
557    erase(?MODULE).
558
559share_opt(_Config) ->
560    ok = do_share_opt_1(0),
561    ok = do_share_opt_2(),
562    ok.
563
564do_share_opt_1(A) ->
565    %% The compiler would be stuck in an infinite loop in beam_ssa_share.
566    case A of
567        0 -> a;
568        1 -> b;
569        2 -> c
570    end,
571    receive after 1 -> ok end.
572
573do_share_opt_2() ->
574    ok = sopt_2({[pointtopoint], [{dstaddr,any}]}, ok),
575    ok = sopt_2({[broadcast], [{broadaddr,any}]}, ok),
576    ok = sopt_2({[], []}, ok),
577    ok.
578
579sopt_2({Flags, Opts}, ok) ->
580    Broadcast = lists:member(broadcast, Flags),
581    P2P = lists:member(pointtopoint, Flags),
582    case Opts of
583        %% The following two clauses would be combined to one, silently
584        %% discarding the guard test of the P2P variable.
585        [{broadaddr,_}|Os] when Broadcast ->
586            sopt_2({Flags, Os}, ok);
587        [{dstaddr,_}|Os] when P2P ->
588            sopt_2({Flags, Os}, ok);
589        [] ->
590            ok
591    end.
592
593beam_ssa_dead_crash(_Config) ->
594    not_A_B = do_beam_ssa_dead_crash(id(false), id(true)),
595    not_A_not_B = do_beam_ssa_dead_crash(false, false),
596    neither = do_beam_ssa_dead_crash(true, false),
597    neither = do_beam_ssa_dead_crash(true, true),
598    ok.
599
600do_beam_ssa_dead_crash(A, B) ->
601    %% beam_ssa_dead attempts to shortcut branches that branch other
602    %% branches. When a two-way branch is encountered, beam_ssa_dead
603    %% will simulate execution along both paths, in the hope that both
604    %% paths happens to end up in the same place.
605    %%
606    %% During the simulated execution of this function, the boolean
607    %% varible for a `br` instruction would be replaced with the
608    %% literal atom `nil`, which is not allowed, and would crash the
609    %% compiler. In practice, during the actual execution, control
610    %% would never be transferred to that `br` instruction when the
611    %% variable in question had the value `nil`.
612    %%
613    %% beam_ssa_dead has been updated to immediately abort the search
614    %% along the current path if there is an attempt to substitute a
615    %% non-boolean value into a `br` instruction.
616
617    case
618        case not A of
619            false ->
620                false;
621            true ->
622                B
623        end
624    of
625        V
626            when
627                V /= nil
628                andalso
629                V /= false ->
630            not_A_B;
631        _ ->
632            case
633                case not A of
634                    false ->
635                        false;
636                    true ->
637                        not B
638                end
639            of
640                true ->
641                    not_A_not_B;
642                false ->
643                    neither
644            end
645    end.
646
647stack_init(_Config) ->
648    6 = stack_init(a, #{a => [1,2,3]}),
649    0 = stack_init(missing, #{}),
650    ok.
651
652stack_init(Key, Map) ->
653    %% beam_ssa_codegen would wrongly assume that y(0) would always be
654    %% initialized by the `get_map_elements` instruction that follows, and
655    %% would set up the stack frame using an `allocate` instruction and
656    %% would not generate an `init` instruction to initialize y(0).
657    Res = case Map of
658              #{Key := Elements} ->
659                  %% Elements will be assigned to y(0) if the key Key exists.
660                  lists:foldl(fun(El, Acc) ->
661                                      Acc + El
662                              end, 0, Elements);
663              #{} ->
664                  %% y(0) will be left uninitialized when the key is not
665                  %% present in the map.
666                  0
667          end,
668    %% y(0) would be uninitialized here if the key was not present in the map
669    %% (if the second clause was executed).
670    id(Res).
671
672grab_bag(_Config) ->
673    {'EXIT',_} = (catch grab_bag_3()),
674    ok.
675
676grab_bag_3() ->
677    case
678        fun (V0)
679              when
680                  %% The only thing left after optimizations would be
681                  %% a bs_add instruction not followed by succeeded,
682                  %% which would crash beam_ssa_codegen because there
683                  %% was no failure label available.
684                  binary_part(<<>>,
685                              <<V0:V0/unit:196>>) ->
686                []
687        end
688    of
689        <<>> ->
690            []
691    end.
692
693
694%% The identity function.
695id(I) -> I.
696