aboutsummaryrefslogtreecommitdiffstats
path: root/lib/compiler/src/sys_core_inline.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compiler/src/sys_core_inline.erl')
-rw-r--r--lib/compiler/src/sys_core_inline.erl212
1 files changed, 212 insertions, 0 deletions
diff --git a/lib/compiler/src/sys_core_inline.erl b/lib/compiler/src/sys_core_inline.erl
new file mode 100644
index 0000000000..c8d75b80c6
--- /dev/null
+++ b/lib/compiler/src/sys_core_inline.erl
@@ -0,0 +1,212 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%% Purpose : Function inlining optimisation for Core.
+
+%% This simple function inliner works in two stages:
+%%
+%% 1. First it extracts all the inlineable functions, either given
+%% explicitly or of light enough weight, and inlines them with
+%% themselves. This inlining only uses lighter functions to save
+%% recursion and a real code explosion.
+%%
+%% 2. Run through the rest of the functions inlining all calls to
+%% inlineable functions.
+%%
+%% The weight function is VERY simple, we count the number of nodes in
+%% the function body. We would like to remove non-exported,
+%% inlineable functions but this is not trivial as they may be
+%% (mutually) recursive.
+%%
+%% This module will catch many access functions and allow code to use
+%% extra functions for clarity which are then explicitly inlined for
+%% speed with a compile attribute. See the example below.
+%%
+%% It is not clear that inlining will give you very much.
+
+-module(sys_core_inline).
+
+%%-compile({inline,{match_fail_fun,0}}).
+
+-export([module/2]).
+
+-import(lists, [member/2,map/2,foldl/3,mapfoldl/3]).
+
+-include("core_parse.hrl").
+
+%% Inline status.
+-record(inline, {exports=[],thresh=0,inline=[]}).
+
+%% General function info.
+-record(fstat, {func :: atom(), %Function name
+ arity :: byte(), % arity
+ def, %Original definition
+ weight=0, %Weight
+ inline=false :: boolean(), %Inline func flag
+ modified=false :: boolean()}). %Mod flag
+
+%% Inlineable function info.
+-record(ifun, {func :: atom(), %Function name
+ arity :: byte(), % arity
+ vars, %Fun vars
+ body, % body
+ weight}). %Weight
+
+-spec module(#c_module{}, [_]) -> {'ok', #c_module{}}.
+
+module(#c_module{exports=Es,defs=Ds0}=Mod, Opts) ->
+ case inline_option(Opts) of
+ {Thresh,Fs} when is_integer(Thresh), Thresh > 0; Fs =/= [] ->
+ case proplists:get_bool(verbose, Opts) of
+ true ->
+ io:format("Old inliner: threshold=~p functions=~p\n",
+ [Thresh,Fs]);
+ false -> ok
+ end,
+ Ds1 = inline(Ds0, #inline{exports=Es,thresh=Thresh,inline=Fs}),
+ {ok,Mod#c_module{defs=Ds1}};
+ _Other -> {ok,Mod}
+ end.
+
+inline_option(Opts) ->
+ foldl(fun ({inline,{_,_}=Val}, {T,Fs}) ->
+ {T,[Val|Fs]};
+ ({inline,Val}, {T,Fs}) when is_list(Val) ->
+ {T,Val ++ Fs};
+ ({inline,Val}, {_,Fs}) when is_integer(Val) ->
+ {Val,Fs};
+ (_Opt, {_,_}=Def) -> Def
+ end, {0,[]}, Opts).
+
+%% inline([Func], Stat) -> [Func].
+%% Here we do all the work.
+
+inline(Fs0, St0) ->
+ %% Generate list of augmented functions.
+ Fs1 = map(fun ({#c_var{name={F,A}},#c_fun{body=B}}=Def) ->
+ Weight = cerl_trees:fold(fun weight_func/2, 0, B),
+ #fstat{func=F,arity=A,def=Def,weight=Weight}
+ end, Fs0),
+ %% Get inlineable functions, and inline them with themselves.
+ {Fs2,Is0} = mapfoldl(fun (Fst, Ifs) ->
+ case is_inlineable(Fst, St0#inline.thresh,
+ St0#inline.inline) of
+ true ->
+ {_,Ffun} = Fst#fstat.def,
+ If = #ifun{func=Fst#fstat.func,
+ arity=Fst#fstat.arity,
+ vars=Ffun#c_fun.vars,
+ body=Ffun#c_fun.body,
+ weight=Fst#fstat.weight},
+ {Fst#fstat{inline=true},[If|Ifs]};
+ false -> {Fst,Ifs}
+ end
+ end, [], Fs1),
+ Is1 = map(fun (#ifun{body=B}=If) ->
+ If#ifun{body=cerl_trees:map(match_fail_fun(), B)}
+ end, Is0),
+ Is2 = [inline_inline(If, Is1) || If <- Is1],
+ %% We would like to remove inlined, non-exported functions here,
+ %% but this can be difficult as they may be recursive.
+ %% Use fixed inline functions on all functions.
+ Fs = [inline_func(F, Is2) || F <- Fs2],
+ %% Regenerate module body.
+ [Def || #fstat{def=Def} <- Fs].
+
+%% is_inlineable(Fstat, Thresh, [Inline]) -> boolean().
+
+is_inlineable(#fstat{weight=W}, Thresh, _Ofs) when W =< Thresh -> true;
+is_inlineable(#fstat{func=F,arity=A}, _Thresh, Ofs) ->
+ member({F,A}, Ofs).
+
+%% inline_inline(Ifun, [Inline]) -> Ifun.
+%% Try to inline calls in an inlineable function. To save us from a
+%% to great code explosion we only inline functions "smaller" than
+%% ourselves.
+
+inline_inline(#ifun{body=B,weight=Iw}=If, Is) ->
+ Inline = fun (#c_apply{op=#c_var{name={F,A}},args=As}=Call) ->
+ case find_inl(F, A, Is) of
+ #ifun{vars=Vs,body=B2,weight=W} when W < Iw ->
+ #c_let{vars=Vs,
+ arg=core_lib:make_values(As),
+ body=kill_id_anns(B2)};
+ _Other -> Call
+ end;
+ (Core) -> Core
+ end,
+ If#ifun{body=cerl_trees:map(Inline, B)}.
+
+%% inline_func(Fstat, [Inline]) -> Fstat.
+%% Try to inline calls in a normal function. Here we inline anything
+%% in the inline list.
+
+inline_func(#fstat{def={Name,F0}}=Fstat, Is) ->
+ Inline = fun (#c_apply{op=#c_var{name={F,A}},args=As}=Call, Mod) ->
+ case find_inl(F, A, Is) of
+ #ifun{vars=Vs,body=B} ->
+ {#c_let{vars=Vs,
+ arg=core_lib:make_values(As),
+ body=kill_id_anns(B)},
+ true}; %Have modified
+ _Other -> {Call,Mod}
+ end;
+ (Core, Mod) -> {Core,Mod}
+ end,
+ {F1,Mod} = cerl_trees:mapfold(Inline, false, F0),
+ Fstat#fstat{def={Name,F1},modified=Mod}.
+
+weight_func(_Core, Acc) -> Acc + 1.
+
+%% match_fail_fun() -> fun/1.
+%% Return a function to use with map to fix inlineable functions
+%% function_clause match_fail (if they have one).
+
+match_fail_fun() ->
+ fun (#c_primop{name=#c_literal{val=match_fail},
+ args=[#c_tuple{es=[#c_literal{val=function_clause}|As]}]}=P) ->
+ Fail = #c_tuple{es=[#c_literal{val=case_clause},
+ #c_tuple{es=As}]},
+ P#c_primop{args=[Fail]};
+ (Other) -> Other
+ end.
+
+%% find_inl(Func, Arity, [Inline]) -> #ifun{} | no.
+
+find_inl(F, A, [#ifun{func=F,arity=A}=If|_]) -> If;
+find_inl(F, A, [_|Is]) -> find_inl(F, A, Is);
+find_inl(_, _, []) -> no.
+
+%% kill_id_anns(Body) -> Body'
+
+kill_id_anns(Body) ->
+ cerl_trees:map(fun(#c_fun{anno=A0}=CFun) ->
+ A = kill_id_anns_1(A0),
+ CFun#c_fun{anno=A};
+ (Expr) ->
+ %% Mark everything as compiler generated to suppress
+ %% bogus warnings.
+ A = [compiler_generated|core_lib:get_anno(Expr)],
+ core_lib:set_anno(Expr, A)
+ end, Body).
+
+kill_id_anns_1([{'id',_}|As]) ->
+ kill_id_anns_1(As);
+kill_id_anns_1([A|As]) ->
+ [A|kill_id_anns_1(As)];
+kill_id_anns_1([]) -> [].