%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2002-2013. 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%
%%
-module(beam_dead).
-export([module/2]).
%%% The following optimisations are done:
%%%
%%% (1) In this code
%%%
%%% move DeadValue {x,0}
%%% jump L2
%%% .
%%% .
%%% .
%%% L2: move Anything {x,0}
%%% .
%%% .
%%% .
%%%
%%% the first assignment to {x,0} has no effect (is dead),
%%% so it can be removed. Besides removing a move instruction,
%%% if the move was preceeded by a label, the resulting code
%%% will look this
%%%
%%% L1: jump L2
%%% .
%%% .
%%% .
%%% L2: move Anything {x,0}
%%% .
%%% .
%%% .
%%%
%%% which can be further optimized by the jump optimizer (beam_jump).
%%%
%%% (2) In this code
%%%
%%% L1: move AtomLiteral {x,0}
%%% jump L2
%%% .
%%% .
%%% .
%%% L2: test is_atom FailLabel {x,0}
%%% select_val {x,0}, FailLabel [... AtomLiteral => L3...]
%%% .
%%% .
%%% .
%%% L3: ...
%%%
%%% FailLabel: ...
%%%
%%% the first code fragment can be changed to
%%%
%%% L1: move AtomLiteral {x,0}
%%% jump L3
%%%
%%% If the literal is not included in the table of literals in the
%%% select_val instruction, the first code fragment will instead be
%%% rewritten as:
%%%
%%% L1: move AtomLiteral {x,0}
%%% jump FailLabel
%%%
%%% The move instruction will be removed by optimization (1) above,
%%% if the code following the L3 label overwrites {x,0}.
%%%
%%% The code following the L2 label will be kept, but it will be removed later
%%% by the jump optimizer.
%%%
%%% (3) In this code
%%%
%%% test is_eq_exact ALabel Src Dst
%%% move Src Dst
%%%
%%% the move instruction can be removed.
%%% Same thing for
%%%
%%% test is_nil ALabel Dst
%%% move [] Dst
%%%
%%%
%%% (4) In this code
%%%
%%% select_val {x,Reg}, ALabel [... Literal => L1...]
%%% .
%%% .
%%% .
%%% L1: move Literal {x,Reg}
%%%
%%% we can remove the move instruction.
%%%
%%% (5) In the following code
%%%
%%% bif '=:=' Fail Src1 Src2 {x,0}
%%% jump L1
%%% .
%%% .
%%% .
%%% L1: select_val {x,0}, ALabel [... true => L2..., ...false => L3...]
%%% .
%%% .
%%% .
%%% L2: .... L3: ....
%%%
%%% the first two instructions can be replaced with
%%%
%%% test is_eq_exact L3 Src1 Src2
%%% jump L2
%%%
%%% provided that {x,0} is killed at both L2 and L3.
%%%
-import(lists, [mapfoldl/3,reverse/1]).
module({Mod,Exp,Attr,Fs0,_}, _Opts) ->
{Fs1,Lc1} = beam_clean:clean_labels(Fs0),
{Fs,Lc} = mapfoldl(fun function/2, Lc1, Fs1),
%%{Fs,Lc} = {Fs1,Lc1},
{ok,{Mod,Exp,Attr,Fs,Lc}}.
function({function,Name,Arity,CLabel,Is0}, Lc0) ->
try
Is1 = beam_jump:remove_unused_labels(Is0),
%% Initialize label information with the code
%% for the func_info label. Without it, a register
%% may seem to be live when it is not.
[{label,L}|FiIs] = Is1,
D0 = beam_utils:empty_label_index(),
D = beam_utils:index_label(L, FiIs, D0),
%% Optimize away dead code.
{Is2,Lc} = forward(Is1, Lc0),
Is3 = backward(Is2, D),
Is = move_move_into_block(Is3, []),
{{function,Name,Arity,CLabel,Is},Lc}
catch
Class:Error ->
Stack = erlang:get_stacktrace(),
io:fwrite("Function: ~w/~w\n", [Name,Arity]),
erlang:raise(Class, Error, Stack)
end.
%% 'move' instructions outside of blocks may thwart the jump optimizer.
%% Move them back into the block.
move_move_into_block([{block,Bl0},{move,S,D}|Is], Acc) ->
Bl = Bl0 ++ [{set,[D],[S],move}],
move_move_into_block([{block,Bl}|Is], Acc);
move_move_into_block([{move,S,D}|Is], Acc) ->
Bl = [{set,[D],[S],move}],
move_move_into_block([{block,Bl}|Is], Acc);
move_move_into_block([I|Is], Acc) ->
move_move_into_block(Is, [I|Acc]);
move_move_into_block([], Acc) -> reverse(Acc).
%%%
%%% Scan instructions in execution order and remove dead code.
%%%
forward(Is, Lc) ->
forward(Is, gb_trees:empty(), Lc, []).
forward([{block,[]}|Is], D, Lc, Acc) ->
%% Empty blocks can prevent optimizations.
forward(Is, D, Lc, Acc);
forward([{select,select_val,Reg,_,List}=I|Is], D0, Lc, Acc) ->
D = update_value_dict(List, Reg, D0),
forward(Is, D, Lc, [I|Acc]);
forward([{label,Lbl}=LblI,{block,[{set,[Dst],[Lit],move}|BlkIs]}=Blk|Is], D, Lc, Acc) ->
%% Assumption: The target labels in a select_val/3 instruction
%% cannot be reached in any other way than through the select_val/3
%% instruction (i.e. there can be no fallthrough to such label and
%% it cannot be referenced by, for example, a jump/1 instruction).
Block = case gb_trees:lookup({Lbl,Dst}, D) of
{value,Lit} -> {block,BlkIs}; %Safe to remove move instruction.
_ -> Blk %Must keep move instruction.
end,
forward([Block|Is], D, Lc, [LblI|Acc]);
forward([{label,Lbl}=LblI|[{move,Lit,Dst}|Is1]=Is0], D, Lc, Acc) ->
%% Assumption: The target labels in a select_val/3 instruction
%% cannot be reached in any other way than through the select_val/3
%% instruction (i.e. there can be no fallthrough to such label and
%% it cannot be referenced by, for example, a jump/1 instruction).
Is = case gb_trees:lookup({Lbl,Dst}, D) of
{value,Lit} -> Is1; %Safe to remove move instruction.
_ -> Is0 %Keep move instruction.
end,
forward(Is, D, Lc, [LblI|Acc]);
forward([{test,is_eq_exact,_,[Dst,Src]}=I,
{block,[{set,[Dst],[Src],move}|Bl]}|Is], D, Lc, Acc) ->
forward([I,{block,Bl}|Is], D, Lc, Acc);
forward([{test,is_nil,_,[Dst]}=I,
{block,[{set,[Dst],[nil],move}|Bl]}|Is], D, Lc, Acc) ->
forward([I,{block,Bl}|Is], D, Lc, Acc);
forward([{test,is_eq_exact,_,[Dst,Src]}=I,{move,Src,Dst}|Is], D, Lc, Acc) ->
forward([I|Is], D, Lc, Acc);
forward([{test,is_nil,_,[Dst]}=I,{move,nil,Dst}|Is], D, Lc, Acc) ->
forward([I|Is], D, Lc, Acc);
forward([{test,is_eq_exact,_,_}=I|Is], D, Lc, Acc) ->
case Is of
[{label,_}|_] -> forward(Is, D, Lc, [I|Acc]);
_ -> forward(Is, D, Lc+1, [{label,Lc},I|Acc])
end;
forward([{test,is_ne_exact,_,_}=I|Is], D, Lc, Acc) ->
case Is of
[{label,_}|_] -> forward(Is, D, Lc, [I|Acc]);
_ -> forward(Is, D, Lc+1, [{label,Lc},I|Acc])
end;
forward([I|Is], D, Lc, Acc) ->
forward(Is, D, Lc, [I|Acc]);
forward([], _, Lc, Acc) -> {Acc,Lc}.
update_value_dict([Lit,{f,Lbl}|T], Reg, D0) ->
Key = {Lbl,Reg},
D = case gb_trees:lookup(Key, D0) of
none -> gb_trees:insert(Key, Lit, D0); %New.
{value,inconsistent} -> D0; %Inconsistent.
{value,_} -> gb_trees:update(Key, inconsistent, D0)
end,
update_value_dict(T, Reg, D);
update_value_dict([], _, D) -> D.
%%%
%%% Scan instructions in reverse execution order and remove dead code.
%%%
backward(Is, D) ->
backward(Is, D, []).
backward([{test,is_eq_exact,Fail,[Dst,{integer,Arity}]}=I|
[{bif,tuple_size,Fail,[Reg],Dst}|Is]=Is0], D, Acc) ->
%% Provided that Dst is killed following this sequence,
%% we can rewrite the instructions like this:
%%
%% bif tuple_size Fail Reg Dst ==> is_tuple Fail Reg
%% is_eq_exact Fail Dst Integer test_arity Fail Reg Integer
%%
%% (still two instructions, but they they will be combined to
%% one by the loader).
case beam_utils:is_killed(Dst, Acc, D) andalso (Arity bsr 32) =:= 0 of
false ->
%% Not safe because the register Dst is not killed
%% (probably cannot not happen in practice) or the arity
%% does not fit in 32 bits (the loader will fail to load
%% the module). We must move the first instruction to the
%% accumulator to avoid an infinite loop.
backward(Is0, D, [I|Acc]);
true ->
%% Safe.
backward([{test,test_arity,Fail,[Reg,Arity]},
{test,is_tuple,Fail,[Reg]}|Is], D, Acc)
end;
backward([{label,Lbl}=L|Is], D, Acc) ->
backward(Is, beam_utils:index_label(Lbl, Acc, D), [L|Acc]);
backward([{select,select_val,Reg,{f,Fail0},List0}|Is], D, Acc) ->
List = shortcut_select_list(List0, Reg, D, []),
Fail1 = shortcut_label(Fail0, D),
Fail = shortcut_bs_test(Fail1, Is, D),
Sel = {select,select_val,Reg,{f,Fail},List},
backward(Is, D, [Sel|Acc]);
backward([{jump,{f,To0}},{move,Src,Reg}=Move0|Is], D, Acc) ->
{To,Move} = case Src of
{atom,Val0} ->
To1 = shortcut_select_label(To0, Reg, Val0, D),
{To2,Val} = shortcut_boolean_label(To1, Reg, Val0, D),
{To2,{move,{atom,Val},Reg}};
_ ->
{shortcut_label(To0, D),Move0}
end,
Jump = {jump,{f,To}},
case beam_utils:is_killed_at(Reg, To, D) of
false -> backward([Move|Is], D, [Jump|Acc]);
true -> backward([Jump|Is], D, Acc)
end;
backward([{jump,{f,To}}=J|[{bif,Op,_,Ops,Reg}|Is]=Is0], D, Acc) ->
try replace_comp_op(To, Reg, Op, Ops, D) of
I -> backward(Is, D, I++Acc)
catch
throw:not_possible -> backward(Is0, D, [J|Acc])
end;
backward([{test,bs_start_match2,{f,To0},Live,[Src|_]=Info,Dst}|Is], D, Acc) ->
To = shortcut_bs_start_match(To0, Src, D),
I = {test,bs_start_match2,{f,To},Live,Info,Dst},
backward(Is, D, [I|Acc]);
backward([{test,is_eq_exact,{f,To0},[Reg,{atom,Val}]=Ops}|Is], D, Acc) ->
To1 = shortcut_bs_test(To0, Is, D),
To = shortcut_fail_label(To1, Reg, Val, D),
I = combine_eqs(To, Ops, D, Acc),
backward(Is, D, [I|Acc]);
backward([{test,Op,{f,To0},Ops0}|Is], D, Acc) ->
To1 = shortcut_bs_test(To0, Is, D),
To2 = shortcut_label(To1, D),
%% Try to shortcut a repeated test:
%%
%% test Op {f,Fail1} Operands test Op {f,Fail2} Operands
%% . . . ==> ...
%% Fail1: test Op {f,Fail2} Operands Fail1: test Op {f,Fail2} Operands
%%
To = case beam_utils:code_at(To2, D) of
[{test,Op,{f,To3},Ops}|_] ->
case equal_ops(Ops0, Ops) of
true -> To3;
false -> To2
end;
_Code ->
To2
end,
I = case Op of
is_eq_exact -> combine_eqs(To, Ops0, D, Acc);
_ -> {test,Op,{f,To},Ops0}
end,
backward(Is, D, [I|Acc]);
backward([{test,Op,{f,To0},Live,Ops0,Dst}|Is], D, Acc) ->
To1 = shortcut_bs_test(To0, Is, D),
To2 = shortcut_label(To1, D),
%% Try to shortcut a repeated test:
%%
%% test Op {f,Fail1} _ Ops _ test Op {f,Fail2} _ Ops _
%% . . . ==> ...
%% Fail1: test Op {f,Fail2} _ Ops _ Fail1: test Op {f,Fail2} _ Ops _
%%
To = case beam_utils:code_at(To2, D) of
[{test,Op,{f,To3},_,Ops,_}|_] ->
case equal_ops(Ops0, Ops) of
true -> To3;
false -> To2
end;
_Code ->
To2
end,
I = {test,Op,{f,To},Live,Ops0,Dst},
backward(Is, D, [I|Acc]);
backward([{kill,_}=I|Is], D, [{line,_},Exit|_]=Acc) ->
case beam_jump:is_exit_instruction(Exit) of
false -> backward(Is, D, [I|Acc]);
true -> backward(Is, D, Acc)
end;
backward([I|Is], D, Acc) ->
backward(Is, D, [I|Acc]);
backward([], _D, Acc) -> Acc.
equal_ops([{field_flags,FlA0}|T0], [{field_flags,FlB0}|T1]) ->
FlA = lists:keydelete(anno, 1, FlA0),
FlB = lists:keydelete(anno, 1, FlB0),
FlA =:= FlB andalso equal_ops(T0, T1);
equal_ops([Op|T0], [Op|T1]) ->
equal_ops(T0, T1);
equal_ops([], []) -> true;
equal_ops(_, _) -> false.
shortcut_select_list([{_,Val}=Lit,{f,To0}|T], Reg, D, Acc) ->
To = shortcut_select_label(To0, Reg, Val, D),
shortcut_select_list(T, Reg, D, [{f,To},Lit|Acc]);
shortcut_select_list([], _, _, Acc) -> reverse(Acc).
shortcut_label(To0, D) ->
case beam_utils:code_at(To0, D) of
[{jump,{f,To}}|_] -> shortcut_label(To, D);
_ -> To0
end.
shortcut_select_label(To0, Reg, Val, D) ->
case beam_utils:code_at(To0, D) of
[{jump,{f,To}}|_] ->
shortcut_select_label(To, Reg, Val, D);
[{test,is_atom,_,[Reg]},{select,select_val,Reg,{f,Fail},Map}|_] ->
To = find_select_val(Map, Val, Fail),
shortcut_select_label(To, Reg, Val, D);
[{test,is_eq_exact,{f,_},[Reg,{atom,Val}]},{label,To}|_] when is_atom(Val) ->
shortcut_select_label(To, Reg, Val, D);
[{test,is_eq_exact,{f,_},[Reg,{atom,Val}]},{jump,{f,To}}|_] when is_atom(Val) ->
shortcut_select_label(To, Reg, Val, D);
[{test,is_eq_exact,{f,To},[Reg,{atom,AnotherVal}]}|_]
when is_atom(Val), Val =/= AnotherVal ->
shortcut_select_label(To, Reg, Val, D);
[{test,is_ne_exact,{f,To},[Reg,{atom,Val}]}|_] when is_atom(Val) ->
shortcut_select_label(To, Reg, Val, D);
[{test,is_ne_exact,{f,_},[Reg,{atom,_}]},{label,To}|_] when is_atom(Val) ->
shortcut_select_label(To, Reg, Val, D);
[{test,is_tuple,{f,To},[Reg]}|_] when is_atom(Val) ->
shortcut_select_label(To, Reg, Val, D);
_ ->
To0
end.
shortcut_fail_label(To0, Reg, Val, D) ->
case beam_utils:code_at(To0, D) of
[{jump,{f,To}}|_] ->
shortcut_fail_label(To, Reg, Val, D);
[{test,is_eq_exact,{f,To},[Reg,{atom,Val}]}|_] when is_atom(Val) ->
shortcut_fail_label(To, Reg, Val, D);
_ ->
To0
end.
shortcut_boolean_label(To0, Reg, Bool0, D) when is_boolean(Bool0) ->
case beam_utils:code_at(To0, D) of
[{line,_},{bif,'not',_,[Reg],Reg},{jump,{f,To}}|_] ->
Bool = not Bool0,
{shortcut_select_label(To, Reg, Bool, D),Bool};
_ ->
{To0,Bool0}
end;
shortcut_boolean_label(To, _, Bool, _) -> {To,Bool}.
find_select_val([{_,Val},{f,To}|_], Val, _) -> To;
find_select_val([{_,_}, {f,_}|T], Val, Fail) ->
find_select_val(T, Val, Fail);
find_select_val([], _, Fail) -> Fail.
replace_comp_op(To, Reg, Op, Ops, D) ->
False = comp_op_find_shortcut(To, Reg, false, D),
True = comp_op_find_shortcut(To, Reg, true, D),
[bif_to_test(Op, Ops, False),{jump,{f,True}}].
comp_op_find_shortcut(To0, Reg, Val, D) ->
case shortcut_select_label(To0, Reg, Val, D) of
To0 ->
not_possible();
To ->
case beam_utils:is_killed_at(Reg, To, D) of
false -> not_possible();
true -> To
end
end.
bif_to_test(Name, Args, Fail) ->
try
beam_utils:bif_to_test(Name, Args, {f,Fail})
catch
error:_ -> not_possible()
end.
not_possible() -> throw(not_possible).
%% combine_eqs(To, Operands, Acc) -> Instruction.
%% Combine two is_eq_exact instructions or (an is_eq_exact
%% instruction and a select_val instruction) to a select_val
%% instruction if possible.
%%
%% Example:
%%
%% is_eq_exact F1 Reg Lit1 select_val Reg F2 [ Lit1 L1
%% L1: . Lit2 L2 ]
%% .
%% . ==>
%% .
%% F1: is_eq_exact F2 Reg Lit2 F1: is_eq_exact F2 Reg Lit2
%% L2: .... L2:
%%
combine_eqs(To, [Reg,{Type,_}=Lit1]=Ops, D, [{label,L1}|_])
when Type =:= atom; Type =:= integer ->
case beam_utils:code_at(To, D) of
[{test,is_eq_exact,{f,F2},[Reg,{Type,_}=Lit2]},
{label,L2}|_] when Lit1 =/= Lit2 ->
{select,select_val,Reg,{f,F2},[Lit1,{f,L1},Lit2,{f,L2}]};
[{select,select_val,Reg,{f,F2},[{Type,_}|_]=List0}|_] ->
List = remove_from_list(Lit1, List0),
{select,select_val,Reg,{f,F2},[Lit1,{f,L1}|List]};
_Is ->
{test,is_eq_exact,{f,To},Ops}
end;
combine_eqs(To, Ops, _D, _Acc) ->
{test,is_eq_exact,{f,To},Ops}.
remove_from_list(Lit, [Lit,{f,_}|T]) ->
T;
remove_from_list(Lit, [Val,{f,_}=Fail|T]) ->
[Val,Fail|remove_from_list(Lit, T)];
remove_from_list(_, []) -> [].
%% shortcut_bs_test(TargetLabel, [Instruction], D) -> TargetLabel'
%% Try to shortcut the failure label for a bit syntax matching.
%% We know that the binary contains at least Bits bits after
%% the latest save point.
shortcut_bs_test(To, Is, D) ->
shortcut_bs_test_1(beam_utils:code_at(To, D), Is, To, D).
shortcut_bs_test_1([{bs_restore2,Reg,SavePoint}|Is], PrevIs, To, D) ->
shortcut_bs_test_2(Is, {Reg,SavePoint}, PrevIs, To, D);
shortcut_bs_test_1([_|_], _, To, _) -> To.
shortcut_bs_test_2([{label,_}|Is], Save, PrevIs, To, D) ->
shortcut_bs_test_2(Is, Save, PrevIs, To, D);
shortcut_bs_test_2([{test,bs_test_tail2,{f,To},[_,TailBits]}|_],
{Reg,_Point} = RP, PrevIs, To0, D) ->
case count_bits_matched(PrevIs, RP, 0) of
Bits when Bits > TailBits ->
%% This instruction will fail. We know because a restore has been
%% done from the previous point SavePoint in the binary, and we also know
%% that the binary contains at least Bits bits from SavePoint.
%%
%% Since we will skip a bs_restore2 if we shortcut to label To,
%% we must now make sure that code at To does not depend on the position
%% in the context in any way.
case shortcut_bs_pos_used(To, Reg, D) of
false -> To;
true -> To0
end;
_Bits ->
To0
end;
shortcut_bs_test_2([_|_], _, _, To, _) -> To.
count_bits_matched([{test,_,_,_,[_,Sz,U,{field_flags,_}],_}|Is], SavePoint, Bits) ->
case Sz of
{integer,N} -> count_bits_matched(Is, SavePoint, Bits+N*U);
_ -> count_bits_matched(Is, SavePoint, Bits)
end;
count_bits_matched([{test,bs_match_string,_,[_,Bits,_]}|Is], SavePoint, Bits0) ->
count_bits_matched(Is, SavePoint, Bits0+Bits);
count_bits_matched([{test,_,_,_}|Is], SavePoint, Bits) ->
count_bits_matched(Is, SavePoint, Bits);
count_bits_matched([{bs_save2,Reg,SavePoint}|_], {Reg,SavePoint}, Bits) ->
%% The save point we are looking for - we are done.
Bits;
count_bits_matched([_|_], _, Bits) -> Bits.
shortcut_bs_pos_used(To, Reg, D) ->
shortcut_bs_pos_used_1(beam_utils:code_at(To, D), Reg, D).
shortcut_bs_pos_used_1([{bs_context_to_binary,Reg}|_], Reg, _) ->
false;
shortcut_bs_pos_used_1(Is, Reg, D) ->
not beam_utils:is_killed(Reg, Is, D).
%% shortcut_bs_start_match(TargetLabel, Reg) -> TargetLabel
%% A failing bs_start_match2 instruction means that the source
%% cannot be a binary, so there is no need to jump bs_context_to_binary/1
%% or another bs_start_match2 instruction.
shortcut_bs_start_match(To, Reg, D) ->
shortcut_bs_start_match_1(beam_utils:code_at(To, D), Reg, To).
shortcut_bs_start_match_1([{bs_context_to_binary,Reg}|Is], Reg, To) ->
shortcut_bs_start_match_2(Is, Reg, To);
shortcut_bs_start_match_1(_, _, To) -> To.
shortcut_bs_start_match_2([{jump,{f,To}}|_], _, _) ->
To;
shortcut_bs_start_match_2([{test,bs_start_match2,{f,To},_,[Reg|_],_}|_], Reg, _) ->
To;
shortcut_bs_start_match_2(_Is, _Reg, To) ->
To.