1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2000-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%%% Purpose : Tests inlining.
21
22-module(inline_SUITE).
23
24-include_lib("common_test/include/ct.hrl").
25
26-compile(export_all).
27-compile({inline,[badarg/2]}).
28
29%% Needed by test case `lists'.
30-compile(inline_list_funcs).
31
32suite() -> [{ct_hooks,[ts_install_cth]}].
33
34all() ->
35    [{group,p}].
36
37groups() ->
38    [{p,test_lib:parallel(),
39      [attribute,bsdecode,bsdes,barnes2,decode1,smith,fname,
40       itracer,pseudoknot,maps_inline_test,comma_splitter,lists,really_inlined,otp_7223,
41       coverage]}].
42
43init_per_suite(Config) ->
44    test_lib:recompile(?MODULE),
45    Config.
46
47end_per_suite(_Config) ->
48    ok.
49
50init_per_group(_GroupName, Config) ->
51    Config.
52
53end_per_group(_GroupName, Config) ->
54    Config.
55
56
57attribute(Config) when is_list(Config) ->
58    Name = "attribute",
59    Src = filename:join(proplists:get_value(data_dir, Config), Name),
60    Out = proplists:get_value(priv_dir,Config),
61
62    {ok,attribute=Mod} = compile:file(Src, [{outdir,Out},report,time]),
63    Outfile = filename:join(Out, Name++".beam"),
64    {ok,{Mod,[{locals,Locals}]}} = beam_lib:chunks(Outfile, [locals]),
65    io:format("locals: ~p\n", [Locals]),
66
67    %% The inliner should have removed all local functions.
68    [] = Locals,
69
70    ok.
71
72-define(comp(Name),
73	Name(Config) when is_list(Config) ->
74	       try_inline(Name, Config)).
75
76?comp(bsdecode).
77?comp(bsdes).
78?comp(barnes2).
79?comp(decode1).
80?comp(smith).
81?comp(itracer).
82?comp(pseudoknot).
83?comp(comma_splitter).
84?comp(fname).
85?comp(maps_inline_test).
86
87try_inline(Mod, Config) ->
88    Src = filename:join(proplists:get_value(data_dir, Config),
89			atom_to_list(Mod)),
90    Out = proplists:get_value(priv_dir,Config),
91
92    %% Normal compilation.
93    io:format("Compiling: ~s\n", [Src]),
94    {ok,Mod} = compile:file(Src, [{outdir,Out},report,
95                                  bin_opt_info,clint,ssalint]),
96
97    ct:timetrap({minutes,10}),
98    NormalResult = load_and_call(Out, Mod),
99
100    %% Inlining.
101    io:format("Compiling with old inliner: ~s\n", [Src]),
102    {ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info,
103                                  {inline,1000},clint,ssalint]),
104
105    %% Run inlined code.
106    ct:timetrap({minutes,10}),
107    OldInlinedResult = load_and_call(Out, Mod),
108
109    %% Compare results.
110    compare(NormalResult, OldInlinedResult),
111    NormalResult = OldInlinedResult,
112
113    %% Inlining.
114    io:format("Compiling with new inliner: ~s\n", [Src]),
115    {ok,Mod} = compile:file(Src, [{outdir,Out},report,
116					bin_opt_info,inline,clint,ssalint]),
117
118    %% Run inlined code.
119    ct:timetrap({minutes,10}),
120    InlinedResult = load_and_call(Out, Mod),
121
122    %% Compare results.
123    compare(NormalResult, InlinedResult),
124    NormalResult = InlinedResult,
125
126    %% Delete Beam file.
127    ok = file:delete(filename:join(Out, atom_to_list(Mod)++code:objfile_extension())),
128
129    %% Delete loaded module.
130    _ = code:purge(Mod),
131    _ = code:delete(Mod),
132    _ = code:purge(Mod),
133
134    ok.
135
136compare(Same, Same) -> ok;
137compare([Same|T1], [Same|T2]) ->
138    compare(T1, T2);
139compare([{X,Y,RGB1}|T1], [{X,Y,RGB2}|T2]) ->
140    io:format("X = ~p, Y = ~p, RGB normal = ~p, RGB inlined ~p\n", [X,Y,RGB1,RGB2]),
141    compare(T1, T2);
142compare([H1|_], [H2|_]) ->
143    io:format("Normal = ~p, Inlined = ~p\n", [H1,H2]),
144    ct:fail(different);
145compare([], []) -> ok.
146
147load_and_call(Out, Module) ->
148    io:format("Loading...\n",[]),
149    code:purge(Module),
150    LoadRc = code:load_abs(filename:join(Out, Module)),
151    {module,Module} = LoadRc,
152
153    io:format("Calling...\n",[]),
154    {Time,CallResult} = timer:tc(Module, Module, []),
155    io:format("Time: ~p\n", [Time]),
156    CallResult.
157
158%% Macros used by lists/1 below.
159
160-define(TestHighOrder_2(Name, Body, List),
161	begin
162	    put(?MODULE, []),
163	    (fun({Res,Res2}) ->
164		     {Res,Res2} = my_apply(lists, Name, [Body,List], [])
165	     end)(begin
166		      (fun(R) ->
167			       {R,get(?MODULE)}
168		       end)(lists:Name(Body, List))
169		  end)
170	 end).
171
172-define(TestHighOrder_3(Name, Body, Init, List),
173	begin
174	    put(?MODULE, []),
175	    (fun({Res,Res2}) ->
176		     {Res,Res2} = my_apply(lists, Name, [Body,Init,List], [])
177	     end)(begin
178		      (fun(R) ->
179			       {R,get(?MODULE)}
180		       end)(lists:Name(Body, Init, List))
181		  end)
182	 end).
183
184%% For each high order function in the lists module, verify
185%% that the inlined version produces the same result and is evaluated
186%% in the same order as the function in the lists module.
187%%
188%% Note: This module must be compiled with the inline_lists_funcs option.
189
190lists(Config) when is_list(Config) ->
191    List = lists:seq(1, 20),
192
193    %% lists:map/2
194    ?TestHighOrder_2(map,
195		     (fun(E) ->
196			      R = E band 16#ff,
197			      put(?MODULE, [E|get(?MODULE)]),
198			      R
199		      end), List),
200
201    %% lists:flatmap/2
202    ?TestHighOrder_2(flatmap,
203		     (fun(E) ->
204			      R = lists:duplicate(E, E),
205			      put(?MODULE, [E|get(?MODULE)]),
206			      R
207		      end), List),
208
209    %% lists:foreach/2
210    ?TestHighOrder_2(foreach,
211		     (fun(E) ->
212			      put(?MODULE, [E bor 7|get(?MODULE)])
213		      end), List),
214
215    %% lists:filter/2
216    ?TestHighOrder_2(filter,
217		     (fun(E) ->
218			      put(?MODULE, [E|get(?MODULE)]),
219			      (E bsr 1) band 1 =/= 0
220		      end), List),
221
222    %% lists:any/2
223    ?TestHighOrder_2(any,
224		     (fun(E) ->
225			      put(?MODULE, [E|get(?MODULE)]),
226			      false	  %Force it to go through all.
227		      end), List),
228
229    %% lists:all/2
230    ?TestHighOrder_2(all,
231		     (fun(E) ->
232			      put(?MODULE, [E|get(?MODULE)]),
233			      true	  %Force it to go through all.
234		      end), List),
235
236    %% lists:foldl/3
237    ?TestHighOrder_3(foldl,
238		     (fun(E, A) ->
239			      put(?MODULE, [E|get(?MODULE)]),
240			      A bxor E
241		      end), 0, List),
242
243    %% lists:foldr/3
244    ?TestHighOrder_3(foldr,
245		     (fun(E, A) ->
246			      put(?MODULE, [E|get(?MODULE)]),
247			      A bxor (bnot E)
248		      end), 0, List),
249
250    %% lists:mapfoldl/3
251    ?TestHighOrder_3(mapfoldl,
252		     (fun(E, A) ->
253			      put(?MODULE, [E|get(?MODULE)]),
254			      {bnot E,A bxor (bnot E)}
255		      end), 0, List),
256
257    %% lists:mapfoldr/3
258    ?TestHighOrder_3(mapfoldr,
259		     (fun(E, A) ->
260			      put(?MODULE, [E|get(?MODULE)]),
261			      {bnot E,A bxor (bnot E)}
262		      end), 0, List),
263
264    %% Cleanup.
265    erase(?MODULE),
266
267    {'EXIT',{function_clause,[{?MODULE,_,[_,not_a_list],_}|_]}} =
268        (catch lists:map(fun (X) -> X end, not_a_list)),
269    {'EXIT',{function_clause,[{?MODULE,_,[_,not_a_list],_}|_]}} =
270        (catch lists:flatmap(fun (X) -> X end, not_a_list)),
271    {'EXIT',{function_clause,[{?MODULE,_,[_,not_a_list],_}|_]}} =
272        (catch lists:foreach(fun (X) -> X end, not_a_list)),
273    {'EXIT',{function_clause,[{?MODULE,_,[_,not_a_list],_}|_]}} =
274        (catch lists:filter(fun (_) -> true end, not_a_list)),
275    {'EXIT',{function_clause,[{?MODULE,_,[_,not_a_list],_}|_]}} =
276        (catch lists:any(fun (_) -> false end, not_a_list)),
277    {'EXIT',{function_clause,[{?MODULE,_,[_,not_a_list],_}|_]}} =
278        (catch lists:all(fun (_) -> true end, not_a_list)),
279    {'EXIT',{function_clause,[{?MODULE,_,[_,acc,not_a_list],_}|_]}} =
280        (catch lists:foldl(fun (X, Acc) -> {X,Acc} end, acc, not_a_list)),
281    {'EXIT',{function_clause,[{?MODULE,_,[_,acc,not_a_list],_}|_]}} =
282        (catch lists:foldr(fun (X, Acc) -> {X,Acc} end, acc, not_a_list)),
283    {'EXIT',{function_clause,[{?MODULE,_,[_,acc,not_a_list],_}|_]}} =
284        (catch lists:mapfoldl(fun (X, Acc) -> {X,Acc} end, acc, not_a_list)),
285    {'EXIT',{function_clause,[{?MODULE,_,[_,acc,not_a_list],_}|_]}} =
286        (catch lists:mapfoldr(fun (X, Acc) -> {X,Acc} end, acc, not_a_list)),
287
288    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,[]],_}|_]}} =
289        (catch lists:map(not_a_function, [])),
290    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,[]],_}|_]}} =
291        (catch lists:flatmap(not_a_function, [])),
292    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,[]],_}|_]}} =
293        (catch lists:foreach(not_a_function, [])),
294    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,[]],_}|_]}} =
295        (catch lists:filter(not_a_function, [])),
296    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,[]],_}|_]}} =
297        (catch lists:any(not_a_function, [])),
298    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,[]],_}|_]}} =
299        (catch lists:all(not_a_function, [])),
300    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,acc,[]],_}|_]}} =
301        (catch lists:foldl(not_a_function, acc, [])),
302    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,acc,[]],_}|_]}} =
303        (catch lists:foldr(not_a_function, acc, [])),
304    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,acc,[]],_}|_]}} =
305        (catch lists:mapfoldl(not_a_function, acc, [])),
306    {'EXIT',{function_clause,[{?MODULE,_,[not_a_function,acc,[]],_}|_]}} =
307        (catch lists:mapfoldr(not_a_function, acc, [])),
308
309    ok.
310
311my_apply(M, F, A, Init) ->
312    put(?MODULE, Init),
313    Res = apply(M, F, A),
314    {Res,get(?MODULE)}.
315
316really_inlined(Config) when is_list(Config) ->
317    %% Make sure that badarg/2 really gets inlined.
318    {'EXIT',{badarg,[{?MODULE,fail_me_now,[],_}|_]}} =
319	(catch fail_me_now()),
320    ok.
321
322fail_me_now() ->
323    badarg(foo(bar), []).
324
325foo(_X) ->
326    badarg.
327
328%% Inlined.
329badarg(badarg, A) ->
330    erlang:error(badarg, A);
331badarg(Reply, _A) ->
332    Reply.
333
334otp_7223(Config) when is_list(Config) ->
335    {'EXIT', {{case_clause,{1}},_}} = (catch otp_7223_1(1)),
336    ok.
337
338-compile({inline,[{otp_7223_1,1}]}).
339otp_7223_1(X) ->
340    otp_7223_2(X).
341
342-compile({inline,[{otp_7223_2,1}]}).
343otp_7223_2({a}) ->
344    1.
345
346coverage(Config) when is_list(Config) ->
347    Mod = attribute,
348    Src = filename:join(proplists:get_value(data_dir, Config), Mod),
349    {ok,Mod,_} = compile:file(Src, [binary,report,{inline,0},
350                                    clint,ssalint]),
351    ok.
352