%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(beam_trim).
-export([module/2]).
-import(lists, [reverse/1,reverse/2,splitwith/2,sort/1]).
-record(st,
{safe :: cerl_sets:set(beam_asm:label()), %Safe labels.
lbl :: beam_utils:code_index() %Code at each label.
}).
-spec module(beam_utils:module_code(), [compile:option()]) ->
{'ok',beam_utils:module_code()}.
module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
Fs = [function(F) || F <- Fs0],
{ok,{Mod,Exp,Attr,Fs,Lc}}.
function({function,Name,Arity,CLabel,Is0}) ->
try
St = #st{safe=safe_labels(Is0, []),lbl=beam_utils:index_labels(Is0)},
Is = trim(Is0, St, []),
{function,Name,Arity,CLabel,Is}
catch
Class:Error:Stack ->
io:fwrite("Function: ~w/~w\n", [Name,Arity]),
erlang:raise(Class, Error, Stack)
end.
trim([{kill,_}|_]=Is0, St, Acc) ->
{Kills0,Is1} = splitwith(fun({kill,_}) -> true;
(_) -> false
end, Is0),
Kills = sort(Kills0),
try
{FrameSize,Layout} = frame_layout(Is1, Kills, St),
Configs = trim_instructions(Layout),
try_remap(Configs, Is1, FrameSize)
of
{Is,TrimInstr} ->
trim(Is, St, reverse(TrimInstr)++Acc)
catch
not_possible ->
trim(Is1, St, reverse(Kills, Acc))
end;
trim([I|Is], St, Acc) ->
trim(Is, St, [I|Acc]);
trim([], _, Acc) ->
reverse(Acc).
%% trim_instructions([{kill,R}|{live,R}|{dead,R}]) -> {[Instruction],MapFun}
%% Figure out the sequence of moves and trim to use.
trim_instructions(Layout) ->
Cost = length([I || {kill,_}=I <- Layout]),
trim_instructions_1(Layout, 0, [], {Cost,[]}).
trim_instructions_1([{kill,{y,Trim0}}|Ks], Trim0, Moves, Config0) ->
Trim = Trim0 + 1,
Config = save_config(Ks, Trim, Moves, Config0),
trim_instructions_1(Ks, Trim, Moves, Config);
trim_instructions_1([{dead,{y,Trim0}}|Ks], Trim0, Moves, Config0) ->
Trim = Trim0 + 1,
Config = save_config(Ks, Trim, Moves, Config0),
trim_instructions_1(Ks, Trim, Moves, Config);
trim_instructions_1([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Config0) ->
case take_last_dead(Ks0) of
none ->
{_,ConfigList} = Config0,
ConfigList;
{Dst,Ks} ->
Trim = Trim0 + 1,
Moves = [{move,Src,Dst}|Moves0],
Config = save_config(Ks, Trim, Moves, Config0),
trim_instructions_1(Ks, Trim, Moves, Config)
end;
trim_instructions_1([], _, _, {_,ConfigList}) ->
ConfigList.
take_last_dead(L) ->
take_last_dead_1(reverse(L)).
take_last_dead_1([{kill,Reg}|Is]) ->
{Reg,reverse(Is)};
take_last_dead_1([{dead,Reg}|Is]) ->
{Reg,reverse(Is)};
take_last_dead_1(_) -> none.
save_config(Ks, Trim, Moves, {MaxCost,Acc}=Config) ->
case config_cost(Ks, Moves) of
Cost when Cost =< MaxCost ->
{MaxCost,[{Ks,Trim,Moves}|Acc]};
_Cost ->
Config
end.
config_cost(Ks, Moves) ->
%% We estimate that a {move,{y,_},{y,_}} instruction is roughly twice as
%% expensive as a {kill,{y,_}} instruction. A {trim,_} instruction is
%% roughly as expensive as a {kill,{y,_}} instruction.
config_cost_1(Ks, 1+2*length(Moves)).
config_cost_1([{kill,_}|Ks], Cost) ->
config_cost_1(Ks, Cost+1);
config_cost_1([_|Ks], Cost) ->
config_cost_1(Ks, Cost);
config_cost_1([], Cost) -> Cost.
expand_config({Layout,Trim,Moves}, FrameSize) ->
Kills = [Kill || {kill,_}=Kill <- Layout],
{Kills++reverse(Moves, [{trim,Trim,FrameSize-Trim}]),create_map(Trim, Moves)}.
create_map(Trim, []) ->
fun({y,Y}) when Y < Trim -> throw(not_possible);
({y,Y}) -> {y,Y-Trim};
({frame_size,N}) -> N - Trim;
(Any) -> Any
end;
create_map(Trim, Moves) ->
Map0 = [{Src,Dst-Trim} || {move,{y,Src},{y,Dst}} <- Moves],
Map = maps:from_list(Map0),
IllegalTargets = cerl_sets:from_list([Dst || {move,_,{y,Dst}} <- Moves]),
fun({y,Y0}) when Y0 < Trim ->
case Map of
#{Y0:=Y} -> {y,Y};
#{} -> throw(not_possible)
end;
({y,Y}) ->
case cerl_sets:is_element(Y, IllegalTargets) of
true -> throw(not_possible);
false -> {y,Y-Trim}
end;
({frame_size,N}) -> N - Trim;
(Any) -> Any
end.
try_remap([C|Cs], Is, FrameSize) ->
{TrimInstr,Map} = expand_config(C, FrameSize),
try
{remap(Is, Map, []),TrimInstr}
catch
throw:not_possible ->
try_remap(Cs, Is, FrameSize)
end;
try_remap([], _, _) -> throw(not_possible).
remap([{block,Bl0}|Is], Map, Acc) ->
Bl = remap_block(Bl0, Map, []),
remap(Is, Map, [{block,Bl}|Acc]);
remap([{call_fun,_}=I|Is], Map, Acc) ->
remap(Is, Map, [I|Acc]);
remap([{call,_,_}=I|Is], Map, Acc) ->
remap(Is, Map, [I|Acc]);
remap([{call_ext,_,_}=I|Is], Map, Acc) ->
remap(Is, Map, [I|Acc]);
remap([{apply,_}=I|Is], Map, Acc) ->
remap(Is, Map, [I|Acc]);
remap([{bif,Name,Fail,Ss,D}|Is], Map, Acc) ->
I = {bif,Name,Fail,[Map(S) || S <- Ss],Map(D)},
remap(Is, Map, [I|Acc]);
remap([{gc_bif,Name,Fail,Live,Ss,D}|Is], Map, Acc) ->
I = {gc_bif,Name,Fail,Live,[Map(S) || S <- Ss],Map(D)},
remap(Is, Map, [I|Acc]);
remap([{get_map_elements,Fail,M,{list,L0}}|Is], Map, Acc) ->
L = [Map(E) || E <- L0],
I = {get_map_elements,Fail,Map(M),{list,L}},
remap(Is, Map, [I|Acc]);
remap([{bs_init,Fail,Info,Live,Ss0,Dst0}|Is], Map, Acc) ->
Ss = [Map(Src) || Src <- Ss0],
Dst = Map(Dst0),
I = {bs_init,Fail,Info,Live,Ss,Dst},
remap(Is, Map, [I|Acc]);
remap([{bs_put=Op,Fail,Info,Ss}|Is], Map, Acc) ->
I = {Op,Fail,Info,[Map(S) || S <- Ss]},
remap(Is, Map, [I|Acc]);
remap([{kill,Y}|T], Map, Acc) ->
remap(T, Map, [{kill,Map(Y)}|Acc]);
remap([{make_fun2,_,_,_,_}=I|T], Map, Acc) ->
remap(T, Map, [I|Acc]);
remap([{deallocate,N}|Is], Map, Acc) ->
I = {deallocate,Map({frame_size,N})},
remap(Is, Map, [I|Acc]);
remap([{test,Name,Fail,Ss}|Is], Map, Acc) ->
I = {test,Name,Fail,[Map(S) || S <- Ss]},
remap(Is, Map, [I|Acc]);
remap([{test,Name,Fail,Live,Ss,Dst}|Is], Map, Acc) ->
I = {test,Name,Fail,Live,[Map(S) || S <- Ss],Map(Dst)},
remap(Is, Map, [I|Acc]);
remap([return|_]=Is, _, Acc) ->
reverse(Acc, Is);
remap([{line,_}=I|Is], Map, Acc) ->
remap(Is, Map, [I|Acc]).
remap_block([{set,Ds0,Ss0,Info}|Is], Map, Acc) ->
Ds = [Map(D) || D <- Ds0],
Ss = [Map(S) || S <- Ss0],
remap_block(Is, Map, [{set,Ds,Ss,Info}|Acc]);
remap_block([], _, Acc) -> reverse(Acc).
safe_labels([{label,L},{line,_},{badmatch,{Tag,_}}|Is], Acc) when Tag =/= y ->
safe_labels(Is, [L|Acc]);
safe_labels([{label,L},{line,_},{case_end,{Tag,_}}|Is], Acc) when Tag =/= y ->
safe_labels(Is, [L|Acc]);
safe_labels([{label,L},{line,_},if_end|Is], Acc) ->
safe_labels(Is, [L|Acc]);
safe_labels([{label,L},
{block,[{set,[{x,0}],[{Tag,_}],move}]},
{line,_},
{call_ext,1,{extfunc,erlang,error,1}}|Is], Acc) when Tag =/= y ->
safe_labels(Is, [L|Acc]);
safe_labels([_|Is], Acc) ->
safe_labels(Is, Acc);
safe_labels([], Acc) -> cerl_sets:from_list(Acc).
%% frame_layout([Instruction], [{kill,_}], St) ->
%% [{kill,Reg} | {live,Reg} | {dead,Reg}]
%% Figure out the layout of the stack frame.
frame_layout(Is, Kills, #st{safe=Safe,lbl=D}) ->
N = frame_size(Is, Safe),
IsKilled = fun(R) -> beam_utils:is_not_used(R, Is, D) end,
{N,frame_layout_1(Kills, 0, N, IsKilled, [])}.
frame_layout_1([{kill,{y,Y}}=I|Ks], Y, N, IsKilled, Acc) ->
frame_layout_1(Ks, Y+1, N, IsKilled, [I|Acc]);
frame_layout_1(Ks, Y, N, IsKilled, Acc) when Y < N ->
R = {y,Y},
I = case IsKilled(R) of
false -> {live,R};
true -> {dead,R}
end,
frame_layout_1(Ks, Y+1, N, IsKilled, [I|Acc]);
frame_layout_1([], Y, Y, _, Acc) ->
frame_layout_2(Acc).
frame_layout_2([{live,_}|Is]) -> frame_layout_2(Is);
frame_layout_2(Is) -> reverse(Is).
%% frame_size([Instruction], SafeLabels) -> FrameSize
%% Find out the frame size by looking at the code that follows.
frame_size([{block,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{call_fun,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{call,_,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{call_ext,_,_}=I|Is], Safe) ->
case beam_jump:is_exit_instruction(I) of
true -> throw(not_possible);
false -> frame_size(Is, Safe)
end;
frame_size([{apply,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{bif,_,{f,L},_,_}|Is], Safe) ->
frame_size_branch(L, Is, Safe);
frame_size([{gc_bif,_,{f,L},_,_,_}|Is], Safe) ->
frame_size_branch(L, Is, Safe);
frame_size([{test,_,{f,L},_}|Is], Safe) ->
frame_size_branch(L, Is, Safe);
frame_size([{test,_,{f,L},_,_,_}|Is], Safe) ->
frame_size_branch(L, Is, Safe);
frame_size([{bs_init,{f,L},_,_,_,_}|Is], Safe) ->
frame_size_branch(L, Is, Safe);
frame_size([{bs_put,{f,L},_,_}|Is], Safe) ->
frame_size_branch(L, Is, Safe);
frame_size([{kill,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{make_fun2,_,_,_,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{get_map_elements,{f,L},_,_}|Is], Safe) ->
frame_size_branch(L, Is, Safe);
frame_size([{deallocate,N}|_], _) -> N;
frame_size([{line,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size(_, _) -> throw(not_possible).
frame_size_branch(0, Is, Safe) ->
frame_size(Is, Safe);
frame_size_branch(L, Is, Safe) ->
case cerl_sets:is_element(L, Safe) of
false -> throw(not_possible);
true -> frame_size(Is, Safe)
end.