diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/compiler/src/beam_dead.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/compiler/src/beam_dead.erl')
-rw-r--r-- | lib/compiler/src/beam_dead.erl | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/lib/compiler/src/beam_dead.erl b/lib/compiler/src/beam_dead.erl new file mode 100644 index 0000000000..7b4cd814a2 --- /dev/null +++ b/lib/compiler/src/beam_dead.erl @@ -0,0 +1,599 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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% +%% + +-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 = [split_blocks(F) || F <- Fs0], + {Fs2,Lc1} = beam_clean:clean_labels(Fs1), + {Fs,Lc} = mapfoldl(fun function/2, Lc1, Fs2), + %%{Fs,Lc} = {Fs2,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},{func_info,_,_,_}=FI|_] = Is1, + D0 = beam_utils:empty_label_index(), + D = beam_utils:index_label(L, [FI], 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. + +%% We must split the basic block when we encounter instructions with labels, +%% such as catches and BIFs. All labels must be visible outside the blocks. +%% Also remove empty blocks. + +split_blocks({function,Name,Arity,CLabel,Is0}) -> + Is = split_blocks(Is0, []), + {function,Name,Arity,CLabel,Is}. + +split_blocks([{block,[]}|Is], Acc) -> + split_blocks(Is, Acc); +split_blocks([{block,Bl}|Is], Acc0) -> + Acc = split_block(Bl, [], Acc0), + split_blocks(Is, Acc); +split_blocks([I|Is], Acc) -> + split_blocks(Is, [I|Acc]); +split_blocks([], Acc) -> reverse(Acc). + +split_block([{set,[R],[_,_,_]=As,{bif,is_record,{f,Lbl}}}|Is], Bl, Acc) -> + %% is_record/3 must be translated by beam_clean; therefore, + %% it must be outside of any block. + split_block(Is, [], [{bif,is_record,{f,Lbl},As,R}|make_block(Bl, Acc)]); +split_block([{set,[R],As,{bif,N,{f,Lbl}=Fail}}|Is], Bl, Acc) when Lbl =/= 0 -> + split_block(Is, [], [{bif,N,Fail,As,R}|make_block(Bl, Acc)]); +split_block([{set,[R],As,{alloc,Live,{gc_bif,N,{f,Lbl}=Fail}}}|Is], Bl, Acc) + when Lbl =/= 0 -> + split_block(Is, [], [{gc_bif,N,Fail,Live,As,R}|make_block(Bl, Acc)]); +split_block([{set,[R],[],{'catch',L}}|Is], Bl, Acc) -> + split_block(Is, [], [{'catch',R,L}|make_block(Bl, Acc)]); +split_block([I|Is], Bl, Acc) -> + split_block(Is, [I|Bl], Acc); +split_block([], Bl, Acc) -> make_block(Bl, Acc). + +make_block([], Acc) -> Acc; +make_block([{set,[D],Ss,{bif,Op,Fail}}|Bl]=Bl0, Acc) -> + %% If the last instruction in the block is a comparison or boolean operator + %% (such as '=:='), move it out of the block to facilitate further + %% optimizations. + Arity = length(Ss), + case erl_internal:comp_op(Op, Arity) orelse + erl_internal:new_type_test(Op, Arity) orelse + erl_internal:bool_op(Op, Arity) of + false -> + [{block,reverse(Bl0)}|Acc]; + true -> + I = {bif,Op,Fail,Ss,D}, + case Bl =:= [] of + true -> [I|Acc]; + false -> [I,{block,reverse(Bl)}|Acc] + end + end; +make_block([{set,[Dst],[Src],move}|Bl], Acc) -> + %% Make optimization of {move,Src,Dst}, {jump,...} possible. + I = {move,Src,Dst}, + case Bl =:= [] of + true -> [I|Acc]; + false -> [I,{block,reverse(Bl)}|Acc] + end; +make_block(Bl, Acc) -> [{block,reverse(Bl)}|Acc]. + +%% '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_val,Reg,_,{list,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) -> + Block = case gb_trees:lookup({Lbl,Dst}, D) of + {value,Lit} -> + %% The move instruction seems to be redundant, but also make + %% sure that the instruction preceeding the label + %% cannot fall through to the move instruction. + case is_unreachable_after(Acc) of + false -> Blk; %Must keep move instruction. + true -> {block,BlkIs} %Safe to remove move instruction. + end; + _ -> Blk %Keep move instruction. + end, + forward([Block|Is], D, Lc, [LblI|Acc]); +forward([{label,Lbl}=LblI|[{move,Lit,Dst}|Is1]=Is0], D, Lc, Acc) -> + Is = case gb_trees:lookup({Lbl,Dst}, D) of + {value,Lit} -> + %% The move instruction seems to be redundant, but also make + %% sure that the instruction preceeding the label + %% cannot fall through to the move instruction. + case is_unreachable_after(Acc) of + false -> Is0; %Must keep move instruction. + true -> Is1 %Safe to remove move instruction. + end; + _ -> 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,_,[_,{atom,_}]}=I|Is], D, Lc, [{label,_}|_]=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,_,[_,{atom,_}]}=I|Is], D, Lc, [{label,_}|_]=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,Lit} -> D0; %Already correct. + {value,inconsistent} -> D0; %Inconsistent. + {value,_} -> gb_trees:update(Key, inconsistent, D0) + end, + update_value_dict(T, Reg, D); +update_value_dict([], _, D) -> D. + +is_unreachable_after([I|_]) -> + beam_jump:is_unreachable_after(I). + +%%% +%%% 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_val,Reg,{f,Fail0},{list,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_val,Reg,{f,Fail},{list,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=Op,{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 = {test,Op,{f,To},Ops}, + 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 = {test,Op,{f,To},Ops0}, + 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, [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_val,Reg,{f,Fail},{list,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 + [{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). + + +%% 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,_,_,_}|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([{bs_save2,_,_}|Is], SavePoint, Bits) -> + %% Another save point - keep counting. + count_bits_matched(Is, SavePoint, 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_restore2,Reg,_}|_], Reg, _) -> + false; +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. |