diff options
39 files changed, 10342 insertions, 320 deletions
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile index 08042aeba4..f036a2d8dc 100644 --- a/lib/compiler/src/Makefile +++ b/lib/compiler/src/Makefile @@ -62,6 +62,15 @@ MODULES = \ beam_opcodes \ beam_peep \ beam_split \ + beam_ssa \ + beam_ssa_codegen \ + beam_ssa_lint \ + beam_ssa_opt \ + beam_ssa_pp \ + beam_ssa_pre_codegen \ + beam_ssa_recv \ + beam_ssa_type \ + beam_kernel_to_ssa \ beam_trim \ beam_utils \ beam_validator \ @@ -86,7 +95,6 @@ MODULES = \ sys_core_fold_lists \ sys_core_inline \ sys_pre_attributes \ - v3_codegen \ v3_core \ v3_kernel \ v3_kernel_pp @@ -95,6 +103,7 @@ BEAM_H = $(wildcard ../priv/beam_h/*.h) HRL_FILES= \ beam_disasm.hrl \ + beam_ssa.hrl \ core_parse.hrl \ v3_kernel.hrl @@ -181,7 +190,16 @@ release_docs_spec: # ---------------------------------------------------- $(EBIN)/beam_disasm.beam: $(EGEN)/beam_opcodes.hrl beam_disasm.hrl -$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl +$(EBIN)/beam_listing.beam: core_parse.hrl v3_kernel.hrl beam_ssa.hrl +$(EBIN)/beam_kernel_to_ssa.beam: v3_kernel.hrl beam_ssa.hrl +$(EBIN)/beam_ssa.beam: beam_ssa.hrl +$(EBIN)/beam_ssa_codegen.beam: beam_ssa.hrl +$(EBIN)/beam_ssa_lint.beam: beam_ssa.hrl +$(EBIN)/beam_ssa_opt.beam: beam_ssa.hrl +$(EBIN)/beam_ssa_pp.beam: beam_ssa.hrl +$(EBIN)/beam_ssa_pre_codegen.beam: beam_ssa.hrl +$(EBIN)/beam_ssa_recv.beam: beam_ssa.hrl +$(EBIN)/beam_ssa_type.beam: beam_ssa.hrl $(EBIN)/cerl.beam: core_parse.hrl $(EBIN)/compile.beam: core_parse.hrl ../../stdlib/include/erl_compile.hrl $(EBIN)/core_lib.beam: core_parse.hrl diff --git a/lib/compiler/src/beam_a.erl b/lib/compiler/src/beam_a.erl index a26f245446..266e8f46c8 100644 --- a/lib/compiler/src/beam_a.erl +++ b/lib/compiler/src/beam_a.erl @@ -39,8 +39,13 @@ function({function,Name,Arity,CLabel,Is0}) -> %% Remove unusued labels for cleanliness and to help %% optimization passes and HiPE. - Is = beam_jump:remove_unused_labels(Is1), - {function,Name,Arity,CLabel,Is} + Is2 = beam_jump:remove_unused_labels(Is1), + + %% Some optimization passes can't handle consecutive labels. + %% Coalesce multiple consecutive labels. + Is = coalesce_consecutive_labels(Is2, [], []), + + {function,Name,Arity,CLabel,Is} catch Class:Error:Stack -> io:fwrite("Function: ~w/~w\n", [Name,Arity]), @@ -122,3 +127,11 @@ rename_instr({select_tuple_arity=I,Reg,Fail,{list,List}}) -> rename_instr(send) -> {call_ext,2,send}; rename_instr(I) -> I. + +coalesce_consecutive_labels([{label,L}=Lbl,{label,Alias}|Is], Replace, Acc) -> + coalesce_consecutive_labels([Lbl|Is], [{Alias,L}|Replace], Acc); +coalesce_consecutive_labels([I|Is], Replace, Acc) -> + coalesce_consecutive_labels(Is, Replace, [I|Acc]); +coalesce_consecutive_labels([], Replace, Acc) -> + D = maps:from_list(Replace), + beam_utils:replace_labels(Acc, [], D, fun(L) -> L end). diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl index 3e28ff2c01..c928fc7187 100644 --- a/lib/compiler/src/beam_block.erl +++ b/lib/compiler/src/beam_block.erl @@ -17,13 +17,12 @@ %% %% %CopyrightEnd% %% -%% Purpose : Partitions assembly instructions into basic blocks and -%% optimizes them. +%% Purpose: Partition BEAM instructions into basic blocks. -module(beam_block). -export([module/2]). --import(lists, [reverse/1]). +-import(lists, [reverse/1,splitwith/2]). -spec module(beam_utils:module_code(), [compile:option()]) -> {'ok',beam_utils:module_code()}. @@ -34,11 +33,8 @@ module({Mod,Exp,Attr,Fs0,Lc}, _Opts) -> function({function,Name,Arity,CLabel,Is0}) -> try - %% Collect basic blocks and optimize them. Is1 = blockify(Is0), Is = embed_lines(Is1), - - %% Done. {function,Name,Arity,CLabel,Is} catch Class:Error:Stack -> @@ -69,12 +65,10 @@ collect_block(Is) -> collect_block(Is, []). collect_block([{allocate,N,R}|Is0], Acc) -> - {Inits,Is} = lists:splitwith(fun ({init,{y,_}}) -> true; - (_) -> false - end, Is0), + {Inits,Is} = splitwith(fun ({init,{y,_}}) -> true; + (_) -> false + end, Is0), collect_block(Is, [{set,[],[],{alloc,R,{nozero,N,0,Inits}}}|Acc]); -collect_block([{allocate_zero,Ns,R},{test_heap,Nh,R}|Is], Acc) -> - collect_block(Is, [{set,[],[],{alloc,R,{zero,Ns,Nh,[]}}}|Acc]); collect_block([I|Is]=Is0, Acc) -> case collect(I) of error -> {reverse(Acc),Is0}; diff --git a/lib/compiler/src/beam_clean.erl b/lib/compiler/src/beam_clean.erl index 5a9023c259..f5f0ac2218 100644 --- a/lib/compiler/src/beam_clean.erl +++ b/lib/compiler/src/beam_clean.erl @@ -23,7 +23,7 @@ -export([module/2]). -export([clean_labels/1]). --import(lists, [foldl/3,reverse/1]). +-import(lists, [foldl/3]). -spec module(beam_utils:module_code(), [compile:option()]) -> {'ok',beam_utils:module_code()}. @@ -36,8 +36,7 @@ module({Mod,Exp,Attr,Fs0,_}, Opts) -> Used = find_all_used(WorkList, All, sets:from_list(WorkList)), Fs1 = remove_unused(Order, Used, All), {Fs2,Lc} = clean_labels(Fs1), - Fs3 = bs_fix(Fs2), - Fs = maybe_remove_lines(Fs3, Opts), + Fs = maybe_remove_lines(Fs2, Opts), {ok,{Mod,Exp,Attr,Fs,Lc}}. %% Determine the rootset, i.e. exported functions and @@ -87,12 +86,8 @@ add_to_work_list(F, {Fs,Used}=Sets) -> %%% %%% Coalesce adjacent labels. Renumber all labels to eliminate gaps. -%%% This cleanup will slightly reduce file size and slightly speed up loading. -%%% -%%% We also expand is_record/3 to a sequence of instructions. It is done -%%% here merely because this module will always be called even if optimization -%%% is turned off. We don't want to do the expansion in beam_asm because we -%%% want to see the expanded code in a .S file. +%%% This cleanup will slightly reduce file size and slightly speed up +%%% loading. %%% -type label() :: beam_asm:label(). @@ -117,45 +112,6 @@ function_renumber([{function,Name,Arity,_Entry,Asm0}|Fs], St0, Acc) -> function_renumber(Fs, St, [{function,Name,Arity,St#st.entry,Asm}|Acc]); function_renumber([], St, Acc) -> {Acc,St}. -renumber_labels([{bif,is_record,{f,_}, - [Term,{atom,Tag}=TagAtom,{integer,Arity}],Dst}|Is0], Acc, St) -> - ContLabel = 900000000+2*St#st.lc, - FailLabel = ContLabel+1, - Fail = {f,FailLabel}, - Tmp = Dst, - Is = case is_record_tuple(Term, Tag, Arity) of - yes -> - [{move,{atom,true},Dst}|Is0]; - no -> - [{move,{atom,false},Dst}|Is0]; - maybe -> - [{test,is_tuple,Fail,[Term]}, - {test,test_arity,Fail,[Term,Arity]}, - {get_tuple_element,Term,0,Tmp}, - {test,is_eq_exact,Fail,[Tmp,TagAtom]}, - {move,{atom,true},Dst}, - {jump,{f,ContLabel}}, - {label,FailLabel}, - {move,{atom,false},Dst}, - {jump,{f,ContLabel}}, %Improves optimization by beam_dead. - {label,ContLabel}|Is0] - end, - renumber_labels(Is, Acc, St); -renumber_labels([{test,is_record,{f,_}=Fail, - [Term,{atom,Tag}=TagAtom,{integer,Arity}]}|Is0], Acc, St) -> - Tmp = {x,1022}, - Is = case is_record_tuple(Term, Tag, Arity) of - yes -> - Is0; - no -> - [{jump,Fail}|Is0]; - maybe -> - [{test,is_tuple,Fail,[Term]}, - {test,test_arity,Fail,[Term,Arity]}, - {get_tuple_element,Term,0,Tmp}, - {test,is_eq_exact,Fail,[Tmp,TagAtom]}|Is0] - end, - renumber_labels(Is, Acc, St); renumber_labels([{label,Old}|Is], [{label,New}|_]=Acc, #st{lmap=D0}=St) -> D = [{Old,New}|D0], renumber_labels(Is, Acc, St#st{lmap=D}); @@ -169,12 +125,6 @@ renumber_labels([I|Is], Acc, St0) -> renumber_labels(Is, [I|Acc], St0); renumber_labels([], Acc, St) -> {Acc,St}. -is_record_tuple({x,_}, _, _) -> maybe; -is_record_tuple({y,_}, _, _) -> maybe; -is_record_tuple({literal,Tuple}, Tag, Arity) - when element(1, Tuple) =:= Tag, tuple_size(Tuple) =:= Arity -> yes; -is_record_tuple(_, _, _) -> no. - function_replace([{function,Name,Arity,Entry,Asm0}|Fs], Dict, Acc) -> Asm = try Fb = fun(Old) -> throw({error,{undefined_label,Old}}) end, @@ -189,91 +139,6 @@ function_replace([{function,Name,Arity,Entry,Asm0}|Fs], Dict, Acc) -> function_replace([], _, Acc) -> Acc. %%% -%%% Final fixup of bs_start_match2/5,bs_save2/bs_restore2 instructions for -%%% new bit syntax matching (introduced in R11B). -%%% -%%% Pass 1: Scan the code, looking for bs_restore2/2 instructions. -%%% -%%% Pass 2: Update bs_save2/2 and bs_restore/2 instructions. Remove -%%% any bs_save2/2 instruction whose save position are never referenced -%%% by any bs_restore2/2 instruction. -%%% -%%% Note this module can be invoked several times, so we must be careful -%%% not to touch instructions that have already been fixed up. -%%% - -bs_fix(Fs) -> - bs_fix(Fs, []). - -bs_fix([{function,Name,Arity,Entry,Asm0}|Fs], Acc) -> - Asm = bs_function(Asm0), - bs_fix(Fs, [{function,Name,Arity,Entry,Asm}|Acc]); -bs_fix([], Acc) -> reverse(Acc). - -bs_function(Is) -> - Dict0 = bs_restores(Is, []), - S0 = sofs:relation(Dict0, [{context,save_point}]), - S1 = sofs:relation_to_family(S0), - S = sofs:to_external(S1), - Dict = make_save_point_dict(S, []), - bs_replace(Is, Dict, []). - -make_save_point_dict([{Ctx,Pts}|T], Acc0) -> - Acc = make_save_point_dict_1(Pts, Ctx, 0, Acc0), - make_save_point_dict(T, Acc); -make_save_point_dict([], Acc) -> - gb_trees:from_orddict(ordsets:from_list(Acc)). - -make_save_point_dict_1([H|T], Ctx, I, Acc) -> - make_save_point_dict_1(T, Ctx, I+1, [{{Ctx,H},I}|Acc]); -make_save_point_dict_1([], Ctx, I, Acc) -> - [{Ctx,I}|Acc]. - -%% Pass 1. -bs_restores([{bs_restore2,_,{Same,Same}}|Is], Dict) -> - %% This save point is special. No explicit save is needed. - bs_restores(Is, Dict); -bs_restores([{bs_restore2,_,{atom,start}}|Is], Dict) -> - %% This instruction can occur if "compilation" - %% started from a .S file. - bs_restores(Is, Dict); -bs_restores([{bs_restore2,_,{_,_}=SavePoint}|Is], Dict) -> - bs_restores(Is, [SavePoint|Dict]); -bs_restores([_|Is], Dict) -> - bs_restores(Is, Dict); -bs_restores([], Dict) -> Dict. - -%% Pass 2. -bs_replace([{test,bs_start_match2,F,Live,[Src,{context,Ctx}],CtxR}|T], Dict, Acc) -> - Slots = case gb_trees:lookup(Ctx, Dict) of - {value,Slots0} -> Slots0; - none -> 0 - end, - I = {test,bs_start_match2,F,Live,[Src,Slots],CtxR}, - bs_replace(T, Dict, [I|Acc]); -bs_replace([{bs_save2,CtxR,{_,_}=SavePoint}|T], Dict, Acc) -> - case gb_trees:lookup(SavePoint, Dict) of - {value,N} -> - bs_replace(T, Dict, [{bs_save2,CtxR,N}|Acc]); - none -> - bs_replace(T, Dict, Acc) - end; -bs_replace([{bs_restore2,_,{atom,start}}=I|T], Dict, Acc) -> - %% This instruction can occur if "compilation" - %% started from a .S file. - bs_replace(T, Dict, [I|Acc]); -bs_replace([{bs_restore2,CtxR,{Same,Same}}|T], Dict, Acc) -> - %% This save point refers to the point in the binary where the match - %% started. It has a special name. - bs_replace(T, Dict, [{bs_restore2,CtxR,{atom,start}}|Acc]); -bs_replace([{bs_restore2,CtxR,{_,_}=SavePoint}|T], Dict, Acc) -> - N = gb_trees:get(SavePoint, Dict), - bs_replace(T, Dict, [{bs_restore2,CtxR,N}|Acc]); -bs_replace([I|Is], Dict, Acc) -> - bs_replace(Is, Dict, [I|Acc]); -bs_replace([], _, Acc) -> reverse(Acc). - -%%% %%% Remove line instructions if requested. %%% diff --git a/lib/compiler/src/beam_dead.erl b/lib/compiler/src/beam_dead.erl index 1004f6048b..546f0461b9 100644 --- a/lib/compiler/src/beam_dead.erl +++ b/lib/compiler/src/beam_dead.erl @@ -325,25 +325,11 @@ backward([{test,Op,{f,To0},Ops0}|Is], D, Acc) -> is_eq_exact -> combine_eqs(To, Ops0, D, Acc); _ -> {test,Op,{f,To},Ops0} end, - case {I,Acc} of - {{test,is_atom,Fail,Ops0},[{test,is_boolean,Fail,Ops0}|_]} -> - %% An is_atom test before an is_boolean test (with the - %% same failure label) is redundant. - backward(Is, D, Acc); - {{test,is_atom,Fail,[R]}, - [{test,is_eq_exact,Fail,[R,{atom,_}]}|_]} -> - %% An is_atom test before a comparison with an atom (with - %% the same failure label) is redundant. - backward(Is, D, Acc); - {{test,is_integer,Fail,[R]}, - [{test,is_eq_exact,Fail,[R,{integer,_}]}|_]} -> - %% An is_integer test before a comparison with an integer - %% (with the same failure label) is redundant. - backward(Is, D, Acc); - {{test,_,_,_},_} -> + case I of + {test,_,_,_} -> %% Still a test instruction. Done. backward(Is, D, [I|Acc]); - {_,_} -> + _ -> %% Rewritten to a select_val. Rescan. backward([I|Is], D, Acc) end; diff --git a/lib/compiler/src/beam_flatten.erl b/lib/compiler/src/beam_flatten.erl index 20bd23a912..9a1c5a1021 100644 --- a/lib/compiler/src/beam_flatten.erl +++ b/lib/compiler/src/beam_flatten.erl @@ -43,13 +43,15 @@ block([{block,Is0}|Is1], Acc) -> block(Is1, norm_block(Is0, Acc)); block([I|Is], Acc) -> block(Is, [I|Acc]); block([], Acc) -> reverse(Acc). -norm_block([{set,[],[],{alloc,R,Alloc}}|Is], Acc0) -> +norm_block([{set,[],[],{alloc,R,{_,nostack,_,_}=Alloc}}|Is], Acc0) -> case insert_alloc_in_bs_init(Acc0, Alloc) of impossible -> norm_block(Is, reverse(norm_allocate(Alloc, R), Acc0)); Acc -> norm_block(Is, Acc) end; +norm_block([{set,[],[],{alloc,R,Alloc}}|Is], Acc0) -> + norm_block(Is, reverse(norm_allocate(Alloc, R), Acc0)); norm_block([{set,[D1],[S],get_hd},{set,[D2],[S],get_tl}|Is], Acc) -> I = {get_list,S,D1,D2}, norm_block(Is, [I|Acc]); diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index 43084ad588..42b4cdaf4f 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -610,6 +610,12 @@ ulbl({put_map,Lbl,_Op,_Src,_Dst,_Live,_List}, Used) -> mark_used(Lbl, Used); ulbl({get_map_elements,Lbl,_Src,_List}, Used) -> mark_used(Lbl, Used); +ulbl({recv_mark,Lbl}, Used) -> + mark_used(Lbl, Used); +ulbl({recv_set,Lbl}, Used) -> + mark_used(Lbl, Used); +ulbl({fcheckerror,Lbl}, Used) -> + mark_used(Lbl, Used); ulbl(_, Used) -> Used. mark_used({f,0}, Used) -> Used; diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl new file mode 100644 index 0000000000..c55a57ab32 --- /dev/null +++ b/lib/compiler/src/beam_kernel_to_ssa.erl @@ -0,0 +1,1330 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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% +%% +%% Purpose: Convert the Kernel Erlang format to the SSA format. + +-module(beam_kernel_to_ssa). + +%% The main interface. +-export([module/2]). + +-import(lists, [append/1,duplicate/2,flatmap/2,foldl/3, + keysort/2,mapfoldl/3,map/2,member/2, + reverse/1,reverse/2,sort/1]). + +-include("v3_kernel.hrl"). +-include("beam_ssa.hrl"). + +-type label() :: beam_ssa:label(). + +%% Main codegen structure. +-record(cg, {lcount=1 :: label(), %Label counter + bfail=1 :: label(), + catch_label=none :: 'none' | label(), + vars=#{} :: map(), %Defined variables. + break=0 :: label(), %Break label + recv=0 :: label(), %Receive label + ultimate_failure=0 :: label() %Label for ultimate match failure. + }). + +%% Internal records. +-record(cg_break, {args :: [beam_ssa:value()], + phi :: label() + }). +-record(cg_phi, {vars :: [beam_ssa:b_var()] + }). +-record(cg_unreachable, {}). + +-spec module(#k_mdef{}, [compile:option()]) -> {'ok',#b_module{}}. + +module(#k_mdef{name=Mod,exports=Es,attributes=Attr,body=Forms}, _Opts) -> + Body = functions(Forms, Mod), + Module = #b_module{name=Mod,exports=Es,attributes=Attr,body=Body}, + {ok,Module}. + +functions(Forms, Mod) -> + [function(F, Mod) || F <- Forms]. + +function(#k_fdef{anno=Anno0,func=Name,arity=Arity, + vars=As0,body=Kb}, Mod) -> + try + #k_match{} = Kb, %Assertion. + + %% Generate the SSA form immediate format. + St0 = #cg{}, + {As,St1} = new_ssa_vars(As0, St0), + {Asm,St} = cg_fun(Kb, St1), + Anno1 = line_anno(Anno0), + Anno = Anno1#{func_info=>{Mod,Name,Arity}}, + #b_function{anno=Anno,args=As,bs=Asm,cnt=St#cg.lcount} + catch + Class:Error:Stack -> + io:fwrite("Function: ~w/~w\n", [Name,Arity]), + erlang:raise(Class, Error, Stack) + end. + +%% cg_fun([Lkexpr], [HeadVar], State) -> {[Ainstr],State} + +cg_fun(Ke, St0) -> + {UltimateFail,FailIs,St1} = make_failure(badarg, St0), + St2 = St1#cg{bfail=UltimateFail,ultimate_failure=UltimateFail}, + {B,St} = cg(Ke, St2), + Asm = [{label,0}|B++FailIs], + finalize(Asm, St). + +make_failure(Reason, St0) -> + {Lbl,St1} = new_label(St0), + {Dst,St} = new_ssa_var('@ssa_ret', St1), + Is = [{label,Lbl}, + #b_set{op=call,dst=Dst, + args=[#b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=error}, + arity=1}, + #b_literal{val=Reason}]}, + #b_ret{arg=Dst}], + {Lbl,Is,St}. + +%% cg(Lkexpr, State) -> {[Ainstr],State}. +%% Generate code for a kexpr. + +cg(#k_match{body=M,ret=Rs}, St) -> + do_match_cg(M, Rs, St); +cg(#k_guard_match{body=M,ret=Rs}, St) -> + do_match_cg(M, Rs, St); +cg(#k_seq{arg=Arg,body=Body}, St0) -> + {ArgIs,St1} = cg(Arg, St0), + {BodyIs,St} = cg(Body, St1), + {ArgIs++BodyIs,St}; +cg(#k_call{anno=Le,op=Func,args=As,ret=Rs}, St) -> + call_cg(Func, As, Rs, Le, St); +cg(#k_enter{anno=Le,op=Func,args=As}, St) -> + enter_cg(Func, As, Le, St); +cg(#k_bif{anno=Le}=Bif, St) -> + bif_cg(Bif, Le, St); +cg(#k_try{arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th,ret=Rs}, St) -> + try_cg(Ta, Vs, Tb, Evs, Th, Rs, St); +cg(#k_try_enter{arg=Ta,vars=Vs,body=Tb,evars=Evs,handler=Th}, St) -> + try_enter_cg(Ta, Vs, Tb, Evs, Th, St); +cg(#k_catch{body=Cb,ret=[R]}, St) -> + do_catch_cg(Cb, R, St); +cg(#k_receive{anno=Le,timeout=Te,var=Rvar,body=Rm,action=Tes,ret=Rs}, St) -> + recv_loop_cg(Te, Rvar, Rm, Tes, Rs, Le, St); +cg(#k_receive_next{}, #cg{recv=Recv}=St) -> + Is = [#b_set{op=recv_next},make_uncond_branch(Recv)], + {Is,St}; +cg(#k_receive_accept{}, St) -> + Remove = #b_set{op=remove_message}, + {[Remove],St}; +cg(#k_put{anno=Le,arg=Con,ret=Var}, St) -> + put_cg(Var, Con, Le, St); +cg(#k_return{args=[Ret0]}, St) -> + Ret = ssa_arg(Ret0, St), + {[#b_ret{arg=Ret}],St}; +cg(#k_break{args=Bs}, #cg{break=Br}=St) -> + Args = ssa_args(Bs, St), + {[#cg_break{args=Args,phi=Br}],St}; +cg(#k_guard_break{args=Bs}, St) -> + cg(#k_break{args=Bs}, St). + +%% match_cg(Matc, [Ret], State) -> {[Ainstr],State}. +%% Generate code for a match. + +do_match_cg(M, Rs, St0) -> + {B,St1} = new_label(St0), + {Mis,St2} = match_cg(M, St1#cg.bfail, St1#cg{break=B}), + {BreakVars,St} = new_ssa_vars(Rs, St2), + {Mis ++ [{label,B},#cg_phi{vars=BreakVars}], + St#cg{bfail=St0#cg.bfail,break=St1#cg.break}}. + +%% match_cg(Match, Fail, State) -> {[Ainstr],State}. +%% Generate code for a match tree. + +match_cg(#k_alt{first=F,then=S}, Fail, St0) -> + {Tf,St1} = new_label(St0), + {Fis,St2} = match_cg(F, Tf, St1), + {Sis,St3} = match_cg(S, Fail, St2), + {Fis ++ [{label,Tf}] ++ Sis,St3}; +match_cg(#k_select{var=#k_var{}=V,types=Scs}, Fail, St) -> + match_fmf(fun (S, F, Sta) -> + select_cg(S, V, F, Fail, Sta) + end, Fail, St, Scs); +match_cg(#k_guard{clauses=Gcs}, Fail, St) -> + match_fmf(fun (G, F, Sta) -> + guard_clause_cg(G, F, Sta) + end, Fail, St, Gcs); +match_cg(Ke, _Fail, St0) -> + cg(Ke, St0). + +%% select_cg(Sclause, V, TypeFail, ValueFail, State) -> {Is,State}. +%% Selecting type and value needs two failure labels, TypeFail is the +%% label to jump to of the next type test when this type fails, and +%% ValueFail is the label when this type is correct but the value is +%% wrong. These are different as in the second case there is no need +%% to try the next type, it will always fail. + +select_cg(#k_type_clause{type=k_binary,values=[S]}, Var, Tf, Vf, St) -> + select_binary(S, Var, Tf, Vf, St); +select_cg(#k_type_clause{type=k_bin_seg,values=Vs}, Var, Tf, _Vf, St) -> + select_bin_segs(Vs, Var, Tf, St); +select_cg(#k_type_clause{type=k_bin_int,values=Vs}, Var, Tf, _Vf, St) -> + select_bin_segs(Vs, Var, Tf, St); +select_cg(#k_type_clause{type=k_bin_end,values=[S]}, Var, Tf, _Vf, St) -> + select_bin_end(S, Var, Tf, St); +select_cg(#k_type_clause{type=k_map,values=Vs}, Var, Tf, Vf, St) -> + select_map(Vs, Var, Tf, Vf, St); +select_cg(#k_type_clause{type=k_cons,values=[S]}, Var, Tf, Vf, St) -> + select_cons(S, Var, Tf, Vf, St); +select_cg(#k_type_clause{type=k_nil,values=[S]}, Var, Tf, Vf, St) -> + select_nil(S, Var, Tf, Vf, St); +select_cg(#k_type_clause{type=k_literal,values=Vs}, Var, Tf, Vf, St) -> + select_literal(Vs, Var, Tf, Vf, St); +select_cg(#k_type_clause{type=Type,values=Scs}, Var, Tf, Vf, St0) -> + {Vis,St1} = + mapfoldl(fun (S, Sta) -> + {Val,Is,Stb} = select_val(S, Var, Vf, Sta), + {{Is,[Val]},Stb} + end, St0, Scs), + OptVls = combine(lists:sort(combine(Vis))), + {Vls,Sis,St2} = select_labels(OptVls, St1, [], []), + Arg = ssa_arg(Var, St2), + {Is,St} = select_val_cg(Type, Arg, Vls, Tf, Vf, Sis, St2), + {Is,St}. + +select_val_cg(k_tuple, Tuple, Vls, Tf, Vf, Sis, St0) -> + {Is0,St1} = make_cond_branch({bif,is_tuple}, [Tuple], Tf, St0), + {Arity,St2} = new_ssa_var('@ssa_arity', St1), + GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]}, + {Is,St} = select_val_cg(k_int, Arity, Vls, Vf, Vf, Sis, St2), + {Is0++[GetArity]++Is,St}; +select_val_cg(Type, R, Vls, Tf, Vf, Sis, St0) -> + {TypeIs,St1} = if + Tf =:= Vf -> + %% The type and value failure labels are the same; + %% we don't need a type test. + {[],St0}; + true -> + %% Different labels for type failure and + %% label failure; we need a type test. + Test = select_type_test(Type), + make_cond_branch(Test, [R], Tf, St0) + end, + case Vls of + [{Val,Succ}] -> + {Is,St} = make_cond({bif,'=:='}, [R,Val], Vf, Succ, St1), + {TypeIs++Is++Sis,St}; + [_|_] -> + {TypeIs++[#b_switch{arg=R,fail=Vf,list=Vls}|Sis],St1} + end. + +select_type_test(k_int) -> {bif,is_integer}; +select_type_test(k_atom) -> {bif,is_atom}; +select_type_test(k_float) -> {bif,is_float}. + +combine([{Is,Vs1},{Is,Vs2}|Vis]) -> combine([{Is,Vs1 ++ Vs2}|Vis]); +combine([V|Vis]) -> [V|combine(Vis)]; +combine([]) -> []. + +select_labels([{Is,Vs}|Vis], St0, Vls, Sis) -> + {Lbl,St1} = new_label(St0), + select_labels(Vis, St1, add_vls(Vs, Lbl, Vls), [[{label,Lbl}|Is]|Sis]); +select_labels([], St, Vls, Sis) -> + {Vls,append(Sis),St}. + +add_vls([V|Vs], Lbl, Acc) -> + add_vls(Vs, Lbl, [{#b_literal{val=V},Lbl}|Acc]); +add_vls([], _, Acc) -> Acc. + +select_literal(S, V, Tf, Vf, St) -> + Src = ssa_arg(V, St), + F = fun(ValClause, Fail, St0) -> + {Val,ValIs,St1} = select_val(ValClause, V, Vf, St0), + Args = [Src,#b_literal{val=Val}], + {Is,St2} = make_cond_branch({bif,'=:='}, Args, Fail, St1), + {Is++ValIs,St2} + end, + match_fmf(F, Tf, St, S). + +select_cons(#k_val_clause{val=#k_cons{hd=Hd,tl=Tl},body=B}, + V, Tf, Vf, St0) -> + Es = [Hd,Tl], + {Eis,St1} = select_extract_cons(V, Es, St0), + {Bis,St2} = match_cg(B, Vf, St1), + Src = ssa_arg(V, St2), + {Is,St} = make_cond_branch(is_nonempty_list, [Src], Tf, St2), + {Is ++ Eis ++ Bis,St}. + +select_nil(#k_val_clause{val=#k_nil{},body=B}, V, Tf, Vf, St0) -> + {Bis,St1} = match_cg(B, Vf, St0), + Src = ssa_arg(V, St1), + {Is,St} = make_cond_branch({bif,'=:='}, [Src,#b_literal{val=[]}], Tf, St1), + {Is ++ Bis,St}. + +select_binary(#k_val_clause{val=#k_binary{segs=#k_var{name=Ctx0}},body=B}, + #k_var{anno=Anno0}=Src, Tf, Vf, St0) -> + Anno = #{reuse_for_context=>member(reuse_for_context, Anno0)}, + {Ctx,St1} = new_ssa_var(Ctx0, St0), + {Bis0,St2} = match_cg(B, Vf, St1), + {TestIs,St} = make_cond_branch(succeeded, [Ctx], Tf, St2), + Bis1 = [#b_set{anno=Anno,op=bs_start_match,dst=Ctx, + args=[ssa_arg(Src, St)]}] ++ TestIs ++ Bis0, + Bis = finish_bs_matching(Bis1), + {Bis,St}. + +finish_bs_matching([#b_set{op=bs_match, + args=[#b_literal{val=string},Ctx,#b_literal{val=BinList}]}=Set|Is]) + when is_list(BinList) -> + I = Set#b_set{args=[#b_literal{val=string},Ctx, + #b_literal{val=list_to_bitstring(BinList)}]}, + finish_bs_matching([I|Is]); +finish_bs_matching([I|Is]) -> + [I|finish_bs_matching(Is)]; +finish_bs_matching([]) -> []. + +make_cond(Cond, Args, Fail, Succ, St0) -> + {Bool,St} = new_ssa_var('@ssa_bool', St0), + Bif = #b_set{op=Cond,dst=Bool,args=Args}, + Br = #b_br{bool=Bool,succ=Succ,fail=Fail}, + {[Bif,Br],St}. + +make_cond_branch(Cond, Args, Fail, St0) -> + {Bool,St1} = new_ssa_var('@ssa_bool', St0), + {Succ,St} = new_label(St1), + Bif = #b_set{op=Cond,dst=Bool,args=Args}, + Br = #b_br{bool=Bool,succ=Succ,fail=Fail}, + {[Bif,Br,{label,Succ}],St}. + +make_uncond_branch(Fail) -> + #b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail}. + +%% Instructions for selection of binary segments. + +select_bin_segs(Scs, Ivar, Tf, St) -> + match_fmf(fun(S, Fail, Sta) -> + select_bin_seg(S, Ivar, Fail, Sta) + end, Tf, St, Scs). + +select_bin_seg(#k_val_clause{val=#k_bin_seg{size=Size,unit=U,type=T, + seg=Seg,flags=Fs,next=Next}, + body=B,anno=Anno}, + #k_var{}=Src, Fail, St0) -> + LineAnno = line_anno(Anno), + Ctx = get_context(Src, St0), + {Mis,St1} = select_extract_bin(Next, Size, U, T, Fs, Fail, + Ctx, LineAnno, St0), + {Extracted,St2} = new_ssa_var(Seg#k_var.name, St1), + {Bis,St} = bin_match_cg(Size, B, Fail, St2), + BsGet = #b_set{op=bs_extract,dst=Extracted,args=[ssa_arg(Next, St)]}, + Is = Mis ++ [BsGet] ++ Bis, + {Is,St}; +select_bin_seg(#k_val_clause{val=#k_bin_int{size=Sz,unit=U,flags=Fs, + val=Val,next=Next}, + body=B}, + #k_var{}=Src, Fail, St0) -> + Ctx = get_context(Src, St0), + {Mis,St1} = select_extract_int(Next, Val, Sz, U, Fs, Fail, + Ctx, St0), + {Bis,St} = match_cg(B, Fail, St1), + Is = case Mis ++ Bis of + [#b_set{op=bs_match,args=[#b_literal{val=string},OtherCtx1,Bin1]}, + #b_set{op=succeeded,dst=Bool1}, + #b_br{bool=Bool1,succ=Succ,fail=Fail}, + {label,Succ}, + #b_set{op=bs_match,dst=Dst,args=[#b_literal{val=string},_OtherCtx2,Bin2]}| + [#b_set{op=succeeded,dst=Bool2}, + #b_br{bool=Bool2,fail=Fail}|_]=Is0] -> + %% We used to do this optimization later, but it + %% turns out that in huge functions with many + %% string matching instructions, it's a huge win + %% to do the combination now. To avoid copying the + %% binary data again and again, we'll combine bitstrings + %% in a list and convert all of it to a bitstring later. + {#b_literal{val=B1},#b_literal{val=B2}} = {Bin1,Bin2}, + Bin = #b_literal{val=[B1,B2]}, + Set = #b_set{op=bs_match,dst=Dst,args=[#b_literal{val=string},OtherCtx1,Bin]}, + [Set|Is0]; + Is0 -> + Is0 + end, + {Is,St}. + +bin_match_cg(#k_atom{val=all}, B0, Fail, St) -> + #k_select{types=Types} = B0, + [#k_type_clause{type=k_bin_end,values=Values}] = Types, + [#k_val_clause{val=#k_bin_end{},body=B}] = Values, + match_cg(B, Fail, St); +bin_match_cg(_, B, Fail, St) -> + match_cg(B, Fail, St). + +get_context(#k_var{}=Var, St) -> + ssa_arg(Var, St). + +select_bin_end(#k_val_clause{val=#k_bin_end{},body=B}, Src, Tf, St0) -> + Ctx = get_context(Src, St0), + {Bis,St1} = match_cg(B, Tf, St0), + {TestIs,St} = make_cond_branch(bs_test_tail, [Ctx,#b_literal{val=0}], Tf, St1), + Is = TestIs++Bis, + {Is,St}. + +select_extract_bin(#k_var{name=Hd}, Size0, Unit, Type, Flags, Vf, + Ctx, Anno, St0) -> + {Dst,St1} = new_ssa_var(Hd, St0), + Size = ssa_arg(Size0, St0), + build_bs_instr(Anno, Type, Vf, Ctx, Size, Unit, Flags, Dst, St1). + +select_extract_int(#k_var{name=Tl}, 0, #k_int{val=0}, _U, _Fs, _Vf, + Ctx, St0) -> + St = set_ssa_var(Tl, Ctx, St0), + {[],St}; +select_extract_int(#k_var{name=Tl}, Val, #k_int{val=Sz}, U, Fs, Vf, + Ctx, St0) -> + {Dst,St1} = new_ssa_var(Tl, St0), + Bits = U*Sz, + Bin = case member(big, Fs) of + true -> + <<Val:Bits>>; + false -> + true = member(little, Fs), %Assertion. + <<Val:Bits/little>> + end, + Bits = bit_size(Bin), %Assertion. + {TestIs,St} = make_cond_branch(succeeded, [Dst], Vf, St1), + Set = #b_set{op=bs_match,dst=Dst, + args=[#b_literal{val=string},Ctx,#b_literal{val=Bin}]}, + {[Set|TestIs],St}. + +build_bs_instr(Anno, Type, Fail, Ctx, Size, Unit0, Flags0, Dst, St0) -> + Unit = #b_literal{val=Unit0}, + Flags = #b_literal{val=Flags0}, + NeedSize = bs_need_size(Type), + TypeArg = #b_literal{val=Type}, + Get = case NeedSize of + true -> + #b_set{anno=Anno,op=bs_match,dst=Dst, + args=[TypeArg,Ctx,Flags,Size,Unit]}; + false -> + #b_set{anno=Anno,op=bs_match,dst=Dst, + args=[TypeArg,Ctx,Flags]} + end, + {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St0), + {[Get|Is],St}. + +select_val(#k_val_clause{val=#k_tuple{es=Es},body=B}, V, Vf, St0) -> + #k{us=Used} = k_get_anno(B), + {Eis,St1} = select_extract_tuple(V, Es, Used, St0), + {Bis,St2} = match_cg(B, Vf, St1), + {length(Es),Eis ++ Bis,St2}; +select_val(#k_val_clause{val=Val0,body=B}, _V, Vf, St0) -> + Val = case Val0 of + #k_atom{val=Lit} -> Lit; + #k_float{val=Lit} -> Lit; + #k_int{val=Lit} -> Lit; + #k_literal{val=Lit} -> Lit + end, + {Bis,St1} = match_cg(B, Vf, St0), + {Val,Bis,St1}. + +%% select_extract_tuple(Src, [V], State) -> {[E],State}. +%% Extract tuple elements, but only if they are actually used. +%% +%% Not extracting tuple elements that are not used is an +%% optimization for compile time and memory use during compilation. +%% It is probably worthwhile because it is common to extract only a +%% few elements from a huge record. + +select_extract_tuple(Src, Vs, Used, St0) -> + Tuple = ssa_arg(Src, St0), + F = fun (#k_var{name=V}, {Elem,S0}) -> + case member(V, Used) of + true -> + Args = [Tuple,#b_literal{val=Elem}], + {Dst,S} = new_ssa_var(V, S0), + Get = #b_set{op=get_tuple_element,dst=Dst,args=Args}, + {[Get],{Elem+1,S}}; + false -> + {[],{Elem+1,S0}} + end + end, + {Es,{_,St}} = flatmapfoldl(F, {0,St0}, Vs), + {Es,St}. + +select_map(Scs, V, Tf, Vf, St0) -> + MapSrc = ssa_arg(V, St0), + {Is,St1} = + match_fmf(fun(#k_val_clause{val=#k_map{op=exact,es=Es}, + body=B}, Fail, St1) -> + select_map_val(V, Es, B, Fail, St1) + end, Vf, St0, Scs), + {TestIs,St} = make_cond_branch({bif,is_map}, [MapSrc], Tf, St1), + {TestIs++Is,St}. + +select_map_val(V, Es, B, Fail, St0) -> + {Eis,St1} = select_extract_map(Es, V, Fail, St0), + {Bis,St2} = match_cg(B, Fail, St1), + {Eis++Bis,St2}. + +select_extract_map([P|Ps], Src, Fail, St0) -> + MapSrc = ssa_arg(Src, St0), + #k_map_pair{key=Key0,val=#k_var{name=Dst0}} = P, + Key = ssa_arg(Key0, St0), + {Dst,St1} = new_ssa_var(Dst0, St0), + Set = #b_set{op=get_map_element,dst=Dst,args=[MapSrc,Key]}, + {TestIs,St2} = make_cond_branch(succeeded, [Dst], Fail, St1), + {Is,St} = select_extract_map(Ps, Src, Fail, St2), + {[Set|TestIs]++Is,St}; +select_extract_map([], _, _, St) -> + {[],St}. + +select_extract_cons(Src0, [#k_var{name=Hd},#k_var{name=Tl}], St0) -> + Src = ssa_arg(Src0, St0), + {HdDst,St1} = new_ssa_var(Hd, St0), + {TlDst,St2} = new_ssa_var(Tl, St1), + GetHd = #b_set{op=get_hd,dst=HdDst,args=[Src]}, + GetTl = #b_set{op=get_tl,dst=TlDst,args=[Src]}, + {[GetHd,GetTl],St2}. + +guard_clause_cg(#k_guard_clause{guard=G,body=B}, Fail, St0) -> + {Gis,St1} = guard_cg(G, Fail, St0), + {Bis,St} = match_cg(B, Fail, St1), + {Gis ++ Bis,St}. + +%% guard_cg(Guard, Fail, State) -> {[Ainstr],State}. +%% A guard is a boolean expression of tests. Tests return true or +%% false. A fault in a test causes the test to return false. Tests +%% never return the boolean, instead we generate jump code to go to +%% the correct exit point. Primops and tests all go to the next +%% instruction on success or jump to a failure label. + +guard_cg(#k_protected{arg=Ts,ret=Rs,inner=Inner}, Fail, St) -> + protected_cg(Ts, Rs, Inner, Fail, St); +guard_cg(#k_test{op=Test0,args=As,inverted=Inverted}, Fail, St0) -> + #k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Test}} = Test0, + test_cg(Test, Inverted, As, Fail, St0); +guard_cg(#k_seq{arg=Arg,body=Body}, Fail, St0) -> + {ArgIs,St1} = guard_cg(Arg, Fail, St0), + {BodyIs,St} = guard_cg(Body, Fail, St1), + {ArgIs++BodyIs,St}; +guard_cg(G, _Fail, St) -> + cg(G, St). + +test_cg('=/=', Inverted, As, Fail, St) -> + test_cg('=:=', not Inverted, As, Fail, St); +test_cg('/=', Inverted, As, Fail, St) -> + test_cg('==', not Inverted, As, Fail, St); +test_cg(Test, Inverted, As0, Fail, St0) -> + As = ssa_args(As0, St0), + case {Test,ssa_args(As0, St0)} of + {is_record,[Tuple,#b_literal{val=Atom}=Tag,#b_literal{val=Int}=Arity]} + when is_atom(Atom), is_integer(Int) -> + test_is_record_cg(Inverted, Fail, Tuple, Tag, Arity, St0); + {_,As} -> + {Bool,St1} = new_ssa_var('@ssa_bool', St0), + {Succ,St} = new_label(St1), + Bif = #b_set{op={bif,Test},dst=Bool,args=As}, + Br = case Inverted of + false -> #b_br{bool=Bool,succ=Succ,fail=Fail}; + true -> #b_br{bool=Bool,succ=Fail,fail=Succ} + end, + {[Bif,Br,{label,Succ}],St} + end. + +test_is_record_cg(false, Fail, Tuple, TagVal, ArityVal, St0) -> + {Arity,St1} = new_ssa_var('@ssa_arity', St0), + {Tag,St2} = new_ssa_var('@ssa_tag', St1), + {Is0,St3} = make_cond_branch({bif,is_tuple}, [Tuple], Fail, St2), + GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]}, + {Is1,St4} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], Fail, St3), + GetTag = #b_set{op=get_tuple_element,dst=Tag, + args=[Tuple,#b_literal{val=0}]}, + {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], Fail, St4), + Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2, + {Is,St}; +test_is_record_cg(true, Fail, Tuple, TagVal, ArityVal, St0) -> + {Succ,St1} = new_label(St0), + {Arity,St2} = new_ssa_var('@ssa_arity', St1), + {Tag,St3} = new_ssa_var('@ssa_tag', St2), + {Is0,St4} = make_cond_branch({bif,is_tuple}, [Tuple], Succ, St3), + GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]}, + {Is1,St5} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], Succ, St4), + GetTag = #b_set{op=get_tuple_element,dst=Tag, + args=[Tuple,#b_literal{val=0}]}, + {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], Succ, St5), + Is3 = [make_uncond_branch(Fail),{label,Succ}], + Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2 ++ Is3, + {Is,St}. + +%% protected_cg([Kexpr], [Ret], Fail, St) -> {[Ainstr],St}. +%% Do a protected. Protecteds without return values are just done +%% for effect, the return value is not checked, success passes on to +%% the next instruction and failure jumps to Fail. If there are +%% return values then these must be set to 'false' on failure, +%% control always passes to the next instruction. + +protected_cg(Ts, [], _, Fail, St0) -> + %% Protect these calls, revert when done. + {Tis,St1} = guard_cg(Ts, Fail, St0#cg{bfail=Fail}), + {Tis,St1#cg{bfail=St0#cg.bfail}}; +protected_cg(Ts, Rs, Inner0, _Fail, St0) -> + {Pfail,St1} = new_label(St0), + {Br,St2} = new_label(St1), + Prot = duplicate(length(Rs), #b_literal{val=false}), + {Tis,St3} = guard_cg(Ts, Pfail, St2#cg{break=Pfail,bfail=Pfail}), + Inner = ssa_args(Inner0, St3), + {BreakVars,St} = new_ssa_vars(Rs, St3), + Is = Tis ++ [#cg_break{args=Inner,phi=Br}, + {label,Pfail},#cg_break{args=Prot,phi=Br}, + {label,Br},#cg_phi{vars=BreakVars}], + {Is,St#cg{break=St0#cg.break,bfail=St0#cg.bfail}}. + +%% match_fmf(Fun, LastFail, State, [Clause]) -> {Is,State}. +%% This is a special flatmapfoldl for match code gen where we +%% generate a "failure" label for each clause. The last clause uses +%% an externally generated failure label, LastFail. N.B. We do not +%% know or care how the failure labels are used. + +match_fmf(F, LastFail, St, [H]) -> + F(H, LastFail, St); +match_fmf(F, LastFail, St0, [H|T]) -> + {Fail,St1} = new_label(St0), + {R,St2} = F(H, Fail, St1), + {Rs,St3} = match_fmf(F, LastFail, St2, T), + {R ++ [{label,Fail}] ++ Rs,St3}. + +%% fail_label(State) -> {Where,FailureLabel}. +%% Where = guard | no_catch | in_catch +%% Return an indication of which part of a function code is +%% being generated for and the appropriate failure label to +%% use. +%% +%% Where has the following meaning: +%% +%% guard - Inside a guard. +%% no_catch - In a function body, not in the scope of +%% a try/catch or catch. +%% in_catch - In the scope of a try/catch or catch. + +fail_label(#cg{catch_label=Catch,bfail=Fail,ultimate_failure=Ult}) -> + if + Fail =/= Ult -> + {guard,Fail}; + Catch =:= none -> + {no_catch,Fail}; + is_integer(Catch) -> + {in_catch,Catch} + end. + +%% bif_fail_label(State) -> FailureLabel. +%% Return the appropriate failure label for a guard BIF call or +%% primop that fails. + +bif_fail_label(St) -> + {_,Fail} = fail_label(St), + Fail. + +%% call_cg(Func, [Arg], [Ret], Le, State) -> +%% {[Ainstr],State}. +%% enter_cg(Func, [Arg], Le, St) -> {[Ainstr],St}. +%% Generate code for call and enter. + +call_cg(Func, As, [], Le, St) -> + call_cg(Func, As, [#k_var{name='@ssa_ignored'}], Le, St); +call_cg(Func0, As, [#k_var{name=R}|MoreRs]=Rs, Le, St0) -> + case fail_label(St0) of + {guard,Fail} -> + %% Inside a guard. The only allowed function call is to + %% erlang:error/1,2. We will generate a branch to the + %% failure branch. + #k_remote{mod=#k_atom{val=erlang}, + name=#k_atom{val=error}} = Func0, %Assertion. + [#k_var{name=DestVar}] = Rs, + St = set_ssa_var(DestVar, #b_literal{val=unused}, St0), + {[make_uncond_branch(Fail),#cg_unreachable{}],St}; + {Catch,Fail} -> + %% Ordinary function call in a function body. + Args = ssa_args(As, St0), + {Ret,St1} = new_ssa_var(R, St0), + Func = call_target(Func0, Args, St0), + Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=[Func|Args]}, + + %% If this is a call to erlang:error(), MoreRs could be a + %% nonempty list of variables that each need a value. + St2 = foldl(fun(#k_var{name=Dummy}, S) -> + set_ssa_var(Dummy, #b_literal{val=unused}, S) + end, St1, MoreRs), + case Catch of + no_catch -> + {[Call],St2}; + in_catch -> + {TestIs,St} = make_cond_branch(succeeded, [Ret], Fail, St2), + {[Call|TestIs],St} + end + end. + +enter_cg(Func0, As0, Le, St0) -> + Anno = line_anno(Le), + Func = call_target(Func0, As0, St0), + As = ssa_args(As0, St0), + {Ret,St} = new_ssa_var('@ssa_ret', St0), + Call = #b_set{anno=Anno,op=call,dst=Ret,args=[Func|As]}, + {[Call,#b_ret{arg=Ret}],St}. + +call_target(Func, As, St) -> + Arity = length(As), + case Func of + #k_remote{mod=Mod0,name=Name0} -> + Mod = ssa_arg(Mod0, St), + Name = ssa_arg(Name0, St), + #b_remote{mod=Mod,name=Name,arity=Arity}; + #k_local{name=Name} when is_atom(Name) -> + #b_local{name=#b_literal{val=Name},arity=Arity}; + #k_var{}=Var -> + ssa_arg(Var, St) + end. + +%% bif_cg(#k_bif{}, Le,State) -> {[Ainstr],State}. +%% Generate code for a guard BIF or primop. + +bif_cg(#k_bif{op=#k_internal{name=Name},args=As,ret=Rs}, Le, St) -> + internal_cg(Name, As, Rs, Le, St); +bif_cg(#k_bif{op=#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Name}}, + args=As,ret=Rs}, Le, St) -> + bif_cg(Name, As, Rs, Le, St). + +%% internal_cg(Bif, [Arg], [Ret], Le, State) -> +%% {[Ainstr],State}. + +internal_cg(bs_context_to_binary, [Src0], [], _Le, St) -> + Src = ssa_arg(Src0, St), + Set = #b_set{op=context_to_binary,args=[Src]}, + {[Set],St}; +internal_cg(dsetelement, [Index0,Tuple0,New0], _Rs, _Le, St) -> + [New,Tuple,#b_literal{val=Index1}] = ssa_args([New0,Tuple0,Index0], St), + Index = #b_literal{val=Index1-1}, + Set = #b_set{op=set_tuple_element,args=[New,Tuple,Index]}, + {[Set],St}; +internal_cg(make_fun, [Name0,Arity0|As], Rs, _Le, St0) -> + #k_atom{val=Name} = Name0, + #k_int{val=Arity} = Arity0, + [#k_var{name=Dst0}] = Rs, + {Dst,St} = new_ssa_var(Dst0, St0), + Args = ssa_args(As, St), + Local = #b_local{name=#b_literal{val=Name},arity=Arity}, + MakeFun = #b_set{op=make_fun,dst=Dst,args=[Local|Args]}, + {[MakeFun],St}; +internal_cg(bs_init_writable=I, As, [#k_var{name=Dst0}], _Le, St0) -> + %% This behaves like a function call. + {Dst,St} = new_ssa_var(Dst0, St0), + Args = ssa_args(As, St), + Set = #b_set{op=I,dst=Dst,args=Args}, + {[Set],St}; +internal_cg(build_stacktrace=I, As, [#k_var{name=Dst0}], _Le, St0) -> + {Dst,St} = new_ssa_var(Dst0, St0), + Args = ssa_args(As, St), + Set = #b_set{op=I,dst=Dst,args=Args}, + {[Set],St}; +internal_cg(raise, As, [#k_var{name=Dst0}], _Le, St0) -> + Args = ssa_args(As, St0), + {Dst,St} = new_ssa_var(Dst0, St0), + Resume = #b_set{op=resume,dst=Dst,args=Args}, + case St of + #cg{catch_label=none} -> + {[Resume],St}; + #cg{catch_label=Catch} when is_integer(Catch) -> + Is = [Resume,make_uncond_branch(Catch),#cg_unreachable{}], + {Is,St} + end; +internal_cg(raw_raise=I, As, [#k_var{name=Dst0}], _Le, St0) -> + %% This behaves like a function call. + {Dst,St} = new_ssa_var(Dst0, St0), + Args = ssa_args(As, St), + Set = #b_set{op=I,dst=Dst,args=Args}, + {[Set],St}. + +bif_cg(Bif, As0, [#k_var{name=Dst0}], Le, St0) -> + {Dst,St1} = new_ssa_var(Dst0, St0), + case {Bif,ssa_args(As0, St0)} of + {is_record,[Tuple,#b_literal{val=Atom}=Tag, + #b_literal{val=Int}=Arity]} + when is_atom(Atom), is_integer(Int) -> + bif_is_record_cg(Dst, Tuple, Tag, Arity, St1); + {_,As} -> + I = #b_set{anno=line_anno(Le),op={bif,Bif},dst=Dst,args=As}, + case erl_bifs:is_safe(erlang, Bif, length(As)) of + false -> + Fail = bif_fail_label(St1), + {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St1), + {[I|Is],St}; + true-> + {[I],St1} + end + end. + +bif_is_record_cg(Dst, Tuple, TagVal, ArityVal, St0) -> + {Arity,St1} = new_ssa_var('@ssa_arity', St0), + {Tag,St2} = new_ssa_var('@ssa_tag', St1), + {Phi,St3} = new_label(St2), + {False,St4} = new_label(St3), + {Is0,St5} = make_cond_branch({bif,is_tuple}, [Tuple], False, St4), + GetArity = #b_set{op={bif,tuple_size},dst=Arity,args=[Tuple]}, + {Is1,St6} = make_cond_branch({bif,'=:='}, [Arity,ArityVal], False, St5), + GetTag = #b_set{op=get_tuple_element,dst=Tag, + args=[Tuple,#b_literal{val=0}]}, + {Is2,St} = make_cond_branch({bif,'=:='}, [Tag,TagVal], False, St6), + Is3 = [#cg_break{args=[#b_literal{val=true}],phi=Phi}, + {label,False}, + #cg_break{args=[#b_literal{val=false}],phi=Phi}, + {label,Phi}, + #cg_phi{vars=[Dst]}], + Is = Is0 ++ [GetArity] ++ Is1 ++ [GetTag] ++ Is2 ++ Is3, + {Is,St}. + +%% recv_loop_cg(TimeOut, ReceiveVar, ReceiveMatch, TimeOutExprs, +%% [Ret], Le, St) -> {[Ainstr],St}. + +recv_loop_cg(Te, Rvar, Rm, Tes, Rs, Le, St0) -> + %% Get labels. + {Rl,St1} = new_label(St0), + {Tl,St2} = new_label(St1), + {Bl,St3} = new_label(St2), + St4 = St3#cg{break=Bl,recv=Rl}, + {Ris,St5} = cg_recv_mesg(Rvar, Rm, Tl, Le, St4), + {Wis,St6} = cg_recv_wait(Te, Tes, St5), + {BreakVars,St} = new_ssa_vars(Rs, St6), + {Ris ++ [{label,Tl}] ++ Wis ++ + [{label,Bl},#cg_phi{vars=BreakVars}], + St#cg{break=St0#cg.break,recv=St0#cg.recv}}. + +%% cg_recv_mesg( ) -> {[Ainstr],St}. + +cg_recv_mesg(#k_var{name=R}, Rm, Tl, Le, St0) -> + {Dst,St1} = new_ssa_var(R, St0), + {Mis,St2} = match_cg(Rm, none, St1), + RecvLbl = St1#cg.recv, + {TestIs,St} = make_cond_branch(succeeded, [Dst], Tl, St2), + Is = [#b_br{anno=line_anno(Le),bool=#b_literal{val=true}, + succ=RecvLbl,fail=RecvLbl}, + {label,RecvLbl}, + #b_set{op=peek_message,dst=Dst}|TestIs], + {Is++Mis,St}. + +%% cg_recv_wait(Te, Tes, St) -> {[Ainstr],St}. + +cg_recv_wait(#k_int{val=0}, Es, St0) -> + {Tis,St} = cg(Es, St0), + {[#b_set{op=timeout}|Tis],St}; +cg_recv_wait(Te, Es, St0) -> + {Tis,St1} = cg(Es, St0), + Args = [ssa_arg(Te, St1)], + {WaitDst,St2} = new_ssa_var('@ssa_wait', St1), + {WaitIs,St} = make_cond_branch(succeeded, [WaitDst], St1#cg.recv, St2), + %% Infinite timeout will be optimized later. + Is = [#b_set{op=wait_timeout,dst=WaitDst,args=Args}] ++ WaitIs ++ + [#b_set{op=timeout}] ++ Tis, + {Is,St}. + +%% try_cg(TryBlock, [BodyVar], TryBody, [ExcpVar], TryHandler, [Ret], St) -> +%% {[Ainstr],St}. + +try_cg(Ta, Vs, Tb, Evs, Th, Rs, St0) -> + {B,St1} = new_label(St0), %Body label + {H,St2} = new_label(St1), %Handler label + {E,St3} = new_label(St2), %End label + {Next,St4} = new_label(St3), + {TryTag,St5} = new_ssa_var('@ssa_catch_tag', St4), + {SsaVs,St6} = new_ssa_vars(Vs, St5), + {SsaEvs,St7} = new_ssa_vars(Evs, St6), + {Ais,St8} = cg(Ta, St7#cg{break=B,catch_label=H}), + St9 = St8#cg{break=E,catch_label=St7#cg.catch_label}, + {Bis,St10} = cg(Tb, St9), + {His,St11} = cg(Th, St10), + {BreakVars,St12} = new_ssa_vars(Rs, St11), + {CatchedAgg,St} = new_ssa_var('@ssa_agg', St12), + ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0), + KillTryTag = #b_set{op=kill_try_tag,args=[TryTag]}, + Args = [#b_literal{val='try'},TryTag], + Handler = [{label,H}, + #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++ + ExtractVs ++ [KillTryTag], + {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]}, + #b_br{bool=TryTag,succ=Next,fail=H}, + {label,Next}] ++ Ais ++ + [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++ + Handler ++ His ++ + [{label,E},#cg_phi{vars=BreakVars}], + St#cg{break=St0#cg.break}}. + +try_enter_cg(Ta, Vs, Tb, Evs, Th, St0) -> + {B,St1} = new_label(St0), %Body label + {H,St2} = new_label(St1), %Handler label + {Next,St3} = new_label(St2), + {TryTag,St4} = new_ssa_var('@ssa_catch_tag', St3), + {SsaVs,St5} = new_ssa_vars(Vs, St4), + {SsaEvs,St6} = new_ssa_vars(Evs, St5), + {Ais,St7} = cg(Ta, St6#cg{break=B,catch_label=H}), + St8 = St7#cg{catch_label=St6#cg.catch_label}, + {Bis,St9} = cg(Tb, St8), + {His,St10} = cg(Th, St9), + {CatchedAgg,St} = new_ssa_var('@ssa_agg', St10), + ExtractVs = extract_vars(SsaEvs, CatchedAgg, 0), + KillTryTag = #b_set{op=kill_try_tag,args=[TryTag]}, + Args = [#b_literal{val='try'},TryTag], + Handler = [{label,H}, + #b_set{op=landingpad,dst=CatchedAgg,args=Args}] ++ + ExtractVs ++ [KillTryTag], + {[#b_set{op=new_try_tag,dst=TryTag,args=[#b_literal{val='try'}]}, + #b_br{bool=TryTag,succ=Next,fail=H}, + {label,Next}] ++ Ais ++ + [{label,B},#cg_phi{vars=SsaVs},KillTryTag] ++ Bis ++ + Handler ++ His, + St#cg{break=St0#cg.break}}. + +extract_vars([V|Vs], Agg, N) -> + I = #b_set{op=extract,dst=V,args=[Agg,#b_literal{val=N}]}, + [I|extract_vars(Vs, Agg, N+1)]; +extract_vars([], _, _) -> []. + +%% do_catch_cg(CatchBlock, Ret, St) -> {[Ainstr],St}. + +do_catch_cg(Block, #k_var{name=R}, St0) -> + {B,St1} = new_label(St0), + {Next,St2} = new_label(St1), + {H,St3} = new_label(St2), + {CatchReg,St4} = new_ssa_var('@ssa_catch_tag', St3), + {Dst,St5} = new_ssa_var(R, St4), + {Succ,St6} = new_label(St5), + {Cis,St7} = cg(Block, St6#cg{break=Succ,catch_label=H}), + {CatchedVal,St8} = new_ssa_var('@catched_val', St7), + {SuccVal,St9} = new_ssa_var('@success_val', St8), + {CatchedAgg,St10} = new_ssa_var('@ssa_agg', St9), + {CatchEndVal,St} = new_ssa_var('@catch_end_val', St10), + Args = [#b_literal{val='catch'},CatchReg], + {[#b_set{op=new_try_tag,dst=CatchReg,args=[#b_literal{val='catch'}]}, + #b_br{bool=CatchReg,succ=Next,fail=H}, + {label,Next}] ++ Cis ++ + [{label,H}, + #b_set{op=landingpad,dst=CatchedAgg,args=Args}, + #b_set{op=extract,dst=CatchedVal, + args=[CatchedAgg,#b_literal{val=0}]}, + #cg_break{args=[CatchedVal],phi=B}, + {label,Succ}, + #cg_phi{vars=[SuccVal]}, + #cg_break{args=[SuccVal],phi=B}, + {label,B},#cg_phi{vars=[CatchEndVal]}, + #b_set{op=catch_end,dst=Dst,args=[CatchReg,CatchEndVal]}], + St#cg{break=St1#cg.break,catch_label=St1#cg.catch_label}}. + +%% put_cg([Var], Constr, Le, Vdb, Bef, St) -> {[Ainstr],St}. +%% Generate code for constructing terms. + +put_cg([#k_var{name=R}], #k_cons{hd=Hd,tl=Tl}, _Le, St0) -> + Args = ssa_args([Hd,Tl], St0), + {Dst,St} = new_ssa_var(R, St0), + PutList = #b_set{op=put_list,dst=Dst,args=Args}, + {[PutList],St}; +put_cg([#k_var{name=R}], #k_tuple{es=Es}, _Le, St0) -> + {Ret,St} = new_ssa_var(R, St0), + Args = ssa_args(Es, St), + PutTuple = #b_set{op=put_tuple,dst=Ret,args=Args}, + {[PutTuple],St}; +put_cg([#k_var{name=R}], #k_binary{segs=Segs}, Le, St0) -> + Fail = bif_fail_label(St0), + {Dst,St1} = new_ssa_var(R, St0), + cg_binary(Dst, Segs, Fail, Le, St1); +put_cg([#k_var{name=R}], #k_map{op=Op,var=Map, + es=[#k_map_pair{key=#k_var{}=K,val=V}]}, + Le, St0) -> + %% Map: single variable key. + SrcMap = ssa_arg(Map, St0), + LineAnno = line_anno(Le), + List = [ssa_arg(K, St0),ssa_arg(V, St0)], + {Dst,St1} = new_ssa_var(R, St0), + {Is,St} = put_cg_map(LineAnno, Op, SrcMap, Dst, List, St1), + {Is,St}; +put_cg([#k_var{name=R}], #k_map{op=Op,var=Map,es=Es}, Le, St0) -> + %% Map: one or more literal keys. + [] = [Var || #k_map_pair{key=#k_var{}=Var} <- Es], %Assertion + SrcMap = ssa_arg(Map, St0), + LineAnno = line_anno(Le), + List = flatmap(fun(#k_map_pair{key=K,val=V}) -> + [ssa_arg(K, St0),ssa_arg(V, St0)] + end, Es), + {Dst,St1} = new_ssa_var(R, St0), + {Is,St} = put_cg_map(LineAnno, Op, SrcMap, Dst, List, St1), + {Is,St}; +put_cg([#k_var{name=R}], Con0, _Le, St0) -> + %% Create an alias for a variable or literal. + Con = ssa_arg(Con0, St0), + St = set_ssa_var(R, Con, St0), + {[],St}. + +put_cg_map(LineAnno, Op, SrcMap, Dst, List, St0) -> + Fail = bif_fail_label(St0), + Args = [#b_literal{val=Op},SrcMap|List], + PutMap = #b_set{anno=LineAnno,op=put_map,dst=Dst,args=Args}, + if + Op =:= assoc -> + {[PutMap],St0}; + true -> + {Is,St} = make_cond_branch(succeeded, [Dst], Fail, St0), + {[PutMap|Is],St} + end. + +%%% +%%% Code generation for constructing binaries. +%%% + +cg_binary(Dst, Segs0, Fail, Le, St0) -> + {PutCode0,SzCalc0,St1} = cg_bin_put(Segs0, Fail, St0), + LineAnno = line_anno(Le), + Anno = Le#k.a, + case PutCode0 of + [#b_set{op=bs_put,dst=Bool,args=[_,_,Src,#b_literal{val=all}|_]}, + #b_br{bool=Bool}, + {label,_}|_] -> + #k_bin_seg{unit=Unit0,next=Segs} = Segs0, + Unit = #b_literal{val=Unit0}, + {PutCode,SzCalc1,St2} = cg_bin_put(Segs, Fail, St1), + {_,SzVar,SzCode0,St3} = cg_size_calc(1, SzCalc1, Fail, St2), + SzCode = cg_bin_anno(SzCode0, LineAnno), + Args = case member(single_use, Anno) of + true -> + [#b_literal{val=private_append},Src,SzVar,Unit]; + false -> + [#b_literal{val=append},Src,SzVar,Unit] + end, + BsInit = #b_set{anno=LineAnno,op=bs_init,dst=Dst,args=Args}, + {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St3), + {SzCode ++ [BsInit] ++ TestIs ++ PutCode,St}; + [#b_set{op=bs_put}|_] -> + {Unit,SzVar,SzCode0,St2} = cg_size_calc(8, SzCalc0, Fail, St1), + SzCode = cg_bin_anno(SzCode0, LineAnno), + Args = [#b_literal{val=new},SzVar,Unit], + BsInit = #b_set{anno=LineAnno,op=bs_init,dst=Dst,args=Args}, + {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St2), + {SzCode ++ [BsInit] ++ TestIs ++ PutCode0,St} + end. + +cg_bin_anno([Set|Sets], Anno) -> + [Set#b_set{anno=Anno}|Sets]; +cg_bin_anno([], _) -> []. + +%% cg_size_calc(PreferredUnit, SzCalc, Fail, St0) -> +%% {ActualUnit,SizeVariable,SizeCode,St}. +%% Generate size calculation code. + +cg_size_calc(Unit, error, _Fail, St) -> + {#b_literal{val=Unit},#b_literal{val=badarg},[],St}; +cg_size_calc(8, [{1,_}|_]=SzCalc, Fail, St) -> + cg_size_calc(1, SzCalc, Fail, St); +cg_size_calc(8, SzCalc, Fail, St0) -> + {Var,Pre,St} = cg_size_calc_1(SzCalc, Fail, St0), + {#b_literal{val=8},Var,Pre,St}; +cg_size_calc(1, SzCalc0, Fail, St0) -> + SzCalc = map(fun({8,#b_literal{val=Size}}) -> + {1,#b_literal{val=8*Size}}; + ({8,{{bif,byte_size},Src}}) -> + {1,{{bif,bit_size},Src}}; + ({8,{_,_}=UtfCalc}) -> + {1,{'*',#b_literal{val=8},UtfCalc}}; + ({_,_}=Pair) -> + Pair + end, SzCalc0), + {Var,Pre,St} = cg_size_calc_1(SzCalc, Fail, St0), + {#b_literal{val=1},Var,Pre,St}. + +cg_size_calc_1(SzCalc, Fail, St0) -> + cg_size_calc_2(SzCalc, #b_literal{val=0}, Fail, St0). + +cg_size_calc_2([{_,{'*',Unit,{_,_}=Bif}}|T], Sum0, Fail, St0) -> + {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0), + {BifDst,Pre1,St2} = cg_size_bif(Bif, Fail, St1), + {Sum,Pre2,St} = cg_size_add(Sum1, BifDst, Unit, Fail, St2), + {Sum,Pre0++Pre1++Pre2,St}; +cg_size_calc_2([{_,#b_literal{}=Sz}|T], Sum0, Fail, St0) -> + {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0), + {Sum,Pre,St} = cg_size_add(Sum1, Sz, #b_literal{val=1}, Fail, St1), + {Sum,Pre0++Pre,St}; +cg_size_calc_2([{_,#b_var{}=Sz}|T], Sum0, Fail, St0) -> + {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0), + {Sum,Pre,St} = cg_size_add(Sum1, Sz, #b_literal{val=1}, Fail, St1), + {Sum,Pre0++Pre,St}; +cg_size_calc_2([{_,{_,_}=Bif}|T], Sum0, Fail, St0) -> + {Sum1,Pre0,St1} = cg_size_calc_2(T, Sum0, Fail, St0), + {BifDst,Pre1,St2} = cg_size_bif(Bif, Fail, St1), + {Sum,Pre2,St} = cg_size_add(Sum1, BifDst, #b_literal{val=1}, Fail, St2), + {Sum,Pre0++Pre1++Pre2,St}; +cg_size_calc_2([], Sum, _Fail, St) -> + {Sum,[],St}. + +cg_size_bif(#b_var{}=Var, _Fail, St) -> + {Var,[],St}; +cg_size_bif({Name,Src}, Fail, St0) -> + {Dst,St1} = new_ssa_var('@ssa_bif', St0), + Bif = #b_set{op=Name,dst=Dst,args=[Src]}, + {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St1), + {Dst,[Bif|TestIs],St}. + +cg_size_add(#b_literal{val=0}, Val, #b_literal{val=1}, _Fail, St) -> + {Val,[],St}; +cg_size_add(A, B, Unit, Fail, St0) -> + {Dst,St1} = new_ssa_var('@ssa_sum', St0), + {TestIs,St} = make_cond_branch(succeeded, [Dst], Fail, St1), + BsAdd = #b_set{op=bs_add,dst=Dst,args=[A,B,Unit]}, + {Dst,[BsAdd|TestIs],St}. + +cg_bin_put(Seg, Fail, St) -> + cg_bin_put_1(Seg, Fail, [], [], St). + +cg_bin_put_1(#k_bin_seg{size=Size0,unit=U,type=T,flags=Fs,seg=Src0,next=Next}, + Fail, Acc, SzCalcAcc, St0) -> + [Src,Size] = ssa_args([Src0,Size0], St0), + NeedSize = bs_need_size(T), + TypeArg = #b_literal{val=T}, + Flags = #b_literal{val=Fs}, + Unit = #b_literal{val=U}, + Args = case NeedSize of + true -> [TypeArg,Flags,Src,Size,Unit]; + false -> [TypeArg,Flags,Src] + end, + {Is,St} = make_cond_branch(bs_put, Args, Fail, St0), + SzCalc = bin_size_calc(T, Src, Size, U), + cg_bin_put_1(Next, Fail, reverse(Is, Acc), [SzCalc|SzCalcAcc], St); +cg_bin_put_1(#k_bin_end{}, _, Acc, SzCalcAcc, St) -> + SzCalc = fold_size_calc(SzCalcAcc, 0, []), + {reverse(Acc),SzCalc,St}. + +bs_need_size(utf8) -> false; +bs_need_size(utf16) -> false; +bs_need_size(utf32) -> false; +bs_need_size(_) -> true. + +bin_size_calc(utf8, Src, _Size, _Unit) -> + {8,{bs_utf8_size,Src}}; +bin_size_calc(utf16, Src, _Size, _Unit) -> + {8,{bs_utf16_size,Src}}; +bin_size_calc(utf32, _Src, _Size, _Unit) -> + {8,#b_literal{val=4}}; +bin_size_calc(binary, Src, #b_literal{val=all}, Unit) -> + case Unit rem 8 of + 0 -> {8,{{bif,byte_size},Src}}; + _ -> {1,{{bif,bit_size},Src}} + end; +bin_size_calc(_Type, _Src, Size, Unit) -> + {Unit,Size}. + +fold_size_calc([{Unit,#b_literal{val=Size}}|T], Bits, Acc) -> + if + is_integer(Size) -> + fold_size_calc(T, Bits + Unit*Size, Acc); + true -> + error + end; +fold_size_calc([{U,#b_var{}}=H|T], Bits, Acc) when U =:= 1; U =:= 8 -> + fold_size_calc(T, Bits, [H|Acc]); +fold_size_calc([{U,#b_var{}=Var}|T], Bits, Acc) -> + fold_size_calc(T, Bits, [{1,{'*',#b_literal{val=U},Var}}|Acc]); +fold_size_calc([{_,_}=H|T], Bits, Acc) -> + fold_size_calc(T, Bits, [H|Acc]); +fold_size_calc([], Bits, Acc) -> + Bytes = Bits div 8, + RemBits = Bits rem 8, + Sizes = sort([{1,#b_literal{val=RemBits}},{8,#b_literal{val=Bytes}}|Acc]), + [Pair || {_,Sz}=Pair <- Sizes, Sz =/= #b_literal{val=0}]. + +%%% +%%% Utilities for creating the SSA types. +%%% + +ssa_args(As, St) -> + [ssa_arg(A, St) || A <- As]. + +ssa_arg(#k_var{name=V}, #cg{vars=Vars}) -> maps:get(V, Vars); +ssa_arg(#k_literal{val=V}, _) -> #b_literal{val=V}; +ssa_arg(#k_atom{val=V}, _) -> #b_literal{val=V}; +ssa_arg(#k_float{val=V}, _) -> #b_literal{val=V}; +ssa_arg(#k_int{val=V}, _) -> #b_literal{val=V}; +ssa_arg(#k_nil{}, _) -> #b_literal{val=[]}. + +new_ssa_vars(Vs, St) -> + mapfoldl(fun(#k_var{name=V}, S) -> + new_ssa_var(V, S) + end, St, Vs). + +new_ssa_var(VarBase, #cg{lcount=Uniq,vars=Vars}=St0) + when is_atom(VarBase); is_integer(VarBase) -> + case Vars of + #{VarBase:=_} -> + Var = #b_var{name={VarBase,Uniq}}, + St = St0#cg{lcount=Uniq+1,vars=Vars#{VarBase=>Var}}, + {Var,St}; + #{} -> + Var = #b_var{name=VarBase}, + St = St0#cg{vars=Vars#{VarBase=>Var}}, + {Var,St} + end. + +set_ssa_var(VarBase, Val, #cg{vars=Vars}=St) + when is_atom(VarBase); is_integer(VarBase) -> + St#cg{vars=Vars#{VarBase=>Val}}. + +%% new_label(St) -> {L,St}. + +new_label(#cg{lcount=Next}=St) -> + {Next,St#cg{lcount=Next+1}}. + +%% line_anno(Le) -> #{} | #{location:={File,Line}}. +%% Create a location annotation, containing information about the +%% current filename and line number. The annotation should be +%% included in any operation that could cause an exception. + +line_anno(#k{a=Anno}) -> + line_anno_1(Anno). + +line_anno_1([Line,{file,Name}]) when is_integer(Line) -> + line_anno_2(Name, Line); +line_anno_1([_|_]=A) -> + {Name,Line} = find_loc(A, no_file, 0), + line_anno_2(Name, Line); +line_anno_1([]) -> + #{}. + +line_anno_2(no_file, _) -> + #{}; +line_anno_2(_, 0) -> + %% Missing line number or line number 0. + #{}; +line_anno_2(Name, Line) -> + #{location=>{Name,Line}}. + +find_loc([Line|T], File, _) when is_integer(Line) -> + find_loc(T, File, Line); +find_loc([{file,File}|T], _, Line) -> + find_loc(T, File, Line); +find_loc([_|T], File, Line) -> + find_loc(T, File, Line); +find_loc([], File, Line) -> {File,Line}. + +flatmapfoldl(F, Accu0, [Hd|Tail]) -> + {R,Accu1} = F(Hd, Accu0), + {Rs,Accu2} = flatmapfoldl(F, Accu1, Tail), + {R++Rs,Accu2}; +flatmapfoldl(_, Accu, []) -> {[],Accu}. + +%%% +%%% Finalize the code. +%%% + +finalize(Asm0, St0) -> + Asm1 = fix_phis(Asm0), + {Asm,St} = fix_sets(Asm1, [], St0), + {build_map(Asm),St}. + +fix_phis(Is) -> + fix_phis_1(Is, none, #{}). + +fix_phis_1([{label,L},#cg_phi{vars=[]}=Phi|Is0], _Lbl, Map0) -> + case maps:is_key(L, Map0) of + false -> + %% No #cg_break{} references this label. Nothing else can + %% reference it, so it can be safely be removed. + {Is,Map} = drop_upto_label(Is0, Map0), + fix_phis_1(Is, none, Map); + true -> + %% There is a break referencing this label; probably caused + %% by a try/catch whose return value is ignored. + [{label,L}|fix_phis_1([Phi|Is0], L, Map0)] + end; +fix_phis_1([{label,L}=I|Is], _Lbl, Map) -> + [I|fix_phis_1(Is, L, Map)]; +fix_phis_1([#cg_unreachable{}|Is0], _Lbl, Map0) -> + {Is,Map} = drop_upto_label(Is0, Map0), + fix_phis_1(Is, none, Map); +fix_phis_1([#cg_break{args=Args,phi=Target}|Is], Lbl, Map) when is_integer(Lbl) -> + Pairs1 = case Map of + #{Target:=Pairs0} -> Pairs0; + #{} -> [] + end, + Pairs = [[{Arg,Lbl} || Arg <- Args]|Pairs1], + I = make_uncond_branch(Target), + [I|fix_phis_1(Is, none, Map#{Target=>Pairs})]; +fix_phis_1([#cg_phi{vars=Vars}|Is0], Lbl, Map0) -> + Pairs = maps:get(Lbl, Map0), + Map1 = maps:remove(Lbl, Map0), + case gen_phis(Vars, Pairs) of + [#b_set{op=phi,args=[]}] -> + {Is,Map} = drop_upto_label(Is0, Map1), + Ret = #b_ret{arg=#b_literal{val=unreachable}}, + [Ret|fix_phis_1(Is, none, Map)]; + Phis -> + Phis ++ fix_phis_1(Is0, Lbl, Map1) + end; +fix_phis_1([I|Is], Lbl, Map) -> + [I|fix_phis_1(Is, Lbl, Map)]; +fix_phis_1([], _, Map) -> + [] = maps:to_list(Map), %Assertion. + []. + +gen_phis([V|Vs], Preds0) -> + {Pairs,Preds} = collect_preds(Preds0, [], []), + [#b_set{op=phi,dst=V,args=Pairs}|gen_phis(Vs, Preds)]; +gen_phis([], _) -> []. + +collect_preds([[First|Rest]|T], ColAcc, RestAcc) -> + collect_preds(T, [First|ColAcc], [Rest|RestAcc]); +collect_preds([], ColAcc, RestAcc) -> + {keysort(2, ColAcc),RestAcc}. + +fix_sets([#b_set{dst=none}=Set|Is], Acc, St0) -> + {Dst,St} = new_ssa_var('@ssa_ignored', St0), + I = Set#b_set{dst=Dst}, + fix_sets(Is, [I|Acc], St); +fix_sets([I|Is], Acc, St) -> + fix_sets(Is, [I|Acc], St); +fix_sets([], Acc, St) -> + {reverse(Acc),St}. + +build_map(Is) -> + Blocks = build_graph_1(Is, [], []), + maps:from_list(Blocks). + +build_graph_1([{label,L}|Is], Lbls, []) -> + build_graph_1(Is, [L|Lbls], []); +build_graph_1([{label,L}|Is], Lbls, [_|_]=BlockAcc) -> + make_blocks(Lbls, BlockAcc) ++ build_graph_1(Is, [L], []); +build_graph_1([I|Is], Lbls, BlockAcc) -> + build_graph_1(Is, Lbls, [I|BlockAcc]); +build_graph_1([], Lbls, BlockAcc) -> + make_blocks(Lbls, BlockAcc). + +make_blocks(Lbls, [Last|Is0]) -> + Is = reverse(Is0), + Block = #b_blk{is=Is,last=Last}, + [{L,Block} || L <- Lbls]. + +drop_upto_label([{label,_}|_]=Is, Map) -> + {Is,Map}; +drop_upto_label([#cg_break{phi=Target}|Is], Map) -> + Pairs = case Map of + #{Target:=Pairs0} -> Pairs0; + #{} -> [] + end, + drop_upto_label(Is, Map#{Target=>Pairs}); +drop_upto_label([_|Is], Map) -> + drop_upto_label(Is, Map). + +k_get_anno(Thing) -> element(2, Thing). diff --git a/lib/compiler/src/beam_listing.erl b/lib/compiler/src/beam_listing.erl index 518b958794..8a0ce5b50a 100644 --- a/lib/compiler/src/beam_listing.erl +++ b/lib/compiler/src/beam_listing.erl @@ -23,6 +23,7 @@ -include("core_parse.hrl"). -include("v3_kernel.hrl"). +-include("beam_ssa.hrl"). -include("beam_disasm.hrl"). -import(lists, [foreach/2]). @@ -41,6 +42,12 @@ module(File, #k_mdef{}=Kern) -> %% This is a kernel module. io:put_chars(File, v3_kernel_pp:format(Kern)); %%io:put_chars(File, io_lib:format("~p~n", [Kern])); +module(File, #b_module{name=Mod,exports=Exp,attributes=Attr,body=Fs}) -> + io:format(File, "module ~p.\n", [Mod]), + io:format(File, "exports ~p.\n", [Exp]), + io:format(File, "attributes ~p.\n\n", [Attr]), + PP = [beam_ssa_pp:format_function(F) || F <- Fs], + io:put_chars(File, lists:join($\n, PP)); module(Stream, {Mod,Exp,Attr,Code,NumLabels}) -> %% This is output from v3_codegen. io:format(Stream, "{module, ~p}. %% version = ~w\n", diff --git a/lib/compiler/src/beam_peep.erl b/lib/compiler/src/beam_peep.erl index 2b8dd40e29..74da6aa704 100644 --- a/lib/compiler/src/beam_peep.erl +++ b/lib/compiler/src/beam_peep.erl @@ -101,23 +101,13 @@ peep([{select,Op,R,F,Vls0}|Is], SeenTests0, Acc0) -> I = {jump,F}, peep([I|Is], gb_sets:empty(), Acc0); [{atom,_}=Value,Lbl] when Op =:= select_val -> - %% Single value left. Convert to regular test and pop redundant tests. + %% Single value left. Convert to regular test. Is1 = [{test,is_eq_exact,F,[R,Value]},{jump,Lbl}|Is], - case Acc0 of - [{test,is_atom,F,[R]}|Acc] -> - peep(Is1, SeenTests0, Acc); - _ -> - peep(Is1, SeenTests0, Acc0) - end; + peep(Is1, SeenTests0, Acc0); [{integer,_}=Value,Lbl] when Op =:= select_val -> - %% Single value left. Convert to regular test and pop redundant tests. + %% Single value left. Convert to regular test. Is1 = [{test,is_eq_exact,F,[R,Value]},{jump,Lbl}|Is], - case Acc0 of - [{test,is_integer,F,[R]}|Acc] -> - peep(Is1, SeenTests0, Acc); - _ -> - peep(Is1, SeenTests0, Acc0) - end; + peep(Is1, SeenTests0, Acc0); [Arity,Lbl] when Op =:= select_tuple_arity -> %% Single value left. Convert to regular test Is1 = [{test,test_arity,F,[R,Arity]},{jump,Lbl}|Is], @@ -126,6 +116,21 @@ peep([{select,Op,R,F,Vls0}|Is], SeenTests0, Acc0) -> I = {select,Op,R,F,Vls}, peep(Is, gb_sets:empty(), [I|Acc0]) end; +peep([{get_map_elements,Fail,Src,List}=I|Is], _SeenTests, Acc0) -> + SeenTests = gb_sets:empty(), + case simplify_get_map_elements(Fail, Src, List, Acc0) of + {ok,Acc} -> + peep(Is, SeenTests, Acc); + error -> + peep(Is, SeenTests, [I|Acc0]) + end; +peep([{test,has_map_fields,Fail,Ops}=I|Is], SeenTests, Acc0) -> + case simplify_has_map_fields(Fail, Ops, Acc0) of + {ok,Acc} -> + peep(Is, SeenTests, Acc); + error -> + peep(Is, SeenTests, [I|Acc0]) + end; peep([{test,Op,_,Ops}=I|Is], SeenTests0, Acc) -> case beam_utils:is_pure_test(I) of false -> @@ -176,3 +181,39 @@ prune_redundant_values([_Val,F|Vls], F) -> prune_redundant_values([Val,Lbl|Vls], F) -> [Val,Lbl|prune_redundant_values(Vls, F)]; prune_redundant_values([], _) -> []. + +simplify_get_map_elements(Fail, Src, {list,[Key,Dst]}, + [{get_map_elements,Fail,Src,{list,List1}}|Acc]) -> + case are_keys_literals([Key]) andalso are_keys_literals(List1) of + true -> + case member(Key, List1) of + true -> + %% The key is already in the other list. That is + %% very unusual, because there are optimizations to get + %% rid of duplicate keys. Therefore, don't try to + %% do anything smart here; just keep the + %% get_map_elements instructions separate. + error; + false -> + List = [Key,Dst|List1], + {ok,[{get_map_elements,Fail,Src,{list,List}}|Acc]} + end; + false -> + error + end; +simplify_get_map_elements(_, _, _, _) -> error. + +simplify_has_map_fields(Fail, [Src|Keys0], + [{test,has_map_fields,Fail,[Src|Keys1]}|Acc]) -> + case are_keys_literals(Keys0) andalso are_keys_literals(Keys1) of + true -> + Keys = Keys0 ++ Keys1, + {ok,[{test,has_map_fields,Fail,[Src|Keys]}|Acc]}; + false -> + error + end; +simplify_has_map_fields(_, _, _) -> error. + +are_keys_literals([{x,_}|_]) -> false; +are_keys_literals([{y,_}|_]) -> false; +are_keys_literals([_|_]) -> true. diff --git a/lib/compiler/src/beam_split.erl b/lib/compiler/src/beam_split.erl index 809e49b3d0..7b18946ae0 100644 --- a/lib/compiler/src/beam_split.erl +++ b/lib/compiler/src/beam_split.erl @@ -44,10 +44,6 @@ 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,[],[],{line,_}=Line}, diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl new file mode 100644 index 0000000000..a2766a0501 --- /dev/null +++ b/lib/compiler/src/beam_ssa.erl @@ -0,0 +1,616 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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% +%% +%% Purpose: Type definitions and utilities for the SSA format. + +-module(beam_ssa). +-export([add_anno/3,get_anno/2, + clobbers_xregs/1,def/2,def_used/2,dominators/1, + flatmapfold_instrs_rpo/4, + fold_po/3,fold_po/4,fold_rpo/3,fold_rpo/4, + fold_instrs_rpo/4, + linearize/1, + mapfold_instrs_rpo/4, + predecessors/1, + rename_vars/3, + rpo/1,rpo/2, + split_blocks/3, + successors/1,successors/2, + update_phi_labels/4,used/1]). + +-export_type([b_module/0,b_function/0,b_blk/0,b_set/0, + b_ret/0,b_br/0,b_switch/0,terminator/0, + b_var/0,b_literal/0,b_remote/0,b_local/0, + value/0,argument/0,label/0, + var_name/0,var_base/0,literal_value/0, + op/0,anno/0,block_map/0]). + +-include("beam_ssa.hrl"). + +-type b_module() :: #b_module{}. +-type b_function() :: #b_function{}. +-type b_blk() :: #b_blk{}. +-type b_set() :: #b_set{}. + +-type b_br() :: #b_br{}. +-type b_ret() :: #b_ret{}. +-type b_switch() :: #b_switch{}. +-type terminator() :: b_br() | b_ret() | b_switch(). + +-type b_var() :: #b_var{}. +-type b_literal() :: #b_literal{}. +-type b_remote() :: #b_remote{}. +-type b_local() :: #b_local{}. + +-type value() :: b_var() | b_literal(). +-type phi_value() :: {value(),label()}. +-type argument() :: value() | b_remote() | b_local() | phi_value(). +-type label() :: non_neg_integer(). + +-type var_name() :: var_base() | {var_base(),non_neg_integer()}. +-type var_base() :: atom() | non_neg_integer(). + +-type literal_value() :: atom() | integer() | float() | list() | + nil() | tuple() | map() | binary(). + +-type op() :: {'bif',atom()} | {'float',float_op()} | prim_op() | cg_prim_op(). +-type anno() :: #{atom() := any()}. + +-type block_map() :: #{label():=b_blk()}. + +%% Note: By default, dialyzer will collapse this type to atom(). +%% To avoid the collapsing, change the value of SET_LIMIT to 50 in the +%% file erl_types.erl in the hipe application. + +-type prim_op() :: 'bs_add' | 'bs_extract' | 'bs_init' | 'bs_init_writable' | + 'bs_match' | 'bs_put' | 'bs_start_match' | 'bs_test_tail' | + 'bs_utf16_size' | 'bs_utf8_size' | 'build_stacktrace' | + 'call' | 'catch_end' | 'context_to_binary' | + 'extract' | + 'get_hd' | 'get_map_element' | 'get_tl' | 'get_tuple_element' | + 'has_map_field' | + 'is_nonempty_list' | 'is_tagged_tuple' | + 'kill_try_tag' | + 'landingpad' | + 'make_fun' | 'new_try_tag' | + 'peek_message' | 'phi' | 'put_list' | 'put_map' | 'put_tuple' | + 'raw_raise' | 'recv_next' | 'remove_message' | 'resume' | + 'set_tuple_element' | 'succeeded' | + 'timeout' | + 'wait' | 'wait_timeout'. + +-type float_op() :: 'checkerror' | 'clearerror' | 'convert' | 'get' | 'put' | + '+' | '-' | '*' | '/'. + +%% Primops only used internally during code generation. +-type cg_prim_op() :: 'bs_get' | 'bs_match_string' | 'bs_restore' | 'bs_skip' | +'copy' | 'put_tuple_arity' | 'put_tuple_element'. + +-import(lists, [foldl/3,mapfoldl/3,reverse/1]). + +-spec add_anno(Key, Value, Construct) -> Construct when + Key :: atom(), + Value :: any(), + Construct :: b_function() | b_blk() | b_set() | terminator(). + +add_anno(Key, Val, #b_function{anno=Anno}=Bl) -> + Bl#b_function{anno=Anno#{Key=>Val}}; +add_anno(Key, Val, #b_blk{anno=Anno}=Bl) -> + Bl#b_blk{anno=Anno#{Key=>Val}}; +add_anno(Key, Val, #b_set{anno=Anno}=Bl) -> + Bl#b_set{anno=Anno#{Key=>Val}}; +add_anno(Key, Val, #b_br{anno=Anno}=Bl) -> + Bl#b_br{anno=Anno#{Key=>Val}}; +add_anno(Key, Val, #b_ret{anno=Anno}=Bl) -> + Bl#b_ret{anno=Anno#{Key=>Val}}; +add_anno(Key, Val, #b_switch{anno=Anno}=Bl) -> + Bl#b_switch{anno=Anno#{Key=>Val}}. + +-spec get_anno(atom(), b_blk()|b_set()|terminator()) -> any(). + +get_anno(Key, Construct) -> + maps:get(Key, get_anno(Construct)). + +get_anno(#b_blk{anno=Anno}) -> Anno; +get_anno(#b_set{anno=Anno}) -> Anno; +get_anno(#b_br{anno=Anno}) -> Anno; +get_anno(#b_ret{anno=Anno}) -> Anno; +get_anno(#b_switch{anno=Anno}) -> Anno. + +%% clobbers_xregs(#b_set{}) -> true|false. +%% Test whether the instruction invalidates all X registers. + +-spec clobbers_xregs(b_set()) -> boolean(). + +clobbers_xregs(#b_set{op=Op}) -> + case Op of + bs_init_writable -> true; + build_stacktrace -> true; + call -> true; + landingpad -> true; + make_fun -> true; + peek_message -> true; + raw_raise -> true; + _ -> false + end. + +-spec predecessors(Blocks) -> #{BlockNumber:=[Predecessor]} when + Blocks :: block_map(), + BlockNumber :: label(), + Predecessor :: label(). + +predecessors(Blocks) -> + P0 = [{S,L} || {L,Blk} <- maps:to_list(Blocks), + S <- successors(Blk)], + P1 = sofs:relation(P0), + P2 = sofs:rel2fam(P1), + P3 = sofs:to_external(P2), + P = [{0,[]}|P3], + maps:from_list(P). + +-spec successors(b_blk()) -> [label()]. + +successors(#b_blk{last=Terminator}) -> + case Terminator of + #b_br{bool=#b_literal{val=true},succ=Succ} -> + [Succ]; + #b_br{bool=#b_literal{val=false},fail=Fail} -> + [Fail]; + #b_br{succ=Succ,fail=Fail} -> + [Fail,Succ]; + #b_switch{fail=Fail,list=List} -> + [Fail|[L || {_,L} <- List]]; + #b_ret{} -> + [] + end. + +-spec successors(label(), block_map()) -> [label()]. + +successors(L, Blocks) -> + successors(maps:get(L, Blocks)). + +-spec def(Ls, Blocks) -> Def when + Ls :: [label()], + Blocks :: block_map(), + Def :: ordsets:ordset(var_name()). + +def(Ls, Blocks) -> + Top = rpo(Ls, Blocks), + Blks = [maps:get(L, Blocks) || L <- Top], + def_1(Blks, []). + +-spec def_used(Ls, Blocks) -> {Def,Used} when + Ls :: [label()], + Blocks :: block_map(), + Def :: ordsets:ordset(var_name()), + Used :: ordsets:ordset(var_name()). + +def_used(Ls, Blocks) -> + Top = rpo(Ls, Blocks), + Blks = [maps:get(L, Blocks) || L <- Top], + Preds = gb_sets:from_list(Top), + def_used_1(Blks, Preds, [], gb_sets:empty()). + +-spec dominators(Blocks) -> Result when + Blocks :: block_map(), + Result :: #{label():=ordsets:ordset(label())}. + +dominators(Blocks) -> + Preds = predecessors(Blocks), + Top0 = rpo(Blocks), + Top = [{L,maps:get(L, Preds)} || L <- Top0], + + %% The flow graph for an Erlang function is reducible, and + %% therefore one traversal in reverse postorder is sufficient. + iter_dominators(Top, #{}). + +-spec fold_instrs_rpo(Fun, From, Acc0, Blocks) -> any() when + Fun :: fun((b_blk()|terminator(), any()) -> any()), + From :: [label()], + Acc0 :: any(), + Blocks :: block_map(). + +fold_instrs_rpo(Fun, From, Acc0, Blocks) -> + Top = rpo(From, Blocks), + fold_instrs_rpo_1(Top, Fun, Blocks, Acc0). + +-spec mapfold_instrs_rpo(Fun, From, Acc0, Blocks0) -> {Blocks,Acc} when + Fun :: fun((b_blk()|terminator(), any()) -> any()), + From :: [label()], + Acc0 :: any(), + Acc :: any(), + Blocks0 :: block_map(), + Blocks :: block_map(). + +mapfold_instrs_rpo(Fun, From, Acc0, Blocks) -> + Top = rpo(From, Blocks), + mapfold_instrs_rpo_1(Top, Fun, Blocks, Acc0). + +-spec flatmapfold_instrs_rpo(Fun, From, Acc0, Blocks0) -> {Blocks,Acc} when + Fun :: fun((b_blk()|terminator(), any()) -> any()), + From :: [label()], + Acc0 :: any(), + Acc :: any(), + Blocks0 :: block_map(), + Blocks :: block_map(). + +flatmapfold_instrs_rpo(Fun, From, Acc0, Blocks) -> + Top = rpo(From, Blocks), + flatmapfold_instrs_rpo_1(Top, Fun, Blocks, Acc0). + +-type fold_fun() :: fun((label(), b_blk(), any()) -> any()). + +%% fold_rpo(Fun, [Label], Acc0, Blocks) -> Acc. +%% Fold over all blocks a reverse postorder traversal of the block +%% graph; that is, first visit a block, then visit its successors. + +-spec fold_rpo(Fun, Acc0, Blocks) -> any() when + Fun :: fold_fun(), + Acc0 :: any(), + Blocks :: #{label():=b_blk()}. + +fold_rpo(Fun, Acc0, Blocks) -> + fold_rpo(Fun, [0], Acc0, Blocks). + +%% fold_rpo(Fun, [Label], Acc0, Blocks) -> Acc. Fold over all blocks +%% reachable from a given set of labels in a reverse postorder +%% traversal of the block graph; that is, first visit a block, then +%% visit its successors. + +-spec fold_rpo(Fun, Labels, Acc0, Blocks) -> any() when + Fun :: fold_fun(), + Labels :: [label()], + Acc0 :: any(), + Blocks :: #{label():=b_blk()}. + +fold_rpo(Fun, From, Acc0, Blocks) -> + Top = rpo(From, Blocks), + fold_rpo_1(Top, Fun, Blocks, Acc0). + +%% fold_po(Fun, Acc0, Blocks) -> Acc. +%% Fold over all blocks in a postorder traversal of the block graph; +%% that is, first visit all successors of block, then the block +%% itself. + +-spec fold_po(Fun, Acc0, Blocks) -> any() when + Fun :: fold_fun(), + Acc0 :: any(), + Blocks :: #{label():=b_blk()}. + +%% fold_po(Fun, From, Acc0, Blocks) -> Acc. +%% Fold over the blocks reachable from the block numbers given +%% by From in a postorder traversal of the block graph. + +fold_po(Fun, Acc0, Blocks) -> + fold_po(Fun, [0], Acc0, Blocks). + +-spec fold_po(Fun, Labels, Acc0, Blocks) -> any() when + Fun :: fold_fun(), + Labels :: [label()], + Acc0 :: any(), + Blocks :: block_map(). + +fold_po(Fun, From, Acc0, Blocks) -> + Top = reverse(rpo(From, Blocks)), + fold_rpo_1(Top, Fun, Blocks, Acc0). + +%% linearize(Blocks) -> [{BlockLabel,#b_blk{}}]. +%% Linearize the intermediate representation of the code. + +-spec linearize(Blocks) -> Linear when + Blocks :: block_map(), + Linear :: [{label(),b_blk()}]. + +linearize(Blocks) -> + Seen = gb_sets:empty(), + {Linear,_} = linearize_1([0], Blocks, Seen, []), + Linear. + +-spec rpo(Blocks) -> [Label] when + Blocks :: block_map(), + Label :: label(). + +rpo(Blocks) -> + rpo([0], Blocks). + +-spec rpo(From, Blocks) -> Labels when + From :: [label()], + Blocks :: block_map(), + Labels :: [label()]. + +rpo(From, Blocks) -> + Seen = gb_sets:empty(), + {Ls,_} = rpo_1(From, Blocks, Seen, []), + Ls. + +-spec rename_vars(Rename, [label()], block_map()) -> block_map() when + Rename :: [{var_name(),value()}] | #{var_name():=value()}. + +rename_vars(Rename, From, Blocks) when is_list(Rename) -> + rename_vars(maps:from_list(Rename), From, Blocks); +rename_vars(Rename, From, Blocks) when is_map(Rename)-> + Top = rpo(From, Blocks), + Preds = gb_sets:from_list(Top), + F = fun(#b_set{op=phi,args=Args0}=Set) -> + Args = rename_phi_vars(Args0, Preds, Rename), + Set#b_set{args=Args}; + (#b_set{args=Args0}=Set) -> + Args = [rename_var(A, Rename) || A <- Args0], + Set#b_set{args=Args}; + (#b_switch{arg=Bool}=Sw) -> + Sw#b_switch{arg=rename_var(Bool, Rename)}; + (#b_br{bool=Bool}=Br) -> + Br#b_br{bool=rename_var(Bool, Rename)}; + (#b_ret{arg=Arg}=Ret) -> + Ret#b_ret{arg=rename_var(Arg, Rename)} + end, + map_instrs_1(Top, F, Blocks). + +%% split_blocks(Predicate, Blocks0, Count0) -> {Blocks,Count}. +%% Call Predicate(Instruction) for each instruction in all +%% blocks. If Predicate/1 returns true, split the block +%% before this instruction. + +-spec split_blocks(Pred, Blocks0, Count0) -> {Blocks,Count} when + Pred :: fun((b_set()) -> boolean()), + Blocks :: block_map(), + Count0 :: beam_ssa:label(), + Blocks0 :: block_map(), + Blocks :: block_map(), + Count :: beam_ssa:label(). + +split_blocks(P, Blocks, Count) -> + Ls = beam_ssa:rpo(Blocks), + split_blocks_1(Ls, P, Blocks, Count). + +%% update_phi_labels([BlockLabel], Old, New, Blocks0) -> Blocks. +%% In the given blocks, replace label Old in with New in all +%% phi nodes. This is useful after merging or splitting +%% blocks. + +-spec update_phi_labels(From, Old, New, Blocks0) -> Blocks when + From :: [label()], + Old :: label(), + New :: label(), + Blocks0 :: block_map(), + Blocks :: block_map(). + +update_phi_labels([L|Ls], Old, New, Blocks0) -> + case Blocks0 of + #{L:=#b_blk{is=[#b_set{op=phi}|_]=Is0}=Blk0} -> + Is = update_phi_labels_is(Is0, Old, New), + Blk = Blk0#b_blk{is=Is}, + Blocks = Blocks0#{L:=Blk}, + update_phi_labels(Ls, Old, New, Blocks); + #{L:=#b_blk{}} -> + %% No phi nodes in this block. + update_phi_labels(Ls, Old, New, Blocks0) + end; +update_phi_labels([], _, _, Blocks) -> Blocks. + +-spec used(b_blk() | b_set() | terminator()) -> [var_name()]. + +used(#b_blk{is=Is,last=Last}) -> + used_1([Last|Is], ordsets:new()); +used(#b_br{bool=#b_var{name=V}}) -> + [V]; +used(#b_ret{arg=#b_var{name=V}}) -> + [V]; +used(#b_set{op=phi,args=Args}) -> + ordsets:from_list([V || {#b_var{name=V},_} <- Args]); +used(#b_set{args=Args}) -> + ordsets:from_list(used_args(Args)); +used(#b_switch{arg=#b_var{name=V}}) -> + [V]; +used(_) -> []. + +%%% +%%% Internal functions. +%%% + +def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, Used0) -> + {Def,Used1} = def_used_is(Is, Preds, Def0, Used0), + Used = gb_sets:union(gb_sets:from_list(used(Last)), Used1), + def_used_1(Bs, Preds, Def, Used); +def_used_1([], _Preds, Def, Used) -> + {ordsets:from_list(Def),gb_sets:to_list(Used)}. + +def_used_is([#b_set{op=phi,dst=#b_var{name=Dst},args=Args}|Is], + Preds, Def0, Used0) -> + Def = [Dst|Def0], + %% We must be careful to only include variables that will + %% be used when arriving from one of the predecessor blocks + %% in Preds. + Used1 = [V || {#b_var{name=V},L} <- Args, + gb_sets:is_member(L, Preds)], + Used = gb_sets:union(gb_sets:from_list(Used1), Used0), + def_used_is(Is, Preds, Def, Used); +def_used_is([#b_set{dst=#b_var{name=Dst}}=I|Is], Preds, Def0, Used0) -> + Def = [Dst|Def0], + Used = gb_sets:union(gb_sets:from_list(used(I)), Used0), + def_used_is(Is, Preds, Def, Used); +def_used_is([], _Preds, Def, Used) -> + {Def,Used}. + +def_1([#b_blk{is=Is}|Bs], Def0) -> + Def = def_is(Is, Def0), + def_1(Bs, Def); +def_1([], Def) -> + ordsets:from_list(Def). + +def_is([#b_set{dst=#b_var{name=Dst}}|Is], Def) -> + def_is(Is, [Dst|Def]); +def_is([], Def) -> Def. + +iter_dominators([{0,[]}|Ls], _Doms) -> + Dom = [0], + iter_dominators(Ls, #{0=>Dom}); +iter_dominators([{L,Preds}|Ls], Doms) -> + DomPreds = [maps:get(P, Doms) || P <- Preds, maps:is_key(P, Doms)], + Dom = ordsets:add_element(L, ordsets:intersection(DomPreds)), + iter_dominators(Ls, Doms#{L=>Dom}); +iter_dominators([], Doms) -> Doms. + +fold_rpo_1([L|Ls], Fun, Blocks, Acc0) -> + Block = maps:get(L, Blocks), + Acc = Fun(L, Block, Acc0), + fold_rpo_1(Ls, Fun, Blocks, Acc); +fold_rpo_1([], _, _, Acc) -> Acc. + +fold_instrs_rpo_1([L|Ls], Fun, Blocks, Acc0) -> + #b_blk{is=Is,last=Last} = maps:get(L, Blocks), + Acc1 = foldl(Fun, Acc0, Is), + Acc = Fun(Last, Acc1), + fold_instrs_rpo_1(Ls, Fun, Blocks, Acc); +fold_instrs_rpo_1([], _, _, Acc) -> Acc. + +mapfold_instrs_rpo_1([L|Ls], Fun, Blocks0, Acc0) -> + #b_blk{is=Is0,last=Last0} = Block0 = maps:get(L, Blocks0), + {Is,Acc1} = mapfoldl(Fun, Acc0, Is0), + {Last,Acc} = Fun(Last0, Acc1), + Block = Block0#b_blk{is=Is,last=Last}, + Blocks = maps:put(L, Block, Blocks0), + mapfold_instrs_rpo_1(Ls, Fun, Blocks, Acc); +mapfold_instrs_rpo_1([], _, Blocks, Acc) -> + {Blocks,Acc}. + +flatmapfold_instrs_rpo_1([L|Ls], Fun, Blocks0, Acc0) -> + #b_blk{is=Is0,last=Last0} = Block0 = maps:get(L, Blocks0), + {Is,Acc1} = flatmapfoldl(Fun, Acc0, Is0), + {[Last],Acc} = Fun(Last0, Acc1), + Block = Block0#b_blk{is=Is,last=Last}, + Blocks = maps:put(L, Block, Blocks0), + flatmapfold_instrs_rpo_1(Ls, Fun, Blocks, Acc); +flatmapfold_instrs_rpo_1([], _, Blocks, Acc) -> + {Blocks,Acc}. + +linearize_1([L|Ls], Blocks, Seen0, Acc0) -> + case gb_sets:is_member(L, Seen0) of + true -> + linearize_1(Ls, Blocks, Seen0, Acc0); + false -> + Seen1 = gb_sets:insert(L, Seen0), + Block = maps:get(L, Blocks), + Successors = successors(Block), + {Acc,Seen} = linearize_1(Successors, Blocks, Seen1, Acc0), + linearize_1(Ls, Blocks, Seen, [{L,Block}|Acc]) + end; +linearize_1([], _, Seen, Acc) -> + {Acc,Seen}. + +rpo_1([L|Ls], Blocks, Seen0, Acc0) -> + case gb_sets:is_member(L, Seen0) of + true -> + rpo_1(Ls, Blocks, Seen0, Acc0); + false -> + Block = maps:get(L, Blocks), + Seen1 = gb_sets:insert(L, Seen0), + Successors = successors(Block), + {Acc,Seen} = rpo_1(Successors, Blocks, Seen1, Acc0), + rpo_1(Ls, Blocks, Seen, [L|Acc]) + end; +rpo_1([], _, Seen, Acc) -> + {Acc,Seen}. + +rename_var(#b_var{name=Old}=V, Rename) -> + case Rename of + #{Old:=New} -> New; + #{} -> V + end; +rename_var(#b_remote{mod=Mod0,name=Name0}=Remote, Rename) -> + Mod = rename_var(Mod0, Rename), + Name = rename_var(Name0, Rename), + Remote#b_remote{mod=Mod,name=Name}; +rename_var(Old, _) -> Old. + +rename_phi_vars([{Var,L}|As], Preds, Ren) -> + case gb_sets:is_member(L, Preds) of + true -> + [{rename_var(Var, Ren),L}|rename_phi_vars(As, Preds, Ren)]; + false -> + [{Var,L}|rename_phi_vars(As, Preds, Ren)] + end; +rename_phi_vars([], _, _) -> []. + +map_instrs_1([L|Ls], Fun, Blocks0) -> + #b_blk{is=Is0,last=Last0} = Blk0 = maps:get(L, Blocks0), + Is = [Fun(I) || I <- Is0], + Last = Fun(Last0), + Blk = Blk0#b_blk{is=Is,last=Last}, + Blocks = maps:put(L, Blk, Blocks0), + map_instrs_1(Ls, Fun, Blocks); +map_instrs_1([], _, Blocks) -> Blocks. + +flatmapfoldl(F, Accu0, [Hd|Tail]) -> + {R,Accu1} = F(Hd, Accu0), + {Rs,Accu2} = flatmapfoldl(F, Accu1, Tail), + {R++Rs,Accu2}; +flatmapfoldl(_, Accu, []) -> {[],Accu}. + +split_blocks_1([L|Ls], P, Blocks0, Count0) -> + #b_blk{is=Is0} = Blk = maps:get(L, Blocks0), + case split_blocks_is(Is0, P, []) of + {yes,Bef,Aft} -> + NewLbl = Count0, + Count = Count0 + 1, + Br = #b_br{bool=#b_literal{val=true},succ=NewLbl,fail=NewLbl}, + BefBlk = Blk#b_blk{is=Bef,last=Br}, + NewBlk = Blk#b_blk{is=Aft}, + Blocks1 = Blocks0#{L:=BefBlk,NewLbl=>NewBlk}, + Successors = beam_ssa:successors(NewBlk), + Blocks = beam_ssa:update_phi_labels(Successors, L, NewLbl, Blocks1), + split_blocks_1([NewLbl|Ls], P, Blocks, Count); + no -> + split_blocks_1(Ls, P, Blocks0, Count0) + end; +split_blocks_1([], _, Blocks, Count) -> + {Blocks,Count}. + +split_blocks_is([I|Is], P, []) -> + split_blocks_is(Is, P, [I]); +split_blocks_is([I|Is], P, Acc) -> + case P(I) of + true -> + {yes,reverse(Acc),[I|Is]}; + false -> + split_blocks_is(Is, P, [I|Acc]) + end; +split_blocks_is([], _, _) -> no. + +update_phi_labels_is([#b_set{op=phi,args=Args0}=I0|Is], Old, New) -> + Args = [{Arg,rename_label(Lbl, Old, New)} || {Arg,Lbl} <- Args0], + I = I0#b_set{args=Args}, + [I|update_phi_labels_is(Is, Old, New)]; +update_phi_labels_is(Is, _, _) -> Is. + +rename_label(Old, Old, New) -> New; +rename_label(Lbl, _Old, _New) -> Lbl. + +used_args([#b_var{name=V}|As]) -> + [V|used_args(As)]; +used_args([#b_remote{mod=Mod,name=Name}|As]) -> + used_args([Mod,Name|As]); +used_args([_|As]) -> + used_args(As); +used_args([]) -> []. + +used_1([H|T], Used0) -> + Used = ordsets:union(used(H), Used0), + used_1(T, Used); +used_1([], Used) -> Used. diff --git a/lib/compiler/src/beam_ssa.hrl b/lib/compiler/src/beam_ssa.hrl new file mode 100644 index 0000000000..fa76b08453 --- /dev/null +++ b/lib/compiler/src/beam_ssa.hrl @@ -0,0 +1,66 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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% +%% + +-record(b_module, {anno=#{} :: beam_ssa:anno(), + name :: module(), + exports :: [{atom(),arity()}], + attributes :: list(), + body :: [beam_ssa:b_function()]}). +-record(b_function, {anno=#{} :: beam_ssa:anno(), + args :: [beam_ssa:b_var()], + bs :: #{beam_ssa:label():=beam_ssa:b_blk()}, + cnt :: beam_ssa:label()}). + +-record(b_blk, {anno=#{} :: beam_ssa:anno(), + is :: [beam_ssa:b_set()], + last :: beam_ssa:terminator()}). +-record(b_set, {anno=#{} :: beam_ssa:anno(), + dst=none :: 'none'|beam_ssa:b_var(), + op :: beam_ssa:op(), + args=[] :: [beam_ssa:argument()]}). + +%% Terminators. +-record(b_ret, {anno=#{} :: beam_ssa:anno(), + arg :: beam_ssa:value()}). + +-record(b_br, {anno=#{}, + bool :: beam_ssa:value(), + succ :: beam_ssa:label(), + fail :: beam_ssa:label()}). + +-record(b_switch, {anno=#{} :: beam_ssa:anno(), + arg :: beam_ssa:value(), + fail :: beam_ssa:label(), + list :: [{beam_ssa:b_literal(),beam_ssa:label()}]}). + +%% Values. +-record(b_var, {name :: beam_ssa:var_name()}). + +-record(b_literal, {val :: beam_ssa:literal_value()}). + +-record(b_remote, {mod :: beam_ssa:value(), + name :: beam_ssa:value(), + arity :: non_neg_integer()}). + +-record(b_local, {name :: beam_ssa:b_literal(), + arity :: non_neg_integer()}). + +%% If this block exists, it calls erlang:error(badarg). +-define(BADARG_BLOCK, 1). diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl new file mode 100644 index 0000000000..357352269c --- /dev/null +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -0,0 +1,1827 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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% +%% +%% Purpose: Generate BEAM assembly code from the SSA format. + +-module(beam_ssa_codegen). + +-export([module/2]). +-export([classify_heap_need/2]). %Called from beam_ssa_pre_codegen. + +-export_type([ssa_register/0]). + +-include("beam_ssa.hrl"). + +-import(lists, [foldl/3,keymember/3,keysort/2,last/1,map/2,mapfoldl/3, + reverse/1,reverse/2,sort/1,splitwith/2,takewhile/2]). + +-record(cg, {lcount=1 :: beam_label(), %Label counter + functable=#{} :: #{fa()=>beam_label()}, + labels=#{} :: #{ssa_label()=>0|beam_label()}, + used_labels=gb_sets:empty() :: gb_sets:set(ssa_label()), + regs=#{} :: #{beam_ssa:var_name()=>ssa_register()}, + ultimate_fail=1 :: beam_label(), + catches=gb_sets:empty() :: gb_sets:set(ssa_label()) + }). + +-spec module(beam_ssa:b_module(), [compile:option()]) -> + {'ok',beam_asm:module_code()}. + +module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, _Opts) -> + {Asm,St} = functions(Fs, {atom,Mod}), + {ok,{Mod,Es,Attrs,Asm,St#cg.lcount}}. + +-record(need, {h=0 :: non_neg_integer(), + f=0 :: non_neg_integer()}). + +-record(cg_blk, {anno=#{} :: anno(), + is=[] :: [instruction()], + last :: terminator()}). + +-record(cg_set, {anno=#{} :: anno(), + dst :: b_var(), + op :: beam_ssa:op(), + args :: [beam_ssa:argument() | xreg()]}). + +-record(cg_alloc, {anno=#{} :: anno(), + stack=none :: 'none' | pos_integer(), + words=#need{} :: #need{}, + live :: 'undefined' | pos_integer(), + def_yregs=[] :: [yreg()] + }). + +-record(cg_br, {bool :: beam_ssa:value(), + succ :: ssa_label(), + fail :: ssa_label() + }). +-record(cg_ret, {arg :: cg_value(), + dealloc=none :: 'none' | pos_integer() + }). +-record(cg_switch, {arg :: cg_value(), + fail :: ssa_label(), + list :: [sw_list_item()] + }). + +-type fa() :: {beam_asm:function_name(),arity()}. +-type ssa_label() :: beam_ssa:label(). +-type beam_label() :: beam_asm:label(). + +-type anno() :: beam_ssa:anno(). + +-type b_var() :: beam_ssa:b_var(). +-type b_literal() :: beam_ssa:b_literal(). + +-type cg_value() :: beam_ssa:value() | xreg(). + +-type cg_set() :: #cg_set{}. +-type cg_alloc() :: #cg_alloc{}. + +-type instruction() :: cg_set() | cg_alloc(). + +-type cg_br() :: #cg_br{}. +-type cg_ret() :: #cg_ret{}. +-type cg_switch() :: #cg_switch{}. +-type terminator() :: cg_br() | cg_ret() | cg_switch(). + +-type sw_list_item() :: {b_literal(),ssa_label()}. + +-type reg_num() :: beam_asm:reg_num(). +-type xreg() :: {'x',reg_num()}. +-type yreg() :: {'y',reg_num()}. + +-type ssa_register() :: xreg() | yreg() | {'fr',reg_num()} | {'z',reg_num()}. + +functions(Forms, AtomMod) -> + mapfoldl(fun (F, St) -> function(F, AtomMod, St) end, #cg{lcount=1}, Forms). + +function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) -> + #{func_info:={_,Name,Arity}} = Anno, + try + assert_badarg_block(Blocks), %Assertion. + Regs = maps:get(registers, Anno), + St1 = St0#cg{labels=#{},used_labels=gb_sets:empty(), + regs=Regs}, + {Fi,St2} = new_label(St1), %FuncInfo label + {Entry,St3} = local_func_label(Name, Arity, St2), + {Ult,St4} = new_label(St3), %Ultimate failure + Labels = (St4#cg.labels)#{0=>Entry,?BADARG_BLOCK=>0}, + St5 = St4#cg{labels=Labels,used_labels=gb_sets:singleton(Entry), + ultimate_fail=Ult}, + {Body,St} = cg_fun(Blocks, St5), + Asm = [{label,Fi},line(Anno), + {func_info,AtomMod,{atom,Name},Arity}] ++ Body ++ + [{label,Ult},if_end], + Func = {function,Name,Arity,Entry,Asm}, + {Func,St} + catch + Class:Error:Stack -> + io:fwrite("Function: ~w/~w\n", [Name,Arity]), + erlang:raise(Class, Error, Stack) + end. + +assert_badarg_block(Blocks) -> + %% Assertion: ?BADARG_BLOCK must be the call erlang:error(badarg). + case Blocks of + #{?BADARG_BLOCK:=Blk} -> + #b_blk{is=[#b_set{op=call,dst=Ret, + args=[#b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=error}}, + #b_literal{val=badarg}]}], + last=#b_ret{arg=Ret}} = Blk, + ok; + #{} -> + %% ?BADARG_BLOCK has been removed because it was never used. + ok + end. + +cg_fun(Blocks, St0) -> + Linear0 = linearize(Blocks), + St = collect_catch_labels(Linear0, St0), + Linear1 = need_heap(Linear0), + Linear2 = prefer_xregs(Linear1, St), + Linear3 = liveness(Linear2, St), + Linear4 = defined(Linear3, St), + Linear = opt_allocate(Linear4, St), + cg_linear(Linear, St). + +%% collect_catch_labels(Linear, St) -> St. +%% Collect all catch labels (labels for blocks that begin +%% with 'landingpad' instructions) for later use. + +collect_catch_labels(Linear, St) -> + Labels = collect_catch_labels_1(Linear), + St#cg{catches=gb_sets:from_list(Labels)}. + +collect_catch_labels_1([{L,#cg_blk{is=[#cg_set{op=landingpad}|_]}}|Bs]) -> + [L|collect_catch_labels_1(Bs)]; +collect_catch_labels_1([_|Bs]) -> + collect_catch_labels_1(Bs); +collect_catch_labels_1([]) -> []. + +%% need_heap([{BlockLabel,Block]) -> [{BlockLabel,Block}]. +%% Insert need_heap instructions in the instruction list. Try to be smart and +%% collect them together as much as possible. + +need_heap(Bs0) -> + Bs1 = need_heap_allocs(Bs0, #{}), + {Bs,#need{h=0,f=0}} = need_heap_blks(reverse(Bs1), #need{}, []), + Bs. + +need_heap_allocs([{L,#cg_blk{is=Is0,last=Terminator}=Blk0}|Bs], Counts0) -> + Next = next_block(Bs), + Successors = successors(Terminator), + Counts = foldl(fun(S, Cnts) -> + case Cnts of + #{S:=C} -> Cnts#{S:=C+1}; + #{} when S =:= Next -> Cnts#{S=>1}; + #{} -> Cnts#{S=>42} + end + end, Counts0, Successors), + case Counts of + #{L:=1} -> + [{L,Blk0}|need_heap_allocs(Bs, Counts)]; + #{L:=_} -> + %% This block has multiple predecessors. Force an allocation + %% in this block so that the predecessors don't need to do + %% an allocation on behalf of this block. + Is = case need_heap_never(Is0) of + true -> Is0; + false -> [#cg_alloc{}|Is0] + end, + Blk = Blk0#cg_blk{is=Is}, + [{L,Blk}|need_heap_allocs(Bs, Counts)]; + #{} -> + [{L,Blk0}|need_heap_allocs(Bs, Counts)] + end; +need_heap_allocs([], _) -> []. + +need_heap_never([#cg_alloc{}|_]) -> true; +need_heap_never([#cg_set{op=recv_next}|_]) -> true; +need_heap_never([#cg_set{op=wait}|_]) -> true; +need_heap_never(_) -> false. + +need_heap_blks([{L,#cg_blk{is=Is0}=Blk0}|Bs], H0, Acc) -> + {Is1,H1} = need_heap_is(reverse(Is0), H0, []), + {Ns,H} = need_heap_terminator(Bs, H1), + Is = Ns ++ Is1, + Blk = Blk0#cg_blk{is=Is}, + need_heap_blks(Bs, H, [{L,Blk}|Acc]); +need_heap_blks([], H, Acc) -> + {Acc,H}. + +need_heap_is([#cg_alloc{words=Words}=Alloc0|Is], N, Acc) -> + Alloc = Alloc0#cg_alloc{words=add_heap_words(N, Words)}, + need_heap_is(Is, #need{}, [Alloc|Acc]); +need_heap_is([#cg_set{op=Op,args=Args}=I|Is], N, Acc) -> + case classify_heap_need(Op, Args) of + {put,Words} -> + %% Pass through adding to needed heap. + need_heap_is(Is, add_heap_words(N, Words), [I|Acc]); + put_float -> + need_heap_is(Is, add_heap_float(N), [I|Acc]); + neutral -> + need_heap_is(Is, N, [I|Acc]); + gc -> + need_heap_is(Is, #need{}, [I]++need_heap_need(N)++Acc) + end; +need_heap_is([], N, Acc) -> + {Acc,N}. + +need_heap_terminator([{_,#cg_blk{last=#cg_br{succ=Same,fail=Same}}}|_], N) -> + {[],N}; +need_heap_terminator([{_,#cg_blk{}}|_], N) -> + {need_heap_need(N),#need{}}; +need_heap_terminator([], H) -> + {need_heap_need(H),#need{}}. + +need_heap_need(#need{h=0,f=0}) -> []; +need_heap_need(#need{}=N) -> [#cg_alloc{words=N}]. + +add_heap_words(#need{h=H1,f=F1}, #need{h=H2,f=F2}) -> + #need{h=H1+H2,f=F1+F2}; +add_heap_words(#need{h=Heap}=N, Words) when is_integer(Words) -> + N#need{h=Heap+Words}. + +add_heap_float(#need{f=F}=N) -> + N#need{f=F+1}. + +%% classify_heap_need(Operation, Arguments) -> +%% gc | neutral | {put,Words} | put_float. +%% Classify the heap need for this instruction. The return +%% values have the following meaning. +%% +%% {put,Words} means that the instruction will use Words words to build +%% something on the heap. +%% +%% 'put_float' means that the instruction will build one floating point +%% number on the heap. +%% +%% 'gc' means that that the instruction can potentially do a GC or throw an +%% exception. That means that an allocation instruction for any building +%% must be placed after this instruction. +%% +%% 'neutral' means that the instruction does nothing to disturb the heap. + +-spec classify_heap_need(beam_ssa:op(), [beam_ssa:value()]) -> + 'gc' | 'neutral' | + {'put',non_neg_integer()} | 'put_float'. + +classify_heap_need(put_list, _) -> + {put,2}; +classify_heap_need(put_tuple_arity, [#b_literal{val=Words}]) -> + {put,Words+1}; +classify_heap_need({bif,Name}, Args) -> + case is_gc_bif(Name, Args) of + false -> neutral; + true -> gc + end; +classify_heap_need({float,Op}, _Args) -> + case Op of + get -> put_float; + _ -> neutral + end; +classify_heap_need(Name, _Args) -> + classify_heap_need(Name). + +%% classify_heap_need(Operation) -> gc | neutral. +%% Return either 'gc' or 'neutral'. +%% +%% 'gc' means that that the instruction can potentially do a GC or throw an +%% exception. That means that an allocation instruction for any building +%% must be placed after this instruction. +%% +%% 'neutral' means that the instruction does nothing to disturb the heap. +%% +%% Note: Only handle operations in this function that are not handled +%% by classify_heap_need/2. + +classify_heap_need(bs_add) -> gc; +classify_heap_need(bs_get) -> gc; +classify_heap_need(bs_init) -> gc; +classify_heap_need(bs_init_writable) -> gc; +classify_heap_need(bs_match_string) -> gc; +classify_heap_need(bs_put) -> neutral; +classify_heap_need(bs_restore) -> neutral; +classify_heap_need(bs_save) -> neutral; +classify_heap_need(bs_skip) -> gc; +classify_heap_need(bs_start_match) -> neutral; +classify_heap_need(bs_test_tail) -> neutral; +classify_heap_need(bs_utf16_size) -> neutral; +classify_heap_need(bs_utf8_size) -> neutral; +classify_heap_need(build_stacktrace) -> gc; +classify_heap_need(call) -> gc; +classify_heap_need(catch_end) -> gc; +classify_heap_need(context_to_binary) -> gc; +classify_heap_need(copy) -> neutral; +classify_heap_need(extract) -> gc; +classify_heap_need(get_hd) -> neutral; +classify_heap_need(get_map_element) -> neutral; +classify_heap_need(get_tl) -> neutral; +classify_heap_need(get_tuple_element) -> neutral; +classify_heap_need(has_map_field) -> neutral; +classify_heap_need(is_nonempty_list) -> neutral; +classify_heap_need(is_tagged_tuple) -> neutral; +classify_heap_need(kill_try_tag) -> gc; +classify_heap_need(landingpad) -> gc; +classify_heap_need(make_fun) -> gc; +classify_heap_need(new_try_tag) -> gc; +classify_heap_need(peek_message) -> gc; +classify_heap_need(put_map) -> gc; +classify_heap_need(put_tuple_elements) -> neutral; +classify_heap_need(raw_raise) -> gc; +classify_heap_need(recv_next) -> gc; +classify_heap_need(remove_message) -> neutral; +classify_heap_need(resume) -> gc; +classify_heap_need(set_tuple_element) -> gc; +classify_heap_need(succeeded) -> neutral; +classify_heap_need(timeout) -> gc; +classify_heap_need(wait) -> gc; +classify_heap_need(wait_timeout) -> gc. + +%%% +%%% Because beam_ssa_pre_codegen has inserted 'copy' instructions to copy +%%% variables that must be saved on the stack, a value can for some time +%%% be in both an X register and a Y register. +%%% +%%% Here we will keep track of variables that have the same value and +%%% rewrite instructions to use the variable that refers to the X +%%% register instead of the Y register. That could improve performance, +%%% since the BEAM interpreter have more optimized instructions +%%% operating on X registers than on Y registers. +%%% +%%% 'call' and 'make_fun' are handled somewhat specially. If a value +%%% already is in the correct X register, the X register will always +%%% be used instead of the Y register. However, if there are one or more +%%% values in the wrong X registers, the X registers variables will be +%%% used only if that does not cause more 'move' instructions to be +%%% be emitted than if the Y register variables were used. +%%% +%%% Here are some examples. The first example shows how a 'move' from +%%% an Y register is eliminated: +%%% +%%% move x0 y1 +%%% move y1 x0 %%Will be eliminated. +%%% +%%% call f/1 +%%% +%%% Here is an example when x0 and x1 must be swapped to load the argument +%%% registers. Here the 'call' instruction will use the Y registers to +%%% avoid introducing an extra 'move' insruction: +%%% +%%% move x0 y0 +%%% move x1 y1 +%%% +%%% move y0 x1 +%%% move y1 x0 +%%% +%%% call f/2 +%%% +%%% Using the X register to load the argument registers would need +%%% an extra 'move' instruction like this: +%%% +%%% move x0 y0 +%%% move x1 y1 +%%% +%%% move x1 x2 +%%% move x0 x1 +%%% move x2 x0 +%%% +%%% call f/2 +%%% + +prefer_xregs(Linear, St) -> + prefer_xregs(Linear, St, #{0=>#{}}). + +prefer_xregs([{L,#cg_blk{is=Is0,last=Last0}=Blk0}|Bs], St, Map0) -> + Copies0 = maps:get(L, Map0), + {Is,Copies} = prefer_xregs_is(Is0, St, Copies0, []), + Last = prefer_xregs_terminator(Last0, Copies, St), + Blk = Blk0#cg_blk{is=Is,last=Last}, + Successors = successors(Last), + Map = prefer_xregs_successors(Successors, Copies, Map0), + [{L,Blk}|prefer_xregs(Bs, St, Map)]; +prefer_xregs([], _St, _Map) -> []. + +prefer_xregs_successors([L|Ls], Copies0, Map0) -> + case Map0 of + #{L:=Copies1} -> + Copies = merge_copies(Copies0, Copies1), + Map = Map0#{L:=Copies}, + prefer_xregs_successors(Ls, Copies0, Map); + #{} -> + Map = Map0#{L=>Copies0}, + prefer_xregs_successors(Ls, Copies0, Map) + end; +prefer_xregs_successors([], _, Map) -> Map. + +prefer_xregs_is([#cg_alloc{}=I|Is], St, Copies0, Acc) -> + Copies = case I of + #cg_alloc{stack=none,words=#need{h=0,f=0}} -> + Copies0; + #cg_alloc{} -> + #{} + end, + prefer_xregs_is(Is, St, Copies, [I|Acc]); +prefer_xregs_is([#cg_set{op=copy,dst=Dst,args=[Src]}=I|Is], St, Copies0, Acc) -> + Copies1 = prefer_xregs_prune(I, Copies0, St), + Copies = case beam_args([Src,Dst], St) of + [Same,Same] -> Copies1; + [_,_] -> Copies1#{Dst=>Src} + end, + prefer_xregs_is(Is, St, Copies, [I|Acc]); +prefer_xregs_is([#cg_set{op=call,dst=Dst}=I0|Is], St, Copies, Acc) -> + I = prefer_xregs_call(I0, Copies, St), + prefer_xregs_is(Is, St, #{Dst=>{x,0}}, [I|Acc]); +prefer_xregs_is([#cg_set{op=make_fun,dst=Dst}=I0|Is], St, Copies, Acc) -> + I = prefer_xregs_call(I0, Copies, St), + prefer_xregs_is(Is, St, #{Dst=>{x,0}}, [I|Acc]); +prefer_xregs_is([#cg_set{op=set_tuple_element}=I|Is], St, Copies, Acc) -> + %% FIXME: HiPE translates the following code segment incorrectly: + %% {call_ext,3,{extfunc,erlang,setelement,3}}. + %% {move,{x,0},{y,3}}. + %% {set_tuple_element,{y,1},{y,3},1}. + %% Therefore, skip the translation of the arguments for set_tuple_element. + prefer_xregs_is(Is, St, Copies, [I|Acc]); +prefer_xregs_is([#cg_set{args=Args0}=I0|Is], St, Copies0, Acc) -> + Args = [do_prefer_xreg(A, Copies0, St) || A <- Args0], + I = I0#cg_set{args=Args}, + Copies = prefer_xregs_prune(I, Copies0, St), + prefer_xregs_is(Is, St, Copies, [I|Acc]); +prefer_xregs_is([], _St, Copies, Acc) -> + {reverse(Acc),Copies}. + +prefer_xregs_terminator(#cg_br{bool=Arg0}=I, Copies, St) -> + Arg = do_prefer_xreg(Arg0, Copies, St), + I#cg_br{bool=Arg}; +prefer_xregs_terminator(#cg_ret{arg=Arg0}=I, Copies, St) -> + Arg = do_prefer_xreg(Arg0, Copies, St), + I#cg_ret{arg=Arg}; +prefer_xregs_terminator(#cg_switch{arg=Arg0}=I, Copies, St) -> + Arg = do_prefer_xreg(Arg0, Copies, St), + I#cg_switch{arg=Arg}. + +prefer_xregs_prune(#cg_set{anno=#{clobbers:=true}}, _, _) -> + #{}; +prefer_xregs_prune(#cg_set{dst=Dst}, Copies, St) -> + DstReg = beam_arg(Dst, St), + F = fun(_, Alias) -> + beam_arg(Alias, St) =/= DstReg + end, + maps:filter(F, Copies). + +%% prefer_xregs_call(Instruction, Copies, St) -> Instruction. +%% Given a 'call' or 'make_fun' instruction, minimize the number +%% of 'move' instructions to set up the argument registers. +%% Prefer using X registers over Y registers, unless that will +%% result in more 'move' instructions. + +prefer_xregs_call(#cg_set{args=[_]}=I, _Copies, _St) -> + I; +prefer_xregs_call(#cg_set{args=[F|Args0]}=I, Copies, St) -> + case Args0 of + [A0] -> + %% Only one argument. Always prefer the X register + %% if available. + A = do_prefer_xreg(A0, Copies, St), + I#cg_set{args=[F,A]}; + [_|_] -> + %% Two or more arguments. Try rewriting arguments in + %% two ways and see which way produces the least + %% number of 'move' instructions. + Args1 = prefer_xregs_call_1(Args0, Copies, 0, St), + Args2 = [do_prefer_xreg(A, Copies, St) || A <- Args0], + case {count_moves(Args1, St),count_moves(Args2, St)} of + {N1,N2} when N1 < N2 -> + %% There will be fewer 'move' instructions if + %% we keep using Y registers. + I#cg_set{args=[F|Args1]}; + {_,_} -> + %% Always use the values in X registers. + I#cg_set{args=[F|Args2]} + end + end. + +count_moves(Args, St) -> + length(setup_args(beam_args(Args, St))). + +prefer_xregs_call_1([#b_var{}=A|As], Copies, X, St) -> + case {beam_arg(A, St),Copies} of + {{y,_},#{A:=Other}} -> + case beam_arg(Other, St) of + {x,X} -> + %% This value is already in the correct X register. + %% It is always benefical to use the X register variable. + [Other|prefer_xregs_call_1(As, Copies, X+1, St)]; + _ -> + %% This value is another X register. Keep using + %% the Y register variable. + [A|prefer_xregs_call_1(As, Copies, X+1, St)] + end; + {_,_} -> + %% The value is not available in an X register. + [A|prefer_xregs_call_1(As, Copies, X+1, St)] + end; +prefer_xregs_call_1([A|As], Copies, X, St) -> + [A|prefer_xregs_call_1(As, Copies, X+1, St)]; +prefer_xregs_call_1([], _, _, _) -> []. + +do_prefer_xreg(#b_var{}=A, Copies, St) -> + case {beam_arg(A, St),Copies} of + {{y,_},#{A:=Copy}} -> + Copy; + {_,_} -> + A + end; +do_prefer_xreg(A, _, _) -> A. + +merge_copies(Copies0, Copies1) when map_size(Copies0) =< map_size(Copies1) -> + maps:filter(fun(K, V) -> + case Copies1 of + #{K:=V} -> true; + #{} -> false + end + end, Copies0); +merge_copies(Copies0, Copies1) -> + merge_copies(Copies1, Copies0). + + +%%% +%%% Add annotations for the number of live registers. +%%% + +liveness(Linear, #cg{regs=Regs}) -> + liveness(reverse(Linear), #{}, Regs, []). + +liveness([{L,#cg_blk{is=Is0,last=Last0}=Blk0}|Bs], LiveMap0, Regs, Acc) -> + Successors = liveness_successors(Last0), + Live0 = ordsets:union([liveness_get(S, LiveMap0) || S <- Successors]), + Live1 = liveness_terminator(Last0, Live0), + {Is,Live} = liveness_is(reverse(Is0), Regs, Live1, []), + LiveMap = LiveMap0#{L=>Live}, + Blk = Blk0#cg_blk{is=Is}, + liveness(Bs, LiveMap, Regs, [{L,Blk}|Acc]); +liveness([], _LiveMap, _Regs, Acc) -> Acc. + +liveness_get(S, LiveMap) -> + case LiveMap of + #{S:=Live} -> Live; + #{} -> [] + end. + +liveness_successors(Terminator) -> + successors(Terminator) -- [?BADARG_BLOCK]. + +liveness_is([#cg_alloc{}=I0|Is], Regs, Live, Acc) -> + I = I0#cg_alloc{live=num_live(Live, Regs)}, + liveness_is(Is, Regs, Live, [I|Acc]); +liveness_is([#cg_set{dst=Dst0,args=Args}=I0|Is], Regs, Live0, Acc) -> + #b_var{name=Dst} = Dst0, + Live1 = liveness_clobber(I0, Live0, Regs), + I1 = liveness_yregs_anno(I0, Live1, Regs), + Live2 = liveness_args(Args, Live1), + Live = ordsets:del_element(Dst, Live2), + I = liveness_anno(I1, Live, Regs), + liveness_is(Is, Regs, Live, [I|Acc]); +liveness_is([], _, Live, Acc) -> + {Acc,Live}. + +liveness_terminator(#cg_br{bool=Arg}, Live) -> + liveness_terminator_1(Arg, Live); +liveness_terminator(#cg_switch{arg=Arg}, Live) -> + liveness_terminator_1(Arg, Live); +liveness_terminator(#cg_ret{arg=Arg}, Live) -> + liveness_terminator_1(Arg, Live). + +liveness_terminator_1(#b_var{name=V}, Live) -> + ordsets:add_element(V, Live); +liveness_terminator_1(#b_literal{}, Live) -> + Live; +liveness_terminator_1(Reg, Live) -> + _ = verify_beam_register(Reg), + ordsets:add_element(Reg, Live). + +liveness_args([#b_var{name=V}|As], Live) -> + liveness_args(As, ordsets:add_element(V, Live)); +liveness_args([#b_remote{mod=Mod,name=Name}|As], Live) -> + liveness_args([Mod,Name|As], Live); +liveness_args([A|As], Live) -> + case is_beam_register(A) of + true -> + liveness_args(As, ordsets:add_element(A, Live)); + false -> + liveness_args(As, Live) + end; +liveness_args([], Live) -> Live. + +liveness_anno(#cg_set{op=Op}=I, Live, Regs) -> + case need_live_anno(Op) of + true -> + NumLive = num_live(Live, Regs), + Anno = (I#cg_set.anno)#{live=>NumLive}, + I#cg_set{anno=Anno}; + false -> + I + end. + +liveness_yregs_anno(#cg_set{op=Op,dst=#b_var{name=Dst}}=I, Live0, Regs) -> + case need_live_anno(Op) of + true -> + Live = ordsets:del_element(Dst, Live0), + LiveYregs = [V || V <- Live, is_yreg(V, Regs)], + Anno = (I#cg_set.anno)#{live_yregs=>LiveYregs}, + I#cg_set{anno=Anno}; + false -> + I + end. + +liveness_clobber(#cg_set{anno=Anno}, Live, Regs) -> + case Anno of + #{clobbers:=true} -> + [R || R <- Live, is_yreg(R, Regs)]; + _ -> + Live + end. + +is_yreg(R, Regs) -> + case Regs of + #{R:={y,_}} -> true; + #{} -> false + end. + +num_live(Live, Regs) -> + Rs = ordsets:from_list([get_register(V, Regs) || V <- Live]), + num_live_1(Rs, 0). + +num_live_1([{x,X}|T], X) -> + num_live_1(T, X+1); +num_live_1([{x,_}|_]=T, X) -> + %% error({hole,{x,X},expected,Next}); + num_live_1(T, X+1); +num_live_1([{y,_}|_], X) -> + X; +num_live_1([{z,_}|_], X) -> + X; +num_live_1([{fr,_}|T], X) -> + num_live_1(T, X); +num_live_1([], X) -> + X. + +get_live(#cg_set{anno=#{live:=Live}}) -> + Live. + +%% need_live_anno(Operation) -> true|false. +%% Return 'true' if the instruction needs a 'live' annotation with +%% the number live X registers, or 'false' otherwise. + +need_live_anno(Op) -> + case Op of + {bif,_} -> true; + bs_get -> true; + bs_init -> true; + bs_start_match -> true; + bs_skip -> true; + call -> true; + put_map -> true; + _ -> false + end. + +%%% +%%% Add annotations for defined Y registers. +%%% + +defined(Linear, #cg{regs=Regs}) -> + def(Linear, #{}, Regs). + +def([{L,#cg_blk{is=Is0,last=Last}=Blk0}|Bs], DefMap0, Regs) -> + Def0 = def_get(L, DefMap0), + {Is,Def} = def_is(Is0, Regs, Def0, []), + Successors = successors(Last), + DefMap = def_successors(Successors, Def, DefMap0), + Blk = Blk0#cg_blk{is=Is}, + [{L,Blk}|def(Bs, DefMap, Regs)]; +def([], _, _) -> []. + +def_get(L, DefMap) -> + case DefMap of + #{L:=Def} -> Def; + #{} -> [] + end. + +def_is([#cg_alloc{anno=Anno0}=I0|Is], Regs, Def, Acc) -> + I = I0#cg_alloc{anno=Anno0#{def_yregs=>Def}}, + def_is(Is, Regs, Def, [I|Acc]); +def_is([#cg_set{op=kill_try_tag,args=[#b_var{name=Tag}]}=I|Is], Regs, Def0, Acc) -> + Def = ordsets:del_element(Tag, Def0), + def_is(Is, Regs, Def, [I|Acc]); +def_is([#cg_set{op=catch_end,args=[#b_var{name=Tag}|_]}=I|Is], Regs, Def0, Acc) -> + Def = ordsets:del_element(Tag, Def0), + def_is(Is, Regs, Def, [I|Acc]); +def_is([#cg_set{anno=Anno0,op=call,dst=#b_var{name=Dst}}=I0|Is], + Regs, Def0, Acc) -> + #{live_yregs:=LiveYregVars} = Anno0, + LiveRegs = gb_sets:from_list([maps:get(V, Regs) || V <- LiveYregVars]), + Kill0 = ordsets:subtract(Def0, LiveYregVars), + + %% Kill0 is the set of variables that have just died. However, the registers + %% used for killed variables may have been reused, so we must check that the + %% registers to be killed are not used by other variables. + Kill = [K || K <- Kill0, not gb_sets:is_element(maps:get(K, Regs), LiveRegs)], + Anno = Anno0#{def_yregs=>Def0,kill_yregs=>Kill}, + I = I0#cg_set{anno=Anno}, + Def1 = ordsets:subtract(Def0, Kill), + Def = def_add_yreg(Dst, Def1, Regs), + def_is(Is, Regs, Def, [I|Acc]); +def_is([#cg_set{anno=Anno0,op={bif,Bif},dst=#b_var{name=Dst},args=Args}=I0|Is], + Regs, Def0, Acc) -> + Arity = length(Args), + I = case is_gc_bif(Bif, Args) orelse not erl_bifs:is_safe(erlang, Bif, Arity) of + true -> + I0#cg_set{anno=Anno0#{def_yregs=>Def0}}; + false -> + I0 + end, + Def = def_add_yreg(Dst, Def0, Regs), + def_is(Is, Regs, Def, [I|Acc]); +def_is([#cg_set{anno=Anno0,dst=#b_var{name=Dst}}=I0|Is], Regs, Def0, Acc) -> + I = case need_y_init(I0) of + true -> + I0#cg_set{anno=Anno0#{def_yregs=>Def0}}; + false -> + I0 + end, + Def = def_add_yreg(Dst, Def0, Regs), + def_is(Is, Regs, Def, [I|Acc]); +def_is([], _, Def, Acc) -> + {reverse(Acc),Def}. + +def_add_yreg(Dst, Def, Regs) -> + case is_yreg(Dst, Regs) of + true -> ordsets:add_element(Dst, Def); + false -> Def + end. + +def_successors([S|Ss], Def0, DefMap) -> + case DefMap of + #{S:=Def1} -> + Def = ordsets:intersection(Def0, Def1), + def_successors(Ss, Def0, DefMap#{S:=Def}); + #{} -> + def_successors(Ss, Def0, DefMap#{S=>Def0}) + end; +def_successors([], _, DefMap) -> DefMap. + +%% need_y_init(#cg_set{}) -> true|false. +%% Return true if this instructions needs initialized Y registers +%% (because the instruction may do a GC or cause an exception +%% so that the stack will be scanned), or false otherwise. + +need_y_init(#cg_set{anno=#{clobbers:=Clobbers}}) -> Clobbers; +need_y_init(#cg_set{op=bs_get}) -> true; +need_y_init(#cg_set{op=bs_init}) -> true; +need_y_init(#cg_set{op=bs_skip,args=[#b_literal{val=Type}|_]}) -> + case Type of + utf8 -> true; + utf16 -> true; + utf32 -> true; + _ -> false + end; +need_y_init(#cg_set{op=bs_start_match}) -> true; +need_y_init(#cg_set{op=put_map}) -> true; +need_y_init(#cg_set{}) -> false. + +%% opt_allocate([{BlockLabel,Block}], #st{}) -> [BeamInstruction]. +%% Update the def_yregs field of each #cg_alloc{} that allocates +%% a stack frame. #cg_alloc.def_yregs will list all Y registers +%% that will be initialized by the subsequent code (thus, the +%% listed Y registers don't require init/1 instructions). + +opt_allocate(Linear, #cg{regs=Regs}) -> + opt_allocate_1(Linear, Regs). + +opt_allocate_1([{L,#cg_blk{is=[#cg_alloc{stack=Stk}=I0|Is]}=Blk0}|Bs]=Bs0, Regs) + when is_integer(Stk) -> + Yregs = opt_alloc_def(Bs0, gb_sets:singleton(L), []), + I = I0#cg_alloc{def_yregs=Yregs}, + [{L,Blk0#cg_blk{is=[I|Is]}}|opt_allocate_1(Bs, Regs)]; +opt_allocate_1([B|Bs], Regs) -> + [B|opt_allocate_1(Bs, Regs)]; +opt_allocate_1([], _) -> []. + +opt_alloc_def([{L,#cg_blk{is=Is,last=Last}}|Bs], Ws0, Def0) -> + case gb_sets:is_member(L, Ws0) of + false -> + opt_alloc_def(Bs, Ws0, Def0); + true -> + case opt_allocate_is(Is) of + none -> + Succ = successors(Last), + Ws = gb_sets:union(Ws0, gb_sets:from_list(Succ)), + opt_alloc_def(Bs, Ws, Def0); + Def1 when is_list(Def1) -> + Def = [Def1|Def0], + opt_alloc_def(Bs, Ws0, Def) + end + end; +opt_alloc_def([], _, Def) -> + ordsets:intersection(Def). + +opt_allocate_is([#cg_set{anno=Anno}|Is]) -> + case Anno of + #{def_yregs:=Yregs} -> + Yregs; + #{} -> + opt_allocate_is(Is) + end; +opt_allocate_is([#cg_alloc{anno=#{def_yregs:=Yregs},stack=none}|_]) -> + Yregs; +opt_allocate_is([#cg_alloc{}|Is]) -> + opt_allocate_is(Is); +opt_allocate_is([]) -> none. + +%%% +%%% Here follows the main code generation functions. +%%% + +%% cg_linear([{BlockLabel,Block}]) -> [BeamInstruction]. +%% Generate BEAM instructions. + +cg_linear([{L,#cg_blk{anno=#{recv_set:=L}=Anno0}=B0}|Bs], St0) -> + Anno = maps:remove(recv_set, Anno0), + B = B0#cg_blk{anno=Anno}, + {Is,St1} = cg_linear([{L,B}|Bs], St0), + {Fail,St} = use_block_label(L, St1), + {[{recv_set,Fail}|Is],St}; +cg_linear([{L,#cg_blk{is=Is0,last=Last}}|Bs], St0) -> + Next = next_block(Bs), + St1 = new_block_label(L, St0), + {Is1,St2} = cg_block(Is0, Last, Next, St1), + {Is2,St} = cg_linear(Bs, St2), + {def_block_label(L, St)++Is1++Is2,St}; +cg_linear([], St) -> {[],St}. + +cg_block([#cg_set{op=recv_next}], #cg_br{succ=Lr0}, _Next, St0) -> + {Lr,St} = use_block_label(Lr0, St0), + {[{loop_rec_end,Lr}],St}; +cg_block([#cg_set{op=wait}], #cg_br{succ=Lr0}, _Next, St0) -> + {Lr,St} = use_block_label(Lr0, St0), + {[{wait,Lr}],St}; +cg_block(Is0, Last, Next, St0) -> + case Last of + #cg_br{succ=Next,fail=Next} -> + cg_block(Is0, none, St0); + #cg_br{succ=Same,fail=Same} -> + {Fail,St1} = use_block_label(Same, St0), + {Is,St} = cg_block(Is0, none, St1), + {Is++[jump(Fail)],St}; + #cg_br{bool=Bool,succ=Next,fail=Fail0} -> + {Fail,St1} = use_block_label(Fail0, St0), + {Is,St} = cg_block(Is0, {Bool,Fail}, St1), + {Is,St}; + #cg_br{bool=Bool,succ=Succ0,fail=Fail0} -> + {[Succ,Fail],St1} = use_block_labels([Succ0,Fail0], St0), + {Is,St} = cg_block(Is0, {Bool,Fail}, St1), + {Is++[jump(Succ)],St}; + #cg_ret{arg=Src0,dealloc=N} -> + Src = beam_arg(Src0, St0), + cg_block(Is0, {return,Src,N}, St0); + #cg_switch{} -> + cg_switch(Is0, Last, St0) + end. + +cg_switch(Is0, Last, St0) -> + #cg_switch{arg=Src0,fail=Fail0,list=List0} = Last, + Src = beam_arg(Src0, St0), + {Fail1,St1} = use_block_label(Fail0, St0), + Fail = ensure_label(Fail1, St1), + {List1,St2} = + flatmapfoldl(fun({V,L}, S0) -> + {Lbl,S} = use_block_label(L, S0), + {[beam_arg(V, S),Lbl],S} + end, St1, List0), + {Is1,St} = cg_block(Is0, none, St2), + case reverse(Is1) of + [{bif,tuple_size,_,[Tuple],{z,_}=Src}|More] -> + List = map(fun({integer,Arity}) -> Arity; + ({f,_}=F) -> F + end, List1), + Is = reverse(More, [{select_tuple_arity,Tuple,Fail,{list,List}}]), + {Is,St}; + _ -> + SelectVal = {select_val,Src,Fail,{list,List1}}, + {Is1 ++ [SelectVal],St} + end. + +jump({f,_}=Fail) -> + {jump,Fail}; +jump({catch_tag,Fail}) -> + {jump,Fail}. + +bif_fail({f,_}=Fail) -> Fail; +bif_fail({catch_tag,_}) -> {f,0}. + +next_block([]) -> none; +next_block([{Next,_}|_]) -> Next. + +ensure_label(Fail0, #cg{ultimate_fail=Lbl}) -> + case bif_fail(Fail0) of + {f,0} -> {f,Lbl}; + {f,_}=Fail -> Fail + end. + +cg_block([#cg_set{anno=#{recv_mark:=L}=Anno0}=I0|T], Context, St0) -> + Anno = maps:remove(recv_mark, Anno0), + I = I0#cg_set{anno=Anno}, + {Is,St1} = cg_block([I|T], Context, St0), + {Fail,St} = use_block_label(L, St1), + {[{recv_mark,Fail}|Is],St}; +cg_block([#cg_set{op=new_try_tag,dst=Tag,args=Args}], {Tag,Fail0}, St) -> + {catch_tag,Fail} = Fail0, + [Reg,{atom,Kind}] = beam_args([Tag|Args], St), + {[{Kind,Reg,Fail}],St}; +cg_block([#cg_set{anno=Anno,op={bif,Name},dst=Dst0,args=Args0}=I, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) -> + [Dst|Args] = beam_args([Dst0|Args0], St), + Line0 = call_line(body, {extfunc,erlang,Name,length(Args)}, Anno), + Fail = bif_fail(Fail0), + Line = case Fail of + {f,0} -> Line0; + {f,_} -> [] + end, + case is_gc_bif(Name, Args) of + true -> + Live = get_live(I), + Kill = kill_yregs(Anno, St), + {Kill++Line++[{gc_bif,Name,Fail,Live,Args,Dst}],St}; + false -> + {Line++[{bif,Name,Fail,Args,Dst}],St} + end; +cg_block([#cg_set{op={bif,tuple_size},dst=Arity0,args=[Tuple0]}, + #cg_set{op={bif,'=:='},dst=Bool,args=[Arity0,#b_literal{val=Ar}]}=Eq], + {Bool,Fail}=Context, St0) -> + Tuple = beam_arg(Tuple0, St0), + case beam_arg(Arity0, St0) of + {z,_} -> + %% The size will only be used once. Combine to a test_arity instruction. + Test = {test,test_arity,ensure_label(Fail, St0),[Tuple,Ar]}, + {[Test],St0}; + Arity -> + %% The size will be used more than once. Must do an explicit + %% BIF call followed by the '==' test. + TupleSize = {bif,tuple_size,{f,0},[Tuple],Arity}, + {Is,St} = cg_block([Eq], Context, St0), + {[TupleSize|Is],St} + end; +cg_block([#cg_set{op={bif,Name},dst=Dst0,args=Args0}]=Is0, {Dst0,Fail}, St0) -> + [Dst|Args] = beam_args([Dst0|Args0], St0), + case Dst of + {z,_} -> + %% The result of the BIF call will only be used once. Convert to + %% a test instruction. + Test = bif_to_test(Name, Args, ensure_label(Fail, St0)), + {Test,St0}; + _ -> + %% Must explicitly call the BIF since the result will be used + %% more than once. + {Is1,St1} = cg_block(Is0, none, St0), + {Is2,St} = cg_block([], {Dst0,Fail}, St1), + {Is1++Is2,St} + end; +cg_block([#cg_set{anno=Anno,op={bif,Name},dst=Dst0,args=Args0}=I|T], + Context, St0) -> + [Dst|Args] = beam_args([Dst0|Args0], St0), + {Is0,St} = cg_block(T, Context, St0), + case is_gc_bif(Name, Args) of + true -> + Line = call_line(body, {extfunc,erlang,Name,length(Args)}, Anno), + Live = get_live(I), + Kill = kill_yregs(Anno, St), + Is = Kill++Line++[{gc_bif,Name,{f,0},Live,Args,Dst}|Is0], + {Is,St}; + false -> + Is = [{bif,Name,{f,0},Args,Dst}|Is0], + {Is,St} + end; +cg_block([#cg_set{op=bs_init,dst=Dst0,args=Args0,anno=Anno}=I, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) -> + Fail = bif_fail(Fail0), + Line = line(Anno), + [#b_literal{val=Kind}|Args1] = Args0, + case Kind of + new -> + [Dst,Size,{integer,Unit}] = beam_args([Dst0|Args1], St), + Live = get_live(I), + {[Line|cg_bs_init(Dst, Size, Unit, Live, Fail)],St}; + private_append -> + [Dst,Src,Bits,{integer,Unit}] = beam_args([Dst0|Args1], St), + Flags = {field_flags,[]}, + Is = [Line,{bs_private_append,Fail,Bits,Unit,Src,Flags,Dst}], + {Is,St}; + append -> + [Dst,Src,Bits,{integer,Unit}] = beam_args([Dst0|Args1], St), + Flags = {field_flags,[]}, + Live = get_live(I), + Is = [Line,{bs_append,Fail,Bits,0,Live,Unit,Src,Flags,Dst}], + {Is,St} + end; +cg_block([#cg_set{anno=Anno,op=bs_start_match,dst=Ctx0,args=[Bin0]}=I, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) -> + #{num_slots:=Slots} = Anno, + [Dst,Bin1] = beam_args([Ctx0,Bin0], St), + {Bin,Pre} = force_reg(Bin1, Dst), + Live = get_live(I), + Is = Pre ++ [{test,bs_start_match2,Fail,Live,[Bin,Slots],Dst}], + {Is,St}; +cg_block([#cg_set{op=bs_get}=Set, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) -> + {cg_bs_get(Fail, Set, St),St}; +cg_block([#cg_set{op=bs_match_string,args=[CtxVar,#b_literal{val=String}]}, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) -> + CtxReg = beam_arg(CtxVar, St), + Is = [{test,bs_match_string,Fail,[CtxReg,String]}], + {Is,St}; +cg_block([#cg_set{dst=Dst0,op=landingpad,args=Args0}|T], Context, St0) -> + [Dst,{atom,Kind},Tag] = beam_args([Dst0|Args0], St0), + case Kind of + 'catch' -> + cg_catch(Dst, T, Context, St0); + 'try' -> + cg_try(Dst, Tag, T, Context, St0) + end; +cg_block([#cg_set{op=kill_try_tag,args=Args0}|Is], Context, St0) -> + [Reg] = beam_args(Args0, St0), + {Is0,St} = cg_block(Is, Context, St0), + {[{try_end,Reg}|Is0],St}; +cg_block([#cg_set{op=catch_end,dst=Dst0,args=Args0}|Is], Context, St0) -> + [Dst,Reg,{x,0}] = beam_args([Dst0|Args0], St0), + {Is0,St} = cg_block(Is, Context, St0), + {[{catch_end,Reg}|copy({x,0}, Dst)++Is0],St}; +cg_block([#cg_set{op=call}=I, + #cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) -> + %% A call in try/catch block. + cg_block([I], none, St); +cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=I, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) -> + [Dst|Args] = beam_args([Dst0|Args0], St), + {cg_test(Op, bif_fail(Fail), Args, Dst, I),St}; +cg_block([#cg_set{op=bs_put,dst=Bool,args=Args0}], {Bool,Fail}, St) -> + Args = beam_args(Args0, St), + {cg_bs_put(bif_fail(Fail), Args),St}; +cg_block([#cg_set{op=bs_test_tail,dst=Bool,args=Args0}], {Bool,Fail}, St) -> + [Ctx,{integer,Bits}] = beam_args(Args0, St), + {[{test,bs_test_tail2,bif_fail(Fail),[Ctx,Bits]}],St}; +cg_block([#cg_set{op={float,checkerror},dst=Bool}], {Bool,Fail}, St) -> + {[{fcheckerror,bif_fail(Fail)}],St}; +cg_block([#cg_set{op=is_tagged_tuple,dst=Bool,args=Args0}], {Bool,Fail}, St) -> + [Src,{integer,Arity},Tag] = beam_args(Args0, St), + {[{test,is_tagged_tuple,ensure_label(Fail, St),[Src,Arity,Tag]}],St}; +cg_block([#cg_set{op=is_nonempty_list,dst=Bool,args=Args0}], {Bool,Fail}, St) -> + Args = beam_args(Args0, St), + {[{test,is_nonempty_list,ensure_label(Fail, St),Args}],St}; +cg_block([#cg_set{op=has_map_field,dst=Bool,args=Args0}], {Bool,Fail}, St) -> + [Src,Key] = beam_args(Args0, St), + {[{test,has_map_fields,Fail,Src,{list,[Key]}}],St}; +cg_block([#cg_set{op=call}=Call], {_Bool,_Fail}=Context, St0) -> + {Is0,St1} = cg_call(Call, body, none, St0), + {Is1,St} = cg_block([], Context, St1), + {Is0++Is1,St}; +cg_block([#cg_set{op=call,dst=Dst0}=Call], Context, St) -> + Dst = beam_arg(Dst0, St), + case Context of + {return,Dst,_} -> + cg_call(Call, tail, Context, St); + _ -> + cg_call(Call, body, Context, St) + end; +cg_block([#cg_set{op=call}=Call|T], Context, St0) -> + {Is0,St1} = cg_call(Call, body, none, St0), + {Is1,St} = cg_block(T, Context, St1), + {Is0++Is1,St}; +cg_block([#cg_set{op=make_fun,dst=Dst0,args=[Local|Args0]}|T], + Context, St0) -> + #b_local{name=#b_literal{val=Func},arity=Arity} = Local, + [Dst|Args] = beam_args([Dst0|Args0], St0), + {FuncLbl,St1} = local_func_label(Func, Arity, St0), + Is0 = setup_args(Args) ++ + [{make_fun2,{f,FuncLbl},0,0,length(Args)}|copy({x,0}, Dst)], + {Is1,St} = cg_block(T, Context, St1), + {Is0++Is1,St}; +cg_block([#cg_set{op=copy}|_]=T0, Context, St0) -> + {Is0,T} = cg_copy(T0, St0), + {Is1,St} = cg_block(T, Context, St0), + Is = Is0 ++ Is1, + case is_call(T) of + {yes,Arity} -> + {opt_call_moves(Is, Arity),St}; + no -> + {Is,St} + end; +cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=Set], none, St) -> + [Dst|Args] = beam_args([Dst0|Args0], St), + Is = cg_instr(Op, Args, Dst, Set), + {Is,St}; +cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=Set|T], Context, St0) -> + [Dst|Args] = beam_args([Dst0|Args0], St0), + Is0 = cg_instr(Op, Args, Dst, Set), + {Is1,St} = cg_block(T, Context, St0), + {Is0++Is1,St}; +cg_block([#cg_alloc{}=Alloc|T], Context, St0) -> + Is0 = cg_alloc(Alloc, St0), + {Is1,St} = cg_block(T, Context, St0), + {Is0++Is1,St}; +cg_block([], {return,Arg,none}, St) -> + Is = copy(Arg, {x,0}) ++ [return], + {Is,St}; +cg_block([], {return,Arg,N}, St) -> + Is = copy(Arg, {x,0}) ++ [{deallocate,N},return], + {Is,St}; +cg_block([], none, St) -> + {[],St}; +cg_block([], {Bool0,Fail}, St) -> + [Bool] = beam_args([Bool0], St), + {[{test,is_eq_exact,Fail,[Bool,{atom,true}]}],St}. + +cg_copy(T0, St) -> + {Copies,T} = splitwith(fun(#cg_set{op=copy}) -> true; + (_) -> false + end, T0), + Moves0 = cg_copy_1(Copies, St), + Moves1 = [Move || {move,Src,Dst}=Move <- Moves0, Src =/= Dst], + Scratch = {x,1022}, + Moves = order_moves(Moves1, Scratch), + {Moves,T}. + +cg_copy_1([#cg_set{dst=Dst0,args=Args}|T], St) -> + [Dst,Src] = beam_args([Dst0|Args], St), + Copies = cg_copy_1(T, St), + case keymember(Dst, 3, Copies) of + true -> + %% Will be overwritten. Don't generate a move instruction. + Copies; + false -> + [{move,Src,Dst}|Copies] + end; +cg_copy_1([], _St) -> []. + +bif_to_test('not', [Var], Fail) -> + [{test,is_eq_exact,Fail,[Var,{atom,false}]}]; +bif_to_test(Name, Args, Fail) -> + [beam_utils:bif_to_test(Name, Args, Fail)]. + +opt_call_moves(Is0, Arity) -> + {Moves0,Is} = splitwith(fun({move,_,_}) -> true; + (_) -> false + end, Is0), + Moves = opt_call_moves_1(Moves0, Arity), + Moves ++ Is. + +opt_call_moves_1([{move,Src,{x,_}=Tmp}=M1,{move,Tmp,Dst}=M2|Is], Arity) -> + case is_killed(Tmp, Is, Arity) of + true -> + %% The X register Tmp is never used again. We can collapse + %% the two move instruction into one. + [{move,Src,Dst}|opt_call_moves_1(Is, Arity)]; + false -> + [M1|opt_call_moves_1([M2|Is], Arity)] + end; +opt_call_moves_1([M|Ms], Arity) -> + [M|opt_call_moves_1(Ms, Arity)]; +opt_call_moves_1([], _Arity) -> []. + +is_killed(R, [{move,R,_}|_], _) -> + false; +is_killed(R, [{move,_,R}|_], _) -> + true; +is_killed(R, [{move,_,_}|Is], Arity) -> + is_killed(R, Is, Arity); +is_killed({x,X}, [], Arity) -> + X >= Arity. + +cg_alloc(#cg_alloc{stack=none,words=#need{h=0,f=0}}, _St) -> + []; +cg_alloc(#cg_alloc{stack=none,words=Need,live=Live}, _St) -> + [{test_heap,alloc(Need),Live}]; +cg_alloc(#cg_alloc{stack=Stk,words=Need,live=Live,def_yregs=DefYregs}, + #cg{regs=Regs}) when is_integer(Stk) -> + Alloc = alloc(Need), + All = [{y,Y} || Y <- lists:seq(0, Stk-1)], + Def = ordsets:from_list([maps:get(V, Regs) || V <- DefYregs]), + NeedInit = ordsets:subtract(All, Def), + NoZero = length(Def)*2 > Stk, + I = case {NoZero,Alloc} of + {true,0} -> {allocate,Stk,Live}; + {true,_} -> {allocate_heap,Stk,Alloc,Live}; + {false,0} -> {allocate_zero,Stk,Live}; + {false,_} -> {allocate_heap_zero,Stk,Alloc,Live} + end, + [I|case NoZero of + true -> [{init,Y} || Y <- NeedInit]; + false -> [] + end]. + +alloc(#need{h=Words,f=0}) -> + Words; +alloc(#need{h=Words,f=Floats}) -> + {alloc,[{words,Words},{floats,Floats}]}. + +is_call([#cg_set{op=call,args=[#b_var{}|Args]}|_]) -> + {yes,1+length(Args)}; +is_call([#cg_set{op=call,args=[_|Args]}|_]) -> + {yes,length(Args)}; +is_call([#cg_set{op=make_fun,args=[_|Args]}|_]) -> + {yes,length(Args)}; +is_call(_) -> + no. + +cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=[#b_local{}=Func0|Args0]}, + Where, Context, St0) -> + [Dst|Args] = beam_args([Dst0|Args0], St0), + #b_local{name=Name0,arity=Arity} = Func0, + {atom,Name} = beam_arg(Name0, St0), + {FuncLbl,St} = local_func_label(Name, Arity, St0), + Line = call_line(Where, local, Anno), + Call = build_call(call, Arity, {f,FuncLbl}, Context, Dst), + Is = setup_args(Args, Anno, Context, St) ++ Line ++ Call, + {Is,St}; +cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=[#b_remote{}=Func0|Args0]}, + Where, Context, St) -> + [Dst|Args] = beam_args([Dst0|Args0], St), + #b_remote{mod=Mod0,name=Name0,arity=Arity} = Func0, + case {beam_arg(Mod0, St),beam_arg(Name0, St)} of + {{atom,Mod},{atom,Name}} -> + Func = {extfunc,Mod,Name,Arity}, + Line = call_line(Where, Func, Anno), + Call = build_call(call_ext, Arity, Func, Context, Dst), + Is = setup_args(Args, Anno, Context, St) ++ Line ++ Call, + {Is,St}; + {Mod,Name} -> + Apply = build_apply(Arity, Context, Dst), + Is = setup_args(Args++[Mod,Name], Anno, Context, St) ++ + [line(Anno)] ++ Apply, + {Is,St} + end; +cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=Args0}, + Where, Context, St) -> + [Dst,Func|Args] = beam_args([Dst0|Args0], St), + Line = call_line(Where, Func, Anno), + Arity = length(Args), + Call = build_call(call_fun, Arity, Func, Context, Dst), + Is = setup_args(Args++[Func], Anno, Context, St) ++ Line ++ Call, + {Is,St}. + +build_call(call_fun, Arity, _Func, none, Dst) -> + [{call_fun,Arity}|copy({x,0}, Dst)]; +build_call(call_fun, Arity, _Func, {return,Dst,N}, Dst) when is_integer(N) -> + [{call_fun,Arity},{deallocate,N},return]; +build_call(call_fun, Arity, _Func, {return,Val,N}, _Dst) when is_integer(N) -> + [{call_fun,Arity},{move,Val,{x,0}},{deallocate,N},return]; +build_call(call_ext, 2, {extfunc,erlang,'!',2}, none, Dst) -> + [send|copy({x,0}, Dst)]; +build_call(call_ext, 2, {extfunc,erlang,'!',2}, {return,Dst,N}, Dst) + when is_integer(N) -> + [send,{deallocate,N},return]; +build_call(Prefix, Arity, Func, {return,Dst,none}, Dst) -> + I = case Prefix of + call -> call_only; + call_ext -> call_ext_only + end, + [{I,Arity,Func}]; +build_call(call_ext, Arity, {extfunc,Mod,Name,Arity}=Func, {return,_,none}, _Dst) -> + true = erl_bifs:is_exit_bif(Mod, Name, Arity), %Assertion. + [{call_ext_only,Arity,Func}]; +build_call(Prefix, Arity, Func, {return,Dst,N}, Dst) when is_integer(N) -> + I = case Prefix of + call -> call_last; + call_ext -> call_ext_last + end, + [{I,Arity,Func,N}]; +build_call(I, Arity, Func, {return,Val,N}, _Dst) when is_integer(N) -> + [{I,Arity,Func}|copy(Val, {x,0})++[{deallocate,N},return]]; +build_call(I, Arity, Func, none, Dst) -> + [{I,Arity,Func}|copy({x,0}, Dst)]. + +build_apply(Arity, {return,Dst,N}, Dst) when is_integer(N) -> + [{apply_last,Arity,N}]; +build_apply(Arity, {return,Val,N}, _Dst) when is_integer(N) -> + [{apply,Arity}|copy(Val, {x,0})++[{deallocate,N},return]]; +build_apply(Arity, none, Dst) -> + [{apply,Arity}|copy({x,0}, Dst)]. + +cg_instr(put_map, [{atom,assoc},SrcMap|Ss], Dst, Set) -> + Live = get_live(Set), + [{put_map_assoc,{f,0},SrcMap,Dst,Live,{list,Ss}}]; +cg_instr(Op, Args, Dst, _Set) -> + cg_instr(Op, Args, Dst). + +cg_instr(bs_init_writable, Args, Dst) -> + setup_args(Args) ++ [bs_init_writable|copy({x,0}, Dst)]; +cg_instr(bs_restore, [Ctx,Slot], _Dst) -> + case Slot of + {integer,N} -> + [{bs_restore2,Ctx,N}]; + {atom,start} -> + [{bs_restore2,Ctx,Slot}] + end; +cg_instr(bs_save, [Ctx,Slot], _Dst) -> + {integer,N} = Slot, + [{bs_save2,Ctx,N}]; +cg_instr(build_stacktrace, Args, Dst) -> + setup_args(Args) ++ [build_stacktrace|copy({x,0}, Dst)]; +cg_instr(context_to_binary, [Src], _Dst) -> + [{bs_context_to_binary,Src}]; +cg_instr(set_tuple_element=Op, [New,Tuple,{integer,Index}], _Dst) -> + [{Op,New,Tuple,Index}]; +cg_instr({float,clearerror}, [], _Dst) -> + [fclearerror]; +cg_instr({float,get}, [Src], Dst) -> + [{fmove,Src,Dst}]; +cg_instr({float,put}, [Src], Dst) -> + [{fmove,Src,Dst}]; +cg_instr(get_hd=Op, [Src], Dst) -> + [{Op,Src,Dst}]; +cg_instr(get_tl=Op, [Src], Dst) -> + [{Op,Src,Dst}]; +cg_instr(get_tuple_element=Op, [Src,{integer,N}], Dst) -> + [{Op,Src,N,Dst}]; +cg_instr(put_list=Op, [Hd,Tl], Dst) -> + [{Op,Hd,Tl,Dst}]; +cg_instr(put_tuple_arity, [{integer,Arity}], Dst) -> + [{put_tuple,Arity,Dst}]; +cg_instr(put_tuple_elements, Elements, _Dst) -> + [{put,E} || E <- Elements]; +cg_instr(raw_raise, Args, Dst) -> + setup_args(Args) ++ [raw_raise|copy({x,0}, Dst)]; +cg_instr(remove_message, [], _Dst) -> + [remove_message]; +cg_instr(resume, [A,B], _Dst) -> + [{bif,raise,{f,0},[A,B],{x,0}}]; +cg_instr(timeout, [], _Dst) -> + [timeout]. + +cg_test(bs_add=Op, Fail, [Src1,Src2,{integer,Unit}], Dst, _I) -> + [{Op,Fail,[Src1,Src2,Unit],Dst}]; +cg_test(bs_skip, Fail, Args, _Dst, I) -> + cg_bs_skip(Fail, Args, I); +cg_test(bs_utf8_size=Op, Fail, [Src], Dst, _I) -> + [{Op,Fail,Src,Dst}]; +cg_test(bs_utf16_size=Op, Fail, [Src], Dst, _I) -> + [{Op,Fail,Src,Dst}]; +cg_test({float,convert}, Fail, [Src], Dst, _I) -> + {f,0} = Fail, %Assertion. + [{fconv,Src,Dst}]; +cg_test({float,Op0}, Fail, Args, Dst, #cg_set{anno=Anno}) -> + Op = case Op0 of + '+' -> fadd; + '-' when length(Args) =:= 2 -> fsub; + '-' -> fnegate; + '*' -> fmul; + '/' -> fdiv + end, + [line(Anno),{bif,Op,Fail,Args,Dst}]; +cg_test(get_map_element, Fail, [Map,Key], Dst, _I) -> + [{get_map_elements,Fail,Map,{list,[Key,Dst]}}]; +cg_test(peek_message, Fail, [], Dst, _I) -> + [{loop_rec,Fail,{x,0}}|copy({x,0}, Dst)]; +cg_test(put_map, Fail, [{atom,exact},SrcMap|Ss], Dst, Set) -> + Live = get_live(Set), + [{put_map_exact,Fail,SrcMap,Dst,Live,{list,Ss}}]; +cg_test(wait_timeout, Fail, [Timeout], _Dst, _) -> + case Timeout of + {atom,infinity} -> + [{wait,Fail}]; + _ -> + [{wait_timeout,Fail,Timeout}] + end. + +cg_bs_get(Fail, #cg_set{dst=Dst0,args=[#b_literal{val=Type}|Ss0]}=Set, St) -> + Op = case Type of + integer -> bs_get_integer2; + float -> bs_get_float2; + binary -> bs_get_binary2; + utf8 -> bs_get_utf8; + utf16 -> bs_get_utf16; + utf32 -> bs_get_utf32 + end, + [Dst|Ss1] = beam_args([Dst0|Ss0], St), + Ss = case Ss1 of + [Ctx,{literal,Flags},Size,{integer,Unit}] -> + %% Plain integer/float/binary. + [Ctx,Size,Unit,field_flags(Flags, Set)]; + [Ctx,{literal,Flags}] -> + %% Utf8/16/32. + [Ctx,field_flags(Flags, Set)] + end, + Live = get_live(Set), + [{test,Op,Fail,Live,Ss,Dst}]. + +cg_bs_skip(Fail, [{atom,Type}|Ss0], Set) -> + Op = case Type of + utf8 -> bs_skip_utf8; + utf16 -> bs_skip_utf16; + utf32 -> bs_skip_utf32; + _ -> bs_skip_bits2 + end, + Live = get_live(Set), + Ss = case Ss0 of + [Ctx,{literal,Flags},Size,{integer,Unit}] -> + %% Plain integer/float/binary. + [Ctx,Size,Unit,field_flags(Flags, Set)]; + [Ctx,{literal,Flags}] -> + %% Utf8/16/32. + [Ctx,Live,field_flags(Flags, Set)] + end, + case {Type,Ss} of + {binary,[_,{atom,all},1,_]} -> + []; + {binary,[R,{atom,all},U,_]} -> + [{test,bs_test_unit,Fail,[R,U]}]; + {_,_} -> + [{test,Op,Fail,Ss}] + end. + +field_flags(Flags, #cg_set{anno=#{location:={File,Line}}}) -> + {field_flags,[{anno,[Line,{file,File}]}|Flags]}; +field_flags(Flags, _) -> + {field_flags,Flags}. + +cg_bs_put(Fail, [{atom,Type},{literal,Flags}|Args]) -> + Op = case Type of + integer -> bs_put_integer; + float -> bs_put_float; + binary -> bs_put_binary; + utf8 -> bs_put_utf8; + utf16 -> bs_put_utf16; + utf32 -> bs_put_utf32 + end, + case Args of + [Src,Size,{integer,Unit}] -> + [{Op,Fail,Size,Unit,{field_flags,Flags},Src}]; + [Src] -> + [{Op,Fail,{field_flags,Flags},Src}] + end. + +cg_bs_init(Dst, Size0, Unit, Live, Fail) -> + Op = case Unit of + 1 -> bs_init_bits; + 8 -> bs_init2 + end, + Size = cg_bs_init_size(Size0), + [{Op,Fail,Size,0,Live,{field_flags,[]},Dst}]. + +cg_bs_init_size({x,_}=R) -> R; +cg_bs_init_size({y,_}=R) -> R; +cg_bs_init_size({integer,Int}) -> Int. + +cg_catch(Agg, T0, Context, St0) -> + {Moves,T1} = cg_extract(T0, Agg, St0), + {T,St} = cg_block(T1, Context, St0), + {Moves++T,St}. + +cg_try(Agg, Tag, T0, Context, St0) -> + {Moves0,T1} = cg_extract(T0, Agg, St0), + Moves = order_moves(Moves0, {x,3}), + [#cg_set{op=kill_try_tag}|T2] = T1, + {T,St} = cg_block(T2, Context, St0), + {[{try_case,Tag}|Moves++T],St}. + +cg_extract([#cg_set{op=extract,dst=Dst0,args=Args0}|Is0], Agg, St) -> + [Dst,Agg,{integer,X}] = beam_args([Dst0|Args0], St), + {Ds,Is} = cg_extract(Is0, Agg, St), + case keymember(Dst, 3, Ds) of + true -> + %% This destination will be overwritten. + {Ds,Is}; + false -> + {copy({x,X}, Dst)++Ds,Is} + end; +cg_extract(Is, _, _) -> + {[],Is}. + +copy(Src, Src) -> []; +copy(Src, Dst) -> [{move,Src,Dst}]. + +force_reg({literal,_}=Lit, Reg) -> + {Reg,[{move,Lit,Reg}]}; +force_reg({Kind,_}=R, _) when Kind =:= x; Kind =:= y -> + {R,[]}. + +%% successors(Terminator) -> [Successor]. +%% Return an ordset of all successors for the given terminator. + +successors(#cg_br{succ=Succ,fail=Fail}) -> + ordsets:from_list([Succ,Fail]); +successors(#cg_switch{fail=Fail,list=List}) -> + ordsets:from_list([Fail|[Lbl || {_,Lbl} <- List]]); +successors(#cg_ret{}) -> []. + +%% linearize(Blocks) -> [{BlockLabel,#cg_blk{}}]. +%% Linearize the intermediate representation of the code. Also +%% translate blocks from the SSA records to internal record types +%% used only in this module. + +linearize(Blocks) -> + Linear = beam_ssa:linearize(Blocks), + linearize_1(Linear, Blocks). + +linearize_1([{?BADARG_BLOCK,_}|Ls], Blocks) -> + linearize_1(Ls, Blocks); +linearize_1([{L,Block0}|Ls], Blocks) -> + Block = translate_block(L, Block0, Blocks), + [{L,Block}|linearize_1(Ls, Blocks)]; +linearize_1([], _Blocks) -> []. + +%% translate_block(BlockLabel, #b_blk{}, Blocks) -> #cg_blk{}. +%% Translate a block to the internal records used in this module. +%% Also eliminate phi nodes, replacing them with 'copy' instructions +%% in the predecessor blocks. + +translate_block(L, #b_blk{anno=Anno,is=Is0,last=Last0}, Blocks) -> + Last = translate_terminator(Last0), + PhiCopies = translate_phis(L, Last, Blocks), + Is1 = translate_is(Is0, PhiCopies), + Is = case Anno of + #{frame_size:=Size} -> + Alloc = #cg_alloc{stack=Size}, + [Alloc|Is1]; + #{} -> Is1 + end, + #cg_blk{anno=Anno,is=Is,last=Last}. + +translate_is([#b_set{op=phi}|Is], Tail) -> + translate_is(Is, Tail); +translate_is([#b_set{anno=Anno0,op=Op,dst=Dst,args=Args}=I|Is], Tail) -> + Anno = case beam_ssa:clobbers_xregs(I) of + true -> Anno0#{clobbers=>true}; + false -> Anno0 + end, + [#cg_set{anno=Anno,op=Op,dst=Dst,args=Args}|translate_is(Is, Tail)]; +translate_is([], Tail) -> Tail. + +translate_terminator(#b_ret{anno=Anno,arg=Arg}) -> + Dealloc = case Anno of + #{deallocate:=N} -> N; + #{} -> none + end, + #cg_ret{arg=Arg,dealloc=Dealloc}; +translate_terminator(#b_br{bool=#b_literal{val=true},succ=Succ}) -> + #cg_br{bool=#b_literal{val=true},succ=Succ,fail=Succ}; +translate_terminator(#b_br{bool=#b_literal{val=false},fail=Fail}) -> + #cg_br{bool=#b_literal{val=true},succ=Fail,fail=Fail}; +translate_terminator(#b_br{bool=Bool,succ=Succ,fail=Fail}) -> + #cg_br{bool=Bool,succ=Succ,fail=Fail}; +translate_terminator(#b_switch{arg=Bool,fail=Fail,list=List}) -> + #cg_switch{arg=Bool,fail=Fail,list=List}. + +translate_phis(L, #cg_br{succ=Target,fail=Target}, Blocks) -> + #b_blk{is=Is} = maps:get(Target, Blocks), + Phis = takewhile(fun(#b_set{op=phi}) -> true; + (#b_set{}) -> false + end, Is), + phi_copies(Phis, L); +translate_phis(_, _, _) -> []. + +phi_copies([#b_set{dst=Dst,args=PhiArgs}|Sets], L) -> + CopyArgs = [V || {V,Target} <- PhiArgs, Target =:= L], + [#cg_set{op=copy,dst=Dst,args=CopyArgs}|phi_copies(Sets, L)]; +phi_copies([], _) -> []. + +%% setup_args(Args, Anno, Context) -> [Instruction]. +%% setup_args(Args) -> [Instruction]. +%% Set up X registers for a call. + +setup_args(Args, Anno, none, St) -> + setup_args(Args) ++ kill_yregs(Anno, St); +setup_args(Args, _, _, _) -> + setup_args(Args). + +setup_args([]) -> + []; +setup_args([_|_]=Args) -> + Moves = gen_moves(Args, 0, []), + Scratch = {x,1+last(sort([length(Args)-1|[X || {x,X} <- Args]]))}, + order_moves(Moves, Scratch). + +%% kill_yregs(Anno, #cg{}) -> [{kill,{y,Y}}]. +%% Kill Y registers that will not be used again. + +kill_yregs(#{kill_yregs:=Kill}, #cg{regs=Regs}) -> + ordsets:from_list([{kill,maps:get(V, Regs)} || V <- Kill]); +kill_yregs(#{}, #cg{}) -> []. + +%% gen_moves(As, I, Acc) +%% Generate the basic move instruction to move the arguments +%% to their proper registers. The list will be sorted on +%% destinations. (I.e. the move to {x,0} will be first -- +%% see the comment to order_moves/2.) + +gen_moves([A|As], I, Acc) -> + gen_moves(As, I+1, copy(A, {x,I}) ++ Acc); +gen_moves([], _, Acc) -> + keysort(3, Acc). + +%% order_moves([Move], ScratchReg) -> [Move] +%% Orders move instruction so that source registers are not +%% destroyed before they are used. If there are cycles +%% (such as {move,{x,0},{x,1}}, {move,{x,1},{x,1}}), +%% the scratch register is used to break up the cycle. +%% If possible, the first move of the input list is placed +%% last in the result list (to make the move to {x,0} occur +%% just before the call to allow the Beam loader to coalesce +%% the instructions). + +order_moves(Ms, Scr) -> order_moves(Ms, Scr, []). + +order_moves([{move,_,_}=M|Ms0], ScrReg, Acc0) -> + {Chain,Ms} = collect_chain(Ms0, [M], ScrReg), + Acc = reverse(Chain, Acc0), + order_moves(Ms, ScrReg, Acc); +order_moves([], _, Acc) -> Acc. + +collect_chain(Ms, Path, ScrReg) -> + collect_chain(Ms, Path, [], ScrReg). + +collect_chain([{move,Src,Same}=M|Ms0], [{move,Same,_}|_]=Path, Others, ScrReg) -> + case keymember(Src, 3, Path) of + false -> + collect_chain(reverse(Others, Ms0), [M|Path], [], ScrReg); + true -> + %% There is a cycle, which we must break up. + {break_up_cycle(M, Path, ScrReg),reverse(Others, Ms0)} + end; +collect_chain([M|Ms], Path, Others, ScrReg) -> + collect_chain(Ms, Path, [M|Others], ScrReg); +collect_chain([], Path, Others, _) -> + {Path,Others}. + +break_up_cycle({move,Src,_}=M, Path, ScrReg) -> + [{move,ScrReg,Src},M|break_up_cycle1(Src, Path, ScrReg)]. + +break_up_cycle1(Dst, [{move,Src,Dst}|Path], ScrReg) -> + [{move,Src,ScrReg}|Path]; +break_up_cycle1(Dst, [M|Path], LastMove) -> + [M|break_up_cycle1(Dst, Path, LastMove)]. + +%%% +%%% General utility functions. +%%% + +verify_beam_register({x,_}=Reg) -> Reg. + +is_beam_register({x,_}) -> true; +is_beam_register(_) -> false. + +get_register(V, Regs) -> + case is_beam_register(V) of + true -> V; + false -> maps:get(V, Regs) + end. + +beam_args(As, St) -> + [beam_arg(A, St) || A <- As]. + +beam_arg(#b_var{name=Name}, #cg{regs=Regs}) -> + maps:get(Name, Regs); +beam_arg(#b_literal{val=Val}, _) -> + if + is_atom(Val) -> {atom,Val}; + is_float(Val) -> {float,Val}; + is_integer(Val) -> {integer,Val}; + Val =:= [] -> nil; + true -> {literal,Val} + end; +beam_arg(Reg, _) -> + verify_beam_register(Reg). + +new_block_label(L, St0) -> + {_Lbl,St} = label_for_block(L, St0), + St. + +def_block_label(L, #cg{labels=Labels,used_labels=Used}) -> + Lbl = maps:get(L, Labels), + case gb_sets:is_member(Lbl, Used) of + false -> []; + true -> [{label,Lbl}] + end. + +use_block_labels(Ls, St) -> + mapfoldl(fun use_block_label/2, St, Ls). + +use_block_label(L, #cg{used_labels=Used,catches=Catches}=St0) -> + {Lbl,St} = label_for_block(L, St0), + case gb_sets:is_member(L, Catches) of + true -> + {{catch_tag,{f,Lbl}}, + St#cg{used_labels=gb_sets:add(Lbl, Used)}}; + false -> + {{f,Lbl},St#cg{used_labels=gb_sets:add(Lbl, Used)}} + end. + +label_for_block(L, #cg{labels=Labels0}=St0) -> + case Labels0 of + #{L:=Lbl} -> + {Lbl,St0}; + #{} -> + {Lbl,St} = new_label(St0), + Labels = Labels0#{L=>Lbl}, + {Lbl,St#cg{labels=Labels}} + end. + +%% local_func_label(Name, Arity, State) -> {Label,State'} +%% local_func_label({Name,Arity}, State) -> {Label,State'} +%% Get the function entry label for a local function. + +local_func_label(Name, Arity, St) -> + local_func_label({Name,Arity}, St). + +local_func_label(Key, #cg{functable=Map}=St0) -> + case Map of + #{Key := Label} -> + {Label,St0}; + _ -> + {Label,St} = new_label(St0), + {Label,St#cg{functable=Map#{Key => Label}}} + end. + +%% is_gc_bif(Name, Args) -> true|false. +%% Determines whether the BIF Name/Arity might do a GC. + +-spec is_gc_bif(atom(), [beam_ssa:value()]) -> boolean(). + +is_gc_bif(hd, [_]) -> false; +is_gc_bif(tl, [_]) -> false; +is_gc_bif(self, []) -> false; +is_gc_bif(node, []) -> false; +is_gc_bif(node, [_]) -> false; +is_gc_bif(element, [_,_]) -> false; +is_gc_bif(get, [_]) -> false; +is_gc_bif(is_map_key, [_,_]) -> false; +is_gc_bif(map_get, [_,_]) -> false; +is_gc_bif(tuple_size, [_]) -> false; +is_gc_bif(Bif, Args) -> + Arity = length(Args), + not (erl_internal:bool_op(Bif, Arity) orelse + erl_internal:new_type_test(Bif, Arity) orelse + erl_internal:comp_op(Bif, Arity)). + +%% new_label(St) -> {L,St}. + +new_label(#cg{lcount=Next}=St) -> + {Next,St#cg{lcount=Next+1}}. + +%% call_line(tail|body, Func, Anno) -> [] | [{line,...}]. +%% Produce a line instruction if it will be needed by the +%% call to Func. + +call_line(_Context, {extfunc,Mod,Name,Arity}, Anno) -> + case erl_bifs:is_safe(Mod, Name, Arity) of + false -> + %% The call could be to a BIF. + %% We'll need a line instruction in case the + %% BIF call fails. + [line(Anno)]; + true -> + %% Call to a safe BIF. Since it cannot fail, + %% we don't need any line instruction here. + [] + end; +call_line(body, _, Anno) -> + [line(Anno)]; +call_line(tail, local, _) -> + %% Tail-recursive call to a local function. A line + %% instruction will not be useful. + []; +call_line(tail, _, Anno) -> + %% Call to a fun. + [line(Anno)]. + +%% line(Le) -> {line,[] | {location,File,Line}} +%% Create a line instruction, containing information about +%% the current filename and line number. A line information +%% instruction should be placed before any operation that could +%% cause an exception. + +line(#{location:={File,Line}}) -> + {line,[{location,File,Line}]}; +line(#{}) -> + {line,[]}. + +flatmapfoldl(F, Accu0, [Hd|Tail]) -> + {R,Accu1} = F(Hd, Accu0), + {Rs,Accu2} = flatmapfoldl(F, Accu1, Tail), + {R++Rs,Accu2}; +flatmapfoldl(_, Accu, []) -> {[],Accu}. diff --git a/lib/compiler/src/beam_ssa_lint.erl b/lib/compiler/src/beam_ssa_lint.erl new file mode 100644 index 0000000000..a003607dab --- /dev/null +++ b/lib/compiler/src/beam_ssa_lint.erl @@ -0,0 +1,349 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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% +%% +%% Purpose: Internal consistency checks for the beam_ssa format. + +-module(beam_ssa_lint). + +-export([module/2, format_error/1]). + +-import(lists, [append/1, foldl/3, foreach/2]). + +-include("beam_ssa.hrl"). + +-spec module(#b_module{}, [compile:option()]) -> + {'ok',#b_module{}} | {'error',list()}. +module(#b_module{body=Fs,name=Name}=Mod0, _Options) -> + Es0 = append([validate_function(F) || F <- Fs]), + case [{?MODULE,E} || E <- Es0] of + [] -> + {ok, Mod0}; + [_|_]=Es -> + {error,[{atom_to_list(Name), Es}]} + end. + +-spec format_error(term()) -> iolist(). +format_error({{_M,F,A},{redefined_variable, Name, Old, I}}) -> + io_lib:format("~p/~p: Variable ~ts (~ts) redefined by ~ts", + [F, A, format_var(Name), format_instr(Old), format_instr(I)]); +format_error({{_M,F,A},{missing_phi_paths, Paths, I}}) -> + io_lib:format("~p/~p: Phi node ~ts doesn't define a value for these " + "branches: ~w", + [F, A, format_instr(I), Paths]); +format_error({{_M,F,A},{garbage_phi_paths, Paths, I}}) -> + io_lib:format("~p/~p: Phi node ~ts defines a value for these unreachable " + "or non-existent branches: ~w", + [F, A, format_instr(I), Paths]); +format_error({{_M,F,A},{unknown_phi_variable, Name, {From, _To}, I}}) -> + io_lib:format("~p/~p: Variable ~ts used in phi node ~ts is undefined on " + "branch ~w", + [F, A, format_var(Name), format_instr(I), From]); +format_error({{_M,F,A},{unknown_block, Label, I}}) -> + io_lib:format("~p/~p: Unknown block ~p referenced in ~ts", + [F, A, Label, I]); +format_error({{_M,F,A},{unknown_variable, Name, I}}) -> + io_lib:format("~p/~p: Unbound variable ~ts used in ~ts", + [F, A, format_var(Name), format_instr(I)]); +format_error({{_M,F,A},{phi_inside_block, Name, Id}}) -> + io_lib:format("~p/~p: Phi node defining ~ts is not at start of block ~p", + [F, A, format_var(Name), Id]); +format_error({{_M,F,A},{undefined_label_in_phi, Label, I}}) -> + io_lib:format("~p/~p: Unknown block label ~p in phi node ~ts", + [F, A, Label, format_instr(I)]). + +format_instr(I) -> + [$',beam_ssa_pp:format_instr(I),$']. + +format_var(V) -> + beam_ssa_pp:format_var(#b_var{name=V}). + +validate_function(F) -> + try + validate_variables(F), + [] + catch + throw:Reason -> + #{func_info:=MFA} = F#b_function.anno, + [{MFA,Reason}]; + Class:Error:Stack -> + io:fwrite("Function: ~p\n", [F#b_function.anno]), + erlang:raise(Class, Error, Stack) + end. + +-type defined_vars() :: gb_sets:set(beam_ssa:var_name()). + +-record(vvars, + {blocks :: #{ beam_ssa:label() => beam_ssa:b_blk() }, + branch_def_vars :: #{ + %% Describes the variable state at the time of this exact branch (phi + %% node validation). + {From :: beam_ssa:label(), To :: beam_ssa:label()} => defined_vars(), + %% Describes the variable state common to all branches leading to this + %% label (un/redefined variable validation). + beam_ssa:label() => defined_vars() }, + defined_vars :: defined_vars()}). + +-spec validate_variables(beam_ssa:b_function()) -> ok. +validate_variables(#b_function{ args = Args, bs = Blocks }) -> + %% Prefill the mapping with function arguments. + ArgNames = vvars_get_varnames(Args), + DefVars = gb_sets:from_list(ArgNames), + Entry = 0, + + State = #vvars{blocks = Blocks, + branch_def_vars = #{ Entry => DefVars }, + defined_vars = DefVars}, + ok = vvars_assert_unique(Blocks, ArgNames), + vvars_phi_nodes(vvars_block(Entry, State)). + +%% Checks the uniqueness of all variables across all blocks. +-spec vvars_assert_unique(Blocks, [beam_ssa:var_name()]) -> ok when + Blocks :: #{ beam_ssa:label() => beam_ssa:b_blk() }. +vvars_assert_unique(Blocks, Args) -> + BlockIs = [Is || #b_blk{is=Is} <- maps:values(Blocks)], + Defined0 = maps:from_list([{V,argument} || V <- Args]), + _ = foldl(fun(Is, Defined) -> + vvars_assert_unique_1(Is, Defined) + end, Defined0, BlockIs), + ok. + +-spec vvars_assert_unique_1(Is, Defined) -> ok when + Is :: list(beam_ssa:b_set()), + Defined :: #{ beam_ssa:var_name() => beam_ssa:b_set() }. +vvars_assert_unique_1([#b_set{dst=#b_var{name=DstName}}=I|Is], Defined) -> + case Defined of + #{DstName:=Old} -> throw({redefined_variable, DstName, Old, I}); + _ -> vvars_assert_unique_1(Is, Defined#{DstName=>I}) + end; +vvars_assert_unique_1([], Defined) -> + Defined. + +-spec vvars_phi_nodes(State :: #vvars{}) -> ok. +vvars_phi_nodes(#vvars{ blocks = Blocks }=State) -> + _ = [vvars_phi_nodes_1(Is, Id, State) || + {Id, #b_blk{ is = Is }} <- maps:to_list(Blocks)], + ok. + +-spec vvars_phi_nodes_1(Is, Id, State) -> ok when + Is :: list(beam_ssa:b_set()), + Id :: beam_ssa:label(), + State :: #vvars{}. +vvars_phi_nodes_1([#b_set{ op = phi, args = Phis }=I | Is], Id, State) -> + ok = vvars_assert_phi_paths(Phis, I, Id, State), + ok = vvars_assert_phi_vars(Phis, I, Id, State), + vvars_phi_nodes_1(Is, Id, State); +vvars_phi_nodes_1([_ | Is], Id, _State) -> + case [Dst || #b_set{op=phi,dst=#b_var{name=Dst}} <- Is] of + [Name|_] -> + throw({phi_inside_block, Name, Id}); + [] -> + ok + end; +vvars_phi_nodes_1([], _Id, _State) -> + ok. + +%% Checks whether all paths leading to this phi node are represented, and that +%% it doesn't reference any non-existent paths. +-spec vvars_assert_phi_paths(Phis, I, Id, State) -> ok when + Phis :: list({beam_ssa:argument(), beam_ssa:label()}), + Id :: beam_ssa:label(), + I :: beam_ssa:b_set(), + State :: #vvars{}. +vvars_assert_phi_paths(Phis, I, Id, State) -> + BranchKeys = maps:keys(State#vvars.branch_def_vars), + RequiredPaths = ordsets:from_list([From || {From, To} <- BranchKeys, To =:= Id]), + ProvidedPaths = ordsets:from_list([From || {_Value, From} <- Phis]), + case ordsets:subtract(RequiredPaths, ProvidedPaths) of + [_|_]=MissingPaths -> throw({missing_phi_paths, MissingPaths, I}); + [] -> ok + end. + %% %% The following test is sometimes useful to find missing optimizations. + %% %% It is commented out, though, because it can be triggered by + %% %% by weird but legal code. + %% case ordsets:subtract(ProvidedPaths, RequiredPaths) of + %% [_|_]=GarbagePaths -> throw({garbage_phi_paths, GarbagePaths, I}); + %% [] -> ok + %% end. + +%% Checks whether all variables used in this phi node are defined in the branch +%% they arrived on. +-spec vvars_assert_phi_vars(Phis, I, Id, State) -> ok when + Phis :: list({beam_ssa:argument(), beam_ssa:label()}), + Id :: beam_ssa:label(), + I :: beam_ssa:b_set(), + State :: #vvars{}. +vvars_assert_phi_vars(Phis, I, Id, #vvars{blocks=Blocks, + branch_def_vars=BranchDefVars}) -> + Vars = [{Var, From} || {#b_var{}=Var, From} <- Phis], + foreach(fun({#b_var{name=VarName}, From}) -> + BranchKey = {From, Id}, + case BranchDefVars of + #{BranchKey:=DefVars} -> + case gb_sets:is_member(VarName, DefVars) of + true -> ok; + false -> throw({unknown_variable, VarName, I}) + end; + #{} -> + throw({unknown_phi_variable, VarName, BranchKey, I}) + end + end, Vars), + Labels = [From || {#b_literal{},From} <- Phis], + foreach(fun(Label) -> + case Blocks of + #{Label:=_} -> + ok; + #{} -> + throw({undefined_label_in_phi, Label, I}) + end + end, Labels). + +-spec vvars_block(Id, State) -> #vvars{} when + Id :: beam_ssa:label(), + State :: #vvars{}. +vvars_block(Id, State0) -> + #{ Id := #b_blk{ is = Is, last = Terminator} } = State0#vvars.blocks, + #{ Id := DefVars } = State0#vvars.branch_def_vars, + State = State0#vvars{ defined_vars = DefVars }, + vvars_terminator(Terminator, Id, vvars_block_1(Is, State)). + +-spec vvars_block_1(Blocks, State) -> #vvars{} when + Blocks :: list(beam_ssa:b_blk()), + State :: #vvars{}. +vvars_block_1([], State) -> + State; +vvars_block_1([#b_set{ dst = #b_var{ name = DstName }, op = phi } | Is], State0) -> + %% We don't check phi node arguments at this point since we may not have + %% visited their definition yet. They'll be handled later on in + %% vvars_phi_nodes/1 after all blocks are processed. + vvars_block_1(Is, vvars_save_var(DstName, State0)); +vvars_block_1([#b_set{ dst = #b_var{ name = DstName }, args = Args }=I | Is], State0) -> + ok = vvars_assert_args(Args, I, State0), + vvars_block_1(Is, vvars_save_var(DstName, State0)). + +-spec vvars_terminator(Terminator, From, State) -> #vvars{} when + Terminator :: beam_ssa:terminator(), + From :: beam_ssa:label(), + State :: #vvars{}. +vvars_terminator(#b_ret{ arg = Arg }=I, _From, State) -> + ok = vvars_assert_args([Arg], I, State), + State; +vvars_terminator(#b_switch{arg=Arg,fail=Fail,list=Switch}=I, From, State) -> + ok = vvars_assert_args([Arg], I, State), + ok = vvars_assert_args([A || {A,_Lbl} <- Switch], I, State), + Labels = [Fail | [Lbl || {_Arg, Lbl} <- Switch]], + ok = vvars_assert_labels(Labels, I, State), + vvars_terminator_1(Labels, From, State); +vvars_terminator(#b_br{bool=#b_literal{val=true},succ=Succ}=I, From, State) -> + Labels = [Succ], + ok = vvars_assert_labels(Labels, I, State), + vvars_terminator_1(Labels, From, State); +vvars_terminator(#b_br{bool=#b_literal{val=false},fail=Fail}=I, From, State) -> + Labels = [Fail], + ok = vvars_assert_labels(Labels, I, State), + vvars_terminator_1(Labels, From, State); +vvars_terminator(#b_br{ bool = Arg, succ = Succ, fail = Fail }=I, From, State) -> + ok = vvars_assert_args([Arg], I, State), + Labels = [Fail, Succ], + ok = vvars_assert_labels(Labels, I, State), + vvars_terminator_1(Labels, From, State). + +-spec vvars_terminator_1(Labels, From, State) -> #vvars{} when + Labels :: list(beam_ssa:label()), + From :: beam_ssa:label(), + State :: #vvars{}. +vvars_terminator_1(Labels0, From, State0) -> + %% Filter out all branches that have already been taken. This should result + %% in either all of Labels0 or an empty list. + Labels = [To || To <- Labels0, + not maps:is_key({From, To}, State0#vvars.branch_def_vars)], + true = Labels =:= Labels0 orelse Labels =:= [], %Assertion + State1 = foldl(fun(To, State) -> + vvars_save_branch(From, To, State) + end, State0, Labels), + foldl(fun(To, State) -> + vvars_block(To, State) + end, State1, Labels). + +%% Gets all variable names in args, ignoring literals etc +-spec vvars_get_varnames(Args) -> list(beam_ssa:var_name()) when + Args :: list(beam_ssa:argument()). +vvars_get_varnames(Args) -> + [Name || #b_var{ name = Name } <- Args]. + +%% Checks that all variables in Args are defined in all paths leading to the +%% current State. +-spec vvars_assert_args(Args, I, State) -> ok when + Args :: list(beam_ssa:argument()), + I :: beam_ssa:terminator() | beam_ssa:b_set(), + State :: #vvars{}. +vvars_assert_args(Args, I, #vvars{defined_vars=DefVars}=State) -> + foreach(fun(#b_remote{mod=Mod,name=Name}) -> + vvars_assert_args([Mod,Name], I, State); + (#b_var{name=Name}) -> + case gb_sets:is_member(Name, DefVars) of + true -> ok; + false -> throw({unknown_variable,Name,I}) + end; + (_) -> ok + end, Args). + +%% Checks that all given labels are defined in State. +-spec vvars_assert_labels(Labels, I, State) -> ok when + Labels :: list(beam_ssa:label()), + I :: beam_ssa:terminator(), + State :: #vvars{}. +vvars_assert_labels(Labels, I, #vvars{blocks=Blocks}) -> + foreach(fun(Label) -> + case maps:is_key(Label, Blocks) of + false -> throw({unknown_block, Label, I}); + true -> ok + end + end, Labels). + +-spec vvars_save_branch(From, To, State) -> #vvars{} when + From :: beam_ssa:label(), + To :: beam_ssa:label(), + State :: #vvars{}. +vvars_save_branch(From, To, State) -> + DefVars = State#vvars.defined_vars, + Branches0 = State#vvars.branch_def_vars, + case Branches0 of + #{ To := LblDefVars } -> + MergedVars = vvars_merge_branches(DefVars, LblDefVars), + + Branches = Branches0#{ To => MergedVars, {From, To} => DefVars }, + State#vvars { branch_def_vars = Branches }; + _ -> + Branches = Branches0#{ To => DefVars, {From, To} => DefVars }, + State#vvars { branch_def_vars = Branches } + end. + +-spec vvars_merge_branches(New, Existing) -> defined_vars() when + New :: defined_vars(), + Existing :: defined_vars(). +vvars_merge_branches(New, Existing) -> + gb_sets:intersection(New, Existing). + +-spec vvars_save_var(VarName, State) -> #vvars{} when + VarName :: beam_ssa:var_name(), + State :: #vvars{}. +vvars_save_var(VarName, State0) -> + %% vvars_assert_unique guarantees that variables are never set twice. + DefVars = gb_sets:insert(VarName, State0#vvars.defined_vars), + State0#vvars{ defined_vars = DefVars }. diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl new file mode 100644 index 0000000000..da466a3316 --- /dev/null +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -0,0 +1,1307 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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_ssa_opt). +-export([module/2]). + +-include("beam_ssa.hrl"). +-import(lists, [all/2,append/1,foldl/3,keyfind/3,member/2,reverse/1,reverse/2, + splitwith/2,takewhile/2,unzip/1]). + +-spec module(beam_ssa:b_module(), [compile:option()]) -> + {'ok',beam_ssa:b_module()}. + +module(#b_module{body=Fs0}=Module, Opts) -> + Ps = passes(Opts), + Fs = functions(Fs0, Ps), + {ok,Module#b_module{body=Fs}}. + +functions([F|Fs], Ps) -> + [function(F, Ps)|functions(Fs, Ps)]; +functions([], _Ps) -> []. + +-type b_blk() :: beam_ssa:b_blk(). +-type b_var() :: beam_ssa:b_var(). +-type label() :: beam_ssa:label(). + +-record(st, {ssa :: beam_ssa:block_map() | [{label(),b_blk()}], + args :: [b_var()], + cnt :: label()}). +-define(PASS(N), {N,fun N/1}). + +passes(Opts0) -> + Ps = [?PASS(ssa_opt_split_blocks), + ?PASS(ssa_opt_element), + ?PASS(ssa_opt_linearize), + ?PASS(ssa_opt_record), + ?PASS(ssa_opt_cse), + ?PASS(ssa_opt_type), + ?PASS(ssa_opt_float), + ?PASS(ssa_opt_live), + ?PASS(ssa_opt_bsm), + ?PASS(ssa_opt_bsm_shortcut), + ?PASS(ssa_opt_misc), + ?PASS(ssa_opt_blockify), + ?PASS(ssa_opt_sink), + ?PASS(ssa_opt_merge_blocks)], + Negations = [{list_to_atom("no_"++atom_to_list(N)),N} || + {N,_} <- Ps], + Opts = proplists:substitute_negations(Negations, Opts0), + [case proplists:get_value(Name, Opts, true) of + true -> + P; + false -> + {NoName,Name} = keyfind(Name, 2, Negations), + {NoName,fun(S) -> S end} + end || {Name,_}=P <- Ps]. + +function(#b_function{anno=Anno,bs=Blocks0,args=Args,cnt=Count0}=F, Ps) -> + try + St = #st{ssa=Blocks0,args=Args,cnt=Count0}, + #st{ssa=Blocks,cnt=Count} = compile:run_sub_passes(Ps, St), + F#b_function{bs=Blocks,cnt=Count} + catch + Class:Error:Stack -> + #{func_info:={_,Name,Arity}} = Anno, + io:fwrite("Function: ~w/~w\n", [Name,Arity]), + erlang:raise(Class, Error, Stack) + end. + +%%% +%%% Trivial sub passes. +%%% + +ssa_opt_linearize(#st{ssa=Blocks}=St) -> + St#st{ssa=beam_ssa:linearize(Blocks)}. + +ssa_opt_type(#st{ssa=Linear,args=Args}=St) -> + St#st{ssa=beam_ssa_type:opt(Linear, Args)}. + +ssa_opt_blockify(#st{ssa=Linear}=St) -> + St#st{ssa=maps:from_list(Linear)}. + +%%% +%%% Split blocks before certain instructions to enable more optimizations. +%%% +%%% Splitting before element/2 enables the optimization that swaps +%%% element/2 instructions. +%%% +%%% Splitting before call and make_fun instructions gives more opportunities +%%% for sinking get_tuple_element instructions. +%%% + +ssa_opt_split_blocks(#st{ssa=Blocks0,cnt=Count0}=St) -> + P = fun(#b_set{op={bif,element}}) -> true; + (#b_set{op=call}) -> true; + (#b_set{op=make_fun}) -> true; + (_) -> false + end, + {Blocks,Count} = beam_ssa:split_blocks(P, Blocks0, Count0), + St#st{ssa=Blocks,cnt=Count}. + +%%% +%%% Order element/2 calls. +%%% +%%% Order an unbroken chain of element/2 calls for the same tuple +%%% with the same failure label so that the highest element is +%%% retrieved first. That will allow the other element/2 calls to +%%% be replaced with get_tuple_element/3 instructions. +%%% + +ssa_opt_element(#st{ssa=Blocks}=St) -> + %% Collect the information about element instructions in this + %% function. + GetEls = collect_element_calls(beam_ssa:linearize(Blocks)), + + %% Collect the element instructions into chains. The + %% element calls in each chain are ordered in reverse + %% execution order. + Chains = collect_chains(GetEls, []), + + %% For each chain, swap the first element call with the + %% element call with the highest index. + St#st{ssa=swap_element_calls(Chains, Blocks)}. + +collect_element_calls([{L,#b_blk{is=Is0,last=Last}}|Bs]) -> + case {Is0,Last} of + {[#b_set{op={bif,element},dst=Element, + args=[#b_literal{val=N},#b_var{}=Tuple]}, + #b_set{op=succeeded,dst=Bool,args=[Element]}], + #b_br{bool=Bool,succ=Succ,fail=Fail}} -> + Info = {L,Succ,{Tuple,Fail},N}, + [Info|collect_element_calls(Bs)]; + {_,_} -> + collect_element_calls(Bs) + end; +collect_element_calls([]) -> []. + +collect_chains([{This,_,V,_}=El|Els], [{_,This,V,_}|_]=Chain) -> + %% Add to the previous chain. + collect_chains(Els, [El|Chain]); +collect_chains([El|Els], [_,_|_]=Chain) -> + %% Save the previous chain and start a new chain. + [Chain|collect_chains(Els, [El])]; +collect_chains([El|Els], _Chain) -> + %% The previous chain is too short; discard it and start a new. + collect_chains(Els, [El]); +collect_chains([], [_,_|_]=Chain) -> + %% Save the last chain. + [Chain]; +collect_chains([], _) -> []. + +swap_element_calls([[{L,_,_,N}|_]=Chain|Chains], Blocks0) -> + Blocks = swap_element_calls_1(Chain, {N,L}, Blocks0), + swap_element_calls(Chains, Blocks); +swap_element_calls([], Blocks) -> Blocks. + +swap_element_calls_1([{L1,_,_,N1}], {N2,L2}, Blocks) when N2 > N1 -> + %% We have reached the end of the chain, and the first + %% element instrution to be executed. Its index is lower + %% than the maximum index found while traversing the chain, + %% so we will need to swap the instructions. + #{L1:=Blk1,L2:=Blk2} = Blocks, + [#b_set{dst=Dst1}=GetEl1,Succ1] = Blk1#b_blk.is, + [#b_set{dst=Dst2}=GetEl2,Succ2] = Blk2#b_blk.is, + Is1 = [GetEl2,Succ1#b_set{args=[Dst2]}], + Is2 = [GetEl1,Succ2#b_set{args=[Dst1]}], + Blocks#{L1:=Blk1#b_blk{is=Is1},L2:=Blk2#b_blk{is=Is2}}; +swap_element_calls_1([{L,_,_,N1}|Els], {N2,_}, Blocks) when N1 > N2 -> + swap_element_calls_1(Els, {N2,L}, Blocks); +swap_element_calls_1([_|Els], Highest, Blocks) -> + swap_element_calls_1(Els, Highest, Blocks); +swap_element_calls_1([], _, Blocks) -> + %% Nothing to do. The element call with highest index + %% is already the first one to be executed. + Blocks. + +%%% +%%% Record optimization. +%%% +%%% Replace tuple matching with an is_tagged_tuple instruction +%%% when applicable. +%%% + +ssa_opt_record(#st{ssa=Linear}=St) -> + Blocks = maps:from_list(Linear), + St#st{ssa=record_opt(Linear, Blocks)}. + +record_opt([{L,#b_blk{is=Is0,last=Last}=Blk0}|Bs], Blocks) -> + Is = record_opt_is(Is0, Last, Blocks), + Blk = Blk0#b_blk{is=Is}, + [{L,Blk}|record_opt(Bs, Blocks)]; +record_opt([], _Blocks) -> []. + +record_opt_is([#b_set{op={bif,is_tuple},dst=#b_var{name=Bool}, + args=[Tuple]}=Set], + Last, Blocks) -> + case is_tagged_tuple(Tuple, Bool, Last, Blocks) of + {yes,Size,Tag} -> + Args = [Tuple,Size,Tag], + [Set#b_set{op=is_tagged_tuple,args=Args}]; + no -> + [Set] + end; +record_opt_is([I|Is], Last, Blocks) -> + [I|record_opt_is(Is, Last, Blocks)]; +record_opt_is([], _Last, _Blocks) -> []. + +is_tagged_tuple(#b_var{name=Tuple}, Bool, + #b_br{bool=#b_var{name=Bool},succ=Succ,fail=Fail}, + Blocks) -> + SuccBlk = maps:get(Succ, Blocks), + is_tagged_tuple_1(SuccBlk, Tuple, Fail, Blocks); +is_tagged_tuple(_, _, _, _) -> no. + +is_tagged_tuple_1(#b_blk{is=Is,last=Last}, Tuple, Fail, Blocks) -> + case Is of + [#b_set{op={bif,tuple_size},dst=#b_var{name=ArityVar}, + args=[#b_var{name=Tuple}]}, + #b_set{op={bif,'=:='}, + dst=#b_var{name=Bool}, + args=[#b_var{name=ArityVar}, + #b_literal{val=ArityVal}=Arity]}] + when is_integer(ArityVal) -> + case Last of + #b_br{bool=#b_var{name=Bool},succ=Succ,fail=Fail} -> + SuccBlk = maps:get(Succ, Blocks), + case is_tagged_tuple_2(SuccBlk, Tuple, Fail) of + no -> + no; + {yes,Tag} -> + {yes,Arity,Tag} + end; + _ -> + no + end; + _ -> + no + end. + +is_tagged_tuple_2(#b_blk{is=Is, + last=#b_br{bool=#b_var{name=Bool},fail=Fail}}, + Tuple, Fail) -> + is_tagged_tuple_3(Is, Bool, Tuple); +is_tagged_tuple_2(#b_blk{}, _, _) -> no. + +is_tagged_tuple_3([#b_set{op=get_tuple_element, + dst=#b_var{name=TagVar}, + args=[#b_var{name=Tuple},#b_literal{val=0}]}|Is], + Bool, Tuple) -> + is_tagged_tuple_4(Is, Bool, TagVar); +is_tagged_tuple_3([_|Is], Bool, Tuple) -> + is_tagged_tuple_3(Is, Bool, Tuple); +is_tagged_tuple_3([], _, _) -> no. + +is_tagged_tuple_4([#b_set{op={bif,'=:='},dst=#b_var{name=Bool}, + args=[#b_var{name=TagVar}, + #b_literal{val=TagVal}=Tag]}], + Bool, TagVar) when is_atom(TagVal) -> + {yes,Tag}; +is_tagged_tuple_4([_|Is], Bool, TagVar) -> + is_tagged_tuple_4(Is, Bool, TagVar); +is_tagged_tuple_4([], _, _) -> no. + +%%% +%%% Common subexpression elimination (CSE). +%%% +%%% Eliminate repeated evaluation of identical expressions. To avoid +%%% increasing the size of the stack frame, we don't eliminate +%%% subexpressions across instructions that clobber the X registers. +%%% + +ssa_opt_cse(#st{ssa=Linear}=St) -> + M = #{0=>#{}}, + St#st{ssa=cse(Linear, #{}, M)}. + +cse([{L,#b_blk{is=Is0,last=Last0}=Blk}|Bs], Sub0, M0) -> + Es0 = maps:get(L, M0), + {Is1,Es,Sub} = cse_is(Is0, Es0, Sub0, []), + Last = sub(Last0, Sub), + M = cse_successors(Is1, Blk, Es, M0), + Is = reverse(Is1), + [{L,Blk#b_blk{is=Is,last=Last}}|cse(Bs, Sub, M)]; +cse([], _, _) -> []. + +cse_successors([#b_set{op=succeeded,args=[Src]},Bif|_], Blk, EsSucc, M0) -> + case cse_suitable(Bif) of + true -> + %% The previous instruction only has a valid value at the success branch. + %% We must remove the substitution for Src from the failure branch. + #b_blk{last=#b_br{succ=Succ,fail=Fail}} = Blk, + M = cse_successors_1([Succ], EsSucc, M0), + EsFail = maps:filter(fun(_, Val) -> Val =/= Src end, EsSucc), + cse_successors_1([Fail], EsFail, M); + false -> + %% There can't be any replacement for Src in EsSucc. No need for + %% any special handling. + cse_successors_1(beam_ssa:successors(Blk), EsSucc, M0) + end; +cse_successors(_Is, Blk, Es, M) -> + cse_successors_1(beam_ssa:successors(Blk), Es, M). + +cse_successors_1([L|Ls], Es0, M) -> + case M of + #{L:=Es1} -> + Es = maps:filter(fun(Key, Value) -> + case Es1 of + #{Key:=Value} -> true; + #{} -> false + end + end, Es0), + cse_successors_1(Ls, Es0, M#{L:=Es}); + #{} -> + cse_successors_1(Ls, Es0, M#{L=>Es0}) + end; +cse_successors_1([], _, M) -> M. + +cse_is([#b_set{op=succeeded,dst=Bool,args=[Src]}=I0|Is], Es, Sub0, Acc) -> + I = sub(I0, Sub0), + case I of + #b_set{args=[Src]} -> + cse_is(Is, Es, Sub0, [I|Acc]); + #b_set{} -> + %% The previous instruction has been eliminated. Eliminate the + %% 'succeeded' instruction too. + Sub = Sub0#{Bool=>#b_literal{val=true}}, + cse_is(Is, Es, Sub, Acc) + end; +cse_is([#b_set{dst=Dst}=I0|Is], Es0, Sub0, Acc) -> + I = sub(I0, Sub0), + case beam_ssa:clobbers_xregs(I) of + true -> + %% Retaining the expressions map across calls and other + %% clobbering instructions would work, but it would cause + %% the common subexpressions to be saved to Y registers, + %% which would probably increase the size of the stack + %% frame. + cse_is(Is, #{}, Sub0, [I|Acc]); + false -> + case cse_expr(I) of + none -> + %% Not suitable for CSE. + cse_is(Is, Es0, Sub0, [I|Acc]); + {ok,ExprKey} -> + case Es0 of + #{ExprKey:=Src} -> + Sub = Sub0#{Dst=>Src}, + cse_is(Is, Es0, Sub, Acc); + #{} -> + Es = Es0#{ExprKey=>Dst}, + cse_is(Is, Es, Sub0, [I|Acc]) + end + end + end; +cse_is([], Es, Sub, Acc) -> + {Acc,Es,Sub}. + +cse_expr(#b_set{op=Op,args=Args}=I) -> + case cse_suitable(I) of + true -> {ok,{Op,Args}}; + false -> none + end. + +cse_suitable(#b_set{op=get_hd}) -> true; +cse_suitable(#b_set{op=get_tl}) -> true; +cse_suitable(#b_set{op=put_list}) -> true; +cse_suitable(#b_set{op=put_tuple}) -> true; +cse_suitable(#b_set{op={bif,Name},args=Args}) -> + %% Doing CSE for comparison operators would prevent + %% creation of 'test' instructions. + Arity = length(Args), + not (erl_internal:new_type_test(Name, Arity) orelse + erl_internal:comp_op(Name, Arity) orelse + erl_internal:bool_op(Name, Arity)); +cse_suitable(#b_set{}) -> false. + +%%% +%%% Using floating point instructions. +%%% +%%% Use the special floating points version of arithmetic +%%% instructions, if the operands are known to be floats or the result +%%% of the operation will be a float. +%%% +%%% The float instructions were never used in guards before, so we +%%% will take special care to keep not using them in guards. Using +%%% them in guards would require a new version of the 'fconv' +%%% instruction that would take a failure label. Since it is unlikely +%%% that using float instructions in guards would be benefical, why +%%% bother implementing a new instruction? Also, implementing float +%%% instructions in guards in HiPE could turn out to be a lot of work. +%%% + +-record(fs, + {s=undefined :: 'undefined' | 'cleared', + regs=#{} :: #{beam_ssa:b_var():=beam_ssa:b_var()}, + fail=none :: 'none' | beam_ssa:label(), + ren=#{} :: #{beam_ssa:label():=beam_ssa:label()}, + non_guards :: gb_sets:set(beam_ssa:label()) + }). + +ssa_opt_float(#st{ssa=Linear0,cnt=Count0}=St) -> + NonGuards0 = float_non_guards(Linear0), + NonGuards = gb_sets:from_list(NonGuards0), + Fs = #fs{non_guards=NonGuards}, + {Linear,Count} = float_opt(Linear0, Count0, Fs), + St#st{ssa=Linear,cnt=Count}. + +float_non_guards([{L,#b_blk{is=Is}}|Bs]) -> + case Is of + [#b_set{op=landingpad}|_] -> + [L|float_non_guards(Bs)]; + _ -> + float_non_guards(Bs) + end; +float_non_guards([]) -> [?BADARG_BLOCK]. + +float_opt([{L,Blk0}|Bs], Count, Fs) -> + Blk = float_rename_phis(Blk0, Fs), + case float_need_flush(Blk, Fs) of + true -> + float_flush(L, Blk, Bs, Count, Fs); + false -> + float_opt_1(L, Blk, Bs, Count, Fs) + end; +float_opt([], Count, _Fs) -> + {[],Count}. + +float_opt_1(L, #b_blk{last=#b_br{fail=F}}=Blk, Bs0, + Count0, #fs{non_guards=NonGuards}=Fs) -> + case gb_sets:is_member(F, NonGuards) of + true -> + %% This block is not inside a guard. + %% We can do the optimization. + float_opt_2(L, Blk, Bs0, Count0, Fs); + false -> + %% This block is inside a guard. Don't do + %% any floating point optimizations. + {Bs,Count} = float_opt(Bs0, Count0, Fs), + {[{L,Blk}|Bs],Count} + end; +float_opt_1(L, Blk, Bs, Count, Fs) -> + float_opt_2(L, Blk, Bs, Count, Fs). + +float_opt_2(L, #b_blk{is=Is0}=Blk0, Bs0, Count0, Fs0) -> + case float_opt_is(Is0, Fs0, Count0, []) of + {Is1,Fs1,Count1} -> + Fs = float_fail_label(Blk0, Fs1), + Split = float_split_conv(Is1, Blk0), + {Blks0,Count2} = float_number(Split, L, Count1), + {Blks,Count3} = float_conv(Blks0, Fs#fs.fail, Count2), + {Bs,Count} = float_opt(Bs0, Count3, Fs), + {Blks++Bs,Count}; + none -> + {Bs,Count} = float_opt(Bs0, Count0, Fs0), + {[{L,Blk0}|Bs],Count} + end. + +%% Split {float,convert} instructions into individual blocks. +float_split_conv(Is0, Blk) -> + Br = #b_br{bool=#b_literal{val=true},succ=0,fail=0}, + case splitwith(fun(#b_set{op=Op}) -> + Op =/= {float,convert} + end, Is0) of + {Is,[]} -> + [Blk#b_blk{is=Is}]; + {[_|_]=Is1,[#b_set{op={float,convert}}=Conv|Is2]} -> + [#b_blk{is=Is1,last=Br}, + #b_blk{is=[Conv],last=Br}|float_split_conv(Is2, Blk)]; + {[],[#b_set{op={float,convert}}=Conv|Is1]} -> + [#b_blk{is=[Conv],last=Br}|float_split_conv(Is1, Blk)] + end. + +%% Number the blocks that were split. +float_number([B|Bs0], FirstL, Count0) -> + {Bs,Count} = float_number(Bs0, Count0), + {[{FirstL,B}|Bs],Count}. + +float_number([B|Bs0], Count0) -> + {Bs,Count} = float_number(Bs0, Count0+1), + {[{Count0,B}|Bs],Count}; +float_number([], Count) -> + {[],Count}. + +%% Insert 'succeeded' instructions after each {float,convert} +%% instruction. +float_conv([{L,#b_blk{is=Is0}=Blk0}|Bs0], Fail, Count0) -> + case Is0 of + [#b_set{op={float,convert}}=Conv] -> + {Bool0,Count1} = new_reg('@ssa_bool', Count0), + Bool = #b_var{name=Bool0}, + Succeeded = #b_set{op=succeeded,dst=Bool, + args=[Conv#b_set.dst]}, + Is = [Conv,Succeeded], + [{NextL,_}|_] = Bs0, + Br = #b_br{bool=Bool,succ=NextL,fail=Fail}, + Blk = Blk0#b_blk{is=Is,last=Br}, + {Bs,Count} = float_conv(Bs0, Fail, Count1), + {[{L,Blk}|Bs],Count}; + [_|_] -> + case Bs0 of + [{NextL,_}|_] -> + Br = #b_br{bool=#b_literal{val=true}, + succ=NextL,fail=NextL}, + Blk = Blk0#b_blk{last=Br}, + {Bs,Count} = float_conv(Bs0, Fail, Count0), + {[{L,Blk}|Bs],Count}; + [] -> + {[{L,Blk0}],Count0} + end + end. + +float_need_flush(#b_blk{is=Is}, #fs{s=cleared}) -> + case Is of + [#b_set{anno=#{float_op:=_}}|_] -> + false; + _ -> + true + end; +float_need_flush(_, _) -> false. + +float_opt_is([#b_set{op=succeeded,args=[Src]}=I0], + #fs{regs=Rs}=Fs, Count, Acc) -> + case Rs of + #{Src:=Fr} -> + I = I0#b_set{args=[Fr]}, + {reverse(Acc, [I]),Fs,Count}; + #{} -> + {reverse(Acc, [I0]),Fs,Count} + end; +float_opt_is([#b_set{anno=Anno0}=I0|Is0], Fs0, Count0, Acc) -> + case Anno0 of + #{float_op:=FTypes} -> + Anno = maps:remove(float_op, Anno0), + I1 = I0#b_set{anno=Anno}, + {Is,Fs,Count} = float_make_op(I1, FTypes, Fs0, Count0), + float_opt_is(Is0, Fs, Count, reverse(Is, Acc)); + #{} -> + float_opt_is(Is0, Fs0#fs{regs=#{}}, Count0, [I0|Acc]) + end; +float_opt_is([], Fs, _Count, _Acc) -> + #fs{s=undefined} = Fs, %Assertion. + none. + +float_rename_phis(#b_blk{is=Is}=Blk, #fs{ren=Ren}) -> + if + map_size(Ren) =:= 0 -> + Blk; + true -> + Blk#b_blk{is=float_rename_phis_1(Is, Ren)} + end. + +float_rename_phis_1([#b_set{op=phi,args=Args0}=I|Is], Ren) -> + Args = [float_phi_arg(Arg, Ren) || Arg <- Args0], + [I#b_set{args=Args}|float_rename_phis_1(Is, Ren)]; +float_rename_phis_1(Is, _) -> Is. + +float_phi_arg({Var,OldLbl}, Ren) -> + case Ren of + #{OldLbl:=NewLbl} -> + {Var,NewLbl}; + #{} -> + {Var,OldLbl} + end. + +float_make_op(#b_set{op={bif,Op},dst=Dst,args=As0}=I0, + Ts, #fs{s=S,regs=Rs0}=Fs, Count0) -> + {As1,Rs1,Count1} = float_load(As0, Ts, Rs0, Count0, []), + {As,Is0} = unzip(As1), + {Fr,Count2} = new_reg('@fr', Count1), + FrDst = #b_var{name=Fr}, + I = I0#b_set{op={float,Op},dst=FrDst,args=As}, + Rs = Rs1#{Dst=>FrDst}, + Is = append(Is0) ++ [I], + case S of + undefined -> + {Ignore,Count} = new_reg('@ssa_ignore', Count2), + C = #b_set{op={float,clearerror},dst=#b_var{name=Ignore}}, + {[C|Is],Fs#fs{s=cleared,regs=Rs},Count}; + cleared -> + {Is,Fs#fs{regs=Rs},Count2} + end. + +float_load([A|As], [T|Ts], Rs0, Count0, Acc) -> + {Load,Rs,Count} = float_reg_arg(A, T, Rs0, Count0), + float_load(As, Ts, Rs, Count, [Load|Acc]); +float_load([], [], Rs, Count, Acc) -> + {reverse(Acc),Rs,Count}. + +float_reg_arg(A, T, Rs, Count0) -> + case Rs of + #{A:=Fr} -> + {{Fr,[]},Rs,Count0}; + #{} -> + {Fr,Count} = new_float_copy_reg(Count0), + Dst = #b_var{name=Fr}, + I = float_load_reg(T, A, Dst), + {{Dst,[I]},Rs#{A=>Dst},Count} + end. + +float_load_reg(convert, #b_var{}=Src, Dst) -> + #b_set{op={float,convert},dst=Dst,args=[Src]}; +float_load_reg(convert, #b_literal{val=Val}=Src, Dst) -> + try float(Val) of + F -> + #b_set{op={float,put},dst=Dst,args=[#b_literal{val=F}]} + catch + error:_ -> + %% Let the exception happen at runtime. + #b_set{op={float,convert},dst=Dst,args=[Src]} + end; +float_load_reg(float, Src, Dst) -> + #b_set{op={float,put},dst=Dst,args=[Src]}. + +new_float_copy_reg(Count) -> + new_reg('@fr_copy', Count). + +new_reg(Base, Count) -> + Fr = {Base,Count}, + {Fr,Count+1}. + +float_flush(L, Blk, Bs0, Count0, #fs{s=cleared,fail=Fail,ren=Ren0}=Fs0) -> + {Bool0,Count1} = new_reg('@ssa_bool', Count0), + Bool = #b_var{name=Bool0}, + + %% Insert two blocks before the current block. First allocate + %% block numbers. + FirstL = L, %For checkerror. + MiddleL = Count1, %For flushed float regs. + LastL = Count1 + 1, %For original block. + Count2 = Count1 + 2, + + %% Build the block with the checkerror instruction. + CheckIs = [#b_set{op={float,checkerror},dst=Bool}], + FirstBlk = #b_blk{is=CheckIs,last=#b_br{bool=Bool,succ=MiddleL,fail=Fail}}, + + %% Build the block that flushes all registers. Note that this must be a + %% separate block in case the original block begins with a phi instruction, + %% to avoid embedding a phi instruction in the middle of a block. + FlushIs = float_flush_regs(Fs0), + MiddleBlk = #b_blk{is=FlushIs,last=#b_br{bool=#b_literal{val=true}, + succ=LastL,fail=LastL}}, + + %% The last block is the original unmodified block. + LastBlk = Blk, + + %% Update state and blocks. + Ren = Ren0#{L=>LastL}, + Fs = Fs0#fs{s=undefined,regs=#{},fail=none,ren=Ren}, + Bs1 = [{FirstL,FirstBlk},{MiddleL,MiddleBlk},{LastL,LastBlk}|Bs0], + float_opt(Bs1, Count2, Fs). + +float_fail_label(#b_blk{last=Last}, Fs) -> + case Last of + #b_br{bool=#b_var{},fail=Fail} -> + Fs#fs{fail=Fail}; + _ -> + Fs + end. + +float_flush_regs(#fs{regs=Rs}) -> + maps:fold(fun(_, #b_var{name={'@fr_copy',_}}, Acc) -> + Acc; + (Dst, Fr, Acc) -> + [#b_set{op={float,get},dst=Dst,args=[Fr]}|Acc] + end, [], Rs). + +%%% +%%% Live optimization. +%%% +%%% Optimize instructions whose values are not used. They could be +%%% removed if they have no side effects, or in a few cases replaced +%%% with a cheaper instructions +%%% + +ssa_opt_live(#st{ssa=Linear}=St) -> + St#st{ssa=live_opt(reverse(Linear), #{}, [])}. + +live_opt([{L,Blk0}|Bs], LiveMap0, Acc) -> + Successors = beam_ssa:successors(Blk0), + Live0 = live_opt_succ(Successors, L, LiveMap0), + {Blk,Live} = live_opt_blk(Blk0, Live0), + LiveMap = live_opt_phis(Blk#b_blk.is, L, Live, LiveMap0), + live_opt(Bs, LiveMap, [{L,Blk}|Acc]); +live_opt([], _, Acc) -> Acc. + +live_opt_succ([S|Ss], L, LiveMap) -> + Live0 = live_opt_succ(Ss, L, LiveMap), + Key = {S,L}, + case LiveMap of + #{Key:=Live} -> + gb_sets:union(Live, Live0); + #{S:=Live} -> + gb_sets:union(Live, Live0); + #{} -> + Live0 + end; +live_opt_succ([], _, _) -> + gb_sets:empty(). + +live_opt_phis(Is, L, Live0, LiveMap0) -> + LiveMap = LiveMap0#{L=>Live0}, + Phis = takewhile(fun(#b_set{op=Op}) -> Op =:= phi end, Is), + case Phis of + [] -> + LiveMap; + [_|_] -> + PhiArgs = append([Args || #b_set{args=Args} <- Phis]), + PhiVars = [{P,V} || {#b_var{name=V},P} <- PhiArgs], + PhiLive0 = rel2fam(PhiVars), + PhiLive = [{{L,P},gb_sets:union(gb_sets:from_list(Vs), Live0)} || + {P,Vs} <- PhiLive0], + maps:merge(LiveMap, maps:from_list(PhiLive)) + end. + +live_opt_blk(#b_blk{is=Is0,last=Last}=Blk, Live0) -> + Live1 = gb_sets:union(Live0, gb_sets:from_ordset(beam_ssa:used(Last))), + {Is,Live} = live_opt_is(reverse(Is0), Live1, []), + {Blk#b_blk{is=Is},Live}. + +live_opt_is([#b_set{op=phi,dst=#b_var{name=Dst}}=I|Is], Live, Acc) -> + case gb_sets:is_member(Dst, Live) of + true -> + live_opt_is(Is, Live, [I|Acc]); + false -> + live_opt_is(Is, Live, Acc) + end; +live_opt_is([#b_set{op=succeeded,dst=#b_var{name=SuccDst}=SuccDstVar, + args=[#b_var{name=Dst}]}=SuccI, + #b_set{dst=#b_var{name=Dst}}=I|Is], Live0, Acc) -> + case gb_sets:is_member(Dst, Live0) of + true -> + case gb_sets:is_member(SuccDst, Live0) of + true -> + Live1 = gb_sets:add(Dst, Live0), + Live = gb_sets:delete_any(SuccDst, Live1), + live_opt_is([I|Is], Live, [SuccI|Acc]); + false -> + live_opt_is([I|Is], Live0, Acc) + end; + false -> + case live_opt_unused(I) of + {replace,NewI0} -> + NewI = NewI0#b_set{dst=SuccDstVar}, + live_opt_is([NewI|Is], Live0, Acc); + keep -> + case gb_sets:is_member(SuccDst, Live0) of + true -> + Live1 = gb_sets:add(Dst, Live0), + Live = gb_sets:delete_any(SuccDst, Live1), + live_opt_is([I|Is], Live, [SuccI|Acc]); + false -> + live_opt_is([I|Is], Live0, Acc) + end + end + end; +live_opt_is([#b_set{op=Op,dst=#b_var{name=Dst}}=I|Is], Live0, Acc) -> + case gb_sets:is_member(Dst, Live0) of + true -> + Live1 = gb_sets:union(Live0, gb_sets:from_ordset(beam_ssa:used(I))), + Live = gb_sets:delete_any(Dst, Live1), + live_opt_is(Is, Live, [I|Acc]); + false -> + case is_pure(Op) of + true -> + live_opt_is(Is, Live0, Acc); + false -> + Live = gb_sets:union(Live0, gb_sets:from_ordset(beam_ssa:used(I))), + live_opt_is(Is, Live, [I|Acc]) + end + end; +live_opt_is([], Live, Acc) -> + {Acc,Live}. + +is_pure({bif,_}) -> true; +is_pure({float,get}) -> true; +is_pure(bs_extract) -> true; +is_pure(extract) -> true; +is_pure(get_hd) -> true; +is_pure(get_tl) -> true; +is_pure(get_tuple_element) -> true; +is_pure(is_nonempty_list) -> true; +is_pure(is_tagged_tuple) -> true; +is_pure(put_list) -> true; +is_pure(put_tuple) -> true; +is_pure(_) -> false. + +live_opt_unused(#b_set{op=get_map_element}=Set) -> + {replace,Set#b_set{op=has_map_field}}; +live_opt_unused(_) -> keep. + +%%% +%%% Optimize binary matching instructions. +%%% + +ssa_opt_bsm(#st{ssa=Linear}=St) -> + Extracted0 = bsm_extracted(Linear), + Extracted = cerl_sets:from_list(Extracted0), + St#st{ssa=bsm_skip(Linear, Extracted)}. + +bsm_skip([{L,#b_blk{is=Is0}=Blk}|Bs], Extracted) -> + Is = bsm_skip_is(Is0, Extracted), + [{L,Blk#b_blk{is=Is}}|bsm_skip(Bs, Extracted)]; +bsm_skip([], _) -> []. + +bsm_skip_is([I0|Is], Extracted) -> + case I0 of + #b_set{op=bs_match,args=[#b_literal{val=string}|_]} -> + [I0|bsm_skip_is(Is, Extracted)]; + #b_set{op=bs_match,dst=Ctx,args=[Type,PrevCtx|Args0]} -> + I = case cerl_sets:is_element(Ctx, Extracted) of + true -> + I0; + false -> + %% The value is never extracted. + Args = [#b_literal{val=skip},PrevCtx,Type|Args0], + I0#b_set{args=Args} + end, + [I|Is]; + #b_set{} -> + [I0|bsm_skip_is(Is, Extracted)] + end; +bsm_skip_is([], _) -> []. + +bsm_extracted([{_,#b_blk{is=Is}}|Bs]) -> + case Is of + [#b_set{op=bs_extract,args=[Ctx]}|_] -> + [Ctx|bsm_extracted(Bs)]; + _ -> + bsm_extracted(Bs) + end; +bsm_extracted([]) -> []. + +%%% +%%% Short-cutting binary matching instructions. +%%% + +ssa_opt_bsm_shortcut(#st{ssa=Linear}=St) -> + Positions = bsm_positions(Linear, #{}), + case map_size(Positions) of + 0 -> + %% No binary matching instructions. + St; + _ -> + St#st{ssa=bsm_shortcut(Linear, Positions)} + end. + +bsm_positions([{L,#b_blk{is=Is,last=Last}}|Bs], PosMap0) -> + PosMap = bsm_positions_is(Is, PosMap0), + case {Is,Last} of + {[#b_set{op=bs_test_tail,dst=Bool,args=[Ctx,#b_literal{val=Bits0}]}], + #b_br{bool=Bool,fail=Fail}} -> + Bits = Bits0 + maps:get(Ctx, PosMap0), + bsm_positions(Bs, PosMap#{L=>{Bits,Fail}}); + {_,_} -> + bsm_positions(Bs, PosMap) + end; +bsm_positions([], PosMap) -> PosMap. + +bsm_positions_is([#b_set{op=bs_start_match,dst=New}|Is], PosMap0) -> + PosMap = PosMap0#{New=>0}, + bsm_positions_is(Is, PosMap); +bsm_positions_is([#b_set{op=bs_match,dst=New,args=Args}|Is], PosMap0) -> + [_,Old|_] = Args, + #{Old:=Bits0} = PosMap0, + Bits = bsm_update_bits(Args, Bits0), + PosMap = PosMap0#{New=>Bits}, + bsm_positions_is(Is, PosMap); +bsm_positions_is([_|Is], PosMap) -> + bsm_positions_is(Is, PosMap); +bsm_positions_is([], PosMap) -> PosMap. + +bsm_update_bits([#b_literal{val=string},_,#b_literal{val=String}], Bits) -> + Bits + bit_size(String); +bsm_update_bits([#b_literal{val=utf8}|_], Bits) -> + Bits + 8; +bsm_update_bits([#b_literal{val=utf16}|_], Bits) -> + Bits + 16; +bsm_update_bits([#b_literal{val=utf32}|_], Bits) -> + Bits + 32; +bsm_update_bits([_,_,_,#b_literal{val=Sz},#b_literal{val=U}], Bits) + when is_integer(Sz) -> + Bits + Sz*U; +bsm_update_bits(_, Bits) -> Bits. + +bsm_shortcut([{L,#b_blk{is=Is,last=Last0}=Blk}|Bs], PosMap) -> + case {Is,Last0} of + {[#b_set{op=bs_match,dst=New,args=[_,Old|_]}, + #b_set{op=succeeded,dst=Bool,args=[New]}], + #b_br{bool=Bool,fail=Fail}} -> + case PosMap of + #{Old:=Bits,Fail:={TailBits,NextFail}} when Bits > TailBits -> + Last = Last0#b_br{fail=NextFail}, + [{L,Blk#b_blk{last=Last}}|bsm_shortcut(Bs, PosMap)]; + #{} -> + [{L,Blk}|bsm_shortcut(Bs, PosMap)] + end; + {_,_} -> + [{L,Blk}|bsm_shortcut(Bs, PosMap)] + end; +bsm_shortcut([], _PosMap) -> []. + +%%% +%%% Miscellanous optimizations in execution order. +%%% + +ssa_opt_misc(#st{ssa=Linear}=St) -> + St#st{ssa=misc_opt(Linear, #{})}. + +misc_opt([{L,#b_blk{is=Is0,last=Last0}=Blk0}|Bs], Sub0) -> + {Is,Sub} = misc_opt_is(Is0, Sub0, []), + Last = sub(Last0, Sub), + Blk = Blk0#b_blk{is=Is,last=Last}, + [{L,Blk}|misc_opt(Bs, Sub)]; +misc_opt([], _) -> []. + +misc_opt_is([#b_set{op=phi}=I0|Is], Sub0, Acc) -> + #b_set{dst=Dst,args=Args} = I = sub(I0, Sub0), + case all_same(Args) of + true -> + %% Eliminate the phi node if there is just one source + %% value or if the values are identical. + [{Val,_}|_] = Args, + Sub = Sub0#{Dst=>Val}, + misc_opt_is(Is, Sub, Acc); + false -> + misc_opt_is(Is, Sub0, [I|Acc]) + end; +misc_opt_is([#b_set{}=I0|Is], Sub, Acc) -> + #b_set{op=Op,dst=Dst,args=Args} = I = sub(I0, Sub), + case make_literal(Op, Args) of + #b_literal{}=Literal -> + misc_opt_is(Is, Sub#{Dst=>Literal}, Acc); + error -> + misc_opt_is(Is, Sub, [I|Acc]) + end; +misc_opt_is([], Sub, Acc) -> + {reverse(Acc),Sub}. + +all_same([{H,_}|T]) -> + all(fun({E,_}) -> E =:= H end, T). + +make_literal(put_tuple, Args) -> + case make_literal_list(Args, []) of + error -> + error; + List -> + #b_literal{val=list_to_tuple(List)} + end; +make_literal(put_list, [#b_literal{val=H},#b_literal{val=T}]) -> + #b_literal{val=[H|T]}; +make_literal(_, _) -> error. + +make_literal_list([#b_literal{val=H}|T], Acc) -> + make_literal_list(T, [H|Acc]); +make_literal_list([_|_], _) -> + error; +make_literal_list([], Acc) -> + reverse(Acc). + +%%% +%%% Merge blocks. +%%% + +ssa_opt_merge_blocks(#st{ssa=Blocks}=St) -> + Preds = beam_ssa:predecessors(Blocks), + St#st{ssa=merge_blocks_1(beam_ssa:rpo(Blocks), Preds, Blocks)}. + +merge_blocks_1([L|Ls], Preds0, Blocks0) -> + case Preds0 of + #{L:=[P]} -> + #{P:=Blk0,L:=Blk1} = Blocks0, + case is_merge_allowed(L, Blk0, Blk1) of + true -> + #b_blk{is=Is0} = Blk0, + #b_blk{is=Is1} = Blk1, + Is = Is0 ++ Is1, + Blk = Blk1#b_blk{is=Is}, + Blocks1 = maps:remove(L, Blocks0), + Blocks2 = maps:put(P, Blk, Blocks1), + Successors = beam_ssa:successors(Blk), + Blocks = beam_ssa:update_phi_labels(Successors, L, P, Blocks2), + Preds = merge_update_preds(Successors, L, P, Preds0), + merge_blocks_1(Ls, Preds, Blocks); + false -> + merge_blocks_1(Ls, Preds0, Blocks0) + end; + #{} -> + merge_blocks_1(Ls, Preds0, Blocks0) + end; +merge_blocks_1([], _Preds, Blocks) -> Blocks. + +merge_update_preds([L|Ls], From, To, Preds0) -> + Ps = [rename_label(P, From, To) || P <- maps:get(L, Preds0)], + Preds = maps:put(L, Ps, Preds0), + merge_update_preds(Ls, From, To, Preds); +merge_update_preds([], _, _, Preds) -> Preds. + +rename_label(From, From, To) -> To; +rename_label(Lbl, _, _) -> Lbl. + +is_merge_allowed(_, _, #b_blk{is=[#b_set{op=peek_message}|_]}) -> + false; +is_merge_allowed(L, Blk0, #b_blk{}) -> + case beam_ssa:successors(Blk0) of + [L] -> true; + [_|_] -> false + end. + +%%% +%%% When a tuple is matched, the pattern matching compiler generates a +%%% get_tuple_element instruction for every tuple element that will +%%% ever be used in the rest of the function. That often forces the +%%% extracted tuple elements to be stored in Y registers until it's +%%% time to use them. It could also mean that there could be execution +%%% paths that will never use the extracted elements. +%%% +%%% This optimization will sink get_tuple_element instructions, that +%%% is, move them forward in the execution stream to the last possible +%%% block there they will still dominate all uses. That may reduce the +%%% size of stack frames, reduce register shuffling, and avoid +%%% extracting tuple elements on execution paths that never use the +%%% extracted values. +%%% + +ssa_opt_sink(#st{ssa=Blocks0}=St) -> + Linear = beam_ssa:linearize(Blocks0), + + %% Create a map with all variables that define get_tuple_element + %% instructions. The variable name map to the block it is defined in. + Defs = maps:from_list(def_blocks(Linear)), + + %% Now find all the blocks that use variables defined by get_tuple_element + %% instructions. + Used = used_blocks(Linear, Defs, []), + + %% Calculate dominators. + Dom0 = beam_ssa:dominators(Blocks0), + + %% It is not safe to move get_tuple_element instructions to blocks + %% that begin with certain instructions. It is also unsafe to move + %% the instructions into any part of a receive. To avoid such + %% unsafe moves, pretend that the unsuitable blocks are not + %% dominators. + Unsuitable = unsuitable(Linear, Blocks0), + Dom = case gb_sets:is_empty(Unsuitable) of + true -> + Dom0; + false -> + F = fun(_, DomBy) -> + [L || L <- DomBy, + not gb_sets:is_element(L, Unsuitable)] + end, + maps:map(F, Dom0) + end, + + %% Calculate new positions for get_tuple_element instructions. The new + %% position is a block that dominates all uses of the variable. + DefLoc = new_def_locations(Used, Defs, Dom), + + %% Now move all suitable get_tuple_element instructions to their + %% new blocks. + Blocks = foldl(fun({V,To}, A) -> + From = maps:get(V, Defs), + move_defs(V, From, To, A) + end, Blocks0, DefLoc), + St#st{ssa=Blocks}. + +def_blocks([{L,#b_blk{is=Is}}|Bs]) -> + def_blocks_is(Is, L, def_blocks(Bs)); +def_blocks([]) -> []. + +def_blocks_is([#b_set{op=get_tuple_element,dst=#b_var{name=Dst}}|Is], L, Acc) -> + def_blocks_is(Is, L, [{Dst,L}|Acc]); +def_blocks_is([_|Is], L, Acc) -> + def_blocks_is(Is, L, Acc); +def_blocks_is([], _, Acc) -> Acc. + +used_blocks([{L,Blk}|Bs], Def, Acc0) -> + Used = beam_ssa:used(Blk), + Acc = [{V,L} || V <- Used, maps:is_key(V, Def)] ++ Acc0, + used_blocks(Bs, Def, Acc); +used_blocks([], _Def, Acc) -> + rel2fam(Acc). + +%% unsuitable(Linear, Blocks) -> Unsuitable. +%% Return an ordset of block labels for the blocks that are not +%% suitable for sinking of get_tuple_element instructions. + +unsuitable(Linear, Blocks) -> + Predecessors = beam_ssa:predecessors(Blocks), + Unsuitable0 = unsuitable_1(Linear), + Unsuitable1 = unsuitable_recv(Linear, Blocks, Predecessors), + gb_sets:from_list(Unsuitable0 ++ Unsuitable1). + +unsuitable_1([{L,#b_blk{is=[#b_set{op=Op}|_]}}|Bs]) -> + Unsuitable = case Op of + bs_extract -> true; + bs_put -> true; + {float,_} -> true; + landingpad -> true; + peek_message -> true; + wait_timeout -> true; + _ -> false + end, + case Unsuitable of + true -> + [L|unsuitable_1(Bs)]; + false -> + unsuitable_1(Bs) + end; +unsuitable_1([{_,#b_blk{}}|Bs]) -> + unsuitable_1(Bs); +unsuitable_1([]) -> []. + +unsuitable_recv([{L,#b_blk{is=[#b_set{op=Op}|_]}}|Bs], Blocks, Predecessors) -> + Ls = case Op of + remove_message -> + unsuitable_loop(L, Blocks, Predecessors); + recv_next -> + unsuitable_loop(L, Blocks, Predecessors); + _ -> + [] + end, + Ls ++ unsuitable_recv(Bs, Blocks, Predecessors); +unsuitable_recv([_|Bs], Blocks, Predecessors) -> + unsuitable_recv(Bs, Blocks, Predecessors); +unsuitable_recv([], _, _) -> []. + +unsuitable_loop(L, Blocks, Predecessors) -> + unsuitable_loop(L, Blocks, Predecessors, []). + +unsuitable_loop(L, Blocks, Predecessors, Acc) -> + Ps = maps:get(L, Predecessors), + unsuitable_loop_1(Ps, Blocks, Predecessors, Acc). + +unsuitable_loop_1([P|Ps], Blocks, Predecessors, Acc0) -> + case maps:get(P, Blocks) of + #b_blk{is=[#b_set{op=peek_message}|_]} -> + unsuitable_loop_1(Ps, Blocks, Predecessors, Acc0); + #b_blk{} -> + case ordsets:is_element(P, Acc0) of + false -> + Acc1 = ordsets:add_element(P, Acc0), + Acc = unsuitable_loop(P, Blocks, Predecessors, Acc1), + unsuitable_loop_1(Ps, Blocks, Predecessors, Acc); + true -> + unsuitable_loop_1(Ps, Blocks, Predecessors, Acc0) + end + end; +unsuitable_loop_1([], _, _, Acc) -> Acc. + +%% new_def_locations([{Variable,[UsedInBlock]}|Vs], Defs, Dominators) -> +%% [{Variable,NewDefinitionBlock}] +%% Calculate new locations for get_tuple_element instructions. For each +%% variable, the new location is a block that dominates all uses of +%% variable and as near to the uses of as possible. If no such block +%% distinct from the block where the instruction currently is, the +%% variable will not be included in the result list. + +new_def_locations([{V,UsedIn}|Vs], Defs, Dom) -> + DefIn = maps:get(V, Defs), + case common_dom(UsedIn, DefIn, Dom) of + [] -> + new_def_locations(Vs, Defs, Dom); + [_|_]=BetterDef -> + L = most_dominated(BetterDef, Dom), + [{V,L}|new_def_locations(Vs, Defs, Dom)] + end; +new_def_locations([], _, _) -> []. + +common_dom([L|Ls], DefIn, Dom) -> + DomBy0 = maps:get(L, Dom), + DomBy = ordsets:subtract(DomBy0, maps:get(DefIn, Dom)), + common_dom_1(Ls, Dom, DomBy). + +common_dom_1(_, _, []) -> + []; +common_dom_1([L|Ls], Dom, [_|_]=DomBy0) -> + DomBy1 = maps:get(L, Dom), + DomBy = ordsets:intersection(DomBy0, DomBy1), + common_dom_1(Ls, Dom, DomBy); +common_dom_1([], _, DomBy) -> DomBy. + +most_dominated([L|Ls], Dom) -> + most_dominated(Ls, L, maps:get(L, Dom), Dom). + +most_dominated([L|Ls], L0, DomBy, Dom) -> + case member(L, DomBy) of + true -> + most_dominated(Ls, L0, DomBy, Dom); + false -> + most_dominated(Ls, L, maps:get(L, Dom), Dom) + end; +most_dominated([], L, _, _) -> L. + + +%% Move get_tuple_element instructions to their new locations. + +move_defs(V, From, To, Blocks) -> + #{From:=FromBlk0,To:=ToBlk0} = Blocks, + {Def,FromBlk} = remove_def(V, FromBlk0), + try insert_def(V, Def, ToBlk0) of + ToBlk -> + %%io:format("~p: ~p => ~p\n", [V,From,To]), + Blocks#{From:=FromBlk,To:=ToBlk} + catch + throw:not_possible -> + Blocks + end. + +remove_def(V, #b_blk{is=Is0}=Blk) -> + {Def,Is} = remove_def_is(Is0, V, []), + {Def,Blk#b_blk{is=Is}}. + +remove_def_is([#b_set{dst=#b_var{name=Dst}}=Def|Is], Dst, Acc) -> + {Def,reverse(Acc, Is)}; +remove_def_is([I|Is], Dst, Acc) -> + remove_def_is(Is, Dst, [I|Acc]). + +insert_def(V, Def, #b_blk{is=Is0}=Blk) -> + Is = insert_def_is(Is0, V, Def), + Blk#b_blk{is=Is}. + +insert_def_is([#b_set{op=phi}=I|Is], V, Def) -> + case member(V, beam_ssa:used(I)) of + true -> + throw(not_possible); + false -> + [I|insert_def_is(Is, V, Def)] + end; +insert_def_is([#b_set{op=Op}=I|Is]=Is0, V, Def) -> + Action0 = case Op of + call -> beyond; + 'catch_end' -> beyond; + set_tuple_element -> beyond; + timeout -> beyond; + _ -> here + end, + Action = case Is of + [#b_set{op=succeeded}|_] -> here; + _ -> Action0 + end, + case Action of + beyond -> + case member(V, beam_ssa:used(I)) of + true -> + %% The variable is used by this instruction. We must + %% place the definition before this instruction. + [Def|Is0]; + false -> + %% Place it beyond the current instruction. + [I|insert_def_is(Is, V, Def)] + end; + here -> + [Def|Is0] + end; +insert_def_is([], _V, Def) -> + [Def]. + + +%%% +%%% Common utilities. +%%% + +rel2fam(S0) -> + S1 = sofs:relation(S0), + S = sofs:rel2fam(S1), + sofs:to_external(S). + +sub(#b_set{op=phi,args=Args}=I, Sub) -> + I#b_set{args=[{sub_arg(A, Sub),P} || {A,P} <- Args]}; +sub(#b_set{args=Args}=I, Sub) -> + I#b_set{args=[sub_arg(A, Sub) || A <- Args]}; +sub(#b_br{bool=#b_var{}=Old}=Br, Sub) -> + New = sub_arg(Old, Sub), + Br#b_br{bool=New}; +sub(#b_switch{arg=#b_var{}=Old}=Sw, Sub) -> + New = sub_arg(Old, Sub), + Sw#b_switch{arg=New}; +sub(#b_ret{arg=#b_var{}=Old}=Ret, Sub) -> + New = sub_arg(Old, Sub), + Ret#b_ret{arg=New}; +sub(Last, _) -> Last. + +sub_arg(#b_remote{mod=Mod,name=Name}=Rem, Sub) -> + Rem#b_remote{mod=sub_arg(Mod, Sub),name=sub_arg(Name, Sub)}; +sub_arg(Old, Sub) -> + case Sub of + #{Old:=New} -> New; + #{} -> Old + end. diff --git a/lib/compiler/src/beam_ssa_pp.erl b/lib/compiler/src/beam_ssa_pp.erl new file mode 100644 index 0000000000..9daa2c2523 --- /dev/null +++ b/lib/compiler/src/beam_ssa_pp.erl @@ -0,0 +1,238 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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_ssa_pp). + +-export([format_function/1,format_instr/1,format_var/1]). + +-include("beam_ssa.hrl"). + +-spec format_function(beam_ssa:b_function()) -> iolist(). + +format_function(#b_function{anno=Anno0,args=Args, + bs=Blocks,cnt=Counter}) -> + #{func_info:={M,F,_}} = Anno0, + Anno = maps:without([func_info,location,live_intervals,registers], Anno0), + FuncAnno = case Anno0 of + #{live_intervals:=Intervals} -> + Anno0#{live_intervals:=maps:from_list(Intervals)}; + #{} -> + Anno0 + end, + ReachableBlocks = beam_ssa:rpo(Blocks), + All = maps:keys(Blocks), + Unreachable = ordsets:subtract(ordsets:from_list(All), + ordsets:from_list(ReachableBlocks)), + [case Anno0 of + #{location:={Filename,Line}} -> + io_lib:format("%% ~ts:~p\n", [Filename,Line]); + #{} -> + [] + end, + io_lib:format("%% Counter = ~p\n", [Counter]), + [format_anno(Key, Value) || + {Key,Value} <- lists:sort(maps:to_list(Anno))], + io_lib:format("function ~p:~p(~ts) {\n", [M,F,format_args(Args, FuncAnno)]), + [format_live_interval(Var, FuncAnno) || Var <- Args], + format_blocks(ReachableBlocks, Blocks, FuncAnno), + case Unreachable of + [] -> + []; + [_|_] -> + ["\n%% Unreachable blocks\n\n", + format_blocks(Unreachable, Blocks, FuncAnno)] + end, + + "}\n"]. + + +-spec format_instr(beam_ssa:b_set()) -> iolist(). + +format_instr(#b_set{}=I) -> + Cs = lists:flatten(format_instr(I#b_set{anno=#{}}, #{}, true)), + string:trim(Cs, leading); +format_instr(I0) -> + I = setelement(2, I0, #{}), + Cs = lists:flatten(format_terminator(I, #{})), + string:trim(Cs, both). + +-spec format_var(beam_ssa:b_var()) -> iolist(). + +format_var(V) -> + Cs = lists:flatten(format_var(V, #{})), + string:trim(Cs, leading). + +%%% +%%% Local functions. +%%% + +format_anno(Key, Map) when is_map(Map) -> + Sorted = lists:sort(maps:to_list(Map)), + [io_lib:format("%% ~s:\n", [Key]), + [io_lib:format("%% ~w => ~w\n", [K,V]) || {K,V} <- Sorted]]; +format_anno(Key, Value) -> + io_lib:format("%% ~s: ~p\n", [Key,Value]). + +format_blocks(Ls, Blocks, Anno) -> + PP = [format_block(L, Blocks, Anno) || L <- Ls], + lists:join($\n, PP). + +format_block(L, Blocks, FuncAnno) -> + #b_blk{anno=Anno,is=Is,last=Last} = maps:get(L, Blocks), + [case map_size(Anno) of + 0 -> []; + _ -> io_lib:format("%% ~p\n", [Anno]) + end, + io_lib:format("~p:", [L]), + format_instrs(Is, FuncAnno, true), + $\n, + format_terminator(Last, FuncAnno)]. + +format_instrs([I|Is], FuncAnno, First) -> + [$\n, + format_instr(I, FuncAnno, First), + format_instrs(Is, FuncAnno, false)]; +format_instrs([], _FuncAnno, _First) -> + []. + +format_instr(#b_set{anno=Anno,op=Op,dst=Dst,args=Args}, + FuncAnno, First) -> + AnnoStr = format_anno(Anno), + LiveIntervalStr = format_live_interval(Dst, FuncAnno), + [if + First -> + []; + AnnoStr =/= []; LiveIntervalStr =/= [] -> + $\n; + true -> + [] + end, + AnnoStr, + LiveIntervalStr, + io_lib:format(" ~s~ts = ~ts", [format_i_number(Anno), + format_var(Dst, FuncAnno), + format_op(Op)]), + case Args of + [] -> + []; + [_|_] -> + io_lib:format(" ~ts", [format_args(Args, FuncAnno)]) + end]. + +format_i_number(#{n:=N}) -> + io_lib:format("[~p] ", [N]); +format_i_number(#{}) -> []. + +format_terminator(#b_br{anno=A,bool=#b_literal{val=true},succ=Lbl}, _) -> + io_lib:format(" ~sbr label ~p\n", [format_i_number(A),Lbl]); +format_terminator(#b_br{anno=A,bool=#b_literal{val=false},fail=Lbl}, _) -> + io_lib:format(" ~sbr label ~p\n", [format_i_number(A),Lbl]); +format_terminator(#b_br{anno=A,bool=Bool,succ=Succ,fail=Fail}, FuncAnno) -> + io_lib:format(" ~sbr ~ts, label ~p, label ~p\n", + [format_i_number(A),format_arg(Bool, FuncAnno),Succ,Fail]); +format_terminator(#b_switch{anno=A,arg=Arg,fail=Fail,list=List}, FuncAnno) -> + io_lib:format(" ~sswitch ~ts, label ~p, ~ts\n", + [format_i_number(A),format_arg(Arg, FuncAnno),Fail, + format_list(List,FuncAnno)]); +format_terminator(#b_ret{anno=A,arg=Arg}, FuncAnno) -> + io_lib:format(" ~sret ~ts\n", [format_i_number(A),format_arg(Arg, FuncAnno)]). + +format_op({Prefix,Name}) -> + io_lib:format("~p:~p", [Prefix,Name]); +format_op(Name) -> + io_lib:format("~p", [Name]). + +format_register(#b_var{name=V}, #{registers:=Regs}) -> + {Tag,N} = maps:get(V, Regs), + io_lib:format("~p~p", [Tag,N]); +format_register(_, #{}) -> "". + +format_var(Var, FuncAnno) -> + VarString = format_var_1(Var), + case format_register(Var, FuncAnno) of + [] -> VarString; + [_|_]=Reg -> [Reg,$/,VarString] + end. + +format_var_1(#b_var{name={Name,Uniq}}) -> + if + is_atom(Name) -> + io_lib:format("~ts:~p", [Name,Uniq]); + is_integer(Name) -> + io_lib:format("_~p:~p", [Name,Uniq]) + end; +format_var_1(#b_var{name=Name}) when is_atom(Name) -> + atom_to_list(Name); +format_var_1(#b_var{name=Name}) when is_integer(Name) -> + "_"++integer_to_list(Name). + +format_args(Args, FuncAnno) -> + Ss = [format_arg(Arg, FuncAnno) || Arg <- Args], + lists:join(", ", Ss). + +format_arg(#b_var{}=Arg, FuncAnno) -> + format_var(Arg, FuncAnno); +format_arg(#b_literal{val=Val}, _FuncAnno) -> + io_lib:format("literal ~p", [Val]); +format_arg(#b_remote{mod=Mod,name=Name,arity=Arity}, FuncAnno) -> + io_lib:format("remote (~ts):(~ts)/~p", + [format_arg(Mod, FuncAnno),format_arg(Name, FuncAnno),Arity]); +format_arg(#b_local{name=Name,arity=Arity}, FuncAnno) -> + io_lib:format("local ~ts/~p", [format_arg(Name, FuncAnno),Arity]); +format_arg({Value,Label}, FuncAnno) when is_integer(Label) -> + io_lib:format("{ ~ts, ~p }", [format_arg(Value, FuncAnno),Label]); +format_arg(Other, _) -> + io_lib:format("*** ~p ***", [Other]). + +format_list(List, FuncAnno) -> + Ss = [io_lib:format("{ ~ts, ~ts }", [format_arg(Val, FuncAnno),format_label(L)]) || + {Val,L} <- List], + io_lib:format("[ ~ts ]", [lists:join(", ", Ss)]). + +format_label(L) -> + ["label ",integer_to_list(L)]. + +format_anno(#{n:=_}=Anno) -> + format_anno(maps:remove(n, Anno)); +format_anno(#{location:={File,Line}}=Anno0) -> + Anno = maps:remove(location, Anno0), + [io_lib:format(" %% ~ts:~p\n", [File,Line])|format_anno_1(Anno)]; +format_anno(Anno) -> + format_anno_1(Anno). + +format_anno_1(Anno) -> + case map_size(Anno) of + 0 -> + []; + _ -> + [io_lib:format(" %% Anno: ~p\n", [Anno])] + end. + +format_live_interval(#b_var{name=V}=Dst, #{live_intervals:=Intervals}) -> + case Intervals of + #{V:=Rs0} -> + Rs1 = [io_lib:format("~p..~p", [Start,End]) || + {Start,End} <- Rs0], + Rs = lists:join(" ", Rs1), + io_lib:format(" %% ~ts: ~s\n", [format_var_1(Dst),Rs]); + #{} -> + [] + end; +format_live_interval(_, _) -> []. + diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl new file mode 100644 index 0000000000..bbc3739eb5 --- /dev/null +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -0,0 +1,2437 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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% +%% +%% Purpose: Prepare for code generation, including register allocation. +%% +%% The output of this compiler pass is still in the SSA format, but +%% it has been annotated and transformed to help the code generator. +%% +%% * Some instructions are translated to other instructions closer to +%% the BEAM instructions. For example, the put_tuple instruction is +%% broken apart into the put_tuple_arity and put_tuple_elements +%% instructions. Similary, the binary matching instructions are +%% transformed from the optimization-friendly internal format to +%% instruction more similar to the actual BEAM instructions. +%% +%% * Blocks that will need an instruction for allocating a stack frame +%% are annotated with a {frame_size,Size} annotation. +%% +%% * 'copy' instructions are added for all variables that need +%% to be saved to the stack frame. Additional 'copy' instructions +%% can be added as an optimization to reuse y registers (see +%% the copy_retval sub pass). +%% +%% * Each function is annotated with a {register,RegisterMap} +%% annotation that maps each variable to a BEAM register. The linear +%% scan algorithm is used to allocate registers. +%% +%% There are four kind of registers. x, y, fr (floating point register), +%% and z. A variable will be allocated to a z register if it is only +%% used by the instruction following the instruction that defines the +%% the variable. The code generator will typically combine those +%% instructions to a test instruction. z registers are also used for +%% some instructions that don't have a return value. +%% +%% References: +%% +%% [1] H. Mössenböck and M. Pfeiffer. Linear scan register allocation +%% in the context of SSA form and register constraints. In Proceedings +%% of the International Conference on Compiler Construction, pages +%% 229–246. LNCS 2304, Springer-Verlag, 2002. +%% +%% [2] C. Wimmer and H. Mössenböck. Optimized interval splitting in a +%% linear scan register allocator. In Proceedings of the ACM/USENIX +%% International Conference on Virtual Execution Environments, pages +%% 132–141. ACM Press, 2005. +%% +%% [3] C. Wimmer and M. Franz. Linear Scan Register Allocation on SSA +%% Form. In Proceedings of the International Symposium on Code +%% Generation and Optimization, pages 170-179. ACM Press, 2010. +%% + +-module(beam_ssa_pre_codegen). + +-export([module/2]). + +-include("beam_ssa.hrl"). + +-import(lists, [all/2,any/2,append/1,duplicate/2, + foldl/3,last/1,map/2,member/2,partition/2, + reverse/1,reverse/2,sort/1,zip/2]). + +-spec module(beam_ssa:b_module(), [compile:option()]) -> + {'ok',beam_ssa:b_module()}. + +module(#b_module{body=Fs0}=Module, Opts) -> + ExtraAnnos = proplists:get_bool(dprecg, Opts), + Ps = passes(ExtraAnnos), + Fs = functions(Fs0, Ps), + {ok,Module#b_module{body=Fs}}. + +functions([F|Fs], Ps) -> + [function(F, Ps)|functions(Fs, Ps)]; +functions([], _Ps) -> []. + +-type b_var() :: beam_ssa:b_var(). +-type var_name() :: beam_ssa:var_name(). +-type instr_number() :: pos_integer(). +-type range() :: {instr_number(),instr_number()}. +-type reg_num() :: beam_asm:reg_num(). +-type xreg() :: {'x',reg_num()}. +-type yreg() :: {'y',reg_num()}. +-type ypool() :: {'y',beam_ssa:label()}. +-type reservation() :: 'fr' | {'prefer',xreg()} | 'x' | {'x',xreg()} | + ypool() | {yreg(),ypool()} | 'z'. +-type ssa_register() :: beam_ssa_codegen:ssa_register(). + +-define(TC(Body), tc(fun() -> Body end, ?FILE, ?LINE)). +-record(st, {ssa :: beam_ssa:block_map(), + args :: [b_var()], + cnt :: beam_ssa:label(), + frames=[] :: [beam_ssa:label()], + intervals=[] :: [{b_var(),[range()]}], + aliases=[] :: [{b_var(),b_var()}], + res=[] :: [{b_var(),reservation()}] | #{b_var():=reservation()}, + regs=#{} :: #{b_var():=ssa_register()}, + extra_annos=[] :: [{atom(),term()}] + }). +-define(PASS(N), {N,fun N/1}). + +passes(ExtraAnnos) -> + Ps = [?PASS(assert_no_critical_edges), + + %% Preliminaries. + ?PASS(fix_bs), + ?PASS(sanitize), + ?PASS(fix_tuples), + ?PASS(place_frames), + ?PASS(fix_receives), + + %% Find and reserve Y registers. + ?PASS(find_yregs), + ?PASS(reserve_yregs), + + %% Improve reuse of Y registers to potentially + %% reduce the size of the stack frame. + ?PASS(copy_retval), + ?PASS(opt_get_list), + + %% Calculate live intervals. + ?PASS(number_instructions), + ?PASS(live_intervals), + ?PASS(remove_unsuitable_aliases), + ?PASS(reserve_regs), + ?PASS(merge_intervals), + + %% If needed for a .precg file, save the live intervals + %% so they can be included in an annotation. + case ExtraAnnos of + false -> ignore; + true -> ?PASS(save_live_intervals) + end, + + %% Allocate registers. + ?PASS(linear_scan), + ?PASS(fix_aliased_regs), + ?PASS(frame_size), + ?PASS(turn_yregs)], + case ExtraAnnos of + false -> [P || P <- Ps, P =/= ignore]; + true -> Ps + end. + +function(#b_function{anno=Anno,args=Args,bs=Blocks0,cnt=Count0}=F0, Ps) -> + try + St0 = #st{ssa=Blocks0,args=Args,cnt=Count0}, + St = compile:run_sub_passes(Ps, St0), + #st{ssa=Blocks,cnt=Count,regs=Regs,extra_annos=ExtraAnnos} = St, + F1 = add_extra_annos(F0, ExtraAnnos), + F = beam_ssa:add_anno(registers, Regs, F1), + F#b_function{bs=Blocks,cnt=Count} + catch + Class:Error:Stack -> + #{func_info:={_,Name,Arity}} = Anno, + io:fwrite("Function: ~w/~w\n", [Name,Arity]), + erlang:raise(Class, Error, Stack) + end. + +save_live_intervals(#st{intervals=Intervals}=St) -> + St#st{extra_annos=[{live_intervals,Intervals}]}. + +fix_aliased_regs(#st{aliases=Aliases,regs=Regs}=St) -> + St#st{regs=fix_aliased_regs(Aliases, Regs)}. + +fix_aliased_regs([{Alias,V}|Aliases], Regs) -> + #{V:=Reg} = Regs, + fix_aliased_regs(Aliases, Regs#{Alias=>Reg}); +fix_aliased_regs([], Regs) -> Regs. + +%% Add extra annotations when a .precg listing file is being produced. +add_extra_annos(F, Annos) -> + foldl(fun({Name,Value}, Acc) -> + beam_ssa:add_anno(Name, Value, Acc) + end, F, Annos). + +%% assert_no_critical_edges(St0) -> St. +%% The code generator will not work if there are critial edges. +%% Abort if any critical edges are found. + +assert_no_critical_edges(#st{ssa=Blocks}=St) -> + F = fun assert_no_ces/3, + beam_ssa:fold_rpo(F, Blocks, Blocks), + St. + +assert_no_ces(_, #b_blk{is=[#b_set{op=phi,args=[_,_]=Phis}|_]}, Blocks) -> + %% This block has multiple predecessors. Make sure that none + %% of the precessors have more than one successor. + true = all(fun({_,P}) -> + length(beam_ssa:successors(P, Blocks)) =:= 1 + end, Phis), %Assertion. + Blocks; +assert_no_ces(_, _, Blocks) -> Blocks. + +%% fix_bs(St0) -> St. +%% Fix up the binary matching instructions: +%% +%% * Insert bs_save and bs_restore instructions where needed. +%% +%% * Combine bs_match and bs_extract instructions to bs_get +%% instructions. + +fix_bs(#st{ssa=Blocks,cnt=Count0}=St) -> + F = fun(#b_set{op=bs_start_match,dst=Dst}, A) -> + %% Mark the root of the match context list. + [{Dst,{context,Dst}}|A]; + (#b_set{op=bs_match,dst=Dst,args=[_,ParentCtx|_]}, A) -> + %% Link this match context the previous match context. + [{Dst,ParentCtx}|A]; + (_, A) -> + A + end, + case beam_ssa:fold_instrs_rpo(F, [0], [],Blocks) of + [] -> + %% No binary matching in this function. + St; + [_|_]=M -> + CtxChain = maps:from_list(M), + Linear0 = beam_ssa:linearize(Blocks), + + %% Insert bs_save / bs_restore instructions where needed. + {Linear1,Count} = bs_save_restore(Linear0, CtxChain, Count0), + + %% Rename instructions. + Linear = bs_instrs(Linear1, CtxChain, []), + + St#st{ssa=maps:from_list(Linear),cnt=Count} + end. + + +%% Insert bs_save and bs_restore instructions as needed. + +bs_save_restore(Linear0, CtxChain, Count0) -> + Rs0 = bs_restores(Linear0, CtxChain, #{}, #{}), + Rs = maps:values(Rs0), + S0 = sofs:relation(Rs, [{context,save_point}]), + S1 = sofs:relation_to_family(S0), + S = sofs:to_external(S1), + Slots = make_save_point_dict(S, []), + {Saves,Count1} = make_save_map(Rs, Slots, Count0, []), + {Restores,Count} = make_restore_map(maps:to_list(Rs0), Slots, Count1, []), + + %% Now insert all saves and restores. + {bs_insert(Linear0, Saves, Restores, Slots),Count}. + +make_save_map([{Ctx,Save}=Ps|T], Slots, Count, Acc) -> + Ignored = #b_var{name={'@ssa_ignored',Count}}, + case make_slot(Ps, Slots) of + #b_literal{val=start} -> + make_save_map(T, Slots, Count, Acc); + Slot -> + I = #b_set{op=bs_save,dst=Ignored,args=[Ctx,Slot]}, + make_save_map(T, Slots, Count+1, [{Save,I}|Acc]) + end; +make_save_map([], _, Count, Acc) -> + {maps:from_list(Acc),Count}. + +make_restore_map([{Bef,{Ctx,_}=Ps}|T], Slots, Count, Acc) -> + Ignored = #b_var{name={'@ssa_ignored',Count}}, + I = #b_set{op=bs_restore,dst=Ignored,args=[Ctx,make_slot(Ps, Slots)]}, + make_restore_map(T, Slots, Count+1, [{Bef,I}|Acc]); +make_restore_map([], _, Count, Acc) -> + {maps:from_list(Acc),Count}. + +make_slot({Same,Same}, _Slots) -> + #b_literal{val=start}; +make_slot({_,_}=Ps, Slots) -> + #b_literal{val=maps:get(Ps, Slots)}. + +make_save_point_dict([{Ctx,Pts}|T], Acc0) -> + Acc = make_save_point_dict_1(Pts, Ctx, 0, Acc0), + make_save_point_dict(T, Acc); +make_save_point_dict([], Acc) -> + maps:from_list(Acc). + +make_save_point_dict_1([Ctx|T], Ctx, I, Acc) -> + %% Special {atom,start} save point. Does not need a + %% bs_save instruction. + make_save_point_dict_1(T, Ctx, I, Acc); +make_save_point_dict_1([H|T], Ctx, I, Acc) -> + make_save_point_dict_1(T, Ctx, I+1, [{{Ctx,H},I}|Acc]); +make_save_point_dict_1([], Ctx, I, Acc) -> + [{Ctx,I}|Acc]. + +bs_restores([{L,#b_blk{is=Is,last=Last}}|Bs], CtxChain, D0, Rs0) -> + FPos = case D0 of + #{L:=Pos0} -> Pos0; + #{} -> #{} + end, + {SPos,Rs} = bs_restores_is(Is, CtxChain, FPos, Rs0), + D = bs_update_successors(Last, SPos, FPos, D0), + bs_restores(Bs, CtxChain, D, Rs); +bs_restores([], _, _, Rs) -> Rs. + +bs_update_successors(#b_br{succ=Succ,fail=Fail}, SPos, FPos, D) -> + join_positions([{Succ,SPos},{Fail,FPos}], D); +bs_update_successors(#b_switch{fail=Fail,list=List}, SPos, FPos, D) -> + SPos = FPos, %Assertion. + Update = [{L,SPos} || {_,L} <- List] ++ [{Fail,SPos}], + join_positions(Update, D); +bs_update_successors(#b_ret{}, _, _, D) -> D. + +join_positions([{L,MapPos0}|T], D) -> + case D of + #{L:=MapPos0} -> + %% Same map. + join_positions(T, D); + #{L:=MapPos1} -> + %% Different maps. + MapPos = join_positions_1(MapPos0, MapPos1), + join_positions(T, D#{L:=MapPos}); + #{} -> + join_positions(T, D#{L=>MapPos0}) + end; +join_positions([], D) -> D. + +join_positions_1(MapPos0, MapPos1) -> + MapPos2 = maps:map(fun(Start, Pos) -> + case MapPos0 of + #{Start:=Pos} -> Pos; + #{Start:=_} -> unknown; + #{} -> Pos + end + end, MapPos1), + maps:merge(MapPos0, MapPos2). + +bs_restores_is([#b_set{op=bs_start_match,dst=Start}|Is], + CtxChain, PosMap0, Rs) -> + PosMap = PosMap0#{Start=>Start}, + bs_restores_is(Is, CtxChain, PosMap, Rs); +bs_restores_is([#b_set{op=bs_match,dst=NewPos,args=Args}=I|Is], + CtxChain, PosMap0, Rs0) -> + Start = bs_subst_ctx(NewPos, CtxChain), + [_,FromPos|_] = Args, + case PosMap0 of + #{Start:=FromPos} -> + %% Same position, no restore needed. + PosMap = case bs_match_type(I) of + plain -> + %% Update position to new position. + PosMap0#{Start:=NewPos}; + _ -> + %% Position will not change (test_unit + %% instruction or no instruction at + %% all). + PosMap0#{Start:=FromPos} + end, + bs_restores_is(Is, CtxChain, PosMap, Rs0); + #{Start:=_} -> + %% Different positions, might need a restore instruction. + case bs_match_type(I) of + none -> + %% The tail test will be optimized away. + %% No need to do a restore. + PosMap = PosMap0#{Start:=FromPos}, + bs_restores_is(Is, CtxChain, PosMap, Rs0); + test_unit -> + %% This match instruction will be replaced by + %% a test_unit instruction. We will need a + %% restore. The new position will be the position + %% restored to (NOT NewPos). + PosMap = PosMap0#{Start:=FromPos}, + Rs = Rs0#{NewPos=>{Start,FromPos}}, + bs_restores_is(Is, CtxChain, PosMap, Rs); + plain -> + %% Match or skip. Position will be changed. + PosMap = PosMap0#{Start:=NewPos}, + Rs = Rs0#{NewPos=>{Start,FromPos}}, + bs_restores_is(Is, CtxChain, PosMap, Rs) + end + end; +bs_restores_is([#b_set{op=bs_extract,args=[FromPos|_]}|Is], + CtxChain, PosMap, Rs) -> + Start = bs_subst_ctx(FromPos, CtxChain), + #{Start:=FromPos} = PosMap, %Assertion. + bs_restores_is(Is, CtxChain, PosMap, Rs); +bs_restores_is([#b_set{op=Op,dst=Dst,args=Args}|Is], + CtxChain, PosMap0, Rs0) + when Op =:= bs_test_tail; + Op =:= call -> + {Rs,PosMap} = bs_restore_args(Args, PosMap0, CtxChain, Dst, Rs0), + bs_restores_is(Is, CtxChain, PosMap, Rs); +bs_restores_is([_|Is], CtxChain, PosMap, Rs) -> + bs_restores_is(Is, CtxChain, PosMap, Rs); +bs_restores_is([], _CtxChain, PosMap, Rs) -> + {PosMap,Rs}. + + +bs_match_type(#b_set{args=[#b_literal{val=skip},_Ctx, + #b_literal{val=binary},_Flags, + #b_literal{val=all},#b_literal{val=U}]}) -> + case U of + 1 -> none; + _ -> test_unit + end; +bs_match_type(_) -> + plain. + +bs_restore_args([#b_var{}=Arg|Args], PosMap0, CtxChain, Dst, Rs0) -> + Start = bs_subst_ctx(Arg, CtxChain), + case PosMap0 of + #{Start:=Arg} -> + %% Same position, no restore needed. + bs_restore_args(Args, PosMap0, CtxChain, Dst, Rs0); + #{Start:=_} -> + %% Different positions, need a restore instruction. + PosMap = PosMap0#{Start:=Arg}, + Rs = Rs0#{Dst=>{Start,Arg}}, + bs_restore_args(Args, PosMap, CtxChain, Dst, Rs); + #{} -> + %% Not a match context. + bs_restore_args(Args, PosMap0, CtxChain, Dst, Rs0) + end; +bs_restore_args([_|Args], PosMap, CtxChain, Dst, Rs) -> + bs_restore_args(Args, PosMap, CtxChain, Dst, Rs); +bs_restore_args([], PosMap, _CtxChain, _Dst, Rs) -> + {Rs,PosMap}. + +%% Insert all bs_save and bs_restore instructions. + +bs_insert([{L,#b_blk{is=Is0}=Blk}|Bs0], Saves, Restores, Slots) -> + Is = bs_insert_is_1(Is0, Restores, Slots), + Bs = bs_insert_saves(Is, Bs0, Saves), + [{L,Blk#b_blk{is=Is}}|bs_insert(Bs, Saves, Restores, Slots)]; +bs_insert([], _, _, _) -> []. + +bs_insert_is_1([#b_set{op=Op,dst=Dst}=I0|Is], Restores, Slots) -> + if + Op =:= bs_test_tail; + Op =:= bs_match; + Op =:= call -> + Rs = case Restores of + #{Dst:=R} -> [R]; + #{} -> [] + end, + Rs ++ [I0|bs_insert_is_1(Is, Restores, Slots)]; + Op =:= bs_start_match -> + NumSlots = case Slots of + #{Dst:=NumSlots0} -> NumSlots0; + #{} -> 0 + end, + I = beam_ssa:add_anno(num_slots, NumSlots, I0), + [I|bs_insert_is_1(Is, Restores, Slots)]; + true -> + [I0|bs_insert_is_1(Is, Restores, Slots)] + end; +bs_insert_is_1([], _, _) -> []. + +bs_insert_saves([#b_set{dst=Dst}|Is], Bs, Saves) -> + case Saves of + #{Dst:=S} -> + bs_insert_save(S, Bs); + #{} -> + bs_insert_saves(Is, Bs, Saves) + end; +bs_insert_saves([], Bs, _) -> Bs. + +bs_insert_save(Save, [{L,#b_blk{is=Is0}=Blk}|Bs]) -> + Is = case Is0 of + [#b_set{op=bs_extract}=Ex|Is1] -> + [Ex,Save|Is1]; + _ -> + [Save|Is0] + end, + [{L,Blk#b_blk{is=Is}}|Bs]. + +%% Translate bs_match instructions to bs_get, bs_match_string, +%% or bs_skip. Also rename match context variables to use the +%% variable assigned to by the start_match instruction. + +bs_instrs([{L,#b_blk{is=Is0}=Blk}|Bs], CtxChain, Acc0) -> + case bs_instrs_is(Is0, CtxChain, []) of + [#b_set{op=bs_extract,dst=Dst,args=[Ctx]}|Is] -> + %% Drop this instruction. Rewrite the corresponding + %% bs_match instruction in the previous block to + %% a bs_get instruction. + Acc = bs_combine(Dst, Ctx, Acc0), + bs_instrs(Bs, CtxChain, [{L,Blk#b_blk{is=Is}}|Acc]); + Is -> + bs_instrs(Bs, CtxChain, [{L,Blk#b_blk{is=Is}}|Acc0]) + end; +bs_instrs([], _, Acc) -> + reverse(Acc). + +bs_instrs_is([#b_set{op=Op,args=Args0}=I0|Is], CtxChain, Acc) -> + Args = [bs_subst_ctx(A, CtxChain) || A <- Args0], + I1 = I0#b_set{args=Args}, + I = case {Op,Args} of + {bs_match,[#b_literal{val=skip},Ctx,Type|As]} -> + I1#b_set{op=bs_skip,args=[Type,Ctx|As]}; + {bs_match,[#b_literal{val=string},Ctx|As]} -> + I1#b_set{op=bs_match_string,args=[Ctx|As]}; + {_,_} -> + I1 + end, + bs_instrs_is(Is, CtxChain, [I|Acc]); +bs_instrs_is([], _, Acc) -> + reverse(Acc). + +%% Combine a bs_match instruction with the destination register +%% taken from a bs_extract instruction. + +bs_combine(Dst, Ctx, [{L,#b_blk{is=Is0}=Blk}|Acc]) -> + [#b_set{}=Succeeded, + #b_set{op=bs_match,args=[Type,_|As]}=BsMatch|Is1] = reverse(Is0), + Is = reverse(Is1, [BsMatch#b_set{op=bs_get,dst=Dst,args=[Type,Ctx|As]}, + Succeeded#b_set{args=[Dst]}]), + [{L,Blk#b_blk{is=Is}}|Acc]. + +bs_subst_ctx(#b_var{}=Var, CtxChain) -> + case CtxChain of + #{Var:={context,Ctx}} -> + Ctx; + #{Var:=ParentCtx} -> + bs_subst_ctx(ParentCtx, CtxChain); + #{} -> + %% Not a match context variable. + Var + end; +bs_subst_ctx(Other, _CtxChain) -> + Other. + +%% sanitize(St0) -> St. +%% Remove constructs that can cause problems later: +%% +%% * Unreachable blocks may cause problems for determination of +%% dominators. +%% +%% * Some instructions (such as get_hd) don't accept literal +%% arguments. Evaluate the instructions and remove them. + +sanitize(#st{ssa=Blocks0,cnt=Count0}=St) -> + Ls = beam_ssa:rpo(Blocks0), + {Blocks,Count} = sanitize(Ls, Count0, Blocks0, #{}), + St#st{ssa=Blocks,cnt=Count}. + +sanitize([L|Ls], Count0, Blocks0, Values0) -> + #b_blk{is=Is0} = Blk0 = maps:get(L, Blocks0), + case sanitize_is(Is0, Count0, Values0, false, []) of + no_change -> + sanitize(Ls, Count0, Blocks0, Values0); + {Is,Count,Values} -> + Blk = Blk0#b_blk{is=Is}, + Blocks = Blocks0#{L:=Blk}, + sanitize(Ls, Count, Blocks, Values) + end; +sanitize([], Count, Blocks0, Values) -> + Blocks = if + map_size(Values) =:= 0 -> + Blocks0; + true -> + beam_ssa:rename_vars(Values, [0], Blocks0) + end, + + %% Unreachable blocks can cause problems for the dominator calculations. + Ls = beam_ssa:rpo(Blocks), + Reachable = gb_sets:from_list(Ls), + {case map_size(Blocks) =:= gb_sets:size(Reachable) of + true -> Blocks; + false -> remove_unreachable(Ls, Blocks, Reachable, []) + end,Count}. + +sanitize_is([#b_set{op=get_map_element, + args=[#b_literal{}=Map,Key]}=I0|Is], + Count0, Values, _Changed, Acc) -> + {MapVarName,Count} = new_var_name('@ssa_map', Count0), + MapVar = #b_var{name=MapVarName}, + I = I0#b_set{args=[MapVar,Key]}, + Copy = #b_set{op=copy,dst=MapVar,args=[Map]}, + sanitize_is(Is, Count, Values, true, [I,Copy|Acc]); +sanitize_is([#b_set{op=Op,dst=#b_var{name=Dst},args=Args0}=I0|Is0], + Count, Values, Changed, Acc) -> + Args = map(fun(#b_var{name=V}=Var) -> + case Values of + #{V:=New} -> New; + #{} -> Var + end; + (Lit) -> Lit + end, Args0), + case sanitize_instr(Op, Args, I0) of + {value,Value0} -> + Value = #b_literal{val=Value0}, + sanitize_is(Is0, Count, Values#{Dst=>Value}, true, Acc); + {ok,I} -> + sanitize_is(Is0, Count, Values, true, [I|Acc]); + ok -> + sanitize_is(Is0, Count, Values, Changed, [I0|Acc]) + end; +sanitize_is([], Count, Values, Changed, Acc) -> + case Changed of + true -> + {reverse(Acc),Count,Values}; + false -> + no_change + end. + +sanitize_instr({bif,Bif}, [#b_literal{val=Lit}], _I) -> + case erl_bifs:is_pure(erlang, Bif, 1) of + false -> + ok; + true -> + try + {value,erlang:Bif(Lit)} + catch + error:_ -> + ok + end + end; +sanitize_instr({bif,Bif}, [#b_literal{val=Lit1},#b_literal{val=Lit2}], _I) -> + true = erl_bifs:is_pure(erlang, Bif, 2), %Assertion. + try + {value,erlang:Bif(Lit1, Lit2)} + catch + error:_ -> + ok + end; +sanitize_instr(get_hd, [#b_literal{val=[Hd|_]}], _I) -> + {value,Hd}; +sanitize_instr(get_tl, [#b_literal{val=[_|Tl]}], _I) -> + {value,Tl}; +sanitize_instr(get_tuple_element, [#b_literal{val=T}, + #b_literal{val=I}], _I) + when I < tuple_size(T) -> + {value,element(I+1, T)}; +sanitize_instr(is_nonempty_list, [#b_literal{val=Lit}], _I) -> + {value,case Lit of + [_|_] -> true; + _ -> false + end}; +sanitize_instr(is_tagged_tuple, [#b_literal{val=Tuple}, + #b_literal{val=Arity}, + #b_literal{val=Tag}], _I) + when is_integer(Arity), is_atom(Tag) -> + if + tuple_size(Tuple) =:= Arity, element(1, Tuple) =:= Tag -> + {value,true}; + true -> + {value,false} + end; +sanitize_instr(bs_init, [#b_literal{val=new},#b_literal{val=Sz}|_], I0) -> + if + is_integer(Sz), Sz >= 0 -> ok; + true -> {ok,sanitize_badarg(I0)} + end; +sanitize_instr(bs_init, [#b_literal{val=append},_,#b_literal{val=Sz}|_], I0) -> + if + is_integer(Sz), Sz >= 0 -> ok; + true -> {ok,sanitize_badarg(I0)} + end; +sanitize_instr(succeeded, [#b_literal{}], _I) -> + {value,true}; +sanitize_instr(_, _, _) -> ok. + +sanitize_badarg(I) -> + Func = #b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=error},arity=1}, + I#b_set{op=call,args=[Func,#b_literal{val=badarg}]}. + +remove_unreachable([L|Ls], Blocks, Reachable, Acc) -> + #b_blk{is=Is0} = Blk0 = maps:get(L, Blocks), + case split_phis(Is0) of + {[_|_]=Phis,Rest} -> + Is = [prune_phi(Phi, Reachable) || Phi <- Phis] ++ Rest, + Blk = Blk0#b_blk{is=Is}, + remove_unreachable(Ls, Blocks, Reachable, [{L,Blk}|Acc]); + {[],_} -> + remove_unreachable(Ls, Blocks, Reachable, [{L,Blk0}|Acc]) + end; +remove_unreachable([], _Blocks, _, Acc) -> + maps:from_list(Acc). + +prune_phi(#b_set{args=Args0}=Phi, Reachable) -> + Args = [A || {_,Pred}=A <- Args0, + gb_sets:is_element(Pred, Reachable)], + Phi#b_set{args=Args}. + +%%% +%%% Fix tuples. +%%% + +%% fix_tuples(St0) -> St. +%% We must split tuple creation into two instruction to mirror the +%% the way tuples are created in BEAM. Each put_tuple instruction is +%% split into put_tuple_arity followed by put_tuple_elements. + +fix_tuples(#st{ssa=Blocks0,cnt=Count0}=St) -> + F = fun (#b_set{op=put_tuple,args=Args}=Put, C0) -> + Arity = #b_literal{val=length(Args)}, + {VarName,C} = new_var_name('@ssa_ignore', C0), + Ignore = #b_var{name=VarName}, + {[Put#b_set{op=put_tuple_arity,args=[Arity]}, + #b_set{dst=Ignore,op=put_tuple_elements,args=Args}],C}; + (I, C) -> {[I],C} + end, + {Blocks,Count} = beam_ssa:flatmapfold_instrs_rpo(F, [0], Count0, Blocks0), + St#st{ssa=Blocks,cnt=Count}. + +%%% +%%% Find out where frames should be placed. +%%% + +%% place_frames(St0) -> St. +%% Return a list of the labels for the blocks that need stack frame +%% allocation instructions. +%% +%% This function attempts to place stack frames as tight as possible +%% around the code, to avoid building stack frames for code paths +%% that don't need one. +%% +%% Stack frames are placed in blocks that dominate all of their +%% descendants. That guarantees that the deallocation instructions +%% cannot be reached from other execution paths that didn't set up +%% a stack frame or set up a stack frame with a different size. + +place_frames(#st{ssa=Blocks}=St) -> + Doms = beam_ssa:dominators(Blocks), + Ls = beam_ssa:rpo(Blocks), + Tried = gb_sets:empty(), + Frames0 = [], + {Frames,_} = place_frames_1(Ls, Blocks, Doms, Tried, Frames0), + St#st{frames=Frames}. + +place_frames_1([L|Ls], Blocks, Doms, Tried0, Frames0) -> + Blk = maps:get(L, Blocks), + case need_frame(Blk) of + true -> + %% This block needs a frame. Try to place it here. + {Frames,Tried} = do_place_frame(L, Blocks, Doms, Tried0, Frames0), + + %% Successfully placed. Try to place more frames in descendants + %% that are not dominated by this block. + place_frames_1(Ls, Blocks, Doms, Tried, Frames); + false -> + try + place_frames_1(Ls, Blocks, Doms, Tried0, Frames0) + catch + throw:{need_frame,For,Tried1}=Reason -> + %% An descendant block needs a stack frame. Try to + %% place it here. + case is_dominated_by(For, L, Doms) of + true -> + %% Try to place a frame here. + {Frames,Tried} = do_place_frame(L, Blocks, Doms, + Tried1, Frames0), + place_frames_1(Ls, Blocks, Doms, Tried, Frames); + false -> + %% Wrong place. This block does not dominate + %% the block that needs the frame. Pass it on + %% to our ancestors. + throw(Reason) + end + end + end; +place_frames_1([], _, _, Tried, Frames) -> + {Frames,Tried}. + +%% do_place_frame(Label, Blocks, Dominators, Tried0, Frames0) -> {Frames,Tried}. +%% Try to place a frame in this block. This function returns +%% successfully if it either succeds at placing a frame in this +%% block, if an ancestor that dominates this block has already placed +%% a frame, or if we have already tried to put a frame in this block. +%% +%% An {need_frame,Label,Tried} exception will be thrown if this block +%% block is not suitable for having a stack frame (i.e. it does not dominate +%% all of its descendants). The exception means that an ancestor will have to +%% place the frame needed by this block. + +do_place_frame(L, Blocks, Doms, Tried0, Frames) -> + case gb_sets:is_element(L, Tried0) of + true -> + %% We have already tried to put a frame in this block. + {Frames,Tried0}; + false -> + %% Try to place a frame in this block. + Tried = gb_sets:insert(L, Tried0), + case place_frame_here(L, Blocks, Doms, Frames) of + yes -> + %% We need a frame and it is safe to place it here. + {[L|Frames],Tried}; + no -> + %% An ancestor has a frame. Not needed. + {Frames,Tried}; + ancestor -> + %% This block does not dominate all of its + %% descendants. We must place the frame in + %% an ancestor. + throw({need_frame,L,Tried}) + end + end. + +%% place_frame_here(Label, Blocks, Doms, Frames) -> no|yes|ancestor. +%% Determine whether a frame should be placed in block Label. + +place_frame_here(L, Blocks, Doms, Frames) -> + B0 = any(fun(DomBy) -> + is_dominated_by(L, DomBy, Doms) + end, Frames), + case B0 of + true -> + %% This block is dominated by an ancestor block that + %% defines a frame. Not needed/allowed to put a frame + %% here. + no; + false -> + %% No frame in any ancestor. We need a frame. + %% Now check whether the frame can be placed here. + %% If this block dominates all of its descendants + %% and the predecessors of any phi nodes it can be + %% placed here. + Descendants = beam_ssa:rpo([L], Blocks), + PhiPredecessors = phi_predecessors(L, Blocks), + MustDominate = ordsets:from_list(PhiPredecessors ++ Descendants), + Dominates = all(fun(?BADARG_BLOCK) -> + %% This block defines no variables and calls + %% erlang:error(badarg). It does not matter + %% whether L dominates ?BADARG_BLOCK or not; + %% it is still safe to put the frame in L. + true; + (Bl) -> + is_dominated_by(Bl, L, Doms) + end, MustDominate), + + %% Also, this block must not be a loop header. + IsLoopHeader = is_loop_header(L, Blocks), + case Dominates andalso not IsLoopHeader of + true -> yes; + false -> ancestor + end + end. + +%% phi_predecessors(Label, Blocks) -> +%% Return all predecessors referenced in phi nodes. + +phi_predecessors(L, Blocks) -> + #b_blk{is=Is} = maps:get(L, Blocks), + [P || #b_set{op=phi,args=Args} <- Is, {_,P} <- Args]. + +%% is_dominated_by(Label, DominatedBy, Dominators) -> true|false. +%% Test whether block Label is dominated by block DominatedBy. + +is_dominated_by(L, DomBy, Doms) -> + DominatedBy = maps:get(L, Doms), + ordsets:is_element(DomBy, DominatedBy). + +%% need_frame(#b_blk{}) -> true|false. +%% Test whether any of the instructions in the block requires a stack frame. + +need_frame(#b_blk{is=Is,last=#b_ret{arg=Ret}}) -> + need_frame_1(Is, {return,Ret}); +need_frame(#b_blk{is=Is}) -> + need_frame_1(Is, body). + +need_frame_1([#b_set{op=make_fun,dst=#b_var{name=Fun}}|Is], {return,_}=Context) -> + %% Since make_fun clobbers X registers, a stack frame is needed if + %% any of the following instructions use any other variable than + %% the one holding the reference to the created fun. + need_frame_1(Is, Context) orelse + case beam_ssa:used(#b_blk{is=Is,last=#b_ret{arg=#b_var{name=Fun}}}) of + [Fun] -> false; + [_|_] -> true + end; +need_frame_1([#b_set{op=new_try_tag}|_], _) -> + true; +need_frame_1([#b_set{op=call,dst=Val}]=Is, {return,Ret}) -> + if + Val =:= Ret -> need_frame_1(Is, tail); + true -> need_frame_1(Is, body) + end; +need_frame_1([#b_set{op=call,args=[Func|_]}|Is], Context) -> + case Func of + #b_remote{mod=#b_literal{val=Mod}, + name=#b_literal{val=Name}, + arity=Arity} -> + case erl_bifs:is_exit_bif(Mod, Name, Arity) of + true -> + false; + false -> + Context =:= body orelse + Is =/= [] orelse + is_trap_bif(Mod, Name, Arity) + end; + #b_remote{} -> + %% This is an apply(), which always needs a frame. + true; + #b_var{} -> + %% A fun call always needs a frame. + true; + _ -> + Context =:= body orelse Is =/= [] + end; +need_frame_1([I|Is], Context) -> + beam_ssa:clobbers_xregs(I) orelse need_frame_1(Is, Context); +need_frame_1([], _) -> false. + +%% is_trap_bif(Mod, Name, Arity) -> true|false. +%% Test whether we need a stack frame for this BIF. + +is_trap_bif(erlang, '!', 2) -> true; +is_trap_bif(erlang, link, 1) -> true; +is_trap_bif(erlang, unlink, 1) -> true; +is_trap_bif(erlang, monitor_node, 2) -> true; +is_trap_bif(erlang, group_leader, 2) -> true; +is_trap_bif(erlang, exit, 2) -> true; +is_trap_bif(_, _, _) -> false. + +%%% +%%% Fix variables used in matching in receive. +%%% +%%% The loop_rec/2 instruction may return a reference to a +%%% message outside of any heap or heap fragment. If the message +%%% does not match, it is not allowed to store any reference to +%%% the message (or part of the message) on the stack. If we do, +%%% the message will be corrupted if there happens to be a GC. +%%% +%%% Here we make sure to introduce copies of variables that are +%%% matched out and subsequently used after the remove_message/0 +%%% instructions. That will make sure that only X registers are +%%% used during matching. +%%% +%%% Depending on where variables are defined and used, they must +%%% be handling in two different ways. +%%% +%%% Variables that are always defined in the receive (before branching +%%% out into the different clauses of the receive) and used after the +%%% receive, must be handled in the following way: Before each +%%% remove_message instruction, each such variable must be copied, and +%%% all variables must be consolidated using a phi node in the +%%% common exit block for the receive. +%%% +%%% Variables that are matched out and used in the same clause +%%% need copy instructions before the remove_message instruction +%%% in that clause. +%%% + +fix_receives(#st{ssa=Blocks0,cnt=Count0}=St) -> + {Blocks,Count} = fix_receives_1(maps:to_list(Blocks0), + Blocks0, Count0), + St#st{ssa=Blocks,cnt=Count}. + +fix_receives_1([{L,Blk}|Ls], Blocks0, Count0) -> + case Blk of + #b_blk{is=[#b_set{op=peek_message}|_]} -> + Rm = find_rm_blocks(L, Blocks0), + LoopExit = find_loop_exit(Rm, Blocks0), + Defs0 = beam_ssa:def([L], Blocks0), + CommonUsed = recv_common(Defs0, LoopExit, Blocks0), + {Blocks1,Count1} = recv_fix_common(CommonUsed, LoopExit, Rm, + Blocks0, Count0), + Defs = ordsets:subtract(Defs0, CommonUsed), + {Blocks,Count} = fix_receive(Rm, Defs, Blocks1, Count1), + fix_receives_1(Ls, Blocks, Count); + #b_blk{} -> + fix_receives_1(Ls, Blocks0, Count0) + end; +fix_receives_1([], Blocks, Count) -> + {Blocks,Count}. + +recv_common(_Defs, none, _Blocks) -> + %% There is no common exit block because receive is used + %% in the tail position of a function. + []; +recv_common(Defs, Exit, Blocks) -> + {ExitDefs,ExitUsed} = beam_ssa:def_used([Exit], Blocks), + Def = ordsets:subtract(Defs, ExitDefs), + ordsets:intersection(Def, ExitUsed). + +%% recv_fix_common([CommonVar], LoopExit, [RemoveMessageLabel], +%% Blocks0, Count0) -> {Blocks,Count}. +%% Handle variables alwys defined in a receive and used +%% in the exit block following the receive. + +recv_fix_common([Msg0|T], Exit, Rm, Blocks0, Count0) -> + {Msg1,Count1} = new_var_name('@recv', Count0), + Msg = #b_var{name=Msg1}, + Blocks1 = beam_ssa:rename_vars(#{Msg0=>Msg}, [Exit], Blocks0), + N = length(Rm), + {MsgVars0,Count} = new_var_names(duplicate(N, '@recv'), Count1), + MsgVars = [#b_var{name=V} || V <- MsgVars0], + PhiArgs = fix_exit_phi_args(MsgVars, Rm, Exit, Blocks1), + Phi = #b_set{op=phi,dst=Msg,args=PhiArgs}, + ExitBlk0 = maps:get(Exit, Blocks1), + ExitBlk = ExitBlk0#b_blk{is=[Phi|ExitBlk0#b_blk.is]}, + Blocks2 = Blocks1#{Exit:=ExitBlk}, + Blocks = recv_fix_common_1(MsgVars, Rm, Msg0, Blocks2), + recv_fix_common(T, Exit, Rm, Blocks, Count); +recv_fix_common([], _, _, Blocks, Count) -> + {Blocks,Count}. + +recv_fix_common_1([V|Vs], [Rm|Rms], Msg, Blocks0) -> + Ren = #{Msg=>V}, + Blocks1 = beam_ssa:rename_vars(Ren, [Rm], Blocks0), + #b_blk{is=Is0} = Blk0 = maps:get(Rm, Blocks1), + Copy = #b_set{op=copy,dst=V,args=[#b_var{name=Msg}]}, + Is = insert_after_phis(Is0, [Copy]), + Blk = Blk0#b_blk{is=Is}, + Blocks = Blocks1#{Rm:=Blk}, + recv_fix_common_1(Vs, Rms, Msg, Blocks); +recv_fix_common_1([], [], _Msg, Blocks) -> Blocks. + +fix_exit_phi_args([V|Vs], [Rm|Rms], Exit, Blocks) -> + Path = beam_ssa:rpo([Rm], Blocks), + Pred = exit_predecessor(Path, Exit), + [{V,Pred}|fix_exit_phi_args(Vs, Rms, Exit, Blocks)]; +fix_exit_phi_args([], [], _, _) -> []. + +exit_predecessor([Pred,Exit|_], Exit) -> + Pred; +exit_predecessor([_|Bs], Exit) -> + exit_predecessor(Bs, Exit). + +%% fix_receive([Label], Defs, Blocks0, Count0) -> {Blocks,Count}. +%% Add a copy instruction for all variables that are matched out and +%% later used within a clause of the receive. + +fix_receive([L|Ls], Defs, Blocks0, Count0) -> + {RmDefs,Used0} = beam_ssa:def_used([L], Blocks0), + Def = ordsets:subtract(Defs, RmDefs), + Used = ordsets:intersection(Def, Used0), + {NewVs,Count} = new_var_names(Used, Count0), + NewVars = [#b_var{name=V} || V <- NewVs], + Ren = zip(Used, NewVars), + Blocks1 = beam_ssa:rename_vars(Ren, [L], Blocks0), + #b_blk{is=Is0} = Blk1 = maps:get(L, Blocks1), + CopyIs = [#b_set{op=copy,dst=New,args=[#b_var{name=Old}]} || + {Old,New} <- Ren], + Is = insert_after_phis(Is0, CopyIs), + Blk = Blk1#b_blk{is=Is}, + Blocks = maps:put(L, Blk, Blocks1), + fix_receive(Ls, Defs, Blocks, Count); +fix_receive([], _Defs, Blocks, Count) -> + {Blocks,Count}. + +%% find_loop_exit([Label], Blocks) -> Label | none. +%% Find the block to which control is transferred when the +%% the receive loop is exited. + +find_loop_exit([L1,L2|_Ls], Blocks) -> + Path1 = beam_ssa:rpo([L1], Blocks), + Path2 = beam_ssa:rpo([L2], Blocks), + find_loop_exit_1(reverse(Path1), reverse(Path2), none); +find_loop_exit(_, _) -> none. + +find_loop_exit_1([H|T1], [H|T2], _) -> + find_loop_exit_1(T1, T2, H); +find_loop_exit_1(_, _, Exit) -> Exit. + +%% find_rm_blocks(StartLabel, Blocks) -> [Label]. +%% Find all blocks that start with remove_message within the receive +%% loop whose peek_message label is StartLabel. + +find_rm_blocks(L, Blocks) -> + Seen = gb_sets:singleton(L), + Blk = maps:get(L, Blocks), + Succ = beam_ssa:successors(Blk), + find_rm_blocks_1(Succ, Seen, Blocks). + +find_rm_blocks_1([L|Ls], Seen0, Blocks) -> + case gb_sets:is_member(L, Seen0) of + true -> + find_rm_blocks_1(Ls, Seen0, Blocks); + false -> + Seen = gb_sets:insert(L, Seen0), + Blk = maps:get(L, Blocks), + case find_rm_act(Blk#b_blk.is) of + prune -> + %% Looping back. Don't look at any successors. + find_rm_blocks_1(Ls, Seen, Blocks); + continue -> + %% Neutral block. Do nothing here, but look at + %% all successors. + Succ = beam_ssa:successors(Blk), + find_rm_blocks_1(Succ++Ls, Seen, Blocks); + found -> + %% Found remove_message instruction. + [L|find_rm_blocks_1(Ls, Seen, Blocks)] + end + end; +find_rm_blocks_1([], _, _) -> []. + +find_rm_act([#b_set{op=Op}|Is]) -> + case Op of + remove_message -> found; + peek_message -> prune; + recv_next -> prune; + wait_timeout -> prune; + wait -> prune; + _ -> find_rm_act(Is) + end; +find_rm_act([]) -> + continue. + +%%% +%%% Find out which variables need to be stored in Y registers. +%%% + +-record(dk, {d :: ordsets:ordset(var_name()), + k :: ordsets:ordset(var_name()) + }). + +%% find_yregs(St0) -> St. +%% Find all variables that must be stored in Y registers. Annotate +%% the blocks that allocate frames with the set of Y registers +%% used within that stack frame. +%% +%% Basically, we following all execution paths starting from a block +%% that allocates a frame, keeping track of of all defined registers +%% and all registers killed by an instruction that clobbers X +%% registers. For every use of a variable, we check if if it is in +%% the set of killed variables; if it is, it must be stored in an Y +%% register. + +find_yregs(#st{frames=[]}=St) -> + St; +find_yregs(#st{frames=[_|_]=Frames,args=Args,ssa=Blocks0}=St) -> + FrameDefs = find_defs(Frames, Blocks0, [V || #b_var{name=V} <- Args]), + Blocks = find_yregs_1(FrameDefs, Blocks0), + St#st{ssa=Blocks}. + +find_yregs_1([{F,Defs}|Fs], Blocks0) -> + DK = #dk{d=Defs,k=[]}, + D0 = #{F=>DK}, + Ls = beam_ssa:rpo([F], Blocks0), + Yregs0 = [], + Yregs = find_yregs_2(Ls, Blocks0, D0, Yregs0), + Blk0 = maps:get(F, Blocks0), + Blk = beam_ssa:add_anno(yregs, Yregs, Blk0), + Blocks = Blocks0#{F:=Blk}, + find_yregs_1(Fs, Blocks); +find_yregs_1([], Blocks) -> Blocks. + +find_yregs_2([L|Ls], Blocks0, D0, Yregs0) -> + Blk0 = maps:get(L, Blocks0), + #b_blk{is=Is,last=Last} = Blk0, + Ys0 = maps:get(L, D0), + {Yregs1,Ys} = find_yregs_is(Is, Ys0, Yregs0), + Yregs = find_yregs_terminator(Last, Ys, Yregs1), + Successors = beam_ssa:successors(Blk0), + D = find_update_succ(Successors, Ys, D0), + find_yregs_2(Ls, Blocks0, D, Yregs); +find_yregs_2([], _Blocks, _D, Yregs) -> Yregs. + +find_defs(Frames, Blocks, Defs) -> + Seen = gb_sets:empty(), + FramesSet = gb_sets:from_list(Frames), + {FrameDefs,_} = find_defs_1([0], Blocks, FramesSet, Seen, Defs, []), + FrameDefs. + +find_defs_1([L|Ls], Blocks, Frames, Seen0, Defs0, Acc0) -> + case gb_sets:is_member(L, Frames) of + true -> + OrderedDefs = ordsets:from_list(Defs0), + find_defs_1(Ls, Blocks, Frames, Seen0, Defs0, + [{L,OrderedDefs}|Acc0]); + false -> + case gb_sets:is_member(L, Seen0) of + true -> + find_defs_1(Ls, Blocks, Frames, Seen0, Defs0, Acc0); + false -> + Seen1 = gb_sets:insert(L, Seen0), + {Acc,Seen} = find_defs_1(Ls, Blocks, Frames, Seen1, Defs0, Acc0), + #b_blk{is=Is} = Blk = maps:get(L, Blocks), + Defs = find_defs_is(Is, Defs0), + Successors = beam_ssa:successors(Blk), + find_defs_1(Successors, Blocks, Frames, Seen, Defs, Acc) + end + end; +find_defs_1([], _, _, Seen, _, Acc) -> + {Acc,Seen}. + +find_defs_is([#b_set{dst=#b_var{name=Dst}}|Is], Acc) -> + find_defs_is(Is, [Dst|Acc]); +find_defs_is([], Acc) -> Acc. + +find_update_succ([S|Ss], #dk{d=Defs0,k=Killed0}=DK0, D0) -> + case D0 of + #{S:=#dk{d=Defs1,k=Killed1}} -> + Defs = ordsets:intersection(Defs0, Defs1), + Killed = ordsets:union(Killed0, Killed1), + DK = #dk{d=Defs,k=Killed}, + D = maps:put(S, DK, D0), + find_update_succ(Ss, DK0, D); + #{} -> + D = maps:put(S, DK0, D0), + find_update_succ(Ss, DK0, D) + end; +find_update_succ([], _, D) -> D. + +find_yregs_is([#b_set{dst=#b_var{name=Dst}}=I|Is], #dk{d=Defs0,k=Killed0}=Ys, Yregs0) -> + Used = beam_ssa:used(I), + Yregs1 = ordsets:intersection(Used, Killed0), + Yregs = ordsets:union(Yregs0, Yregs1), + case beam_ssa:clobbers_xregs(I) of + false -> + Defs = ordsets:add_element(Dst, Defs0), + find_yregs_is(Is, Ys#dk{d=Defs}, Yregs); + true -> + Killed = ordsets:union(Defs0, Killed0), + Defs = [Dst], + find_yregs_is(Is, Ys#dk{d=Defs,k=Killed}, Yregs) + end; +find_yregs_is([], Ys, Yregs) -> {Yregs,Ys}. + +find_yregs_terminator(Terminator, #dk{k=Killed}, Yregs0) -> + Used = beam_ssa:used(Terminator), + Yregs = ordsets:intersection(Used, Killed), + ordsets:union(Yregs0, Yregs). + +%%% +%%% Try to reduce the size of the stack frame, by adding an explicit +%%% 'copy' instructions for return values from 'call' and 'make_fun' that +%%% need to be saved in Y registers. Here is an example to show +%%% how that's useful. First, here is the Erlang code: +%%% +%%% f(Pid) -> +%%% Res = foo(42), +%%% _ = node(Pid), +%%% bar(), +%%% Res. +%%% +%%% Compiled to SSA format, the main part of the code looks like this: +%%% +%%% 0: +%%% Res = call local literal foo/1, literal 42 +%%% _1 = bif:node Pid +%%% @ssa_bool = succeeded _1 +%%% br @ssa_bool, label 3, label 1 +%%% 3: +%%% @ssa_ignored = call local literal bar/0 +%%% ret Res +%%% +%%% It can be seen that the variables Pid and Res must be saved in Y +%%% registers in order to survive the function calls. A previous sub +%%% pass has inserted a 'copy' instruction to save the value of the +%%% variable Pid: +%%% +%%% 0: +%%% Pid:4 = copy Pid +%%% Res = call local literal foo/1, literal 42 +%%% _1 = bif:node Pid:4 +%%% @ssa_bool = succeeded _1 +%%% br @ssa_bool, label 3, label 1 +%%% +%%% 3: +%%% @ssa_ignored = call local literal bar/0 +%%% ret Res +%%% +%%% The Res and Pid:4 variables must be assigned to different Y registers +%%% because they are live at the same time. copy_retval() inserts a +%%% 'copy' instruction to copy Res to a new variable: +%%% +%%% 0: +%%% Pid:4 = copy Pid +%%% Res:6 = call local literal foo/1, literal 42 +%%% _1 = bif:node Pid:4 +%%% @ssa_bool = succeeded _1 +%%% br @ssa_bool, label 3, label 1 +%%% +%%% 3: +%%% Res = copy Res:6 +%%% @ssa_ignored = call local literal bar/0 +%%% ret Res +%%% +%%% The new variable Res:6 is used to capture the return value from the call. +%%% The variables Pid:4 and Res are no longer live at the same time, so they +%%% can be assigned to the same Y register. +%%% + +copy_retval(#st{frames=Frames,ssa=Blocks0,cnt=Count0}=St) -> + {Blocks,Count} = copy_retval_1(Frames, Blocks0, Count0), + St#st{ssa=Blocks,cnt=Count}. + +copy_retval_1([F|Fs], Blocks0, Count0) -> + #b_blk{anno=#{yregs:=Yregs0},is=Is} = maps:get(F, Blocks0), + Yregs1 = gb_sets:from_list(Yregs0), + Yregs = collect_yregs(Is, Yregs1), + Ls = beam_ssa:rpo([F], Blocks0), + {Blocks,Count} = copy_retval_2(Ls, Yregs, none, Blocks0, Count0), + copy_retval_1(Fs, Blocks, Count); +copy_retval_1([], Blocks, Count) -> + {Blocks,Count}. + +collect_yregs([#b_set{op=copy,dst=#b_var{name=Y},args=[#b_var{name=X}]}|Is], + Yregs0) -> + true = gb_sets:is_member(X, Yregs0), %Assertion. + Yregs = gb_sets:insert(Y, gb_sets:delete(X, Yregs0)), + collect_yregs(Is, Yregs); +collect_yregs([#b_set{}|Is], Yregs) -> + collect_yregs(Is, Yregs); +collect_yregs([], Yregs) -> Yregs. + +copy_retval_2([L|Ls], Yregs, Copy0, Blocks0, Count0) -> + #b_blk{is=Is0,last=Last} = Blk = maps:get(L, Blocks0), + RC = case {Last,Ls} of + {#b_br{succ=Succ,fail=?BADARG_BLOCK},[Succ|_]} -> + true; + {_,_} -> + false + end, + case copy_retval_is(Is0, RC, Yregs, Copy0, Count0, []) of + {Is,Count} -> + case Copy0 =:= none andalso Count0 =:= Count of + true -> + copy_retval_2(Ls, Yregs, none, Blocks0, Count0); + false -> + Blocks = Blocks0#{L=>Blk#b_blk{is=Is}}, + copy_retval_2(Ls, Yregs, none, Blocks, Count) + end; + {Is,Count,Copy} -> + Blocks = Blocks0#{L=>Blk#b_blk{is=Is}}, + copy_retval_2(Ls, Yregs, Copy, Blocks, Count) + end; +copy_retval_2([], _Yregs, none, Blocks, Count) -> + {Blocks,Count}. + +copy_retval_is([#b_set{op=put_tuple_elements,args=Args0}=I0], false, _Yregs, + Copy, Count, Acc) -> + I = I0#b_set{args=copy_sub_args(Args0, Copy)}, + {reverse(Acc, [I|acc_copy([], Copy)]),Count}; +copy_retval_is([#b_set{}]=Is, false, _Yregs, Copy, Count, Acc) -> + {reverse(Acc, acc_copy(Is, Copy)),Count}; +copy_retval_is([#b_set{},#b_set{op=succeeded}]=Is, false, _Yregs, Copy, Count, Acc) -> + {reverse(Acc, acc_copy(Is, Copy)),Count}; +copy_retval_is([#b_set{op=Op,dst=#b_var{name=RetVal}=Dst}=I0|Is], RC, Yregs, + Copy0, Count0, Acc0) when Op =:= call; Op =:= make_fun -> + {I1,Count1,Acc} = place_retval_copy(I0, Yregs, Copy0, Count0, Acc0), + case gb_sets:is_member(RetVal, Yregs) of + true -> + {NewVarName,Count} = new_var_name(RetVal, Count1), + NewVar = #b_var{name=NewVarName}, + Copy = #b_set{op=copy,dst=Dst,args=[NewVar]}, + I = I1#b_set{dst=NewVar}, + copy_retval_is(Is, RC, Yregs, Copy, Count, [I|Acc]); + false -> + copy_retval_is(Is, RC, Yregs, none, Count1, [I1|Acc]) + end; +copy_retval_is([#b_set{args=Args0}=I0|Is], RC, Yregs, Copy, Count, Acc) -> + I = I0#b_set{args=copy_sub_args(Args0, Copy)}, + case beam_ssa:clobbers_xregs(I) of + true -> + copy_retval_is(Is, RC, Yregs, none, Count, [I|acc_copy(Acc, Copy)]); + false -> + copy_retval_is(Is, RC, Yregs, Copy, Count, [I|Acc]) + end; +copy_retval_is([], RC, _, Copy, Count, Acc) -> + case {Copy,RC} of + {none,_} -> + {reverse(Acc),Count}; + {#b_set{},true} -> + {reverse(Acc),Count,Copy}; + {#b_set{},false} -> + {reverse(Acc, [Copy]),Count} + end. + +%% +%% Consider this code: +%% +%% Var = ... +%% ... +%% A1 = call foo/0 +%% A = copy A1 +%% B = call bar/1, Var +%% +%% If the Var variable is no longer used after this code, its Y register +%% can't be reused for A. To allow the Y register to be reused +%% we will need to insert 'copy' instructions for arguments that are +%% in Y registers: +%% +%% Var = ... +%% ... +%% A1 = call foo/0 +%% Var1 = copy Var +%% A = copy A1 +%% B = call bar/1, Var1 +%% + +place_retval_copy(I, _Yregs, none, Count, Acc) -> + {I,Count,Acc}; +place_retval_copy(#b_set{args=[F|Args0]}=I, Yregs, Copy, Count0, Acc0) -> + #b_set{dst=#b_var{name=Avoid}} = Copy, + {Args,Acc1,Count} = copy_func_args(Args0, Yregs, Avoid, Acc0, [], Count0), + Acc = [Copy|Acc1], + {I#b_set{args=[F|Args]},Count,Acc}. + +copy_func_args([#b_var{name=V}=A|As], Yregs, Avoid, CopyAcc, Acc, Count0) -> + case gb_sets:is_member(V, Yregs) of + true when V =/= Avoid -> + {NewVarName,Count} = new_var_name(V, Count0), + NewVar = #b_var{name=NewVarName}, + Copy = #b_set{op=copy,dst=NewVar,args=[A]}, + copy_func_args(As, Yregs, Avoid, [Copy|CopyAcc], [NewVar|Acc], Count); + _ -> + copy_func_args(As, Yregs, Avoid, CopyAcc, [A|Acc], Count0) + end; +copy_func_args([A|As], Yregs, Avoid, CopyAcc, Acc, Count) -> + copy_func_args(As, Yregs, Avoid, CopyAcc, [A|Acc], Count); +copy_func_args([], _Yregs, _Avoid, CopyAcc, Acc, Count) -> + {reverse(Acc),CopyAcc,Count}. + +acc_copy(Acc, none) -> Acc; +acc_copy(Acc, #b_set{}=Copy) -> [Copy|Acc]. + +copy_sub_args(Args, none) -> + Args; +copy_sub_args(Args, #b_set{dst=Dst,args=[Src]}) -> + [sub_arg(A, Dst, Src) || A <- Args]. + +sub_arg(Old, Old, New) -> New; +sub_arg(Old, _, _) -> Old. + +%%% +%%% Consider: +%%% +%%% x1/Hd = get_hd x0/Cons +%%% y0/Tl = get_tl x0/Cons +%%% +%%% Register x0 can't be reused for Hd. If Hd needs to be in x0, +%%% a 'move' instruction must be inserted. +%%% +%%% If we swap get_hd and get_tl when Tl is in a Y register, +%%% x0 can be used for Hd if Cons is not used again: +%%% +%%% y0/Tl = get_tl x0/Cons +%%% x0/Hd = get_hd x0/Cons +%%% + +opt_get_list(#st{ssa=Blocks,res=Res}=St) -> + ResMap = maps:from_list(Res), + Ls = beam_ssa:rpo(Blocks), + St#st{ssa=opt_get_list_1(Ls, ResMap, Blocks)}. + +opt_get_list_1([L|Ls], Res, Blocks0) -> + #b_blk{is=Is0} = Blk = maps:get(L, Blocks0), + case opt_get_list_is(Is0, Res, [], false) of + no -> + opt_get_list_1(Ls, Res, Blocks0); + {yes,Is} -> + Blocks = Blocks0#{L:=Blk#b_blk{is=Is}}, + opt_get_list_1(Ls, Res, Blocks) + end; +opt_get_list_1([], _, Blocks) -> Blocks. + +opt_get_list_is([#b_set{op=get_hd,dst=#b_var{name=Hd}, + args=[Cons]}=GetHd, + #b_set{op=get_tl,dst=#b_var{name=Tl}, + args=[Cons]}=GetTl|Is], + Res, Acc, Changed) -> + %% Note that when this pass is run, only Y registers have + %% reservations. The absence of an entry for a variable therefore + %% means that the variable will be in an X register. + case Res of + #{Hd:={y,_}} -> + %% Hd will be in a Y register. Don't swap. + opt_get_list_is([GetTl|Is], Res, [GetHd|Acc], Changed); + #{Tl:={y,_}} -> + %% Tl will be in a Y register. Swap. + opt_get_list_is([GetHd|Is], Res, [GetTl|Acc], true); + #{} -> + %% Both are in X registers. Nothing to do. + opt_get_list_is([GetTl|Is], Res, [GetHd|Acc], Changed) + end; +opt_get_list_is([I|Is], Res, Acc, Changed) -> + opt_get_list_is(Is, Res, [I|Acc], Changed); +opt_get_list_is([], _Res, Acc, Changed) -> + case Changed of + true -> + {yes,reverse(Acc)}; + false -> + no + end. + +%%% +%%% Number instructions in the order they are executed. +%%% + +%% number_instructions(St0) -> St. +%% Number instructions in the order they are executed. Use a step +%% size of 2. Don't number phi instructions. All phi variables in +%% a block will be live one unit before the first non-phi instruction +%% in the block. + +number_instructions(#st{ssa=Blocks0}=St) -> + Ls = beam_ssa:rpo(Blocks0), + St#st{ssa=number_is_1(Ls, 1, Blocks0)}. + +number_is_1([L|Ls], N0, Blocks0) -> + #b_blk{is=Is0,last=Last0} = Bl0 = maps:get(L, Blocks0), + {Is,N1} = number_is_2(Is0, N0, []), + Last = beam_ssa:add_anno(n, N1, Last0), + N = N1 + 2, + Bl = Bl0#b_blk{is=Is,last=Last}, + Blocks = maps:put(L, Bl, Blocks0), + number_is_1(Ls, N, Blocks); +number_is_1([], _, Blocks) -> Blocks. + +number_is_2([#b_set{op=phi}=I|Is], N, Acc) -> + number_is_2(Is, N, [I|Acc]); +number_is_2([I0|Is], N, Acc) -> + I = beam_ssa:add_anno(n, N, I0), + number_is_2(Is, N+2, [I|Acc]); +number_is_2([], N, Acc) -> + {reverse(Acc),N}. + +%%% +%%% Calculate live intervals. +%%% + +live_intervals(#st{args=Args,ssa=Blocks}=St) -> + Vars0 = [{V,{0,1}} || #b_var{name=V} <- Args], + F = fun(L, _, A) -> live_interval_blk(L, Blocks, A) end, + LiveMap0 = #{}, + Acc0 = {[],[],LiveMap0}, + {Vars,Aliases,_} = beam_ssa:fold_po(F, Acc0, Blocks), + Intervals = merge_ranges(rel2fam(Vars0++Vars)), + St#st{intervals=Intervals,aliases=Aliases}. + +merge_ranges([{V,Rs}|T]) -> + [{V,merge_ranges_1(Rs)}|merge_ranges(T)]; +merge_ranges([]) -> []. + +merge_ranges_1([{A,N},{N,Z}|Rs]) -> + merge_ranges_1([{A,Z}|Rs]); +merge_ranges_1([R|Rs]) -> + [R|merge_ranges_1(Rs)]; +merge_ranges_1([]) -> []. + +live_interval_blk(L, Blocks, {Vars0,Aliases0,LiveMap0}) -> + Live0 = [], + Successors = beam_ssa:successors(L, Blocks), + Live1 = update_successors(Successors, L, Blocks, LiveMap0, Live0), + + %% Add ranges for all variables that are live in the successors. + #b_blk{is=Is,last=Last} = maps:get(L, Blocks), + End = beam_ssa:get_anno(n, Last), + Use = [{V,{use,End+1}} || V <- Live1], + + %% Determine used and defined variables in this block. + FirstNumber = first_number(Is, Last), + {UseDef0,Aliases} = live_interval_blk_1([Last|reverse(Is)], + FirstNumber, Aliases0, Use), + UseDef = rel2fam(UseDef0), + + %% Update what is live at the beginning of this block and + %% store it. + Used = [V || {V,[{use,_}|_]} <- UseDef], + Live2 = ordsets:union(Live1, Used), + Killed = [V || {V,[{def,_}|_]} <- UseDef], + Live = ordsets:subtract(Live2, Killed), + LiveMap = LiveMap0#{L=>Live}, + + %% Construct the ranges for this block. + Vars = make_block_ranges(UseDef, FirstNumber, Vars0), + {Vars,Aliases,LiveMap}. + +make_block_ranges([{V,[{def,Def}]}|Vs], First, Acc) -> + make_block_ranges(Vs, First, [{V,{Def,Def}}|Acc]); +make_block_ranges([{V,[{def,Def}|Uses]}|Vs], First, Acc) -> + {use,Last} = last(Uses), + make_block_ranges(Vs, First, [{V,{Def,Last}}|Acc]); +make_block_ranges([{V,[{use,_}|_]=Uses}|Vs], First, Acc) -> + {use,Last} = last(Uses), + make_block_ranges(Vs, First, [{V,{First,Last}}|Acc]); +make_block_ranges([], _, Acc) -> Acc. + +live_interval_blk_1([#b_set{op=phi,dst=#b_var{name=Dst}}|Is], + FirstNumber, Aliases, Acc0) -> + Acc = [{Dst,{def,FirstNumber}}|Acc0], + live_interval_blk_1(Is, FirstNumber, Aliases, Acc); +live_interval_blk_1([#b_set{op=bs_start_match}=I|Is], FirstNumber, + Aliases0, Acc0) -> + N = beam_ssa:get_anno(n, I), + #b_set{dst=#b_var{name=Dst}} = I, + Acc1 = [{Dst,{def,N}}|Acc0], + Aliases = case beam_ssa:get_anno(reuse_for_context, I) of + true -> + #b_set{args=[#b_var{name=Src}]} = I, + [{Dst,Src}|Aliases0]; + false -> + Aliases0 + end, + Acc = [{V,{use,N}} || V <- beam_ssa:used(I)] ++ Acc1, + live_interval_blk_1(Is, FirstNumber, Aliases, Acc); +live_interval_blk_1([I|Is], FirstNumber, Aliases, Acc0) -> + N = beam_ssa:get_anno(n, I), + Acc1 = case I of + #b_set{dst=#b_var{name=Dst}} -> + [{Dst,{def,N}}|Acc0]; + _ -> + Acc0 + end, + Used = beam_ssa:used(I), + Acc = [{V,{use,N}} || V <- Used] ++ Acc1, + live_interval_blk_1(Is, FirstNumber, Aliases, Acc); +live_interval_blk_1([], _FirstNumber, Aliases, Acc) -> + {Acc,Aliases}. + +%% first_number([#b_set{}]) -> InstructionNumber. +%% Return the number for the first instruction for the block. +%% Note that this number is one less than the first +%% non-phi instruction in the block. + +first_number([#b_set{op=phi}|Is], Last) -> + first_number(Is, Last); +first_number([I|_], _) -> + beam_ssa:get_anno(n, I) - 1; +first_number([], Last) -> + beam_ssa:get_anno(n, Last) - 1. + +update_successors([L|Ls], Pred, Blocks, LiveMap, Live0) -> + Live1 = ordsets:union(Live0, get_live(L, LiveMap)), + #b_blk{is=Is} = maps:get(L, Blocks), + Live = update_live_phis(Is, Pred, Live1), + update_successors(Ls, Pred, Blocks, LiveMap, Live); +update_successors([], _, _, _, Live) -> Live. + +get_live(L, LiveMap) -> + case LiveMap of + #{L:=Live} -> Live; + #{} -> [] + end. + +update_live_phis([#b_set{op=phi,dst=#b_var{name=Killed},args=Args}|Is], + Pred, Live0) -> + Used = [V || {#b_var{name=V},L} <- Args, L =:= Pred], + Live1 = ordsets:union(ordsets:from_list(Used), Live0), + Live = ordsets:del_element(Killed, Live1), + update_live_phis(Is, Pred, Live); +update_live_phis(_, _, Live) -> Live. + +%%% +%%% Reserve Y registers. +%%% + +%% reserve_yregs(St0) -> St. +%% In each block that allocates a stack frame, insert instructions +%% that copy variables that must be in Y registers (given by +%% YRegisters) to new variables. +%% +%% Also allocate specific Y registers for try and catch tags. +%% The outermost try/catch tag is placed in y0, any directly +%% nested tag in y1, and so on. Note that this is the reversed +%% order as required by BEAM; it will be corrected later by +%% turn_yregs(). + +reserve_yregs(#st{frames=Frames}=St0) -> + foldl(fun reserve_yregs_1/2, St0, Frames). + +reserve_yregs_1(L, #st{ssa=Blocks0,cnt=Count0,res=Res0}=St) -> + Blk = maps:get(L, Blocks0), + Yregs = beam_ssa:get_anno(yregs, Blk), + {Def,Used} = beam_ssa:def_used([L], Blocks0), + UsedYregs = ordsets:intersection(Yregs, Used), + DefBefore = ordsets:subtract(UsedYregs, Def), + {BeforeVars,Blocks,Count} = rename_vars(DefBefore, L, Blocks0, Count0), + InsideVars = ordsets:subtract(UsedYregs, DefBefore), + ResTryTags0 = reserve_try_tags(L, Blocks), + ResTryTags = [{V,{Reg,Count}} || {V,Reg} <- ResTryTags0], + Vars = BeforeVars ++ InsideVars, + Res = [{V,{y,Count}} || V <- Vars] ++ ResTryTags ++ Res0, + St#st{res=Res,ssa=Blocks,cnt=Count+1}. + +reserve_try_tags(L, Blocks) -> + Seen = gb_sets:empty(), + {Res0,_} = reserve_try_tags_1([L], Blocks, Seen, #{}), + Res1 = [maps:to_list(M) || {_,M} <- maps:to_list(Res0)], + Res = [{V,{y,Y}} || {V,Y} <- append(Res1)], + ordsets:from_list(Res). + +reserve_try_tags_1([L|Ls], Blocks, Seen0, ActMap0) -> + case gb_sets:is_element(L, Seen0) of + true -> + reserve_try_tags_1(Ls, Blocks, Seen0, ActMap0); + false -> + Seen1 = gb_sets:insert(L, Seen0), + #b_blk{is=Is} = Blk = maps:get(L, Blocks), + Active0 = get_active(L, ActMap0), + Active = reserve_try_tags_is(Is, Active0), + Successors = beam_ssa:successors(Blk), + ActMap1 = update_act_map(Successors, Active, ActMap0), + {ActMap,Seen} = reserve_try_tags_1(Ls, Blocks, Seen1, ActMap1), + reserve_try_tags_1(Successors, Blocks, Seen,ActMap) + end; +reserve_try_tags_1([], _Blocks, Seen, ActMap) -> + {ActMap,Seen}. + +get_active(L, ActMap) -> + case ActMap of + #{L:=Active} -> Active; + #{} -> #{} + end. + +reserve_try_tags_is([#b_set{op=new_try_tag,dst=#b_var{name=V}}|Is], Active) -> + N = map_size(Active), + reserve_try_tags_is(Is, Active#{V=>N}); +reserve_try_tags_is([#b_set{op=kill_try_tag,args=[#b_var{name=Tag}]}|Is], Active) -> + reserve_try_tags_is(Is, maps:remove(Tag, Active)); +reserve_try_tags_is([_|Is], Active) -> + reserve_try_tags_is(Is, Active); +reserve_try_tags_is([], Active) -> Active. + +update_act_map([L|Ls], Active0, ActMap0) -> + case ActMap0 of + #{L:=Active1} -> + ActMap = ActMap0#{L=>maps:merge(Active0, Active1)}, + update_act_map(Ls, Active0, ActMap); + #{} -> + ActMap = ActMap0#{L=>Active0}, + update_act_map(Ls, Active0, ActMap) + end; +update_act_map([], _, ActMap) -> ActMap. + +rename_vars([], _, Blocks, Count) -> + {[],Blocks,Count}; +rename_vars(Vs, L, Blocks0, Count0) -> + {NewVs,Count} = new_var_names(Vs, Count0), + NewVars = [#b_var{name=V} || V <- NewVs], + Ren = zip(Vs, NewVars), + Blocks1 = beam_ssa:rename_vars(Ren, [L], Blocks0), + #b_blk{is=Is0} = Blk0 = maps:get(L, Blocks1), + CopyIs = [#b_set{op=copy,dst=New,args=[#b_var{name=Old}]} || + {Old,New} <- Ren], + Is = insert_after_phis(Is0, CopyIs), + Blk = Blk0#b_blk{is=Is}, + Blocks = maps:put(L, Blk, Blocks1), + {NewVs,Blocks,Count}. + +insert_after_phis([#b_set{op=phi}=I|Is], InsertIs) -> + [I|insert_after_phis(Is, InsertIs)]; +insert_after_phis(Is, InsertIs) -> + InsertIs ++ Is. + +%% frame_size(St0) -> St. +%% Calculate the frame size for each block that allocates a frame. +%% Annotate the block with the frame size. Also annotate all +%% return instructions with {deallocate,FrameSize} to simplify +%% code generation. + +frame_size(#st{frames=Frames,regs=Regs,ssa=Blocks0}=St) -> + Blocks = foldl(fun(L, Blks) -> + frame_size_1(L, Regs, Blks) + end, Blocks0, Frames), + St#st{ssa=Blocks}. + +frame_size_1(L, Regs, Blocks0) -> + Def = beam_ssa:def([L], Blocks0), + Yregs0 = [maps:get(V, Regs) || V <- Def, is_yreg(maps:get(V, Regs))], + Yregs = ordsets:from_list(Yregs0), + FrameSize = length(ordsets:from_list(Yregs)), + if + FrameSize =/= 0 -> + [{y,0}|_] = Yregs, %Assertion. + {y,Last} = last(Yregs), + Last = FrameSize - 1, %Assertion. + ok; + true -> + ok + end, + Blk0 = maps:get(L, Blocks0), + Blk = beam_ssa:add_anno(frame_size, FrameSize, Blk0), + + %% Insert an annotation for frame deallocation on + %% each #b_ret{}. + Blocks = maps:put(L, Blk, Blocks0), + Reachable = beam_ssa:rpo([L], Blocks), + frame_deallocate(Reachable, FrameSize, Blocks). + +frame_deallocate([L|Ls], Size, Blocks0) -> + Blk0 = maps:get(L, Blocks0), + Blk = case Blk0 of + #b_blk{last=#b_ret{}=Ret0} -> + Ret = beam_ssa:add_anno(deallocate, Size, Ret0), + Blk0#b_blk{last=Ret}; + #b_blk{} -> + Blk0 + end, + Blocks = maps:put(L, Blk, Blocks0), + frame_deallocate(Ls, Size, Blocks); +frame_deallocate([], _, Blocks) -> Blocks. + + +%% turn_yregs(St0) -> St. +%% Renumber y registers so that {y,0} becomes {y,FrameSize-1}, +%% {y,FrameSize-1} becomes {y,0} and so on. This is to make nested +%% catches work. The register allocator (linear_scan()) has given +%% a lower number to the outermost catch. + +turn_yregs(#st{frames=Frames,regs=Regs0,ssa=Blocks}=St) -> + Regs1 = foldl(fun(L, A) -> + Blk = maps:get(L, Blocks), + FrameSize = beam_ssa:get_anno(frame_size, Blk), + Def = beam_ssa:def([L], Blocks), + [turn_yregs_1(Def, FrameSize, Regs0)|A] + end, [], Frames), + Regs = maps:merge(Regs0, maps:from_list(append(Regs1))), + St#st{regs=Regs}. + +turn_yregs_1(Def, FrameSize, Regs) -> + Yregs0 = [{maps:get(V, Regs),V} || V <- Def, is_yreg(maps:get(V, Regs))], + Yregs1 = rel2fam(Yregs0), + FrameSize = length(Yregs1), + Yregs2 = [{{y,FrameSize-Y-1},Vs} || {{y,Y},Vs} <- Yregs1], + R0 = sofs:family(Yregs2), + R1 = sofs:family_to_relation(R0), + R = sofs:converse(R1), + sofs:to_external(R). + +%%% +%%% Reserving registers before register allocation. +%%% + +%% reserve_regs(St0) -> St. +%% Reserve registers prior to register allocation. Y registers +%% have already been reserved. This function will reserve z, +%% fr, and specific x registers. + +reserve_regs(#st{args=Args,ssa=Blocks,intervals=Intervals,res=Res0}=St) -> + %% Reserve x0, x1, and so on for the function arguments. + Res1 = reserve_arg_regs(Args, 0, Res0), + + %% Reserve Z registers (dummy registers) for instructions with no + %% return values (e.g. remove_message) or pseudo-return values + %% (e.g. landingpad). + Res2 = reserve_zregs(Blocks, Intervals, Res1), + + %% Reserve float registers. + Res3 = reserve_fregs(Blocks, Res2), + + %% Reserve all remaining unreserved variables as X registers. + Res = maps:from_list(Res3), + St#st{res=reserve_xregs(Blocks, Res)}. + +reserve_arg_regs([#b_var{name=Arg}|Is], N, Acc) -> + reserve_arg_regs(Is, N+1, [{Arg,{x,N}}|Acc]); +reserve_arg_regs([], _, Acc) -> Acc. + +reserve_zregs(Blocks, Intervals, Res) -> + ShortLived0 = [V || {V,[{Start,End}]} <- Intervals, Start+2 =:= End], + ShortLived = cerl_sets:from_list(ShortLived0), + F = fun(_, #b_blk{is=Is,last=Last}, A) -> + reserve_zreg(Is, Last, ShortLived, A) + end, + beam_ssa:fold_rpo(F, [0], Res, Blocks). + +reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst}, + #b_set{op={bif,'=:='},args=[Dst,Val]}], _Last, ShortLived, A0) -> + case Val of + #b_literal{val=Arity} when Arity bsr 32 =:= 0 -> + %% These two instructions can be combined to a test_arity + %% instruction provided that the arity variable is short-lived. + reserve_zreg_1(Dst, ShortLived, A0); + _ -> + A0 + end; +reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst}], + #b_switch{}, ShortLived, A) -> + reserve_zreg_1(Dst, ShortLived, A); +reserve_zreg([#b_set{op=Op,dst=#b_var{name=Dst}}|Is], Last, ShortLived, A0) -> + IsZReg = case Op of + context_to_binary -> true; + bs_match_string -> true; + bs_restore -> true; + bs_save -> true; + {float,clearerror} -> true; + kill_try_tag -> true; + landingpad -> true; + put_tuple_elements -> true; + remove_message -> true; + set_tuple_element -> true; + succeeded -> true; + timeout -> true; + wait_timeout -> true; + _ -> false + end, + A = case IsZReg of + true -> [{Dst,z}|A0]; + false -> A0 + end, + reserve_zreg(Is, Last, ShortLived, A); +reserve_zreg([], #b_br{bool=Bool}, ShortLived, A) -> + reserve_zreg_1(Bool, ShortLived, A); +reserve_zreg([], _, _, A) -> A. + +reserve_zreg_1(#b_var{name=V}, ShortLived, A) -> + case cerl_sets:is_element(V, ShortLived) of + true -> [{V,z}|A]; + false -> A + end; +reserve_zreg_1(#b_literal{}, _, A) -> A. + +reserve_fregs(Blocks, Res) -> + F = fun(_, #b_blk{is=Is}, A) -> + reserve_freg(Is, A) + end, + beam_ssa:fold_rpo(F, [0], Res, Blocks). + +reserve_freg([#b_set{op={float,Op},dst=#b_var{name=V}}|Is], Res) -> + case Op of + get -> + reserve_freg(Is, Res); + _ -> + reserve_freg(Is, [{V,fr}|Res]) + end; +reserve_freg([_|Is], Res) -> + reserve_freg(Is, Res); +reserve_freg([], Res) -> Res. + +%% reserve_xregs(St0) -> St. +%% Reserve all remaining variables as X registers. +%% +%% If a variable will need to be in a specific X register for a +%% 'call' or 'make_fun' (and there is nothing that will kill it +%% between the definition and use), reserve the register using a +%% {prefer,{x,X} annotation. That annotation means that the linear +%% scan algorithm will place the variable in the preferred register, +%% unless that register is already occupied. +%% +%% All remaining variables are reserved as X registers. Linear scan +%% will allocate the lowest free X register for the variable. + +reserve_xregs(Blocks, Res) -> + F = fun(L, #b_blk{is=Is,last=Last}, R) -> + {Xs0,Used0} = reserve_terminator(L, Last, Blocks, R), + reserve_xregs_is(reverse(Is), R, Xs0, Used0) + end, + beam_ssa:fold_po(F, Res, Blocks). + +reserve_xregs_is([#b_set{op=Op,dst=#b_var{name=Dst},args=Args}=I|Is], Res0, Xs0, Used0) -> + Xs1 = case is_gc_safe(I) of + true -> + Xs0; + false -> + %% There may be a garbage collection after executing this + %% instruction. We will need prune the list of preferred + %% X registers. + res_xregs_prune(Xs0, Used0, Res0) + end, + Res = reserve_xreg(Dst, Xs1, Res0), + Used1 = ordsets:union(Used0, beam_ssa:used(I)), + Used = ordsets:del_element(Dst, Used1), + case Op of + call -> + Xs = reserve_call_args(tl(Args)), + reserve_xregs_is(Is, Res, Xs, Used); + make_fun -> + Xs = reserve_call_args(tl(Args)), + reserve_xregs_is(Is, Res, Xs, Used); + _ -> + reserve_xregs_is(Is, Res, Xs1, Used) + end; +reserve_xregs_is([], Res, _Xs, _Used) -> Res. + +reserve_terminator(L, #b_br{bool=#b_literal{val=true},succ=Succ}, Blocks, Res) -> + case maps:get(Succ, Blocks) of + #b_blk{is=[],last=Last} -> + reserve_terminator(Succ, Last, Blocks, Res); + #b_blk{is=[_|_]=Is} -> + {res_xregs_from_phi(Is, L, Res, #{}),[]} + end; +reserve_terminator(_, Last, _, _) -> + {#{},beam_ssa:used(Last)}. + +res_xregs_from_phi([#b_set{op=phi,dst=#b_var{name=Dst},args=Args}|Is], + Pred, Res, Acc) -> + case [V || {#b_var{name=V},L} <- Args, L =:= Pred] of + [] -> + res_xregs_from_phi(Is, Pred, Res, Acc); + [V] -> + case Res of + #{Dst:={prefer,Reg}} -> + res_xregs_from_phi(Is, Pred, Res, Acc#{V=>Reg}); + #{Dst:=_} -> + res_xregs_from_phi(Is, Pred, Res, Acc) + end + end; +res_xregs_from_phi(_, _, _, Acc) -> Acc. + +reserve_call_args(Args) -> + reserve_call_args(Args, 0, #{}). + +reserve_call_args([#b_var{name=Name}|As], X, Xs) -> + reserve_call_args(As, X+1, Xs#{Name=>{x,X}}); +reserve_call_args([#b_literal{}|As], X, Xs) -> + reserve_call_args(As, X+1, Xs); +reserve_call_args([], _, Xs) -> Xs. + +reserve_xreg(V, Xs, Res) -> + case Res of + #{V:=_} -> + %% Already reserved. + Res; + #{} -> + case Xs of + #{V:=X} -> + %% Add a hint that a specific X register is + %% preferred, unless it is already in use. + Res#{V=>{prefer,X}}; + #{} -> + %% Reserve as an X register in general. + Res#{V=>x} + end + end. + +is_gc_safe(#b_set{op=phi}) -> + false; +is_gc_safe(#b_set{op=Op,args=Args}) -> + case beam_ssa_codegen:classify_heap_need(Op, Args) of + neutral -> true; + {put,_} -> true; + _ -> false + end. + +%% res_xregs_prune(PreferredRegs, Used, Res) -> PreferredRegs. +%% Prune the list of preferred to only include X registers that +%% are guaranteed to survice a garbage collection. + +res_xregs_prune(Xs, Used, Res) -> + %% The number of safe registers is the number of the X registers + %% used after this point. The actual number of safe registers may + %% be highter than this number, but this is a conservative safe + %% estimate. + NumSafe = foldl(fun(V, N) -> + case Res of + #{V:={x,_}} -> N + 1; + #{V:=_} -> N; + #{} -> N + 1 + end + end, 0, Used), + + %% Remove unsafe registers from the list of potential + %% preferred registers. + maps:filter(fun(_, {x,X}) -> X < NumSafe end, Xs). + +%%% +%%% Remove unsuitable aliases. +%%% +%%% If a binary is matched more than once, we must not put the +%%% the match context in the same register as the binary to +%%% avoid the following situation: +%%% +%%% {test,bs_start_match2,{f,3},1,[{x,0},0],{x,0}}. +%%% . +%%% . +%%% . +%%% {test,bs_start_match2,{f,6},1,[{x,0},0],{x,1}}. %% ILLEGAL! +%%% +%%% The second instruction is illegal because a match context source +%%% is only allowed if source and destination registers are identical. +%%% + +remove_unsuitable_aliases(#st{aliases=[_|_]=Aliases0,ssa=Blocks}=St) -> + R = rem_unsuitable(maps:values(Blocks)), + Unsuitable0 = [V || {V,[_,_|_]} <- rel2fam(R)], + Unsuitable = gb_sets:from_list(Unsuitable0), + Aliases =[P || {_,V}=P <- Aliases0, + not gb_sets:is_member(V, Unsuitable)], + St#st{aliases=Aliases}; +remove_unsuitable_aliases(#st{aliases=[]}=St) -> St. + +rem_unsuitable([#b_blk{is=Is}|Bs]) -> + Vs = [{V,Dst} || + #b_set{op=bs_start_match,dst=#b_var{name=Dst}, + args=[#b_var{name=V}]} <- Is], + Vs ++ rem_unsuitable(Bs); +rem_unsuitable([]) -> []. + +%%% +%%% Merge intervals. +%%% + +merge_intervals(#st{aliases=Aliases0,intervals=Intervals0, + res=Reserved}=St) -> + Aliases1 = [A || A <- Aliases0, + is_suitable_alias(A, Reserved)], + case Aliases1 of + [] -> + St#st{aliases=Aliases1}; + [_|_] -> + Intervals1 = maps:from_list(Intervals0), + {Intervals,Aliases} = + merge_intervals_1(Aliases1, Intervals1, []), + St#st{aliases=Aliases,intervals=Intervals} + end. + +merge_intervals_1([{Alias,V}|Vs], Intervals0, Acc) -> + #{Alias:=Int1,V:=Int2} = Intervals0, + Int3 = lists:merge(Int1, Int2), + Int = merge_intervals_2(Int3), + Intervals1 = maps:remove(Alias, Intervals0), + Intervals = Intervals1#{V:=Int}, + merge_intervals_1(Vs, Intervals, [{Alias,V}|Acc]); +merge_intervals_1([], Intervals, Acc) -> + {maps:to_list(Intervals),Acc}. + +merge_intervals_2([{A1,B1},{A2,B2}|Is]) when A2 =< B1 -> + merge_intervals_2([{min(A1, A2),max(B1, B2)}|Is]); +merge_intervals_2([{_A1,B1}=R|[{A2,_B2}|_]=Is]) when B1 < A2 -> + [R|merge_intervals_2(Is)]; +merge_intervals_2([_]=Is) -> Is. + +is_suitable_alias({V1,V2}, Reserved) -> + #{V1:=Res1,V2:=Res2} = Reserved, + case {Res1,Res2} of + {x,x} -> true; + {x,{x,_}} -> true; + {{x,_},x} -> true; + {_,_} -> false + end. + +%%% +%%% Register allocation using linear scan. +%%% + +-record(i, + {sort=1 :: instr_number(), + reg=none :: i_reg(), + pool=x :: pool_id(), + var=#b_var{} :: b_var(), + rs=[] :: [range()] + }). + +-record(l, + {cur=#i{} :: interval(), + unhandled_res=[] :: [interval()], + unhandled_any=[] :: [interval()], + active=[] :: [interval()], + inactive=[] :: [interval()], + free=#{} :: #{var_name()=>pool(), + {'next',pool_id()}:=reg_num()}, + regs=[] :: [{b_var(),ssa_register()}] + }). + +-type interval() :: #i{}. +-type i_reg() :: ssa_register() | {'prefer',xreg()} | 'none'. +-type pool_id() :: 'fr' | 'x' | 'z' | instr_number(). +-type pool() :: ordsets:ordset(ssa_register()). + +linear_scan(#st{intervals=Intervals0,res=Res}=St0) -> + St = St0#st{intervals=[],res=[]}, + Free = init_free(maps:to_list(Res)), + Intervals1 = [init_interval(Int, Res) || Int <- Intervals0], + Intervals = sort(Intervals1), + IsReserved = fun (#i{reg=Reg}) -> Reg =/= none end, + {UnhandledRes,Unhandled} = partition(IsReserved, Intervals), + L = #l{unhandled_res=UnhandledRes, + unhandled_any=Unhandled,free=Free}, + #l{regs=Regs} = do_linear(L), + St#st{regs=maps:from_list(Regs)}. + +init_interval({V,[{Start,_}|_]=Rs}, Res) -> + Info = maps:get(V, Res), + Pool = case Info of + {prefer,{x,_}} -> x; + x -> x; + {x,_} -> x; + {y,Uniq} -> Uniq; + {{y,_},Uniq} -> Uniq; + z -> z; + fr -> fr + end, + Reg = case Info of + {prefer,{x,_}} -> Info; + {x,_} -> Info; + {{y,_}=Y,_} -> Y; + _ -> none + end, + #i{sort=Start,var=V,reg=Reg,pool=Pool,rs=Rs}. + +init_free(Res) -> + Free0 = rel2fam([{x,{x,0}}|init_free_1(Res)]), + #{x:=Xs0} = Free1 = maps:from_list(Free0), + Xs = init_xregs(Xs0), + Free = Free1#{x:=Xs}, + Next = maps:fold(fun(K, V, A) -> [{{next,K},length(V)}|A] end, [], Free), + maps:merge(Free, maps:from_list(Next)). + +init_free_1([{_,{prefer,{x,_}=Reg}}|Res]) -> + [{x,Reg}|init_free_1(Res)]; +init_free_1([{_,{x,_}=Reg}|Res]) -> + [{x,Reg}|init_free_1(Res)]; +init_free_1([{_,{y,Uniq}}|Res]) -> + [{Uniq,{y,0}}|init_free_1(Res)]; +init_free_1([{_,{{y,_}=Reg,Uniq}}|Res]) -> + [{Uniq,Reg}|init_free_1(Res)]; +init_free_1([{_,z}|Res]) -> + [{z,{z,0}}|init_free_1(Res)]; +init_free_1([{_,fr}|Res]) -> + [{fr,{fr,0}}|init_free_1(Res)]; +init_free_1([{_,x}|Res]) -> + init_free_1(Res); +init_free_1([]) -> []. + +%% Make sure that the pool of xregs is contiguous. +init_xregs([{x,N},{x,M}|Is]) when N+1 =:= M -> + [{x,N}|init_xregs([{x,M}|Is])]; +init_xregs([{x,N}|[{x,_}|_]=Is]) -> + [{x,N}|init_xregs([{x,N+1}|Is])]; +init_xregs([{x,_}]=Is) -> Is. + +do_linear(L0) -> + case set_next_current(L0) of + done -> + L0; + L1 -> + L2 = expire_active(L1), + L3 = check_inactive(L2), + Available = collect_available(L3), + L4 = select_register(Available, L3), + L = make_cur_active(L4), + do_linear(L) + end. + +set_next_current(#l{unhandled_res=[Cur1|T1], + unhandled_any=[Cur2|T2]}=L) -> + case {Cur1,Cur2} of + {#i{sort=N1},#i{sort=N2}} when N1 < N2 -> + L#l{cur=Cur1,unhandled_res=T1}; + {_,_} -> + L#l{cur=Cur2,unhandled_any=T2} + end; +set_next_current(#l{unhandled_res=[], + unhandled_any=[Cur|T]}=L) -> + L#l{cur=Cur,unhandled_any=T}; +set_next_current(#l{unhandled_res=[Cur|T], + unhandled_any=[]}=L) -> + L#l{cur=Cur,unhandled_res=T}; +set_next_current(#l{unhandled_res=[],unhandled_any=[]}) -> + done. + +expire_active(#l{cur=#i{sort=CurBegin},active=Act0}=L0) -> + {Act,L} = expire_active(Act0, CurBegin, L0, []), + L#l{active=Act}. + +expire_active([#i{reg=Reg,rs=Rs0}=I|Is], CurBegin, L0, Acc) -> + {_,_} = Reg, %Assertion. + case overlap_status(Rs0, CurBegin) of + ends_before_cur -> + L = free_reg(I, L0), + expire_active(Is, CurBegin, L, Acc); + overlapping -> + expire_active(Is, CurBegin, L0, [I|Acc]); + not_overlapping -> + Rs = strip_before_current(Rs0, CurBegin), + L1 = free_reg(I, L0), + L = L1#l{inactive=[I#i{rs=Rs}|L1#l.inactive]}, + expire_active(Is, CurBegin, L, Acc) + end; +expire_active([], _CurBegin, L, Acc) -> + {Acc,L}. + +check_inactive(#l{cur=#i{sort=CurBegin},inactive=InAct0}=L0) -> + {InAct,L} = check_inactive(InAct0, CurBegin, L0, []), + L#l{inactive=InAct}. + +check_inactive([#i{rs=Rs0}=I|Is], CurBegin, L0, Acc) -> + case overlap_status(Rs0, CurBegin) of + ends_before_cur -> + check_inactive(Is, CurBegin, L0, Acc); + not_overlapping -> + check_inactive(Is, CurBegin, L0, [I|Acc]); + overlapping -> + Rs = strip_before_current(Rs0, CurBegin), + L1 = L0#l{active=[I#i{rs=Rs}|L0#l.active]}, + L = reserve_reg(I, L1), + check_inactive(Is, CurBegin, L, Acc) + end; +check_inactive([], _CurBegin, L, Acc) -> + {Acc,L}. + +strip_before_current([{_,E}|Rs], CurBegin) when E =< CurBegin -> + strip_before_current(Rs, CurBegin); +strip_before_current(Rs, _CurBegin) -> Rs. + +collect_available(#l{cur=#i{reg={prefer,{_,_}=Prefer}}=I}=L) -> + %% Use the preferred register if it is available. + Avail = collect_available(L#l{cur=I#i{reg=none}}), + case member(Prefer, Avail) of + true -> [Prefer]; + false -> Avail + end; +collect_available(#l{cur=#i{reg={_,_}=ReservedReg}}) -> + %% Return the already reserved register. + [ReservedReg]; +collect_available(#l{unhandled_res=Unhandled,cur=Cur}=L) -> + Free = get_pool(Cur, L), + + %% Note that since the live intervals are constructed from + %% SSA form, there cannot be any overlap of the current interval + %% with any inactive interval. See [3], page 175. Therefore we + %% only have check the unhandled intervals for overlap with + %% the current interval. As a further optimization, we only need + %% to check the intervals that have reserved registers. + collect_available(Unhandled, Cur, Free). + +collect_available([#i{pool=Pool1}|Is], #i{pool=Pool2}=Cur, Free) + when Pool1 =/= Pool2 -> + %% Wrong pool. Ignore this interval. + collect_available(Is, Cur, Free); +collect_available([#i{reg={_,_}=Reg}=I|Is], Cur, Free0) -> + case overlaps(I, Cur) of + true -> + Free = ordsets:del_element(Reg, Free0), + collect_available(Is, Cur, Free); + false -> + collect_available(Is, Cur, Free0) + end; +collect_available([], _, Free) -> Free. + +select_register([{_,_}=Reg|_], #l{cur=Cur0,regs=Regs}=L) -> + Cur = Cur0#i{reg=Reg}, + reserve_reg(Cur, L#l{cur=Cur,regs=[{Cur#i.var,Reg}|Regs]}); +select_register([], #l{cur=Cur0,regs=Regs}=L0) -> + %% Allocate a new register in the pool. + {Reg,L1} = get_next_free(Cur0, L0), + Cur = Cur0#i{reg=Reg}, + L = L1#l{cur=Cur,regs=[{Cur#i.var,Reg}|Regs]}, + reserve_reg(Cur, L). + +make_cur_active(#l{cur=Cur,active=Act}=L) -> + L#l{active=[Cur|Act]}. + +overlaps(#i{rs=Rs1}, #i{rs=Rs2}) -> + are_overlapping(Rs1, Rs2). + +overlap_status([{S,E}], CurBegin) -> + if + E =< CurBegin -> ends_before_cur; + CurBegin < S -> not_overlapping; + true -> overlapping + end; +overlap_status([{S,E}|Rs], CurBegin) -> + if + E =< CurBegin -> + overlap_status(Rs, CurBegin); + S =< CurBegin -> + overlapping; + true -> + not_overlapping + end. + +reserve_reg(#i{reg={_,_}=Reg}=I, L) -> + FreeRegs0 = get_pool(I, L), + FreeRegs = ordsets:del_element(Reg, FreeRegs0), + update_pool(I, FreeRegs, L). + +free_reg(#i{reg={_,_}=Reg}=I, L) -> + FreeRegs0 = get_pool(I, L), + FreeRegs = ordsets:add_element(Reg, FreeRegs0), + update_pool(I, FreeRegs, L). + +get_pool(#i{pool=Pool}, #l{free=Free}) -> + maps:get(Pool, Free). + +update_pool(#i{pool=Pool}, New, #l{free=Free0}=L) -> + Free = maps:put(Pool, New, Free0), + L#l{free=Free}. + +get_next_free(#i{pool=Pool}, #l{free=Free0}=L0) -> + K = {next,Pool}, + N = maps:get(K, Free0), + Free = maps:put(K, N+1, Free0), + L = L0#l{free=Free}, + if + is_integer(Pool) -> {{y,N},L}; + is_atom(Pool) -> {{Pool,N},L} + end. + +%%% +%%% Interval utilities. +%%% + +are_overlapping([R|Rs1], Rs2) -> + case are_overlapping_1(R, Rs2) of + true -> + true; + false -> + are_overlapping(Rs1, Rs2) + end; +are_overlapping([], _) -> false. + +are_overlapping_1({_S1,E1}, [{S2,_E2}|_]) when E1 < S2 -> + false; +are_overlapping_1({S1,E1}=R, [{S2,E2}|Rs]) -> + (S2 < E1 andalso E2 > S1) orelse are_overlapping_1(R, Rs); +are_overlapping_1({_,_}, []) -> false. + +%%% +%%% Utilities. +%%% + +%% is_loop_header(L, Blocks) -> false|true. +%% Check whether the block is a loop header. + +is_loop_header(L, Blocks) -> + %% We KNOW that a loop header must start with a peek_message + %% instruction. + case maps:get(L, Blocks) of + #b_blk{is=[#b_set{op=peek_message}|_]} -> true; + _ -> false + end. + +rel2fam(S0) -> + S1 = sofs:relation(S0), + S = sofs:rel2fam(S1), + sofs:to_external(S). + +split_phis(Is) -> + partition(fun(#b_set{op=Op}) -> Op =:= phi end, Is). + +is_yreg({y,_}) -> true; +is_yreg({x,_}) -> false; +is_yreg({z,_}) -> false; +is_yreg({fr,_}) -> false. + +new_var_names([V0|Vs0], Count0) -> + {V,Count1} = new_var_name(V0, Count0), + {Vs,Count} = new_var_names(Vs0, Count1), + {[V|Vs],Count}; +new_var_names([], Count) -> {[],Count}. + +new_var_name({Base,Int}, Count) -> + true = is_integer(Int), %Assertion. + {{Base,Count},Count+1}; +new_var_name(Base, Count) -> + {{Base,Count},Count+1}. diff --git a/lib/compiler/src/beam_ssa_recv.erl b/lib/compiler/src/beam_ssa_recv.erl new file mode 100644 index 0000000000..82fe006487 --- /dev/null +++ b/lib/compiler/src/beam_ssa_recv.erl @@ -0,0 +1,281 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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_ssa_recv). +-export([module/2]). + +%%% +%%% In code such as: +%%% +%%% Ref = make_ref(), %Or erlang:monitor(process, Pid) +%%% . +%%% . +%%% . +%%% receive +%%% {Ref,Reply} -> Reply +%%% end. +%%% +%%% we know that none of the messages that exist in the message queue +%%% before the call to make_ref/0 can be matched out in the receive +%%% statement. Therefore we can avoid going through the entire message +%%% queue if we introduce two new instructions (here written as +%%% BIFs in pseudo-Erlang): +%%% +%%% recv_mark(SomeUniqInteger), +%%% Ref = make_ref(), +%%% . +%%% . +%%% . +%%% recv_set(SomeUniqInteger), +%%% receive +%%% {Ref,Reply} -> Reply +%%% end. +%%% +%%% The recv_mark/1 instruction will save the current position and +%%% SomeUniqInteger in the process context. The recv_set +%%% instruction will verify that SomeUniqInteger is still stored +%%% in the process context. If it is, it will set the current pointer +%%% for the message queue (the next message to be read out) to the +%%% position that was saved by recv_mark/1. +%%% +%%% The remove_message instruction must be modified to invalidate +%%% the information stored by the previous recv_mark/1, in case there +%%% is another receive executed between the calls to recv_mark/1 and +%%% recv_set/1. +%%% +%%% We use a reference to a label (i.e. a position in the loaded code) +%%% as the SomeUniqInteger. +%%% + +-include("beam_ssa.hrl"). +-import(lists, [all/2,reverse/2]). + +-spec module(beam_ssa:b_module(), [compile:option()]) -> + {'ok',beam_ssa:b_module()}. + +module(#b_module{body=Fs0}=Module, _Opts) -> + Fs = [function(F) || F <- Fs0], + {ok,Module#b_module{body=Fs}}. + +%%% +%%% Local functions. +%%% + +function(#b_function{anno=Anno,bs=Blocks0}=F) -> + try + Blocks = opt(Blocks0), + F#b_function{bs=Blocks} + catch + Class:Error:Stack -> + #{func_info:={_,Name,Arity}} = Anno, + io:fwrite("Function: ~w/~w\n", [Name,Arity]), + erlang:raise(Class, Error, Stack) + end. + +opt(Blocks) -> + Linear = beam_ssa:linearize(Blocks), + opt(Linear, Blocks, []). + +opt([{L,#b_blk{is=[#b_set{op=peek_message}|_]}=Blk0}|Bs], Blocks0, Preds) -> + %% Search for a suitable reference creating call in one of the predecessor + %% blocks. Whether we find such a call or not, we always clear the + %% the list of predecessors to ensure that any nested receive can't + %% search above the current receive. + case recv_opt(Preds, L, Blocks0) of + {yes,Blocks1} -> + Blk = beam_ssa:add_anno(recv_set, L, Blk0), + Blocks = maps:put(L, Blk, Blocks1), + opt(Bs, Blocks, []); + no -> + opt(Bs, Blocks0, []) + end; +opt([{L,_}|Bs], Blocks, Preds) -> + opt(Bs, Blocks, [L|Preds]); +opt([], Blocks, _) -> Blocks. + +recv_opt([L|Ls], RecvLbl, Blocks) -> + #b_blk{is=Is0} = Blk0 = maps:get(L, Blocks), + case recv_opt_is(Is0, RecvLbl, Blocks, []) of + {yes,Is} -> + Blk = Blk0#b_blk{is=Is}, + {yes,maps:put(L, Blk, Blocks)}; + no -> + recv_opt(Ls, RecvLbl, Blocks) + end; +recv_opt([], _, _Blocks) -> no. + +recv_opt_is([#b_set{op=call}=I0|Is], RecvLbl, Blocks0, Acc) -> + case makes_ref(I0, Blocks0) of + no -> + recv_opt_is(Is, RecvLbl, Blocks0, [I0|Acc]); + {yes,Ref} -> + case opt_ref_used(RecvLbl, Ref, Blocks0) of + false -> + recv_opt_is(Is, RecvLbl, Blocks0, [I0|Acc]); + true -> + I = beam_ssa:add_anno(recv_mark, RecvLbl, I0), + {yes,reverse(Acc, [I|Is])} + end + end; +recv_opt_is([I|Is], RecvLbl, Blocks, Acc) -> + recv_opt_is(Is, RecvLbl, Blocks, [I|Acc]); +recv_opt_is([], _, _, _) -> no. + +makes_ref(#b_set{dst=#b_var{name=Dst},args=[Func0|_]}, Blocks) -> + Func = case Func0 of + #b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=Name},arity=A0} -> + {Name,A0}; + _ -> + none + end, + case Func of + {make_ref,0} -> + {yes,Dst}; + {monitor,2} -> + {yes,Dst}; + {spawn_monitor,A} when A =:= 1; A =:= 3 -> + ref_in_tuple(Dst, Blocks); + _ -> + no + end. + +ref_in_tuple(Tuple, Blocks) -> + F = fun(#b_set{op=get_tuple_element,dst=#b_var{name=Ref}, + args=[#b_var{name=Tup},#b_literal{val=1}]}, no) + when Tup =:= Tuple -> {yes,Ref}; + (_, A) -> A + end, + beam_ssa:fold_instrs_rpo(F, [0], no, Blocks). + +opt_ref_used(RecvLbl, Ref, Blocks) -> + Vs = #{{var,Ref}=>ref,ref=>Ref,ref_matched=>false}, + case opt_ref_used_1(RecvLbl, Vs, Blocks) of + used -> true; + not_used -> false; + done -> false + end. + +opt_ref_used_1(L, Vs0, Blocks) -> + #b_blk{is=Is} = Blk = maps:get(L, Blocks), + case opt_ref_used_is(Is, Vs0) of + #{}=Vs -> + opt_ref_used_last(Blk, Vs, Blocks); + Result -> + Result + end. + +opt_ref_used_is([#b_set{op=peek_message,dst=#b_var{name=M}}|Is], Vs0) -> + Vs = Vs0#{{var,M}=>message}, + opt_ref_used_is(Is, Vs); +opt_ref_used_is([#b_set{op={bif,Bif},args=Args,dst=#b_var{name=B}}=I|Is], + Vs0) -> + S = case Bif of + '=:=' -> true; + '==' -> true; + _ -> none + end, + case S of + none -> + Vs = update_vars(I, Vs0), + opt_ref_used_is(Is, Vs); + Bool when is_boolean(Bool) -> + case is_ref_msg_comparison(Args, Vs0) of + true -> + Vs = Vs0#{B=>{is_ref,Bool}}, + opt_ref_used_is(Is, Vs); + false -> + opt_ref_used_is(Is, Vs0) + end + end; +opt_ref_used_is([#b_set{op=remove_message}|_], Vs) -> + case Vs of + #{ref_matched:=true} -> + used; + #{ref_matched:=false} -> + not_used + end; +opt_ref_used_is([#b_set{op=recv_next}|_], _Vs) -> + done; +opt_ref_used_is([#b_set{op=wait_timeout}|_], _Vs) -> + done; +opt_ref_used_is([#b_set{op=wait}|_], _Vs) -> + done; +opt_ref_used_is([#b_set{}=I|Is], Vs0) -> + Vs = update_vars(I, Vs0), + opt_ref_used_is(Is, Vs); +opt_ref_used_is([], Vs) -> Vs. + +opt_ref_used_last(#b_blk{last=Last}=Blk, Vs, Blocks) -> + case Last of + #b_br{bool=#b_var{name=Bool},succ=Succ,fail=Fail} -> + case Vs of + #{Bool:={is_ref,Matched}} -> + ref_used_in([{Succ,Vs#{ref_matched:=Matched}}, + {Fail,Vs#{ref_matched:=not Matched}}], + Blocks); + #{} -> + ref_used_in([{Succ,Vs},{Fail,Vs}], Blocks) + end; + _ -> + SuccVs = [{Succ,Vs} || Succ <- beam_ssa:successors(Blk)], + ref_used_in(SuccVs, Blocks) + end. + +ref_used_in([{L,Vs0}|Ls], Blocks) -> + case opt_ref_used_1(L, Vs0, Blocks) of + not_used -> + not_used; + used -> + case ref_used_in(Ls, Blocks) of + done -> used; + Result -> Result + end; + done -> ref_used_in(Ls, Blocks) + end; +ref_used_in([], _) -> done. + +update_vars(#b_set{args=Args,dst=#b_var{name=B}}, Vs) -> + Vars = [V || #b_var{name=V} <- Args], + All = all(fun(V) -> + Var = {var,V}, + case Vs of + #{Var:=message} -> true; + #{} -> false + end + end, Vars), + case All of + true -> Vs#{{var,B}=>message}; + false -> Vs + end. + +%% is_ref_msg_comparison(Args, Variables) -> true|false. +%% Return 'true' if Args denotes a comparison between the +%% reference and message or part of the message. + +is_ref_msg_comparison([#b_var{name=A1},#b_var{name=A2}], Vs) -> + V1 = {var,A1}, + V2 = {var,A2}, + case Vs of + #{V1:=ref,V2:=message} -> true; + #{V1:=message,V2:=ref} -> true; + #{} -> false + end; +is_ref_msg_comparison(_, _) -> false. diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl new file mode 100644 index 0000000000..e5f15da836 --- /dev/null +++ b/lib/compiler/src/beam_ssa_type.erl @@ -0,0 +1,1106 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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_ssa_type). +-export([opt/2]). + +-include("beam_ssa.hrl"). +-import(lists, [any/2,droplast/1,foldl/3,last/1,member/2, + reverse/1,search/2,sort/1]). + +-define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}). + +-record(d, {ds :: #{beam_ssa:var_name():=beam_ssa:b_set()}, + ls :: #{beam_ssa:label():=type_db()}}). + +-define(ATOM_SET_SIZE, 5). + +%% Records that represent type information. +-record(t_atom, {elements=any :: 'any' | [atom()]}). +-record(t_integer, {elements=any :: 'any' | {integer(),integer()}}). +-record(t_bs_match, {type :: type()}). +-record(t_tuple, {size=0 :: integer(), + exact=false :: boolean(), + elements=[] :: [any()] + }). + +-type type() :: 'any' | 'none' | + #t_atom{} | #t_integer{} | #t_bs_match{} | #t_tuple{} | + {'binary',pos_integer()} | 'cons' | 'float' | 'list' | 'map' | 'nil' |'number'. +-type type_db() :: #{beam_ssa:var_name():=type()}. + +-spec opt([{Label0,Block0}], Args) -> [{Label,Block}] when + Label0 :: beam_ssa:label(), + Block0 :: beam_ssa:b_blk(), + Args :: [beam_ssa:b_var()], + Label :: beam_ssa:label(), + Block :: beam_ssa:b_blk(). + +opt(Linear, Args) -> + Ts = maps:from_list([{V,any} || #b_var{name=V} <- Args]), + FakeCall = #b_set{op=call,args=[#b_remote{mod=#b_literal{val=unknown}, + name=#b_literal{val=unknown}, + arity=0}]}, + Defs = maps:from_list([{V,FakeCall#b_set{dst=Var}} || + #b_var{name=V}=Var <- Args]), + D = #d{ds=Defs,ls=#{0=>Ts}}, + opt_1(Linear, D). + +opt_1([{L,Blk}|Bs], #d{ls=Ls}=D) -> + case Ls of + #{L:=Ts} -> + opt_2(L, Blk, Bs, Ts, D); + #{} -> + %% This block is never reached. Discard it. + opt_1(Bs, D) + end; +opt_1([], _) -> []. + +opt_2(L, #b_blk{is=Is0}=Blk0, Bs, Ts, D0) -> + case Is0 of + [#b_set{op=call,dst=Dst, + args=[#b_remote{mod=#b_literal{val=Mod}, + name=#b_literal{val=Name}}=Rem|Args0]}=I0] -> + case erl_bifs:is_exit_bif(Mod, Name, length(Args0)) of + true -> + %% This call will never reach the successor block. + %% Rewrite the terminator to a 'ret', and remove + %% all type information for this label. That will + %% simplify the phi node in the former successor. + Args = [simplify_arg(Arg, Ts) || Arg <- Args0], + I = I0#b_set{args=[Rem|Args]}, + Ret = #b_ret{arg=Dst}, + Blk = Blk0#b_blk{is=[I],last=Ret}, + Ls = maps:remove(L, D0#d.ls), + D = D0#d{ls=Ls}, + [{L,Blk}|opt_1(Bs, D)]; + false -> + opt_3(L, Blk0, Bs, Ts, D0) + end; + _ -> + opt_3(L, Blk0, Bs, Ts, D0) + end. + +opt_3(L, #b_blk{is=Is0,last=Last0}=Blk0, Bs, Ts0, #d{ds=Ds0,ls=Ls0}=D0) -> + {Is,Ts,Ds} = opt_is(Is0, Ts0, Ds0, Ls0, []), + D1 = D0#d{ds=Ds}, + Last = opt_terminator(Last0, Ts, Ds), + D = update_successors(Last, Ts, D1), + Blk = Blk0#b_blk{is=Is,last=Last}, + [{L,Blk}|opt_1(Bs, D)]. + +opt_is([#b_set{op=phi,dst=#b_var{name=Dst},args=Args0}=I0|Is], Ts0, Ds0, Ls, Acc) -> + %% Simplify the phi node by removing all predecessor blocks that no + %% longer exists or no longer branches to this block. + Args = [P || {_,From}=P <- Args0, maps:is_key(From, Ls)], + I = I0#b_set{args=Args}, + Ts = update_types(I, Ts0, Ds0), + Ds = Ds0#{Dst=>I}, + opt_is(Is, Ts, Ds, Ls, [I|Acc]); +opt_is([#b_set{dst=#b_var{name=Dst}}=I0|Is], Ts0, Ds0, Ls, Acc) -> + I = simplify(I0, Ts0), + Ts = update_types(I, Ts0, Ds0), + Ds = Ds0#{Dst=>I}, + opt_is(Is, Ts, Ds, Ls, [I|Acc]); +opt_is([], Ts, Ds, _Ls, Acc) -> + {reverse(Acc),Ts,Ds}. + +simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I, Ts) -> + case t_tuple_size(get_type(Tuple, Ts)) of + {_,Size} when is_integer(Index), 1 =< Index, Index =< Size -> + I#b_set{op=get_tuple_element,args=[Tuple,#b_literal{val=Index-1}]}; + _ -> + I + end; +simplify(#b_set{op={bif,hd},args=[List]}=I, Ts) -> + case get_type(List, Ts) of + cons -> + I#b_set{op=get_hd}; + _ -> + I + end; +simplify(#b_set{op={bif,tl},args=[List]}=I, Ts) -> + case get_type(List, Ts) of + cons -> + I#b_set{op=get_tl}; + _ -> + I + end; +simplify(#b_set{op={bif,size},args=[Term]}=I, Ts) -> + case get_type(Term, Ts) of + #t_tuple{} -> + I#b_set{op={bif,tuple_size}}; + _ -> + I + end; +simplify(#b_set{op={bif,'=='},args=Args0}=I0, Ts) -> + Args = [simplify_arg(Arg, Ts) || Arg <- Args0], + I = I0#b_set{args=Args}, + Types = get_types(Args, Ts), + EqEq = case {meet(Types),join(Types)} of + {none,any} -> true; + {#t_integer{},#t_integer{}} -> true; + {float,float} -> true; + {{binary,_},_} -> true; + {#t_atom{},_} -> true; + {_,_} -> false + end, + case EqEq of + true -> + I#b_set{op={bif,'=:='}}; + false -> + I + end; +simplify(#b_set{op={bif,Op},args=Args0}=I0, Ts) -> + Args = [simplify_arg(Arg, Ts) || Arg <- Args0], + I = I0#b_set{args=Args}, + Types = get_types(Args, Ts), + case is_float_op(Op, Types) of + false -> + I; + true -> + AnnoArgs = [anno_float_arg(A) || A <- Types], + beam_ssa:add_anno(float_op, AnnoArgs, I) + end; +simplify(#b_set{op=wait_timeout,args=[Timeout0]}=I, Ts) -> + case simplify_arg(Timeout0, Ts) of + #b_literal{val=infinity} -> + I#b_set{op=wait,args=[]}; + Timeout -> + I#b_set{args=[Timeout]} + end; +simplify(#b_set{op=Op,args=Args0}=I, Ts) -> + Safe = case Op of + call -> true; + put_list -> true; + put_tuple -> true; + _ -> false + end, + case Safe of + true -> + Args = [simplify_arg(Arg, Ts) || Arg <- Args0], + I#b_set{args=Args}; + false -> + I + end. + +simplify_arg(#b_var{}=Arg, Ts) -> + Type = get_type(Arg, Ts), + case get_literal_from_type(Type) of + none -> Arg; + #b_literal{}=Lit -> Lit + end; +simplify_arg(Arg, _Ts) -> Arg. + +is_float_op('-', [float]) -> + true; +is_float_op('/', [_,_]) -> + true; +is_float_op(Op, [float,_Other]) -> + is_float_op_1(Op); +is_float_op(Op, [_Other,float]) -> + is_float_op_1(Op); +is_float_op(_, _) -> false. + +is_float_op_1('+') -> true; +is_float_op_1('-') -> true; +is_float_op_1('*') -> true; +is_float_op_1(_) -> false. + +anno_float_arg(float) -> float; +anno_float_arg(_) -> convert. + +opt_terminator(#b_br{bool=#b_literal{}}=Br, _Ts, _Ds) -> + Br; +opt_terminator(#b_br{bool=#b_var{name=V}=Var}=Br, Ts, Ds) -> + BoolType = get_type(Var, Ts), + case get_literal_from_type(BoolType) of + #b_literal{}=BoolLit -> + Br#b_br{bool=BoolLit}; + none -> + #{V:=Set} = Ds, + case Set of + #b_set{op={bif,'=:='},args=[Bool,#b_literal{val=true}]} -> + case t_is_boolean(get_type(Bool, Ts)) of + true -> + %% Bool =:= true ==> Bool + simplify_not(Br#b_br{bool=Bool}, Ts, Ds); + false -> + Br + end; + #b_set{} -> + simplify_not(Br, Ts, Ds) + end + end; +opt_terminator(#b_switch{arg=#b_literal{val=Val0}=Arg,fail=Fail,list=List}, + _Ts, _Ds) -> + {value,{_,L}} = search(fun({#b_literal{val=Val1},_}) -> + Val1 =:= Val0 + end, List ++ [{Arg,Fail}]), + #b_br{bool=#b_literal{val=true},succ=L,fail=L}; +opt_terminator(#b_switch{arg=V}=Sw0, Ts, Ds) -> + case get_literal_from_type(Ts) of + #b_literal{}=Lit -> + Sw = Sw0#b_switch{arg=Lit}, + opt_terminator(Sw, Ts, Ds); + none -> + case get_type(V, Ts) of + #t_integer{elements={_,_}=Range} -> + simplify_switch_int(Sw0, Range); + Type -> + case t_is_boolean(Type) of + true -> + case simplify_switch_bool(Sw0, Ts, Ds) of + #b_br{}=Br -> + opt_terminator(Br, Ts, Ds); + Sw -> + Sw + end; + false -> + Sw0 + end + end + end; +opt_terminator(#b_ret{}=Ret, _Ts, _Ds) -> + Ret. + +update_successors(#b_br{bool=#b_literal{val=false},fail=S}, Ts, D) -> + update_successor(S, Ts, D); +update_successors(#b_br{bool=#b_literal{val=true},succ=S}, Ts, D) -> + update_successor(S, Ts, D); +update_successors(#b_br{bool=#b_var{name=V},succ=Succ,fail=Fail}, Ts, D0) -> + D = update_successor(Fail, Ts#{V:=t_atom(false)}, D0), + SuccTs = infer_types(V, Ts, D0), + update_successor(Succ, SuccTs#{V:=t_atom(true)}, D); +update_successors(#b_switch{arg=#b_var{name=V},fail=Fail,list=List}, Ts, D0) -> + D = update_successor(Fail, Ts, D0), + foldl(fun({Val,S}, A) -> + T = get_type(Val, Ts), + update_successor(S, Ts#{V=>T}, A) + end, D, List); +update_successors(#b_ret{}, _Ts, D) -> D. + +update_successor(S, Ts0, #d{ls=Ls}=D) -> + case Ls of + #{S:=Ts1} -> + Ts = join_types(Ts0, Ts1), + D#d{ls=Ls#{S:=Ts}}; + #{} -> + D#d{ls=Ls#{S=>Ts0}} + end. + +update_types(#b_set{op=Op,dst=#b_var{name=Dst},args=Args}, Ts, Ds) -> + T = type(Op, Args, Ts, Ds), + Ts#{Dst=>T}. + +type(phi, Args, Ts, _Ds) -> + Types = [get_type(A, Ts) || {A,_} <- Args], + join(Types); +type({bif,'=:='}, [Same,Same], _Ts, _Ds) -> + t_atom(true); +type({bif,'=:='}, [_,_]=Args, Ts, _Ds) -> + case get_literals(Args, Ts) of + [#b_literal{val=Lit1},#b_literal{val=Lit2}] -> + t_atom(Lit1 =:= Lit2); + [_,_] -> + case meet(get_types(Args, Ts)) of + none -> t_atom(false); + _ -> t_boolean() + end + end; +type({bif,tuple_size}, [Src], Ts, _Ds) -> + case t_tuple_size(get_type(Src, Ts)) of + {exact,Size} -> + t_integer(Size); + _ -> + t_integer() + end; +type({bif,'band'}, Args, Ts, _Ds) -> + band_type(Args, Ts); +type({bif,Bif}, [Src]=Args, Ts, _Ds) -> + case get_type(Src, Ts) of + any -> + bif_type(Bif, Args); + Type -> + case will_succeed(Bif, Type) of + yes -> + t_atom(true); + no -> + t_atom(false); + maybe -> + bif_type(Bif, Args) + end + end; +type({bif,Bif}, Args, Ts, _Ds) -> + case bif_type(Bif, Args) of + number -> + arith_op_type(Args, Ts); + Type -> + Type + end; +type(bs_init, [#b_literal{val=Type}|Args], _Ts, _Ds) -> + case {Type,Args} of + {new,[_,#b_literal{val=Unit}]} -> + {binary,Unit}; + {append,[_,_,#b_literal{val=Unit}]} -> + {binary,Unit}; + {private_append,[_,_,#b_literal{val=Unit}]} -> + {binary,Unit} + end; +type(bs_extract, [Ctx], Ts, _Ds) -> + #t_bs_match{type=Type} = get_type(Ctx, Ts), + Type; +type(bs_match, Args, _Ts, _Ds) -> + #t_bs_match{type=bs_match_type(Args)}; +type(call, [#b_remote{mod=#b_literal{val=Mod}, + name=#b_literal{val=Name}}|Args], Ts, _Ds) -> + case {Mod,Name,Args} of + {erlang,setelement,[Pos,Tuple,_]} -> + case {get_type(Pos, Ts),get_type(Tuple, Ts)} of + {#t_integer{elements={MinIndex,_}},#t_tuple{}=T} + when MinIndex > 1 -> + %% First element is not updated. The result + %% will have the same type. + T; + {_,#t_tuple{}=T} -> + %% Position is 1 or unknown. May update the first + %% element of the tuple. + T#t_tuple{elements=[]}; + {#t_integer{elements={MinIndex,_}},_} -> + #t_tuple{size=MinIndex}; + {_,_} -> + #t_tuple{} + end; + {math,_,_} -> + case is_math_bif(Name, length(Args)) of + false -> any; + true -> float + end; + {_,_,_} -> + case erl_bifs:is_exit_bif(Mod, Name, length(Args)) of + true -> none; + false -> any + end + end; +type(get_tuple_element, [Tuple,#b_literal{val=0}], Ts, _Ds) -> + case get_type(Tuple, Ts) of + #t_tuple{elements=[First]} -> + get_type(#b_literal{val=First}, Ts); + #t_tuple{} -> + any + end; +type(is_nonempty_list, [Src], Ts, _Ds) -> + case get_type(Src, Ts) of + any -> + t_boolean(); + list -> + t_boolean(); + cons -> + t_atom(true); + _ -> + t_atom(false) + end; +type(is_tagged_tuple, [Src,#b_literal{val=Size},#b_literal{val=Tag}], Ts, _Ds) -> + case get_type(Src, Ts) of + #t_tuple{exact=true,size=Size,elements=[Tag]} -> + t_atom(true); + #t_tuple{exact=true,size=ActualSize,elements=[]} -> + if + Size =/= ActualSize -> + t_atom(false); + true -> + t_boolean() + end; + #t_tuple{exact=false} -> + t_boolean(); + any -> + t_boolean(); + _ -> + t_atom(false) + end; +type(put_list, _Args, _Ts, _Ds) -> + cons; +type(put_tuple, Args, _Ts, _Ds) -> + case Args of + [#b_literal{val=First}|_] -> + #t_tuple{exact=true,size=length(Args),elements=[First]}; + _ -> + #t_tuple{exact=true,size=length(Args)} + end; +type(succeeded, [#b_var{name=Src}], Ts, Ds) -> + case maps:get(Src, Ds) of + #b_set{op={bif,Bif},args=BifArgs} -> + Types = get_types(BifArgs, Ts), + case {Bif,Types} of + {byte_size,[{binary,_}]} -> + t_atom(true); + {bit_size,[{binary,_}]} -> + t_atom(true); + {map_size,[map]} -> + t_atom(true); + {'not',[Type]} -> + case t_is_boolean(Type) of + true -> t_atom(true); + false -> t_boolean() + end; + {size,[{binary,_}]} -> + t_atom(true); + {tuple_size,[#t_tuple{}]} -> + t_atom(true); + {_,_} -> + t_boolean() + end; + #b_set{op=get_hd} -> + t_atom(true); + #b_set{op=get_tl} -> + t_atom(true); + #b_set{op=get_tuple_element} -> + t_atom(true); + #b_set{op=wait} -> + t_atom(false); + #b_set{} -> + t_boolean() + end; +type(_, _, _, _) -> any. + +arith_op_type(Args, Ts) -> + Types = get_types(Args, Ts), + foldl(fun(#t_integer{}, unknown) -> t_integer(); + (#t_integer{}, number) -> number; + (#t_integer{}, float) -> float; + (#t_integer{}, #t_integer{}) -> t_integer(); + (float, unknown) -> float; + (float, #t_integer{}) -> float; + (float, number) -> float; + (number, unknown) -> number; + (number, #t_integer{}) -> number; + (number, float) -> float; + (any, _) -> number; + (_, any) -> number; + (Same, Same) -> Same; + (_, _) -> none + end, unknown, Types). + +%% will_succeed(TestOperation, Type) -> yes|no|maybe. +%% Test whether TestOperation applied to an argument of type Type +%% will succeed. Return yes, no, or maybe. +%% +%% Type is a type as described in the comment for verified_type/1 at +%% the very end of this file, but it will *never* be 'any'. + +will_succeed(is_atom, Type) -> + case Type of + #t_atom{} -> yes; + _ -> no + end; +will_succeed(is_binary, Type) -> + case Type of + {binary,U} when U rem 8 =:= 0 -> yes; + {binary,_} -> maybe; + _ -> no + end; +will_succeed(is_bitstring, Type) -> + case Type of + {binary,_} -> yes; + _ -> no + end; +will_succeed(is_boolean, Type) -> + case Type of + #t_atom{elements=any} -> + maybe; + #t_atom{elements=Es} -> + case t_is_boolean(Type) of + true -> + yes; + false -> + case any(fun is_boolean/1, Es) of + true -> maybe; + false -> no + end + end; + _ -> + no + end; +will_succeed(is_float, Type) -> + case Type of + float -> yes; + number -> maybe; + _ -> no + end; +will_succeed(is_integer, Type) -> + case Type of + #t_integer{} -> yes; + number -> maybe; + _ -> no + end; +will_succeed(is_list, Type) -> + case Type of + list -> yes; + cons -> yes; + nil -> yes; + _ -> no + end; +will_succeed(is_map, Type) -> + case Type of + map -> yes; + _ -> no + end; +will_succeed(is_number, Type) -> + case Type of + float -> yes; + #t_integer{} -> yes; + number -> yes; + _ -> no + end; +will_succeed(is_tuple, Type) -> + case Type of + #t_tuple{} -> yes; + _ -> no + end; +will_succeed(_, _) -> maybe. + + +band_type([#b_literal{val=Int},Other], Ts) when is_integer(Int) -> + band_type_1(Int, Other, Ts); +band_type([Other,#b_literal{val=Int}], Ts) when is_integer(Int) -> + band_type_1(Int, Other, Ts); +band_type([_,_], _) -> t_integer(). + +band_type_1(Int, OtherSrc, Ts) -> + Type = band_type_2(Int, 0), + OtherType = get_type(OtherSrc, Ts), + meet(Type, OtherType). + +band_type_2(N, Bits) when Bits < 64 -> + case 1 bsl Bits of + P when P =:= N + 1 -> + t_integer(0, N); + P when P > N + 1 -> + t_integer(); + _ -> + band_type_2(N, Bits+1) + end; +band_type_2(_, _) -> + %% Negative or large positive number. Give up. + t_integer(). + +bs_match_type([#b_literal{val=Type}|Args]) -> + bs_match_type(Type, Args). + +bs_match_type(binary, Args) -> + [_,_,_,#b_literal{val=U}] = Args, + {binary,U}; +bs_match_type(float, _) -> + float; +bs_match_type(integer, Args) -> + case Args of + [_, + #b_literal{val=Flags}, + #b_literal{val=Size}, + #b_literal{val=Unit}] when Size * Unit < 64 -> + NumBits = Size * Unit, + case member(unsigned, Flags) of + true -> + t_integer(0, (1 bsl NumBits)-1); + false -> + %% Signed integer. Don't bother. + t_integer() + end; + [_|_] -> + t_integer() + end; +bs_match_type(skip, _) -> + any; +bs_match_type(string, _) -> + any; +bs_match_type(utf8, _) -> + ?UNICODE_INT; +bs_match_type(utf16, _) -> + ?UNICODE_INT; +bs_match_type(utf32, _) -> + ?UNICODE_INT. + +simplify_switch_int(#b_switch{list=List0}=Sw, {Min,Max}) -> + List1 = sort(List0), + Vs = [V || {#b_literal{val=V},_} <- List1], + case eq_ranges(Vs, Min, Max) of + true -> + {_,LastL} = last(List1), + List = droplast(List1), + Sw#b_switch{fail=LastL,list=List}; + false -> + Sw + end. + +eq_ranges([H], H, H) -> true; +eq_ranges([H|T], H, Max) -> eq_ranges(T, H+1, Max); +eq_ranges(_, _, _) -> false. + +simplify_switch_bool(#b_switch{arg=B,list=List0}=Sw, Ts, Ds) -> + List = sort(List0), + case List of + [{#b_literal{val=false},Fail},{#b_literal{val=true},Succ}] -> + simplify_not(#b_br{bool=B,succ=Succ,fail=Fail}, Ts, Ds); + [_|_] -> + Sw + end. + +simplify_not(#b_br{bool=#b_var{name=V},succ=Succ,fail=Fail}=Br, Ts, Ds) -> + case Ds of + #{V:=#b_set{op={bif,'not'},args=[Bool]}} -> + case t_is_boolean(get_type(Bool, Ts)) of + true -> + Br#b_br{bool=Bool,succ=Fail,fail=Succ}; + false -> + Br + end; + #{} -> + Br + end. + +get_literals(Values, Ts) -> + [get_literal_from_type(get_type(Val, Ts)) || Val <- Values]. + +get_types(Values, Ts) -> + [get_type(Val, Ts) || Val <- Values]. + +-spec get_type(beam_ssa:value(), type_db()) -> type(). + +get_type(#b_var{name=V}, Ts) -> + #{V:=T} = Ts, + T; +get_type(#b_literal{val=Val}, _Ts) -> + if + is_atom(Val) -> + t_atom(Val); + is_float(Val) -> + float; + is_integer(Val) -> + t_integer(Val); + is_list(Val), Val =/= [] -> + cons; + is_map(Val) -> + map; + Val =:= {} -> + #t_tuple{exact=true}; + is_tuple(Val) -> + #t_tuple{exact=true,size=tuple_size(Val), + elements=[element(1, Val)]}; + Val =:= [] -> + nil; + true -> + any + end. + +infer_types(V, Ts, #d{ds=Ds}) -> + #{V:=#b_set{op=Op,args=Args}} = Ds, + Types = infer_type(Op, Args, Ds), + meet_types(Types, Ts). + +infer_type({bif,element}, [#b_literal{val=Pos},#b_var{name=Tuple}], _Ds) -> + if + is_integer(Pos), 1 =< Pos -> + [{Tuple,#t_tuple{size=Pos}}]; + true -> + [] + end; +infer_type({bif,'=:='}, [#b_var{name=Src},#b_literal{}=Lit], Ds) -> + Def = maps:get(Src, Ds), + Type = get_type(Lit, #{}), + [{Src,Type}|infer_tuple_size(Def, Lit) ++ + infer_first_element(Def, Lit)]; +infer_type({bif,Bif}, [#b_var{name=Src}]=Args, _Ds) -> + case inferred_bif_type(Bif, Args) of + any -> []; + T -> [{Src,T}] + end; +infer_type({bif,is_map_key}, [_,#b_var{name=Src}], _Ds) -> + [{Src,map}]; +infer_type({bif,map_get}, [_,#b_var{name=Src}], _Ds) -> + [{Src,map}]; +infer_type(bs_start_match, [#b_var{name=Bin}], _Ds) -> + [{Bin,{binary,1}}]; +infer_type(is_nonempty_list, [#b_var{name=Src}], _Ds) -> + [{Src,cons}]; +infer_type(is_tagged_tuple, [#b_var{name=Src},#b_literal{val=Size}, + #b_literal{val=Tag}], _Ds) -> + [{Src,#t_tuple{exact=true,size=Size,elements=[Tag]}}]; +infer_type(succeeded, [#b_var{name=Src}], Ds) -> + #b_set{op=Op,args=Args} = maps:get(Src, Ds), + infer_type(Op, Args, Ds); +infer_type(_Op, _Args, _Ds) -> + []. + +%% bif_type(Name, Args) -> Type +%% Return the return type for the guard BIF or operator Name with +%% arguments Args. +%% +%% Note that that the following BIFs are handle elsewhere: +%% +%% band/2 +%% tuple_size/1 + +bif_type(abs, [_]) -> number; +bif_type(bit_size, [_]) -> t_integer(); +bif_type(byte_size, [_]) -> t_integer(); +bif_type(ceil, [_]) -> t_integer(); +bif_type(float, [_]) -> float; +bif_type(floor, [_]) -> t_integer(); +bif_type(is_map_key, [_,_]) -> t_boolean(); +bif_type(length, [_]) -> t_integer(); +bif_type(map_size, [_]) -> t_integer(); +bif_type(round, [_]) -> t_integer(); +bif_type(size, [_]) -> t_integer(); +bif_type(trunc, [_]) -> t_integer(); +bif_type('bnot', [_]) -> t_integer(); +bif_type('bor', [_,_]) -> t_integer(); +bif_type('bsl', [_,_]) -> t_integer(); +bif_type('bsr', [_,_]) -> t_integer(); +bif_type('bxor', [_,_]) -> t_integer(); +bif_type('div', [_,_]) -> t_integer(); +bif_type('rem', [_,_]) -> t_integer(); +bif_type('/', [_,_]) -> float; +bif_type(Name, Args) -> + Arity = length(Args), + case erl_internal:new_type_test(Name, Arity) orelse + erl_internal:bool_op(Name, Arity) orelse + erl_internal:comp_op(Name, Arity) of + true -> + t_boolean(); + false -> + case erl_internal:arith_op(Name, Arity) of + true -> number; + false -> any + end + end. + +inferred_bif_type(is_atom, [_]) -> t_atom(); +inferred_bif_type(is_binary, [_]) -> {binary,8}; +inferred_bif_type(is_bitstring, [_]) -> {binary,1}; +inferred_bif_type(is_boolean, [_]) -> t_boolean(); +inferred_bif_type(is_float, [_]) -> float; +inferred_bif_type(is_integer, [_]) -> t_integer(); +inferred_bif_type(is_list, [_]) -> list; +inferred_bif_type(is_map, [_]) -> map; +inferred_bif_type(is_number, [_]) -> number; +inferred_bif_type(is_tuple, [_]) -> #t_tuple{}; +inferred_bif_type(abs, [_]) -> number; +inferred_bif_type(bit_size, [_]) -> {binary,1}; +inferred_bif_type(byte_size, [_]) -> {binary,1}; +inferred_bif_type(ceil, [_]) -> number; +inferred_bif_type(float, [_]) -> number; +inferred_bif_type(floor, [_]) -> number; +inferred_bif_type(round, [_]) -> number; +inferred_bif_type(trunc, [_]) -> number; +inferred_bif_type(tuple_size, [_]) -> #t_tuple{}; +inferred_bif_type(_, _) -> any. + +infer_tuple_size(#b_set{op={bif,tuple_size},args=[#b_var{name=Tuple}]}, + #b_literal{val=Size}) when is_integer(Size) -> + [{Tuple,#t_tuple{exact=true,size=Size}}]; +infer_tuple_size(_, _) -> []. + +infer_first_element(#b_set{op=get_tuple_element, + args=[#b_var{name=Tuple},#b_literal{val=0}]}, + #b_literal{val=First}) -> + [{Tuple,#t_tuple{size=1,elements=[First]}}]; +infer_first_element(_, _) -> []. + +is_math_bif(cos, 1) -> true; +is_math_bif(cosh, 1) -> true; +is_math_bif(sin, 1) -> true; +is_math_bif(sinh, 1) -> true; +is_math_bif(tan, 1) -> true; +is_math_bif(tanh, 1) -> true; +is_math_bif(acos, 1) -> true; +is_math_bif(acosh, 1) -> true; +is_math_bif(asin, 1) -> true; +is_math_bif(asinh, 1) -> true; +is_math_bif(atan, 1) -> true; +is_math_bif(atanh, 1) -> true; +is_math_bif(erf, 1) -> true; +is_math_bif(erfc, 1) -> true; +is_math_bif(exp, 1) -> true; +is_math_bif(log, 1) -> true; +is_math_bif(log2, 1) -> true; +is_math_bif(log10, 1) -> true; +is_math_bif(sqrt, 1) -> true; +is_math_bif(atan2, 2) -> true; +is_math_bif(pow, 2) -> true; +is_math_bif(ceil, 1) -> true; +is_math_bif(floor, 1) -> true; +is_math_bif(fmod, 2) -> true; +is_math_bif(pi, 0) -> true; +is_math_bif(_, _) -> false. + +join_types(Ts0, Ts1) -> + if + map_size(Ts0) < map_size(Ts1) -> + join_types_1(maps:keys(Ts0), Ts1, Ts0); + true -> + join_types_1(maps:keys(Ts1), Ts0, Ts1) + end. + +join_types_1([V|Vs], Ts0, Ts1) -> + case {Ts0,Ts1} of + {#{V:=Same},#{V:=Same}} -> + join_types_1(Vs, Ts0, Ts1); + {#{V:=T0},#{V:=T1}} -> + case join(T0, T1) of + T1 -> + join_types_1(Vs, Ts0, Ts1); + T -> + join_types_1(Vs, Ts0, Ts1#{V:=T}) + end; + {#{},#{V:=_}} -> + join_types_1(Vs, Ts0, Ts1) + end; +join_types_1([], Ts0, Ts1) -> + maps:merge(Ts0, Ts1). + +join([T1,T2|Ts]) -> + join([join(T1, T2)|Ts]); +join([T]) -> T. + +get_literal_from_type(#t_atom{elements=[Atom]}) -> + #b_literal{val=Atom}; +get_literal_from_type(#t_integer{elements={Int,Int}}) -> + #b_literal{val=Int}; +get_literal_from_type(nil) -> + #b_literal{val=[]}; +get_literal_from_type(_) -> none. + +t_atom() -> + #t_atom{elements=any}. + +t_atom(Atom) when is_atom(Atom) -> + #t_atom{elements=[Atom]}. + +t_boolean() -> + #t_atom{elements=[false,true]}. + +t_integer() -> + #t_integer{elements=any}. + +t_integer(Int) when is_integer(Int) -> + #t_integer{elements={Int,Int}}. + +t_integer(Min, Max) when is_integer(Min), is_integer(Max) -> + #t_integer{elements={Min,Max}}. + +t_is_boolean(#t_atom{elements=[F,T]}) -> + F =:= false andalso T =:= true; +t_is_boolean(#t_atom{elements=[B]}) -> + is_boolean(B); +t_is_boolean(_) -> false. + +t_tuple_size(#t_tuple{size=Size,exact=false}) -> + {at_least,Size}; +t_tuple_size(#t_tuple{size=Size,exact=true}) -> + {exact,Size}; +t_tuple_size(_) -> + none. + +%% join(Type1, Type2) -> Type +%% Return the "join" of Type1 and Type2. The join is a more general +%% type than Type1 and Type2. For example: +%% +%% join(#t_integer{elements=any}, #t_integer=elements={0,3}}) -> +%% #t_integer{} +%% +%% The join for two different types result in 'any', which is +%% the top element for our type lattice: +%% +%% join(#t_integer{}, map) -> any + +-spec join(type(), type()) -> type(). + +join(T, T) -> + verified_type(T); +join(none, T) -> + verified_type(T); +join(T, none) -> + verified_type(T); +join(any, _) -> any; +join(_, any) -> any; +join(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) -> + Set = ordsets:union(Set1, Set2), + case ordsets:size(Set) of + Size when Size =< ?ATOM_SET_SIZE -> + #t_atom{elements=Set}; + _Size -> + #t_atom{elements=any} + end; +join(#t_atom{elements=any}=T, #t_atom{elements=[_|_]}) -> T; +join(#t_atom{elements=[_|_]}, #t_atom{elements=any}=T) -> T; +join({binary,U1}, {binary,U2}) -> + {binary,gcd(U1, U2)}; +join(#t_integer{}, #t_integer{}) -> t_integer(); +join(list, cons) -> list; +join(cons, list) -> list; +join(nil, cons) -> list; +join(cons, nil) -> list; +join(nil, list) -> list; +join(list, nil) -> list; +join(#t_integer{}, float) -> number; +join(float, #t_integer{}) -> number; +join(#t_integer{}, number) -> number; +join(number, #t_integer{}) -> number; +join(float, number) -> number; +join(number, float) -> number; +join(#t_tuple{size=Sz,exact=Exact1}, #t_tuple{size=Sz,exact=Exact2}) -> + Exact = Exact1 and Exact2, + #t_tuple{size=Sz,exact=Exact}; +join(#t_tuple{size=Sz1}, #t_tuple{size=Sz2}) -> + #t_tuple{size=min(Sz1, Sz2)}; +join(_T1, _T2) -> + %%io:format("~p ~p\n", [_T1,_T2]), + any. + +gcd(A, B) -> + case A rem B of + 0 -> B; + X -> gcd(B, X) + end. + +meet_types([{V,T0}|Vs], Ts) -> + #{V:=T1} = Ts, + T = meet(T0, T1), + meet_types(Vs, Ts#{V:=T}); +meet_types([], Ts) -> Ts. + +meet([T1,T2|Ts]) -> + meet([meet(T1, T2)|Ts]); +meet([T]) -> T. + +%% meet(Type1, Type2) -> Type +%% Return the "meet" of Type1 and Type2. The meet is a narrower +%% type than Type1 and Type2. For example: +%% +%% meet(#t_integer{elements=any}, #t_integer{elements={0,3}}) -> +%% #t_integer{elements={0,3}} +%% +%% The meet for two different types result in 'none', which is +%% the bottom element for our type lattice: +%% +%% meet(#t_integer{}, map) -> none + +-spec meet(type(), type()) -> type(). + +meet(T, T) -> + verified_type(T); +meet(#t_atom{elements=[_|_]=Set1}, #t_atom{elements=[_|_]=Set2}) -> + case ordsets:intersection(Set1, Set2) of + [] -> + none; + [_|_]=Set -> + #t_atom{elements=Set} + end; +meet(#t_atom{elements=[_|_]}=T, #t_atom{elements=any}) -> + T; +meet(#t_atom{elements=any}, #t_atom{elements=[_|_]}=T) -> + T; +meet(#t_integer{elements={_,_}}=T, #t_integer{elements=any}) -> + T; +meet(#t_integer{elements=any}, #t_integer{elements={_,_}}=T) -> + T; +meet(#t_integer{elements={Min1,Max1}}, + #t_integer{elements={Min2,Max2}}) -> + #t_integer{elements={max(Min1, Min2),min(Max1, Max2)}}; +meet(#t_integer{}=T, number) -> T; +meet(float, number) -> float; +meet(#t_integer{}=T, number) -> T; +meet(float, number) -> float; +meet(number, #t_integer{}=T) -> T; +meet(#t_integer{}=T, number) -> T; +meet(number, float=T) -> T; +meet(float=T, number) -> T; +meet(list, cons) -> cons; +meet(list, nil) -> nil; +meet(cons, list) -> cons; +meet(nil, list) -> nil; +meet(#t_tuple{}=T1, #t_tuple{}=T2) -> + meet_tuples(T1, T2); +meet({binary,U1}, {binary,U2}) -> + {binary,max(U1, U2)}; +meet(any, T) -> + verified_type(T); +meet(T, any) -> + verified_type(T); +meet(_, _) -> + %% Inconsistent types. There will be an exception at runtime. + none. + +meet_tuples(#t_tuple{elements=[E1]}, #t_tuple{elements=[E2]}) + when E1 =/= E2 -> + none; +meet_tuples(#t_tuple{size=Sz1,exact=true}, + #t_tuple{size=Sz2,exact=true}) when Sz1 =/= Sz2 -> + none; +meet_tuples(#t_tuple{size=Sz1,exact=Ex1,elements=Es1}, + #t_tuple{size=Sz2,exact=Ex2,elements=Es2}) -> + Size = max(Sz1, Sz2), + Exact = Ex1 or Ex2, + Es = case {Es1,Es2} of + {[],[_|_]} -> Es2; + {[_|_],[]} -> Es1; + {_,_} -> Es1 + end, + #t_tuple{size=Size,exact=Exact,elements=Es}. + +%% verified_type(Type) -> Type +%% Returns the passed in type if it is one of the defined types. +%% Crashes if there is anything wrong with the type. +%% +%% Here are all possible types: +%% +%% any Any Erlang term (top element for the type lattice). +%% +%% #t_atom{} Any atom or some specific atoms. +%% {binary,Unit} Binary/bitstring aligned to unit Unit. +%% float Floating point number. +%% #t_integer{} Integer +%% list Empty or nonempty list. +%% map Map. +%% nil Empty list. +%% cons Cons (nonempty list). +%% number A number (float or integer). +%% #t_tuple{} Tuple. +%% +%% none No type (bottom element for the type lattice). + +-spec verified_type(T) -> T when + T :: type(). + +verified_type(any=T) -> T; +verified_type(none=T) -> T; +verified_type(#t_atom{elements=any}=T) -> T; +verified_type(#t_atom{elements=[_|_]}=T) -> T; +verified_type({binary,U}=T) when is_integer(U) -> T; +verified_type(#t_integer{elements=any}=T) -> T; +verified_type(#t_integer{elements={Min,Max}}=T) + when is_integer(Min), is_integer(Max) -> T; +verified_type(list=T) -> T; +verified_type(map=T) -> T; +verified_type(nil=T) -> T; +verified_type(cons=T) -> T; +verified_type(number=T) -> T; +verified_type(#t_tuple{}=T) -> T; +verified_type(float=T) -> T. diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl index af1e719206..686d314c2d 100644 --- a/lib/compiler/src/beam_utils.erl +++ b/lib/compiler/src/beam_utils.erl @@ -193,8 +193,7 @@ bif_to_test('=:=', [C,A], Fail) when ?is_const(C) -> bif_to_test('=:=', [_,_]=Ops, Fail) -> {test,is_eq_exact,Fail,Ops}; bif_to_test('=/=', [C,A], Fail) when ?is_const(C) -> {test,is_ne_exact,Fail,[A,C]}; -bif_to_test('=/=', [_,_]=Ops, Fail) -> {test,is_ne_exact,Fail,Ops}; -bif_to_test(is_record, [_,_,_]=Ops, Fail) -> {test,is_record,Fail,Ops}. +bif_to_test('=/=', [_,_]=Ops, Fail) -> {test,is_ne_exact,Fail,Ops}. %% is_pure_test({test,Op,Fail,Ops}) -> true|false. diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 5fdea23a26..3a835cfb2f 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -31,6 +31,9 @@ %% Erlc interface. -export([compile/3,compile_beam/3,compile_asm/3,compile_core/3]). +%% Utility functions for compiler passes. +-export([run_sub_passes/2]). + -export_type([option/0]). -include("erl_compile.hrl"). @@ -39,6 +42,8 @@ -import(lists, [member/2,reverse/1,reverse/2,keyfind/3,last/1, map/2,flatmap/2,foreach/2,foldr/3,any/2]). +-define(SUB_PASS_TIMES, compile__sub_pass_times). + %%---------------------------------------------------------------------- -type abstract_code() :: [erl_parse:abstract_form()]. @@ -64,6 +69,7 @@ -type err_ret() :: 'error' | {'error', errors(), warnings()}. -type comp_ret() :: mod_ret() | bin_ret() | err_ret(). + %%---------------------------------------------------------------------- %% @@ -143,6 +149,30 @@ noenv_output_generated(Opts) -> env_compiler_options() -> env_default_opts(). + +%%% +%%% Run sub passes from a compiler pass. +%%% + +-spec run_sub_passes([term()], term()) -> term(). + +run_sub_passes(Ps, St) -> + case get(?SUB_PASS_TIMES) of + undefined -> + Runner = fun(_Name, Run, S) -> Run(S) end, + run_sub_passes_1(Ps, Runner, St); + Times when is_list(Times) -> + Runner = fun(Name, Run, S0) -> + T1 = erlang:monotonic_time(), + S = Run(S0), + T2 = erlang:monotonic_time(), + put(?SUB_PASS_TIMES, + [{Name,T2-T1}|get(?SUB_PASS_TIMES)]), + S + end, + run_sub_passes_1(Ps, Runner, St) + end. + %% %% Local functions %% @@ -219,22 +249,24 @@ expand_opt(report, Os) -> expand_opt(return, Os) -> [return_errors,return_warnings|Os]; expand_opt(r16, Os) -> - [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os]; + expand_opt_before_21(Os); expand_opt(r17, Os) -> - [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os]; + expand_opt_before_21(Os); expand_opt(r18, Os) -> - [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os]; + expand_opt_before_21(Os); expand_opt(r19, Os) -> - [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os]; + expand_opt_before_21(Os); expand_opt(r20, Os) -> - [no_get_hd_tl,no_record_opt,no_utf8_atoms|Os]; + expand_opt_before_21(Os); +expand_opt(r21, Os) -> + Os; expand_opt({debug_info_key,_}=O, Os) -> [encrypt_debug_info,O|Os]; -expand_opt(no_float_opt, Os) -> - %%Turn off the entire type optimization pass. - [no_topt|Os]; expand_opt(O, Os) -> [O|Os]. +expand_opt_before_21(Os) -> + [no_get_hd_tl,no_ssa_opt_record,no_utf8_atoms|Os]. + %% format_error(ErrorDescriptor) -> string() -spec format_error(term()) -> iolist(). @@ -387,17 +419,57 @@ fold_comp([{Name,Pass}|Ps], Run, Code0, St0) -> end; fold_comp([], _Run, Code, St) -> {ok,Code,St}. +run_sub_passes_1([{Name,Run}|Ps], Runner, St0) + when is_atom(Name), is_function(Run, 1) -> + try Runner(Name, Run, St0) of + St -> + run_sub_passes_1(Ps, Runner, St) + catch + C:E:Stk -> + io:format("Sub pass ~s\n", [Name]), + erlang:raise(C, E, Stk) + end; +run_sub_passes_1([], _, St) -> St. + run_tc({Name,Fun}, Code, St) -> + put(?SUB_PASS_TIMES, []), T1 = erlang:monotonic_time(), Val = (catch Fun(Code, St)), T2 = erlang:monotonic_time(), - Elapsed = erlang:convert_time_unit(T2 - T1, native, millisecond), + Times = erase(?SUB_PASS_TIMES), + Elapsed = erlang:convert_time_unit(T2 - T1, native, microsecond), Mem0 = erts_debug:flat_size(Val)*erlang:system_info(wordsize), Mem = lists:flatten(io_lib:format("~.1f kB", [Mem0/1024])), io:format(" ~-30s: ~10.3f s ~12s\n", - [Name,Elapsed/1000,Mem]), + [Name,Elapsed/1000000,Mem]), + print_times(Times, Name), Val. +print_times(Times0, Name) -> + Fam0 = sofs:relation(Times0), + Fam1 = sofs:rel2fam(Fam0), + Fam2 = sofs:to_external(Fam1), + Fam3 = [{W,lists:sum(Times)} || {W,Times} <- Fam2], + Fam = reverse(lists:keysort(2, Fam3)), + Total = case lists:sum([T || {_,T} <- Fam]) of + 0 -> 1; + Total0 -> Total0 + end, + case Fam of + [] -> + ok; + [_|_] -> + io:format(" %% Sub passes of ~s from slowest to fastest:\n", [Name]), + print_times_1(Fam, Total) + end. + +print_times_1([{Name,T}|Ts], Total) -> + Elapsed = erlang:convert_time_unit(T, native, microsecond), + io:format(" ~-27s: ~10.3f s ~3w %\n", + [Name,Elapsed/1000000,round(100*T/Total)]), + print_times_1(Ts, Total); +print_times_1([], _Total) -> ok. + run_eprof({Name,Fun}, Code, Name, St) -> io:format("~p: Running eprof\n", [Name]), c:appcall(tools, eprof, start_profiling, [[self()]]), @@ -741,8 +813,20 @@ kernel_passes() -> ?pass(v3_kernel), {iff,dkern,{listing,"kernel"}}, {iff,'to_kernel',{done,"kernel"}}, - {pass,v3_codegen}, - {iff,dcg,{listing,"codegen"}} + {pass,beam_kernel_to_ssa}, + {iff,dssa,{listing,"ssa"}}, + {iff,ssalint,{pass,beam_ssa_lint}}, + {unless,no_ssa_opt,{pass,beam_ssa_opt}}, + {iff,dssaopt,{listing,"ssaopt"}}, + {iff,ssalint,{pass,beam_ssa_lint}}, + {unless,no_recv_opt,{pass,beam_ssa_recv}}, + {iff,drecv,{listing,"recv"}}, + {pass,beam_ssa_pre_codegen}, + {iff,dprecg,{listing,"precodegen"}}, + {iff,ssalint,{pass,beam_ssa_lint}}, + {pass,beam_ssa_codegen}, + {iff,dcg,{listing,"codegen"}}, + {iff,doldcg,{listing,"codegen"}} | asm_passes()]. asm_passes() -> @@ -1958,8 +2042,15 @@ pre_load() -> beam_except, beam_flatten, beam_jump, + beam_kernel_to_ssa, beam_opcodes, beam_peep, + beam_ssa, + beam_ssa_codegen, + beam_ssa_opt, + beam_ssa_pre_codegen, + beam_ssa_recv, + beam_ssa_type, beam_split, beam_trim, beam_utils, @@ -1980,7 +2071,6 @@ pre_load() -> sys_core_bsm, sys_core_dsetel, sys_core_fold, - v3_codegen, v3_core, v3_kernel], _ = code:ensure_modules_loaded(L), diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src index 9fa5a1c6ea..74529f7fef 100644 --- a/lib/compiler/src/compiler.app.src +++ b/lib/compiler/src/compiler.app.src @@ -33,9 +33,18 @@ beam_except, beam_flatten, beam_jump, + beam_kernel_to_ssa, beam_listing, beam_opcodes, beam_peep, + beam_ssa, + beam_ssa_codegen, + beam_ssa_lint, + beam_ssa_opt, + beam_ssa_pp, + beam_ssa_pre_codegen, + beam_ssa_recv, + beam_ssa_type, beam_split, beam_trim, beam_utils, @@ -61,7 +70,6 @@ sys_core_fold_lists, sys_core_inline, sys_pre_attributes, - v3_codegen, v3_core, v3_kernel, v3_kernel_pp diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl index aef0b6cc9f..fe8e252e5a 100644 --- a/lib/compiler/src/v3_kernel.erl +++ b/lib/compiler/src/v3_kernel.erl @@ -2043,9 +2043,6 @@ get_match(#k_cons{}, St0) -> get_match(#k_binary{}, St0) -> {[V]=Mes,St1} = new_vars(1, St0), {#k_binary{segs=V},Mes,St1}; -get_match(#k_bin_seg{size=#k_atom{val=all},next={k_bin_end,[]}}=Seg, St0) -> - {[S]=Vars,St1} = new_vars(1, St0), - {Seg#k_bin_seg{seg=S,next=[]},Vars,St1}; get_match(#k_bin_seg{}=Seg, St0) -> {[S,N0],St1} = new_vars(2, St0), N = set_kanno(N0, [no_usage]), @@ -2073,9 +2070,6 @@ new_clauses(Cs0, U, St) -> #k_cons{hd=H,tl=T} -> [H,T|As]; #k_tuple{es=Es} -> Es ++ As; #k_binary{segs=E} -> [E|As]; - #k_bin_seg{size=#k_atom{val=all}, - seg=S,next={k_bin_end,[]}} -> - [S|As]; #k_bin_seg{seg=S,next=N} -> [S,N|As]; #k_bin_int{next=N} -> @@ -2374,9 +2368,10 @@ uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0}, true -> {[#k_var{name=X}],#k_var{name=X}} = {Vs,B0}, %Assertion. #k_atom{val=false} = H0, %Assertion. - {A1,Bu,St1} = uexpr(A0, Br, St0), + {Avs,St1} = new_vars(length(Rs0), St0), + {A1,Bu,St} = uexpr(A0, {break,Avs}, St1), {#k_protected{anno=#k{us=Bu,ns=lit_list_vars(Rs0),a=A}, - arg=A1,ret=Rs0},Bu,St1}; + arg=A1,ret=Rs0,inner=Avs},Bu,St}; false -> {Avs,St1} = new_vars(length(Vs), St0), {A1,Au,St2} = ubody(A0, {break,Avs}, St1), diff --git a/lib/compiler/src/v3_kernel.hrl b/lib/compiler/src/v3_kernel.hrl index e6f0d3c1f7..e26360a6da 100644 --- a/lib/compiler/src/v3_kernel.hrl +++ b/lib/compiler/src/v3_kernel.hrl @@ -66,7 +66,7 @@ -record(k_receive_next, {anno=[]}). -record(k_try, {anno=[],arg,vars,body,evars,handler,ret=[]}). -record(k_try_enter, {anno=[],arg,vars,body,evars,handler}). --record(k_protected, {anno=[],arg,ret=[]}). +-record(k_protected, {anno=[],arg,ret=[],inner}). -record(k_catch, {anno=[],body,ret=[]}). -record(k_guard_match, {anno=[],vars,body,ret=[]}). diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index da5d207db9..2a5004aa4c 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -14,6 +14,7 @@ MODULES= \ beam_except_SUITE \ beam_jump_SUITE \ beam_reorder_SUITE \ + beam_ssa_SUITE \ beam_type_SUITE \ beam_utils_SUITE \ bif_SUITE \ @@ -52,6 +53,7 @@ NO_OPT= \ beam_except \ beam_jump \ beam_reorder \ + beam_ssa \ beam_type \ beam_utils \ bif \ @@ -75,6 +77,7 @@ INLINE= \ andor \ apply \ beam_block \ + beam_ssa \ beam_utils \ bif \ bs_bincomp \ @@ -124,7 +127,7 @@ RELSYSDIR = $(RELEASE_PATH)/compiler_test # ---------------------------------------------------- ERL_MAKE_FLAGS += -ERL_COMPILE_FLAGS += +clint +clint0 +ERL_COMPILE_FLAGS += +clint +clint0 +ssalint EBIN = . @@ -135,7 +138,8 @@ EBIN = . make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(INLINE_ERL_FILES) $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \ > $(EMAKEFILE) - $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt $(ERL_COMPILE_FLAGS) \ + $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt \ + +no_ssa_opt +no_recv_opt $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(NO_OPT_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_copt $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(POST_OPT_MODULES) >> $(EMAKEFILE) diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl new file mode 100644 index 0000000000..0356c99c5a --- /dev/null +++ b/lib/compiler/test/beam_ssa_SUITE.erl @@ -0,0 +1,290 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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_ssa_SUITE). + +-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, + init_per_group/2,end_per_group/2, + calls/1,tuple_matching/1,recv/1,maps/1]). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [{group,p}]. + +groups() -> + [{p,test_lib:parallel(), + [tuple_matching, + calls, + recv, + maps + ]}]. + +init_per_suite(Config) -> + test_lib:recompile(?MODULE), + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +calls(Config) -> + Ret = {return,value,Config}, + Ret = fun_call(fun(42) -> ok end, Ret), + Ret = apply_fun(fun(a, b) -> ok end, [a,b], Ret), + Ret = apply_mfa(test_lib, id, [anything], Ret), + {'EXIT',{badarg,_}} = (catch call_error()), + {'EXIT',{badarg,_}} = (catch call_error(42)), + 5 = start_it([erlang,length,1,2,3,4,5]), + ok. + +fun_call(Fun, X0) -> + X = id(X0), + Fun(42), + X. + +apply_fun(Fun, Args, X0) -> + X = id(X0), + apply(Fun, Args), + X. + +apply_mfa(Mod, Name, Args, X0) -> + X = id(X0), + apply(Mod, Name, Args), + X. + +call_error() -> + error(badarg), + ok. + +call_error(I) -> + <<I:(-8)>>, + ok. + +start_it([_|_]=MFA) -> + case MFA of + [M,F|Args] -> M:F(Args) + end. + +tuple_matching(_Config) -> + do_tuple_matching({tag,42}). + +do_tuple_matching(Arg) -> + Res = do_tuple_matching_1(Arg), + Res = do_tuple_matching_2(Arg), + Res = do_tuple_matching_3(Arg), + Res. + +do_tuple_matching_1({tag,V}) -> + {ok,V}. + +do_tuple_matching_2(Tuple) when is_tuple(Tuple) -> + Size = tuple_size(Tuple), + if + Size =:= 2 -> + {ok,element(2, Tuple)} + end. + +do_tuple_matching_3(Tuple) when is_tuple(Tuple) -> + Size = tuple_size(Tuple), + if + Size =:= 2 -> + 2 = id(Size), + {ok,element(2, Tuple)} + end. + +-record(reporter_state, {res,run_config}). +-record(run_config, {report_interval=0}). + +recv(_Config) -> + Parent = self(), + + %% Test sync_wait_mon/2. + Succ = fun() -> Parent ! {ack,self(),{result,42}} end, + {result,42} = sync_wait_mon(spawn_monitor(Succ), infinity), + + Down = fun() -> exit(down) end, + {error,down} = sync_wait_mon(spawn_monitor(Down), infinity), + + Exit = fun() -> + Self = self(), + spawn(fun() -> exit(Self, kill_me) end), + receive _ -> ok end + end, + {error,kill_me} = sync_wait_mon(spawn_monitor(Exit), infinity), + + Timeout = fun() -> receive _ -> ok end end, + {error,timeout} = sync_wait_mon(spawn_monitor(Timeout), 0), + + %% Test reporter_loop/1. + {a,Parent} = reporter_loop(#reporter_state{res={a,Parent}, + run_config=#run_config{}}), + + %% Test bad_sink/0. + bad_sink(), + + %% Test tricky_recv_1/0. + self() ! 1, + a = tricky_recv_1(), + self() ! 2, + b = tricky_recv_1(), + + %% Test tricky_recv_2/0. + self() ! 1, + {1,yes} = tricky_recv_2(), + self() ! 2, + {2,maybe} = tricky_recv_2(), + + %% Test 'receive after infinity' in try/catch. + Pid = spawn(fun recv_after_inf_in_try/0), + exit(Pid, done), + + %% Test tricky_recv_3(). + self() ! {{self(),r0},{1,42,"name"}}, + {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_3(), + self() ! {{self(),r1},{2,99,<<"data">>}}, + {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_3(), + + %% Test tricky_recv_4(). + self() ! {[self(),r0],{1,42,"name"}}, + {Parent,r0,[<<1:32,1:8,42:8>>,"name",0]} = tricky_recv_4(), + self() ! {[self(),r1],{2,99,<<"data">>}}, + {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_4(), + + ok. + +sync_wait_mon({Pid, Ref}, Timeout) -> + receive + {ack,Pid,Return} -> + erlang:demonitor(Ref, [flush]), + Return; + {'DOWN',Ref,_Type,Pid,Reason} -> + {error,Reason}; + {'EXIT',Pid,Reason} -> + erlang:demonitor(Ref, [flush]), + {error,Reason} + after Timeout -> + erlang:demonitor(Ref, [flush]), + exit(Pid, kill), + {error,timeout} + end. + +reporter_loop(State) -> + RC = State#reporter_state.run_config, + receive after RC#run_config.report_interval -> + State#reporter_state.res + end. + +bad_sink() -> + {ok,Pid} = my_spawn(self()), + %% The get_tuple_element instruction for the matching + %% above was sinked into the receive loop. That will + %% not work (and would be bad for performance if it + %% would work). + receive + {ok,Pid} -> + ok; + error -> + exit(failed) + end, + exit(Pid, kill). + +my_spawn(Parent) -> + Pid = spawn(fun() -> + Parent ! {ok,self()}, + receive _ -> ok end + end), + {ok,Pid}. + +tricky_recv_1() -> + receive + X=1 -> + id(42), + a; + X=2 -> + b + end, + case X of + 1 -> a; + 2 -> b + end. + +tricky_recv_2() -> + receive + X=1 -> + Y = case id(X) of + 1 -> yes; + _ -> no + end, + a; + X=2 -> + Y = maybe, + b + end, + {X,Y}. + +recv_after_inf_in_try() -> + try + %% Used to crash beam_kernel_to_ssa. + receive after infinity -> ok end + catch + _A:_B -> + receive after infinity -> ok end + end. + +tricky_recv_3() -> + {Pid, R, Request} = + receive + {{Pid0,R0}, {1, Proto0, Name0}} -> + {Pid0, R0, + [<<1:32, 1:8, Proto0:8>>,Name0,0]}; + {{Pid1,R1}, {2, Proto1, Data1}} -> + {Pid1, R1, + <<1:32, 2:8, Proto1:8, Data1/binary>>} + end, + id({Pid,R,Request}). + +tricky_recv_4() -> + {Pid, R, Request} = + receive + {[Pid0,R0], {1, Proto0, Name0}} -> + {Pid0, R0, + [<<1:32, 1:8, Proto0:8>>,Name0,0]}; + {[Pid1,R1], {2, Proto1, Data1}} -> + {Pid1, R1, + <<1:32, 2:8, Proto1:8, Data1/binary>>} + end, + id({Pid,R,Request}). + +maps(_Config) -> + {'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)), + ok. + +maps_1(K) -> + _ = id(42), + #{K:=V} = #{}, + V. + +%% The identity function. +id(I) -> I. diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl index 061076b3ff..caf43dad40 100644 --- a/lib/compiler/test/beam_type_SUITE.erl +++ b/lib/compiler/test/beam_type_SUITE.erl @@ -23,7 +23,8 @@ init_per_group/2,end_per_group/2, integers/1,coverage/1,booleans/1,setelement/1,cons/1, tuple/1,record_float/1,binary_float/1,float_compare/1, - arity_checks/1,elixir_binaries/1,find_best/1]). + arity_checks/1,elixir_binaries/1,find_best/1, + test_size/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -43,7 +44,8 @@ groups() -> float_compare, arity_checks, elixir_binaries, - find_best + find_best, + test_size ]}]. init_per_suite(Config) -> @@ -177,11 +179,50 @@ setelement(_Config) -> cons(_Config) -> [did] = cons(assigned, did), + + true = cons_is_empty_list([]), + false = cons_is_empty_list([a]), + + false = cons_not(true), + true = cons_not(false), + + {$a,"bc"} = cons_hdtl(true), + {$d,"ef"} = cons_hdtl(false), ok. cons(assigned, Instrument) -> [Instrument] = [did]. +cons_is_empty_list(L) -> + Cons = case L of + [] -> "true"; + _ -> "false" + end, + id(1), + case Cons of + "true" -> true; + "false" -> false + end. + +cons_not(B) -> + Cons = case B of + true -> "true"; + false -> "false" + end, + id(1), + case Cons of + "true" -> false; + "false" -> true + end. + +cons_hdtl(B) -> + Cons = case B of + true -> "abc"; + false -> "def" + end, + id(1), + {id(hd(Cons)),id(tl(Cons))}. + tuple(_Config) -> {'EXIT',{{badmatch,{necessary}},_}} = (catch do_tuple()), ok. @@ -320,6 +361,15 @@ find_best([], <<"a">>) -> find_best([], nil) -> {error,<<"should not get here">>}. +test_size(Config) -> + 2 = do_test_size({a,b}), + 4 = do_test_size(<<42:32>>), + ok. + +do_test_size(Term) when is_tuple(Term) -> + size(Term); +do_test_size(Term) when is_binary(Term) -> + size(Term). id(I) -> I. diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index 7814738449..b4277f0705 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -248,6 +248,12 @@ bin_tail(Config) when is_list(Config) -> ok = bin_tail_e(<<2:2,1:1,1:5,42:64>>), error = bin_tail_e(<<3:2,1:1,1:5,42:64>>), error = bin_tail_e(<<>>), + + MD5 = erlang:md5(<<42>>), + <<"abc">> = bin_tail_f(<<MD5/binary,"abc">>, MD5, 3), + error = bin_tail_f(<<MD5/binary,"abc">>, MD5, 999), + {'EXIT',{_,_}} = (catch bin_tail_f(<<0:16/unit:8>>, MD5, 0)), + ok. bin_tail_c(Bin, Offset) -> @@ -304,6 +310,14 @@ bin_tail_e_var(Bin) -> <<2:2,_:1,1:5,Tail/binary>> -> Tail; _ -> error end. + +bin_tail_f(Bin, MD5, Size) -> + case Bin of + <<MD5:16/binary, Tail:Size/binary>> -> + Tail; + <<MD5:16/binary, _/binary>> -> + error + end. save_restore(Config) when is_list(Config) -> 0 = save_restore_1(<<0:2,42:6>>), @@ -455,6 +469,15 @@ unit(Config) when is_list(Config) -> 127 = peek7(<<127:7>>), 100 = peek7(<<100:7,19:7>>), fc(peek7, [<<1,2>>], catch peek7(<<1,2>>)), + + 1 = unit_opt(1, -1), + 8 = unit_opt(8, -1), + + <<1:32,"abc">> = unit_opt_2(<<1:32,"abc">>), + <<"def">> = unit_opt_2(<<2:32,"def">>), + {'EXIT',_} = (catch unit_opt_2(<<1:32,33:7>>)), + {'EXIT',_} = (catch unit_opt_2(<<2:32,55:7>>)), + ok. peek1(<<B:8,_/bitstring>>) -> B. @@ -465,6 +488,27 @@ peek8(<<B:8,_/binary>>) -> B. peek16(<<B:16,_/binary-unit:16>>) -> B. +unit_opt(U, X) -> + %% Cover type analysis in beam_ssa_type. + Bin = case U of + 1 -> <<X:7>>; + 8 -> <<X>> + end, + %% The type of Bin will be set to {binary,gcd(1, 8)}. + case Bin of + <<_/binary-unit:8>> -> 8; + <<_/binary-unit:1>> -> 1 + end. + +unit_opt_2(<<St:32,KO/binary>> = Bin0) -> + Bin = if + St =:= 1 -> + Bin0; + St =:= 2 -> + <<KO/binary>> + end, + id(Bin). + shared_sub_bins(Config) when is_list(Config) -> {15,[<<>>,<<5>>,<<4,5>>,<<3,4,5>>,<<2,3,4,5>>]} = sum(<<1,2,3,4,5>>, [], 0), ok. diff --git a/lib/compiler/test/compilation_SUITE.erl b/lib/compiler/test/compilation_SUITE.erl index 3ba3ce7cdf..def7a60f45 100644 --- a/lib/compiler/test/compilation_SUITE.erl +++ b/lib/compiler/test/compilation_SUITE.erl @@ -170,7 +170,7 @@ try_it(Module, Conf) -> atom_to_list(Module)), Out = proplists:get_value(priv_dir,Conf), io:format("Compiling: ~s\n", [Src]), - CompRc0 = compile:file(Src, [clint0,clint,{outdir,Out},report, + CompRc0 = compile:file(Src, [clint0,clint,ssalint,{outdir,Out},report, bin_opt_info|OtherOpts]), io:format("Result: ~p\n",[CompRc0]), {ok,_Mod} = CompRc0, @@ -189,7 +189,7 @@ try_it(Module, Conf) -> ct:timetrap(Timetrap), io:format("Compiling (with old inliner): ~s\n", [Src]), - CompRc2 = compile:file(Src, [clint, + CompRc2 = compile:file(Src, [clint,ssalint, {outdir,Out},report,bin_opt_info, {inline,1000}|OtherOpts]), io:format("Result: ~p\n",[CompRc2]), @@ -355,7 +355,7 @@ compile_compiler(Files, OutDir, Version, InlineOpts) -> io:format("~ts", [code:which(compile)]), io:format("Compiling ~s into ~ts", [Version,OutDir]), Opts = [report, - clint0,clint, + clint0,clint,ssalint, bin_opt_info, {outdir,OutDir}, {d,'COMPILER_VSN',"\""++Version++"\""}, diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 8056982d8e..85f0b7dc46 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -385,6 +385,9 @@ do_file_listings(DataDir, PrivDir, [File|Files]) -> do_listing(Simple, TargetDir, dcbsm, ".core_bsm"), do_listing(Simple, TargetDir, dsetel, ".dsetel"), do_listing(Simple, TargetDir, dkern, ".kernel"), + do_listing(Simple, TargetDir, dssa, ".ssa"), + do_listing(Simple, TargetDir, dssaopt, ".ssaopt"), + do_listing(Simple, TargetDir, dprecg, ".precodegen"), do_listing(Simple, TargetDir, dcg, ".codegen"), do_listing(Simple, TargetDir, dblk, ".block"), do_listing(Simple, TargetDir, dexcept, ".except"), @@ -423,6 +426,9 @@ listings_big(Config) when is_list(Config) -> do_listing(Big, TargetDir, 'E'), do_listing(Big, TargetDir, 'P'), do_listing(Big, TargetDir, dkern, ".kernel"), + do_listing(Big, TargetDir, dssa, ".ssa"), + do_listing(Big, TargetDir, dssaopt, ".ssaopt"), + do_listing(Big, TargetDir, dprecg, ".precodegen"), do_listing(Big, TargetDir, to_dis, ".dis"), TargetNoext = filename:rootname(Target, code:objfile_extension()), @@ -919,7 +925,7 @@ do_core_pp_1(M, A, Outdir) -> ok = file:delete(CoreFile), %% Compile as usual (including optimizations). - compile_forms(M, Core, [clint,from_core,binary]), + compile_forms(M, Core, [clint,ssalint,from_core,binary]), %% Don't optimize to test that we are not dependent %% on the Core Erlang optmimization passes. @@ -928,7 +934,7 @@ do_core_pp_1(M, A, Outdir) -> %% records; if sys_core_fold was run it would fix %% that; if sys_core_fold was not run v3_kernel would %% crash.) - compile_forms(M, Core, [clint,from_core,no_copt,binary]), + compile_forms(M, Core, [clint,ssalint,from_core,no_copt,binary]), ok. @@ -1241,21 +1247,14 @@ do_opt_guards_fun([_|Is]) -> do_opt_guards_fun(Is); do_opt_guards_fun([]) -> []. -is_exception(bs_match_SUITE, {matching_and_andalso_2,2}) -> true; -is_exception(bs_match_SUITE, {matching_and_andalso_3,2}) -> true; is_exception(guard_SUITE, {'-complex_not/1-fun-4-',1}) -> true; is_exception(guard_SUITE, {'-complex_not/1-fun-5-',1}) -> true; -is_exception(guard_SUITE, {basic_andalso_orelse,1}) -> true; is_exception(guard_SUITE, {bad_guards,1}) -> true; is_exception(guard_SUITE, {bad_guards_2,2}) -> true; is_exception(guard_SUITE, {bad_guards_3,2}) -> true; -is_exception(guard_SUITE, {cqlc,4}) -> true; is_exception(guard_SUITE, {csemi7,3}) -> true; -is_exception(guard_SUITE, {misc,1}) -> true; is_exception(guard_SUITE, {nested_not_2b,4}) -> true; is_exception(guard_SUITE, {tricky_1,2}) -> true; -is_exception(map_SUITE, {map_guard_update,2}) -> true; -is_exception(map_SUITE, {map_guard_update_variables,3}) -> true; is_exception(_, _) -> false. sys_pre_attributes(Config) -> @@ -1477,18 +1476,18 @@ bc_options(Config) -> 101 = highest_opcode(DataDir, small_float, [no_get_hd_tl,no_line_info]), 103 = highest_opcode(DataDir, big, - [no_get_hd_tl,no_record_opt, + [no_get_hd_tl,no_ssa_opt_record, no_line_info,no_stack_trimming]), 125 = highest_opcode(DataDir, small_float, - [no_get_hd_tl,no_line_info,no_float_opt]), + [no_get_hd_tl,no_line_info,no_ssa_opt_float]), 132 = highest_opcode(DataDir, small, - [no_get_hd_tl,no_record_opt,no_float_opt,no_line_info]), + [no_get_hd_tl,no_ssa_opt_record,no_ssa_opt_float,no_line_info]), - 136 = highest_opcode(DataDir, big, [no_get_hd_tl,no_record_opt,no_line_info]), + 136 = highest_opcode(DataDir, big, [no_get_hd_tl,no_ssa_opt_record,no_line_info]), - 153 = highest_opcode(DataDir, big, [no_get_hd_tl,no_record_opt]), + 153 = highest_opcode(DataDir, big, [no_get_hd_tl,no_ssa_opt_record]), 153 = highest_opcode(DataDir, big, [r16]), 153 = highest_opcode(DataDir, big, [r17]), 153 = highest_opcode(DataDir, big, [r18]), diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index 30d145582a..68bc5c6e2f 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -373,7 +373,7 @@ unused_multiple_values_error(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), Dir = test_lib:get_data_dir(Config), Core = filename:join(Dir, "unused_multiple_values_error"), - Opts = [no_copt,clint,return,from_core,{outdir,PrivDir} + Opts = [no_copt,clint,ssalint,return,from_core,{outdir,PrivDir} |test_lib:opt_opts(?MODULE)], {error,[{unused_multiple_values_error, [{none,core_lint,{return_mismatch,{hello,1}}}]}], diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index 99dc06b525..73a8dc0fda 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -1788,6 +1788,11 @@ t_tuple_size(Config) when is_list(Config) -> 14 = Mod:t({1,2,3,4}), _ = code:delete(Mod), _ = code:purge(Mod), + + good_ip({1,2,3,4}), + good_ip({1,2,3,4,5,6,7,8}), + error = validate_ip({42,11}), + error = validate_ip(atom), ok. @@ -1805,6 +1810,16 @@ ludicrous_tuple_size(T) when tuple_size(T) =:= 16#FFFFFFFFFFFFFFFF -> ok; ludicrous_tuple_size(_) -> error. +good_ip(IP) -> + IP = validate_ip(IP). + +validate_ip(Value) when is_tuple(Value) andalso + ((size(Value) =:= 4) orelse (size(Value) =:= 8)) -> + %% size/1 (converted to tuple_size) used more than once. + Value; +validate_ip(_) -> + error. + %% %% The binary_part/2,3 guard BIFs %% diff --git a/lib/compiler/test/inline_SUITE.erl b/lib/compiler/test/inline_SUITE.erl index fdf2fe88b4..e03769012b 100644 --- a/lib/compiler/test/inline_SUITE.erl +++ b/lib/compiler/test/inline_SUITE.erl @@ -96,7 +96,8 @@ try_inline(Mod, Config) -> %% Normal compilation. io:format("Compiling: ~s\n", [Src]), - {ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info,clint]), + {ok,Mod} = compile:file(Src, [{outdir,Out},report, + bin_opt_info,clint,ssalint]), ct:timetrap({minutes,10}), NormalResult = rpc:call(Node, ?MODULE, load_and_call, [Out,Mod]), @@ -104,7 +105,7 @@ try_inline(Mod, Config) -> %% Inlining. io:format("Compiling with old inliner: ~s\n", [Src]), {ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info, - {inline,1000},clint]), + {inline,1000},clint,ssalint]), %% Run inlined code. ct:timetrap({minutes,10}), @@ -117,7 +118,7 @@ try_inline(Mod, Config) -> %% Inlining. io:format("Compiling with new inliner: ~s\n", [Src]), {ok,Mod} = compile:file(Src, [{outdir,Out},report, - bin_opt_info,inline,clint]), + bin_opt_info,inline,clint,ssalint]), %% Run inlined code. ct:timetrap({minutes,10}), @@ -351,7 +352,8 @@ otp_7223_2({a}) -> coverage(Config) when is_list(Config) -> Mod = bsdecode, Src = filename:join(proplists:get_value(data_dir, Config), Mod), - {ok,Mod,_} = compile:file(Src, [binary,report,{inline,0},clint]), + {ok,Mod,_} = compile:file(Src, [binary,report,{inline,0}, + clint,ssalint]), {ok,Mod,_} = compile:file(Src, [binary,report,{inline,20}, - verbose,clint]), + verbose,clint,ssalint]), ok. diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl index 497cbad636..beae5eb618 100644 --- a/lib/compiler/test/misc_SUITE.erl +++ b/lib/compiler/test/misc_SUITE.erl @@ -171,7 +171,7 @@ silly_coverage(Config) when is_list(Config) -> expect_error(fun() -> sys_core_dsetel:module(BadCoreErlang, []) end), expect_error(fun() -> v3_kernel:module(BadCoreErlang, []) end), - %% v3_codegen + %% beam_kernel_to_ssa BadKernel = {k_mdef,[],?MODULE, [{foo,0}], [], @@ -179,7 +179,31 @@ silly_coverage(Config) when is_list(Config) -> {k,[],[],[]}, f,0,[], seriously_bad_body}]}, - expect_error(fun() -> v3_codegen:module(BadKernel, []) end), + expect_error(fun() -> beam_kernel_to_ssa:module(BadKernel, []) end), + + %% beam_ssa_lint + %% beam_ssa_recv + %% beam_ssa_pre_codegen + %% beam_ssa_opt + %% beam_ssa_codegen + BadSSA = {b_module,#{},a,b,c, + [{b_function,#{func_info=>{mod,foo,0}},args,bad_blocks,0}]}, + expect_error(fun() -> beam_ssa_lint:module(BadSSA, []) end), + expect_error(fun() -> beam_ssa_recv:module(BadSSA, []) end), + expect_error(fun() -> beam_ssa_pre_codegen:module(BadSSA, []) end), + expect_error(fun() -> beam_ssa_opt:module(BadSSA, []) end), + expect_error(fun() -> beam_ssa_codegen:module(BadSSA, []) end), + + %% beam_ssa_lint, beam_ssa_pp + {error,[{_,Errors}]} = beam_ssa_lint:module(bad_ssa_lint_input(), []), + _ = [io:put_chars(Mod:format_error(Reason)) || + {Mod,Reason} <- Errors], + + %% Cover printing of annotations in beam_ssa_pp + PPAnno = #{func_info=>{mod,foo,0},other_anno=>value,map_anno=>#{k=>v}}, + PPBlocks = #{0=>{b_blk,#{},[],{b_ret,#{},{b_literal,42}}}}, + PP = {b_function,PPAnno,[],PPBlocks,0}, + io:put_chars(beam_ssa_pp:format_function(PP)), %% beam_a BeamAInput = {?MODULE,[{foo,0}],[], @@ -268,6 +292,31 @@ silly_coverage(Config) when is_list(Config) -> ok. +bad_ssa_lint_input() -> + {b_module,#{},t, + [{foobar,1},{module_info,0},{module_info,1}], + [], + [{b_function, + #{func_info => {t,foobar,1},location => {"t.erl",4}}, + [{b_var,0}], + #{0 => {b_blk,#{},[],{b_ret,#{},{b_var,'@undefined_var'}}}}, + 3}, + {b_function, + #{func_info => {t,module_info,0}}, + [], + #{0 => + {b_blk,#{}, + [{b_set,#{}, + {b_var,{'@ssa_ret',3}}, + call, + [{b_remote, + {b_literal,erlang}, + {b_literal,get_module_info}, + 1}, + {b_var,'@unknown_variable'}]}], + {b_ret,#{},{b_var,{'@ssa_ret',3}}}}}, + 4}]}. + expect_error(Fun) -> try Fun() of Any -> diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/no_5.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/no_5.erl new file mode 100644 index 0000000000..4fbde3a83d --- /dev/null +++ b/lib/compiler/test/receive_SUITE_data/ref_opt/no_5.erl @@ -0,0 +1,38 @@ +-module(no_5). +-compile([export_all,nowarn_export_all]). + +?MODULE() -> + ok. + +%% Nested receives were not handled properly. + +confusing_recv_mark(Pid) -> + Ref = make_ref(), + %% There would be a recv_mark here. + MRef = erlang:monitor(process, Pid), + receive + Ref -> + %% And a recv_set here. + receive + MRef -> gurka + end; + MRef -> + gaffel + end. + +%% The optimization could potentially be improved to +%% handle matching of multiple refs, like this: + +proper_recv_mark(Pid) -> + %% Place the recv_mark before the creation of both refs. + Ref = make_ref(), + MRef = erlang:monitor(process, Pid), + %% Place the recv_set here. + receive + Ref -> + receive + MRef -> gurka + end; + MRef -> + gaffel + end. diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.S b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.S deleted file mode 100644 index fd14228135..0000000000 --- a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.S +++ /dev/null @@ -1,71 +0,0 @@ -{module, yes_14}. %% version = 0 - -{exports, [{f,2},{module_info,0},{module_info,1},{yes_14,0}]}. - -{attributes, []}. - -{labels, 12}. - - -{function, yes_14, 0, 2}. - {label,1}. - {func_info,{atom,yes_14},{atom,yes_14},0}. - {label,2}. - {move,{atom,ok},{x,0}}. - return. - - -{function, f, 2, 4}. - {label,3}. - {func_info,{atom,yes_14},{atom,f},2}. - {label,4}. - {allocate_heap,2,3,2}. - {move,{x,0},{y,1}}. - {put_tuple,2,{y,0}}. - {put,{atom,data}}. - {put,{x,1}}. - {call_ext,0,{extfunc,erlang,make_ref,0}}. % Ref in [x0] - {test_heap,4,1}. - {put_tuple,3,{x,1}}. - {put,{atom,request}}. - {put,{x,0}}. - {put,{y,0}}. - {move,{x,0},{y,0}}. % Ref in [x0,y0] - {move,{y,1},{x,0}}. % Ref in [y0] - {kill,{y,1}}. - send. - {move,{y,0},{x,0}}. % Ref in [x0,y0] - {move,{x,0},{y,1}}. % Ref in [x0,y0,y1] - {label,5}. - {loop_rec,{f,7},{x,0}}. % Ref in [y0,y1] - {test,is_tuple,{f,6},[{x,0}]}. - {test,test_arity,{f,6},[{x,0},2]}. - {get_tuple_element,{x,0},0,{x,1}}. - {get_tuple_element,{x,0},1,{x,2}}. - {test,is_eq_exact,{f,6},[{x,1},{atom,reply}]}. - {test,is_eq_exact,{f,6},[{x,2},{y,1}]}. - remove_message. - {move,{atom,ok},{x,0}}. - {deallocate,2}. - return. - {label,6}. - {loop_rec_end,{f,5}}. - {label,7}. - {wait,{f,5}}. - - -{function, module_info, 0, 9}. - {label,8}. - {func_info,{atom,yes_14},{atom,module_info},0}. - {label,9}. - {move,{atom,yes_14},{x,0}}. - {call_ext_only,1,{extfunc,erlang,get_module_info,1}}. - - -{function, module_info, 1, 11}. - {label,10}. - {func_info,{atom,yes_14},{atom,module_info},1}. - {label,11}. - {move,{x,0},{x,1}}. - {move,{atom,yes_14},{x,0}}. - {call_ext_only,2,{extfunc,erlang,get_module_info,2}}. diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.erl new file mode 100644 index 0000000000..aa47c02af9 --- /dev/null +++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_14.erl @@ -0,0 +1,27 @@ +-module(yes_14). +-compile(export_all). + +?MODULE() -> + ok. + +do_call(Process, Request) -> + Mref = erlang:monitor(process, Process), + Process ! Request, + Local = case node(Process) of + Node when Node =:= node() -> true; + _Node -> false + end, + id(Local), + receive + {X,Y,Z} when Mref =/= X, Z =:= 42, Mref =:= Y -> + error; + {X,Y,_} when Mref =/= X, Mref =:= Y -> + error; + {Mref, Reply} -> + erlang:demonitor(Mref, [flush]), + {ok, Reply}; + {'DOWN', Mref, _, _, _} -> + error + end. + +id(I) -> I. diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index 8954a9f5fb..c6baa611ec 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -72,11 +72,9 @@ opt_opts(Mod) -> {options,Opts} = lists:keyfind(options, 1, Comp), lists:filter(fun(no_copt) -> true; (no_postopt) -> true; - (no_float_opt) -> true; - (no_new_funs) -> true; - (no_new_binaries) -> true; - (no_new_apply) -> true; - (no_gc_bifs) -> true; + (no_ssa_opt) -> true; + (no_recv_opt) -> true; + (no_ssa_float) -> true; (no_stack_trimming) -> true; (debug_info) -> true; (inline) -> true; |