aboutsummaryrefslogtreecommitdiffstats
path: root/lib/compiler/src/beam_jump.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compiler/src/beam_jump.erl')
-rw-r--r--lib/compiler/src/beam_jump.erl562
1 files changed, 562 insertions, 0 deletions
diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl
new file mode 100644
index 0000000000..739928f411
--- /dev/null
+++ b/lib/compiler/src/beam_jump.erl
@@ -0,0 +1,562 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-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 : Optimise jumps and remove unreachable code.
+
+-module(beam_jump).
+
+-export([module/2,module_labels/1,
+ is_unreachable_after/1,is_exit_instruction/1,
+ remove_unused_labels/1,is_label_used_in/2]).
+
+%%% The following optimisations are done:
+%%%
+%%% (1) This code with two identical instruction sequences
+%%%
+%%% L1: <Instruction sequence>
+%%% L2:
+%%% . . .
+%%% L3: <Instruction sequence>
+%%% L4:
+%%%
+%%% can be replaced with
+%%%
+%%% L1: jump L3
+%%% L2:
+%%% . . .
+%%% L3: <Instruction sequence>
+%%% L4
+%%%
+%%% Note: The instruction sequence must end with an instruction
+%%% such as a jump that never transfers control to the instruction
+%%% following it.
+%%%
+%%% (2) case_end, if_end, and badmatch, and function calls that cause an
+%%% exit (such as calls to exit/1) are moved to the end of the function.
+%%% The purpose is to allow further optimizations at the place from
+%%% which the code was moved.
+%%%
+%%% (3) Any unreachable code is removed. Unreachable code is code
+%%% after jump, call_last and other instructions which never
+%%% transfer control to the following instruction. Code is
+%%% unreachable up to the next *referenced* label. Note that the
+%%% optimisations below might generate more possibilities for
+%%% removing unreachable code.
+%%%
+%%% (4) This code:
+%%% L1: jump L2
+%%% . . .
+%%% L2: ...
+%%%
+%%% will be changed to
+%%%
+%%% jump L2
+%%% . . .
+%%% L1:
+%%% L2: ...
+%%%
+%%% If the jump is unreachable, it will be removed according to (1).
+%%%
+%%% (5) In
+%%%
+%%% jump L1
+%%% L1:
+%%%
+%%% the jump (but not the label) will be removed.
+%%%
+%%% (6) If test instructions are used to skip a single jump instruction,
+%%% the test is inverted and the jump is eliminated (provided that
+%%% the test can be inverted). Example:
+%%%
+%%% is_eq L1 {x,1} {x,2}
+%%% jump L2
+%%% L1:
+%%%
+%%% will be changed to
+%%%
+%%% is_ne L2 {x,1} {x,2}
+%%% L1:
+%%%
+%%% Because there may be backward references to the label L1
+%%% (for instance from the wait_timeout/1 instruction), we will
+%%% always keep the label. (beam_clean will remove any unused
+%%% labels.)
+%%%
+%%% Note: This modules depends on (almost) all branches and jumps only
+%%% going forward, so that we can remove instructions (including definition
+%%% of labels) after any label that has not been referenced by the code
+%%% preceeding the labels. Regarding the few instructions that have backward
+%%% references to labels, we assume that they only transfer control back
+%%% to an instruction that has already been executed. That is, code such as
+%%%
+%%% jump L_entry
+%%%
+%%% L_again:
+%%% .
+%%% .
+%%% .
+%%% L_entry:
+%%% .
+%%% .
+%%% .
+%%% jump L_again;
+%%%
+%%% is NOT allowed (and such code is never generated by the code generator).
+%%%
+%%% Terminology note: The optimisation done here is called unreachable-code
+%%% removal, NOT dead-code elimination. Dead code elimination means the
+%%% removal of instructions that are executed, but have no visible effect
+%%% on the program state.
+%%%
+
+-import(lists, [reverse/1,reverse/2,foldl/3,dropwhile/2]).
+
+module({Mod,Exp,Attr,Fs0,Lc}, _Opt) ->
+ Fs = [function(F) || F <- Fs0],
+ {ok,{Mod,Exp,Attr,Fs,Lc}}.
+
+module_labels({Mod,Exp,Attr,Fs,Lc}) ->
+ {Mod,Exp,Attr,[function_labels(F) || F <- Fs],Lc}.
+
+function_labels({function,Name,Arity,CLabel,Asm0}) ->
+ Asm = remove_unused_labels(Asm0),
+ {function,Name,Arity,CLabel,Asm}.
+
+%% function(Function) -> Function'
+%% Optimize jumps and branches.
+%%
+%% NOTE: This function assumes that there are no labels inside blocks.
+function({function,Name,Arity,CLabel,Asm0}) ->
+ Asm1 = share(Asm0),
+ Asm2 = move(Asm1),
+ Asm3 = opt(Asm2, CLabel),
+ Asm = remove_unused_labels(Asm3),
+ {function,Name,Arity,CLabel,Asm}.
+
+%%%
+%%% (1) We try to share the code for identical code segments by replacing all
+%%% occurrences except the last with jumps to the last occurrence.
+%%%
+
+share(Is0) ->
+ %% We will get more sharing if we never fall through to a label.
+ Is = eliminate_fallthroughs(Is0, []),
+ share_1(Is, dict:new(), [], []).
+
+share_1([{label,_}=Lbl|Is], Dict, [], Acc) ->
+ share_1(Is, Dict, [], [Lbl|Acc]);
+share_1([{label,L}=Lbl|Is], Dict0, Seq, Acc) ->
+ case dict:find(Seq, Dict0) of
+ error ->
+ Dict = dict:store(Seq, L, Dict0),
+ share_1(Is, Dict, [], [Lbl|Seq ++ Acc]);
+ {ok,Label} ->
+ share_1(Is, Dict0, [], [Lbl,{jump,{f,Label}}|Acc])
+ end;
+share_1([{func_info,_,_,_}=I|Is], _, [], Acc) ->
+ Is++[I|Acc];
+share_1([I|Is], Dict, Seq, Acc) ->
+ case is_unreachable_after(I) of
+ false ->
+ share_1(Is, Dict, [I|Seq], Acc);
+ true ->
+ share_1(Is, Dict, [I], Acc)
+ end.
+
+
+%% Eliminate all fallthroughs. Return the result reversed.
+
+eliminate_fallthroughs([I,{label,L}=Lbl|Is], Acc) ->
+ case is_unreachable_after(I) orelse is_label(I) of
+ false ->
+ %% Eliminate fallthrough.
+ eliminate_fallthroughs(Is, [Lbl,{jump,{f,L}},I|Acc]);
+ true ->
+ eliminate_fallthroughs(Is, [Lbl,I|Acc])
+ end;
+eliminate_fallthroughs([I|Is], Acc) ->
+ eliminate_fallthroughs(Is, [I|Acc]);
+eliminate_fallthroughs([], Acc) -> Acc.
+
+is_label({label,_}) -> true;
+is_label(_) -> false.
+
+%%%
+%%% (2) Move short code sequences ending in an instruction that causes an exit
+%%% to the end of the function.
+%%%
+%%% Implementation note: Since share/1 eliminated fallthroughs to labels,
+%%% we don't have to test whether instructions before labels may fail through.
+%%%
+move(Is) ->
+ move_1(Is, [], []).
+
+move_1([I|Is], End, Acc) ->
+ case is_exit_instruction(I) of
+ false -> move_1(Is, End, [I|Acc]);
+ true -> move_2(I, Is, End, Acc)
+ end;
+move_1([], End, Acc) ->
+ reverse(Acc, reverse(End)).
+
+move_2(Exit, Is, End, [{block,_},{label,_},{func_info,_,_,_}|_]=Acc) ->
+ move_1(Is, End, [Exit|Acc]);
+move_2(Exit, Is, End, [{block,_}=Blk,{label,_}=Lbl,Unreachable|More]) ->
+ move_1([Unreachable|Is], [Exit,Blk,Lbl|End], More);
+move_2(Exit, Is, End, [{bs_context_to_binary,_}=Bs,{label,_}=Lbl,
+ Unreachable|More]) ->
+ move_1([Unreachable|Is], [Exit,Bs,Lbl|End], More);
+move_2(Exit, Is, End, [{label,_}=Lbl,Unreachable|More]) ->
+ move_1([Unreachable|Is], [Exit,Lbl|End], More);
+move_2(Exit, Is, End, Acc) ->
+ move_1(Is, End, [Exit|Acc]).
+
+%%%
+%%% (3) (4) (5) (6) Jump and unreachable code optimizations.
+%%%
+
+-record(st, {fc, %Label for function class errors.
+ entry, %Entry label (must not be moved).
+ mlbl, %Moved labels.
+ labels %Set of referenced labels.
+ }).
+
+opt([{label,Fc}|_]=Is0, CLabel) ->
+ Lbls = initial_labels(Is0),
+ find_fixpoint(fun(Is) ->
+ St = #st{fc=Fc,entry=CLabel,mlbl=dict:new(),
+ labels=Lbls},
+ opt(Is, [], St)
+ end, Is0).
+
+find_fixpoint(OptFun, Is0) ->
+ case OptFun(Is0) of
+ Is0 -> Is0;
+ Is -> find_fixpoint(OptFun, Is)
+ end.
+
+opt([{test,Test0,{f,Lnum}=Lbl,Ops}=I|Is0], Acc, St) ->
+ case Is0 of
+ [{jump,{f,Lnum}}|Is] ->
+ %% We have
+ %% Test Label Ops
+ %% jump Label
+ %% The test instruction is definitely not needed.
+ %% The jump instruction is not needed if there is
+ %% a definition of Label following the jump instruction.
+ case is_label_defined(Is, Lnum) of
+ false ->
+ %% The jump instruction is still needed.
+ opt(Is0, [I|Acc], label_used(Lbl, St));
+ true ->
+ %% Neither the test nor the jump are needed.
+ opt(Is, Acc, St)
+ end;
+ [{jump,To}|Is] ->
+ case is_label_defined(Is, Lnum) of
+ false ->
+ opt(Is0, [I|Acc], label_used(Lbl, St));
+ true ->
+ case invert_test(Test0) of
+ not_possible ->
+ opt(Is0, [I|Acc], label_used(Lbl, St));
+ Test ->
+ opt([{test,Test,To,Ops}|Is], Acc, St)
+ end
+ end;
+ _Other ->
+ opt(Is0, [I|Acc], label_used(Lbl, St))
+ end;
+opt([{test,_,{f,_}=Lbl,_,_,_}=I|Is], Acc, St) ->
+ opt(Is, [I|Acc], label_used(Lbl, St));
+opt([{select_val,_R,Fail,{list,Vls}}=I|Is], Acc, St) ->
+ skip_unreachable(Is, [I|Acc], label_used([Fail|Vls], St));
+opt([{select_tuple_arity,_R,Fail,{list,Vls}}=I|Is], Acc, St) ->
+ skip_unreachable(Is, [I|Acc], label_used([Fail|Vls], St));
+opt([{label,L}=I|Is], Acc, #st{entry=L}=St) ->
+ %% NEVER move the entry label.
+ opt(Is, [I|Acc], St);
+opt([{label,L1},{jump,{f,L2}}=I|Is], [Prev|Acc], St0) ->
+ St = St0#st{mlbl=dict:append(L2, L1, St0#st.mlbl)},
+ opt([Prev,I|Is], Acc, label_used({f,L2}, St));
+opt([{label,Lbl}=I|Is], Acc, #st{mlbl=Mlbl}=St0) ->
+ case dict:find(Lbl, Mlbl) of
+ {ok,Lbls} ->
+ %% Essential to remove the list of labels from the dictionary,
+ %% since we will rescan the inserted labels. We MUST rescan.
+ St = St0#st{mlbl=dict:erase(Lbl, Mlbl)},
+ insert_labels([Lbl|Lbls], Is, Acc, St);
+ error -> opt(Is, [I|Acc], St0)
+ end;
+opt([{jump,{f,Lbl}},{label,Lbl}=I|Is], Acc, St) ->
+ opt([I|Is], Acc, St);
+opt([{jump,Lbl}=I|Is], Acc, St) ->
+ skip_unreachable(Is, [I|Acc], label_used(Lbl, St));
+%% Optimization: quickly handle some common instructions that don't
+%% have any failure labels and where is_unreachable_after(I) =:= false.
+opt([{block,_}=I|Is], Acc, St) ->
+ opt(Is, [I|Acc], St);
+opt([{kill,_}=I|Is], Acc, St) ->
+ opt(Is, [I|Acc], St);
+opt([{call,_,_}=I|Is], Acc, St) ->
+ opt(Is, [I|Acc], St);
+opt([{deallocate,_}=I|Is], Acc, St) ->
+ opt(Is, [I|Acc], St);
+%% All other instructions.
+opt([I|Is], Acc, #st{labels=Used0}=St0) ->
+ Used = ulbl(I, Used0),
+ St = St0#st{labels=Used},
+ case is_unreachable_after(I) of
+ true -> skip_unreachable(Is, [I|Acc], St);
+ false -> opt(Is, [I|Acc], St)
+ end;
+opt([], Acc, #st{fc=Fc,mlbl=Mlbl}) ->
+ Code = reverse(Acc),
+ case dict:find(Fc, Mlbl) of
+ {ok,Lbls} -> insert_fc_labels(Lbls, Mlbl, Code);
+ error -> Code
+ end.
+
+insert_fc_labels([L|Ls], Mlbl, Acc0) ->
+ Acc = [{label,L}|Acc0],
+ case dict:find(L, Mlbl) of
+ error ->
+ insert_fc_labels(Ls, Mlbl, Acc);
+ {ok,Lbls} ->
+ insert_fc_labels(Lbls++Ls, Mlbl, Acc)
+ end;
+insert_fc_labels([], _, Acc) -> Acc.
+
+%% label_defined(Is, Label) -> true | false.
+%% Test whether the label Label is defined at the start of the instruction
+%% sequence, possibly preceeded by other label definitions.
+%%
+is_label_defined([{label,L}|_], L) -> true;
+is_label_defined([{label,_}|Is], L) -> is_label_defined(Is, L);
+is_label_defined(_, _) -> false.
+
+%% invert_test(Test0) -> not_possible | Test
+
+invert_test(is_ge) -> is_lt;
+invert_test(is_lt) -> is_ge;
+invert_test(is_eq) -> is_ne;
+invert_test(is_ne) -> is_eq;
+invert_test(is_eq_exact) -> is_ne_exact;
+invert_test(is_ne_exact) -> is_eq_exact;
+invert_test(_) -> not_possible.
+
+insert_labels([L|Ls], Is, [{jump,{f,L}}|Acc], St) ->
+ insert_labels(Ls, [{label,L}|Is], Acc, St);
+insert_labels([L|Ls], Is, Acc, St) ->
+ insert_labels(Ls, [{label,L}|Is], Acc, St);
+insert_labels([], Is, Acc, St) ->
+ opt(Is, Acc, St).
+
+%% skip_unreachable([Instruction], St).
+%% Remove all instructions (including definitions of labels
+%% that have not been referenced yet) up to the next
+%% referenced label, then call opt/3 to optimize the rest
+%% of the instruction sequence.
+%%
+skip_unreachable([{label,L}|_Is]=Is0, [{jump,{f,L}}|Acc], St) ->
+ opt(Is0, Acc, St);
+skip_unreachable([{label,L}|Is]=Is0, Acc, St) ->
+ case is_label_used(L, St) of
+ true -> opt(Is0, Acc, St);
+ false -> skip_unreachable(Is, Acc, St)
+ end;
+skip_unreachable([_|Is], Acc, St) ->
+ skip_unreachable(Is, Acc, St);
+skip_unreachable([], Acc, St) ->
+ opt([], Acc, St).
+
+%% Add one or more label to the set of used labels.
+
+label_used({f,L}, St) -> St#st{labels=gb_sets:add(L, St#st.labels)};
+label_used([H|T], St0) -> label_used(T, label_used(H, St0));
+label_used([], St) -> St;
+label_used(_Other, St) -> St.
+
+%% Test if label is used.
+
+is_label_used(L, St) ->
+ gb_sets:is_member(L, St#st.labels).
+
+%% is_unreachable_after(Instruction) -> boolean()
+%% Test whether the code after Instruction is unreachable.
+
+is_unreachable_after({func_info,_M,_F,_A}) -> true;
+is_unreachable_after(return) -> true;
+is_unreachable_after({call_ext_last,_Ar,_ExtFunc,_D}) -> true;
+is_unreachable_after({call_ext_only,_Ar,_ExtFunc}) -> true;
+is_unreachable_after({call_last,_Ar,_Lbl,_D}) -> true;
+is_unreachable_after({call_only,_Ar,_Lbl}) -> true;
+is_unreachable_after({apply_last,_Ar,_N}) -> true;
+is_unreachable_after({jump,_Lbl}) -> true;
+is_unreachable_after({select_val,_R,_Lbl,_Cases}) -> true;
+is_unreachable_after({select_tuple_arity,_R,_Lbl,_Cases}) -> true;
+is_unreachable_after({loop_rec_end,_}) -> true;
+is_unreachable_after({wait,_}) -> true;
+is_unreachable_after(I) -> is_exit_instruction(I).
+
+%% is_exit_instruction(Instruction) -> boolean()
+%% Test whether the instruction Instruction always
+%% causes an exit/failure.
+
+is_exit_instruction({call_ext,_,{extfunc,M,F,A}}) ->
+ erl_bifs:is_exit_bif(M, F, A);
+is_exit_instruction({call_ext_last,_,{extfunc,M,F,A},_}) ->
+ erl_bifs:is_exit_bif(M, F, A);
+is_exit_instruction({call_ext_only,_,{extfunc,M,F,A}}) ->
+ erl_bifs:is_exit_bif(M, F, A);
+is_exit_instruction(if_end) -> true;
+is_exit_instruction({case_end,_}) -> true;
+is_exit_instruction({try_case_end,_}) -> true;
+is_exit_instruction({badmatch,_}) -> true;
+is_exit_instruction(_) -> false.
+
+%% is_label_used_in(LabelNumber, [Instruction]) -> boolean()
+%% Check whether the label is used in the instruction sequence
+%% (including inside blocks).
+
+is_label_used_in(Lbl, Is) ->
+ is_label_used_in_1(Is, Lbl, gb_sets:empty()).
+
+is_label_used_in_1([{block,Block}|Is], Lbl, Empty) ->
+ lists:any(fun(I) -> is_label_used_in_2(I, Lbl) end, Block)
+ orelse is_label_used_in_1(Is, Lbl, Empty);
+is_label_used_in_1([I|Is], Lbl, Empty) ->
+ Used = ulbl(I, Empty),
+ gb_sets:is_member(Lbl, Used) orelse is_label_used_in_1(Is, Lbl, Empty);
+is_label_used_in_1([], _, _) -> false.
+
+is_label_used_in_2({set,_,_,Info}, Lbl) ->
+ case Info of
+ {bif,_,{f,F}} -> F =:= Lbl;
+ {alloc,_,{gc_bif,_,{f,F}}} -> F =:= Lbl;
+ {'catch',{f,F}} -> F =:= Lbl;
+ {alloc,_,_} -> false;
+ {put_tuple,_} -> false;
+ {put_string,_,_} -> false;
+ {get_tuple_element,_} -> false;
+ {set_tuple_element,_} -> false;
+ _ when is_atom(Info) -> false
+ end.
+
+%% remove_unused_labels(Instructions0) -> Instructions
+%% Remove all unused labels. Also remove unreachable
+%% instructions following labels that are removed.
+
+remove_unused_labels(Is) ->
+ Used0 = initial_labels(Is),
+ Used = foldl(fun ulbl/2, Used0, Is),
+ rem_unused(Is, Used, []).
+
+rem_unused([{label,Lbl}=I|Is0], Used, [Prev|_]=Acc) ->
+ case gb_sets:is_member(Lbl, Used) of
+ false ->
+ Is = case is_unreachable_after(Prev) of
+ true ->
+ dropwhile(fun({label,_}) -> false;
+ (_) -> true
+ end, Is0);
+ false -> Is0
+ end,
+ rem_unused(Is, Used, Acc);
+ true ->
+ rem_unused(Is0, Used, [I|Acc])
+ end;
+rem_unused([I|Is], Used, Acc) ->
+ rem_unused(Is, Used, [I|Acc]);
+rem_unused([], _, Acc) -> reverse(Acc).
+
+initial_labels(Is) ->
+ initial_labels(Is, []).
+
+initial_labels([{label,Lbl}|Is], Acc) ->
+ initial_labels(Is, [Lbl|Acc]);
+initial_labels([{func_info,_,_,_},{label,Lbl}|_], Acc) ->
+ gb_sets:from_list([Lbl|Acc]).
+
+%% ulbl(Instruction, UsedGbSet) -> UsedGbSet'
+%% Update the gb_set UsedGbSet with any function-local labels
+%% (i.e. not with labels in call instructions) referenced by
+%% the instruction Instruction.
+%%
+%% NOTE: This function does NOT look for labels inside blocks.
+
+ulbl({test,_,Fail,_}, Used) ->
+ mark_used(Fail, Used);
+ulbl({test,_,Fail,_,_,_}, Used) ->
+ mark_used(Fail, Used);
+ulbl({select_val,_,Fail,{list,Vls}}, Used) ->
+ mark_used_list(Vls, mark_used(Fail, Used));
+ulbl({select_tuple_arity,_,Fail,{list,Vls}}, Used) ->
+ mark_used_list(Vls, mark_used(Fail, Used));
+ulbl({'try',_,Lbl}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({'catch',_,Lbl}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({jump,Lbl}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({loop_rec,Lbl,_}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({loop_rec_end,Lbl}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({wait,Lbl}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({wait_timeout,Lbl,_To}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bif,_Name,Lbl,_As,_R}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({gc_bif,_Name,Lbl,_Live,_As,_R}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_init2,Lbl,_,_,_,_,_}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_init_bits,Lbl,_,_,_,_,_}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_put_integer,Lbl,_Bits,_Unit,_Fl,_Val}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_put_float,Lbl,_Bits,_Unit,_Fl,_Val}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_put_binary,Lbl,_Bits,_Unit,_Fl,_Val}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_put_utf8,Lbl,_Fl,_Val}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_put_utf16,Lbl,_Fl,_Val}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_put_utf32,Lbl,_Fl,_Val}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_add,Lbl,_,_}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_append,Lbl,_,_,_,_,_,_,_}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_utf8_size,Lbl,_,_}, Used) ->
+ mark_used(Lbl, Used);
+ulbl({bs_utf16_size,Lbl,_,_}, Used) ->
+ mark_used(Lbl, Used);
+ulbl(_, Used) -> Used.
+
+mark_used({f,0}, Used) -> Used;
+mark_used({f,L}, Used) -> gb_sets:add(L, Used).
+
+mark_used_list([{f,L}|T], Used) ->
+ mark_used_list(T, gb_sets:add(L, Used));
+mark_used_list([_|T], Used) ->
+ mark_used_list(T, Used);
+mark_used_list([], Used) -> Used.