diff options
Diffstat (limited to 'lib/hipe/icode')
27 files changed, 17323 insertions, 0 deletions
diff --git a/lib/hipe/icode/Makefile b/lib/hipe/icode/Makefile new file mode 100644 index 0000000000..de37c4e4c4 --- /dev/null +++ b/lib/hipe/icode/Makefile @@ -0,0 +1,144 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2001-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# + +ifndef EBIN +EBIN = ../ebin +endif + +ifndef DOCS +DOCS = ../doc +endif + +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(HIPE_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/hipe-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +ifdef HIPE_ENABLED +HIPE_MODULES = hipe_icode_heap_test +else +HIPE_MODULES = +endif + +DOC_MODULES = hipe_beam_to_icode \ + hipe_icode hipe_icode_bincomp \ + hipe_icode_callgraph hipe_icode_cfg hipe_icode_coordinator \ + hipe_icode_fp \ + hipe_icode_exceptions \ + hipe_icode_inline_bifs hipe_icode_instruction_counter \ + hipe_icode_liveness \ + hipe_icode_pp hipe_icode_primops \ + hipe_icode_range \ + hipe_icode_split_arith \ + hipe_icode_ssa hipe_icode_ssa_const_prop \ + hipe_icode_ssa_copy_prop hipe_icode_ssa_struct_reuse \ + hipe_icode_type $(HIPE_MODULES) + +MODULES = $(DOC_MODULES) hipe_icode_ebb hipe_icode_mulret + +HRL_FILES=hipe_icode.hrl hipe_icode_primops.hrl hipe_icode_type.hrl +ERL_FILES= $(MODULES:%=%.erl) +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +DOC_FILES= $(DOC_MODULES:%=$(DOCS)/%.html) + +# APP_FILE= +# APP_SRC= $(APP_FILE).src +# APP_TARGET= $(EBIN)/$(APP_FILE) +# +# APPUP_FILE= +# APPUP_SRC= $(APPUP_FILE).src +# APPUP_TARGET= $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- + +include ../native.mk + +ERL_COMPILE_FLAGS += +warn_unused_import +warn_missing_spec +warn_untyped_record + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) + +docs: $(DOC_FILES) + +clean: + rm -f $(TARGET_FILES) + rm -f core + +$(DOCS)/%.html:%.erl + erl -noshell -run edoc_run file '"$<"' '[{dir, "$(DOCS)"}]' -s init stop + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/icode + $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)/icode + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: + +$(EBIN)/hipe_beam_to_icode.beam: hipe_icode_primops.hrl ../main/hipe.hrl ../../compiler/src/beam_disasm.hrl +$(EBIN)/hipe_icode.beam: ../main/hipe.hrl +$(EBIN)/hipe_icode_bincomp.beam: ../flow/cfg.hrl +$(EBIN)/hipe_icode_callgraph.beam: hipe_icode_primops.hrl +$(EBIN)/hipe_icode_cfg.beam: ../flow/hipe_bb.hrl ../flow/cfg.hrl ../flow/cfg.inc ../main/hipe.hrl +$(EBIN)/hipe_icode_ebb.beam: ../flow/cfg.hrl ../flow/ebb.inc +$(EBIN)/hipe_icode_exceptions.beam: ../flow/cfg.hrl +$(EBIN)/hipe_icode_fp.beam: ../flow/cfg.hrl +$(EBIN)/hipe_icode_heap_test.beam: ../main/hipe.hrl hipe_icode_primops.hrl ../flow/cfg.hrl ../rtl/hipe_literals.hrl +$(EBIN)/hipe_icode_inline_bifs.beam: ../flow/cfg.hrl +$(EBIN)/hipe_icode_instruction_counter.beam: ../main/hipe.hrl ../flow/cfg.hrl +$(EBIN)/hipe_icode_liveness.beam: ../flow/cfg.hrl ../flow/liveness.inc +$(EBIN)/hipe_icode_mulret.beam: ../main/hipe.hrl hipe_icode_primops.hrl +$(EBIN)/hipe_icode_primops.beam: hipe_icode_primops.hrl +$(EBIN)/hipe_icode_range.beam: ../main/hipe.hrl ../flow/cfg.hrl hipe_icode_primops.hrl +$(EBIN)/hipe_icode_split_arith.beam: ../main/hipe.hrl hipe_icode.hrl ../flow/cfg.hrl +$(EBIN)/hipe_icode_ssa.beam: ../main/hipe.hrl ../ssa/hipe_ssa.inc ../ssa/hipe_ssa_liveness.inc +$(EBIN)/hipe_icode_ssa_const_prop.beam: ../main/hipe.hrl hipe_icode_primops.hrl ../flow/cfg.hrl ../ssa/hipe_ssa_const_prop.inc +$(EBIN)/hipe_icode_ssa_copy_prop.beam: ../flow/cfg.hrl ../ssa/hipe_ssa_copy_prop.inc +$(EBIN)/hipe_icode_type.beam: hipe_icode_primops.hrl ../flow/cfg.hrl hipe_icode_type.hrl +$(EBIN)/hipe_icode_ssa_struct_reuse.beam: ../main/hipe.hrl hipe_icode_primops.hrl ../flow/cfg.hrl + +$(TARGET_FILES): hipe_icode.hrl ../misc/hipe_consttab.hrl diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl new file mode 100644 index 0000000000..3923e98673 --- /dev/null +++ b/lib/hipe/icode/hipe_beam_to_icode.erl @@ -0,0 +1,2326 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%======================================================================= +%% File : hipe_beam_to_icode.erl +%% Author : Kostis Sagonas +%% Description : Translates symbolic BEAM code to Icode +%%======================================================================= +%% $Id$ +%%======================================================================= +%% @doc +%% This file translates symbolic BEAM code to Icode which is HiPE's +%% intermediate code representation. Either the code of an entire +%% module, or the code of a specified function can be translated. +%% @end +%%======================================================================= + +-module(hipe_beam_to_icode). + +-export([module/2, mfa/3]). + +%%----------------------------------------------------------------------- + +%% Uncomment the following lines to turn on debugging for this module +%% or comment them to it turn off. Debug-level 6 inserts a print in +%% each compiled function. +%% +%%-ifndef(DEBUG). +%%-define(DEBUG,6). +%%-endif. + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("hipe_icode_primops.hrl"). +-include("../../compiler/src/beam_disasm.hrl"). + +-define(no_debug_msg(Str,Xs),ok). +%%-define(no_debug_msg(Str,Xs),msg(Str,Xs)). + +-define(mk_debugcode(MFA, Env, Code), + case MFA of + {io,_,_} -> + %% We do not want to loop infinitely if we are compiling + %% the module io. + {Code,Env}; + {M,F,A} -> + MFAVar = mk_var(new), + StringVar = mk_var(new), + Ignore = mk_var(new), + MkMfa = hipe_icode:mk_move(MFAVar,hipe_icode:mk_const([MFA])), + MkString = hipe_icode:mk_move(StringVar, + hipe_icode:mk_const( + atom_to_list(M) ++ ":" ++ atom_to_list(F) ++"/"++ integer_to_list(A) ++ + " Native enter fun ~w\n")), + Call = + hipe_icode:mk_call([Ignore],io,format,[StringVar,MFAVar],remote), + {[MkMfa,MkString,Call | Code], Env} + end). + +%%----------------------------------------------------------------------- +%% Exported types +%%----------------------------------------------------------------------- + +-type hipe_beam_to_icode_ret() :: [{mfa(),#icode{}}]. + + +%%----------------------------------------------------------------------- +%% Internal data structures +%%----------------------------------------------------------------------- + +-record(beam_const, {value :: simple_const()}). % defined in hipe_icode.hrl + +-record(closure_info, {mfa :: mfa(), arity :: arity(), fv_arity :: arity()}). + +-record(environment, {mfa :: mfa(), entry :: non_neg_integer()}). + + +%%----------------------------------------------------------------------- +%% @doc +%% Translates the code of a whole module into Icode. +%% Returns a tuple whose first argument is a list of {{M,F,A}, ICode} +%% pairs, and its second argument is the list of HiPE compiler options. +%% @end +%%----------------------------------------------------------------------- + +-spec module([#function{}], comp_options()) -> hipe_beam_to_icode_ret(). + +module(BeamFuns, Options) -> + BeamCode0 = [beam_disasm:function__code(F) || F <- BeamFuns], + {ModCode, ClosureInfo} = preprocess_code(BeamCode0), + pp_beam(ModCode, Options), + [trans_beam_function_chunk(FunCode, ClosureInfo) || FunCode <- ModCode]. + +trans_beam_function_chunk(FunBeamCode, ClosureInfo) -> + {M,F,A} = MFA = find_mfa(FunBeamCode), + Icode = trans_mfa_code(M,F,A, FunBeamCode, ClosureInfo), + {MFA,Icode}. + +%%----------------------------------------------------------------------- +%% @doc +%% Translates the BEAM code of a single function into Icode. +%% Returns a tuple whose first argument is list of {{M,F,A}, ICode} +%% pairs, where the first entry is that of the given MFA, and the +%% following (in undefined order) are those of the funs that are +%% defined in the function, and recursively, in the funs. The +%% second argument of the tuple is the HiPE compiler options +%% contained in the file. +%% @end +%%----------------------------------------------------------------------- + +-spec mfa(list(), mfa(), comp_options()) -> hipe_beam_to_icode_ret(). + +mfa(BeamFuns, {M,F,A} = MFA, Options) + when is_atom(M), is_atom(F), is_integer(A) -> + BeamCode0 = [beam_disasm:function__code(Fn) || Fn <- BeamFuns], + {ModCode, ClosureInfo} = preprocess_code(BeamCode0), + mfa_loop([MFA], [], sets:new(), ModCode, ClosureInfo, Options). + +mfa_loop([{M,F,A} = MFA | MFAs], Acc, Seen, ModCode, ClosureInfo, + Options) when is_atom(M), is_atom(F), is_integer(A) -> + case sets:is_element(MFA, Seen) of + true -> + mfa_loop(MFAs, Acc, Seen, ModCode, ClosureInfo, Options); + false -> + {Icode, FunMFAs} = mfa_get(M, F, A, ModCode, ClosureInfo, Options), + mfa_loop(FunMFAs ++ MFAs, [{MFA, Icode} | Acc], + sets:add_element(MFA, Seen), + ModCode, ClosureInfo, Options) + end; +mfa_loop([], Acc, _, _, _, _) -> + lists:reverse(Acc). + +mfa_get(M, F, A, ModCode, ClosureInfo, Options) -> + BeamCode = get_fun(ModCode, M,F,A), + pp_beam([BeamCode], Options), % cheat by using a list + Icode = trans_mfa_code(M,F,A, BeamCode, ClosureInfo), + FunMFAs = get_fun_mfas(BeamCode), + {Icode, FunMFAs}. + +get_fun_mfas([{patched_make_fun,{M,F,A} = MFA,_,_,_}|BeamCode]) + when is_atom(M), is_atom(F), is_integer(A) -> + [MFA|get_fun_mfas(BeamCode)]; +get_fun_mfas([_|BeamCode]) -> + get_fun_mfas(BeamCode); +get_fun_mfas([]) -> + []. + +%%----------------------------------------------------------------------- +%% The main translation function. +%%----------------------------------------------------------------------- + +trans_mfa_code(M,F,A, FunBeamCode, ClosureInfo) -> + ?no_debug_msg("disassembling: {~p,~p,~p} ...", [M,F,A]), + hipe_gensym:init(icode), + %% Extract the function arguments + FunArgs = extract_fun_args(A), + %% Record the function arguments + FunLbl = mk_label(new), + Env1 = env__mk_env(M, F, A, hipe_icode:label_name(FunLbl)), + Code1 = lists:flatten(trans_fun(FunBeamCode,Env1)), + Code2 = fix_fallthroughs(fix_catches(Code1)), + MFA = {M,F,A}, + %% Debug code + ?IF_DEBUG_LEVEL(5, + {Code3,_Env3} = ?mk_debugcode(MFA, Env2, Code2), + {Code3,_Env3} = {Code2,Env1}), + %% For stack optimization + Leafness = leafness(Code3), + IsLeaf = is_leaf_code(Leafness), + Code4 = + [FunLbl | + case needs_redtest(Leafness) of + false -> Code3; + true -> [mk_redtest()|Code3] + end], + IsClosure = get_closure_info(MFA, ClosureInfo) =/= not_a_closure, + Code5 = hipe_icode:mk_icode(MFA, FunArgs, IsClosure, IsLeaf, + remove_dead_code(Code4), + hipe_gensym:var_range(icode), + hipe_gensym:label_range(icode)), + Icode = %% If this function is the code for a closure ... + case get_closure_info(MFA, ClosureInfo) of + not_a_closure -> Code5; + CI -> %% ... then patch the code to + %% get the free_vars from the closure + patch_closure_entry(Code5, CI) + end, + ?no_debug_msg("ok~n", []), + Icode. + +mk_redtest() -> hipe_icode:mk_primop([], redtest, []). + +leafness(Is) -> % -> true, selfrec, or false + leafness(Is, true). + +leafness([], Leafness) -> + Leafness; +leafness([I|Is], Leafness) -> + case I of + #icode_comment{} -> + %% BEAM self-tailcalls become gotos, but they leave + %% a trace behind in comments. Check those to ensure + %% that the computed leafness is correct. Needed to + %% prevent redtest elimination in those cases. + NewLeafness = + case hipe_icode:comment_text(I) of + 'tail_recursive' -> selfrec; % call_last to selfrec + 'self_tail_recursive' -> selfrec; % call_only to selfrec + _ -> Leafness + end, + leafness(Is, NewLeafness); + #icode_call{} -> + case hipe_icode:call_type(I) of + 'primop' -> + case hipe_icode:call_fun(I) of + call_fun -> false; % Calls closure + enter_fun -> false; % Calls closure + #apply_N{} -> false; + _ -> leafness(Is, Leafness) % Other primop calls are ok + end; + T when T =:= 'local' orelse T =:= 'remote' -> + {M,F,A} = hipe_icode:call_fun(I), + case erlang:is_builtin(M, F, A) of + true -> leafness(Is, Leafness); + false -> false + end + end; + #icode_enter{} -> + case hipe_icode:enter_type(I) of + 'primop' -> + case hipe_icode:enter_fun(I) of + enter_fun -> false; + #apply_N{} -> false; + _ -> + %% All primops should be ok except those excluded above, + %% except we don't actually tailcall them... + io:format("leafness: unexpected enter to primop ~w\n", [I]), + true + end; + T when T =:= 'local' orelse T =:= 'remote' -> + {M,F,A} = hipe_icode:enter_fun(I), + case erlang:is_builtin(M, F, A) of + true -> leafness(Is, Leafness); + _ -> false + end + end; + _ -> leafness(Is, Leafness) + end. + +%% XXX: this old stuff is passed around but essentially unused +is_leaf_code(Leafness) -> + case Leafness of + true -> true; + selfrec -> true; + false -> false + end. + +needs_redtest(Leafness) -> + case Leafness of + true -> false; + selfrec -> true; + false -> true + end. + +%%----------------------------------------------------------------------- +%% The main translation switch. +%%----------------------------------------------------------------------- + +%%--- label & func_info combo --- +trans_fun([{label,B},{label,_}, + {func_info,M,F,A},{label,L}|Instructions], Env) -> + trans_fun([{label,B},{func_info,M,F,A},{label,L}|Instructions], Env); +trans_fun([{label,B}, + {func_info,{atom,_M},{atom,_F},_A}, + {label,L}|Instructions], Env) -> + %% Emit code to handle function_clause errors. The BEAM test instructions + %% branch to this label if they fail during function clause selection. + %% Obviously, we must goto past this error point on normal entry. + Begin = mk_label(B), + V = mk_var(new), + EntryPt = mk_label(L), + Goto = hipe_icode:mk_goto(hipe_icode:label_name(EntryPt)), + Mov = hipe_icode:mk_move(V, hipe_icode:mk_const(function_clause)), + Fail = hipe_icode:mk_fail([V],error), + [Goto, Begin, Mov, Fail, EntryPt | trans_fun(Instructions, Env)]; +%%--- label --- +trans_fun([{label,L1},{label,L2}|Instructions], Env) -> + %% Old BEAM code can have two consecutive labels. + Lab1 = mk_label(L1), + Lab2 = mk_label(L2), + Goto = hipe_icode:mk_goto(map_label(L2)), + [Lab1, Goto, Lab2 | trans_fun(Instructions, Env)]; +trans_fun([{label,L}|Instructions], Env) -> + [mk_label(L) | trans_fun(Instructions, Env)]; +%%--- int_code_end --- SHOULD NEVER OCCUR HERE +%%--- call --- +trans_fun([{call,_N,{_M,_F,A}=MFA}|Instructions], Env) -> + Args = extract_fun_args(A), + Dst = [mk_var({r,0})], + I = trans_call(MFA, Dst, Args, local), + [I | trans_fun(Instructions, Env)]; +%%--- call_last --- +%% Differs from call_only in that it deallocates the environment +trans_fun([{call_last,_N,{_M,_F,A}=MFA,_}|Instructions], Env) -> + %% IS IT OK TO IGNORE LAST ARG ?? + ?no_debug_msg(" translating call_last: ~p ...~n", [Env]), + case env__get_mfa(Env) of + MFA -> + %% Does this case really happen, or is it covered by call_only? + Entry = env__get_entry(Env), + [hipe_icode:mk_comment('tail_recursive'), % needed by leafness/2 + hipe_icode:mk_goto(Entry) | trans_fun(Instructions,Env)]; + _ -> + Args = extract_fun_args(A), + I = trans_enter(MFA, Args, local), + [I | trans_fun(Instructions, Env)] + end; +%%--- call_only --- +%% Used when the body contains only one call in which case +%% an environment is not needed/created. +trans_fun([{call_only,_N,{_M,_F,A}=MFA}|Instructions], Env) -> + ?no_debug_msg(" translating call_only: ~p ...~n", [Env]), + case env__get_mfa(Env) of + MFA -> + Entry = env__get_entry(Env), + [hipe_icode:mk_comment('self_tail_recursive'), % needed by leafness/2 + hipe_icode:mk_goto(Entry) | trans_fun(Instructions,Env)]; + _ -> + Args = extract_fun_args(A), + I = trans_enter(MFA,Args,local), + [I | trans_fun(Instructions,Env)] + end; +%%--- call_ext --- +trans_fun([{call_ext,_N,{extfunc,M,F,A}}|Instructions], Env) -> + Args = extract_fun_args(A), + Dst = [mk_var({r,0})], + I = trans_call({M,F,A},Dst,Args,remote), + [hipe_icode:mk_comment('call_ext'),I | trans_fun(Instructions,Env)]; +%%--- call_ext_last --- +trans_fun([{call_ext_last,_N,{extfunc,M,F,A},_}|Instructions], Env) -> + %% IS IT OK TO IGNORE LAST ARG ?? + Args = extract_fun_args(A), + %% Dst = [mk_var({r,0})], + I = trans_enter({M,F,A},Args,remote), + [hipe_icode:mk_comment('call_ext_last'), I | trans_fun(Instructions,Env)]; +%%--- bif0 --- +trans_fun([{bif,BifName,nofail,[],Reg}|Instructions], Env) -> + BifInst = trans_bif0(BifName,Reg), + [hipe_icode:mk_comment({bif0,BifName}),BifInst|trans_fun(Instructions,Env)]; +%%--- bif1 --- +trans_fun([{bif,BifName,{f,Lbl},[_] = Args,Reg}|Instructions], Env) -> + {BifInsts,Env1} = trans_bif(1,BifName,Lbl,Args,Reg,Env), + [hipe_icode:mk_comment({bif1,BifName})|BifInsts] ++ trans_fun(Instructions,Env1); +%%--- bif2 --- +trans_fun([{bif,BifName,{f,Lbl},[_,_] = Args,Reg}|Instructions], Env) -> + {BifInsts,Env1} = trans_bif(2,BifName,Lbl,Args,Reg,Env), + [hipe_icode:mk_comment({bif2,BifName})|BifInsts] ++ trans_fun(Instructions,Env1); +%%--- allocate +trans_fun([{allocate,StackSlots,_}|Instructions], Env) -> + trans_allocate(StackSlots) ++ trans_fun(Instructions,Env); +%%--- allocate_heap +trans_fun([{allocate_heap,StackSlots,_,_}|Instructions], Env) -> + trans_allocate(StackSlots) ++ trans_fun(Instructions,Env); +%%--- allocate_zero +trans_fun([{allocate_zero,StackSlots,_}|Instructions], Env) -> + trans_allocate(StackSlots) ++ trans_fun(Instructions,Env); +%%--- allocate_heap_zero +trans_fun([{allocate_heap_zero,StackSlots,_,_}|Instructions], Env) -> + trans_allocate(StackSlots) ++ trans_fun(Instructions,Env); +%%--- test_heap --- IGNORED ON PURPOSE +trans_fun([{test_heap,_,_}|Instructions], Env) -> + trans_fun(Instructions,Env); +%%--- init --- IGNORED - CORRECT?? +trans_fun([{init,_}|Instructions], Env) -> + trans_fun(Instructions,Env); +%%--- deallocate --- IGNORED ON PURPOSE +trans_fun([{deallocate,_}|Instructions], Env) -> + trans_fun(Instructions,Env); +%%--- return --- +trans_fun([return|Instructions], Env) -> + [hipe_icode:mk_return([mk_var({r,0})]) | trans_fun(Instructions,Env)]; +%%--- send --- +trans_fun([send|Instructions], Env) -> + I = hipe_icode:mk_call([mk_var({r,0})], erlang, send, + [mk_var({x,0}),mk_var({x,1})], remote), + [I | trans_fun(Instructions,Env)]; +%%--- remove_message --- +trans_fun([remove_message|Instructions], Env) -> + [hipe_icode:mk_primop([],select_msg,[]) | trans_fun(Instructions,Env)]; +%%--- timeout --- +trans_fun([timeout|Instructions], Env) -> + [hipe_icode:mk_primop([],clear_timeout,[]) | trans_fun(Instructions,Env)]; +%%--- loop_rec --- +trans_fun([{loop_rec,{_,Lbl},Reg}|Instructions], Env) -> + {Movs,[Temp],Env1} = get_constants_in_temps([Reg],Env), + GotitLbl = mk_label(new), + ChkGetMsg = hipe_icode:mk_primop([Temp],check_get_msg,[], + hipe_icode:label_name(GotitLbl), + map_label(Lbl)), + Movs ++ [ChkGetMsg, GotitLbl | trans_fun(Instructions,Env1)]; +%%--- loop_rec_end --- +trans_fun([{loop_rec_end,{_,Lbl}}|Instructions], Env) -> + Loop = hipe_icode:mk_goto(map_label(Lbl)), + [hipe_icode:mk_primop([],next_msg,[]), Loop | trans_fun(Instructions,Env)]; +%%--- wait --- +trans_fun([{wait,{_,Lbl}}|Instructions], Env) -> + Susp = hipe_icode:mk_primop([],suspend_msg,[]), + Loop = hipe_icode:mk_goto(map_label(Lbl)), + [Susp, Loop | trans_fun(Instructions,Env)]; +%%--- wait_timeout --- +trans_fun([{wait_timeout,{_,Lbl},Reg}|Instructions], Env) -> + {Movs,[_]=Temps,Env1} = get_constants_in_temps([Reg],Env), + SetTmout = hipe_icode:mk_primop([],set_timeout,Temps), + DoneLbl = mk_label(new), + SuspTmout = hipe_icode:mk_if(suspend_msg_timeout,[], + map_label(Lbl),hipe_icode:label_name(DoneLbl)), + Movs ++ [SetTmout, SuspTmout, DoneLbl | trans_fun(Instructions,Env1)]; +%%-------------------------------------------------------------------- +%%--- Translation of arithmetics {bif,ArithOp, ...} --- +%%-------------------------------------------------------------------- +trans_fun([{arithbif,ArithOp,{f,L},SrcRs,DstR}|Instructions], Env) -> + {ICode,NewEnv} = trans_arith(ArithOp,SrcRs,DstR,L,Env), + ICode ++ trans_fun(Instructions,NewEnv); +%%-------------------------------------------------------------------- +%%--- Translation of arithmetic tests {test,is_ARITHTEST, ...} --- +%%-------------------------------------------------------------------- +%%--- is_lt --- +trans_fun([{test,is_lt,{f,Lbl},[Arg1,Arg2]}|Instructions], Env) -> + {ICode,Env1} = trans_test_guard('<',Lbl,Arg1,Arg2,Env), + ICode ++ trans_fun(Instructions,Env1); +%%--- is_ge --- +trans_fun([{test,is_ge,{f,Lbl},[Arg1,Arg2]}|Instructions], Env) -> + {ICode,Env1} = trans_test_guard('>=',Lbl,Arg1,Arg2,Env), + ICode ++ trans_fun(Instructions,Env1); +%%--- is_eq --- +trans_fun([{test,is_eq,{f,Lbl},[Arg1,Arg2]}|Instructions], Env) -> + {ICode,Env1} = trans_is_eq(Lbl,Arg1,Arg2,Env), + ICode ++ trans_fun(Instructions,Env1); +%%--- is_ne --- +trans_fun([{test,is_ne,{f,Lbl},[Arg1,Arg2]}|Instructions], Env) -> + {ICode,Env1} = trans_is_ne(Lbl,Arg1,Arg2,Env), + ICode ++ trans_fun(Instructions,Env1); +%%--- is_eq_exact --- +trans_fun([{test,is_eq_exact,{f,Lbl},[Arg1,Arg2]}|Instructions], Env) -> + {ICode,Env1} = trans_is_eq_exact(Lbl,Arg1,Arg2,Env), + ICode ++ trans_fun(Instructions,Env1); +%%--- is_ne_exact --- +trans_fun([{test,is_ne_exact,{f,Lbl},[Arg1,Arg2]}|Instructions], Env) -> + {ICode,Env1} = trans_is_ne_exact(Lbl,Arg1,Arg2,Env), + ICode ++ trans_fun(Instructions,Env1); +%%-------------------------------------------------------------------- +%%--- Translation of type tests {test,is_TYPE, ...} --- +%%-------------------------------------------------------------------- +%%--- is_integer --- +trans_fun([{test,is_integer,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(integer,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_float --- +trans_fun([{test,is_float,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(float,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_number --- +trans_fun([{test,is_number,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(number,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_atom --- +trans_fun([{test,is_atom,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(atom,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_pid --- +trans_fun([{test,is_pid,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(pid,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_ref --- +trans_fun([{test,is_reference,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(reference,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_port --- +trans_fun([{test,is_port,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(port,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_nil --- +trans_fun([{test,is_nil,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(nil,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_binary --- +trans_fun([{test,is_binary,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(binary,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_constant --- +trans_fun([{test,is_constant,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(constant,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_list --- +trans_fun([{test,is_list,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(list,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_nonempty_list --- +trans_fun([{test,is_nonempty_list,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(cons,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- is_tuple --- +trans_fun([{test,is_tuple,{f,_Lbl}=FLbl,[Xreg]}, + {test,test_arity,FLbl,[Xreg,_]=Args}|Instructions], Env) -> + trans_fun([{test,test_arity,FLbl,Args}|Instructions],Env); +trans_fun([{test,is_tuple,{_,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(tuple,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- test_arity --- +trans_fun([{test,test_arity,{f,Lbl},[Reg,N]}|Instructions], Env) -> + True = mk_label(new), + I = hipe_icode:mk_type([trans_arg(Reg)],{tuple,N}, + hipe_icode:label_name(True),map_label(Lbl)), + [I,True | trans_fun(Instructions,Env)]; +%%-------------------------------------------------------------------- +%%--- select_val --- +trans_fun([{select_val,Reg,{f,Lbl},{list,Cases}}|Instructions], Env) -> + {SwVar,CasePairs} = trans_select_stuff(Reg,Cases), + Len = length(CasePairs), + I = hipe_icode:mk_switch_val(SwVar,map_label(Lbl),Len,CasePairs), + ?no_debug_msg("switch_val instr is ~p~n",[I]), + [I | trans_fun(Instructions,Env)]; +%%--- select_tuple_arity --- +trans_fun([{select_tuple_arity,Reg,{f,Lbl},{list,Cases}}|Instructions],Env) -> + {SwVar,CasePairs} = trans_select_stuff(Reg,Cases), + Len = length(CasePairs), + I = hipe_icode:mk_switch_tuple_arity(SwVar,map_label(Lbl),Len,CasePairs), + ?no_debug_msg("switch_tuple_arity instr is ~p~n",[I]), + [I | trans_fun(Instructions,Env)]; +%%--- jump --- +trans_fun([{jump,{_,L}}|Instructions], Env) -> + Label = mk_label(L), + I = hipe_icode:mk_goto(hipe_icode:label_name(Label)), + [I | trans_fun(Instructions,Env)]; +%%--- move --- +trans_fun([{move,Src,Dst}|Instructions], Env) -> + Dst1 = mk_var(Dst), + Src1 = trans_arg(Src), + [hipe_icode:mk_move(Dst1,Src1) | trans_fun(Instructions,Env)]; +%%--- catch --- ITS PROCESSING IS POSTPONED +trans_fun([{'catch',N,{_,EndLabel}}|Instructions], Env) -> + NewContLbl = mk_label(new), + [{'catch',N,EndLabel},NewContLbl | trans_fun(Instructions,Env)]; +%%--- catch_end --- ITS PROCESSING IS POSTPONED +trans_fun([{catch_end,_N}=I|Instructions], Env) -> + [I | trans_fun(Instructions,Env)]; +%%--- try --- ITS PROCESSING IS POSTPONED +trans_fun([{'try',N,{_,EndLabel}}|Instructions], Env) -> + NewContLbl = mk_label(new), + [{'try',N,EndLabel},NewContLbl | trans_fun(Instructions,Env)]; +%%--- try_end --- +trans_fun([{try_end,_N}|Instructions], Env) -> + [hipe_icode:mk_end_try() | trans_fun(Instructions,Env)]; +%%--- try_case --- ITS PROCESSING IS POSTPONED +trans_fun([{try_case,_N}=I|Instructions], Env) -> + [I | trans_fun(Instructions,Env)]; +%%--- try_case_end --- +trans_fun([{try_case_end,Arg}|Instructions], Env) -> + BadArg = trans_arg(Arg), + ErrVar = mk_var(new), + Vs = [mk_var(new)], + Atom = hipe_icode:mk_move(ErrVar,hipe_icode:mk_const(try_clause)), + Tuple = hipe_icode:mk_primop(Vs,mktuple,[ErrVar,BadArg]), + Fail = hipe_icode:mk_fail(Vs,error), + [Atom,Tuple,Fail | trans_fun(Instructions,Env)]; +%%--- raise --- +trans_fun([{raise,{f,0},[Reg1,Reg2],{x,0}}|Instructions], Env) -> + V1 = trans_arg(Reg1), + V2 = trans_arg(Reg2), + Fail = hipe_icode:mk_fail([V1,V2],rethrow), + [Fail | trans_fun(Instructions,Env)]; +%%--- get_list --- +trans_fun([{get_list,List,Head,Tail}|Instructions], Env) -> + TransList = [trans_arg(List)], + I1 = hipe_icode:mk_primop([mk_var(Head)],unsafe_hd,TransList), + I2 = hipe_icode:mk_primop([mk_var(Tail)],unsafe_tl,TransList), + %% Handle the cases where the dest overwrites the src!! + if + Head =/= List -> + [I1, I2 | trans_fun(Instructions,Env)]; + Tail =/= List -> + [I2, I1 | trans_fun(Instructions,Env)]; + true -> + %% XXX: We should take care of this case!!!!! + ?error_msg("hd and tl regs identical in get_list~n",[]), + erlang:error(not_handled) + end; +%%--- get_tuple_element --- +trans_fun([{get_tuple_element,Xreg,Index,Dst}|Instructions], Env) -> + I = hipe_icode:mk_primop([mk_var(Dst)], + #unsafe_element{index=Index+1}, + [trans_arg(Xreg)]), + [I | trans_fun(Instructions,Env)]; +%%--- set_tuple_element --- +trans_fun([{set_tuple_element,Elem,Tuple,Index}|Instructions], Env) -> + Elem1 = trans_arg(Elem), + I = hipe_icode:mk_primop([mk_var(Tuple)], + #unsafe_update_element{index=Index+1}, + [mk_var(Tuple),Elem1]), + [I | trans_fun(Instructions,Env)]; +%%--- put_string --- +trans_fun([{put_string,_Len,String,Dst}|Instructions], Env) -> + Mov = hipe_icode:mk_move(mk_var(Dst),trans_const(String)), + [Mov | trans_fun(Instructions,Env)]; +%%--- put_list --- +trans_fun([{put_list,Car,Cdr,Dest}|Instructions], Env) -> + {M1,V1,Env2} = mk_move_and_var(Car,Env), + {M2,V2,Env3} = mk_move_and_var(Cdr,Env2), + D = mk_var(Dest), + M1 ++ M2 ++ [hipe_icode:mk_primop([D],cons,[V1,V2]) + | trans_fun(Instructions,Env3)]; +%%--- put_tuple --- +trans_fun([{put_tuple,_Size,Reg}|Instructions], Env) -> + {Moves,Instructions2,Vars,Env2} = trans_puts(Instructions,Env), + Dest = [mk_var(Reg)], + Src = lists:reverse(Vars), + Primop = hipe_icode:mk_primop(Dest,mktuple,Src), + Moves ++ [Primop | trans_fun(Instructions2,Env2)]; +%%--- put --- SHOULD NOT REALLY EXIST HERE; put INSTRUCTIONS ARE HANDLED ABOVE. +%%--- badmatch --- +trans_fun([{badmatch,Arg}|Instructions], Env) -> + BadVar = trans_arg(Arg), + ErrVar = mk_var(new), + Vs = [mk_var(new)], + Atom = hipe_icode:mk_move(ErrVar,hipe_icode:mk_const(badmatch)), + Tuple = hipe_icode:mk_primop(Vs,mktuple,[ErrVar,BadVar]), + Fail = hipe_icode:mk_fail(Vs,error), + [Atom,Tuple,Fail | trans_fun(Instructions,Env)]; +%%--- if_end --- +trans_fun([if_end|Instructions], Env) -> + V = mk_var(new), + Mov = hipe_icode:mk_move(V,hipe_icode:mk_const(if_clause)), + Fail = hipe_icode:mk_fail([V],error), + [Mov,Fail | trans_fun(Instructions, Env)]; +%%--- case_end --- +trans_fun([{case_end,Arg}|Instructions], Env) -> + BadArg = trans_arg(Arg), + ErrVar = mk_var(new), + Vs = [mk_var(new)], + Atom = hipe_icode:mk_move(ErrVar,hipe_icode:mk_const(case_clause)), + Tuple = hipe_icode:mk_primop(Vs,mktuple,[ErrVar,BadArg]), + Fail = hipe_icode:mk_fail(Vs,error), + [Atom,Tuple,Fail | trans_fun(Instructions,Env)]; +%%--- enter_fun --- +trans_fun([{call_fun,N},{deallocate,_},return|Instructions], Env) -> + Args = extract_fun_args(N+1), %% +1 is for the fun itself + [hipe_icode:mk_comment('enter_fun'), + hipe_icode:mk_enter_primop(enter_fun,Args) | trans_fun(Instructions,Env)]; +%%--- call_fun --- +trans_fun([{call_fun,N}|Instructions], Env) -> + Args = extract_fun_args(N+1), %% +1 is for the fun itself + Dst = [mk_var({r,0})], + [hipe_icode:mk_comment('call_fun'), + hipe_icode:mk_primop(Dst,call_fun,Args) | trans_fun(Instructions,Env)]; +%%--- patched_make_fun --- make_fun/make_fun2 after fixes +trans_fun([{patched_make_fun,MFA,Magic,FreeVarNum,Index}|Instructions], Env) -> + Args = extract_fun_args(FreeVarNum), + Dst = [mk_var({r,0})], + Fun = hipe_icode:mk_primop(Dst, + #mkfun{mfa=MFA,magic_num=Magic,index=Index}, + Args), + ?no_debug_msg("mkfun translates to: ~p~n",[Fun]), + [Fun | trans_fun(Instructions,Env)]; +%%--- is_function --- +trans_fun([{test,is_function,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(function,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%--- call_ext_only --- +trans_fun([{call_ext_only,_N,{extfunc,M,F,A}}|Instructions], Env) -> + Args = extract_fun_args(A), + I = trans_enter({M,F,A}, Args, remote), + [hipe_icode:mk_comment('call_ext_only'), I | trans_fun(Instructions,Env)]; +%%-------------------------------------------------------------------- +%%--- Translation of binary instructions --- +%%-------------------------------------------------------------------- +%% This code uses a somewhat unorthodox translation: +%% Since we do not want non-erlang values as arguments to Icode +%% instructions some compile time constants are coded into the +%% name of the function (or rather the primop). +%% TODO: Make sure all cases of argument types are covered. +%%-------------------------------------------------------------------- +trans_fun([{test,bs_start_match2,{f,Lbl},[X,_Live,Max,Ms]}|Instructions], Env) -> + Bin = trans_arg(X), + MsVar = mk_var(Ms), + trans_op_call({hipe_bs_primop, {bs_start_match, Max}}, Lbl, [Bin], + [MsVar], Env, Instructions); +trans_fun([{test,bs_get_float2,{f,Lbl},[Ms,_Live,Size,Unit,{field_flags,Flags0},X]}| + Instructions], Env) -> + Dst = mk_var(X), + MsVar = mk_var(Ms), + Flags = resolve_native_endianess(Flags0), + {Name, Args} = + case Size of + {integer, NoBits} when is_integer(NoBits), NoBits >= 0 -> + {{bs_get_float,NoBits*Unit,Flags}, [MsVar]}; + {integer, NoBits} when is_integer(NoBits), NoBits < 0 -> + ?EXIT({bad_bs_size_constant,Size}); + BitReg -> + Bits = mk_var(BitReg), + {{bs_get_float,Unit,Flags}, [Bits,MsVar]} + end, + trans_op_call({hipe_bs_primop,Name}, Lbl, Args, [Dst,MsVar], Env, Instructions); +trans_fun([{test,bs_get_integer2,{f,Lbl},[Ms,_Live,Size,Unit,{field_flags,Flags0},X]}| + Instructions], Env) -> + Dst = mk_var(X), + MsVar = mk_var(Ms), + Flags = resolve_native_endianess(Flags0), + {Name, Args} = + case Size of + {integer,NoBits} when is_integer(NoBits), NoBits >= 0 -> + {{bs_get_integer,NoBits*Unit,Flags}, [MsVar]}; + {integer,NoBits} when is_integer(NoBits), NoBits < 0 -> + ?EXIT({bad_bs_size_constant,Size}); + BitReg -> + Bits = mk_var(BitReg), + {{bs_get_integer,Unit,Flags}, [MsVar,Bits]} + end, + trans_op_call({hipe_bs_primop,Name}, Lbl, Args, [Dst,MsVar], Env, Instructions); +trans_fun([{test,bs_get_binary2,{f,Lbl},[Ms,_Live,Size,Unit,{field_flags,Flags},X]}| + Instructions], Env) -> + MsVar = mk_var(Ms), + {Name, Args, Dsts} = + case Size of + {atom, all} -> %% put all bits + if Ms =:= X -> + {{bs_get_binary_all,Unit,Flags},[MsVar],[mk_var(X)]}; + true -> + {{bs_get_binary_all_2,Unit,Flags},[MsVar],[mk_var(X),MsVar]} + end; + {integer, NoBits} when is_integer(NoBits), NoBits >= 0 -> + {{bs_get_binary,NoBits*Unit,Flags}, [MsVar], [mk_var(X),MsVar]};%% Create a N*Unit bits subbinary + {integer, NoBits} when is_integer(NoBits), NoBits < 0 -> + ?EXIT({bad_bs_size_constant,Size}); + BitReg -> % Use a number of bits only known at runtime. + Bits = mk_var(BitReg), + {{bs_get_binary,Unit,Flags}, [MsVar,Bits], [mk_var(X),MsVar]} + end, + trans_op_call({hipe_bs_primop,Name}, Lbl, Args, Dsts, Env, Instructions); +trans_fun([{test,bs_skip_bits2,{f,Lbl},[Ms,Size,NumBits,{field_flags,Flags}]}| + Instructions], Env) -> + %% the current match buffer + MsVar = mk_var(Ms), + {Name, Args} = + case Size of + {atom, all} -> %% Skip all bits + {{bs_skip_bits_all,NumBits,Flags},[MsVar]}; + {integer, BitSize} when is_integer(BitSize), BitSize >= 0-> %% Skip N bits + {{bs_skip_bits,BitSize*NumBits}, [MsVar]}; + {integer, BitSize} when is_integer(BitSize), BitSize < 0 -> + ?EXIT({bad_bs_size_constant,Size}); + X -> % Skip a number of bits only known at runtime. + Src = mk_var(X), + {{bs_skip_bits,NumBits},[MsVar,Src]} + end, + trans_op_call({hipe_bs_primop,Name}, Lbl, Args, [MsVar], Env, Instructions); +trans_fun([{test,bs_test_unit,{f,Lbl},[Ms,Unit]}| + Instructions], Env) -> + %% the current match buffer + MsVar = mk_var(Ms), + trans_op_call({hipe_bs_primop,{bs_test_unit,Unit}}, Lbl, + [MsVar], [], Env, Instructions); +trans_fun([{test,bs_match_string,{f,Lbl},[Ms,BitSize,Bin]}| + Instructions], Env) -> + True = mk_label(new), + FalseLabName = map_label(Lbl), + TrueLabName = hipe_icode:label_name(True), + MsVar = mk_var(Ms), + TmpVar = mk_var(new), + ByteSize = BitSize div 8, + ExtraBits = BitSize rem 8, + WordSize = hipe_rtl_arch:word_size(), + if ExtraBits =:= 0 -> + trans_op_call({hipe_bs_primop,{bs_match_string,Bin,ByteSize}}, Lbl, + [MsVar], [MsVar], Env, Instructions); + BitSize =< ((WordSize * 8) - 5) -> + <<Int:BitSize, _/bits>> = Bin, + {I1,Env1} = trans_one_op_call({hipe_bs_primop,{bs_get_integer,BitSize,0}}, Lbl, + [MsVar], [TmpVar, MsVar], Env), + I2 = hipe_icode:mk_type([TmpVar], {integer,Int}, TrueLabName, FalseLabName), + I1 ++ [I2,True] ++ trans_fun(Instructions, Env1); + true -> + <<RealBin:ByteSize/binary, Int:ExtraBits, _/bits>> = Bin, + {I1,Env1} = trans_one_op_call({hipe_bs_primop,{bs_match_string,RealBin,ByteSize}}, Lbl, + [MsVar], [MsVar], Env), + {I2,Env2} = trans_one_op_call({hipe_bs_primop,{bs_get_integer,ExtraBits,0}}, Lbl, + [MsVar], [TmpVar, MsVar], Env1), + I3 = hipe_icode:mk_type([TmpVar], {integer,Int}, TrueLabName, FalseLabName), + I1 ++ I2 ++ [I3,True] ++ trans_fun(Instructions, Env2) + end; +trans_fun([{bs_context_to_binary,Var}|Instructions], Env) -> + %% the current match buffer + IVars = [trans_arg(Var)], + [hipe_icode:mk_primop(IVars,{hipe_bs_primop,bs_context_to_binary},IVars)| + trans_fun(Instructions, Env)]; +trans_fun([{bs_append,{f,Lbl},Size,W,R,U,Binary,{field_flags,F},Dst}| + Instructions], Env) -> + %% the current match buffer + SizeArg = trans_arg(Size), + BinArg = trans_arg(Binary), + IcodeDst = mk_var(Dst), + Offset = mk_var(reg), + Base = mk_var(reg), + trans_bin_call({hipe_bs_primop,{bs_append,W,R,U,F}},Lbl,[SizeArg,BinArg], + [IcodeDst,Base,Offset], + Base, Offset, Env, Instructions); +trans_fun([{bs_private_append,{f,Lbl},Size,U,Binary,{field_flags,F},Dst}| + Instructions], Env) -> + %% the current match buffer + SizeArg = trans_arg(Size), + BinArg = trans_arg(Binary), + IcodeDst = mk_var(Dst), + Offset = mk_var(reg), + Base = mk_var(reg), + trans_bin_call({hipe_bs_primop,{bs_private_append,U,F}}, + Lbl,[SizeArg,BinArg], + [IcodeDst,Base,Offset], + Base, Offset, Env, Instructions); +trans_fun([bs_init_writable|Instructions], Env) -> + Vars = [mk_var({x,0})], %{x,0} is implict arg and dst + [hipe_icode:mk_primop(Vars,{hipe_bs_primop,bs_init_writable},Vars), + trans_fun(Instructions, Env)]; +trans_fun([{bs_save2,Ms,IndexName}|Instructions], Env) -> + Index = + case IndexName of + {atom, start} -> 0; + _ -> IndexName+1 + end, + MsVars = [mk_var(Ms)], + [hipe_icode:mk_primop(MsVars,{hipe_bs_primop,{bs_save,Index}},MsVars) | + trans_fun(Instructions, Env)]; +trans_fun([{bs_restore2,Ms,IndexName}|Instructions], Env) -> + Index = + case IndexName of + {atom, start} -> 0; + _ -> IndexName+1 + end, + MsVars = [mk_var(Ms)], + [hipe_icode:mk_primop(MsVars,{hipe_bs_primop,{bs_restore,Index}},MsVars) | + trans_fun(Instructions, Env)]; +trans_fun([{test,bs_test_tail2,{f,Lbl},[Ms,Numbits]}| Instructions], Env) -> + MsVar = mk_var(Ms), + trans_op_call({hipe_bs_primop,{bs_test_tail,Numbits}}, + Lbl, [MsVar], [], Env, Instructions); +%%-------------------------------------------------------------------- +%% New bit syntax instructions added in February 2004 (R10B). +%%-------------------------------------------------------------------- +trans_fun([{bs_init2,{f,Lbl},Size,_Words,_LiveRegs,{field_flags,Flags0},X}| + Instructions], Env) -> + Dst = mk_var(X), + Flags = resolve_native_endianess(Flags0), + Offset = mk_var(reg), + Base = mk_var(reg), + {Name, Args} = + case Size of + NoBytes when is_integer(NoBytes) -> + {{bs_init, Size, Flags}, []}; + BitReg -> + Bits = mk_var(BitReg), + {{bs_init, Flags}, [Bits]} + end, + trans_bin_call({hipe_bs_primop,Name}, Lbl, Args, [Dst, Base, Offset], + Base, Offset, Env, Instructions); +trans_fun([{bs_init_bits,{f,Lbl},Size,_Words,_LiveRegs,{field_flags,Flags0},X}| + Instructions], Env) -> + Dst = mk_var(X), + Flags = resolve_native_endianess(Flags0), + Offset = mk_var(reg), + Base = mk_var(reg), + {Name, Args} = + case Size of + NoBits when is_integer(NoBits) -> + {{bs_init_bits, NoBits, Flags}, []}; + BitReg -> + Bits = mk_var(BitReg), + {{bs_init_bits, Flags}, [Bits]} + end, + trans_bin_call({hipe_bs_primop,Name}, Lbl, Args, [Dst, Base, Offset], + Base, Offset, Env, Instructions); +trans_fun([{bs_bits_to_bytes2, Bits, Bytes}|Instructions], Env) -> + Src = trans_arg(Bits), + Dst = mk_var(Bytes), + [hipe_icode:mk_primop([Dst], 'bsl', [Src, hipe_icode:mk_const(3)])| + trans_fun(Instructions,Env)]; +trans_fun([{bs_add, {f,Lbl}, [Old,New,Unit], Res}|Instructions], Env) -> + Dst = mk_var(Res), + Temp = mk_var(new), + MultIs = + case {New,Unit} of + {{integer, NewInt}, _} -> + [hipe_icode:mk_move(Temp, hipe_icode:mk_const(NewInt*Unit))]; + {_, 1} -> + NewVar = mk_var(New), + [hipe_icode:mk_move(Temp, NewVar)]; + _ -> + NewVar = mk_var(New), + if Lbl =:= 0 -> + [hipe_icode:mk_primop([Temp], '*', + [NewVar, hipe_icode:mk_const(Unit)])]; + true -> + Succ = mk_label(new), + [hipe_icode:mk_primop([Temp], '*', + [NewVar, hipe_icode:mk_const(Unit)], + hipe_icode:label_name(Succ), Lbl), + Succ] + end + end, + Succ2 = mk_label(new), + {FailLblName, FailCode} = + if Lbl =:= 0 -> + FailLbl = mk_label(new), + {hipe_icode:label_name(FailLbl), + [FailLbl, + hipe_icode:mk_fail([hipe_icode:mk_const(badarg)], error)]}; + true -> + {Lbl, []} + end, + IsPos = + [hipe_icode:mk_if('>=', [Temp, hipe_icode:mk_const(0)], + hipe_icode:label_name(Succ2), FailLblName)] ++ + FailCode ++ [Succ2], + AddI = + case Old of + {integer,OldInt} -> + hipe_icode:mk_primop([Dst], '+', [Temp, hipe_icode:mk_const(OldInt)]); + _ -> + OldVar = mk_var(Old), + hipe_icode:mk_primop([Dst], '+', [Temp, OldVar]) + end, + MultIs ++ IsPos ++ [AddI|trans_fun(Instructions, Env)]; +%%-------------------------------------------------------------------- +%% Bit syntax instructions added in R12B-5 (Fall 2008) +%%-------------------------------------------------------------------- +trans_fun([{bs_utf8_size,{f,Lbl},A2,A3}|Instructions], Env) -> + Bin = trans_arg(A2), + Dst = mk_var(A3), + trans_op_call({hipe_bs_primop, bs_utf8_size}, Lbl, [Bin], [Dst], Env, Instructions); +trans_fun([{test,bs_get_utf8,{f,Lbl},[Ms,_Live,{field_flags,_Flags},X]} | + Instructions], Env) -> + trans_bs_get_or_skip_utf8(Lbl, Ms, X, Instructions, Env); +trans_fun([{test,bs_skip_utf8,{f,Lbl},[Ms,_Live,{field_flags,_Flags}]} | + Instructions], Env) -> + trans_bs_get_or_skip_utf8(Lbl, Ms, 'new', Instructions, Env); +trans_fun([{bs_utf16_size,{f,Lbl},A2,A3}|Instructions], Env) -> + Bin = trans_arg(A2), + Dst = mk_var(A3), + trans_op_call({hipe_bs_primop, bs_utf16_size}, Lbl, [Bin], [Dst], Env, Instructions); +trans_fun([{test,bs_get_utf16,{f,Lbl},[Ms,_Live,{field_flags,Flags0},X]} | + Instructions], Env) -> + trans_bs_get_or_skip_utf16(Lbl, Ms, Flags0, X, Instructions, Env); +trans_fun([{test,bs_skip_utf16,{f,Lbl},[Ms,_Live,{field_flags,Flags0}]} | + Instructions], Env) -> + trans_bs_get_or_skip_utf16(Lbl, Ms, Flags0, 'new', Instructions, Env); +trans_fun([{test,bs_get_utf32,{f,Lbl},[Ms,_Live,{field_flags,Flags0},X]} | Instructions], Env) -> + trans_bs_get_or_skip_utf32(Lbl, Ms, Flags0, X, Instructions, Env); +trans_fun([{test,bs_skip_utf32,{f,Lbl},[Ms,_Live,{field_flags,Flags0}]} | Instructions], Env) -> + trans_bs_get_or_skip_utf32(Lbl, Ms, Flags0, 'new', Instructions, Env); +%%-------------------------------------------------------------------- +%%--- Translation of floating point instructions --- +%%-------------------------------------------------------------------- +%%--- fclearerror --- +trans_fun([fclearerror|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + [hipe_icode:mk_primop([], fclearerror, []) | + trans_fun(Instructions,Env)]; + _ -> + trans_fun(Instructions,Env) + end; +%%--- fcheckerror --- +trans_fun([{fcheckerror,{_,Fail}}|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + ContLbl = mk_label(new), + case Fail of + 0 -> + [hipe_icode:mk_primop([], fcheckerror, [], + hipe_icode:label_name(ContLbl), []), + ContLbl | trans_fun(Instructions,Env)]; + _ -> %% Can this happen? + {Guard,Env1} = + make_guard([], fcheckerror, [], + hipe_icode:label_name(ContLbl), map_label(Fail), Env), + [Guard, ContLbl | trans_fun(Instructions,Env1)] + end; + _ -> + trans_fun(Instructions, Env) + end; +%%--- fmove --- +trans_fun([{fmove,Src,Dst}|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + Dst1 = mk_var(Dst), + Src1 = trans_arg(Src), + case{hipe_icode:is_fvar(Dst1), + hipe_icode:is_fvar(Src1)} of + {true, true} -> %% fvar := fvar + [hipe_icode:mk_move(Dst1,Src1) | trans_fun(Instructions,Env)]; + {false, true} -> %% var := fvar + [hipe_icode:mk_primop([Dst1], unsafe_tag_float, [Src1]) | + trans_fun(Instructions,Env)]; + {true, false} -> %% fvar := var or fvar := constant + [hipe_icode:mk_primop([Dst1], unsafe_untag_float, [Src1]) | + trans_fun(Instructions,Env)] + end; + _ -> + trans_fun([{move,Src,Dst}|Instructions], Env) + end; +%%--- fconv --- +trans_fun([{fconv,Eterm,FReg}|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + Src = trans_arg(Eterm), + ContLbl = mk_label(new), + Dst = mk_var(FReg), + [hipe_icode:mk_primop([Dst], conv_to_float, [Src], + hipe_icode:label_name(ContLbl), []), + ContLbl| trans_fun(Instructions, Env)]; + _ -> + trans_fun([{fmove,Eterm,FReg}|Instructions], Env) + end; +%%--- fadd --- +trans_fun([{arithfbif,fadd,Lab,SrcRs,DstR}|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + trans_fun([{arithbif,fp_add,Lab,SrcRs,DstR}|Instructions], Env); + _ -> + trans_fun([{arithbif,'+',Lab,SrcRs,DstR}|Instructions], Env) + end; +%%--- fsub --- +trans_fun([{arithfbif,fsub,Lab,SrcRs,DstR}|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + trans_fun([{arithbif,fp_sub,Lab,SrcRs,DstR}|Instructions], Env); + _ -> + trans_fun([{arithbif,'-',Lab,SrcRs,DstR}|Instructions], Env) + end; +%%--- fmult --- +trans_fun([{arithfbif,fmul,Lab,SrcRs,DstR}|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + trans_fun([{arithbif,fp_mul,Lab,SrcRs,DstR}|Instructions], Env); + _ -> + trans_fun([{arithbif,'*',Lab,SrcRs,DstR}|Instructions], Env) + end; +%%--- fdiv --- +trans_fun([{arithfbif,fdiv,Lab,SrcRs,DstR}|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + trans_fun([{arithbif,fp_div,Lab,SrcRs,DstR}|Instructions], Env); + _ -> + trans_fun([{arithbif,'/',Lab,SrcRs,DstR}|Instructions], Env) + end; +%%--- fnegate --- +trans_fun([{arithfbif,fnegate,Lab,[SrcR],DestR}|Instructions], Env) -> + case get(hipe_inline_fp) of + true -> + Src = trans_arg(SrcR), + Dst = mk_var(DestR), + [hipe_icode:mk_primop([Dst], fnegate, [Src])| + trans_fun(Instructions,Env)]; + _ -> + trans_fun([{arithbif,'-',Lab,[{float,0.0},SrcR],DestR}|Instructions], Env) + end; +%%-------------------------------------------------------------------- +%% New apply instructions added in April 2004 (R10B). +%%-------------------------------------------------------------------- +trans_fun([{apply,Arity}|Instructions], Env) -> + BeamArgs = extract_fun_args(Arity+2), %% +2 is for M and F + {Args,[M,F]} = lists:split(Arity,BeamArgs), + Dst = [mk_var({r,0})], + [hipe_icode:mk_comment('apply'), + hipe_icode:mk_primop(Dst, #apply_N{arity=Arity}, [M,F|Args]) + | trans_fun(Instructions,Env)]; +trans_fun([{apply_last,Arity,_N}|Instructions], Env) -> % N is StackAdjustment? + BeamArgs = extract_fun_args(Arity+2), %% +2 is for M and F + {Args,[M,F]} = lists:split(Arity,BeamArgs), + [hipe_icode:mk_comment('apply_last'), + hipe_icode:mk_enter_primop(#apply_N{arity=Arity}, [M,F|Args]) + | trans_fun(Instructions,Env)]; +%%-------------------------------------------------------------------- +%% New test instruction added in April 2004 (R10B). +%%-------------------------------------------------------------------- +%%--- is_boolean --- +trans_fun([{test,is_boolean,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(boolean,Lbl,Arg,Env), + [Code | trans_fun(Instructions,Env1)]; +%%-------------------------------------------------------------------- +%% New test instruction added in June 2005 for R11 +%%-------------------------------------------------------------------- +%%--- is_function2 --- +trans_fun([{test,is_function2,{f,Lbl},[Arg,Arity]}|Instructions], Env) -> + {Code,Env1} = trans_type_test2(function2,Lbl,Arg,Arity,Env), + [Code | trans_fun(Instructions,Env1)]; +%%-------------------------------------------------------------------- +%% New garbage-collecting BIFs added in January 2006 for R11B. +%%-------------------------------------------------------------------- +trans_fun([{gc_bif,'-',Fail,_Live,[SrcR],DstR}|Instructions], Env) -> + %% Unary minus. Change this to binary minus. + trans_fun([{arithbif,'-',Fail,[{integer,0},SrcR],DstR}|Instructions], Env); +trans_fun([{gc_bif,'+',Fail,_Live,[SrcR],DstR}|Instructions], Env) -> + %% Unary plus. Change this to a bif call. + trans_fun([{bif,'+',Fail,[SrcR],DstR}|Instructions], Env); +trans_fun([{gc_bif,Name,Fail,_Live,SrcRs,DstR}|Instructions], Env) -> + case erl_internal:guard_bif(Name, length(SrcRs)) of + false -> + %% Arithmetic instruction. + trans_fun([{arithbif,Name,Fail,SrcRs,DstR}|Instructions], Env); + true -> + %% A guard BIF. + trans_fun([{bif,Name,Fail,SrcRs,DstR}|Instructions], Env) + end; +%%-------------------------------------------------------------------- +%% Instruction for constant pool added in February 2007 for R11B-4. +%%-------------------------------------------------------------------- +trans_fun([{put_literal,{literal,Literal},DstR}|Instructions], Env) -> + DstV = mk_var(DstR), + Move = hipe_icode:mk_move(DstV, hipe_icode:mk_const(Literal)), + [Move | trans_fun(Instructions, Env)]; +%%-------------------------------------------------------------------- +%% New test instruction added in July 2007 for R12. +%%-------------------------------------------------------------------- +%%--- is_bitstr --- +trans_fun([{test,is_bitstr,{f,Lbl},[Arg]}|Instructions], Env) -> + {Code,Env1} = trans_type_test(bitstr, Lbl, Arg, Env), + [Code | trans_fun(Instructions, Env1)]; +%%-------------------------------------------------------------------- +%% New stack triming instruction added in October 2007 for R12. +%%-------------------------------------------------------------------- +trans_fun([{trim,N,NY}|Instructions], Env) -> + %% trim away N registers leaving NY registers + Moves = trans_trim(N, NY), + Moves ++ trans_fun(Instructions, Env); +%%-------------------------------------------------------------------- +%%--- ERROR HANDLING --- +%%-------------------------------------------------------------------- +trans_fun([X|_], _) -> + ?EXIT({'trans_fun/2',X}); +trans_fun([], _) -> + []. + +%%-------------------------------------------------------------------- +%% trans_call and trans_enter generate correct Icode calls/tail-calls, +%% recognizing explicit fails. +%%-------------------------------------------------------------------- + +trans_call(MFA={M,F,_A}, Dst, Args, Type) -> + handle_fail(MFA, Args, fun () -> hipe_icode:mk_call(Dst,M,F,Args,Type) end). + +trans_enter(MFA={M,F,_A}, Args, Type) -> + handle_fail(MFA, Args, fun () -> hipe_icode:mk_enter(M,F,Args,Type) end). + +handle_fail(MFA, Args, F) -> + case MFA of + {erlang,exit,1} -> + hipe_icode:mk_fail(Args,exit); + {erlang,throw,1} -> + hipe_icode:mk_fail(Args,throw); + {erlang,fault,1} -> + hipe_icode:mk_fail(Args,error); + {erlang,fault,2} -> + hipe_icode:mk_fail(Args,error); + {erlang,error,1} -> + hipe_icode:mk_fail(Args,error); + {erlang,error,2} -> + hipe_icode:mk_fail(Args,error); + _ -> + F() + end. + +%%----------------------------------------------------------------------- +%% trans_bif0(BifName, DestReg) +%% trans_bif(Arity, BifName, FailLab, Args, DestReg, Environment) +%%----------------------------------------------------------------------- + +trans_bif0(BifName, DestReg) -> + ?no_debug_msg(" found BIF0: ~p() ...~n", [BifName]), + BifRes = mk_var(DestReg), + hipe_icode:mk_call([BifRes],erlang,BifName,[],remote). + +trans_bif(Arity, BifName, Lbl, Args, DestReg, Env) -> + ?no_debug_msg(" found BIF: ~p(~p) ...~n", [BifName,Args]), + BifRes = mk_var(DestReg), + {Movs, SrcVars, Env1} = get_constants_in_temps(Args,Env), + case Lbl of + 0 -> % Bif is not in a guard + I = hipe_icode:mk_call([BifRes],erlang,BifName,SrcVars,remote), + {Movs ++ [I], Env1}; + _ -> % Bif occurs in a guard - fail silently to Lbl + {GuardI,Env2} = + make_fallthrough_guard([BifRes],{erlang,BifName,Arity},SrcVars, + map_label(Lbl),Env1), + {[Movs,GuardI], Env2} + end. + +trans_op_call(Name, Lbl, Args, Dests, Env, Instructions) -> + {Code, Env1} = trans_one_op_call(Name, Lbl, Args, Dests, Env), + [Code|trans_fun(Instructions, Env1)]. + +trans_one_op_call(Name, Lbl, Args, Dests, Env) -> + case Lbl of + 0 -> % Op is not in a guard + I = hipe_icode:mk_primop(Dests, Name, Args), + {[I], Env}; + _ -> % op occurs in a guard - fail silently to Lbl + make_fallthrough_guard(Dests, Name, Args, map_label(Lbl), Env) + end. + +%%----------------------------------------------------------------------- +%% trans_bin_call +%%----------------------------------------------------------------------- + +trans_bin_call(Name, Lbl, Args, Dests, Base, Offset, Env, Instructions) -> + {Code, Env1} = + case Lbl of + 0 -> % Op is not in a guard + I = hipe_icode:mk_primop(Dests, Name, Args), + {[I], Env}; + _ -> % op occurs in a guard - fail silently to Lbl + make_fallthrough_guard(Dests, Name, Args, map_label(Lbl), Env) + end, + [Code|trans_bin(Instructions, Base, Offset, Env1)]. + +%% Translate instructions for building binaries separately to give +%% them an appropriate state + +trans_bin([{bs_put_float,{f,Lbl},Size,Unit,{field_flags,Flags0},Source}| + Instructions], Base, Offset, Env) -> + Flags = resolve_native_endianess(Flags0), + %% Get source + {Src,SourceInstrs,ConstInfo} = + case is_var(Source) of + true -> + {mk_var(Source),[], var}; + false -> + case Source of + {float, X} when is_float(X) -> + C = trans_const(Source), + SrcVar = mk_var(new), + I = hipe_icode:mk_move(SrcVar, C), + {SrcVar,[I],pass}; + _ -> + C = trans_const(Source), + SrcVar = mk_var(new), + I = hipe_icode:mk_move(SrcVar, C), + {SrcVar,[I],fail} + end + end, + %% Get type of put_float + {Name,Args,Env2} = + case Size of + {integer,NoBits} when is_integer(NoBits), NoBits >= 0 -> + %% Create a N*Unit bits float + {{bs_put_float, NoBits*Unit, Flags, ConstInfo}, [Src, Base, Offset], Env}; + {integer,NoBits} when is_integer(NoBits), NoBits < 0 -> + ?EXIT({bad_bs_size_constant,Size}); + BitReg -> % Use a number of bits only known at runtime. + Bits = mk_var(BitReg), + {{bs_put_float, Unit, Flags, ConstInfo}, [Src,Bits,Base,Offset], Env} + end, + %% Generate code for calling the bs-op. + SourceInstrs ++ + trans_bin_call({hipe_bs_primop,Name}, Lbl, Args, [Offset], Base, Offset, Env2, Instructions); +trans_bin([{bs_put_binary,{f,Lbl},Size,Unit,{field_flags,Flags},Source}| + Instructions], Base, Offset, Env) -> + %% Get the source of the binary. + Src = trans_arg(Source), + %% Get type of put_binary + {Name, Args, Env2} = + case Size of + {atom,all} -> %% put all bits + {{bs_put_binary_all, Flags}, [Src,Base,Offset], Env}; + {integer,NoBits} when is_integer(NoBits), NoBits >= 0 -> + %% Create a N*Unit bits subbinary + {{bs_put_binary, NoBits*Unit, Flags}, [Src,Base,Offset], Env}; + {integer,NoBits} when is_integer(NoBits), NoBits < 0 -> + ?EXIT({bad_bs_size_constant,Size}); + BitReg -> % Use a number of bits only known at runtime. + Bits = mk_var(BitReg), + {{bs_put_binary, Unit, Flags}, [Src, Bits,Base,Offset], Env} + end, + %% Generate code for calling the bs-op. + trans_bin_call({hipe_bs_primop, Name}, + Lbl, Args, [Offset], + Base, Offset, Env2, Instructions); +%%--- bs_put_string --- +trans_bin([{bs_put_string,SizeInBytes,{string,String}}|Instructions], Base, + Offset, Env) -> + [hipe_icode:mk_primop([Offset], + {hipe_bs_primop,{bs_put_string, String, SizeInBytes}}, + [Base, Offset]) | + trans_bin(Instructions, Base, Offset, Env)]; +trans_bin([{bs_put_integer,{f,Lbl},Size,Unit,{field_flags,Flags0},Source}| + Instructions], Base, Offset, Env) -> + Flags = resolve_native_endianess(Flags0), + %% Get size-type + + %% Get the source of the binary. + {Src, SrcInstrs, ConstInfo} = + case is_var(Source) of + true -> + {mk_var(Source), [], var}; + false -> + case Source of + {integer, X} when is_integer(X) -> + C = trans_const(Source), + SrcVar = mk_var(new), + I = hipe_icode:mk_move(SrcVar, C), + {SrcVar,[I], pass}; + _ -> + C = trans_const(Source), + SrcVar = mk_var(new), + I = hipe_icode:mk_move(SrcVar, C), + {SrcVar,[I], fail} + + end + end, + {Name, Args, Env2} = + case is_var(Size) of + true -> + SVar = mk_var(Size), + {{bs_put_integer,Unit,Flags,ConstInfo}, [SVar, Base, Offset], Env}; + false -> + case Size of + {integer, NoBits} when NoBits >= 0 -> + {{bs_put_integer,NoBits*Unit,Flags,ConstInfo}, [Base, Offset], Env}; + _ -> + ?EXIT({bad_bs_size_constant,Size}) + end + end, + SrcInstrs ++ trans_bin_call({hipe_bs_primop, Name}, + Lbl, [Src|Args], [Offset], Base, Offset, Env2, Instructions); +%%---------------------------------------------------------------- +%% New binary construction instructions added in R12B-5 (Fall 2008). +%%---------------------------------------------------------------- +trans_bin([{bs_put_utf8,{f,Lbl},_FF,A3}|Instructions], Base, Offset, Env) -> + Src = trans_arg(A3), + Args = [Src, Base, Offset], + trans_bin_call({hipe_bs_primop, bs_put_utf8}, Lbl, Args, [Offset], Base, Offset, Env, Instructions); +trans_bin([{bs_put_utf16,{f,Lbl},{field_flags,Flags0},A3}|Instructions], Base, Offset, Env) -> + Src = trans_arg(A3), + Args = [Src, Base, Offset], + Flags = resolve_native_endianess(Flags0), + Name = {bs_put_utf16, Flags}, + trans_bin_call({hipe_bs_primop, Name}, Lbl, Args, [Offset], Base, Offset, Env, Instructions); +trans_bin([{bs_put_utf32,F={f,Lbl},FF={field_flags,_Flags0},A3}|Instructions], Base, Offset, Env) -> + Src = trans_arg(A3), + trans_bin_call({hipe_bs_primop,bs_validate_unicode}, Lbl, [Src], [], Base, Offset, Env, + [{bs_put_integer,F,{integer,32},1,FF,A3} | Instructions]); +%%---------------------------------------------------------------- +%% Base cases for the end of a binary construction sequence. +%%---------------------------------------------------------------- +trans_bin([{bs_final2,Src,Dst}|Instructions], _Base, Offset, Env) -> + [hipe_icode:mk_primop([mk_var(Dst)], {hipe_bs_primop, bs_final}, + [trans_arg(Src),Offset]) + |trans_fun(Instructions, Env)]; +trans_bin(Instructions, _Base, _Offset, Env) -> + trans_fun(Instructions, Env). + +%% this translates bs_get_utf8 and bs_skip_utf8 (get with new unused dst) +trans_bs_get_or_skip_utf8(Lbl, Ms, X, Instructions, Env) -> + Dst = mk_var(X), + MsVar = mk_var(Ms), + trans_op_call({hipe_bs_primop,bs_get_utf8}, Lbl, [MsVar], [Dst,MsVar], Env, Instructions). + +%% this translates bs_get_utf16 and bs_skip_utf16 (get with new unused dst) +trans_bs_get_or_skip_utf16(Lbl, Ms, Flags0, X, Instructions, Env) -> + Dst = mk_var(X), + MsVar = mk_var(Ms), + Flags = resolve_native_endianess(Flags0), + Name = {bs_get_utf16,Flags}, + trans_op_call({hipe_bs_primop,Name}, Lbl, [MsVar], [Dst,MsVar], Env, Instructions). + +%% this translates bs_get_utf32 and bs_skip_utf32 (get with new unused dst) +trans_bs_get_or_skip_utf32(Lbl, Ms, Flags0, X, Instructions, Env) -> + Dst = mk_var(X), + MsVar = mk_var(Ms), + Flags = resolve_native_endianess(Flags0), + {I1,Env1} = trans_one_op_call({hipe_bs_primop,{bs_get_integer,32,Flags}}, + Lbl, [MsVar], [Dst,MsVar], Env), + I1 ++ trans_op_call({hipe_bs_primop,bs_validate_unicode_retract}, + Lbl, [Dst,MsVar], [MsVar], Env1, Instructions). + +%%----------------------------------------------------------------------- +%% trans_arith(Op, SrcVars, Des, Lab, Env) -> { Icode, NewEnv } +%% A failure label of type {f,0} means in a body. +%% A failure label of type {f,L} where L>0 means in a guard. +%% Within a guard a failure should branch to the next guard and +%% not trigger an exception!! +%% Handles body arithmetic with Icode primops! +%% Handles guard arithmetic with Icode guardops! +%%----------------------------------------------------------------------- + +trans_arith(Op, SrcRs, DstR, Lbl, Env) -> + {Movs,SrcVars,Env1} = get_constants_in_temps(SrcRs,Env), + DstVar = mk_var(DstR), + %%io:format("~w:trans_arith()\n ~w := ~w ~w\n", + %% [?MODULE,DstVar,SrcVars,Op]), + case Lbl of + 0 -> % Body arithmetic + Primop = hipe_icode:mk_primop([DstVar], arith_op_name(Op), SrcVars), + {Movs++[Primop], Env1}; + _ -> % Guard arithmetic + {Guard,Env2} = + make_fallthrough_guard([DstVar], arith_op_name(Op), SrcVars, + map_label(Lbl), Env1), + {[Movs,Guard], Env2} + end. + +%% Prevent arbitrary names from leaking into Icode from BEAM. +arith_op_name('+') -> '+'; +arith_op_name('-') -> '-'; +arith_op_name('*') -> '*'; +arith_op_name('/') -> '/'; +arith_op_name('div') -> 'div'; +arith_op_name('fp_add') -> 'fp_add'; +arith_op_name('fp_sub') -> 'fp_sub'; +arith_op_name('fp_mul') -> 'fp_mul'; +arith_op_name('fp_div') -> 'fp_div'; +arith_op_name('rem') -> 'rem'; +arith_op_name('bsl') -> 'bsl'; +arith_op_name('bsr') -> 'bsr'; +arith_op_name('band') -> 'band'; +arith_op_name('bor') -> 'bor'; +arith_op_name('bxor') -> 'bxor'; +arith_op_name('bnot') -> 'bnot'. + +%%----------------------------------------------------------------------- +%%----------------------------------------------------------------------- + +trans_test_guard(TestOp,F,Arg1,Arg2,Env) -> + {Movs,Vars,Env1} = get_constants_in_temps([Arg1,Arg2],Env), + True = mk_label(new), + I = hipe_icode:mk_if(TestOp,Vars,hipe_icode:label_name(True),map_label(F)), + {[Movs,I,True], Env1}. + +%%----------------------------------------------------------------------- +%%----------------------------------------------------------------------- + +make_fallthrough_guard(DstVar,GuardOp,Args,FailLName,Env) -> + ContL = mk_label(new), + ContLName = hipe_icode:label_name(ContL), + {Instrs, NewDsts} = clone_dsts(DstVar), + {Guard,Env1} = make_guard(NewDsts,GuardOp,Args,ContLName,FailLName,Env), + {[Guard,ContL]++Instrs,Env1}. + +%% Make sure DstVar gets initialised to a dummy value after a fail: +%make_guard(Dests,{hipe_bs_primop,Primop},Args,ContLName,FailLName,Env) -> +% {[hipe_icode:mk_guardop(Dests,{hipe_bs_primop,Primop},Args,ContLName,FailLName)], +% Env}; +make_guard(Dests=[_|_],GuardOp,Args,ContLName,FailLName,Env) -> + TmpFailL = mk_label(new), + TmpFailLName = hipe_icode:label_name(TmpFailL), + GuardOpIns = hipe_icode:mk_guardop(Dests,GuardOp,Args, + ContLName,TmpFailLName), + FailCode = [TmpFailL, + nillify_all(Dests), + hipe_icode:mk_goto(FailLName)], + {[GuardOpIns|FailCode], Env}; +%% A guard that does not return anything: +make_guard([],GuardOp,Args,ContLName,FailLName,Env) -> + {[hipe_icode:mk_guardop([],GuardOp,Args,ContLName,FailLName)], + Env}. + +nillify_all([Var|Vars]) -> + [hipe_icode:mk_move(Var,hipe_icode:mk_const([]))|nillify_all(Vars)]; +nillify_all([]) -> []. + +clone_dsts(Dests) -> + clone_dsts(Dests, [],[]). + +clone_dsts([Dest|Dests], Instrs, NewDests) -> + {I,ND} = clone_dst(Dest), + clone_dsts(Dests, [I|Instrs], [ND|NewDests]); +clone_dsts([], Instrs, NewDests) -> + {lists:reverse(Instrs), lists:reverse(NewDests)}. + +clone_dst(Dest) -> + New = + case hipe_icode:is_reg(Dest) of + true -> + mk_var(reg); + false -> + true = hipe_icode:is_var(Dest), + mk_var(new) + end, + {hipe_icode:mk_move(Dest, New), New}. + + +%%----------------------------------------------------------------------- +%% trans_type_test(Test, Lbl, Arg, Env) -> { Icode, NewEnv } +%% Handles all unary type tests like is_integer etc. +%%----------------------------------------------------------------------- + +trans_type_test(Test, Lbl, Arg, Env) -> + True = mk_label(new), + {Move,Var,Env1} = mk_move_and_var(Arg,Env), + I = hipe_icode:mk_type([Var], Test, + hipe_icode:label_name(True), map_label(Lbl)), + {[Move,I,True],Env1}. + +%% +%% This handles binary type tests. Currently, the only such is the new +%% is_function/2 BIF. +%% +trans_type_test2(function2, Lbl, Arg, Arity, Env) -> + True = mk_label(new), + {Move1,Var1,Env1} = mk_move_and_var(Arg, Env), + {Move2,Var2,Env2} = mk_move_and_var(Arity, Env1), + I = hipe_icode:mk_type([Var1,Var2], function2, + hipe_icode:label_name(True), map_label(Lbl)), + {[Move1,Move2,I,True],Env2}. + +%%----------------------------------------------------------------------- +%% trans_puts(Code, Environment) -> +%% { Movs, Code, Vars, NewEnv } +%%----------------------------------------------------------------------- + +trans_puts(Code, Env) -> + trans_puts(Code, [], [], Env). + +trans_puts([{put,X}|Code], Vars, Moves, Env) -> + case type(X) of + var -> + Var = mk_var(X), + trans_puts(Code, [Var|Vars], Moves, Env); + #beam_const{value=C} -> + Var = mk_var(new), + Move = hipe_icode:mk_move(Var, hipe_icode:mk_const(C)), + trans_puts(Code, [Var|Vars], [Move|Moves], Env) + end; +trans_puts(Code, Vars, Moves, Env) -> %% No more put operations + {Moves, Code, Vars, Env}. + +%%----------------------------------------------------------------------- +%% The code for this instruction is a bit large because we are treating +%% different cases differently. We want to use the icode `type' +%% instruction when it is applicable to take care of match expressions. +%%----------------------------------------------------------------------- + +trans_is_eq_exact(Lbl, Arg1, Arg2, Env) -> + case {is_var(Arg1),is_var(Arg2)} of + {true,true} -> + True = mk_label(new), + I = hipe_icode:mk_if('=:=', + [mk_var(Arg1),mk_var(Arg2)], + hipe_icode:label_name(True), map_label(Lbl)), + {[I,True], Env}; + {true,false} -> %% right argument is a constant -- use type()! + trans_is_eq_exact_var_const(Lbl, Arg1, Arg2, Env); + {false,true} -> %% mirror of the case above; swap args + trans_is_eq_exact_var_const(Lbl, Arg2, Arg1, Env); + {false,false} -> %% both arguments are constants !!! + case Arg1 =:= Arg2 of + true -> + {[], Env}; + false -> + Never = mk_label(new), + I = hipe_icode:mk_goto(map_label(Lbl)), + {[I,Never], Env} + end + end. + +trans_is_eq_exact_var_const(Lbl, Arg1, Arg2, Env) -> % var =:= const + True = mk_label(new), + NewArg1 = mk_var(Arg1), + TrueLabName = hipe_icode:label_name(True), + FalseLabName = map_label(Lbl), + I = case Arg2 of + {float,Float} -> + hipe_icode:mk_if('=:=', + [NewArg1, hipe_icode:mk_const(Float)], + TrueLabName, FalseLabName); + {literal,Literal} -> + hipe_icode:mk_if('=:=', + [NewArg1, hipe_icode:mk_const(Literal)], + TrueLabName, FalseLabName); + _ -> + hipe_icode:mk_type([NewArg1], Arg2, TrueLabName, FalseLabName) + end, + {[I,True], Env}. + +%%----------------------------------------------------------------------- +%% ... and this is analogous to the above +%%----------------------------------------------------------------------- + +trans_is_ne_exact(Lbl, Arg1, Arg2, Env) -> + case {is_var(Arg1),is_var(Arg2)} of + {true,true} -> + True = mk_label(new), + I = hipe_icode:mk_if('=/=', + [mk_var(Arg1),mk_var(Arg2)], + hipe_icode:label_name(True), map_label(Lbl)), + {[I,True], Env}; + {true,false} -> %% right argument is a constant -- use type()! + trans_is_ne_exact_var_const(Lbl, Arg1, Arg2, Env); + {false,true} -> %% mirror of the case above; swap args + trans_is_ne_exact_var_const(Lbl, Arg2, Arg1, Env); + {false,false} -> %% both arguments are constants !!! + case Arg1 =/= Arg2 of + true -> + {[], Env}; + false -> + Never = mk_label(new), + I = hipe_icode:mk_goto(map_label(Lbl)), + {[I,Never], Env} + end + end. + +trans_is_ne_exact_var_const(Lbl, Arg1, Arg2, Env) -> % var =/= const + True = mk_label(new), + NewArg1 = mk_var(Arg1), + TrueLabName = hipe_icode:label_name(True), + FalseLabName = map_label(Lbl), + I = case Arg2 of + {float,Float} -> + hipe_icode:mk_if('=/=', + [NewArg1, hipe_icode:mk_const(Float)], + TrueLabName, FalseLabName); + {literal,Literal} -> + hipe_icode:mk_if('=/=', + [NewArg1, hipe_icode:mk_const(Literal)], + TrueLabName, FalseLabName); + _ -> + hipe_icode:mk_type([NewArg1], Arg2, FalseLabName, TrueLabName) + end, + {[I,True], Env}. + +%%----------------------------------------------------------------------- +%% Try to do a relatively straightforward optimization: if equality with +%% an atom is used, then convert this test to use of exact equality test +%% with the same atom (which in turn will be translated to a `type' test +%% instruction by the code of trans_is_eq_exact_var_const/4 above). +%%----------------------------------------------------------------------- + +trans_is_eq(Lbl, Arg1, Arg2, Env) -> + case {is_var(Arg1),is_var(Arg2)} of + {true,true} -> %% not much can be done in this case + trans_test_guard('==', Lbl, Arg1, Arg2, Env); + {true,false} -> %% optimize this case, if possible + case Arg2 of + {atom,_SomeAtom} -> + trans_is_eq_exact_var_const(Lbl, Arg1, Arg2, Env); + _ -> + trans_test_guard('==', Lbl, Arg1, Arg2, Env) + end; + {false,true} -> %% probably happens rarely; hence the recursive call + trans_is_eq(Lbl, Arg2, Arg1, Env); + {false,false} -> %% both arguments are constants !!! + case Arg1 == Arg2 of + true -> + {[], Env}; + false -> + Never = mk_label(new), + I = hipe_icode:mk_goto(map_label(Lbl)), + {[I,Never], Env} + end + end. + +%%----------------------------------------------------------------------- +%% ... and this is analogous to the above +%%----------------------------------------------------------------------- + +trans_is_ne(Lbl, Arg1, Arg2, Env) -> + case {is_var(Arg1),is_var(Arg2)} of + {true,true} -> %% not much can be done in this case + trans_test_guard('/=', Lbl, Arg1, Arg2, Env); + {true,false} -> %% optimize this case, if possible + case Arg2 of + {atom,_SomeAtom} -> + trans_is_ne_exact_var_const(Lbl, Arg1, Arg2, Env); + _ -> + trans_test_guard('/=', Lbl, Arg1, Arg2, Env) + end; + {false,true} -> %% probably happens rarely; hence the recursive call + trans_is_ne(Lbl, Arg2, Arg1, Env); + {false,false} -> %% both arguments are constants !!! + case Arg1 /= Arg2 of + true -> + {[], Env}; + false -> + Never = mk_label(new), + I = hipe_icode:mk_goto(map_label(Lbl)), + {[I,Never], Env} + end + end. + + +%%----------------------------------------------------------------------- +%% Translates an allocate instruction into a sequence of initializations +%%----------------------------------------------------------------------- + +trans_allocate(N) -> + trans_allocate(N, []). + +trans_allocate(0, Acc) -> + Acc; +trans_allocate(N, Acc) -> + Move = hipe_icode:mk_move(mk_var({y,N-1}), + hipe_icode:mk_const('dummy_value')), + trans_allocate(N-1, [Move|Acc]). + +%%----------------------------------------------------------------------- +%% Translates a trim instruction into a sequence of moves +%%----------------------------------------------------------------------- + +trans_trim(N, NY) -> + lists:reverse(trans_trim(N, NY, 0, [])). + +trans_trim(_, 0, _, Acc) -> + Acc; +trans_trim(N, NY, Y, Acc) -> + Move = hipe_icode:mk_move(mk_var({y,Y}), mk_var({y,N})), + trans_trim(N+1, NY-1, Y+1, [Move|Acc]). + +%%----------------------------------------------------------------------- +%%----------------------------------------------------------------------- + +mk_move_and_var(Var, Env) -> + case type(Var) of + var -> + V = mk_var(Var), + {[], V, Env}; + #beam_const{value=C} -> + V = mk_var(new), + {[hipe_icode:mk_move(V,hipe_icode:mk_const(C))], V, Env} + end. + +%%----------------------------------------------------------------------- +%% Find names of closures and number of free vars. +%%----------------------------------------------------------------------- + +closure_info_mfa(#closure_info{mfa=MFA}) -> MFA. +closure_info_arity(#closure_info{arity=Arity}) -> Arity. +%% closure_info_fv_arity(#closure_info{fv_arity=Arity}) -> Arity. + +find_closure_info(Code) -> mod_find_closure_info(Code, []). + +mod_find_closure_info([FunCode|Fs], CI) -> + mod_find_closure_info(Fs, find_closure_info(FunCode, CI)); +mod_find_closure_info([], CI) -> + CI. + +find_closure_info([{patched_make_fun,MFA={_M,_F,A},_Magic,FreeVarNum,_Index}|BeamCode], + ClosureInfo) -> + NewClosure = %% A-FreeVarNum+1 (The real arity + 1 for the closure) + #closure_info{mfa=MFA, arity=A-FreeVarNum+1, fv_arity=FreeVarNum}, + find_closure_info(BeamCode, [NewClosure|ClosureInfo]); +find_closure_info([_Inst|BeamCode], ClosureInfo) -> + find_closure_info(BeamCode, ClosureInfo); +find_closure_info([], ClosureInfo) -> + ClosureInfo. + +%%----------------------------------------------------------------------- +%% Is closure +%%----------------------------------------------------------------------- + +get_closure_info(MFA, [CI|Rest]) -> + case closure_info_mfa(CI) of + MFA -> CI; + _ -> get_closure_info(MFA, Rest) + end; +get_closure_info(_, []) -> + not_a_closure. + +%%----------------------------------------------------------------------- +%% Patch closure entry. +%%----------------------------------------------------------------------- + +%% NOTE: this changes the number of parameters in the ICode function, +%% but does *not* change the arity in the function name. Thus, all +%% closure-functions have the exact same names in Beam and in native +%% code, although they have different calling conventions. + +patch_closure_entry(Icode, ClosureInfo)-> + Arity = closure_info_arity(ClosureInfo), + %% ?msg("Arity ~w\n",[Arity]), + {Args, Closure, FreeVars} = + split_params(Arity, hipe_icode:icode_params(Icode), []), + [Start|_] = hipe_icode:icode_code(Icode), + {_LMin, LMax} = hipe_icode:icode_label_range(Icode), + hipe_gensym:set_label(icode,LMax+1), + {_VMin, VMax} = hipe_icode:icode_var_range(Icode), + hipe_gensym:set_var(icode,VMax+1), + MoveCode = gen_get_free_vars(FreeVars, Closure, + hipe_icode:label_name(Start)), + Icode1 = hipe_icode:icode_code_update(Icode, MoveCode ++ + hipe_icode:icode_code(Icode)), + Icode2 = hipe_icode:icode_params_update(Icode1, Args), + %% Arity - 1 since the original arity did not have the closure argument. + Icode3 = hipe_icode:icode_closure_arity_update(Icode2, Arity-1), + Icode3. + +%%----------------------------------------------------------------------- + +gen_get_free_vars(Vars, Closure, StartName) -> + [hipe_icode:mk_new_label()] ++ + get_free_vars(Vars, Closure, 1, []) ++ [hipe_icode:mk_goto(StartName)]. + +get_free_vars([V|Vs], Closure, No, MoveCode) -> + %% TempV = hipe_icode:mk_new_var(), + get_free_vars(Vs, Closure, No+1, + [%% hipe_icode:mk_move(TempV,hipe_icode:mk_const(No)), + hipe_icode:mk_primop([V], #closure_element{n=No}, [Closure]) + |MoveCode]); +get_free_vars([],_,_,MoveCode) -> + MoveCode. + +%%----------------------------------------------------------------------- + +split_params(1, [Closure|_OrgArgs] = Params, Args) -> + {lists:reverse([Closure|Args]), Closure, Params}; +split_params(1, [], Args) -> + Closure = hipe_icode:mk_new_var(), + {lists:reverse([Closure|Args]), Closure, []}; +split_params(N, [ArgN|OrgArgs], Args) -> + split_params(N-1, OrgArgs, [ArgN|Args]). + +%%----------------------------------------------------------------------- + +preprocess_code(ModuleCode) -> + PatchedCode = patch_R7_funs(ModuleCode), + ClosureInfo = find_closure_info(PatchedCode), + {PatchedCode, ClosureInfo}. + +%%----------------------------------------------------------------------- +%% Patches the "make_fun" BEAM instructions of R7 so that they also +%% contain the index that the BEAM loader generates for funs. +%% +%% The index starts from 0 and is incremented by 1 for each make_fun +%% instruction encountered. +%% +%% Retained only for compatibility with BEAM code prior to R8. +%% +%% Temporarily, it also rewrites R8-PRE-RELEASE "make_fun2" +%% instructions, since their embedded indices don't work. +%%----------------------------------------------------------------------- + +patch_R7_funs(ModuleCode) -> + patch_make_funs(ModuleCode, 0). + +patch_make_funs([FunCode0|Fs], FunIndex0) -> + {PatchedFunCode,FunIndex} = patch_make_funs(FunCode0, FunIndex0, []), + [PatchedFunCode|patch_make_funs(Fs, FunIndex)]; +patch_make_funs([], _) -> []. + +patch_make_funs([{make_fun,MFA,Magic,FreeVarNum}|Is], FunIndex, Acc) -> + Patched = {patched_make_fun,MFA,Magic,FreeVarNum,FunIndex}, + patch_make_funs(Is, FunIndex+1, [Patched|Acc]); +patch_make_funs([{make_fun2,MFA,_BogusIndex,Magic,FreeVarNum}|Is], FunIndex, Acc) -> + Patched = {patched_make_fun,MFA,Magic,FreeVarNum,FunIndex}, + patch_make_funs(Is, FunIndex+1, [Patched|Acc]); +patch_make_funs([I|Is], FunIndex, Acc) -> + patch_make_funs(Is, FunIndex, [I|Acc]); +patch_make_funs([], FunIndex, Acc) -> + {lists:reverse(Acc),FunIndex}. + +%%----------------------------------------------------------------------- + +find_mfa([{label,_}|Code]) -> + find_mfa(Code); +find_mfa([{func_info,{atom,M},{atom,F},A}|_]) + when is_atom(M), is_atom(F), is_integer(A), 0 =< A, A =< 255 -> + {M, F, A}. + +%%----------------------------------------------------------------------- + +%% Localize a particular function in a module +get_fun([[L, {func_info,{atom,M},{atom,F},A} | Is] | _], M,F,A) -> + [L, {func_info,{atom,M},{atom,F},A} | Is]; +get_fun([[_L1,_L2, {func_info,{atom,M},{atom,F},A} = MFA| _Is] | _], M,F,A) -> + ?WARNING_MSG("Consecutive labels found; please re-create the .beam file~n", []), + [_L1,_L2, MFA | _Is]; +get_fun([_|Rest], M,F,A) -> + get_fun(Rest, M,F,A). + +%%----------------------------------------------------------------------- +%% Takes a list of arguments and returns the constants of them into +%% fresh temporaries. Return a triple consisting of a list of move +%% instructions, a list of proper icode arguments and the new environment. +%%----------------------------------------------------------------------- + +get_constants_in_temps(Args, Env) -> + get_constants_in_temps(Args, [], [], Env). + +get_constants_in_temps([Arg|Args], Instrs, Temps, Env) -> + case get_constant_in_temp(Arg, Env) of + {none,ArgVar,Env1} -> + get_constants_in_temps(Args, Instrs, [ArgVar|Temps], Env1); + {Instr,Temp,Env1} -> + get_constants_in_temps(Args, [Instr|Instrs], [Temp|Temps], Env1) + end; +get_constants_in_temps([], Instrs, Temps, Env) -> + {lists:reverse(Instrs), lists:reverse(Temps), Env}. + +%% If Arg is a constant then put Arg in a fresh temp! +get_constant_in_temp(Arg, Env) -> + case is_var(Arg) of + true -> % Convert into Icode variable format before return + {none, mk_var(Arg), Env}; + false -> % Create a new temp and move the constant into it + Temp = mk_var(new), + Const = trans_const(Arg), + {hipe_icode:mk_move(Temp, Const), Temp, Env} + end. + +%%----------------------------------------------------------------------- +%% Makes a list of function arguments. +%%----------------------------------------------------------------------- + +extract_fun_args(A) -> + lists:reverse(extract_fun_args1(A)). + +extract_fun_args1(0) -> + []; +extract_fun_args1(1) -> + [mk_var({r,0})]; +extract_fun_args1(N) -> + [mk_var({x,N-1}) | extract_fun_args1(N-1)]. + +%%----------------------------------------------------------------------- +%% Auxiliary translation for arguments of select_val & select_tuple_arity +%%----------------------------------------------------------------------- + +trans_select_stuff(Reg, CaseList) -> + SwVar = case is_var(Reg) of + true -> + mk_var(Reg); + false -> + trans_const(Reg) + end, + CasePairs = trans_case_list(CaseList), + {SwVar,CasePairs}. + +trans_case_list([Symbol,{f,Lbl}|L]) -> + [{trans_const(Symbol),map_label(Lbl)} | trans_case_list(L)]; +trans_case_list([]) -> + []. + +%%----------------------------------------------------------------------- +%% Makes an Icode argument from a BEAM argument. +%%----------------------------------------------------------------------- + +trans_arg(Arg) -> + case is_var(Arg) of + true -> + mk_var(Arg); + false -> + trans_const(Arg) + end. + +%%----------------------------------------------------------------------- +%% Makes an Icode constant from a BEAM constant. +%%----------------------------------------------------------------------- + +trans_const(Const) -> + case Const of + {atom,Atom} when is_atom(Atom) -> + hipe_icode:mk_const(Atom); + {integer,N} when is_integer(N) -> + hipe_icode:mk_const(N); + {float,Float} when is_float(Float) -> + hipe_icode:mk_const(Float); + {string,String} -> + hipe_icode:mk_const(String); + {literal,Literal} -> + hipe_icode:mk_const(Literal); + nil -> + hipe_icode:mk_const([]); + Int when is_integer(Int) -> + hipe_icode:mk_const(Int) + end. + +%%----------------------------------------------------------------------- +%% Make an icode variable of proper type +%% (Variables mod 5) =:= 0 are X regs +%% (Variables mod 5) =:= 1 are Y regs +%% (Variables mod 5) =:= 2 are FR regs +%% (Variables mod 5) =:= 3 are new temporaries +%% (Variables mod 5) =:= 4 are new register temporaries +%% Tell hipe_gensym to update its state for each new thing created!! +%%----------------------------------------------------------------------- + +mk_var({r,0}) -> + hipe_icode:mk_var(0); +mk_var({x,R}) when is_integer(R) -> + V = 5*R, + hipe_gensym:update_vrange(icode,V), + hipe_icode:mk_var(V); +mk_var({y,R}) when is_integer(R) -> + V = (5*R)+1, + hipe_gensym:update_vrange(icode,V), + hipe_icode:mk_var(V); +mk_var({fr,R}) when is_integer(R) -> + V = (5*R)+2, + hipe_gensym:update_vrange(icode,V), + case get(hipe_inline_fp) of + true -> + hipe_icode:mk_fvar(V); + _ -> + hipe_icode:mk_var(V) + end; +mk_var(new) -> + T = hipe_gensym:new_var(icode), + V = (5*T)+3, + hipe_gensym:update_vrange(icode,V), + hipe_icode:mk_var(V); +mk_var(reg) -> + T = hipe_gensym:new_var(icode), + V = (5*T)+4, + hipe_gensym:update_vrange(icode,V), + hipe_icode:mk_reg(V). + +%%----------------------------------------------------------------------- +%% Make an icode label of proper type +%% (Labels mod 2) =:= 0 are actually occuring in the BEAM code +%% (Labels mod 2) =:= 1 are new labels generated by the translation +%%----------------------------------------------------------------------- + +mk_label(L) when is_integer(L) -> + LL = 2 * L, + hipe_gensym:update_lblrange(icode, LL), + hipe_icode:mk_label(LL); +mk_label(new) -> + L = hipe_gensym:new_label(icode), + LL = (2 * L) + 1, + hipe_gensym:update_lblrange(icode, LL), + hipe_icode:mk_label(LL). + +%% Maps from the BEAM's labelling scheme to our labelling scheme. +%% See mk_label to understand how it works. + +map_label(L) -> + L bsl 1. % faster and more type-friendly version of 2 * L + +%%----------------------------------------------------------------------- +%% Returns the type of the given variables. +%%----------------------------------------------------------------------- + +type({x,_}) -> + var; +type({y,_}) -> + var; +type({fr,_}) -> + var; +type({atom,A}) when is_atom(A) -> + #beam_const{value=A}; +type(nil) -> + #beam_const{value=[]}; +type({integer,X}) when is_integer(X) -> + #beam_const{value=X}; +type({float,X}) when is_float(X) -> + #beam_const{value=X}; +type({literal,X}) -> + #beam_const{value=X}. + +%%----------------------------------------------------------------------- +%% Returns true iff the argument is a variable. +%%----------------------------------------------------------------------- + +is_var({x,_}) -> + true; +is_var({y,_}) -> + true; +is_var({fr,_}) -> + true; +is_var({atom,A}) when is_atom(A) -> + false; +is_var(nil) -> + false; +is_var({integer,N}) when is_integer(N) -> + false; +is_var({float,F}) when is_float(F) -> + false; +is_var({literal,_Literal}) -> + false. + +%%----------------------------------------------------------------------- +%% Fixes the code for catches by adding some code. +%%----------------------------------------------------------------------- + +fix_catches(Code) -> + fix_catches(Code, gb_trees:empty()). + +%% We need to handle merged catch blocks, that is multiple 'catch' with +%% only one 'catch_end', or multiple 'try' with one 'try_case'. (Catch +%% and try can never be merged.) All occurrences of 'catch' or 'try' +%% with a particular fail-to label are assumed to only occur before the +%% corresponding 'catch_end'/'try_end' in the Beam code. + +fix_catches([{'catch',N,Lbl},ContLbl|Code], HandledCatchLbls) -> + fix_catch('catch',Lbl,ContLbl,Code,HandledCatchLbls,{catch_end,N}); +fix_catches([{'try',N,Lbl},ContLbl|Code], HandledCatchLbls) -> + fix_catch('try',Lbl,ContLbl,Code,HandledCatchLbls,{try_case,N}); +fix_catches([Instr|Code], HandledCatchLbls) -> + [Instr|fix_catches(Code, HandledCatchLbls)]; +fix_catches([], _HandledCatchLbls) -> + []. + +fix_catch(Type, Lbl, ContLbl, Code, HandledCatchLbls, Instr) -> + TLbl = {Type, Lbl}, + case gb_trees:lookup(TLbl, HandledCatchLbls) of + {value, Catch} when is_integer(Catch) -> + NewCode = fix_catches(Code, HandledCatchLbls), + Cont = hipe_icode:label_name(ContLbl), + [hipe_icode:mk_begin_try(Catch,Cont),ContLbl | NewCode]; + none -> + OldCatch = map_label(Lbl), + OldCatchLbl = hipe_icode:mk_label(OldCatch), + {CodeToCatch,RestOfCode} = split_code(Code,OldCatchLbl,Instr), + NewCatchLbl = mk_label(new), + NewCatch = hipe_icode:label_name(NewCatchLbl), + %% The rest of the code cannot contain catches with the same label. + RestOfCode1 = fix_catches(RestOfCode, HandledCatchLbls), + %% The catched code *can* contain more catches with the same label. + NewHandledCatchLbls = gb_trees:insert(TLbl, NewCatch, HandledCatchLbls), + CatchedCode = fix_catches(CodeToCatch, NewHandledCatchLbls), + %% The variables which will get the tag, value, and trace. + Vars = [mk_var({r,0}), mk_var({x,1}), mk_var({x,2})], + Cont = hipe_icode:label_name(ContLbl), + [hipe_icode:mk_begin_try(NewCatch,Cont), ContLbl] + ++ CatchedCode + ++ [mk_label(new), % dummy label before the goto + hipe_icode:mk_goto(OldCatch), % normal execution path + NewCatchLbl, % exception handing enters here + hipe_icode:mk_begin_handler(Vars)] + ++ catch_handler(Type, Vars, OldCatchLbl) + ++ RestOfCode1 % back to normal execution + end. + +catch_handler('try', _Vars, OldCatchLbl) -> + %% A try just falls through to the old fail-to label which marked the + %% start of the try_case block. All variables are set up as expected. + [OldCatchLbl]; +catch_handler('catch', [TagVar,ValueVar,TraceVar], OldCatchLbl) -> + %% This basically implements a catch as a try-expression. We must jump + %% to the given end label afterwards so we don't pass through both the + %% begin_handler and the end_try. + ContLbl = mk_label(new), + Cont = hipe_icode:label_name(ContLbl), + ThrowLbl = mk_label(new), + NoThrowLbl = mk_label(new), + ExitLbl = mk_label(new), + ErrorLbl = mk_label(new), + Dst = mk_var({r,0}), + [hipe_icode:mk_if('=:=', [TagVar, hipe_icode:mk_const('throw')], + hipe_icode:label_name(ThrowLbl), + hipe_icode:label_name(NoThrowLbl)), + ThrowLbl, + hipe_icode:mk_move(Dst, ValueVar), + hipe_icode:mk_goto(Cont), + NoThrowLbl, + hipe_icode:mk_if('=:=', [TagVar, hipe_icode:mk_const('exit')], + hipe_icode:label_name(ExitLbl), + hipe_icode:label_name(ErrorLbl)), + ExitLbl, + hipe_icode:mk_primop([Dst],mktuple,[hipe_icode:mk_const('EXIT'), + ValueVar]), + hipe_icode:mk_goto(Cont), + ErrorLbl, + %% We use the trace variable to hold the symbolic trace. Its previous + %% value is just that in p->ftrace, so get_stacktrace() works fine. + hipe_icode:mk_call([TraceVar],erlang,get_stacktrace,[],remote), + hipe_icode:mk_primop([ValueVar],mktuple, [ValueVar, TraceVar]), + hipe_icode:mk_goto(hipe_icode:label_name(ExitLbl)), + OldCatchLbl, % normal execution paths must go through end_try + hipe_icode:mk_end_try(), + hipe_icode:mk_goto(Cont), + ContLbl]. + +%% Note that it is the fail-to label that is the important thing, but +%% for 'catch' we want to make sure that the label is followed by the +%% 'catch_end' instruction - if it is not, we might have a real problem. +%% Checking that a 'try' label is followed by 'try_case' is not as +%% important, but we get that as a bonus. + +split_code([First|Code], Label, Instr) -> + split_code(Code, Label, Instr, First, []). + +split_code([Instr|Code], Label, Instr, Prev, As) when Prev =:= Label -> + split_code_final(Code, As); % drop both label and instruction +split_code([Other|_Code], Label, Instr, Prev, _As) when Prev =:= Label -> + ?EXIT({missing_instr_after_label, Label, Instr, [Other, Prev | _As]}); +split_code([Other|Code], Label, Instr, Prev, As) -> + split_code(Code, Label, Instr, Other, [Prev|As]); +split_code([], _Label, _Instr, Prev, As) -> + split_code_final([], [Prev|As]). + +split_code_final(Code, As) -> + {lists:reverse(As), Code}. + +%%----------------------------------------------------------------------- +%% Fixes fallthroughs +%%----------------------------------------------------------------------- + +fix_fallthroughs([]) -> + []; +fix_fallthroughs([I|Is]) -> + fix_fallthroughs(Is, I, []). + +fix_fallthroughs([I1|Is], I0, Acc) -> + case hipe_icode:is_label(I1) of + false -> + fix_fallthroughs(Is, I1, [I0 | Acc]); + true -> + case hipe_icode:is_branch(I0) of + true -> + fix_fallthroughs(Is, I1, [I0 | Acc]); + false -> + %% non-branch before label - insert a goto + Goto = hipe_icode:mk_goto(hipe_icode:label_name(I1)), + fix_fallthroughs(Is, I1, [Goto, I0 | Acc]) + end + end; +fix_fallthroughs([], I, Acc) -> + lists:reverse([I | Acc]). + +%%----------------------------------------------------------------------- +%% Removes the code between a fail instruction and the closest following +%% label. +%%----------------------------------------------------------------------- + +-spec remove_dead_code(icode_instrs()) -> icode_instrs(). +remove_dead_code([I|Is]) -> + case I of + #icode_fail{} -> + [I|remove_dead_code(skip_to_label(Is))]; + _ -> + [I|remove_dead_code(Is)] + end; +remove_dead_code([]) -> + []. + +%% returns the instructions from the closest label +-spec skip_to_label(icode_instrs()) -> icode_instrs(). +skip_to_label([I|Is] = Instrs) -> + case I of + #icode_label{} -> Instrs; + _ -> skip_to_label(Is) + end; +skip_to_label([]) -> + []. + +%%----------------------------------------------------------------------- +%% This needs to be extended in case new architectures are added. +%%----------------------------------------------------------------------- + +resolve_native_endianess(Flags) -> + case {Flags band 16#10, hipe_rtl_arch:endianess()} of + {16#10, big} -> + Flags band 5; + {16#10, little} -> + (Flags bor 2) band 7; + _ -> + Flags band 7 + end. + +%%----------------------------------------------------------------------- +%% Potentially useful for debugging. +%%----------------------------------------------------------------------- + +pp_beam(BeamCode, Options) -> + case proplists:get_value(pp_beam, Options) of + true -> + pp(BeamCode); + {file,FileName} -> + {ok,File} = file:open(FileName, [write]), + pp(File, BeamCode); + _ -> %% includes "false" case + ok + end. + +pp(Code) -> + pp(standard_io, Code). + +pp(Stream, []) -> + case Stream of %% I am not sure whether this is necessary + standard_io -> ok; + _ -> ok = file:close(Stream) + end; +pp(Stream, [FunCode|FunCodes]) -> + pp_mfa(Stream, FunCode), + put_nl(Stream), + pp(Stream, FunCodes). + +pp_mfa(Stream, FunCode) -> + lists:foreach(fun(Instr) -> print_instr(Stream, Instr) end, FunCode). + +print_instr(Stream, {label,Lbl}) -> + io:format(Stream, " label ~p:\n", [Lbl]); +print_instr(Stream, Op) -> + io:format(Stream, " ~p\n", [Op]). + +put_nl(Stream) -> + io:format(Stream, "\n", []). + +%%----------------------------------------------------------------------- +%% Handling of environments -- used to process local tail calls. +%%----------------------------------------------------------------------- + +%% Construct an environment +env__mk_env(M, F, A, Entry) -> + #environment{mfa={M,F,A}, entry=Entry}. + +%% Get current MFA +env__get_mfa(#environment{mfa=MFA}) -> MFA. + +%% Get entry point of the current function +env__get_entry(#environment{entry=EP}) -> EP. + +%%----------------------------------------------------------------------- diff --git a/lib/hipe/icode/hipe_icode.erl b/lib/hipe/icode/hipe_icode.erl new file mode 100644 index 0000000000..a4614d7237 --- /dev/null +++ b/lib/hipe/icode/hipe_icode.erl @@ -0,0 +1,1820 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% HiPE Intermediate Code +%% ==================================================================== +%% Filename : hipe_icode.erl +%% Module : hipe_icode +%% Purpose : Provide primops for the Icode data structure. +%% History : 1997-? Erik Johansson ([email protected]): Created. +%% 2001-01-30 EJ ([email protected]): +%% Apply, primop, guardop removed +%% 2003-03-15 ES ([email protected]): +%% Started commenting in Edoc. +%% Moved pretty printer to separate file. +%% +%% $Id$ +%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%@doc +%% This module implements "Linear Icode" and Icode instructions. +%% +%% <p> Icode is a simple (in that it has few instructions) imperative +%% language, used as the first Intermediate Code in the HiPE compiler. +%% Icode is closely related to Erlang, and Icode instructions operate +%% on Erlang terms. </p> +%% +%% <h2><a href="#type-icode">Icode</a></h2> +%% +%% <p> Linear Icode for a function consists of: +%% <ul> +%% <li> the function's name (`{M,F,A}'), </li> +%% <li> a list of parameters, </li> +%% <li> a list of instructions, </li> +%% <li> data, </li> +%% <li> information about whether the function is a leaf function, </li> +%% <li> information about whether the function is a closure, and </li> +%% <li> the range for labels and variables in the code. </li> +%% </ul> +%% </p> +%% +%% <h2><a href="#type-icode_instruction">Icode Instructions</a> (and +%% their components)</h2> +%% +%% Control flow: +%% <dl> +%% <dt><code><a href="#type-if">'if'</a> +%% {Cond::<a href="#type-cond">cond()</a>, +%% Args::[<a href="#type-arg">arg()</a>], +%% TrueLabel::<a href="#type-label_name">label_name()</a>, +%% FalseLabel::<a href="#type-label_name">label_name()</a> +%% } :: +%% <a href="#type-icode_instruction">icode_instr()</a></code></dt> +%% <dd> +%% The if instruction compares the arguments (Args) with +%% condition (Cond) and jumps to either TrueLabel or +%% FalseLabel. (At the moment...) There are only binary +%% conditions so the number of arguments should be two. +%% <p> +%% An if instructions ends a basic block and should be followed +%% by a label (or be the last instruction of the code). +%% </p></dd> +%% +%% <dt><code><a href="#type-switch_val">switch_val</a> +%% {Term::<a href="#type-arg">var()</a>, +%% FailLabel::<a href="#type-label_name">label_name()</a>, +%% Length::integer(), +%% Cases::[{<a href="#type-symbol">symbol()</a>,<a +%% href="#type-label_name">label_name()</a>}] +%% }:: +%% <a href="#type-icode_instruction">icode_instr()</a></code></dt> +%% <dd> +%% The switch_val instruction compares the argument Term to the +%% symbols in the lists Cases, control is transfered to the label +%% that corresponds to the first symbol that matches. If no +%% symbol matches control is transfered to FailLabel. (NOTE: The +%% length argument is not currently in use.) +%% <p> +%% The switch_val instruction can be assumed to be implemented as +%% efficiently as possible given the symbols in the case +%% list. (Jump-table, bianry-serach, or nested ifs) +%% </p><p> +%% A switch_val instructions ends a basic block and should be +%% followed by a label (or be the last instruction of the code). +%% </p></dd> +%% +%% <dt><code><a href="#type-switch_tuple_arity">switch_tuple_arity</a> +%% {Term::<a href="#type-arg">var()</a>, +%% FailLabel::<a href="#type-label_name">label_name()</a>, +%% Length::integer(), +%% Cases::[{integer(),<a href="#type-label_name">label_name()</a>}] +%% }:: +%% <a href="#type-icode_instruction">icode_instr()</a></code></dt> +%% <dd> +%% The switch_tuple_arity instruction compares the size of the +%% tuple in the argument Term to the integers in the lists Cases, +%% control is transfered to the label that corresponds to the +%% first integer that matches. If no integer matches control is +%% transfered to FailLabel. (NOTE: The length argument is not +%% currently in use.) +%% <p> +%% The switch_tuple_arity instruction can be assumed to be +%% implemented as efficently as possible given the symbols in the +%% case list. (Jump-table, bianry-serach, or nested ifs) +%% </p><p> +%% A switch_tuple_arity instructions ends a basic block and +%% should be followed by a label (or be the last instruction of +%% the code). +%% </p></dd> +%% +%% <dt>`type {typ_expr, arg, true_label, false_label}}'</dt> +%% <dt>`goto {label}'</dt> +%% <dt>`label {name}'</dt> +%% </dl> +%% +%% Moves: +%% <dl> +%% <dt>`move {dst, src}'</dt> +%% <dt>`phi {dst, arglist}'</dt> +%% </dl> +%% +%% Function application: +%% <dl> +%% <dt>`call {[dst], fun, [arg], type, continuation, fail, +%% in_guard}'</dt> +%% <dd> +%% Where `type' is one of {`local', `remote', `primop'} +%% and `in_guard' is either `true' or `false'.</dd> +%% <dt>`enter {fun, [arg], type}'</dt> +%% <dd> +%% Where `type' is one of {`local', `remote', `primop'} +%% and `in_guard' is either `true' or `false'.</dd> +%% <dt>`return {[var]}'</dt> +%% <dd> +%% <strong>WARNING:</strong> Multiple return values are yet not +%% fully implemented and tested. +%% </dd> +%% </dl> +%% +%% Error handling: +%% <dl> +%% <dt>`begin_try {label, successor}'</dt> +%% <dt>`end_try'</dt> +%% <dt>`begin_handler {dstlist}'</dt> +%% <dt>`fail {Args, Class}'</dt> +%% <dd>Where `Class' is one of +%% {`exit', `throw', `error', `rethrow'}. For `error/2', `[args]' +%% is `[Reason,Trace]'. For `rethrow', `Args' is +%% `[Exception,Reason]' - this only occurs in autogenerated code. +%% </dd> +%% </dl> +%% +%% Comments: +%% <dl> +%% <dt>`comment{Text::string()}'</dt> +%% </dl> +%% +%% <h4>Notes</h4> +%% +%% <p> A constant can only show up on the RHS of a `move' instruction +%% and in `if' and `switch_*'</p> +%% <p> +%% Classification of primops should be like this: +%% <ul> +%% <li> `erlang:exit/1, erlang:throw/1, erlang:error/1, +%% erlang:error/2, erlang:fault/1', +%% and `erlang:fault/2' should use the +%% {@link fail(). fail-instruction} in Icode.</li> +%% <li> Calls or tail-recursive calls to BIFs, operators, or internal +%% functions should be implemented with `call' or `enter' +%% respectively, with the primop flag set.</li> +%% <li> All other Erlang functions should be implemented with `call' +%% or `enter' respectively, without the primop flag set.</li> +%% </ul> +%% </p> +%% +%% <h4>Primops</h4> +%% +%% <pre> +%% Constructors: +%% cons - [Car, Cdr] +%% mktuple - [Element1, Element2, ..., ElementN] +%% call_fun - [BoundArg1, ..., BoundArgN, Fun] +%% enter_fun - [BoundArg1, ..., BoundArgN, Fun] +%% #mkfun{} - [FreeVar1, FreeVar2, ..., FreeVarN] +%% +%% Binaries: +%% bs_init +%% {bs_put_string, Bytes, Size} +%% bs_final +%% +%% Selectors: +%% element - [Index, Tuple] +%% unsafe_hd - [List] +%% unsafe_tl - [List] +%% #unsafe_element{} - [Tuple] +%% #unsafe_update_element{} - [Tuple, Val] +%% #closure_element{} - [Fun] +%% +%% Arithmetic: [Arg1, Arg2] +%% '+', '-', '*', '/', 'div', 'rem', +%% 'band', 'bor', 'bxor', 'bnot', 'bsl', 'bsr' +%% +%% Receive: +%% check_get_msg - [] +%% next_msg - [] +%% select_msg - [] +%% set_timeout - [Timeout] +%% clear_timeout - [] +%% suspend_msg - [] +%% +%% </pre> +%% +%% <h4>Guardops: (primops that can be used in guards and can fail)</h4> +%% <pre> +%% Selectors: +%% unsafe_hd - [List] +%% unsafe_tl - [List] +%% #element{} - [Index, Tuple] +%% #unsafe_element{} - [Tuple] +%% +%% Arithmetic: [Arg1, Arg2] +%% '+', '-', '*', '/', 'div', 'rem', +%% 'band', 'bor', 'bxor', 'bnot', 'bsl', 'bsr', +%% fix_add, fix_sub %% Do these exist? +%% +%% Concurrency: +%% {erlang,self,0} - [] +%% </pre> +%% +%% +%% <h4>Relational Operations (Cond in if instruction)</h4> +%% <pre> +%% gt, lt, geq, leq, +%% eqeq, neq, exact_eqeq, exact_neq +%% </pre> +%% +%% <h4>Type tests</h4> +%% <pre> +%% list +%% nil +%% cons +%% tuple +%% {tuple, N} +%% atom +%% {atom, Atom} +%% constant +%% number +%% integer +%% {integer, N} +%% fixnum +%% bignum +%% float +%% pid +%% port +%% {record, Atom, Size} +%% reference +%% binary +%% function +%% </pre> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%===================================================================== + +-module(hipe_icode). + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). + +%% @type icode(Fun, Params, IsClosure, IsLeaf, Code, Data, VarRange,LabelRange) +%% Fun = mfa() +%% Params = [var()] +%% IsClosure = boolean() +%% IsLeaf = boolean() +%% Code = [icode_instr()] +%% Data = data() +%% VarRange = {integer(),integer()} +%% LabelRange = {integer(),integer()} +%% +%% @type icode_instr(I) +%% I = if() | switch_val() | switch_tuple_arity() | type() | goto() +%% | label() | move() | phi() | call() | enter() | return() +%% | begin_try() | end_try() | begin_handler() | fail() | comment() +%% +%% @type if(Cond, Args, TrueLabel, FalseLabel) +%% Cond = cond() +%% Args = [arg()] +%% TrueLabel = label_name() +%% FalseLabel = label_name() +%% +%% @type switch_val(Term, FailLabel, Length, Cases) +%% Term = var() +%% FailLabel = label_name() +%% Length = integer() +%% Cases = [{symbol(),label_name()}] +%% +%% @type switch_tuple_arity(Arg, FailLabel, Length, Cases) +%% Term = var() +%% FailLabel = label_name() +%% Length = integer() +%% Cases = [{symbol(),label_name()}] +%% +%% @type type(TypeTest, Arg, True_label, False_label) +%% TypeTest = type_test() +%% Args = [arg()] +%% TrueLabel = label_name() +%% FalseLabel = label_name() +%% +%% @type goto(Label) Label = label_name() +%% +%% @type label(Name) Name = label_name() +%% +%% @type move(Dst, Src) Dst = var() Src = arg() +%% +%% @type phi(Dst, Id, Arglist) +%% Dst = var() | fvar() +%% Id = var() | fvar() +%% Arglist = [{Pred, Src}] +%% Pred = label_name() +%% Src = var() | fvar() +%% +%% @type call(Dst, Fun, Arg, Type, Continuation, FailLabel, InGuard) +%% Dst = [var()] +%% Fun = mfa() | primop() | closure() +%% Arg = [var()] +%% Type = call_type() +%% Continuation = [] | label_name() +%% FailLabel = [] | label_name() +%% InGuard = boolean() +%% +%% @type enter(Fun, Arg, Type) +%% Fun = mfa() | primop() | closure() +%% Arg = [var()] +%% Type = call_type() +%% +%% @type return (Vars) Vars = [var()] +%% +%% @type begin_try(FailLabel, Successor) +%% FailLabel = label_name() +%% Successor = label_name() +%% +%% @type end_try() +%% +%% @type begin_handler(Dst) +%% Dst = [var()] +%% +%% @type fail(Class, Args, Label) +%% Class = exit_class() +%% Args = [var()] +%% Label = label_name() +%% +%% @type comment(Text) Text = string() + +%% @type call_type() = 'local' | 'remote' | 'primop' +%% @type exit_class() = 'exit' | 'throw' | 'error' | 'rethrow' +%% @type cond() = gt | lt | geq | leq | eqeq | neq | exact_eqeq | exact_neq +%% @type type_test() = +%% list +%% | nil +%% | cons +%% | tuple +%% | {tuple, integer()} +%% | atom +%% | {atom, atom()} +%% | constant +%% | number +%% | integer +%% | {integer, integer()} +%% | fixnum +%% | bignum +%% | float +%% | pid +%% | port +%% | {record, atom(), integer()} +%% | reference +%% | binary +%% | function +%% +%% @type mfa(Mod,Fun,Arity) = {atom(),atom(),arity()} + +%% @type arg() = var() | const() +%% @type farg() = fvar() | float() +%% @type var(Name) Name = integer() +%% @type fvar(Name) Name = integer() +%% @type label_name(Name) Name = integer() +%% @type symbol(S) = atom() | number() +%% @type const(C) C = immediate() +%% @type immediate(I) = I +%% I = term() +%% @end + + +%% ____________________________________________________________________ +%% +%% Exports +%% +-export([mk_icode/7, %% mk_icode(Fun, Params, IsClosure, IsLeaf, + %% Code, VarRange, LabelRange) + mk_icode/8, %% mk_icode(Fun, Params, IsClosure, IsLeaf, + %% Code, Data, VarRange, LabelRange) + icode_fun/1, + icode_params/1, + icode_params_update/2, + icode_is_closure/1, + icode_closure_arity/1, + icode_closure_arity_update/2, + icode_is_leaf/1, + icode_code/1, + icode_code_update/2, + icode_data/1, + %% icode_data_update/2, + icode_var_range/1, + icode_label_range/1, + icode_info/1, + icode_info_update/2]). + +-export([mk_if/4, %% mk_if(Op, Args, TrueLbl, FalseLbl) + %% mk_if/5, %% mk_if(Op, Args, TrueLbl, FalseLbl, Prob) + if_op/1, + if_op_update/2, + if_true_label/1, + if_false_label/1, + if_args/1, + if_pred/1, + %% is_if/1, + + mk_switch_val/4, + %% mk_switch_val/5, + switch_val_term/1, + switch_val_fail_label/1, + %% switch_val_length/1, + switch_val_cases/1, + switch_val_cases_update/2, + %% is_switch_val/1, + + mk_switch_tuple_arity/4, + %% mk_switch_tuple_arityl/5, + switch_tuple_arity_term/1, + switch_tuple_arity_fail_label/1, + switch_tuple_arity_fail_label_update/2, + %% switch_tuple_arity_length/1, + switch_tuple_arity_cases/1, + switch_tuple_arity_cases_update/2, + %% is_switch_tuple_arity/1, + + mk_type/4, %% mk_type(Args, Type, TrueLbl, FalseLbl) + mk_type/5, %% mk_type(Args, Type, TrueLbl, FalseLbl, P) + type_args/1, + %% type_args_update/2, + type_test/1, + type_true_label/1, + type_false_label/1, + type_pred/1, + is_type/1, + + mk_guardop/5, %% mk_guardop(Dst, Fun, Args, Continuation, Fail) + mk_primop/3, %% mk_primop(Dst, Fun, Args) + mk_primop/5, %% mk_primop(Dst, Fun, Args, Cont, Fail) + mk_call/5, %% mk_call(Dst, Mod, Fun, Args, Type) + %% mk_call/7, %% mk_call(Dst, Mod, Fun, Args, Type, + %% Continuation, Fail) + mk_call/8, %% mk_call(Dst, Mod, Fun, Args, Type, + %% Continuation, Fail, Guard) + call_dstlist/1, + call_dstlist_update/2, + %% call_dst_type/1, + call_args/1, + call_args_update/2, + call_fun/1, + call_fun_update/2, + call_type/1, + call_continuation/1, + call_fail_label/1, + call_set_fail_label/2, + call_set_continuation/2, + is_call/1, + call_in_guard/1, + + mk_goto/1, %% mk_goto(Lbl) + goto_label/1, + + mk_enter/4, %% mk_enter(Mod, Fun, Args, Type) + mk_enter_primop/2, %% mk_enter_primop(Op, Type) + enter_fun/1, + enter_fun_update/2, + enter_args/1, + enter_args_update/2, + enter_type/1, + is_enter/1, + + + mk_return/1, %% mk_return(Vars) + %% mk_fail/1, %% mk_fail(Args) class = exit + mk_fail/2, %% mk_fail(Args, Class) + %% mk_fail/3, %% mk_fail(Args, Class, Label) + mk_move/2, %% mk_move(Dst, Src) + %% mk_moves/2, %% mk_moves(DstList, SrcList) + mk_begin_try/2, %% mk_begin_try(Label, Successor) + mk_begin_handler/1, %% mk_begin_handler(ReasonDst) + mk_end_try/0, %% mk_end_try() + %% mk_elements/2, %% mk_elements(Tuple, Vars) + mk_label/1, %% mk_label(Name) + mk_new_label/0, %% mk_new_label() + mk_comment/1, %% mk_comment(Text) + mk_const/1, %% mk_const(Const) + mk_var/1, %% mk_var(Id) + annotate_variable/2, %% annotate_var_or_reg(VarOrReg, Type) + unannotate_variable/1,%% unannotate_var_or_reg(VarOrReg) + mk_reg/1, %% mk_reg(Id) + mk_fvar/1, %% mk_fvar(Id) + mk_new_var/0, %% mk_new_var() + mk_new_fvar/0, %% mk_new_fvar() + mk_new_reg/0, %% mk_new_reg() + mk_phi/1, %% mk_phi(Id) + mk_phi/2 %% mk_phi(Id, ArgList) + ]). + +%% +%% Identifiers +%% + +-export([%% is_fail/1, + is_return/1, + is_move/1, + %% is_begin_try/1, + %% is_begin_handler/1, + %% is_end_try/1, + is_goto/1, + is_label/1, + is_comment/1, + is_const/1, + is_var/1, + is_fvar/1, + is_reg/1, + is_variable/1, + is_annotated_variable/1, + %% is_uncond/1, + is_phi/1]). + +%% +%% Selectors +%% + +-export([phi_dst/1, + phi_id/1, + %% phi_args/1, + phi_arg/2, + phi_arglist/1, + phi_enter_pred/3, + phi_remove_pred/2, + phi_redirect_pred/3, + move_dst/1, + move_src/1, + move_src_update/2, + begin_try_label/1, + begin_try_successor/1, + begin_handler_dstlist/1, + label_name/1, + comment_text/1, + return_vars/1, + fail_args/1, + fail_class/1, + fail_label/1, + fail_set_label/2, + var_name/1, + variable_annotation/1, + fvar_name/1, + reg_name/1, + reg_is_gcsafe/1, + const_value/1 + ]). + +%% +%% Misc +%% + +-export([args/1, + uses/1, + defines/1, + is_safe/1, + strip_comments/1, + subst/2, + subst_uses/2, + subst_defines/2, + redirect_jmp/3, + successors/1, + fails_to/1, + is_branch/1 + ]). + +-export([highest_var/1, highest_label/1]). + +%%--------------------------------------------------------------------- +%% +%% Icode +%% +%%--------------------------------------------------------------------- + +-spec mk_icode(mfa(), [icode_var()], boolean(), boolean(), [icode_instr()], + {non_neg_integer(),non_neg_integer()}, + {icode_lbl(),icode_lbl()}) -> #icode{}. +mk_icode(Fun, Params, IsClosure, IsLeaf, Code, VarRange, LabelRange) -> + #icode{'fun'=Fun, params=Params, code=Code, + is_closure=IsClosure, + is_leaf=IsLeaf, + data=hipe_consttab:new(), + var_range=VarRange, + label_range=LabelRange}. + +-spec mk_icode(mfa(), [icode_var()], boolean(), boolean(), [icode_instr()], + hipe_consttab(), {non_neg_integer(),non_neg_integer()}, + {icode_lbl(),icode_lbl()}) -> #icode{}. +mk_icode(Fun, Params, IsClosure, IsLeaf, Code, Data, VarRange, LabelRange) -> + #icode{'fun'=Fun, params=Params, code=Code, + data=Data, is_closure=IsClosure, is_leaf=IsLeaf, + var_range=VarRange, label_range=LabelRange}. + +-spec icode_fun(#icode{}) -> mfa(). +icode_fun(#icode{'fun' = MFA}) -> MFA. + +-spec icode_params(#icode{}) -> [icode_var()]. +icode_params(#icode{params = Params}) -> Params. + +-spec icode_params_update(#icode{}, [icode_var()]) -> #icode{}. +icode_params_update(Icode, Params) -> + Icode#icode{params = Params}. + +-spec icode_is_closure(#icode{}) -> boolean(). +icode_is_closure(#icode{is_closure = Closure}) -> Closure. + +-spec icode_is_leaf(#icode{}) -> boolean(). +icode_is_leaf(#icode{is_leaf = Leaf}) -> Leaf. + +-spec icode_code(#icode{}) -> icode_instrs(). +icode_code(#icode{code = Code}) -> Code. + +-spec icode_code_update(#icode{}, icode_instrs()) -> #icode{}. +icode_code_update(Icode, NewCode) -> + Vmax = highest_var(NewCode), + Lmax = highest_label(NewCode), + Icode#icode{code = NewCode, var_range = {0,Vmax}, label_range = {0,Lmax}}. + +-spec icode_data(#icode{}) -> hipe_consttab(). +icode_data(#icode{data=Data}) -> Data. + +%% %% -spec icode_data_update(#icode{}, hipe_consttab()) -> #icode{}. +%% icode_data_update(Icode, NewData) -> Icode#icode{data=NewData}. + +-spec icode_var_range(#icode{}) -> {non_neg_integer(), non_neg_integer()}. +icode_var_range(#icode{var_range = VarRange}) -> VarRange. + +-spec icode_label_range(#icode{}) -> {non_neg_integer(), non_neg_integer()}. +icode_label_range(#icode{label_range = LabelRange}) -> LabelRange. + +-spec icode_info(#icode{}) -> icode_info(). +icode_info(#icode{info = Info}) -> Info. + +-spec icode_info_update(#icode{}, icode_info()) -> #icode{}. +icode_info_update(Icode, Info) -> Icode#icode{info = Info}. + +-spec icode_closure_arity(#icode{}) -> arity(). +icode_closure_arity(#icode{closure_arity = Arity}) -> Arity. + +-spec icode_closure_arity_update(#icode{}, arity()) -> #icode{}. +icode_closure_arity_update(Icode, Arity) -> Icode#icode{closure_arity = Arity}. + + +%%---------------------------------------------------------------------------- +%% Instructions +%%---------------------------------------------------------------------------- + +%%---- +%% if +%%---- + +-spec mk_if(icode_if_op(), [icode_term_arg()], + icode_lbl(), icode_lbl()) -> #icode_if{}. +mk_if(Op, Args, TrueLbl, FalseLbl) -> + #icode_if{op=Op, args=Args, true_label=TrueLbl, false_label=FalseLbl, p=0.5}. +%% mk_if(Op, Args, TrueLbl, FalseLbl, P) -> +%% #icode_if{op=Op, args=Args, true_label=TrueLbl, false_label=FalseLbl, p=P}. + +-spec if_op(#icode_if{}) -> icode_if_op(). +if_op(#icode_if{op=Op}) -> Op. + +-spec if_op_update(#icode_if{}, icode_if_op()) -> #icode_if{}. +if_op_update(IF, NewOp) -> IF#icode_if{op=NewOp}. + +-spec if_args(#icode_if{}) -> [icode_term_arg()]. +if_args(#icode_if{args=Args}) -> Args. + +-spec if_true_label(#icode_if{}) -> icode_lbl(). +if_true_label(#icode_if{true_label=TrueLbl}) -> TrueLbl. + +-spec if_true_label_update(#icode_if{}, icode_lbl()) -> #icode_if{}. +if_true_label_update(IF, TrueLbl) -> IF#icode_if{true_label=TrueLbl}. + +-spec if_false_label(#icode_if{}) -> icode_lbl(). +if_false_label(#icode_if{false_label=FalseLbl}) -> FalseLbl. + +-spec if_false_label_update(#icode_if{}, icode_lbl()) -> #icode_if{}. +if_false_label_update(IF, FalseLbl) -> IF#icode_if{false_label=FalseLbl}. + +-spec if_pred(#icode_if{}) -> float(). +if_pred(#icode_if{p=P}) -> P. + +%%------------ +%% switch_val +%%------------ + +-spec mk_switch_val(icode_var(), icode_lbl(), + non_neg_integer(), [icode_switch_case()]) -> + #icode_switch_val{}. +mk_switch_val(Term = #icode_variable{kind='var'}, FailLbl, Length, Cases) -> + #icode_switch_val{term=Term, fail_label=FailLbl, length=Length, cases=Cases}. + +-spec switch_val_term(#icode_switch_val{}) -> icode_var(). +switch_val_term(#icode_switch_val{term=Term}) -> Term. + +-spec switch_val_fail_label(#icode_switch_val{}) -> icode_lbl(). +switch_val_fail_label(#icode_switch_val{fail_label=FailLbl}) -> FailLbl. + +-spec switch_val_fail_label_update(#icode_switch_val{}, icode_lbl()) -> + #icode_switch_val{}. +switch_val_fail_label_update(SV, FailLbl) -> + SV#icode_switch_val{fail_label=FailLbl}. + +%% switch_val_length(#icode_switch_val{length=Length}) -> Length. + +-spec switch_val_cases(#icode_switch_val{}) -> [icode_switch_case()]. +switch_val_cases(#icode_switch_val{cases=Cases}) -> Cases. + +-spec switch_val_cases_update(#icode_switch_val{}, [icode_switch_case()]) -> + #icode_switch_val{}. +switch_val_cases_update(SV, NewCases) -> + SV#icode_switch_val{cases = NewCases}. + +%%-------------------- +%% switch_tuple_arity +%%-------------------- + +-spec mk_switch_tuple_arity(icode_var(), icode_lbl(), + non_neg_integer(), [icode_switch_case()]) -> + #icode_switch_tuple_arity{}. +mk_switch_tuple_arity(Term = #icode_variable{kind='var'}, FailLbl, Length, Cases) -> + #icode_switch_tuple_arity{term=Term, fail_label=FailLbl, + length=Length, cases=Cases}. + +-spec switch_tuple_arity_term(#icode_switch_tuple_arity{}) -> icode_var(). +switch_tuple_arity_term(#icode_switch_tuple_arity{term=Term}) -> Term. + +-spec switch_tuple_arity_fail_label(#icode_switch_tuple_arity{}) -> icode_lbl(). +switch_tuple_arity_fail_label(#icode_switch_tuple_arity{fail_label=FailLbl}) -> + FailLbl. + +-spec switch_tuple_arity_fail_label_update(#icode_switch_tuple_arity{}, icode_lbl()) -> + #icode_switch_tuple_arity{}. +switch_tuple_arity_fail_label_update(S, FailLbl) -> + S#icode_switch_tuple_arity{fail_label=FailLbl}. + +%% switch_tuple_arity_length(#icode_switch_tuple_arity{length=Length}) -> Length. + +-spec switch_tuple_arity_cases(#icode_switch_tuple_arity{}) -> [icode_switch_case()]. +switch_tuple_arity_cases(#icode_switch_tuple_arity{cases=Cases}) -> Cases. + +-spec switch_tuple_arity_cases_update(#icode_switch_tuple_arity{}, + [icode_switch_case()]) -> + #icode_switch_tuple_arity{}. +switch_tuple_arity_cases_update(Cond, NewCases) -> + Cond#icode_switch_tuple_arity{cases = NewCases}. + +%%------ +%% type +%%------ + +-spec mk_type([icode_term_arg()], icode_type_test(), icode_lbl(), icode_lbl()) -> + #icode_type{}. +mk_type(Args, Test, TrueLbl, FalseLbl) -> + mk_type(Args, Test, TrueLbl, FalseLbl, 0.5). + +-spec mk_type([icode_term_arg()], icode_type_test(), + icode_lbl(), icode_lbl(), float()) -> #icode_type{}. +mk_type(Args, Test, TrueLbl, FalseLbl, P) -> + #icode_type{test=Test, args=Args, + true_label=TrueLbl, false_label=FalseLbl, p=P}. + +-spec type_test(#icode_type{}) -> icode_type_test(). +type_test(#icode_type{test=Test}) -> Test. + +-spec type_args(#icode_type{}) -> [icode_term_arg()]. +type_args(#icode_type{args=Args}) -> Args. + +%% type_args_update(T, Args) -> T#icode_type{args=Args}. + +-spec type_true_label(#icode_type{}) -> icode_lbl(). +type_true_label(#icode_type{true_label=TrueLbl}) -> TrueLbl. + +-spec type_false_label(#icode_type{}) -> icode_lbl(). +type_false_label(#icode_type{false_label=FalseLbl}) -> FalseLbl. + +-spec type_pred(#icode_type{}) -> float(). +type_pred(#icode_type{p=P}) -> P. + +-spec is_type(icode_instr()) -> boolean(). +is_type(#icode_type{}) -> true; +is_type(_) -> false. + +%%------ +%% goto +%%------ + +-spec mk_goto(icode_lbl()) -> #icode_goto{}. +mk_goto(Lbl) -> #icode_goto{label=Lbl}. + +-spec goto_label(#icode_goto{}) -> icode_lbl(). +goto_label(#icode_goto{label=Lbl}) -> Lbl. + +-spec is_goto(icode_instr()) -> boolean(). +is_goto(#icode_goto{}) -> true; +is_goto(_) -> false. + +%%-------- +%% return +%%-------- + +-spec mk_return([icode_var()]) -> #icode_return{}. +mk_return(Vars) -> #icode_return{vars=Vars}. + +-spec return_vars(#icode_return{}) -> [icode_var()]. +return_vars(#icode_return{vars=Vars}) -> Vars. + +-spec is_return(icode_instr()) -> boolean(). +is_return(#icode_return{}) -> true; +is_return(_) -> false. + +%%------ +%% fail +%%------ + +%% mk_fail(Args) when is_list(Args) -> mk_fail(Args, error). + +-spec mk_fail([icode_term_arg()], icode_exit_class()) -> #icode_fail{}. +mk_fail(Args, Class) when is_list(Args) -> + case Class of + error -> ok; + exit -> ok; + rethrow -> ok; + throw -> ok + end, + #icode_fail{class=Class, args=Args}. + +%% mk_fail(Args, Class, Label) when is_list(Args) -> +%% #icode_fail{class=Class, args=Args, fail_label=Label}. + +-spec fail_class(#icode_fail{}) -> icode_exit_class(). +fail_class(#icode_fail{class=Class}) -> Class. + +-spec fail_args(#icode_fail{}) -> [icode_term_arg()]. +fail_args(#icode_fail{args=Args}) -> Args. + +-spec fail_label(#icode_fail{}) -> [] | icode_lbl(). +fail_label(#icode_fail{fail_label=Label}) -> Label. + +-spec fail_set_label(#icode_fail{}, [] | icode_lbl()) -> #icode_fail{}. +fail_set_label(I=#icode_fail{}, Label) -> + I#icode_fail{fail_label = Label}. + +%%------ +%% move +%%------ + +-spec mk_move(#icode_variable{}, #icode_variable{} | #icode_const{}) -> + #icode_move{}. +mk_move(Dst, Src) -> + case Src of + #icode_variable{} -> ok; + #icode_const{} -> ok + end, + #icode_move{dst=Dst, src=Src}. + +-spec move_dst(#icode_move{}) -> #icode_variable{}. +move_dst(#icode_move{dst=Dst}) -> Dst. + +-spec move_src(#icode_move{}) -> #icode_variable{} | #icode_const{}. +move_src(#icode_move{src=Src}) -> Src. + +-spec move_src_update(#icode_move{}, #icode_variable{} | #icode_const{}) -> + #icode_move{}. +move_src_update(M, NewSrc) -> M#icode_move{src=NewSrc}. + +-spec is_move(icode_instr()) -> boolean(). +is_move(#icode_move{}) -> true; +is_move(_) -> false. + +%%----- +%% phi +%%----- + +%% The id field is not entirely redundant. It is used in mappings +%% in the SSA pass since the dst field can change. +-spec mk_phi(#icode_variable{}) -> #icode_phi{}. +mk_phi(Var) -> #icode_phi{dst=Var, id=Var, arglist=[]}. + +-spec mk_phi(#icode_variable{}, [{icode_lbl(), #icode_variable{}}]) -> + #icode_phi{}. +mk_phi(Var, ArgList) -> #icode_phi{dst=Var, id=Var, arglist=ArgList}. + +-spec phi_dst(#icode_phi{}) -> #icode_variable{}. +phi_dst(#icode_phi{dst=Dst}) -> Dst. + +-spec phi_id(#icode_phi{}) -> #icode_variable{}. +phi_id(#icode_phi{id=Id}) -> Id. + +-spec phi_arglist(#icode_phi{}) -> [{icode_lbl(), #icode_variable{}}]. +phi_arglist(#icode_phi{arglist=ArgList}) -> ArgList. + +-spec phi_args(#icode_phi{}) -> [#icode_variable{}]. +phi_args(P) -> [Var || {_, Var} <- phi_arglist(P)]. + +-spec phi_arg(#icode_phi{}, icode_lbl()) -> #icode_variable{}. +phi_arg(P, Pred) -> + case lists:keyfind(Pred, 1, phi_arglist(P)) of + {_, Var} -> Var; + false -> exit({'No such predecessor to phi', {Pred, P}}) + end. + +-spec is_phi(icode_instr()) -> boolean(). +is_phi(#icode_phi{}) -> true; +is_phi(_) -> false. + +-spec phi_enter_pred(#icode_phi{}, icode_lbl(), #icode_variable{}) -> + #icode_phi{}. +phi_enter_pred(Phi, Pred, Var) -> + NewArg = {Pred, Var}, + Phi#icode_phi{arglist=[NewArg|lists:keydelete(Pred, 1, phi_arglist(Phi))]}. + +-spec phi_remove_pred(#icode_phi{}, icode_lbl()) -> #icode_move{} | #icode_phi{}. +phi_remove_pred(Phi, Pred) -> + NewArgList = lists:keydelete(Pred, 1, phi_arglist(Phi)), + case NewArgList of + [Arg] -> %% the Phi should be turned into an appropriate move instruction + {_Label, Var = #icode_variable{}} = Arg, + mk_move(phi_dst(Phi), Var); + [_|_] -> + Phi#icode_phi{arglist=NewArgList} + end. + +phi_argvar_subst(P, Subst) -> + NewArgList = [{Pred, subst1(Subst, Var)} || {Pred,Var} <- phi_arglist(P)], + P#icode_phi{arglist=NewArgList}. + +-spec phi_redirect_pred(#icode_phi{}, icode_lbl(), icode_lbl()) -> #icode_phi{}. +phi_redirect_pred(P, OldPred, NewPred) -> + Subst = [{OldPred, NewPred}], + NewArgList = [{subst1(Subst, Pred), Var} || {Pred,Var} <- phi_arglist(P)], + P#icode_phi{arglist=NewArgList}. + +%% +%% primop and guardop +%% +%% Whether a function is a "primop" - i.e., an internal thing - or not, +%% is really only shown by its name. An {M,F,A} always represents a +%% function in some Erlang module (although it might be a BIF, and +%% could possibly be inline expanded). It is convenient to let the +%% constructor functions check the name and set the type automatically, +%% especially for guardops - some guardops are primitives and some are +%% MFA:s, and this way we won't have to rewrite all calls to mk_guardop +%% to flag whether they are primops or not. + +-spec mk_primop([#icode_variable{}], icode_funcall(), + [icode_argument()]) -> #icode_call{}. +mk_primop(DstList, Fun, ArgList) -> + mk_primop(DstList, Fun, ArgList, [], []). + +-spec mk_primop([#icode_variable{}], icode_funcall(), + [icode_argument()], [] | icode_lbl(), [] | icode_lbl()) -> + #icode_call{}. +mk_primop(DstList, Fun, ArgList, Continuation, Fail) -> + Type = op_type(Fun), + make_call(DstList, Fun, ArgList, Type, Continuation, Fail, false). + +%% Note that a 'guardop' is just a call that occurred in a guard. In +%% this case, we should always have continuation labels True and False. + +-spec mk_guardop([#icode_variable{}], icode_funcall(), + [icode_argument()], icode_lbl(), icode_lbl()) -> #icode_call{}. +mk_guardop(DstList, Fun, ArgList, True, False) -> + Type = op_type(Fun), + make_call(DstList, Fun, ArgList, Type, True, False, true). + +op_type(Fun) -> + case is_mfa(Fun) of + true -> remote; + false -> primop + end. + +is_mfa({M,F,A}) when is_atom(M), is_atom(F), + is_integer(A), 0 =< A, A =< 255 -> true; +is_mfa(_) -> false. + +%%------ +%% call +%%------ + +-spec mk_call([#icode_variable{}], atom(), atom(), + [icode_argument()], 'local' | 'remote') -> #icode_call{}. +mk_call(DstList, M, F, ArgList, Type) -> + mk_call(DstList, M, F, ArgList, Type, [], [], false). + +%% mk_call(DstList, M, F, ArgList, Type, Continuation, Fail) -> +%% mk_call(DstList, M, F, ArgList, Type, Continuation, Fail, false). + +-spec mk_call([#icode_variable{}], atom(), atom(), [icode_argument()], + 'local' | 'remote', [] | icode_lbl(), [] | icode_lbl(), boolean()) -> + #icode_call{}. +mk_call(DstList, M, F, ArgList, Type, Continuation, Fail, InGuard) + when is_atom(M), is_atom(F) -> + case Type of + local -> ok; + remote -> ok + end, + Fun = {M,F,length(ArgList)}, + make_call(DstList, Fun, ArgList, Type, Continuation, Fail, InGuard). + +%% The common constructor for all calls (for internal use only) +%% +%% Note: If the "guard" flag is `true', it means that if the call fails, +%% we can simply jump to the Fail label (if it exists) without +%% generating any additional exception information - it isn't needed. +-spec make_call([#icode_variable{}], icode_funcall(), [icode_argument()], + icode_call_type(), [] | icode_lbl(), [] | icode_lbl(), boolean()) -> + #icode_call{}. +make_call(DstList, Fun, ArgList, Type, Continuation, Fail, InGuard) -> + #icode_call{dstlist=DstList, 'fun'=Fun, args=ArgList, type=Type, + continuation=Continuation, fail_label=Fail, in_guard=InGuard}. + +-spec call_dstlist(#icode_call{}) -> [#icode_variable{}]. +call_dstlist(#icode_call{dstlist=DstList}) -> DstList. + +-spec call_dstlist_update(#icode_call{}, [#icode_variable{}]) -> #icode_call{}. +call_dstlist_update(C, Dest) -> C#icode_call{dstlist=Dest}. + +-spec call_type(#icode_call{}) -> icode_call_type(). +call_type(#icode_call{type=Type}) -> Type. + +%% -spec call_dst_type(#icode_call{}) -> erl_type(). +%% call_dst_type(#icode_call{dst_type=DstType}) -> DstType. + +-spec call_args(#icode_call{}) -> [icode_argument()]. +call_args(#icode_call{args=Args}) -> Args. + +-spec call_args_update(#icode_call{}, [icode_argument()]) -> #icode_call{}. +call_args_update(C, Args) -> C#icode_call{args=Args}. + +-spec call_fun(#icode_call{}) -> icode_funcall(). +call_fun(#icode_call{'fun'=Fun}) -> Fun. + +%% Note that updating the name field requires recomputing the call type, +%% in case it changes from a remote/local call to a primop call. +-spec call_fun_update(#icode_call{}, icode_funcall()) -> #icode_call{}. +call_fun_update(C, Fun) -> + Type = case is_mfa(Fun) of + true -> call_type(C); + false -> primop + end, + C#icode_call{'fun'=Fun, type=Type}. + +-spec call_continuation(#icode_call{}) -> [] | icode_lbl(). +call_continuation(#icode_call{continuation=Continuation}) -> Continuation. + +-spec call_fail_label(#icode_call{}) -> [] | icode_lbl(). +call_fail_label(#icode_call{fail_label=Fail}) -> Fail. + +-spec call_set_continuation(#icode_call{}, [] | icode_lbl()) -> #icode_call{}. +call_set_continuation(I, Continuation) -> + I#icode_call{continuation = Continuation}. + +-spec call_set_fail_label(#icode_call{}, [] | icode_lbl()) -> #icode_call{}. +call_set_fail_label(I=#icode_call{}, Fail) -> + case Fail of + [] -> + I#icode_call{fail_label=Fail, in_guard=false}; + _ -> + I#icode_call{fail_label=Fail} + end. + +-spec is_call(icode_instr()) -> boolean(). +is_call(#icode_call{}) -> true; +is_call(_) -> false. + +-spec call_in_guard(#icode_call{}) -> boolean(). +call_in_guard(#icode_call{in_guard=InGuard}) -> InGuard. + +%%------- +%% enter +%%------- + +-spec mk_enter(atom(), atom(), [icode_term_arg()], 'local' | 'remote') -> + #icode_enter{}. +mk_enter(M, F, Args, Type) when is_atom(M), is_atom(F) -> + case Type of + local -> ok; + remote -> ok + end, + #icode_enter{'fun'={M,F,length(Args)}, args=Args, type=Type}. + +-spec enter_fun(#icode_enter{}) -> icode_funcall(). +enter_fun(#icode_enter{'fun'=Fun}) -> Fun. + +-spec enter_fun_update(#icode_enter{}, icode_funcall()) -> + #icode_enter{}. +enter_fun_update(E, Fun) -> + Type = case is_mfa(Fun) of + true -> enter_type(E); + false -> primop + end, + E#icode_enter{'fun'=Fun, type=Type}. + +-spec enter_args(#icode_enter{}) -> [icode_term_arg()]. +enter_args(#icode_enter{args=Args}) -> Args. + +-spec enter_args_update(#icode_enter{}, [icode_term_arg()]) -> #icode_enter{}. +enter_args_update(E, Args) -> E#icode_enter{args=Args}. + +-spec enter_type(#icode_enter{}) -> icode_call_type(). +enter_type(#icode_enter{type=Type}) -> Type. + +-spec is_enter(icode_instr()) -> boolean(). +is_enter(#icode_enter{}) -> true; +is_enter(_) -> false. + +-spec mk_enter_primop(icode_primop(), [icode_term_arg()]) -> + #icode_enter{type::'primop'}. +mk_enter_primop(Op, Args) -> + #icode_enter{'fun'=Op, args=Args, type=primop}. + +%%----------- +%% begin_try +%%----------- + +%% The reason that begin_try is a branch instruction is just so that it +%% keeps the fail-to block linked into the CFG, until the exception +%% handling instructions are eliminated. + +-spec mk_begin_try(icode_lbl(), icode_lbl()) -> #icode_begin_try{}. +mk_begin_try(Label, Successor) -> + #icode_begin_try{label=Label, successor=Successor}. + +-spec begin_try_label(#icode_begin_try{}) -> icode_lbl(). +begin_try_label(#icode_begin_try{label=Label}) -> Label. + +-spec begin_try_successor(#icode_begin_try{}) -> icode_lbl(). +begin_try_successor(#icode_begin_try{successor=Successor}) -> Successor. + +%%--------- +%% end_try +%%--------- + +-spec mk_end_try() -> #icode_end_try{}. +mk_end_try() -> #icode_end_try{}. + +%%--------------- +%% begin_handler +%%--------------- + +-spec mk_begin_handler([icode_var()]) -> #icode_begin_handler{}. +mk_begin_handler(Dstlist) -> + #icode_begin_handler{dstlist=Dstlist}. + +-spec begin_handler_dstlist(#icode_begin_handler{}) -> [icode_var()]. +begin_handler_dstlist(#icode_begin_handler{dstlist=Dstlist}) -> Dstlist. + +%% -spec is_begin_handler(icode_instr()) -> boolean(). +%% is_begin_handler(#icode_begin_handler{}) -> true; +%% is_begin_handler(_) -> false. + +%%------- +%% label +%%------- + +-spec mk_label(icode_lbl()) -> #icode_label{}. +mk_label(Name) when is_integer(Name), Name >= 0 -> #icode_label{name=Name}. + +-spec label_name(#icode_label{}) -> icode_lbl(). +label_name(#icode_label{name=Name}) -> Name. + +-spec is_label(icode_instr()) -> boolean(). +is_label(#icode_label{}) -> true; +is_label(_) -> false. + +%%--------- +%% comment +%%--------- + +-spec mk_comment(icode_comment_text()) -> #icode_comment{}. +%% @doc If `Txt' is a list of characters (possibly deep), it will be +%% printed as a string; otherwise, `Txt' will be printed as a term. +mk_comment(Txt) -> #icode_comment{text=Txt}. + +-spec comment_text(#icode_comment{}) -> icode_comment_text(). +comment_text(#icode_comment{text=Txt}) -> Txt. + +-spec is_comment(icode_instr()) -> boolean(). +is_comment(#icode_comment{}) -> true; +is_comment(_) -> false. + + +%%--------------------------------------------------------------------- +%% Arguments (variables and constants) +%%--------------------------------------------------------------------- + +%%------- +%% const +%%------- + +-spec mk_const(simple_const() | structured_const() | binary()) -> #icode_const{}. +mk_const(C) -> #icode_const{value=#flat{value=C}}. + +-spec const_value(#icode_const{}) -> simple_const() | structured_const() | binary(). +const_value(#icode_const{value=#flat{value=X}}) -> X. + +-spec is_const(icode_argument()) -> boolean(). +is_const(#icode_const{}) -> true; +is_const(_) -> false. + +%%----- +%% var +%%----- + +-spec mk_var(non_neg_integer()) -> #icode_variable{kind::'var'}. +mk_var(V) -> #icode_variable{name=V, kind=var}. + +-spec var_name(#icode_variable{kind::'var'}) -> non_neg_integer(). +var_name(#icode_variable{name=Name, kind=var}) -> Name. + +-spec is_var(icode_argument()) -> boolean(). +is_var(#icode_variable{kind=var}) -> true; +is_var(_) -> false. + +-spec mk_reg(non_neg_integer()) -> #icode_variable{kind::'reg'}. +mk_reg(V) -> #icode_variable{name=V, kind=reg}. + +-spec reg_name(#icode_variable{kind::'reg'}) -> non_neg_integer(). +reg_name(#icode_variable{name=Name, kind=reg}) -> Name. + +-spec reg_is_gcsafe(#icode_variable{kind::'reg'}) -> 'false'. +reg_is_gcsafe(#icode_variable{kind=reg}) -> false. % for now + +-spec is_reg(icode_argument()) -> boolean(). +is_reg(#icode_variable{kind=reg}) -> true; +is_reg(_) -> false. + +-spec mk_fvar(non_neg_integer()) -> #icode_variable{kind::'fvar'}. +mk_fvar(V) -> #icode_variable{name=V, kind=fvar}. + +-spec fvar_name(#icode_variable{kind::'fvar'}) -> non_neg_integer(). +fvar_name(#icode_variable{name=Name, kind=fvar}) -> Name. + +-spec is_fvar(icode_argument()) -> boolean(). +is_fvar(#icode_variable{kind=fvar}) -> true; +is_fvar(_) -> false. + +-spec is_variable(icode_argument()) -> boolean(). +is_variable(#icode_variable{}) -> true; +is_variable(_) -> false. + +-spec annotate_variable(#icode_variable{}, variable_annotation()) -> + #icode_variable{}. +annotate_variable(X, Anno) -> + X#icode_variable{annotation = Anno}. + +-spec is_annotated_variable(icode_argument()) -> boolean(). +is_annotated_variable(#icode_variable{annotation=[]}) -> + false; +is_annotated_variable(#icode_variable{}) -> + true; +is_annotated_variable(_) -> + false. + +-spec unannotate_variable(#icode_variable{}) -> #icode_variable{}. +unannotate_variable(X) -> + X#icode_variable{annotation=[]}. + +-spec variable_annotation(#icode_variable{}) -> variable_annotation(). +variable_annotation(#icode_variable{annotation=Anno}) -> + Anno. + +%% +%% Floating point Icode instructions. +%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% Liveness info +%% + +-spec uses(icode_instr()) -> [#icode_variable{}]. +uses(Instr) -> + remove_constants(args(Instr)). + +-spec args(icode_instr()) -> [icode_argument()]. +args(I) -> + case I of + #icode_if{} -> if_args(I); + #icode_switch_val{} -> [switch_val_term(I)]; + #icode_switch_tuple_arity{} -> [switch_tuple_arity_term(I)]; + #icode_type{} -> type_args(I); + #icode_move{} -> [move_src(I)]; + #icode_fail{} -> fail_args(I); + #icode_call{} -> call_args(I); + #icode_enter{} -> enter_args(I); + #icode_return{} -> return_vars(I); + #icode_phi{} -> phi_args(I); + #icode_goto{} -> []; + #icode_begin_try{} -> []; + #icode_begin_handler{} -> []; + #icode_end_try{} -> []; + #icode_comment{} -> []; + #icode_label{} -> [] + end. + +-spec defines(icode_instr()) -> [#icode_variable{}]. +defines(I) -> + case I of + #icode_move{} -> remove_constants([move_dst(I)]); + #icode_call{} -> remove_constants(call_dstlist(I)); + #icode_begin_handler{} -> remove_constants(begin_handler_dstlist(I)); + #icode_phi{} -> remove_constants([phi_dst(I)]); + #icode_if{} -> []; + #icode_switch_val{} -> []; + #icode_switch_tuple_arity{} -> []; + #icode_type{} -> []; + #icode_goto{} -> []; + #icode_fail{} -> []; + #icode_enter{} -> []; + #icode_return{} -> []; + #icode_begin_try{} -> []; + #icode_end_try{} -> []; + #icode_comment{} -> []; + #icode_label{} -> [] + end. + +-spec remove_constants([icode_argument()]) -> [#icode_variable{}]. +remove_constants(L) -> + [V || V <- L, (not is_const(V))]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% Utilities +%% + +%% +%% Substitution: replace occurrences of X by Y if {X,Y} is in the +%% Subst_list. + +-spec subst([{_,_}], I) -> I when is_subtype(I, icode_instr()). + +subst(Subst, I) -> + subst_defines(Subst, subst_uses(Subst, I)). + +-spec subst_uses([{_,_}], I) -> I when is_subtype(I, icode_instr()). + +subst_uses(Subst, I) -> + case I of + #icode_if{} -> I#icode_if{args = subst_list(Subst, if_args(I))}; + #icode_switch_val{} -> + I#icode_switch_val{term = subst1(Subst, switch_val_term(I))}; + #icode_switch_tuple_arity{} -> + I#icode_switch_tuple_arity{term = subst1(Subst, switch_tuple_arity_term(I))}; + #icode_type{} -> I#icode_type{args = subst_list(Subst, type_args(I))}; + #icode_move{} -> I#icode_move{src = subst1(Subst, move_src(I))}; + #icode_fail{} -> I#icode_fail{args = subst_list(Subst, fail_args(I))}; + #icode_call{} -> I#icode_call{args = subst_list(Subst, call_args(I))}; + #icode_enter{} -> I#icode_enter{args = subst_list(Subst, enter_args(I))}; + #icode_return{} -> I#icode_return{vars = subst_list(Subst, return_vars(I))}; + #icode_phi{} -> phi_argvar_subst(I, Subst); + #icode_goto{} -> I; + #icode_begin_try{} -> I; + #icode_begin_handler{} -> I; + #icode_end_try{} -> I; + #icode_comment{} -> I; + #icode_label{} -> I + end. + +-spec subst_defines([{_,_}], I) -> I when is_subtype(I, icode_instr()). + +subst_defines(Subst, I) -> + case I of + #icode_move{} -> I#icode_move{dst = subst1(Subst, move_dst(I))}; + #icode_call{} -> + I#icode_call{dstlist = subst_list(Subst, call_dstlist(I))}; + #icode_begin_handler{} -> + I#icode_begin_handler{dstlist = subst_list(Subst, + begin_handler_dstlist(I))}; + #icode_phi{} -> I#icode_phi{dst = subst1(Subst, phi_dst(I))}; + #icode_if{} -> I; + #icode_switch_val{} -> I; + #icode_switch_tuple_arity{} -> I; + #icode_type{} -> I; + #icode_goto{} -> I; + #icode_fail{} -> I; + #icode_enter{} -> I; + #icode_return{} -> I; + #icode_begin_try{} -> I; + #icode_end_try{} -> I; + #icode_comment{} -> I; + #icode_label{} -> I + end. + +subst_list(S, Is) -> + [subst1(S, I) || I <- Is]. + +subst1([], I) -> I; +subst1([{I,Y}|_], I) -> Y; +subst1([_|Pairs], I) -> subst1(Pairs, I). + +%% +%% @doc Returns the successors of an Icode instruction. +%% In CFG form only branch instructions have successors, +%% but in linear form other instructions like e.g. moves and +%% others might be the last instruction of some basic block. +%% + +-spec successors(icode_instr()) -> [icode_lbl()]. + +successors(I) -> + case I of + #icode_if{} -> + [if_true_label(I), if_false_label(I)]; + #icode_goto{} -> + [goto_label(I)]; + #icode_switch_val{} -> + CaseLabels = [L || {_,L} <- switch_val_cases(I)], + [switch_val_fail_label(I) | CaseLabels]; + #icode_switch_tuple_arity{} -> + CaseLabels = [L || {_,L} <- switch_tuple_arity_cases(I)], + [switch_tuple_arity_fail_label(I) | CaseLabels]; + #icode_type{} -> + [type_true_label(I), type_false_label(I)]; + #icode_call{} -> + case call_continuation(I) of [] -> []; L when is_integer(L) -> [L] end + ++ + case call_fail_label(I) of [] -> []; L when is_integer(L) -> [L] end; + #icode_begin_try{} -> + [begin_try_successor(I), begin_try_label(I)]; + #icode_fail{} -> + case fail_label(I) of [] -> []; L when is_integer(L) -> [L] end; + #icode_enter{} -> []; + #icode_return{} -> []; + %% the following are included here for handling linear code + #icode_move{} -> []; + #icode_begin_handler{} -> [] + end. + +%% +%% @doc Returns the fail labels of an Icode instruction. +%% + +-spec fails_to(icode_instr()) -> [icode_lbl()]. + +fails_to(I) -> + case I of + #icode_switch_val{} -> [switch_val_fail_label(I)]; + #icode_switch_tuple_arity{} -> [switch_tuple_arity_fail_label(I)]; + #icode_call{} -> + case call_fail_label(I) of [] -> []; L when is_integer(L) -> [L] end; + #icode_begin_try{} -> [begin_try_label(I)]; % just for safety + #icode_fail{} -> + case fail_label(I) of [] -> []; L when is_integer(L) -> [L] end; + #icode_if{} -> []; % XXX: Correct? + #icode_enter{} -> []; % XXX: Correct? + #icode_goto{} -> []; + #icode_type{} -> []; % XXX: Correct? + #icode_return{} -> [] + end. + +%% +%% @doc Redirects jumps from label Old to label New. +%% If the instruction does not jump to Old, it remains unchanged. +%% The New label can be the special [] label used for calls with +%% fall-throughs. +%% + +-spec redirect_jmp(icode_instr(), icode_lbl(), [] | icode_lbl()) -> icode_instr(). + +redirect_jmp(Jmp, ToOld, ToOld) -> + Jmp; % no need to do anything +redirect_jmp(Jmp, ToOld, ToNew) -> + NewI = + case Jmp of + #icode_if{} -> + NewJmp = case if_true_label(Jmp) of + ToOld -> if_true_label_update(Jmp, ToNew); + _ -> Jmp + end, + case if_false_label(NewJmp) of + ToOld -> if_false_label_update(NewJmp, ToNew); + _ -> NewJmp + end; + #icode_goto{} -> + case goto_label(Jmp) of + ToOld -> Jmp#icode_goto{label=ToNew}; + _ -> Jmp + end; + #icode_switch_val{} -> + NewJmp = case switch_val_fail_label(Jmp) of + ToOld -> switch_val_fail_label_update(Jmp, ToNew); + _ -> Jmp + end, + Cases = [case Pair of + {Val,ToOld} -> {Val,ToNew}; + Unchanged -> Unchanged + end || Pair <- switch_val_cases(NewJmp)], + NewJmp#icode_switch_val{cases = Cases}; + #icode_switch_tuple_arity{} -> + NewJmp = case switch_tuple_arity_fail_label(Jmp) of + ToOld -> + Jmp#icode_switch_tuple_arity{fail_label=ToNew}; + _ -> Jmp + end, + Cases = [case Pair of + {Val,ToOld} -> {Val,ToNew}; + Unchanged -> Unchanged + end || Pair <- switch_tuple_arity_cases(NewJmp)], + NewJmp#icode_switch_tuple_arity{cases = Cases}; + #icode_type{} -> + NewJmp = case type_true_label(Jmp) of + ToOld -> Jmp#icode_type{true_label=ToNew}; + _ -> Jmp + end, + case type_false_label(NewJmp) of + ToOld -> NewJmp#icode_type{false_label=ToNew}; + _ -> NewJmp + end; + #icode_call{} -> + NewCont = case call_continuation(Jmp) of + ToOld -> ToNew; + OldCont -> OldCont + end, + NewFail = case call_fail_label(Jmp) of + ToOld -> ToNew; + OldFail -> OldFail + end, + Jmp#icode_call{continuation = NewCont, + fail_label = NewFail}; + #icode_begin_try{} -> + NewLabl = case begin_try_label(Jmp) of + ToOld -> ToNew; + OldLab -> OldLab + end, + NewSucc = case begin_try_successor(Jmp) of + ToOld -> ToNew; + OldSucc -> OldSucc + end, + Jmp#icode_begin_try{label=NewLabl, successor=NewSucc}; + #icode_fail{} -> + case fail_label(Jmp) of + ToOld -> Jmp#icode_fail{fail_label=ToNew}; + _ -> Jmp + end + end, + simplify_branch(NewI). + +%% +%% @doc Turns a branch into a goto if it has only one successor and it +%% is safe to do so. +%% + +-spec simplify_branch(icode_instr()) -> icode_instr(). + +simplify_branch(I) -> + case ordsets:from_list(successors(I)) of + [Label] -> + Goto = mk_goto(Label), + case I of + #icode_type{} -> Goto; + #icode_if{} -> Goto; + #icode_switch_tuple_arity{} -> Goto; + #icode_switch_val{} -> Goto; + _ -> I + end; + _ -> I + end. + +%% +%% Is this an unconditional jump (causes a basic block not to have a +%% fallthrough successor). +%% + +%% is_uncond(I) -> +%% case I of +%% #icode_goto{} -> true; +%% #icode_fail{} -> true; +%% #icode_enter{} -> true; +%% #icode_return{} -> true; +%% #icode_call{} -> +%% case call_fail_label(I) of +%% [] -> +%% case call_continuation(I) of +%% [] -> false; +%% _ -> true +%% end; +%% _ -> true +%% end; +%% _ -> false +%% end. + +%% @spec is_branch(icode_instr()) -> boolean() +%% +%% @doc Succeeds if the Icode instruction is a branch. I.e. a +%% (possibly conditional) discontinuation of linear control flow. +%% @end + +-spec is_branch(icode_instr()) -> boolean(). +is_branch(Instr) -> + case Instr of + #icode_if{} -> true; + #icode_switch_val{} -> true; + #icode_switch_tuple_arity{} -> true; + #icode_type{} -> true; + #icode_goto{} -> true; + #icode_fail{} -> true; + #icode_call{} -> + case call_fail_label(Instr) of + [] -> call_continuation(Instr) =/= []; + _ -> true + end; + #icode_enter{} -> true; + #icode_return{} -> true; + #icode_begin_try{} -> true; + %% false cases below + #icode_move{} -> false; + #icode_begin_handler{} -> false; + #icode_end_try{} -> false; + #icode_comment{} -> false; + #icode_label{} -> false; + #icode_phi{} -> false + end. + +%% +%% @doc Makes a new variable. +%% + +-spec mk_new_var() -> icode_var(). +mk_new_var() -> + mk_var(hipe_gensym:get_next_var(icode)). + +%% +%% @doc Makes a new fp variable. +%% + +-spec mk_new_fvar() -> icode_fvar(). +mk_new_fvar() -> + mk_fvar(hipe_gensym:get_next_var(icode)). + +%% +%% @doc Makes a new register. +%% + +-spec mk_new_reg() -> icode_reg(). +mk_new_reg() -> + mk_reg(hipe_gensym:get_next_var(icode)). + +%% +%% @doc Makes a new label. +%% + +-spec mk_new_label() -> #icode_label{}. +mk_new_label() -> + mk_label(hipe_gensym:get_next_label(icode)). + +%% %% +%% %% @doc Makes a bunch of move operations. +%% %% +%% +%% -spec mk_moves([_], [_]) -> [#icode_move{}]. +%% mk_moves([], []) -> +%% []; +%% mk_moves([X|Xs], [Y|Ys]) -> +%% [mk_move(X, Y) | mk_moves(Xs, Ys)]. + +%% +%% Makes a series of element operations. +%% + +%% mk_elements(_, []) -> +%% []; +%% mk_elements(Tuple, [X|Xs]) -> +%% [mk_primop([X], #unsafe_element{index=length(Xs)+1}, [Tuple]) | +%% mk_elements(Tuple, Xs)]. + +%% +%% @doc Removes comments from Icode. +%% + +-spec strip_comments(#icode{}) -> #icode{}. +strip_comments(ICode) -> + icode_code_update(ICode, no_comments(icode_code(ICode))). + +%% The following spec is underspecified: the resulting list does not +%% contain any #comment{} instructions +-spec no_comments(icode_instrs()) -> icode_instrs(). +no_comments([]) -> + []; +no_comments([I|Xs]) -> + case is_comment(I) of + true -> no_comments(Xs); + false -> [I|no_comments(Xs)] + end. + +%%----------------------------------------------------------------------- + +%% @doc True if an Icode instruction is safe (can be removed if the +%% result is not used). Note that pure control flow instructions +%% cannot be regarded as safe, as they are not defining anything. + +-spec is_safe(icode_instr()) -> boolean(). + +is_safe(Instr) -> + case Instr of + %% Instructions that are safe, or might be safe to remove. + #icode_move{} -> true; + #icode_phi{} -> true; + #icode_begin_handler{} -> true; + #icode_call{} -> + case call_fun(Instr) of + {M,F,A} -> + erl_bifs:is_safe(M,F,A); + Op -> + hipe_icode_primops:is_safe(Op) + end; + %% Control flow instructions. + #icode_if{} -> false; + #icode_switch_val{} -> false; + #icode_switch_tuple_arity{} -> false; + #icode_type{} -> false; + #icode_goto{} -> false; + #icode_label{} -> false; + %% Returning instructions without defines. + #icode_return{} -> false; + #icode_fail{} -> false; + #icode_enter{} -> false; + %% Internal auxiliary instructions that should not be removed + %% unless you really know what you are doing. + #icode_comment{} -> false; + #icode_begin_try{} -> false; + #icode_end_try{} -> false + end. + +%%----------------------------------------------------------------------- + +-spec highest_var(icode_instrs()) -> non_neg_integer(). +highest_var(Instrs) -> + highest_var(Instrs, 0). + +-spec highest_var(icode_instrs(), non_neg_integer()) -> non_neg_integer(). +highest_var([I|Is], Max) -> + Defs = defines(I), + Uses = uses(I), + highest_var(Is, new_max(Defs++Uses, Max)); +highest_var([], Max) -> + Max. + +-spec new_max([#icode_variable{}], non_neg_integer()) -> non_neg_integer(). +new_max([V|Vs], Max) -> + VName = + case is_var(V) of + true -> + var_name(V); + false -> + case is_fvar(V) of + true -> + fvar_name(V); + _ -> + reg_name(V) + end + end, + new_max(Vs, erlang:max(VName, Max)); +new_max([], Max) when is_integer(Max) -> + Max. + +%%----------------------------------------------------------------------- + +-spec highest_label(icode_instrs()) -> icode_lbl(). +highest_label(Instrs) -> + highest_label(Instrs, 0). + +-spec highest_label(icode_instrs(), icode_lbl()) -> icode_lbl(). +highest_label([I|Is], Max) -> + case is_label(I) of + true -> + L = label_name(I), + NewMax = erlang:max(L, Max), + highest_label(Is, NewMax); + false -> + highest_label(Is, Max) + end; +highest_label([], Max) when is_integer(Max) -> + Max. + +%%----------------------------------------------------------------------- diff --git a/lib/hipe/icode/hipe_icode.hrl b/lib/hipe/icode/hipe_icode.hrl new file mode 100644 index 0000000000..65deaf6d7c --- /dev/null +++ b/lib/hipe/icode/hipe_icode.hrl @@ -0,0 +1,188 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%===================================================================== +%% +%% Contains type and record definitions for all Icode data structures. +%% +%%===================================================================== + +%%--------------------------------------------------------------------- +%% THIS DOES NOT REALLY BELONG HERE -- PLEASE REMOVE ASAP! +%%--------------------------------------------------------------------- + +-type ordset(T) :: [T]. + +%%--------------------------------------------------------------------- +%% Include files needed for the compilation of this header file +%%--------------------------------------------------------------------- + +-include("../misc/hipe_consttab.hrl"). + +%%--------------------------------------------------------------------- +%% Icode argument types +%%--------------------------------------------------------------------- + +-type simple_const() :: atom() | [] | integer() | float(). +-type structured_const() :: list() | tuple(). + +-type icode_lbl() :: non_neg_integer(). + +%%--------------------------------------------------------------------- +%% Icode records +%%--------------------------------------------------------------------- + +-record(flat, {value :: simple_const() | structured_const() | binary()}). + +-record(icode_const, {value :: #flat{}}). + +-type variable_annotation() :: {atom(), any(), fun((any()) -> string())}. + +-record(icode_variable, {name :: non_neg_integer(), + kind :: 'var' | 'reg' | 'fvar', + annotation = [] :: [] | variable_annotation()}). + +%%--------------------------------------------------------------------- +%% Type declarations for Icode instructions +%%--------------------------------------------------------------------- + +-type icode_if_op() :: '>' | '<' | '>=' | '=<' | '=:=' | '=/=' | '==' | '/=' + | 'fixnum_eq' | 'fixnum_neq' | 'fixnum_lt' + | 'fixnum_le' | 'fixnum_ge' | 'fixnum_gt' + | 'suspend_msg_timeout'. + +-type icode_type_test() :: 'atom' | 'bignum' | 'binary' | 'bitrst' | 'boolean' + | 'cons' | 'constant' | 'fixnum' | 'float' + | 'function' | 'function2' | 'integer' | 'list' | 'nil' + | 'number' | 'pid' | 'port' | 'reference' | 'tuple' + | {'atom', atom()} | {'integer', integer()} + | {'record', atom(), non_neg_integer()} + | {'tuple', non_neg_integer()}. + +-type icode_primop() :: atom() | tuple(). % XXX: temporarily, I hope +-type icode_funcall() :: mfa() | icode_primop(). + +-type icode_var() :: #icode_variable{kind::'var'}. +-type icode_reg() :: #icode_variable{kind::'reg'}. +-type icode_fvar() :: #icode_variable{kind::'fvar'}. +-type icode_argument() :: #icode_const{} | #icode_variable{}. +-type icode_term_arg() :: icode_var() | #icode_const{}. + +-type icode_switch_case() :: {#icode_const{}, icode_lbl()}. + +-type icode_call_type() :: 'local' | 'primop' | 'remote'. +-type icode_exit_class() :: 'error' | 'exit' | 'rethrow' | 'throw'. + +-type icode_comment_text() :: atom() | string() | {atom(), term()}. + +-type icode_info() :: [{'arg_types', [erl_types:erl_type()]}]. + +%%--------------------------------------------------------------------- +%% Icode instructions +%%--------------------------------------------------------------------- + +-record(icode_label, {name :: icode_lbl()}). + +-record(icode_if, {op :: icode_if_op(), + args :: [icode_term_arg()], + true_label :: icode_lbl(), + false_label :: icode_lbl(), + p :: float()}). + +-record(icode_switch_val, {term :: icode_var(), + fail_label :: icode_lbl(), + length :: non_neg_integer(), + cases :: [icode_switch_case()]}). + +-record(icode_switch_tuple_arity, {term :: icode_var(), + fail_label :: icode_lbl(), + length :: non_neg_integer(), + cases :: [icode_switch_case()]}). + + +-record(icode_type, {test :: icode_type_test(), + args :: [icode_term_arg()], + true_label :: icode_lbl(), + false_label :: icode_lbl(), + p :: float()}). + +-record(icode_goto, {label :: icode_lbl()}). + +-record(icode_move, {dst :: #icode_variable{}, + src :: #icode_variable{} | #icode_const{}}). + +-record(icode_phi, {dst :: #icode_variable{}, + id :: #icode_variable{}, + arglist :: [{icode_lbl(), #icode_variable{}}]}). + +-record(icode_call, {dstlist :: [#icode_variable{}], + 'fun' :: icode_funcall(), + args :: [icode_argument()], + type :: icode_call_type(), + continuation :: [] | icode_lbl(), + fail_label = [] :: [] | icode_lbl(), + in_guard = false :: boolean()}). + +-record(icode_enter, {'fun' :: icode_funcall(), + args :: [icode_term_arg()], + type :: icode_call_type()}). + +-record(icode_return, {vars :: [icode_var()]}). + +-record(icode_begin_try, {label :: icode_lbl(), successor :: icode_lbl()}). + +-record(icode_end_try, {}). + +-record(icode_begin_handler, {dstlist :: [icode_var()]}). + +%% TODO: Remove [] from fail_label +-record(icode_fail, {class :: icode_exit_class(), + args :: [icode_term_arg()], + fail_label = [] :: [] | icode_lbl()}). + +-record(icode_comment, {text :: icode_comment_text()}). + +%%--------------------------------------------------------------------- +%% Icode instructions +%%--------------------------------------------------------------------- + +-type icode_instr() :: #icode_begin_handler{} | #icode_begin_try{} + | #icode_call{} | #icode_comment{} | #icode_end_try{} + | #icode_enter{} | #icode_fail{} + | #icode_goto{} | #icode_if{} | #icode_label{} + | #icode_move{} | #icode_phi{} | #icode_return{} + | #icode_switch_tuple_arity{} | #icode_switch_val{} + | #icode_type{}. +-type icode_instrs() :: [icode_instr()]. + +%%--------------------------------------------------------------------- +%% The Icode data structure +%%--------------------------------------------------------------------- + +-record(icode, {'fun' :: mfa(), + params :: [icode_var()], + is_closure :: boolean(), + closure_arity :: arity(), + is_leaf :: boolean(), + code = [] :: icode_instrs(), + data :: hipe_consttab(), + var_range :: {non_neg_integer(), non_neg_integer()}, + label_range :: {icode_lbl(), icode_lbl()}, + info = [] :: icode_info()}). + +%%--------------------------------------------------------------------- diff --git a/lib/hipe/icode/hipe_icode_bincomp.erl b/lib/hipe/icode/hipe_icode_bincomp.erl new file mode 100644 index 0000000000..6f694f2bce --- /dev/null +++ b/lib/hipe/icode/hipe_icode_bincomp.erl @@ -0,0 +1,178 @@ +%%% -*- erlang-indent-level: 2 -*- +%%% +%%% %CopyrightBegin% +%%% +%%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%%% +%%% The contents of this file are subject to the Erlang Public License, +%%% Version 1.1, (the "License"); you may not use this file except in +%%% compliance with the License. You should have received a copy of the +%%% Erlang Public License along with this software. If not, it can be +%%% retrieved online at http://www.erlang.org/. +%%% +%%% Software distributed under the License is distributed on an "AS IS" +%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%%% the License for the specific language governing rights and limitations +%%% under the License. +%%% +%%% %CopyrightEnd% +%%% +%%%------------------------------------------------------------------- +%%% File : hipe_icode_bincomp.erl +%%% Author : Per Gustafsson <[email protected]> +%%% Description : +%%% +%%% Created : 12 Sep 2005 by Per Gustafsson <[email protected]> +%%%------------------------------------------------------------------- + +-module(hipe_icode_bincomp). + +-export([cfg/1]). + +%%-------------------------------------------------------------------- + +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). + +%%-------------------------------------------------------------------- + +-spec cfg(cfg()) -> cfg(). + +cfg(Cfg1) -> + StartLbls = ordsets:from_list([hipe_icode_cfg:start_label(Cfg1)]), + find_bs_get_integer(StartLbls, Cfg1, StartLbls). + +find_bs_get_integer([Lbl|Rest], Cfg, Visited) -> + BB = hipe_icode_cfg:bb(Cfg, Lbl), + Last = hipe_bb:last(BB), + NewCfg = + case ok(Last, Cfg) of + {ok,{Type, FakeFail, RealFail, SuccLbl, MsIn, MsOut}} -> + {Cont, Info, OldLbl, LastMsOut} = + collect_info(SuccLbl, Cfg, [Type], Lbl, RealFail, MsOut), + update_code(Lbl, OldLbl, Cfg, Info, Cont, FakeFail, MsIn, LastMsOut); + not_ok -> + Cfg + end, + Succs = ordsets:from_list(hipe_icode_cfg:succ(NewCfg, Lbl)), + NewSuccs = ordsets:subtract(Succs, Visited), + NewLbls = ordsets:union(NewSuccs, Rest), + NewVisited = ordsets:union(NewSuccs, Visited), + find_bs_get_integer(NewLbls, NewCfg, NewVisited); +find_bs_get_integer([], Cfg, _) -> + Cfg. + +ok(I, Cfg) -> + case hipe_icode:is_call(I) of + true -> + case hipe_icode:call_fun(I) of + {hipe_bs_primop, {bs_get_integer, Size, Flags}} when (Flags band 6) =:= 0 -> + case {hipe_icode:call_dstlist(I), hipe_icode:call_args(I)} of + {[Dst, MsOut] = DstList, [MsIn]} -> + Cont = hipe_icode:call_continuation(I), + FirstFail = hipe_icode:call_fail_label(I), + FirstFailBB = hipe_icode_cfg:bb(Cfg, FirstFail), + case check_for_restore_block(FirstFailBB, DstList) of + {restore_block, RealFail} -> + {ok, {{Dst, Size}, FirstFail, RealFail, Cont, MsIn, MsOut}}; + not_restore_block -> + not_ok + end; + _ -> + not_ok + end; + _ -> + not_ok + end; + false -> + not_ok + end. + +check_for_restore_block(FirstFailBB, DefVars) -> + Moves = hipe_bb:butlast(FirstFailBB), + case [Instr || Instr <- Moves, is_badinstr(Instr, DefVars)] of + [] -> + Last = hipe_bb:last(FirstFailBB), + case hipe_icode:is_goto(Last) of + true -> + {restore_block, hipe_icode:goto_label(Last)}; + false -> + not_restore_block + end; + [_|_] -> + not_restore_block + end. + +is_badinstr(Instr, DefVars) -> + not(hipe_icode:is_move(Instr) andalso + lists:member(hipe_icode:move_dst(Instr), DefVars)). + +collect_info(Lbl, Cfg, Acc, OldLbl, FailLbl, MsOut) -> + case do_collect_info(Lbl, Cfg, Acc, FailLbl, MsOut) of + done -> + {Lbl, Acc, OldLbl, MsOut}; + {cont, NewAcc, NewLbl, NewMsOut} -> + collect_info(NewLbl, Cfg, NewAcc, Lbl, FailLbl, NewMsOut) + end. + +do_collect_info(Lbl, Cfg, Acc, FailLbl, MsOut) -> + BB = hipe_icode_cfg:bb(Cfg,Lbl), + case hipe_bb:code(BB) of + [I] -> + case hipe_icode_cfg:pred(Cfg,Lbl) of + [_] -> + case ok(I, Cfg) of + {ok, {Type,_FakeFail,FailLbl,SuccLbl,MsOut,NewMsOut}} -> + NewAcc = [Type|Acc], + MaxSize = hipe_rtl_arch:word_size() * 8 - 5, + case calc_size(NewAcc) of + Size when Size =< MaxSize -> + {cont,NewAcc,SuccLbl,NewMsOut}; + _ -> + done + end; + _ -> + done + end; + _ -> + done + end; + _ -> + done + end. + +calc_size([{_,Size}|Rest]) when is_integer(Size) -> + Size + calc_size(Rest); +calc_size([]) -> 0. + +update_code(_Lbl, _, Cfg, [_Info], _Cont, _LastFail, _MsIn, _MsOut) -> + Cfg; +update_code(Lbl, OldLbl, Cfg, Info, Cont, LastFail, MsIn, MsOut) -> + BB = hipe_icode_cfg:bb(Cfg, Lbl), + ButLast = hipe_bb:butlast(BB), + NewVar = hipe_icode:mk_new_var(), + Size = calc_size(Info), + NewLast = + hipe_icode:mk_primop([NewVar,MsOut], + {hipe_bs_primop, {bs_get_integer,Size,0}}, + [MsIn], + OldLbl, + LastFail), + NewBB = hipe_bb:mk_bb(ButLast++[NewLast]), + NewCfg = hipe_icode_cfg:bb_add(Cfg, Lbl, NewBB), + fix_rest(Info, NewVar, OldLbl, Cont, NewCfg). + +fix_rest(Info, Var, Lbl, Cont, Cfg) -> + ButLast = make_butlast(Info, Var), + Last = hipe_icode:mk_goto(Cont), + NewBB = hipe_bb:mk_bb(ButLast++[Last]), + hipe_icode_cfg:bb_add(Cfg, Lbl, NewBB). + +make_butlast([{Res,_Size}], Var) -> + [hipe_icode:mk_move(Res, Var)]; +make_butlast([{Res, Size}|Rest], Var) -> + NewVar = hipe_icode:mk_new_var(), + [hipe_icode:mk_primop([Res], 'band', + [Var, hipe_icode:mk_const((1 bsl Size)-1)]), + hipe_icode:mk_primop([NewVar], 'bsr', [Var, hipe_icode:mk_const(Size)]) + |make_butlast(Rest, NewVar)]. diff --git a/lib/hipe/icode/hipe_icode_callgraph.erl b/lib/hipe/icode/hipe_icode_callgraph.erl new file mode 100644 index 0000000000..95182fc002 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_callgraph.erl @@ -0,0 +1,217 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%----------------------------------------------------------------------- +%% File : hipe_icode_callgraph.erl +%% Author : Tobias Lindahl <[email protected]> +%% Purpose : Creates a call graph to find out in what order functions +%% in a module have to be compiled to gain best information +%% in hipe_icode_type.erl. +%% +%% Created : 7 Jun 2004 by Tobias Lindahl <[email protected]> +%% +%% $Id$ +%%----------------------------------------------------------------------- +-module(hipe_icode_callgraph). + +-export([construct/1, + get_called_modules/1, + to_list/1, + construct_callgraph/1]). + +-define(NO_UNUSED, true). + +-ifndef(NO_UNUSED). +-export([is_empty/1, take_first/1, pp/1]). +-endif. + +-include("hipe_icode.hrl"). +-include("hipe_icode_primops.hrl"). + +%%------------------------------------------------------------------------ + +-type mfa_icode() :: {mfa(), #icode{}}. + +-record(icode_callgraph, {codedict :: dict(), ordered_sccs :: [[atom()]]}). + +%%------------------------------------------------------------------------ +%% Exported functions +%%------------------------------------------------------------------------ + +-spec construct([mfa_icode()]) -> #icode_callgraph{}. + +construct(List) -> + Calls = get_local_calls(List), + %% io:format("Calls: ~p\n", [lists:keysort(1, Calls)]), + Edges = get_edges(Calls), + %% io:format("Edges: ~p\n", [Edges]), + DiGraph = hipe_digraph:from_list(Edges), + Nodes = ordsets:from_list([MFA || {MFA, _} <- List]), + DiGraph1 = hipe_digraph:add_node_list(Nodes, DiGraph), + SCCs = hipe_digraph:reverse_preorder_sccs(DiGraph1), + #icode_callgraph{codedict = dict:from_list(List), ordered_sccs = SCCs}. + +-spec construct_callgraph([mfa_icode()]) -> hipe_digraph:hdg(). + +construct_callgraph(List) -> + Calls = get_local_calls2(List), + Edges = get_edges(Calls), + hipe_digraph:from_list(Edges). + +-spec to_list(#icode_callgraph{}) -> [mfa_icode()]. + +to_list(#icode_callgraph{codedict = Dict, ordered_sccs = SCCs}) -> + FlatList = lists:flatten(SCCs), + [{Mod, dict:fetch(Mod, Dict)} || Mod <- FlatList]. + +%%------------------------------------------------------------------------ + +-ifndef(NO_UNUSED). + +-spec is_empty(#icode_callgraph{}) -> boolean(). + +is_empty(#icode_callgraph{ordered_sccs = SCCs}) -> + SCCs =:= []. + +-spec take_first(#icode_callgraph{}) -> {[mfa_icode()], #icode_callgraph{}}. + +take_first(#icode_callgraph{codedict = Dict, ordered_sccs = [H|T]} = CG) -> + SCCCode = [{Mod, dict:fetch(Mod, Dict)} || Mod <- H], + {SCCCode, CG#icode_callgraph{ordered_sccs = T}}. + +-spec pp(#icode_callgraph{}) -> 'ok'. + +pp(#icode_callgraph{ordered_sccs = SCCs}) -> + io:format("Callgraph ~p\n", [SCCs]). +-endif. + +%%------------------------------------------------------------------------ +%% Get the modules called from this module + +-spec get_called_modules([mfa_icode()]) -> ordset(atom()). + +get_called_modules(List) -> + get_remote_calls(List, []). + +get_remote_calls([{_MFA, Icode}|Left], Acc) -> + CallSet = get_remote_calls_1(hipe_icode:icode_code(Icode), Acc), + get_remote_calls(Left, ordsets:union(Acc, CallSet)); +get_remote_calls([], Acc) -> + Acc. + +get_remote_calls_1([I|Left], Set) -> + NewSet = + case I of + #icode_call{} -> + case hipe_icode:call_type(I) of + remote -> + {M, _F, _A} = hipe_icode:call_fun(I), + ordsets:add_element(M, Set); + _ -> + Set + end; + #icode_enter{} -> + case hipe_icode:enter_type(I) of + remote -> + {M, _F, _A} = hipe_icode:enter_fun(I), + ordsets:add_element(M, Set); + _ -> + Set + end; + _ -> + Set + end, + get_remote_calls_1(Left, NewSet); +get_remote_calls_1([], Set) -> + Set. + +%%------------------------------------------------------------------------ +%% Find functions called (or entered) by each function. + +get_local_calls(List) -> + RemoveFun = fun ordsets:del_element/2, + get_local_calls(List, RemoveFun, []). + +get_local_calls2(List) -> + RemoveFun = fun(_,Set) -> Set end, + get_local_calls(List, RemoveFun, []). + +get_local_calls([{{_M, _F, _A} = MFA, Icode}|Left], RemoveFun, Acc) -> + CallSet = get_local_calls_1(hipe_icode:icode_code(Icode)), + %% Exclude recursive calls. + CallSet1 = RemoveFun(MFA, CallSet), + get_local_calls(Left, RemoveFun, [{MFA, CallSet1}|Acc]); +get_local_calls([], _RemoveFun, Acc) -> + Acc. + +get_local_calls_1(Icode) -> + get_local_calls_1(Icode, []). + +get_local_calls_1([I|Left], Set) -> + NewSet = + case I of + #icode_call{} -> + case hipe_icode:call_type(I) of + local -> + Fun = hipe_icode:call_fun(I), + ordsets:add_element(Fun, Set); + primop -> + case hipe_icode:call_fun(I) of + #mkfun{mfa = Fun} -> + ordsets:add_element(Fun, Set); + _ -> + Set + end; + remote -> + Set + end; + #icode_enter{} -> + case hipe_icode:enter_type(I) of + local -> + Fun = hipe_icode:enter_fun(I), + ordsets:add_element(Fun, Set); + primop -> + case hipe_icode:enter_fun(I) of + #mkfun{mfa = Fun} -> + ordsets:add_element(Fun, Set); + _ -> + Set + end; + remote -> + Set + end; + _ -> + Set + end, + get_local_calls_1(Left, NewSet); +get_local_calls_1([], Set) -> + Set. + +%%------------------------------------------------------------------------ +%% Find the edges in the callgraph. + +get_edges(Calls) -> + get_edges(Calls, []). + +get_edges([{MFA, Set}|Left], Edges) -> + EdgeList = [{MFA, X} || X <- Set], + EdgeSet = ordsets:from_list(EdgeList), + get_edges(Left, ordsets:union(EdgeSet, Edges)); +get_edges([], Edges) -> + Edges. diff --git a/lib/hipe/icode/hipe_icode_cfg.erl b/lib/hipe/icode/hipe_icode_cfg.erl new file mode 100644 index 0000000000..9b4a10e273 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_cfg.erl @@ -0,0 +1,203 @@ +%% -*- erlang-indent-level: 2 -*- +%%====================================================================== +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(hipe_icode_cfg). + +-export([bb/2, bb_add/3, + cfg_to_linear/1, + is_closure/1, + closure_arity/1, + linear_to_cfg/1, + labels/1, start_label/1, + pp/1, pp/2, + params/1, params_update/2, + pred/2, + redirect/4, + remove_trivial_bbs/1, remove_unreachable_code/1, + succ/2, + visit/2, is_visited/2, none_visited/0 + ]). +-export([postorder/1, reverse_postorder/1]). + +-define(ICODE_CFG, true). % needed by cfg.inc +%%-define(DO_ASSERT, true). + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("../flow/hipe_bb.hrl"). +-include("../flow/cfg.hrl"). +-include("../flow/cfg.inc"). + +%%---------------------------------------------------------------------- +%% Prototypes for exported functions which are Icode specific +%%---------------------------------------------------------------------- + +-spec labels(cfg()) -> [icode_lbl()]. +-spec postorder(cfg()) -> [icode_lbl()]. +-spec reverse_postorder(cfg()) -> [icode_lbl()]. + +-spec is_visited(icode_lbl(), gb_set()) -> boolean(). +-spec visit(icode_lbl(), gb_set()) -> gb_set(). + +-spec bb(cfg(), icode_lbl()) -> 'not_found' | bb(). +-spec bb_add(cfg(), icode_lbl(), bb()) -> cfg(). +-spec pred(cfg(), icode_lbl()) -> [icode_lbl()]. +-spec succ(cfg(), icode_lbl()) -> [icode_lbl()]. +-spec redirect(cfg(), icode_lbl(), icode_lbl(), icode_lbl()) -> cfg(). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% Interface to Icode +%% + +-spec linear_to_cfg(#icode{}) -> cfg(). + +linear_to_cfg(LinearIcode) -> + %% hipe_icode_pp:pp(Icode), + Code = hipe_icode:icode_code(LinearIcode), + IsClosure = hipe_icode:icode_is_closure(LinearIcode), + StartLabel = hipe_icode:label_name(hd(Code)), + CFG0 = mk_empty_cfg(hipe_icode:icode_fun(LinearIcode), + StartLabel, + hipe_icode:icode_data(LinearIcode), + IsClosure, + hipe_icode:icode_is_leaf(LinearIcode), + hipe_icode:icode_params(LinearIcode)), + CFG1 = info_update(CFG0, hipe_icode:icode_info(LinearIcode)), + CFG2 = case IsClosure of + true -> + closure_arity_update(CFG1, + hipe_icode:icode_closure_arity(LinearIcode)); + false -> + CFG1 + end, + ?opt_start_timer("Get BBs icode"), + FullCFG = take_bbs(Code, CFG2), + ?opt_stop_timer("Get BBs icode"), + FullCFG. + +%% remove_blocks(CFG, []) -> +%% CFG; +%% remove_blocks(CFG, [Lbl|Lbls]) -> +%% remove_blocks(bb_remove(CFG, Lbl), Lbls). + +-spec is_label(icode_instr()) -> boolean(). +is_label(Instr) -> + hipe_icode:is_label(Instr). + +label_name(Instr) -> + hipe_icode:label_name(Instr). + +mk_label(Name) -> + hipe_icode:mk_label(Name). + +mk_goto(Name) -> + hipe_icode:mk_goto(Name). + +branch_successors(Instr) -> + hipe_icode:successors(Instr). + +fails_to(Instr) -> + hipe_icode:fails_to(Instr). + +%% True if instr has no effect. +-spec is_comment(icode_instr()) -> boolean(). +is_comment(Instr) -> + hipe_icode:is_comment(Instr). + +%% True if instr is just a jump (no side-effects). +-spec is_goto(icode_instr()) -> boolean(). +is_goto(Instr) -> + hipe_icode:is_goto(Instr). + +-spec is_branch(icode_instr()) -> boolean(). +is_branch(Instr) -> + hipe_icode:is_branch(Instr). + +-spec is_pure_branch(icode_instr()) -> boolean(). +is_pure_branch(Instr) -> + case Instr of + #icode_if{} -> true; + #icode_goto{} -> true; + #icode_switch_val{} -> true; + #icode_switch_tuple_arity{} -> true; + #icode_type{} -> true; + %% false cases below -- XXX: are they correct? + #icode_label{} -> false; + #icode_move{} -> false; + #icode_phi{} -> false; + #icode_call{} -> false; + #icode_enter{} -> false; + #icode_return{} -> false; + #icode_begin_try{} -> false; + #icode_end_try{} -> false; + #icode_begin_handler{} -> false; + #icode_fail{} -> false; + #icode_comment{} -> false + end. + +-spec is_phi(icode_instr()) -> boolean(). +is_phi(I) -> + hipe_icode:is_phi(I). + +phi_remove_pred(I, Pred) -> + hipe_icode:phi_remove_pred(I, Pred). + +%% phi_redirect_pred(I, OldPred, NewPred) -> +%% hipe_icode:phi_redirect_pred(I, OldPred, NewPred). + +redirect_jmp(Jmp, ToOld, ToNew) -> + hipe_icode:redirect_jmp(Jmp, ToOld, ToNew). + +redirect_ops(_, CFG, _) -> %% We do not refer to labels in Icode ops. + CFG. + +%%---------------------------------------------------------------------------- + +-spec pp(cfg()) -> 'ok'. + +pp(CFG) -> + hipe_icode_pp:pp(cfg_to_linear(CFG)). + +-spec pp(io:device(), cfg()) -> 'ok'. + +pp(Dev, CFG) -> + hipe_icode_pp:pp(Dev, cfg_to_linear(CFG)). + +%%---------------------------------------------------------------------------- + +-spec cfg_to_linear(cfg()) -> #icode{}. +cfg_to_linear(CFG) -> + Code = linearize_cfg(CFG), + IsClosure = is_closure(CFG), + Icode = hipe_icode:mk_icode(function(CFG), + params(CFG), + IsClosure, + is_leaf(CFG), + Code, + data(CFG), + hipe_gensym:var_range(icode), + hipe_gensym:label_range(icode)), + Icode1 = hipe_icode:icode_info_update(Icode, info(CFG)), + case IsClosure of + true -> hipe_icode:icode_closure_arity_update(Icode1, closure_arity(CFG)); + false -> Icode1 + end. diff --git a/lib/hipe/icode/hipe_icode_coordinator.erl b/lib/hipe/icode/hipe_icode_coordinator.erl new file mode 100644 index 0000000000..a71e143192 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_coordinator.erl @@ -0,0 +1,274 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%-------------------------------------------------------------------- +%% File : hipe_icode_coordinator.erl +%% Author : Per Gustafsson <[email protected]> +%% Description : This module coordinates an Icode pass. +%% Created : 20 Feb 2007 by Per Gustafsson <[email protected]> +%%--------------------------------------------------------------------- + +-module(hipe_icode_coordinator). + +-export([coordinate/4]). + +-include("hipe_icode.hrl"). + +%%--------------------------------------------------------------------- + +-define(MAX_CONCURRENT, erlang:system_info(schedulers)). + +%%--------------------------------------------------------------------- + +-spec coordinate(hipe_digraph:hdg(), [{mfa(),boolean()}], [mfa()], module()) -> + no_return(). + +coordinate(CG, Escaping, NonEscaping, Mod) -> + ServerPid = initialize_server(Escaping, Mod), + Clean = [MFA || {MFA, _} <- Escaping], + All = NonEscaping ++ Clean, + Restart = + fun (MFALists, PM) -> restart_funs(MFALists, PM, All, ServerPid) end, + LastAction = + fun (PM) -> last_action(PM, ServerPid, Mod, All) end, + coordinate({Clean,All}, CG, gb_trees:empty(), Restart, LastAction, ServerPid). + +coordinate(MFALists, CG, PM, Restart, LastAction, ServerPid) -> + case MFALists of + {[], []} -> + LastAction(PM), + ServerPid ! stop, + receive + {stop, Ans2Pid} -> + Ans2Pid ! {done, self()}, + exit(normal) + end; + _ -> ok + end, + receive + {stop, AnsPid} -> + ServerPid ! stop, + AnsPid ! {done, self()}, + exit(normal); + Message -> + {NewPM, NewMFALists} = + case Message of + {restart_call, MFA} -> + {PM, handle_restart_call(MFA, MFALists)}; + {ready, {MFA, Pid}} -> + handle_ready(MFA, Pid, MFALists, PM); + {restart_done, MFA} -> + {PM, handle_restart_done(MFA, MFALists, CG)}; + {no_change_done, MFA} -> + {PM, handle_no_change_done(MFA, MFALists)} + end, + coordinate(Restart(NewMFALists, NewPM), CG, NewPM, Restart, + LastAction, ServerPid) + end. + +handle_restart_call(MFA, {Queue, Busy} = QB) -> + case lists:member(MFA, Queue) of + true -> + QB; + false -> + {[MFA|Queue], Busy} + end. + +handle_ready(MFA, Pid, {Queue, Busy}, PM) -> + {gb_trees:insert(MFA, Pid, PM), {Queue, Busy -- [MFA]}}. + +handle_restart_done(MFA, {Queue, Busy}, CG) -> + Restarts = hipe_digraph:get_parents(MFA, CG), + {ordsets:from_list(Restarts ++ Queue), Busy -- [MFA]}. + +handle_no_change_done(MFA, {Queue, Busy}) -> + {Queue, Busy -- [MFA]}. + +last_action(PM, ServerPid, Mod, All) -> + lists:foreach(fun (MFA) -> + gb_trees:get(MFA, PM) ! {done, final_funs(ServerPid, Mod)}, + receive + {done_rewrite, MFA} -> ok + end + end, All), + ok. + +restart_funs({Queue, Busy} = QB, PM, All, ServerPid) -> + case ?MAX_CONCURRENT - length(Busy) of + X when is_integer(X), X > 0 -> + Possible = [Pos || Pos <- Queue, (not lists:member(Pos, Busy))], + Restarts = lists:sublist(Possible, X), + lists:foreach(fun (MFA) -> + restart_fun(MFA, PM, All, ServerPid) + end, Restarts), + {Queue -- Restarts, Busy ++ Restarts}; + X when is_integer(X) -> + QB + end. + +initialize_server(Escaping, Mod) -> + Pid = spawn_link(fun () -> info_server(Mod) end), + lists:foreach(fun ({MFA, _}) -> Pid ! {set_escaping, MFA} end, Escaping), + Pid. + +safe_get_args(MFA, Cfg, Pid, Mod) -> + Mod:replace_nones(get_args(MFA, Cfg, Pid)). + +get_args(MFA, Cfg, Pid) -> + Ref = make_ref(), + Pid ! {get_call, MFA, Cfg, self(), Ref}, + receive + {Ref, Types} -> + Types + end. + +safe_get_res(MFA, Pid, Mod) -> + Mod:replace_nones(get_res(MFA, Pid)). + +get_res(MFA, Pid) -> + Ref = make_ref(), + Pid ! {get_return, MFA, self(), Ref}, + receive + {Ref, Types} -> + Types + end. + +update_return_type(MFA, NewType, Pid) -> + Ref = make_ref(), + Pid ! {update_return, MFA, NewType, self(), Ref}, + receive + {Ref, Ans} -> + Ans + end. + +update_call_type(MFA, NewTypes, Pid) -> + Ref = make_ref(), + Pid ! {update_call, MFA, NewTypes, self(), Ref}, + receive + {Ref, Ans} -> + Ans + end. + +restart_fun(MFA, PM, All, ServerPid) -> + gb_trees:get(MFA, PM) ! {analyse, analysis_funs(All, ServerPid)}, + ok. + +analysis_funs(All, Pid) -> + Self = self(), + ArgsFun = fun (MFA, Cfg) -> get_args(MFA, Cfg, Pid) end, + GetResFun = fun (MFA, Args) -> + case lists:member(MFA, All) of + true -> + case update_call_type(MFA, Args, Pid) of + do_restart -> + Self ! {restart_call, MFA}, + ok; + no_change -> + ok + end; + false -> + ok + end, + [Ans] = get_res(MFA, Pid), + Ans + end, + FinalFun = fun (MFA, RetTypes) -> + case update_return_type(MFA, RetTypes, Pid) of + do_restart -> + Self ! {restart_done, MFA}, + ok; + no_change -> + Self ! {no_change_done, MFA}, + ok + end + end, + {ArgsFun, GetResFun, FinalFun}. + +final_funs(Pid,Mod) -> + ArgsFun = fun (MFA, Cfg) -> safe_get_args(MFA, Cfg, Pid, Mod) end, + GetResFun = fun (MFA, _) -> + [Ans] = safe_get_res(MFA, Pid, Mod), + Ans + end, + FinalFun = fun (_, _) -> ok end, + {ArgsFun, GetResFun, FinalFun}. + +info_server(Mod) -> + info_server_loop(gb_trees:empty(), gb_trees:empty(), Mod). + +info_server_loop(CallInfo, ReturnInfo, Mod) -> + receive + {update_return, MFA, NewInfo, Pid, Ref} -> + NewReturnInfo = handle_update(MFA, ReturnInfo, NewInfo, Pid, Ref, Mod), + info_server_loop(CallInfo, NewReturnInfo, Mod); + {update_call, MFA, NewInfo, Pid, Ref} -> + NewCallInfo = handle_update(MFA, CallInfo, NewInfo, Pid, Ref, Mod), + info_server_loop(NewCallInfo, ReturnInfo, Mod); + {get_return, MFA, Pid, Ref} -> + Ans = + case gb_trees:lookup(MFA, ReturnInfo) of + none -> + Mod:return_none(); + {value, TypesComp} -> + Mod:return__info((TypesComp)) + end, + Pid ! {Ref, Ans}, + info_server_loop(CallInfo, ReturnInfo, Mod); + {get_call, MFA, Cfg, Pid, Ref} -> + Ans = + case gb_trees:lookup(MFA, CallInfo) of + none -> + Mod:return_none_args(Cfg, MFA); + {value, escaping} -> + Mod:return_any_args(Cfg, MFA); + {value, TypesComp} -> + Mod:return__info(TypesComp) + end, + Pid ! {Ref, Ans}, + info_server_loop(CallInfo, ReturnInfo, Mod); + {set_escaping, MFA} -> + NewCallInfo = gb_trees:enter(MFA, escaping, CallInfo), + info_server_loop(NewCallInfo, ReturnInfo, Mod); + stop -> + ok + end. + +handle_update(MFA, Tree, NewInfo, Pid, Ref, Mod) -> + ResType = + case gb_trees:lookup(MFA, Tree) of + none -> + %% io:format("First Type: ~w ~w~n", [NewType, MFA]), + Pid ! {Ref, do_restart}, + Mod:new__info(NewInfo); + {value, escaping} -> + Pid ! {Ref, no_change}, + escaping; + {value, OldInfo} -> + %% io:format("New Type: ~w ~w~n", [NewType, MFA]), + %% io:format("Old Type: ~w ~w~n", [OldType, MFA]), + case Mod:update__info(NewInfo, OldInfo) of + {true, Type} -> + Pid ! {Ref, no_change}, + Type; + {false, Type} -> + Pid ! {Ref, do_restart}, + Type + end + end, + gb_trees:enter(MFA, ResType, Tree). diff --git a/lib/hipe/icode/hipe_icode_ebb.erl b/lib/hipe/icode/hipe_icode_ebb.erl new file mode 100644 index 0000000000..966c4d7564 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_ebb.erl @@ -0,0 +1,30 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% Icode version of extended basic blocks. +%% + +-module(hipe_icode_ebb). + +-define(CFG, hipe_icode_cfg). + +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). +-include("../flow/ebb.inc"). diff --git a/lib/hipe/icode/hipe_icode_exceptions.erl b/lib/hipe/icode/hipe_icode_exceptions.erl new file mode 100644 index 0000000000..787fb05415 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_exceptions.erl @@ -0,0 +1,474 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% ==================================================================== +%% Filename : hipe_icode_exceptions.erl +%% Module : hipe_icode_exceptions +%% Purpose : Rewrite calls in intermediate code to use Continuation +%% and Fail-To labels. +%% +%% Catch-instructions work as follows: +%% - A begin_try(FailLabel) starts a catch-region which +%% is ended by a corresponding end_try(FailLabel). +%% - The handler begins with a begin_handler(FailLabel). +%% +%% However, the begin/end instructions do not always appear +%% as parentheses around the section that they protect (in +%% linear Beam/Icode). Also, different begin_catch +%% instructions can reach the same basic blocks (which may +%% raise exceptions), due to code compation optimizations +%% in the Beam compiler, even though they have different +%% handlers. Because of this, a data flow analysis is +%% necessary to find out which catches may reach which +%% basic blocks. After that, we clone basic blocks as +%% needed to ensure that each block belongs to at most one +%% unique begin_catch. The Beam does not have this problem, +%% since it will find the correct catch-handler frame +%% pushed on the stack. (Note that since there can be no +%% tail-calls within a catch region, our dataflow analysis +%% for finding all catch-stacks is sure to terminate.) +%% +%% Finally, we can remove all special catch instructions +%% and rewrite calls within catch regions to use explicit +%% fail-to labels, which is the main point of all this. +%% Fail labels that were set before this pass are kept. +%% (Note that calls that have only a continuation label do +%% not always end their basic blocks. Adding a fail label +%% to such a call can thus force us to split the block.) +%% +%% Notes : As of November 2003, primops that do not fail in the +%% normal sense are allowed to have a fail-label even +%% before this pass. (Used for the mbox-empty + get_msg +%% primitive in receives.) +%% +%% Native floating point operations cannot fail in the +%% normal sense. Instead they throw a hardware exception +%% which will be caught by a special fp check error +%% instruction. These primops do not need a fail label even +%% in a catch. This pass checks for this with +%% hipe_icode_primops:fails/1. If a call cannot fail, no +%% fail label is added. +%% +%% Explicit fails (exit, error and throw) inside +%% a catch have to be handled. They have to build their +%% exit value and jump directly to the catch handler. An +%% alternative solution would be to have a new type of +%% fail instruction that takes a fail-to label... +%% +%% CVS: +%% $Id$ +%% ==================================================================== + +-module(hipe_icode_exceptions). + +-export([fix_catches/1]). + +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). + +%%---------------------------------------------------------------------------- + +-spec fix_catches(#cfg{}) -> #cfg{}. + +fix_catches(CFG) -> + {Map, State} = build_mapping(find_catches(init_state(CFG))), + hipe_icode_cfg:remove_unreachable_code(get_cfg(rewrite(State, Map))). + +%% This finds the set of possible catch-stacks for each basic block + +find_catches(State) -> + find_catches(get_start_labels(State), + clear_visited(clear_changed(State))). + +find_catches([L|Ls], State0) -> + case is_visited(L, State0) of + true -> + find_catches(Ls, State0); + false -> + State1 = set_visited(L, State0), + Code = get_bb_code(L, State1), + Cs = get_new_catches_in(L, State1), + State2 = set_catches_in(L, Cs, State1), % memorize + Cs1 = catches_out(Code, Cs), + Ls1 = get_succ(L, State2) ++ Ls, + Cs0 = get_catches_out(L, State2), + if Cs1 =:= Cs0 -> + find_catches(Ls1, State2); + true -> + State3 = set_catches_out(L, Cs1, State2), + find_catches(Ls1, set_changed(State3)) + end + end; +find_catches([], State) -> + case is_changed(State) of + true -> + find_catches(State); + false -> + State + end. + +catches_out([I|Is], Cs) -> + catches_out(Is, catches_out_instr(I, Cs)); +catches_out([], Cs) -> + Cs. + +catches_out_instr(I, Cs) -> + case I of + #icode_begin_try{} -> + Id = hipe_icode:begin_try_label(I), + push_catch(Id, Cs); + #icode_end_try{} -> + pop_catch(Cs); + #icode_begin_handler{} -> + pop_catch(Cs); + _ -> + Cs + end. + + +%% This builds the mapping used for cloning + +build_mapping(State) -> + build_mapping(get_start_labels(State), clear_visited(State), + new_mapping()). + +build_mapping([L|Ls], State0, Map) -> + case is_visited(L, State0) of + true -> + build_mapping(Ls, State0, Map); + false -> + State1 = set_visited(L, State0), + Cs = list_of_catches(get_catches_in(L, State1)), % get memorized + {Map1, State2} = map_bb(L, Cs, State1, Map), + Ls1 = get_succ(L, State2) ++ Ls, + build_mapping(Ls1, State2, Map1) + end; +build_mapping([], State, Map) -> + {Map, State}. + +map_bb(_L, [_C], State, Map) -> + {Map, State}; +map_bb(L, [C | Cs], State, Map) -> + %% This block will be cloned - we need to create N-1 new labels. + %% The identity mapping will be used for the first element. + Map1 = new_catch_labels(Cs, L, Map), + State1 = set_catches_in(L, single_catch(C), State), % update catches in + Code = get_bb_code(L, State1), + State2 = clone(Cs, L, Code, State1, Map1), + {Map1, State2}. + +clone([C | Cs], L, Code, State, Map) -> + Ren = get_renaming(C, Map), + L1 = Ren(L), + State1 = set_bb_code(L1, Code, State), + State2 = set_catches_in(L1, single_catch(C), State1), % set catches in + clone(Cs, L, Code, State2, Map); +clone([], _L, _Code, State, _Map) -> + State. + +new_catch_labels([C | Cs], L, Map) -> + L1 = hipe_icode:label_name(hipe_icode:mk_new_label()), + Map1 = set_mapping(C, L, L1, Map), + new_catch_labels(Cs, L, Map1); +new_catch_labels([], _L, Map) -> + Map. + + +%% This does all the actual rewriting and cloning. + +rewrite(State, Map) -> + rewrite(get_start_labels(State), clear_visited(State), Map). + +rewrite([L|Ls], State0, Map) -> + case is_visited(L, State0) of + true -> + rewrite(Ls, State0, Map); + false -> + State1 = set_visited(L, State0), + Code = get_bb_code(L, State1), + Cs = list_of_catches(get_catches_in(L, State1)), % get memorized + State2 = rewrite_bb(L, Cs, Code, State1, Map), + Ls1 = get_succ(L, State2) ++ Ls, + rewrite(Ls1, State2, Map) + end; +rewrite([], State, _Map) -> + State. + +rewrite_bb(L, [C], Code, State, Map) -> + {Code1, State1} = rewrite_code(Code, C, State, Map), + set_bb_code(L, Code1, State1). + +rewrite_code(Is, C, State, Map) -> + rewrite_code(Is, C, State, Map, []). + +rewrite_code([I|Is], C, State, Map, As) -> + [C1] = list_of_catches(catches_out_instr(I, single_catch(C))), + case I of + #icode_begin_try{} -> + {I1, Is1, State1} = update_begin_try(I, Is, C, State, Map), + I2 = redirect_instr(I1, C, Map), + rewrite_code(Is1, C1, State1, Map, [I2 | As]); + #icode_end_try{} -> + rewrite_code(Is, C1, State, Map, As); + #icode_call{} -> + {I1, Is1, State1} = update_call(I, Is, C, State, Map), + I2 = redirect_instr(I1, C, Map), + rewrite_code(Is1, C1, State1, Map, [I2 | As]); + #icode_fail{} -> + {I1, Is1, State1} = update_fail(I, Is, C, State, Map), + I2 = redirect_instr(I1, C, Map), + rewrite_code(Is1, C1, State1, Map, [I2 | As]); + _ -> + I1 = redirect_instr(I, C, Map), + rewrite_code(Is, C1, State, Map, [I1 | As]) + end; +rewrite_code([], _C, State, _Map, As) -> + {lists:reverse(As), State}. + +redirect_instr(I, C, Map) -> + redirect_instr_1(I, hipe_icode:successors(I), get_renaming(C, Map)). + +redirect_instr_1(I, [L0 | Ls], Ren) -> + I1 = hipe_icode:redirect_jmp(I, L0, Ren(L0)), + redirect_instr_1(I1, Ls, Ren); +redirect_instr_1(I, [], _Ren) -> + I. + +update_begin_try(I, Is, _C, State0, _Map) -> + L = hipe_icode:begin_try_successor(I), + I1 = hipe_icode:mk_goto(L), + {I1, Is, State0}. + +update_call(I, Is, C, State0, Map) -> + case top_of_stack(C) of + [] -> + %% No active catch. Assume cont./fail labels are correct as is. + {I, Is, State0}; + L -> + %% Only update the fail label if the call *can* fail. + case hipe_icode_primops:fails(hipe_icode:call_fun(I)) of + true -> + %% We only update the fail label if it is not already set. + case hipe_icode:call_fail_label(I) of + [] -> + I1 = hipe_icode:call_set_fail_label(I, L), + %% Now the call will end the block, so we must put the rest of + %% the code (if nonempty) in a new block! + if Is =:= [] -> + {I1, Is, State0}; + true -> + L1 = hipe_icode:label_name(hipe_icode:mk_new_label()), + I2 = hipe_icode:call_set_continuation(I1, L1), + State1 = set_bb_code(L1, Is, State0), + State2 = set_catches_in(L1, single_catch(C), State1), + State3 = rewrite_bb(L1, [C], Is, State2, Map), + {I2, [], State3} + end; + _ when Is =:= [] -> + %% Something is very wrong if Is is not empty here. A call + %% with a fail label should have ended its basic block. + {I, Is, State0} + end; + false -> + %% Make sure that the fail label is not set. + I1 = hipe_icode:call_set_fail_label(I, []), + {I1, Is, State0} + end + end. + +update_fail(I, Is, C, State, _Map) -> + case hipe_icode:fail_label(I) of + [] -> + {hipe_icode:fail_set_label(I, top_of_stack(C)), Is, State}; + _ -> + {I, Is, State} + end. + + +%%--------------------------------------------------------------------- +%% Abstraction for sets of catch stacks. + +%% This is the bottom element +no_catches() -> []. + +%% A singleton set +single_catch(C) -> [C]. + +%% A single, empty stack +empty_stack() -> []. + +%% Getting the label to fail to +top_of_stack([C|_]) -> C; +top_of_stack([]) -> []. % nil is used in Icode for "no label" + +join_catches(Cs1, Cs2) -> + ordsets:union(Cs1, Cs2). + +list_of_catches(Cs) -> Cs. + +%% Note that prepending an element to all elements in the list will +%% preserve the ordering of the list, and will never make two existing +%% elements become identical, so the list is still an ordset. + +push_catch(L, []) -> + [[L]]; +push_catch(L, Cs) -> + push_catch_1(L, Cs). + +push_catch_1(L, [C|Cs]) -> + [[L|C] | push_catch_1(L, Cs)]; +push_catch_1(_L, []) -> + []. + +%% However, after discarding the head of all elements, the list +%% is no longer an ordset, and must be processed. + +pop_catch(Cs) -> + ordsets:from_list(pop_catch_1(Cs)). + +pop_catch_1([[_|C] | Cs]) -> + [C | pop_catch_1(Cs)]; +pop_catch_1([]) -> + []. + + +%%--------------------------------------------------------------------- +%% Mapping from catch-stacks to renamings on labels. + +new_mapping() -> + gb_trees:empty(). + +set_mapping(C, L0, L1, Map) -> + Dict = case gb_trees:lookup(C, Map) of + {value, Dict0} -> + gb_trees:enter(L0, L1, Dict0); + none -> + gb_trees:insert(L0, L1, gb_trees:empty()) + end, + gb_trees:enter(C, Dict, Map). + +%% Return a label renaming function for a particular catch-stack + +get_renaming(C, Map) -> + case gb_trees:lookup(C, Map) of + {value, Dict} -> + fun (L0) -> + case gb_trees:lookup(L0, Dict) of + {value, L1} -> L1; + none -> L0 + end + end; + none -> + fun (L0) -> L0 end + end. + + +%%--------------------------------------------------------------------- +%% State abstraction + +-record(state, {cfg :: #cfg{}, + changed = false :: boolean(), + succ :: #cfg{}, + pred :: #cfg{}, + start_labels :: [icode_lbl(),...], + visited = hipe_icode_cfg:none_visited() :: gb_set(), + out = gb_trees:empty() :: gb_tree(), + in = gb_trees:empty() :: gb_tree() + }). + +init_state(CFG) -> + State = #state{cfg = CFG}, + refresh_state_cache(State). + +refresh_state_cache(State) -> + CFG = State#state.cfg, + SLs = [hipe_icode_cfg:start_label(CFG)], + State#state{succ = CFG, pred = CFG, start_labels = SLs}. + +get_cfg(State) -> + State#state.cfg. + +get_start_labels(State) -> + State#state.start_labels. + +get_pred(L, State) -> + hipe_icode_cfg:pred(State#state.pred, L). + +get_succ(L, State) -> + hipe_icode_cfg:succ(State#state.succ, L). + +set_changed(State) -> + State#state{changed = true}. + +is_changed(State) -> + State#state.changed. + +clear_changed(State) -> + State#state{changed = false}. + +set_catches_out(L, Cs, State) -> + State#state{out = gb_trees:enter(L, Cs, State#state.out)}. + +get_catches_out(L, State) -> + case gb_trees:lookup(L, State#state.out) of + {value, Cs} -> Cs; + none -> no_catches() + end. + +set_catches_in(L, Cs, State) -> + State#state{in = gb_trees:enter(L, Cs, State#state.in)}. + +get_catches_in(L, State) -> + case gb_trees:lookup(L, State#state.in) of + {value, Cs} -> Cs; + none -> no_catches() + end. + +set_visited(L, State) -> + State#state{visited = hipe_icode_cfg:visit(L, State#state.visited)}. + +is_visited(L, State) -> + hipe_icode_cfg:is_visited(L, State#state.visited). + +clear_visited(State) -> + State#state{visited = hipe_icode_cfg:none_visited()}. + +get_bb_code(L, State) -> + hipe_bb:code(hipe_icode_cfg:bb(State#state.cfg, L)). + +set_bb_code(L, Code, State) -> + CFG = State#state.cfg, + CFG1 = hipe_icode_cfg:bb_add(CFG, L, hipe_bb:mk_bb(Code)), + refresh_state_cache(State#state{cfg = CFG1}). + +get_new_catches_in(L, State) -> + Ps = get_pred(L, State), + Cs = case lists:member(L, get_start_labels(State)) of + true -> single_catch(empty_stack()); + false -> no_catches() + end, + get_new_catches_in(Ps, Cs, State). + +get_new_catches_in([P | Ps], Cs, State) -> + Cs1 = join_catches(Cs, get_catches_out(P, State)), + get_new_catches_in(Ps, Cs1, State); +get_new_catches_in([], Cs, _) -> + Cs. + +%%--------------------------------------------------------------------- diff --git a/lib/hipe/icode/hipe_icode_fp.erl b/lib/hipe/icode/hipe_icode_fp.erl new file mode 100644 index 0000000000..a2ca6132d1 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_fp.erl @@ -0,0 +1,1043 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%-------------------------------------------------------------------- +%% File : hipe_icode_fp.erl +%% Author : Tobias Lindahl <[email protected]> +%% Description : One pass analysis to find floating point values. +%% Mapping to FP variables and creation of FP EBBs. +%% +%% Created : 23 Apr 2003 by Tobias Lindahl <[email protected]> +%%-------------------------------------------------------------------- + +-module(hipe_icode_fp). + +-export([cfg/1]). + +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). + +-record(state, {edge_map = gb_trees:empty() :: gb_tree(), + fp_ebb_map = gb_trees:empty() :: gb_tree(), + cfg :: #cfg{}}). + +%%-------------------------------------------------------------------- + +-spec cfg(#cfg{}) -> #cfg{}. + +cfg(Cfg) -> + %%hipe_icode_cfg:pp(Cfg), + NewCfg = annotate_fclearerror(Cfg), + State = new_state(NewCfg), + NewState = place_fp_blocks(State), + %% hipe_icode_cfg:pp(state__cfg(NewState)), + NewState2 = finalize(NewState), + NewCfg1 = state__cfg(NewState2), + %% hipe_icode_cfg:pp(NewCfg1), + NewCfg2 = unannotate_fclearerror(NewCfg1), + NewCfg2. + +%%-------------------------------------------------------------------- +%% Annotate fclearerror with information of the fail label of the +%% corresponding fcheckerror. +%%-------------------------------------------------------------------- + +annotate_fclearerror(Cfg) -> + Labels = hipe_icode_cfg:reverse_postorder(Cfg), + annotate_fclearerror(Labels, Cfg). + +annotate_fclearerror([Label|Left], Cfg) -> + BB = hipe_icode_cfg:bb(Cfg, Label), + Code = hipe_bb:code(BB), + NewCode = annotate_fclearerror1(Code, Label, Cfg, []), + NewBB = hipe_bb:code_update(BB, NewCode), + NewCfg = hipe_icode_cfg:bb_add(Cfg, Label, NewBB), + annotate_fclearerror(Left, NewCfg); +annotate_fclearerror([], Cfg) -> + Cfg. + +annotate_fclearerror1([I|Left], Label, Cfg, Acc) -> + case I of + #icode_call{} -> + case hipe_icode:call_fun(I) of + fclearerror -> + Fail = lookahead_for_fcheckerror(Left, Label, Cfg), + NewI = hipe_icode:call_fun_update(I, {fclearerror, Fail}), + annotate_fclearerror1(Left, Label, Cfg, [NewI|Acc]); + _ -> + annotate_fclearerror1(Left, Label, Cfg, [I|Acc]) + end; + _ -> + annotate_fclearerror1(Left, Label, Cfg, [I|Acc]) + end; +annotate_fclearerror1([], _Label, _Cfg, Acc) -> + lists:reverse(Acc). + +lookahead_for_fcheckerror([I|Left], Label, Cfg) -> + case I of + #icode_call{} -> + case hipe_icode:call_fun(I) of + fcheckerror -> + hipe_icode:call_fail_label(I); + _ -> + lookahead_for_fcheckerror(Left, Label, Cfg) + end; + _ -> + lookahead_for_fcheckerror(Left, Label, Cfg) + end; +lookahead_for_fcheckerror([], Label, Cfg) -> + case hipe_icode_cfg:succ(Cfg, Label) of + [] -> exit("Unterminated fp ebb"); + SuccList -> + Succ = hd(SuccList), + Code = hipe_bb:code(hipe_icode_cfg:bb(Cfg, Label)), + lookahead_for_fcheckerror(Code, Succ, Cfg) + end. + +unannotate_fclearerror(Cfg) -> + Labels = hipe_icode_cfg:reverse_postorder(Cfg), + unannotate_fclearerror(Labels, Cfg). + +unannotate_fclearerror([Label|Left], Cfg) -> + BB = hipe_icode_cfg:bb(Cfg, Label), + Code = hipe_bb:code(BB), + NewCode = unannotate_fclearerror1(Code, []), + NewBB = hipe_bb:code_update(BB, NewCode), + NewCfg = hipe_icode_cfg:bb_add(Cfg, Label, NewBB), + unannotate_fclearerror(Left, NewCfg); +unannotate_fclearerror([], Cfg) -> + Cfg. + +unannotate_fclearerror1([I|Left], Acc) -> + case I of + #icode_call{} -> + case hipe_icode:call_fun(I) of + {fclearerror, _Fail} -> + NewI = hipe_icode:call_fun_update(I, fclearerror), + unannotate_fclearerror1(Left, [NewI|Acc]); + _ -> + unannotate_fclearerror1(Left, [I|Acc]) + end; + _ -> + unannotate_fclearerror1(Left, [I|Acc]) + end; +unannotate_fclearerror1([], Acc) -> + lists:reverse(Acc). + +%%-------------------------------------------------------------------- +%% Make float EBBs +%%-------------------------------------------------------------------- + +place_fp_blocks(State) -> + WorkList = new_worklist(State), + transform_block(WorkList, State). + +transform_block(WorkList, State) -> + case get_work(WorkList) of + none -> + State; + {Label, NewWorkList} -> + %%io:format("Handling ~w \n", [Label]), + BB = state__bb(State, Label), + Code1 = hipe_bb:butlast(BB), + Last = hipe_bb:last(BB), + NofPreds = length(state__pred(State, Label)), + Map = state__map(State, Label), + FilteredMap = filter_map(Map, NofPreds), + {Prelude, NewFilteredMap} = do_prelude(FilteredMap), + + %% Take care to have a map without any new bindings from the + %% last instruction if it can fail. + {FailMap, NewCode1} = transform_instrs(Code1, Map, NewFilteredMap, []), + {NewMap, NewCode2} = transform_instrs([Last], Map, FailMap, []), + SuccSet0 = ordsets:from_list(hipe_icode:successors(Last)), + FailSet = ordsets:from_list(hipe_icode:fails_to(Last)), + SuccSet = ordsets:subtract(SuccSet0, FailSet), + NewCode = NewCode1 ++ NewCode2, + NewBB = hipe_bb:code_update(BB, Prelude++NewCode), + NewState = state__bb_add(State, Label, NewBB), + case update_maps(NewState, Label, SuccSet, NewMap, FailSet, FailMap) of + fixpoint -> + transform_block(NewWorkList, NewState); + {NewState1, AddBlocks} -> + NewWorkList1 = add_work(NewWorkList, AddBlocks), + transform_block(NewWorkList1, NewState1) + end + end. + +update_maps(State, Label, SuccSet, SuccMap, FailSet, FailMap) -> + {NewState, Add1} = update_maps(State, Label, SuccSet, SuccMap, []), + case update_maps(NewState, Label, FailSet, FailMap, Add1) of + {_NewState1, []} -> fixpoint; + {_NewState1, _Add} = Ret -> Ret + end. + +update_maps(State, From, [To|Left], Map, Acc) -> + case state__map_update(State, From, To, Map) of + fixpoint -> + update_maps(State, From, Left, Map, Acc); + NewState -> + update_maps(NewState, From, Left, Map, [To|Acc]) + end; +update_maps(State, _From, [], _Map, Acc) -> + {State, Acc}. + +transform_instrs([I|Left], PhiMap, Map, Acc) -> + Defines = hipe_icode:defines(I), + NewMap = delete_all(Defines, Map), + NewPhiMap = delete_all(Defines, PhiMap), + case I of + #icode_phi{} -> + Uses = hipe_icode:uses(I), + case [X || X <- Uses, lookup(X, PhiMap) =/= none] of + [] -> + %% No ordinary variables from the argument have been untagged. + transform_instrs(Left, NewPhiMap, NewMap, [I|Acc]); + Uses -> + %% All arguments are untagged. Let's untag the destination. + Dst = hipe_icode:phi_dst(I), + NewDst = hipe_icode:mk_new_fvar(), + NewMap1 = gb_trees:enter(Dst, NewDst, NewMap), + NewI = subst_phi_uncond(I, NewDst, PhiMap), + transform_instrs(Left, NewPhiMap, NewMap1, [NewI|Acc]); + _ -> + %% Some arguments are untagged. Keep the destination. + Dst = hipe_icode:phi_dst(I), + NewI = subst_phi(I, Dst, PhiMap), + transform_instrs(Left, NewPhiMap, NewMap, [NewI|Acc]) + end; + #icode_call{} -> + case hipe_icode:call_fun(I) of + X when X =:= unsafe_untag_float orelse X =:= conv_to_float -> + [Dst] = hipe_icode:defines(I), + case hipe_icode:uses(I) of + [] -> %% Constant + transform_instrs(Left, NewPhiMap, NewMap, [I|Acc]); + [Src] -> + case lookup(Src, Map) of + none -> + NewMap1 = gb_trees:enter(Src, {assigned, Dst}, NewMap), + transform_instrs(Left, NewPhiMap, NewMap1, [I|Acc]); + Dst -> + %% This is the instruction that untagged the variable. + %% Use old maps. + transform_instrs(Left, NewPhiMap, Map, [I|Acc]); + FVar -> + %% The variable was already untagged. + %% This instruction can be changed to a move. + NewI = hipe_icode:mk_move(Dst, FVar), + case hipe_icode:call_continuation(I) of + [] -> + transform_instrs(Left,NewPhiMap,NewMap,[NewI|Acc]); + ContLbl -> + Goto = hipe_icode:mk_goto(ContLbl), + transform_instrs(Left, NewPhiMap, NewMap, + [Goto, NewI|Acc]) + end + end + end; + unsafe_tag_float -> + [Dst] = hipe_icode:defines(I), + [Src] = hipe_icode:uses(I), + NewMap1 = gb_trees:enter(Dst, {assigned, Src}, NewMap), + transform_instrs(Left, NewPhiMap, NewMap1,[I|Acc]); + _ -> + {NewMap1, NewAcc} = check_for_fop_candidates(I, NewMap, Acc), + transform_instrs(Left, NewPhiMap, NewMap1, NewAcc) + end; + _ -> + NewIns = handle_untagged_arguments(I, NewMap), + transform_instrs(Left, NewPhiMap, NewMap, NewIns ++ Acc) + end; +transform_instrs([], _PhiMap, Map, Acc) -> + {Map, lists:reverse(Acc)}. + +check_for_fop_candidates(I, Map, Acc) -> + case is_fop_cand(I) of + false -> + NewIs = handle_untagged_arguments(I, Map), + {Map, NewIs ++ Acc}; + true -> + Fail = hipe_icode:call_fail_label(I), + Cont = hipe_icode:call_continuation(I), + Op = fun_to_fop(hipe_icode:call_fun(I)), + case Fail of + [] -> + Args = hipe_icode:args(I), + ConstArgs = [X || X <- Args, hipe_icode:is_const(X)], + try lists:foreach(fun(X) -> float(hipe_icode:const_value(X)) end, + ConstArgs) of + ok -> + %%io:format("Changing ~w to ~w\n", [hipe_icode:call_fun(I), Op]), + Uses = hipe_icode:uses(I), + Defines = hipe_icode:defines(I), + Convs = [X||X <- remove_duplicates(Uses), lookup(X,Map) =:= none], + NewMap0 = add_new_bindings_assigned(Convs, Map), + NewMap = add_new_bindings_unassigned(Defines, NewMap0), + ConvIns = get_conv_instrs(Convs, NewMap), + NewI = hipe_icode:mk_primop(lookup_list(Defines, NewMap), Op, + lookup_list_keep_consts(Args,NewMap), + Cont, Fail), + NewI2 = conv_consts(ConstArgs, NewI), + {NewMap, [NewI2|ConvIns]++Acc} + catch + error:badarg -> + %% This instruction will fail at runtime. The warning + %% should already have happened in hipe_icode_type. + NewIs = handle_untagged_arguments(I, Map), + {Map, NewIs ++ Acc} + end; + _ -> %% Bailing out! Can't handle instructions in catches (yet). + NewIs = handle_untagged_arguments(I, Map), + {Map, NewIs ++ Acc} + end + end. + + +%% If this is an instruction that needs to operate on tagged values, +%% which currently are untagged, we must tag the values and perhaps +%% end the fp ebb. + +handle_untagged_arguments(I, Map) -> + case [X || X <- hipe_icode:uses(I), must_be_tagged(X, Map)] of + [] -> + [I]; + Tag -> + TagIntrs = + [hipe_icode:mk_primop([Dst], unsafe_tag_float, + [gb_trees:get(Dst, Map)]) || Dst <- Tag], + [I|TagIntrs] + end. + +%% Add phi nodes for untagged fp values. + +do_prelude(Map) -> + case gb_trees:lookup(phi, Map) of + none -> + {[], Map}; + {value, List} -> + %%io:format("Adding phi: ~w\n", [List]), + Fun = fun ({FVar, Bindings}, Acc) -> + [hipe_icode:mk_phi(FVar, Bindings)|Acc] + end, + {lists:foldl(Fun, [], List), gb_trees:delete(phi, Map)} + end. + +split_code(Code) -> + split_code(Code, []). + +split_code([I], Acc) -> + {lists:reverse(Acc), I}; +split_code([I|Left], Acc) -> + split_code(Left, [I|Acc]). + + +%% When all code is mapped to fp instructions we must make sure that +%% the fp ebb information going into each block is the same as the +%% information coming out of each predecessor. Otherwise, we must add +%% a block in between. + +finalize(State) -> + Worklist = new_worklist(State), + NewState = place_error_handling(Worklist, State), + Edges = needs_fcheckerror(NewState), + finalize(Edges, NewState). + +finalize([{From, To}|Left], State) -> + NewState = add_fp_ebb_fixup(From, To, State), + finalize(Left, NewState); +finalize([], State) -> + State. + +needs_fcheckerror(State) -> + Cfg = state__cfg(State), + Labels = hipe_icode_cfg:labels(Cfg), + needs_fcheckerror(Labels, State, []). + +needs_fcheckerror([Label|Left], State, Acc) -> + case state__get_in_block_in(State, Label) of + {true, _} -> + needs_fcheckerror(Left, State, Acc); + false -> + Pred = state__pred(State, Label), + case [X || X <- Pred, state__get_in_block_out(State, X) =/= false] of + [] -> + needs_fcheckerror(Left, State, Acc); + NeedsFcheck -> + case length(Pred) =:= length(NeedsFcheck) of + true -> + %% All edges need fcheckerror. Add this to the + %% beginning of the block instead. + needs_fcheckerror(Left, State, [{none, Label}|Acc]); + false -> + Edges = [{X, Label} || X <- NeedsFcheck], + needs_fcheckerror(Left, State, Edges ++ Acc) + end + end + end; +needs_fcheckerror([], _State, Acc) -> + Acc. + +add_fp_ebb_fixup('none', To, State) -> + %% Add the fcheckerror to the start of the block. + BB = state__bb(State, To), + Code = hipe_bb:code(BB), + Phis = lists:takewhile(fun(X) -> hipe_icode:is_phi(X) end, Code), + TailCode = lists:dropwhile(fun(X) -> hipe_icode:is_phi(X) end, Code), + FC = hipe_icode:mk_primop([], fcheckerror, []), + NewCode = Phis ++ [FC|TailCode], + state__bb_add(State, To, hipe_bb:code_update(BB, NewCode)); +add_fp_ebb_fixup(From, To, State) -> + FCCode = [hipe_icode:mk_primop([], fcheckerror, [], To, [])], + FCBB = hipe_bb:mk_bb(FCCode), + FCLabel = hipe_icode:label_name(hipe_icode:mk_new_label()), + NewState = state__bb_add(State, FCLabel, FCBB), + NewState1 = state__redirect(NewState, From, To, FCLabel), + ToBB = state__bb(NewState, To), + ToCode = hipe_bb:code(ToBB), + NewToCode = redirect_phis(ToCode, From, FCLabel), + NewToBB = hipe_bb:code_update(ToBB, NewToCode), + state__bb_add(NewState1, To, NewToBB). + +redirect_phis(Code, OldFrom, NewFrom) -> + redirect_phis(Code, OldFrom, NewFrom, []). + +redirect_phis([I|Is] = Code, OldFrom, NewFrom, Acc) -> + case I of + #icode_phi{} -> + NewI = hipe_icode:phi_redirect_pred(I, OldFrom, NewFrom), + redirect_phis(Is, OldFrom, NewFrom, [NewI|Acc]); + _ -> + lists:reverse(Acc) ++ Code + end; +redirect_phis([], _OldFrom, _NewFrom, Acc) -> + lists:reverse(Acc). + +subst_phi(I, Dst, Map) -> + ArgList = subst_phi_uses0(hipe_icode:phi_arglist(I), Map, []), + hipe_icode:mk_phi(Dst, ArgList). + +subst_phi_uses0([{Pred, Var}|Left], Map, Acc) -> + case gb_trees:lookup(Var, Map) of + {value, List} -> + case lists:keyfind(Pred, 1, List) of + {Pred, {assigned, _NewVar}} -> + %% The variable is untagged, but it has been assigned. Keep it! + subst_phi_uses0(Left, Map, [{Pred, Var} | Acc]); + {Pred, _NewVar} = PredNV -> + %% The variable is untagged and it has never been assigned as tagged. + subst_phi_uses0(Left, Map, [PredNV | Acc]); + false -> + %% The variable is not untagged. + subst_phi_uses0(Left, Map, [{Pred, Var} | Acc]) + end; + none -> + %% The variable is not untagged. + subst_phi_uses0(Left, Map, [{Pred, Var} | Acc]) + end; +subst_phi_uses0([], _Map, Acc) -> + Acc. + +subst_phi_uncond(I, Dst, Map) -> + ArgList = subst_phi_uses_uncond0(hipe_icode:phi_arglist(I), Map, []), + hipe_icode:mk_phi(Dst, ArgList). + +subst_phi_uses_uncond0([{Pred, Var}|Left], Map, Acc) -> + case gb_trees:lookup(Var, Map) of + {value, List} -> + case lists:keyfind(Pred, 1, List) of + {Pred, {assigned, NewVar}} -> + %% The variable is untagged! + subst_phi_uses_uncond0(Left, Map, [{Pred, NewVar} | Acc]); + {Pred, _NewVar} = PredNV -> + %% The variable is untagged! + subst_phi_uses_uncond0(Left, Map, [PredNV | Acc]); + false -> + %% The variable is not untagged. + subst_phi_uses_uncond0(Left, Map, [{Pred, Var} | Acc]) + end; + none -> + %% The variable is not untagged. + subst_phi_uses_uncond0(Left, Map, [{Pred, Var} | Acc]) + end; +subst_phi_uses_uncond0([], _Map, Acc) -> + Acc. + +place_error_handling(WorkList, State) -> + case get_work(WorkList) of + none -> + State; + {Label, NewWorkList} -> + BB = state__bb(State, Label), + Code = hipe_bb:code(BB), + case state__join_in_block(State, Label) of + fixpoint -> + place_error_handling(NewWorkList, State); + {NewState, NewInBlock} -> + {NewCode1, InBlockOut} = place_error(Code, NewInBlock, []), + Succ = state__succ(NewState, Label), + NewCode2 = handle_unchecked_end(Succ, NewCode1, InBlockOut), + NewBB = hipe_bb:code_update(BB, NewCode2), + NewState1 = state__bb_add(NewState, Label, NewBB), + NewState2 = state__in_block_out_update(NewState1, Label, InBlockOut), + NewWorkList1 = add_work(NewWorkList, Succ), + place_error_handling(NewWorkList1, NewState2) + end + end. + +place_error([I|Left], InBlock, Acc) -> + case I of + #icode_call{} -> + case hipe_icode:call_fun(I) of + X when X =:= fp_add; X =:= fp_sub; + X =:= fp_mul; X =:= fp_div; X =:= fnegate -> + case InBlock of + false -> + Clear = hipe_icode:mk_primop([], {fclearerror, []}, []), + place_error(Left, {true, []}, [I, Clear|Acc]); + {true, _} -> + place_error(Left, InBlock, [I|Acc]) + end; + unsafe_tag_float -> + case InBlock of + {true, Fail} -> + Check = hipe_icode:mk_primop([], fcheckerror, [], [], Fail), + place_error(Left, false, [I, Check|Acc]); + false -> + place_error(Left, InBlock, [I|Acc]) + end; + {fclearerror, Fail} -> + case InBlock of + {true, Fail} -> + %% We can remove this fclearerror! + case hipe_icode:call_continuation(I) of + [] -> + place_error(Left, InBlock, Acc); + Cont -> + place_error(Left, InBlock, [hipe_icode:mk_goto(Cont)|Acc]) + end; + {true, _OtherFail} -> + %% TODO: This can be handled but it requires breaking up + %% the BB in two. Currently this should not happen. + exit("Starting fp ebb with different fail label"); + false -> + place_error(Left, {true, Fail}, [I|Acc]) + end; + fcheckerror -> + case {true, hipe_icode:call_fail_label(I)} of + InBlock -> + %% No problem + place_error(Left, false, [I|Acc]); + NewInblock -> + exit({"Fcheckerror has the wrong fail label", + InBlock, NewInblock}) + end; + X when X =:= conv_to_float; X =:= unsafe_untag_float -> + place_error(Left, InBlock, [I|Acc]); + _Other -> + case hipe_icode_primops:fails(hipe_icode:call_fun(I)) of + false -> + place_error(Left, InBlock, [I|Acc]); + true -> + case InBlock of + {true, Fail} -> + Check = hipe_icode:mk_primop([], fcheckerror, [], [], Fail), + place_error(Left, false, [I, Check|Acc]); + false -> + place_error(Left, InBlock, [I|Acc]) + end + end + end; + #icode_fail{} -> + place_error_1(I, Left, InBlock, Acc); + #icode_return{} -> + place_error_1(I, Left, InBlock, Acc); + #icode_enter{} -> + place_error_1(I, Left, InBlock, Acc); + Other -> + case instr_allowed_in_fp_ebb(Other) of + true -> + place_error(Left, InBlock, [I|Acc]); + false -> + case InBlock of + {true, []} -> + Check = hipe_icode:mk_primop([], fcheckerror, []), + place_error(Left, false, [I, Check|Acc]); + {true, _} -> + exit({"Illegal instruction in caught fp ebb", I}); + false -> + place_error(Left, InBlock, [I|Acc]) + end + end + end; +place_error([], InBlock, Acc) -> + {lists:reverse(Acc), InBlock}. + +place_error_1(I, Left, InBlock, Acc) -> + case InBlock of + {true, []} -> + Check = hipe_icode:mk_primop([], fcheckerror, []), + place_error(Left, false, [I, Check|Acc]); + {true, _} -> + exit({"End of control flow in caught fp ebb", I}); + false -> + place_error(Left, InBlock, [I|Acc]) + end. + +%% If the block has no successors and we still are in a fp ebb we must +%% end it to make sure we don't have any unchecked fp exceptions. + +handle_unchecked_end(Succ, Code, InBlock) -> + case Succ of + [] -> + case InBlock of + {true, []} -> + {TopCode, Last} = split_code(Code), + NewI = hipe_icode:mk_primop([], fcheckerror, []), + TopCode ++ [NewI, Last]; + false -> + Code + end; + _ -> + Code + end. + +instr_allowed_in_fp_ebb(Instr) -> + case Instr of + #icode_comment{} -> true; + #icode_goto{} -> true; + #icode_if{} -> true; + #icode_move{} -> true; + #icode_phi{} -> true; + #icode_begin_handler{} -> true; + #icode_switch_tuple_arity{} -> true; + #icode_switch_val{} -> true; + #icode_type{} -> true; + _ -> false + end. + +%%============================================================= +%% Help functions +%%============================================================= + +%% ------------------------------------------------------------ +%% Handling the gb_tree + +delete_all([Key|Left], Tree) -> + delete_all(Left, gb_trees:delete_any(Key, Tree)); +delete_all([], Tree) -> + Tree. + +lookup_list(List, Info) -> + lookup_list(List, fun lookup/2, Info, []). + +lookup_list([H|T], Fun, Info, Acc) -> + lookup_list(T, Fun, Info, [Fun(H, Info)|Acc]); +lookup_list([], _, _, Acc) -> + lists:reverse(Acc). + +lookup(Key, Tree) -> + case hipe_icode:is_const(Key) of + %% This can be true if the same constant has been + %% untagged more than once + true -> none; + false -> + case gb_trees:lookup(Key, Tree) of + none -> none; + {value, {assigned, Val}} -> Val; + {value, Val} -> Val + end + end. + +lookup_list_keep_consts(List, Info) -> + lookup_list(List, fun lookup_keep_consts/2, Info, []). + +lookup_keep_consts(Key, Tree) -> + case hipe_icode:is_const(Key) of + true -> Key; + false -> + case gb_trees:lookup(Key, Tree) of + none -> none; + {value, {assigned, Val}} -> Val; + {value, Val} -> Val + end + end. + +get_type(Var) -> + case hipe_icode:is_const(Var) of + true -> erl_types:t_from_term(hipe_icode:const_value(Var)); + false -> + case hipe_icode:is_annotated_variable(Var) of + true -> + {type_anno, Type, _} = hipe_icode:variable_annotation(Var), + Type +%%% false -> erl_types:t_any() + end + end. + +%% ------------------------------------------------------------ +%% Handling the map from variables to fp-variables + +join_maps(Edges, EdgeMap) -> + join_maps(Edges, EdgeMap, gb_trees:empty()). + +join_maps([Edge = {Pred, _}|Left], EdgeMap, Map) -> + case gb_trees:lookup(Edge, EdgeMap) of + none -> + %% All predecessors have not been handled. Use empty map. + gb_trees:empty(); + {value, OldMap} -> + NewMap = join_maps0(gb_trees:to_list(OldMap), Pred, Map), + join_maps(Left, EdgeMap, NewMap) + end; +join_maps([], _, Map) -> + Map. + +join_maps0([{phi, _}|Tail], Pred, Map) -> + join_maps0(Tail, Pred, Map); +join_maps0([{Var, FVar}|Tail], Pred, Map) -> + case gb_trees:lookup(Var, Map) of + none -> + join_maps0(Tail, Pred, gb_trees:enter(Var, [{Pred, FVar}], Map)); + {value, List} -> + case lists:keyfind(Pred, 1, List) of + false -> + join_maps0(Tail, Pred, gb_trees:update(Var, [{Pred, FVar}|List], Map)); + {Pred, FVar} -> + %% No problem. + join_maps0(Tail, Pred, Map); + _ -> + exit('New binding to same variable') + end + end; +join_maps0([], _, Map) -> + Map. + +filter_map(Map, NofPreds) -> + filter_map(gb_trees:to_list(Map), NofPreds, Map). + +filter_map([{Var, Bindings}|Left], NofPreds, Map) -> + case length(Bindings) =:= NofPreds of + true -> + case all_args_equal(Bindings) of + true -> + {_, FVar} = hd(Bindings), + filter_map(Left, NofPreds, gb_trees:update(Var, FVar, Map)); + false -> + PhiDst = hipe_icode:mk_new_fvar(), + PhiArgs = strip_of_assigned(Bindings), + NewMap = + case gb_trees:lookup(phi, Map) of + none -> + gb_trees:insert(phi, [{PhiDst, PhiArgs}], Map); + {value, Val} -> + gb_trees:update(phi, [{PhiDst, PhiArgs}|Val], Map) + end, + NewBinding = + case bindings_are_assigned(Bindings) of + true -> {assigned, PhiDst}; + false -> PhiDst + end, + filter_map(Left, NofPreds, gb_trees:update(Var, NewBinding, NewMap)) + end; + false -> + filter_map(Left, NofPreds, gb_trees:delete(Var, Map)) + end; +filter_map([], _NofPreds, Map) -> + Map. + +bindings_are_assigned([{_, {assigned, _}}|Left]) -> + assert_assigned(Left), + true; +bindings_are_assigned(Bindings) -> + assert_not_assigned(Bindings), + false. + +assert_assigned([{_, {assigned, _}}|Left]) -> + assert_assigned(Left); +assert_assigned([]) -> + ok. + +assert_not_assigned([{_, FVar}|Left]) -> + true = hipe_icode:is_fvar(FVar), + assert_not_assigned(Left); +assert_not_assigned([]) -> + ok. + +%% all_args_equal returns true if the mapping for a variable is the +%% same from all predecessors, i.e., we do not need a phi-node. + +all_args_equal([{_, FVar}|Left]) -> + all_args_equal(Left, FVar). + +all_args_equal([{_, FVar1}|Left], FVar1) -> + all_args_equal(Left, FVar1); +all_args_equal([], _) -> + true; +all_args_equal(_, _) -> + false. + + +%% We differentiate between values that have been assigned as +%% tagged variables and those that got a 'virtual' binding. + +add_new_bindings_unassigned([Var|Left], Map) -> + FVar = hipe_icode:mk_new_fvar(), + add_new_bindings_unassigned(Left, gb_trees:insert(Var, FVar, Map)); +add_new_bindings_unassigned([], Map) -> + Map. + +add_new_bindings_assigned([Var|Left], Map) -> + case lookup(Var, Map) of + none -> + FVar = hipe_icode:mk_new_fvar(), + NewMap = gb_trees:insert(Var, {assigned, FVar}, Map), + add_new_bindings_assigned(Left, NewMap); + _ -> + add_new_bindings_assigned(Left, Map) + end; +add_new_bindings_assigned([], Map) -> + Map. + +strip_of_assigned(List) -> + strip_of_assigned(List, []). + +strip_of_assigned([{Pred, {assigned, Val}}|Left], Acc) -> + strip_of_assigned(Left, [{Pred, Val}|Acc]); +strip_of_assigned([Tuple|Left], Acc) -> + strip_of_assigned(Left, [Tuple|Acc]); +strip_of_assigned([], Acc) -> + Acc. + +%% ------------------------------------------------------------ +%% Help functions for the transformation from ordinary instruction to +%% fp-instruction + +is_fop_cand(I) -> + case hipe_icode:call_fun(I) of + '/' -> true; + Fun -> + case fun_to_fop(Fun) of + false -> false; + _ -> any_is_float(hipe_icode:args(I)) + end + end. + +any_is_float(Vars) -> + lists:any(fun (V) -> erl_types:t_is_float(get_type(V)) end, Vars). + +remove_duplicates(List) -> + remove_duplicates(List, []). + +remove_duplicates([X|Left], Acc) -> + case lists:member(X, Acc) of + true -> + remove_duplicates(Left, Acc); + false -> + remove_duplicates(Left, [X|Acc]) + end; +remove_duplicates([], Acc) -> + Acc. + +fun_to_fop(Fun) -> + case Fun of + '+' -> fp_add; + '-' -> fp_sub; + '*' -> fp_mul; + '/' -> fp_div; + _ -> false + end. + + +%% If there is a tagged version of this variable available we don't +%% have to tag the untagged version. + +must_be_tagged(Var, Map) -> + case gb_trees:lookup(Var, Map) of + none -> false; + {value, {assigned, _}} -> false; + {value, Val} -> hipe_icode:is_fvar(Val) + end. + + +%% Converting to floating point variables + +get_conv_instrs(Vars, Map) -> + get_conv_instrs(Vars, Map, []). + +get_conv_instrs([Var|Left], Map, Acc) -> + {_, Dst} = gb_trees:get(Var, Map), + NewI = + case erl_types:t_is_float(get_type(Var)) of + true -> + [hipe_icode:mk_primop([Dst], unsafe_untag_float, [Var])]; + false -> + [hipe_icode:mk_primop([Dst], conv_to_float, [Var])] + end, + get_conv_instrs(Left, Map, NewI++Acc); +get_conv_instrs([], _, Acc) -> + Acc. + + +conv_consts(ConstArgs, I) -> + conv_consts(ConstArgs, I, []). + +conv_consts([Const|Left], I, Subst) -> + NewConst = hipe_icode:mk_const(float(hipe_icode:const_value(Const))), + conv_consts(Left, I, [{Const, NewConst}|Subst]); +conv_consts([], I, Subst) -> + hipe_icode:subst_uses(Subst, I). + + +%% _________________________________________________________________ +%% +%% Handling the state +%% + +new_state(Cfg) -> + #state{cfg = Cfg}. + +state__cfg(#state{cfg = Cfg}) -> + Cfg. + +state__succ(#state{cfg = Cfg}, Label) -> + hipe_icode_cfg:succ(Cfg, Label). + +state__pred(#state{cfg = Cfg}, Label) -> + hipe_icode_cfg:pred(Cfg, Label). + +state__redirect(S = #state{cfg = Cfg}, From, ToOld, ToNew) -> + NewCfg = hipe_icode_cfg:redirect(Cfg, From, ToOld, ToNew), + S#state{cfg=NewCfg}. + +state__bb(#state{cfg = Cfg}, Label) -> + hipe_icode_cfg:bb(Cfg, Label). + +state__bb_add(S = #state{cfg = Cfg}, Label, BB) -> + NewCfg = hipe_icode_cfg:bb_add(Cfg, Label, BB), + S#state{cfg = NewCfg}. + +state__map(S = #state{edge_map = EM}, To) -> + join_maps([{From, To} || From <- state__pred(S, To)], EM). + +state__map_update(S = #state{edge_map = EM}, From, To, Map) -> + FromTo = {From, To}, + MapChanged = + case gb_trees:lookup(FromTo, EM) of + {value, Map1} -> not match(Map1, Map); + none -> true + end, + case MapChanged of + true -> + NewEM = gb_trees:enter(FromTo, Map, EM), + S#state{edge_map = NewEM}; + false -> + fixpoint + end. + +state__join_in_block(S = #state{fp_ebb_map = Map}, Label) -> + Pred = state__pred(S, Label), + Edges = [{X, Label} || X <- Pred], + NewInBlock = join_in_block([gb_trees:lookup(X, Map) || X <- Edges]), + InBlockLabel = {inblock_in, Label}, + case gb_trees:lookup(InBlockLabel, Map) of + none -> + NewMap = gb_trees:insert(InBlockLabel, NewInBlock, Map), + {S#state{fp_ebb_map = NewMap}, NewInBlock}; + {value, NewInBlock} -> + fixpoint; + _Other -> + NewMap = gb_trees:update(InBlockLabel, NewInBlock, Map), + {S#state{fp_ebb_map = NewMap}, NewInBlock} + end. + +state__in_block_out_update(S = #state{fp_ebb_map = Map}, Label, NewInBlock) -> + Succ = state__succ(S, Label), + Edges = [{Label, X} || X <- Succ], + NewMap = update_edges(Edges, NewInBlock, Map), + NewMap1 = gb_trees:enter({inblock_out, Label}, NewInBlock, NewMap), + S#state{fp_ebb_map = NewMap1}. + +update_edges([Edge|Left], NewInBlock, Map) -> + NewMap = gb_trees:enter(Edge, NewInBlock, Map), + update_edges(Left, NewInBlock, NewMap); +update_edges([], _NewInBlock, NewMap) -> + NewMap. + +join_in_block([]) -> + false; +join_in_block([none|_]) -> + false; +join_in_block([{value, InBlock}|Left]) -> + join_in_block(Left, InBlock). + +join_in_block([none|_], _Current) -> + false; +join_in_block([{value, InBlock}|Left], Current) -> + if Current =:= InBlock -> join_in_block(Left, Current); + Current =:= false -> false; + InBlock =:= false -> false; + true -> exit("Basic block is in two different fp ebb:s") + end; +join_in_block([], Current) -> + Current. + + +state__get_in_block_in(#state{fp_ebb_map = Map}, Label) -> + gb_trees:get({inblock_in, Label}, Map). + +state__get_in_block_out(#state{fp_ebb_map = Map}, Label) -> + gb_trees:get({inblock_out, Label}, Map). + + +new_worklist(#state{cfg = Cfg}) -> + Start = hipe_icode_cfg:start_label(Cfg), + {[Start], [], gb_sets:insert(Start, gb_sets:empty())}. + +get_work({[Label|Left], List, Set}) -> + {Label, {Left, List, gb_sets:delete(Label, Set)}}; +get_work({[], [], _Set}) -> + none; +get_work({[], List, Set}) -> + get_work({lists:reverse(List), [], Set}). + +add_work({List1, List2, Set} = Work, [Label|Left]) -> + case gb_sets:is_member(Label, Set) of + true -> + add_work(Work, Left); + false -> + %% io:format("Added work: ~w\n", [Label]), + NewSet = gb_sets:insert(Label, Set), + add_work({List1, [Label|List2], NewSet}, Left) + end; +add_work(WorkList, []) -> + WorkList. + +match(Tree1, Tree2) -> + match_1(gb_trees:to_list(Tree1), Tree2) andalso + match_1(gb_trees:to_list(Tree2), Tree1). + +match_1([{Key, Val}|Left], Tree2) -> + case gb_trees:lookup(Key, Tree2) of + {value, Val} -> + match_1(Left, Tree2); + _ -> false + end; +match_1([], _) -> + true. diff --git a/lib/hipe/icode/hipe_icode_heap_test.erl b/lib/hipe/icode/hipe_icode_heap_test.erl new file mode 100644 index 0000000000..92d5f023fa --- /dev/null +++ b/lib/hipe/icode/hipe_icode_heap_test.erl @@ -0,0 +1,200 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Copyright (c) 2000 by Erik Johansson. All Rights Reserved +%% ==================================================================== +%% Filename : hipe_icode_heap_test.erl +%% Module : hipe_icode_heap_test +%% Purpose : +%% Notes : +%% History : * 2000-11-07 Erik Johansson ([email protected]): +%% Created. +%% +%% $Id$ +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(hipe_icode_heap_test). + +-export([cfg/1]). + +-define(DO_ASSERT,true). + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("hipe_icode_primops.hrl"). +-include("../flow/cfg.hrl"). +-include("../rtl/hipe_literals.hrl"). + +%------------------------------------------------------------------------- + +-spec cfg(#cfg{}) -> #cfg{}. + +cfg(CFG) -> + Icode = hipe_icode_cfg:cfg_to_linear(CFG), + Code = hipe_icode:icode_code(Icode), + ActualVmax = hipe_icode:highest_var(Code), + ActualLmax = hipe_icode:highest_label(Code), + hipe_gensym:set_label(icode, ActualLmax+1), + hipe_gensym:set_var(icode, ActualVmax+1), + EBBs = hipe_icode_ebb:cfg(CFG), + {EBBcode,_Visited} = ebbs(EBBs, [], CFG), + NewCode = add_gc_tests(EBBcode), + NewIcode = hipe_icode:icode_code_update(Icode, NewCode), + NewCFG = hipe_icode_cfg:linear_to_cfg(NewIcode), + %% hipe_icode_cfg:pp(NewCFG), + NewCFG. + +ebbs([EBB|EBBs], Visited, CFG) -> + case hipe_icode_ebb:type(EBB) of + node -> + L = hipe_icode_ebb:node_label(EBB), + case visited(L, Visited) of + true -> + ebbs(EBBs, Visited, CFG); + false -> + EBBCode = hipe_bb:code(hipe_icode_cfg:bb(CFG, L)), + case hipe_icode_ebb:node_successors(EBB) of + [Succ|Succs] -> + {[SuccCode|More], Visited1} = + ebbs([Succ], [L|Visited], CFG), + {[OtherCode|MoreOther], Visited2} = + ebbs(Succs ++ EBBs, Visited1, CFG), + {[[hipe_icode:mk_label(L)|EBBCode] ++ SuccCode| + More] ++ [OtherCode|MoreOther], + Visited2}; + [] -> + {OtherCode, Visited1} = ebbs(EBBs, [L|Visited], CFG), + {[[hipe_icode:mk_label(L)|EBBCode] | OtherCode], Visited1} + end + end; + leaf -> + ebbs(EBBs, Visited, CFG) + end; +ebbs([], Visited,_) -> + {[[]], Visited}. + +visited(L, Visited) -> + lists:member(L, Visited). + +add_gc_tests([[]|EBBCodes]) -> add_gc_tests(EBBCodes); +add_gc_tests([EBBCode|EBBCodes]) -> + case need(EBBCode, 0, []) of + {Need, RestCode, [Lbl|Code]} -> + if Need > 0 -> + [Lbl] ++ gc_test(Need) ++ Code ++ add_gc_tests([RestCode|EBBCodes]); + true -> + [Lbl|Code] ++ add_gc_tests([RestCode|EBBCodes]) + end; + {0, RestCode, []} -> + add_gc_tests([RestCode|EBBCodes]) + end; +add_gc_tests([]) -> []. + +need([I|Is] , Need, Code) -> + case split(I) of + true -> + case I of + #icode_call{} -> + case hipe_icode:call_continuation(I) of + [] -> %% Was fallthrough. + NewLab = hipe_icode:mk_new_label(), + LabName = hipe_icode:label_name(NewLab), + NewCall = hipe_icode:call_set_continuation(I,LabName), + {Need + need(I), [NewLab|Is], lists:reverse([NewCall|Code])}; + _ -> + {Need + need(I), Is, lists:reverse([I|Code])} + end; + _ -> + {Need + need(I), Is, lists:reverse([I|Code])} + end; + false -> + need(Is, Need + need(I), [I|Code]) + end; +need([], Need, Code) -> + {Need, [], lists:reverse(Code)}. + +need(I) -> + case I of + #icode_call{} -> + primop_need(hipe_icode:call_fun(I), hipe_icode:call_args(I)); + #icode_enter{} -> + primop_need(hipe_icode:enter_fun(I), hipe_icode:enter_args(I)); + _ -> + 0 + end. + +primop_need(Op, As) -> + case Op of + cons -> + 2; + mktuple -> + length(As) + 1; + #mkfun{} -> + NumFree = length(As), + ?ERL_FUN_SIZE + NumFree; + unsafe_tag_float -> + 3; + _ -> + 0 + end. + +gc_test(Need) -> + L = hipe_icode:mk_new_label(), + [hipe_icode:mk_primop([], #gc_test{need=Need}, [], + hipe_icode:label_name(L), + hipe_icode:label_name(L)), + L]. + +split(I) -> + case I of + #icode_call{} -> not known_heap_need(hipe_icode:call_fun(I)); + #icode_enter{} -> not known_heap_need(hipe_icode:enter_fun(I)); + _ -> false + end. + +known_heap_need(Name) -> + case Name of + %% Primops + cons -> true; + fcheckerror -> true; + fclearerror -> true; + fnegate -> true; + fp_add -> true; + fp_div -> true; + fp_mul -> true; + fp_sub -> true; + mktuple -> true; + unsafe_hd -> true; + unsafe_tag_float -> true; + unsafe_tl -> true; + unsafe_untag_float -> true; + #element{} -> true; + #unsafe_element{} -> true; + #unsafe_update_element{} -> true; + + %% MFAs + {erlang, element, 2} -> true; + {erlang, length, 1} -> true; + {erlang, self, 0} -> true; + {erlang, size, 1} -> true; + + _ -> false + end. diff --git a/lib/hipe/icode/hipe_icode_inline_bifs.erl b/lib/hipe/icode/hipe_icode_inline_bifs.erl new file mode 100644 index 0000000000..27296dcad5 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_inline_bifs.erl @@ -0,0 +1,240 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%-------------------------------------------------------------------- +%% File : hipe_icode_inline_bifs.erl +%% Author : Per Gustafsson <[email protected]> +%% Purpose : Inlines BIFs which can be expressed easily in ICode. +%% This allows for optimizations in later ICode passes +%% and makes the code faster. +%% +%% Created : 14 May 2007 by Per Gustafsson <[email protected]> +%%-------------------------------------------------------------------- + +%% Currently inlined BIFs: +%% and, or, xor, not, <, >, >=, =<, ==, /=, =/=, =:= +%% is_atom, is_boolean, is_binary, is_constant, is_float, is_function, +%% is_integer, is_list, is_pid, is_port, is_reference, is_tuple + +-module(hipe_icode_inline_bifs). + +-export([cfg/1]). + +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). + +%%-------------------------------------------------------------------- + +-spec cfg(#cfg{}) -> #cfg{}. + +cfg(Cfg) -> + Linear = hipe_icode_cfg:cfg_to_linear(Cfg), + #icode{code = StraightCode} = Linear, + FinalCode = lists:flatten([inline_bif(I) || I <- StraightCode]), + Cfg1 = hipe_icode_cfg:linear_to_cfg(Linear#icode{code = FinalCode}), + hipe_icode_cfg:remove_unreachable_code(Cfg1). + +inline_bif(I = #icode_call{}) -> + try_conditional(I); +inline_bif(I) -> + I. + +try_conditional(I = #icode_call{dstlist = [Dst], 'fun' = {erlang, Name, 2}, + args = [Arg1, Arg2], + continuation = Cont}) -> + case is_conditional(Name) of + true -> + inline_conditional(Dst, Name, Arg1, Arg2, Cont); + false -> + try_bool(I) + end; +try_conditional(I) -> + try_bool(I). + +is_conditional(Name) -> + case Name of + '=:=' -> true; + '=/=' -> true; + '==' -> true; + '/=' -> true; + '>' -> true; + '<' -> true; + '>=' -> true; + '=<' -> true; + _ -> false + end. + +try_bool(I = #icode_call{dstlist = [Dst], 'fun' = Name, + args = [Arg1, Arg2], + continuation = Cont, fail_label = Fail}) -> + case is_binary_bool(Name) of + {true, Results, ResLbls} -> + inline_binary_bool(Dst, Results, ResLbls, Arg1, Arg2, Cont, Fail, I); + false -> + try_type_tests(I) + end; +try_bool(I = #icode_call{dstlist = [Dst], 'fun' = {erlang, 'not', 1}, + args = [Arg1], + continuation = Cont, + fail_label = Fail}) -> + inline_unary_bool(Dst, {false, true}, Arg1, Cont, Fail, I); +try_bool(I) -> try_type_tests(I). + +is_binary_bool({erlang, Name, 2}) -> + ResTLbl = hipe_icode:mk_new_label(), + ResFLbl = hipe_icode:mk_new_label(), + ResTL = hipe_icode:label_name(ResTLbl), + ResFL = hipe_icode:label_name(ResFLbl), + case Name of + 'and' -> {true, {ResTL, ResFL, ResFL}, {ResTLbl, ResFLbl}}; + 'or' -> {true, {ResTL, ResTL, ResFL}, {ResTLbl, ResFLbl}}; + 'xor' -> {true, {ResFL, ResTL, ResFL}, {ResTLbl, ResFLbl}}; + _ -> false + end; +is_binary_bool(_) -> false. + +try_type_tests(I = #icode_call{dstlist=[Dst], 'fun' = {erlang, Name, 1}, + args = Args, continuation = Cont}) -> + case is_type_test(Name) of + {true, Type} -> + inline_type_test(Dst, Type, Args, Cont); + false -> + I + end; +try_type_tests(I) -> I. + +is_type_test(Name) -> + case Name of + is_integer -> {true, integer}; + is_float -> {true, float}; + is_tuple -> {true, tuple}; + is_binary -> {true, binary}; + is_list -> {true, list}; + is_pid -> {true, pid}; + is_atom -> {true, atom}; + is_boolean -> {true, boolean}; + is_function -> {true, function}; + is_reference -> {true, reference}; + is_constant -> {true, constant}; + is_port -> {true, port}; + _ -> false + end. + +inline_type_test(BifRes, Type, Src, Cont) -> + {NewCont, NewEnd} = get_cont_lbl(Cont), + TLbl = hipe_icode:mk_new_label(), + FLbl = hipe_icode:mk_new_label(), + TL = hipe_icode:label_name(TLbl), + FL = hipe_icode:label_name(FLbl), + [hipe_icode:mk_type(Src, Type, TL, FL), + TLbl, + hipe_icode:mk_move(BifRes, hipe_icode:mk_const(true)), + hipe_icode:mk_goto(NewCont), + FLbl, + hipe_icode:mk_move(BifRes, hipe_icode:mk_const(false)), + hipe_icode:mk_goto(NewCont)| + NewEnd]. + +inline_conditional(BifRes, Op, Src1, Src2, Cont) -> + {NewCont, NewEnd} = get_cont_lbl(Cont), + TLbl = hipe_icode:mk_new_label(), + FLbl = hipe_icode:mk_new_label(), + TL = hipe_icode:label_name(TLbl), + FL = hipe_icode:label_name(FLbl), + [hipe_icode:mk_if(Op, [Src1, Src2], TL, FL), + TLbl, + hipe_icode:mk_move(BifRes, hipe_icode:mk_const(true)), + hipe_icode:mk_goto(NewCont), + FLbl, + hipe_icode:mk_move(BifRes, hipe_icode:mk_const(false)), + hipe_icode:mk_goto(NewCont)| + NewEnd]. + +%% +%% The TTL TFL FFL labelnames points to either ResTLbl or ResFLbl +%% Depending on what boolean expression we are inlining +%% + +inline_binary_bool(Dst, {TTL, TFL, FFL}, {ResTLbl, ResFLbl}, + Arg1, Arg2, Cont, Fail, I) -> + {NewCont, NewEnd} = get_cont_lbl(Cont), + {NewFail, FailCode} = get_fail_lbl(Fail, I), + EndCode = FailCode++NewEnd, + TLbl = hipe_icode:mk_new_label(), + FLbl = hipe_icode:mk_new_label(), + NotTLbl = hipe_icode:mk_new_label(), + NotTTLbl = hipe_icode:mk_new_label(), + NotTFLbl = hipe_icode:mk_new_label(), + TL = hipe_icode:label_name(TLbl), + FL = hipe_icode:label_name(FLbl), + NotTL = hipe_icode:label_name(NotTLbl), + NotTTL = hipe_icode:label_name(NotTTLbl), + NotTFL = hipe_icode:label_name(NotTFLbl), + [hipe_icode:mk_type([Arg1], {atom, true}, TL, NotTL, 0.5), + NotTLbl, + hipe_icode:mk_type([Arg1], {atom, false}, FL, NewFail, 0.99), + TLbl, + hipe_icode:mk_type([Arg2], {atom, true}, TTL, NotTTL, 0.5), + NotTTLbl, + hipe_icode:mk_type([Arg2], {atom, false}, TFL, NewFail, 0.99), + FLbl, + hipe_icode:mk_type([Arg2], {atom, true}, TFL, NotTFL, 0.5), + NotTFLbl, + hipe_icode:mk_type([Arg2], {atom, false}, FFL, NewFail, 0.99), + ResTLbl, + hipe_icode:mk_move(Dst, hipe_icode:mk_const(true)), + hipe_icode:mk_goto(NewCont), + ResFLbl, + hipe_icode:mk_move(Dst, hipe_icode:mk_const(false)), + hipe_icode:mk_goto(NewCont)| + EndCode]. + +inline_unary_bool(Dst, {T,F}, Arg1, Cont, Fail, I) -> + TLbl = hipe_icode:mk_new_label(), + NotTLbl = hipe_icode:mk_new_label(), + FLbl = hipe_icode:mk_new_label(), + TL = hipe_icode:label_name(TLbl), + NotTL = hipe_icode:label_name(NotTLbl), + FL = hipe_icode:label_name(FLbl), + {NewCont, NewEnd} = get_cont_lbl(Cont), + {NewFail, FailCode} = get_fail_lbl(Fail, I), + EndCode = FailCode ++ NewEnd, + Arg1L = [Arg1], + [hipe_icode:mk_type(Arg1L, {atom, true}, TL, NotTL, 0.5), + NotTLbl, + hipe_icode:mk_type(Arg1L, {atom, false}, FL, NewFail, 0.99), + TLbl, + hipe_icode:mk_move(Dst, hipe_icode:mk_const(T)), + hipe_icode:mk_goto(NewCont), + FLbl, + hipe_icode:mk_move(Dst, hipe_icode:mk_const(F)), + hipe_icode:mk_goto(NewCont)| + EndCode]. + +get_cont_lbl([]) -> + NL = hipe_icode:mk_new_label(), + {hipe_icode:label_name(NL), [NL]}; +get_cont_lbl(Cont) -> + {Cont, []}. + +get_fail_lbl([], I) -> + NL = hipe_icode:mk_new_label(), + {hipe_icode:label_name(NL), [NL, I]}; +get_fail_lbl(Fail, _) -> + {Fail, []}. diff --git a/lib/hipe/icode/hipe_icode_instruction_counter.erl b/lib/hipe/icode/hipe_icode_instruction_counter.erl new file mode 100644 index 0000000000..92658d294a --- /dev/null +++ b/lib/hipe/icode/hipe_icode_instruction_counter.erl @@ -0,0 +1,135 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%------------------------------------------------------------------- +%% File : icode_instruction_counter.erl +%% Author : Andreas Hasselberg <[email protected]> +%% Purpose : This module counts the number of different instructions +%% in a function. It is useful when you want to know if +%% your Icode analysis or specialization is good, bad or +%% simply unlucky :) +%% +%% Created : 2 Oct 2006 by Andreas Hasselberg <[email protected]> +%%------------------------------------------------------------------- + +-module(hipe_icode_instruction_counter). + +-export([cfg/3, compare/3]). + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). + +%%------------------------------------------------------------------- +%% A general CFG instruction walktrough +%%------------------------------------------------------------------- + +-spec cfg(#cfg{}, mfa(), comp_options()) -> [_]. + +cfg(Cfg, _IcodeFun, _Options) -> + Labels = hipe_icode_cfg:labels(Cfg), + %% Your Info init function goes here + InitInfo = counter__init_info(), + Info = lists:foldl(fun (Label, InfoAcc) -> + BB = hipe_icode_cfg:bb(Cfg, Label), + Code = hipe_bb:code(BB), + walktrough_bb(Code, InfoAcc) + end, InitInfo, Labels), + %% counter__output_info(IcodeFun, Info), + Info. + +walktrough_bb(BB, Info) -> + lists:foldl(fun (Insn, InfoAcc) -> + %% Your analysis function here + counter__analys_insn(Insn, InfoAcc) + end, Info, BB). + +%%------------------------------------------------------------------- +%% The counter specific functions +%%------------------------------------------------------------------- + +-spec compare(gb_tree(), gb_tree(), gb_tree()) -> gb_tree(). + +compare(Name, Old, New) -> + NewList = gb_trees:to_list(New), + OldList = gb_trees:to_list(Old), + TempTree = compare_one_way(NewList, Old, added, gb_trees:empty()), + DiffTree = compare_one_way(OldList, New, removed, TempTree), + DiffList = gb_trees:to_list(DiffTree), + if DiffList =:= [] -> + ok; + true -> + io:format("~p: ~p ~n", [Name, DiffList]) + end, + DiffTree. + +compare_one_way(List, Tree, Key, Fold_tree) -> + lists:foldl(fun({Insn, ListCount}, DiffAcc) when is_integer(ListCount) -> + DiffCount = + case gb_trees:lookup(Insn, Tree) of + {value, TreeCount} when is_integer(TreeCount) -> + ListCount - TreeCount; + none -> + ListCount + end, + if DiffCount > 0 -> + gb_trees:insert({Key, Insn}, DiffCount, DiffAcc); + true -> + DiffAcc + end + end, + Fold_tree, + List). + +counter__init_info() -> + gb_trees:empty(). + +counter__analys_insn(Insn, Info) -> + Key = counter__insn_get_key(Insn), + counter__increase_key(Key, Info). + +counter__insn_get_key(If = #icode_if{}) -> {'if', hipe_icode:if_op(If)}; +counter__insn_get_key(Call = #icode_call{}) -> {call, hipe_icode:call_fun(Call)}; +counter__insn_get_key(#icode_enter{}) -> enter; +counter__insn_get_key(#icode_return{}) -> return; +counter__insn_get_key(#icode_type{}) -> type; +counter__insn_get_key(#icode_switch_val{}) -> switch_val; +counter__insn_get_key(#icode_switch_tuple_arity{}) -> switch_tuple_arity; +counter__insn_get_key(#icode_goto{}) -> goto; +counter__insn_get_key(#icode_move{}) -> move; +counter__insn_get_key(#icode_phi{}) -> phi; +counter__insn_get_key(#icode_begin_try{}) -> begin_try; +counter__insn_get_key(#icode_end_try{}) -> end_try; +counter__insn_get_key(#icode_begin_handler{}) -> begin_handler; +counter__insn_get_key(#icode_fail{}) -> fail; +counter__insn_get_key(#icode_comment{}) -> comment. + +counter__increase_key(Key, Info) -> + NewCounter = + case gb_trees:lookup(Key, Info) of + {value, Counter} when is_integer(Counter) -> + Counter + 1; + none -> + 1 + end, + gb_trees:enter(Key, NewCounter, Info). + +%%counter__output_info(IcodeFun, Info) -> +%% InfoList = gb_trees:to_list(Info), +%% io:format("~p instructions : ~p ~n", [IcodeFun, InfoList]). diff --git a/lib/hipe/icode/hipe_icode_liveness.erl b/lib/hipe/icode/hipe_icode_liveness.erl new file mode 100644 index 0000000000..5816e59032 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_liveness.erl @@ -0,0 +1,101 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% ICODE LIVENESS ANALYSIS +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(hipe_icode_liveness). + +-define(PRETTY_PRINT, true). + +-include("hipe_icode.hrl"). +-include("../flow/liveness.inc"). + +%%-------------------------------------------------------------------- +%% Interface to CFG and icode. +%%-------------------------------------------------------------------- + +cfg_bb(CFG, L) -> + hipe_icode_cfg:bb(CFG, L). + +cfg_postorder(CFG) -> + hipe_icode_cfg:postorder(CFG). + +cfg_succ(CFG, L) -> + hipe_icode_cfg:succ(CFG, L). + +uses(Instr) -> + hipe_icode:uses(Instr). + +defines(Instr) -> + hipe_icode:defines(Instr). + +%% +%% This is the list of registers that are live at exit from a function +%% +cfg_labels(CFG) -> + hipe_icode_cfg:labels(CFG). + +liveout_no_succ() -> + ordsets:new(). + +pp_liveness_info(LiveList) -> + print_live_list(LiveList). + +print_live_list([]) -> + io:format(" none~n", []); +print_live_list([Last]) -> + io:format(" ", []), + print_var(Last), + io:format("~n", []); +print_live_list([Var|Rest]) -> + io:format(" ", []), + print_var(Var), + io:format(",", []), + print_live_list(Rest). + +pp_block(Label, CFG) -> + BB = hipe_icode_cfg:bb(CFG, Label), + Code = hipe_bb:code(BB), + hipe_icode_pp:pp_block(Code). + +print_var(#icode_variable{name=V, kind=Kind, annotation=T}) -> + case Kind of + var -> io:format("v~p", [V]); + reg -> io:format("r~p", [V]); + fvar -> io:format("fv~p", [V]) + end, + case T of + [] -> ok; + {_,X,F} -> io:format(" (~s)", F(X)) + end. + +%% +%% The following are used only if annotation of the code is requested. +%% +-ifdef(DEBUG_LIVENESS). +cfg_bb_add(CFG, L, NewBB) -> + hipe_icode_cfg:bb_add(CFG, L, NewBB). + +mk_comment(Text) -> + hipe_icode:mk_comment(Text). +-endif. diff --git a/lib/hipe/icode/hipe_icode_mulret.erl b/lib/hipe/icode/hipe_icode_mulret.erl new file mode 100644 index 0000000000..a6529c8519 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_mulret.erl @@ -0,0 +1,1323 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%---------------------------------------------------------------------- +%% File : hipe_icode_mulret.erl +%% Author : Christoffer Vikstr�m <[email protected]> +%% Purpose : +%% Created : 23 Jun 2004 by Christoffer Vikstr�m <[email protected]> +%%---------------------------------------------------------------------- + +-module(hipe_icode_mulret). +-export([mult_ret/4]). + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("hipe_icode_primops.hrl"). + +%%>----------------------------------------------------------------------< +%% Procedure : mult_ret/4 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< + +-spec mult_ret([_], atom(), comp_options(), _) -> [_]. + +mult_ret(List, Mod, Opts, Exports) -> + case length(List) > 1 of + true -> + Table = analyse(List, Mod, Exports), + %% printTable(Mod, Exports, Table), + optimize(List, Mod, Opts, Table); + false -> + List + end. + +%%>-----------------------< Analysis Steps >-----------------------------< + +%%>----------------------------------------------------------------------< +%% Procedure : analyse/3 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +analyse(List, _Mod, Exports) -> + MaxRets = hipe_rtl_arch:nr_of_return_regs(), + Table = mkTable(List), + %% printTable(Mod, Exports, Table), + Table2 = filterTable(Table, MaxRets, Exports), + %% printTable(Mod, Exports, Table2), + Table2. + +%%>----------------------------------------------------------------------< +%% Procedure : mkTable/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +mkTable(List) -> + mkTable(List, {[], []}). + +mkTable([{MFA, Icode} | List], Table) -> + %% New Icode + {_LMin,LMax} = hipe_icode:icode_label_range(Icode), + hipe_gensym:set_label(icode, LMax+1), + {_VMin,VMax} = hipe_icode:icode_var_range(Icode), + hipe_gensym:set_var(icode, VMax+1), + case isFunDef(MFA) of + true -> + mkTable(List, Table); + false -> + CallList = mkCallList(MFA, Icode), + Optimizable = isOptimizable(Icode), + NewTable = addToTable(MFA, Optimizable, CallList, Table), + mkTable(List, NewTable) + end; +mkTable([_|List], Table) -> mkTable(List, Table); +mkTable([], Table) -> Table. + +%%>----------------------------------------------------------------------< +%% Procedure : isFunDef/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +isFunDef({_, F, _}) -> + hd(atom_to_list(F)) =:= 45. %% 45 is the character '-' + +%%>----------------------------------------------------------------------< +%% Procedure : mkCallList/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +mkCallList(MFA, Icode) -> + Code = hipe_icode:icode_code(Icode), + mkCallList(Code, MFA, []). + +mkCallList([#icode_call{'fun'=F, dstlist=Vars, type=local}|Code], MFA, Res) -> + {Size, DstList} = lookForDef(Code, Vars), + mkCallList(Code, MFA, [{callPair,MFA,{F,{matchSize,Size,DstList}}}|Res]); +mkCallList([_|Code], MFA, Res) -> mkCallList(Code, MFA, Res); +mkCallList([], _, Res) -> Res. + +%%>----------------------------------------------------------------------< +%% Procedure : lookForDef/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +lookForDef([#icode_type{test={tuple,Size}, true_label=L}|Code], Vars) -> + Code2 = skipToLabel(Code, L), + DstLst = lookForUnElems(Code2, Vars), + case DstLst of + [] -> {1, Vars}; + _ -> + DstLst2 = fixDstLst(DstLst, Size), + {Size, DstLst2} + end; +lookForDef([#icode_move{src=Var, dst=NewVar}|Code], [Var]) -> + lookForDef(Code, [NewVar]); +lookForDef([#icode_label{}|_], Vars) -> + {1, Vars}; +lookForDef([I|Code], [Var] = Vars) -> + Defs = hipe_icode:defines(I), + case lists:member(Var, Defs) of + true -> + {1, Vars}; + false -> + lookForDef(Code, Vars) + end; +lookForDef([], Vars) -> {1, Vars}. + +%%>----------------------------------------------------------------------< +%% Procedure : skipToLabel/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +skipToLabel(Code, L) -> + case skipToLabel2(Code, L) of + noLabel -> + Code; + NewCode -> + NewCode + end. + +skipToLabel2([#icode_label{name = L}|Code],L) -> Code; +skipToLabel2([_|Code], L) -> skipToLabel2(Code, L); +skipToLabel2([], _) -> noLabel. + +%%>----------------------------------------------------------------------< +%% Procedure : lookForUnElems/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +lookForUnElems(Code, Var) -> + lookForUnElems(Code, Var, []). + +lookForUnElems([#icode_call{'fun'=#unsafe_element{index=Nr}, args=Var, + dstlist=[Ret]}|Code], Var, Res) -> + lookForUnElems(Code, Var, [{Nr, Ret}|Res]); +lookForUnElems([#icode_move{dst=Var}|_], [Var], Res) -> + lists:flatten(Res); +lookForUnElems([#icode_call{dstlist=VarList}|_], VarList, Res) -> + lists:flatten(Res); +lookForUnElems([_|Code], Var, Res) -> + lookForUnElems(Code, Var, Res); +lookForUnElems([], _, Res) -> lists:flatten(Res). + +%%>----------------------------------------------------------------------< +%% Procedure : fixDstLst/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +fixDstLst(DstLst, Size) when is_integer(Size) -> + fixDstLst(DstLst, Size, 1, []). + +fixDstLst(DstLst, Size, Cnt, Res) when Cnt =< Size -> + case isInLst(Cnt, DstLst) of + {true, Var} -> + fixDstLst(DstLst, Size, Cnt+1, [Var|Res]); + false -> + Var = hipe_icode:mk_var(hipe_gensym:new_var(icode)), + fixDstLst(DstLst, Size, Cnt+1, [Var|Res]) + end; +fixDstLst(_, Size, Cnt, Res) when Cnt > Size -> lists:reverse(Res). + +%%>----------------------------------------------------------------------< +%% Procedure : isInLst/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +isInLst(Nr, [{Nr,Var}|_]) -> {true, Var}; +isInLst(Cnt, [_|DstLst]) -> isInLst(Cnt, DstLst); +isInLst(_, []) -> false. + +%%>----------------------------------------------------------------------< +%% Procedure : isOptimizable/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +isOptimizable(Icode) -> + %% Icode2 = hipe_icode:fixup_fallthroughs(Icode), + Icode2 = hipe_icode:strip_comments(Icode), + Cfg = hipe_icode_cfg:linear_to_cfg(Icode2), + %% hipe_icode_cfg:pp(Cfg), + case findReturnBlocks(Cfg) of + noReturn -> + {false, -1}; + BlockList -> + processReturnBlocks(BlockList, Cfg) + end. + +%%>----------------------------------------------------------------------< +%% Procedure : findReturnBlocks/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +findReturnBlocks(IcodeCfg) -> + Labels = hipe_icode_cfg:labels(IcodeCfg), + case searchBlocks(Labels, IcodeCfg) of + [] -> + noReturn; + BlockList-> + BlockList + end. + +%%>----------------------------------------------------------------------< +%% Procedure : searchBlocks/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +searchBlocks(Labels, IcodeCfg) -> + searchBlocks(Labels, IcodeCfg, []). + +searchBlocks([Label|Labels], IcodeCfg, Res) -> + Block = hipe_icode_cfg:bb(IcodeCfg, Label), + Code = hipe_bb:code(Block), + case searchBlockCode(Code) of + {hasReturn, RetVar} -> + searchBlocks(Labels, IcodeCfg, [{Label, RetVar}|Res]); + noReturn -> + searchBlocks(Labels, IcodeCfg, Res) + end; +searchBlocks([], _, Res) -> Res. + +%%>----------------------------------------------------------------------< +%% Procedure : searchBlockCode/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +searchBlockCode([#icode_return{vars=Vars}|_]) -> + {hasReturn, Vars}; +searchBlockCode([_|Icode]) -> + searchBlockCode(Icode); +searchBlockCode([]) -> noReturn. + +%%>----------------------------------------------------------------------< +%% Procedure : processReturnBlock/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +processReturnBlocks(Blocks, Cfg) -> + processReturnBlocks(Blocks, Cfg, {true, -1}, []). + +processReturnBlocks([{Label, Var}|BlockList], Cfg, {Opts, Size}, TypeLst) -> + {Opt, Type, Size2} = traverseCode(Label, Var, Cfg), + case (Size =:= -1) orelse (Size =:= Size2) of + true -> + processReturnBlocks(BlockList, Cfg, + {Opt andalso Opts, Size2}, [Type|TypeLst]); + false -> + {false, -1} + end; +processReturnBlocks([], _, Res, TypeLst) -> + case lists:member(icode_var, TypeLst) of + true -> + {_, Size} = Res, + case Size > 1 of + true -> + Res; + false -> + {false, -1} + end; + false -> + {false, -1} + end. + +%%>----------------------------------------------------------------------< +%% Procedure : traverseCode/3 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +traverseCode(Label, Var, Cfg) -> + traverseCode(Label, Var, Cfg, []). + +traverseCode(Label, Var, Cfg, LabLst) -> + Preds = hipe_icode_cfg:pred(Cfg, Label), + Block = hipe_icode_cfg:bb(Cfg, Label), + Code = hipe_bb:code(Block), + case findDefine(lists:reverse(Code), Var) of + {found, Type, NumRets} -> + {true, Type, NumRets}; + {notFound, SrcVar} -> + case Preds of + [] -> + {false, none, -1}; + [Pred] -> + case lists:member(Label, LabLst) of + false -> + traverseCode(Pred, SrcVar, Cfg, [Label|LabLst]); + true -> + {false, none, -1} + end; + _ -> + {false, none, -1} + end + end. + +%%>----------------------------------------------------------------------< +%% Procedure : findDefine/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +findDefine([#icode_call{dstlist=Vars,'fun'=mktuple,args=Vs}|_], Vars) -> + case length(Vs) of + 1 -> + [{Type, _}] = Vs, + {found, Type, 1}; + Len -> + case lists:any(fun hipe_icode:is_var/1, Vs) of + true -> + {found, icode_var, Len}; + false -> + {found, icode_const, Len} + end + end; +findDefine([#icode_move{dst=Var, src=Src}|Code], [Var]) -> + case hipe_icode:is_var(Src) of + true -> + findDefine(Code, [Src]); + false -> + case Src of + #icode_const{value={flat, Value}} -> + case is_tuple(Value) of + true -> + {found, icode_const, tuple_size(Value)}; + false -> + {found, icode_const, 1} + end; + _ -> + findDefine(Code, [Var]) + end + end; +findDefine([_|Code], Var) -> + findDefine(Code, Var); +findDefine([], Var) -> + {notFound, Var}. + +%%>----------------------------------------------------------------------< +%% Procedure : addToTable/4 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +addToTable(MFA, Optimizable, CallList, {FunLst, CallLst}) -> + NewFunLst = [{MFA, Optimizable}|FunLst], + {NewFunLst, CallList ++ CallLst}. + +%%>----------------------------------------------------------------------< +%% Procedure : filterTable/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +filterTable({FunLst, CallLst}, MaxRets, Exports) -> + filterTable(FunLst, CallLst, MaxRets, Exports, {[],[]}). + +filterTable([Fun|FunLst], CallLst, MaxRets, Exports, {Funs, Calls} = FCs) -> + {MFA, {ReturnOpt, Rets}} = Fun, + {CallOpt, CallsToKeep} = checkCalls(CallLst, MFA, Rets), + CallsToKeep2 = removeDuplicateCalls(CallsToKeep), + NotExported = checkExported(MFA, Exports), + case CallOpt andalso ReturnOpt andalso (Rets =< MaxRets) andalso + NotExported andalso (not containRecursiveCalls(CallsToKeep2, MFA)) of + true -> + filterTable(FunLst, CallLst, MaxRets, Exports, + {[Fun|Funs], CallsToKeep2 ++ Calls}); + false -> + filterTable(FunLst, CallLst, MaxRets, Exports, FCs) + end; +filterTable([], _, _, _, Res) -> Res. + +removeDuplicateCalls(Calls) -> + removeDuplicateCalls(Calls, []). + +removeDuplicateCalls([Call|CallsToKeep], Res) -> + case lists:member(Call, CallsToKeep) of + true -> + removeDuplicateCalls(CallsToKeep, Res); + false -> + removeDuplicateCalls(CallsToKeep, [Call|Res]) + end; +removeDuplicateCalls([], Res) -> lists:reverse(Res). + +containRecursiveCalls([Call|Calls], Fun) -> + {callPair, Caller, {Callee, _}} = Call, + case (Callee =:= Fun) andalso (Caller =:= Fun) of + true -> + true; + false-> + containRecursiveCalls(Calls, Fun) + end; +containRecursiveCalls([], _) -> false. + +%%>----------------------------------------------------------------------< +%% Procedure : checkCalls/3 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +checkCalls(CallLst, MFA, Rets) -> + checkCalls(CallLst, MFA, Rets, [], []). + +checkCalls([C = {callPair, _, {MFA, {matchSize, Rets, _}}}|CallLst], + MFA, Rets, Res, Opt) -> + checkCalls(CallLst, MFA, Rets, [C|Res], [true|Opt]); +checkCalls([{callPair, _, {MFA, {matchSize, _, _}}}|CallLst], + MFA, Rets, Res, Opt) -> + checkCalls(CallLst, MFA, Rets, Res, [false|Opt]); +checkCalls([_|CallLst], MFA, Rets, Res, Opt) -> + checkCalls(CallLst, MFA, Rets, Res, Opt); +checkCalls([], _, _, Res, Opt) -> {combineOpts(Opt), Res}. + +%%>----------------------------------------------------------------------< +%% Procedure : combineOpts/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +combineOpts([]) -> false; +combineOpts([Opt]) -> Opt; +combineOpts([Opt|Opts]) -> Opt andalso combineOpts(Opts). + +%%>----------------------------------------------------------------------< +%% Procedure : checkCalls/2 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +checkExported({_,F,A}, [{F,A}|_]) -> false; +checkExported(MFA, [_|Exports]) -> checkExported(MFA, Exports); +checkExported(_, []) -> true. + +%%>----------------------< Optimization Steps >--------------------------< + +%%>----------------------------------------------------------------------< +%% Procedure : optimize/4 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +optimize(List, _Mod, Opts, Table) -> + {FunLst, CallLst} = Table, + List2 = optimizeFuns(FunLst, Opts, List), + optimizeCalls(CallLst, Opts, List2). + +%%>----------------------------------------------------------------------< +%% Procedure : optimizeFuns/3 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +optimizeFuns([{Fun, _}|FunList], Opts, List) -> + NewList = findFun(List, Fun), + optimizeFuns(FunList, Opts, NewList); +optimizeFuns([],_,List) -> List. + +findFun(List, Fun) -> findFun(List, Fun, []). +findFun([{Fun, Icode}|List], Fun, Res) -> + NewIcode = optimizeFun(Icode), + findFun(List, Fun, [{Fun, NewIcode}|Res]); +findFun([I|List], Fun, Res) -> findFun(List, Fun, [I|Res]); +findFun([], _, Res) -> lists:reverse(Res). + + +optimizeFun(Icode) -> + {_LMin,LMax} = hipe_icode:icode_label_range(Icode), + hipe_gensym:set_label(icode, LMax+1), + {_VMin,VMax} = hipe_icode:icode_var_range(Icode), + hipe_gensym:set_var(icode, VMax+1), + %% Icode2 = hipe_icode:fixup_fallthroughs(Icode), + Icode2 = hipe_icode:strip_comments(Icode), + Cfg = hipe_icode_cfg:linear_to_cfg(Icode2), + case findReturnBlocks(Cfg) of + noReturn -> + false; + BlockList -> + NewCfg = optimizeReturnBlocks(BlockList, Cfg), + hipe_icode_cfg:cfg_to_linear(NewCfg) + end. + +optimizeReturnBlocks([Block|BlockList], Cfg) -> + {NewCfg, Vars} = optimizeReturnBlock(Block, Cfg), + NewCfg2 = case Vars of + [_] -> + Cfg; + _ -> + {Label, _} = Block, + updateReturnBlock(Label, Vars, NewCfg) + end, + optimizeReturnBlocks(BlockList, NewCfg2); +optimizeReturnBlocks([], Cfg) -> Cfg. + +optimizeReturnBlock(Block, Cfg) -> + optimizeReturnBlock(Block, Cfg, []). + +optimizeReturnBlock({Label,Var}, Cfg, UpdateMap) -> + Preds = hipe_icode_cfg:pred(Cfg, Label), + Block = hipe_icode_cfg:bb(Cfg, Label), + Code = hipe_bb:code(Block), + case optimizeDefine(Code, Var) of + {found, NewBlockCode, Vars} -> + NewBlock = hipe_bb:code_update(Block, NewBlockCode), + NewCfg = resolveUpdateMap(UpdateMap, Cfg), + {hipe_icode_cfg:bb_add(NewCfg, Label, NewBlock), Vars}; + {none, NewBlockCode, NewVar} -> + case Preds of + [Pred] -> + NewBlock = hipe_bb:code_update(Block, NewBlockCode), + optimizeReturnBlock({Pred,NewVar}, Cfg, + [{Label, NewBlock}|UpdateMap]); + [_|_] -> + {Cfg, Var} + end; + {none, noOpt} -> + {Cfg, Var} + end. + +optimizeDefine(Code, Dst) -> + optimizeDefine(lists:reverse(Code), Dst, [], []). + +optimizeDefine([I|Code], Dsts, DstLst, Res) -> + [Ds] = Dsts, + case isCallPrimop(I, mktuple) andalso DstLst =:= [] of + true -> + case (hipe_icode:call_dstlist(I) =:= Dsts) of + true -> + case (hipe_icode:call_args(I) > 1) of + true -> + optimizeDefine(Code, Dsts, hipe_icode:call_args(I), Res); + false -> + {none, noOpt} + end; + false -> + optimizeDefine(Code, Dsts, DstLst, [I|Res]) + end; + false -> + case hipe_icode:is_move(I) andalso DstLst =:= [] of + true -> + case hipe_icode:move_dst(I) =:= Ds of + true -> + Src = hipe_icode:move_src(I), + case hipe_icode:is_var(Src) of + true -> + NewDst = hipe_icode:move_src(I), + optimizeDefine(Code, [NewDst], DstLst, Res); + false -> + case Src of + #icode_const{value={flat, T}} when is_tuple(T) -> + NewLst = tuple_to_list(T), + optimizeDefine(Code, Dsts, NewLst, Res); + _ -> + {none, noOpt} + end + end; + false -> + optimizeDefine(Code, Dsts, DstLst, [I|Res]) + end; + false -> + case lists:member(Ds, hipe_icode:defines(I)) andalso DstLst =:= [] of + true -> + {none, noOpt}; + false -> + optimizeDefine(Code, Dsts, DstLst, [I|Res]) + end + end + end; +optimizeDefine([], Dsts, DstLst, Res) -> + case DstLst of + [] -> + {none, Res, Dsts}; + _ -> + {found, Res, DstLst} + end. + +resolveUpdateMap([{Label, Block}|UpdateMap], Cfg) -> + resolveUpdateMap(UpdateMap, hipe_icode_cfg:bb_add(Cfg, Label, Block)); +resolveUpdateMap([], Cfg) -> Cfg. + +%%>----------------------------------------------------------------------< +%% Procedure : updateReturnBlock/3 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +updateReturnBlock(Label, Vars, IcodeCfg) -> + Block = hipe_icode_cfg:bb(IcodeCfg, Label), + Code = hipe_bb:code(Block), + NewCode = updateReturnCode(Code, Vars), + NewBlock = hipe_bb:code_update(Block, NewCode), + hipe_icode_cfg:bb_add(IcodeCfg, Label, NewBlock). + +updateReturnCode(Code, DstLst) -> + updateReturnCode(Code, DstLst, []). + +updateReturnCode([I| Code], DstLst, Res) -> + case hipe_icode:is_return(I) of + true -> + updateReturnCode(Code, DstLst, [hipe_icode:mk_return(DstLst)|Res]); + false -> + updateReturnCode(Code, DstLst, [I|Res]) + end; +updateReturnCode([], _, Res) -> lists:reverse(Res). + +%%>----------------------------------------------------------------------< +%% Procedure : optimizeCalls/3 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +optimizeCalls([Call|CallLst], _Opts, List) -> + {callPair, Caller, {Callee, {matchSize, _, DstLst}}} = Call, + NewList = optimizeCall(List, Caller, Callee, DstLst), + optimizeCalls(CallLst, _Opts, NewList); +optimizeCalls([], _Opts, List) -> List. + +%%>----------------------------------------------------------------------< +%% Procedure : optimizeCall/4 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +optimizeCall(List, Caller, Callee, DstLst) -> + optimizeCall(List, Caller, Callee, DstLst, []). + +optimizeCall([{MFA, Icode}|List], MFA, Callee, DstLst, Res) -> + {_LMin,LMax} = hipe_icode:icode_label_range(Icode), + hipe_gensym:set_label(icode, LMax+1), + {_VMin,VMax} = hipe_icode:icode_var_range(Icode), + hipe_gensym:set_var(icode, VMax+1), + %% Icode2 = hipe_icode:fixup_fallthroughs(Icode), + Icode2 = hipe_icode:strip_comments(Icode), + Cfg = hipe_icode_cfg:linear_to_cfg(Icode2), + NewIcode = findAndUpdateCalls(Cfg, Callee, DstLst), + optimizeCall(List, MFA, Callee, DstLst, [{MFA, NewIcode}|Res]); +optimizeCall([I|List], Caller, Callee, DstLst, Res) -> + optimizeCall(List, Caller, Callee, DstLst, [I|Res]); +optimizeCall([], _, _, _, Res) -> lists:reverse(Res). + +%%>----------------------------------------------------------------------< +%% Procedure : findAndUpdateCall/3 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +findAndUpdateCalls(Cfg, Callee, DstLst) -> + Labels = hipe_icode_cfg:labels(Cfg), + Cfg2 = findAndUpdateCalls(Cfg, Labels, Callee, DstLst, []), + hipe_icode_cfg:cfg_to_linear(Cfg2). +findAndUpdateCalls(Cfg, [L|Labels], Callee, DstLst, Visited) -> + %% Block = hipe_icode_cfg:bb(Cfg, L), + %% Code = hipe_bb:code(Block), + case containCorrectCall(Cfg, L, Callee, DstLst) of + true -> + Block = hipe_icode_cfg:bb(Cfg,L), + Code = hipe_bb:code(Block), + {NewCode, OldVar} = updateCode(Code, Callee, DstLst), + NewBlock = hipe_bb:code_update(Block, NewCode), + Cfg2 = hipe_icode_cfg:bb_add(Cfg, L, NewBlock), + Cfg3 = cleanUpAffectedCode(Cfg2, OldVar, Callee, L, Visited), + findAndUpdateCalls(Cfg3, Labels, Callee, DstLst, [L|Visited]); + false -> + findAndUpdateCalls(Cfg, Labels, Callee, DstLst, [L|Visited]) + end; +findAndUpdateCalls(Cfg,[], _, _, _) -> Cfg. + +containCorrectCall(Cfg, Label, Callee, DstLst) -> + Block = hipe_icode_cfg:bb(Cfg,Label), + Code = hipe_bb:code(Block), + case containCallee(Code, Callee) of + {true, OldVar} -> + Succs = hipe_icode_cfg:succ(Cfg, Label), + checkForUnElems(Succs, OldVar, DstLst, Cfg); + false -> + false + end. + +checkForUnElems([], _, _, _) -> false; +checkForUnElems([Succ|Succs], OldVar, DstLst, Cfg) -> + Block = hipe_icode_cfg:bb(Cfg,Succ), + Code = hipe_bb:code(Block), + case checkForUnElems2(Code, OldVar, DstLst, []) of + true -> + true; + false -> + checkForUnElems(Succs, OldVar, DstLst, Cfg) + end. + +checkForUnElems2([I|Code], OldVar, DstLst, DstRes) -> + case isCallPrimop(I, unsafe_element) of + true -> + case (hipe_icode:call_args(I) =:= OldVar) of + true -> + [Dst] = hipe_icode:call_dstlist(I), + case lists:member(Dst, DstLst) of + true -> + checkForUnElems2(Code, OldVar, DstLst, [Dst|DstRes]); + false -> + checkForUnElems2(Code, OldVar, DstLst, DstRes) + end; + false -> + checkForUnElems2(Code, OldVar, DstLst, DstRes) + end; + false -> + checkForUnElems2(Code, OldVar, DstLst, DstRes) + end; +checkForUnElems2([], _, DstLst, DstRes) -> DstLst =:= lists:reverse(DstRes). + + +containCallee([I|Code], Callee) -> + case isCallLocal(I, Callee) of + true -> + {true, hipe_icode:call_dstlist(I)}; + false -> + containCallee(Code, Callee) + end; +containCallee([], _) -> false. + + +updateCode(Code, Callee, DstLst) -> + updateCode(Code, Callee, DstLst, [], []). + +updateCode([I|Code], Callee, DstLst, Res, OldVars) -> + case isCallLocal(I, Callee) of + true -> + Vars = hipe_icode:call_dstlist(I), + I2 = hipe_icode:call_dstlist_update(I, DstLst), + updateCode(Code, Callee, DstLst, [I2|Res], Vars); + false -> + updateCode(Code, Callee, DstLst, [I|Res], OldVars) + end; +updateCode([], _, _, Res, OldVars) -> {lists:reverse(Res), OldVars}. + + +cleanUpAffectedCode(Cfg, OldVar, Callee, Label, Visited) -> + Block = hipe_icode_cfg:bb(Cfg,Label), + Code = hipe_bb:code(Block), + {CodeBefore, CodeAfter, DstLst} = divideAtCall(Code, Callee), + {NewCodeAfter, ContLab, FailLab} = findType(CodeAfter, OldVar), + ContBlock = hipe_icode_cfg:bb(Cfg, ContLab), + Succs = hipe_icode_cfg:succ(Cfg, ContLab), + ContCode = hipe_bb:code(ContBlock), + {NewContCode, NewFailLab} = removeUnElems(ContCode, OldVar, DstLst), + NewBlock = hipe_bb:code_update(Block, + CodeBefore ++ NewCodeAfter ++ NewContCode), + Cfg2 = hipe_icode_cfg:bb_add(Cfg, Label, NewBlock), + Cfg3 = resolveSuccBlocks(Succs, OldVar, DstLst, [Label|Visited], + NewFailLab, Cfg2), + insertMiddleFailBlock(Cfg3, NewFailLab, FailLab, OldVar, DstLst). + +divideAtCall(Code, Caller) -> + divideAtCall(Code, Caller, []). + +divideAtCall([I|Code], Caller, Tail) -> + case isCallLocal(I, Caller) of + true -> + {lists:reverse([I|Tail]), Code, hipe_icode:call_dstlist(I)}; + false -> + divideAtCall(Code, Caller, [I|Tail]) + end; +divideAtCall([], _, Tail) -> {Tail, []}. + +findType(CodeAfter, OldVar) -> + findType(CodeAfter, OldVar, [], {none, none}). + +findType([I|Code], OldVar, Rest, Succs) -> + case hipe_icode:is_type(I) of + true -> + case hipe_icode:type_args(I) =:= OldVar of + true -> + TrueLab = hipe_icode:type_true_label(I), + FalseLab = hipe_icode:type_false_label(I), + findType(Code, OldVar, Rest, {TrueLab, FalseLab}); + false -> + findType(Code, OldVar, [I|Rest], Succs) + end; + false -> + case hipe_icode:is_move(I) of + true -> + case [hipe_icode:move_src(I)] =:= OldVar of + true -> + findType(Code, hipe_icode:move_dst(I), [I|Rest], Succs); + false -> + findType(Code, OldVar, [I|Rest], Succs) + end; + false -> + findType(Code, OldVar, [I|Rest], Succs) + end + end; +findType([],_,Rest, {TrueLab, FalseLab}) -> + {lists:reverse(Rest), TrueLab, FalseLab}. + +%% Nesting hell... check for redundancies. +%% --------------------------------------- +removeUnElems(Code, OldVars, DstLst) -> + removeUnElems(Code, OldVars, DstLst, [], false, none). + +removeUnElems([I|Code], [OldVar] = OldVars, DstLst, Res, Def, Lab) -> + case isCallPrimop(I, unsafe_element) of + true -> + case (hipe_icode:call_args(I) =:= OldVars) of + true -> + removeUnElems(Code, OldVars, DstLst, Res, Def, Lab); + false -> + case lists:member(OldVar, hipe_icode:call_args(I)) of + true -> + %% XXX: the following test seems redundant, + %% hence commented out -- KOSTIS + %% case Def of + %% true -> + removeUnElems(Code, OldVars, DstLst, [I|Res], Def, Lab); + %% false -> + %% removeUnElems(Code, OldVars, DstLst, + %% [I|Res], Def, Lab) + %% end; + false -> + io:format("Borde aldrig kunna hamna h�r!", []), + removeUnElems(Code, OldVars, DstLst, [I|Res], Def, Lab) + end + end; + false -> + case hipe_icode:is_move(I) of + true -> + case hipe_icode:move_src(I) =:= OldVar of + true -> + NewVar = hipe_icode:move_dst(I), + removeUnElems(Code, [NewVar], DstLst, [I|Res], Def, Lab); + false -> + removeUnElems(Code, OldVars, DstLst, [I|Res], Def, Lab) + end; + false -> + case hipe_icode:is_type(I) andalso not Def of + true -> + NewFalseLab = case Lab =:= none of + true -> + hipe_gensym:get_next_label(icode); + false -> + Lab + end, + _I2 = updateTypeFalseLabel(I, NewFalseLab), + removeUnElems(Code, OldVars, DstLst, [I|Res], Def, NewFalseLab); + false -> + case lists:member(OldVar, hipe_icode:uses(I)) andalso Def of + true -> + removeUnElems(Code, OldVars, DstLst, [I|Res], Def, Lab); + false -> + case lists:member(OldVar, hipe_icode:defines(I)) of + true -> + removeUnElems(Code, OldVars, DstLst, [I|Res], true, Lab); + false -> + removeUnElems(Code, OldVars, DstLst, [I|Res], Def, Lab) + end + end + end + end + end; +removeUnElems([], _, _, Res,_, Lab) -> {lists:reverse(Res), Lab}. + + +updateTypeFalseLabel(Instr, NewFalseLabel) -> + TrueLabel = hipe_icode:type_true_label(Instr), + Args = hipe_icode:type_args(Instr), + Type = hipe_icode:type_test(Instr), + hipe_icode:mk_type(Args, Type, TrueLabel, NewFalseLabel). + + +resolveSuccBlocks(Succs, OldVar, DstLst, Visited, FailLab, Cfg) -> + NewSuccs = [X || X <- Succs, not lists:member(X, Visited)], + resolveSuccBlocks2(NewSuccs, OldVar, DstLst, Visited, FailLab, Cfg). + +resolveSuccBlocks2([Succ|Succs], OldVar, DstLst, Vis, FailLab, Cfg) -> + Block = hipe_icode_cfg:bb(Cfg,Succ), + Code = hipe_bb:code(Block), + {NewCode, ReDefined} = checkUsesDefs(Code, OldVar, DstLst, FailLab), + NewBlock = hipe_bb:code_update(Block, NewCode), + Cfg2 = hipe_icode_cfg:bb_add(Cfg, Succ, NewBlock), + case ReDefined of + true -> + resolveSuccBlocks2(Succs, OldVar, DstLst, [Succ|Vis], FailLab, Cfg2); + false -> + NewSuccs = hipe_icode_cfg:succ(Cfg, Succ), + NewSuccs2 = [X || X <- NewSuccs, not lists:member(X, Vis++Succs)], + resolveSuccBlocks2(NewSuccs2++Succs, OldVar, DstLst, + [Succ|Vis], FailLab, Cfg2) + end; +resolveSuccBlocks2([], _, _, _, _, Cfg) -> Cfg. + + +checkUsesDefs(Code, OldVar, DstLst, FailLab) -> + checkUsesDefs(Code, OldVar, DstLst, FailLab, [], false). + +checkUsesDefs([I|Code], OldVar, DstLst, FailLab, Res, Defined) -> + [OVar] = OldVar, + case hipe_icode:is_move(I) of + true -> + case hipe_icode:move_src(I) =:= OVar of + true -> + NewVar = hipe_icode:move_dst(I), + checkUsesDefs(Code, NewVar, DstLst, FailLab, [I|Res], true); + false -> + case lists:member(OVar, hipe_icode:defines(I)) of + true -> + checkUsesDefs(Code, OldVar, DstLst, FailLab, [I|Res], true); + false -> + checkUsesDefs(Code, OldVar, DstLst, FailLab, [I|Res], Defined) + end + end; + false -> + case hipe_icode:is_type(I) andalso not Defined of + true -> + case FailLab =/= none of + true -> + _I2 = updateTypeFalseLabel(I, FailLab), + checkUsesDefs(Code, OldVar, DstLst, FailLab, [I|Res], Defined); + false -> + checkUsesDefs(Code, OldVar, DstLst, FailLab, [I|Res], Defined) + end; + false -> + case (lists:member(OVar, hipe_icode:uses(I))) andalso + (not Defined) andalso (FailLab =/= none) of + true -> + Tpl = hipe_icode:mk_primop(OldVar, mktuple, DstLst), + checkUsesDefs(Code, OldVar, DstLst, FailLab, [I,Tpl|Res], true); + false -> + case lists:member(OVar, hipe_icode:defines(I)) of + true -> + checkUsesDefs(Code, OldVar, DstLst, FailLab, [I|Res], true); + false -> + checkUsesDefs(Code, OldVar, DstLst, FailLab, [I|Res],Defined) + end + end + end + end; +checkUsesDefs([], _, _, _, Res, Defined) -> {lists:reverse(Res), Defined}. + + +insertMiddleFailBlock(Cfg, NewFailLabel, OldFailLabel, OldVar, DstLst) -> + case NewFailLabel =:= none of + true -> + Cfg; + false -> + NewCode = [hipe_icode:mk_primop(OldVar, mktuple, DstLst), + hipe_icode:mk_goto(OldFailLabel)], + NewBlock = hipe_bb:mk_bb(NewCode), + hipe_icode_cfg:bb_add(Cfg, NewFailLabel, NewBlock) + end. + + +isCallLocal(Instr, Fun) -> + hipe_icode:is_call(Instr) andalso (hipe_icode:call_type(Instr) =:= local) + andalso (hipe_icode:call_fun(Instr) =:= Fun). + +isCallPrimop(Instr, Fun) -> + case hipe_icode:is_call(Instr) of + true -> + case is_tuple(hipe_icode:call_fun(Instr)) of + true -> + ((hipe_icode:call_type(Instr) =:= primop) andalso + (element(1,hipe_icode:call_fun(Instr)) =:= Fun)); + false -> + ((hipe_icode:call_type(Instr) =:= primop) andalso + (hipe_icode:call_fun(Instr) =:= Fun)) + end; + false -> + false + end. + + +%% >-------------------------< Debug code >------------------------------< + +-ifdef(DEBUG_MULRET). + +%%>----------------------------------------------------------------------< +%% Procedure : printTable/1 +%% Purpose : +%% Arguments : +%% Return : +%% Notes : +%%>----------------------------------------------------------------------< +printTable(Mod, Exports, {FunLst, CallLst}) -> + {Y,Mo,D} = date(), + {H,Mi,S} = time(), + io:format("Module: ~w - (~w/~w-~w, ~w:~w:~w)~n=======~n", + [Mod,D,Mo,Y,H,Mi,S]), + io:format("Exports: ~w~n", [Exports]), + io:format("FunList: ~n"), + printFunList(FunLst), + io:format("CallList: ~n"), + printCallList(CallLst). + +printFunList([Fun|FunLst]) -> + io:format(" ~w~n", [Fun]), + printFunList(FunLst); +printFunList([]) -> io:format("~n"). + +printCallList([Call|CallLst]) -> + io:format(" ~w~n", [Call]), + printCallList(CallLst); +printCallList([]) -> io:format("~n"). + +-endif. + +%% >----------------------------< Old code >--------------------------------< + +%% %%>----------------------------------------------------------------------< +%% % Procedure : findCallCode/3 +%% % Purpose : +%% % Arguments : +%% % Return : +%% % Notes : +%% %%>----------------------------------------------------------------------< +%% findCallCode(List, Callee, DstLst) -> findCallCode(List, Callee, DstLst, []). +%% findCallCode([I=#icode_call{'fun'=Callee, dstlist=Var, type=local}, I2, I3|List], +%% Callee, DstLst, Res) -> +%% NewList = removeUnElems(List, Var), +%% %% _Uses = checkForUses(NewList, Var, DstLst), +%% Size = length(DstLst), +%% case I2 of +%% #icode_type{test={tuple, Size}, args=Var, true_label=Label} -> +%% case I3 of +%% #icode_label{name=Label} -> +%% findCallCode(NewList, Callee, DstLst, +%% [I#icode_call{dstlist=DstLst}|Res]); +%% _ -> +%% findCallCode(NewList, Callee, DstLst, +%% [#goto{label=Label}, +%% I#icode_call{dstlist=DstLst}|Res]) +%% end; +%% _ -> +%% findCallCode(NewList, Callee, DstLst, +%% [I2,I#icode_call{dstlist=DstLst}|Res]) +%% end; +%% findCallCode([I|List], Callee, DstLst, Res) -> +%% findCallCode(List, Callee, DstLst, [I|Res]); +%% findCallCode([], _, _, Res) -> lists:reverse(Res). + + +%% %%>----------------------------------------------------------------------< +%% % Procedure : checkForUses +%% % Purpose : +%% % Arguments : +%% % Return : +%% % Notes : +%% %%>----------------------------------------------------------------------< +%% checkForUses(List, Var, Dsts) -> checkForUses(List, Var, Dsts, [], List). +%% checkForUses([I|List], Var, Dsts, Rest, Code) -> +%% Defs = hipe_icode:defines(I), +%% Uses = hipe_icode:uses(I), +%% case lists:member(Var, Uses) of +%% true -> +%% true; +%% false -> +%% case lists:member(Var, Defs) of +%% true -> +%% false; +%% false -> +%% case hipe_icode:is_branch(I) of +%% true -> +%% Succs = hipe_icode:successors(I), +%% checkSuccsForUses(Succs, Var, Dsts, Rest, Code); +%% false -> +%% checkForUses(List, Var, Dsts, [I|Rest], Code) +%% end +%% end +%% end; +%% checkForUses([], _, _, _, _) -> false. + +%% checkSuccsForUses(Succs, Var, Dsts, Rest, Code) -> +%% checkSuccsForUses(Succs, Var, Dsts, Rest, Code, false). +%% checkSuccsForUses([S|Succs], Var, Dsts, Rest, Code, Res) -> +%% List = gotoLabel(S, Code), +%% Used = checkForUses(List, Var, Dsts, Rest, Code), +%% checkSuccsForUses(Succs, Var, Code, Dsts, Used andalso Res); +%% checkSuccsForUses([], _, _, _, _, Res) -> Res. + +%% gotoLabel(L, [L|List]) -> List; +%% gotoLabel(L, [_|List]) -> gotoLabel(L, List); +%% gotoLabel(_, []) -> []. + + +%% %%>----------------------------------------------------------------------< +%% % Procedure : removeUnElems/2 +%% % Purpose : +%% % Arguments : +%% % Return : +%% % Notes : Fixa s� att funktionen anv�nder defines(I) ist�llet och +%% % selektorer ist�llet f�r att matcha p� #call{}. L�tt gjort. +%% %%>----------------------------------------------------------------------< +%% removeUnElems(List, Var) -> removeUnElems(List, Var, []). +%% removeUnElems([#icode_call{'fun'={unsafe_element,_}, args=Var}|List], Var, Res) -> +%% removeUnElems(List, Var, Res); +%% removeUnElems([I=#icode_move{dst=Var}|List], [Var], Res) -> +%% lists:reverse(Res) ++ [I|List]; +%% removeUnElems([I=#icode_call{dstlist=Var}|List], Var, Res) -> +%% lists:reverse(Res) ++ [I|List]; +%% removeUnElems([I|List], Var, Res) -> +%% removeUnElems(List, Var, [I|Res]); +%% removeUnElems([], _, Res) -> lists:reverse(Res). + +%% removeUnElems(List, Var) -> removeUnElems(List, Var, []). +%% removeUnElems([I|List], Var, Res) -> +%% Defs = hipe_icode:defines(I), +%% case hipe_icode:is_call(I) of +%% true -> +%% Fn = hipe_icode:call_fun(I), +%% case (hipe_icode:call_args(I) =:= Var) andalso is_tuple(Fn) of +%% true -> +%% case element(1,Fn) =:= unsafe_element of +%% true -> +%% removeUnElems(List, Var, Res); +%% false -> +%% case lists:member(Var, Defs) of +%% true -> +%% lists:reverse(Res) ++ [I|List]; +%% false -> +%% removeUnElems(List, Var, [I|Res]) +%% end +%% end; +%% false -> +%% case lists:member(Var, Defs) of +%% true -> +%% lists:reverse(Res) ++ [I|List]; +%% false -> +%% removeUnElems(List, Var, [I|Res]) +%% end +%% end; +%% false -> +%% case lists:member(Var, Defs) of +%% true -> +%% lists:reverse(Res) ++ [I|List]; +%% false -> +%% removeUnElems(List, Var, [I|Res]) +%% end +%% end; +%% removeUnElems([], _, Res) -> lists:reverse(Res). + + +%% Old findDefine that also could update it. +%% ----------------------------------------- +%% findDefine(Code, Var) -> findDefine(Code, Var, [], []). +%% findDefine([#icode_call{dstlist=Var,'fun'=mktuple,args=Vs}|Code],Var,NewCode,_)-> +%% findDefine(Code, Var, NewCode, Vs); +%% findDefine([I=#icode_move{dst=Var, src=Src}|Code], [Var], NewCode, _) -> +%% case Src of +%% #icode_var{} -> +%% findDefine(Code, [Src], [I|NewCode], [Src]); +%% #icode_const{value={flat, Tuple}} -> +%% findDefine(Code, [Var], [I|NewCode], []) %% Check this case! [Var] +%% end; +%% findDefine([I|Code], Var, NewCode, Vars) -> +%% findDefine(Code, Var, [I|NewCode], Vars); +%% findDefine([], _, NewCode, Vars) -> +%% case Vars of +%% [] -> +%% notFound; +%% [_] -> +%% {notFound, Vars}; +%% _ -> +%% {found, lists:reverse(NewCode), Vars} +%% end. + +%% modifyCode(Code, Var) -> +%% [#icode_return{vars=Var}|Code2] = lists:reverse(Code), +%% case (length(Var) =< hipe_rtl_arch:nr_of_return_regs()) of +%% true -> +%% {Arity, Code3} = modifyCode(Code2, Var, []), +%% {Arity, Code3}; +%% false -> +%% {1, Code} +%% end. + +%% modifyCode([I|Code], Var, Res) -> +%% case scanInstr(I, Var) of +%% {move, Arity, VarLst} -> +%% Code2 = [#icode_return{vars=VarLst}, I |lists:reverse(Res) ++ Code], +%% {Arity, lists:reverse(Code2)}; +%% {mktuple, Arity, VarLst} -> +%% Code2 = [#icode_return{vars=VarLst}|lists:reverse(Res) ++ Code], +%% {Arity, lists:reverse(Code2)}; +%% other -> +%% modifyCode(Code, Var, [I|Res]) +%% end; +%% modifyCode([], Var, Res) -> +%% {1, lists:reverse(Res) ++ [#icode_return{vars=Var}]}. + +%% scanInstr(#icode_call{dstlist=Var, 'fun'=mktuple, args=Lst}, Var) -> +%% {mktuple, length(Lst), Lst}; +%% scanInstr(_, _) -> other. + +%% printCode(Cfg) -> +%% Labels = hipe_icode_cfg:labels(Cfg), +%% {_,_,{_,F,_,_,_,_,_,_},_} = Cfg, +%% io:format("~nFunction: ~w~n", [F]), +%% Print = fun(Label) -> +%% Block = hipe_icode_cfg:bb(Cfg, Label), +%% Code = hipe_bb:code(Block), +%% io:format("Label: ~w~n", [Label]), +%% lists:foreach(fun(I) -> io:format("~w~n", [I]) end, Code), +%% io:format("~n") +%% end, +%% lists:foreach(Print, Labels). + +%% printList(File, [{MFA, #icode{code=Code, params=Parms}}|List]) -> +%% io:format(File, "MFA: ~w - Params: ~w~n", [MFA, Parms]), +%% printList2(File, Code), +%% printList(File, List); +%% printList(_, []) -> ok. + +%% printList2(File, []) -> io:format(File, "~n~n", []); +%% printList2(File, IList) when is_list(IList) -> +%% [I|List] = IList, +%% io:format(File, "~w~n", [I]), +%% printList2(File, List); +%% printList2(File, SomethingElse) -> +%% io:format(File, "Got: ~w~n", [SomethingElse]). + +%% optimizeDefine([#icode_call{dstlist=Var,'fun'=mktuple,args=Vs}|Code], +%% Var, _, Res) -> +%% case Vs of +%% [_] -> +%% {none, noOpt}; +%% _ -> +%% optimizeDefine(Code, Var, Vs, Res) +%% end; +%% optimizeDefine([I=#icode_move{dst=Var, src=Src}|Code], [Var], Rets, Res) -> +%% case hipe_icode:is_var(Src) of +%% true -> +%% optimizeDefine(Code, [Src], Rets, Res); +%% false -> +%% case Src of +%% #icode_const{value={flat, Tuple}} when is_tuple(Tuple) -> +%% optimizeDefine(Code, [Var], tuple_to_list(Tuple), [I|Res]); +%% #icode_const{value={flat, _}} -> +%% {none, noOpt}; +%% _ -> +%% optimizeDefine(Code, [Var], Rets, [I|Res]) +%% end +%% end; +%% optimizeDefine([I|Code], Var, Rets, Res) -> +%% optimizeDefine(Code, Var, Rets, [I|Res]); +%% optimizeDefine([], Var, Rets, Res) -> +%% case Rets of +%% [] -> +%% {none, Res, Var}; +%% _ -> +%% {found, Res, Rets} +%% end. diff --git a/lib/hipe/icode/hipe_icode_pp.erl b/lib/hipe/icode/hipe_icode_pp.erl new file mode 100755 index 0000000000..575bbfe43d --- /dev/null +++ b/lib/hipe/icode/hipe_icode_pp.erl @@ -0,0 +1,303 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Copyright (c) 2003 by Erik Stenman. +%% ==================================================================== +%% Filename : hipe_icode_pp.erl +%% Module : hipe_icode_pp +%% Purpose : Pretty-printer for Icode. +%% Notes : +%% History : * 2003-04-16 ([email protected]): Created. +%% CVS : +%% $Author$ +%% $Date$ +%% $Revision$ +%% ==================================================================== +%% +%% @doc +%% Icode Pretty-Printer. +%% @end +%% +%% ==================================================================== + +-module(hipe_icode_pp). + +-export([pp/1, pp/2, pp_block/1]). + +-ifdef(DEBUG_ICODE). +-export([pp_instrs/2]). +-endif. + +-include("hipe_icode.hrl"). + +%%--------------------------------------------------------------------- + +-spec pp(#icode{}) -> 'ok'. +%% @doc Prettyprints linear Icode on stdout. +%% <p> Badly formed or unknown instructions are printed surrounded +%% by three stars "***".</p> +pp(Icode) -> + pp(standard_io, Icode). + +-spec pp(io:device(), #icode{}) -> 'ok'. +%% @doc Prettyprints linear Icode on IoDevice. +%% <p> Badly formed or unknown instructions are printed surrounded by +%% three stars "***".</p> +pp(Dev, Icode) -> + {Mod, Fun, Arity} = hipe_icode:icode_fun(Icode), + Args = hipe_icode:icode_params(Icode), + io:format(Dev, "~w:~w/~w(", [Mod, Fun, Arity]), + pp_args(Dev, Args), + io:format(Dev, ") ->~n", []), + io:format(Dev, "%% Info:~p\n", + [[case hipe_icode:icode_is_closure(Icode) of + true -> 'Closure'; + false -> 'Not a closure' + end, + case hipe_icode:icode_is_leaf(Icode) of + true -> 'Leaf function'; + false -> 'Not a leaf function' + end | + hipe_icode:icode_info(Icode)]]), + pp_instrs(Dev, hipe_icode:icode_code(Icode)), + io:format(Dev, "%% Data:\n", []), + hipe_data_pp:pp(Dev, hipe_icode:icode_data(Icode), icode, ""). + +-spec pp_block(icode_instrs()) -> 'ok'. +pp_block(Code) -> + pp_instrs(standard_io, Code). + +-spec pp_instrs(io:device(), icode_instrs()) -> 'ok'. +%% @doc Prettyprints a list of Icode instructions. +pp_instrs(Dev, Is) -> + lists:foreach(fun (I) -> pp_instr(Dev, I) end, Is). + +%%--------------------------------------------------------------------- + +pp_instr(Dev, I) -> + case I of + #icode_label{} -> + io:format(Dev, "~p:~n", [hipe_icode:label_name(I)]); + #icode_comment{} -> + Txt = hipe_icode:comment_text(I), + Str = case io_lib:deep_char_list(Txt) of + true -> Txt; + false -> io_lib:format("~p", [Txt]) + end, + io:format(Dev, " % ~s~n", [Str]); + #icode_phi{} -> + io:format(Dev, " ", []), + pp_arg(Dev, hipe_icode:phi_dst(I)), + io:format(Dev, " := phi(", []), + pp_phi_args(Dev, hipe_icode:phi_arglist(I)), + io:format(Dev, ")~n", []); + #icode_move{} -> + io:format(Dev, " ", []), + pp_arg(Dev, hipe_icode:move_dst(I)), + io:format(Dev, " := ", []), + pp_arg(Dev, hipe_icode:move_src(I)), + io:format(Dev, "~n", []); + #icode_call{} -> + io:format(Dev, " ", []), + case hipe_icode:call_dstlist(I) of + [] -> %% result is unused -- e.g. taken out by dead code elimination + io:format(Dev, "_ := ", []); + DstList -> + pp_args(Dev, DstList), + io:format(Dev, " := ", []) + end, + pp_fun(Dev, hipe_icode:call_fun(I), + hipe_icode:call_args(I), + hipe_icode:call_type(I), + hipe_icode:call_in_guard(I)), + case hipe_icode:call_continuation(I) of + [] -> + ok; + CC -> + io:format(Dev, " -> ~w", [CC]) + end, + case hipe_icode:call_fail_label(I) of + [] -> io:format(Dev, "~n", []); + Fail -> io:format(Dev, ", #fail ~w~n", [Fail]) + end; + #icode_enter{} -> + io:format(Dev, " ", []), + pp_fun(Dev, hipe_icode:enter_fun(I), + hipe_icode:enter_args(I), + hipe_icode:enter_type(I)), + io:format(Dev, "~n", []); + #icode_return{} -> + io:format(Dev, " return(", []), + pp_args(Dev, hipe_icode:return_vars(I)), + io:format(Dev, ")~n", []); + #icode_begin_try{} -> + io:format(Dev, " begin_try -> ~w cont ~w~n", + [hipe_icode:begin_try_label(I), + hipe_icode:begin_try_successor(I)]); + #icode_begin_handler{} -> + io:format(Dev, " ", []), + pp_args(Dev, hipe_icode:begin_handler_dstlist(I)), + io:format(Dev, " := begin_handler()~n",[]); + #icode_end_try{} -> + io:format(Dev, " end_try~n", []); + #icode_fail{} -> + Type = hipe_icode:fail_class(I), + io:format(Dev, " fail(~w, [", [Type]), + pp_args(Dev, hipe_icode:fail_args(I)), + case hipe_icode:fail_label(I) of + [] -> io:put_chars(Dev, "])\n"); + Fail -> io:format(Dev, "]) -> ~w\n", [Fail]) + end; + #icode_if{} -> + io:format(Dev, " if ~w(", [hipe_icode:if_op(I)]), + pp_args(Dev, hipe_icode:if_args(I)), + io:format(Dev, ") then ~p (~.2f) else ~p~n", + [hipe_icode:if_true_label(I), hipe_icode:if_pred(I), + hipe_icode:if_false_label(I)]); + #icode_switch_val{} -> + io:format(Dev, " switch_val ",[]), + pp_arg(Dev, hipe_icode:switch_val_term(I)), + pp_switch_cases(Dev, hipe_icode:switch_val_cases(I)), + io:format(Dev, " fail -> ~w\n", + [hipe_icode:switch_val_fail_label(I)]); + #icode_switch_tuple_arity{} -> + io:format(Dev, " switch_tuple_arity ",[]), + pp_arg(Dev, hipe_icode:switch_tuple_arity_term(I)), + pp_switch_cases(Dev,hipe_icode:switch_tuple_arity_cases(I)), + io:format(Dev, " fail -> ~w\n", + [hipe_icode:switch_tuple_arity_fail_label(I)]); + #icode_type{} -> + io:format(Dev, " if is_", []), + pp_type(Dev, hipe_icode:type_test(I)), + io:format(Dev, "(", []), + pp_args(Dev, hipe_icode:type_args(I)), + io:format(Dev, ") then ~p (~.2f) else ~p~n", + [hipe_icode:type_true_label(I), hipe_icode:type_pred(I), + hipe_icode:type_false_label(I)]); + #icode_goto{} -> + io:format(Dev, " goto ~p~n", [hipe_icode:goto_label(I)]) + end. + +pp_fun(Dev, Fun, Args, Type) -> + pp_fun(Dev, Fun, Args, Type, false). + +pp_fun(Dev, Fun, Args, Type, Guard) -> + case Type of + primop -> + hipe_icode_primops:pp(Dev, Fun); + local -> + {_,F,A} = Fun, + io:format(Dev, "~w/~w", [F, A]); + remote -> + {M,F,A} = Fun, + io:format(Dev, "~w:~w/~w", [M, F, A]) + end, + io:format(Dev, "(", []), + pp_args(Dev, Args), + case Guard of + true -> + case Type of + primop -> + io:format(Dev, ") (primop,guard)", []); + _ -> + io:format(Dev, ") (guard)", []) + end; + false -> + case Type of + primop -> + io:format(Dev, ") (primop)", []); + _ -> + io:format(Dev, ")", []) + end + end. + +pp_arg(Dev, Arg) -> + case hipe_icode:is_variable(Arg) of + true -> + case hipe_icode:is_var(Arg) of + true -> + N = hipe_icode:var_name(Arg), + io:format(Dev, "v~p", [N]); + false -> + case hipe_icode:is_reg(Arg) of + true -> + N = hipe_icode:reg_name(Arg), + io:format(Dev, "r~p", [N]); + false -> + N = hipe_icode:fvar_name(Arg), + io:format(Dev, "fv~p", [N]) + end + end, + case hipe_icode:is_annotated_variable(Arg) of + true -> + {_,Val,Fun} = hipe_icode:variable_annotation(Arg), + io:format(Dev, " (~s)", [Fun(Val)]); + false -> + ok + end; + false -> + Const = hipe_icode:const_value(Arg), + io:format(Dev, "~p", [Const]) % ~p because it also prints "" + end. + +pp_args(_Dev, []) -> ok; +pp_args(Dev, [A]) -> + pp_arg(Dev, A); +pp_args(Dev, [A|Args]) -> + pp_arg(Dev, A), + io:format(Dev, ", ", []), + pp_args(Dev, Args). + +pp_phi_args(_Dev, []) -> ok; +pp_phi_args(Dev, [{Pred,A}]) -> + io:format(Dev, "{~w, ", [Pred]), + pp_arg(Dev, A), + io:format(Dev, "}", []); +pp_phi_args(Dev, [{Pred,A}|Args]) -> + io:format(Dev, "{~w, ", [Pred]), + pp_arg(Dev, A), + io:format(Dev, "}, ", []), + pp_phi_args(Dev, Args). + +pp_type(Dev, T) -> + io:format(Dev, "~w", [T]). + +pp_switch_cases(Dev, Cases) -> + io:format(Dev, " of\n",[]), + pp_switch_cases(Dev, Cases,1), + io:format(Dev, "",[]). + +pp_switch_cases(Dev, [{Val,L}], _Pos) -> + io:format(Dev, " ",[]), + pp_arg(Dev, Val), + io:format(Dev, " -> ~w\n", [L]); +pp_switch_cases(Dev, [{Val, L}|Ls], Pos) -> + io:format(Dev, " ",[]), + pp_arg(Dev, Val), + io:format(Dev, " -> ~w;\n", [L]), + NewPos = Pos, + %% case Pos of + %% 5 -> io:format(Dev, "\n ",[]), + %% 0; + %% N -> N + 1 + %% end, + pp_switch_cases(Dev, Ls, NewPos); +pp_switch_cases(_Dev, [], _) -> ok. + diff --git a/lib/hipe/icode/hipe_icode_primops.erl b/lib/hipe/icode/hipe_icode_primops.erl new file mode 100644 index 0000000000..b0fe7eb708 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_primops.erl @@ -0,0 +1,963 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Copyright (c) 2001 by Erik Johansson. All Rights Reserved +%% ==================================================================== +%% Filename : hipe_icode_primops.erl +%% Module : hipe_icode_primops +%% Purpose : +%% Notes : +%% History : * 2001-06-13 Erik Johansson ([email protected]): +%% Created. +%% +%% $Id$ +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(hipe_icode_primops). + +-export([is_safe/1, fails/1, pp/2, type/1, type/2, arg_types/1]). + +-include("hipe_icode.hrl"). +-include("hipe_icode_primops.hrl"). + +%%--------------------------------------------------------------------- + +%% Note that 'unsafe_...' operations are generally "safe", i.e., it is +%% typically unsafe to use them unless you have extra information about +%% the call (e.g., if the types are known). However, if they have been +%% correctly introduced in the code, most of them are also OK to remove +%% if the result is not used. + +-spec is_safe(icode_primop()) -> boolean(). + +is_safe('+') -> false; +is_safe('/') -> false; +is_safe('*') -> false; +is_safe('-') -> false; +is_safe('bsr') -> false; +is_safe('bsl') -> false; +is_safe('band') -> false; +is_safe('bor') -> false; +is_safe('bxor') -> false; +is_safe('bnot') -> false; +is_safe('div') -> false; +is_safe('rem') -> false; +is_safe(call_fun) -> false; +is_safe(check_get_msg) -> false; +is_safe(clear_timeout) -> false; +is_safe(cons) -> true; +%% is_safe(conv_to_float) -> false; +is_safe(extra_unsafe_add) -> true; +is_safe(extra_unsafe_sub) -> true; +is_safe(fcheckerror) -> false; +is_safe(fclearerror) -> false; +is_safe(fp_add) -> false; +is_safe(fp_div) -> false; +is_safe(fp_mul) -> false; +is_safe(fp_sub) -> false; +is_safe(mktuple) -> true; +is_safe(next_msg) -> false; +is_safe(redtest) -> false; +is_safe(select_msg) -> false; +is_safe(self) -> true; +is_safe(set_timeout) -> false; +is_safe(suspend_msg) -> false; +is_safe(unsafe_add) -> true; +is_safe(unsafe_band) -> true; +is_safe(unsafe_bnot) -> true; +is_safe(unsafe_bor) -> true; +is_safe(unsafe_bsl) -> true; +is_safe(unsafe_bsr) -> true; +is_safe(unsafe_bxor) -> true; +is_safe(unsafe_hd) -> true; +is_safe(unsafe_sub) -> true; +is_safe(unsafe_tag_float) -> true; +is_safe(unsafe_tl) -> true; +is_safe(unsafe_untag_float) -> true; +is_safe(#apply_N{}) -> false; +is_safe(#closure_element{}) -> true; +is_safe(#element{}) -> false; +%% is_safe(#gc_test{}) -> ??? +is_safe({hipe_bs_primop, {bs_start_match, _}}) -> false; +is_safe({hipe_bs_primop, {{bs_start_match, bitstr}, _}}) -> true; +is_safe({hipe_bs_primop, {{bs_start_match, ok_matchstate}, _}}) -> false; +is_safe({hipe_bs_primop, {bs_get_binary, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_get_binary_all, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_get_binary_all_2, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_get_integer, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_get_float, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_skip_bits, _}}) -> false; +is_safe({hipe_bs_primop, {bs_skip_bits_all, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_test_tail, _}}) -> false; +is_safe({hipe_bs_primop, {bs_restore, _}}) -> true; +is_safe({hipe_bs_primop, {bs_save, _}}) -> true; +is_safe({hipe_bs_primop, {bs_add, _}}) -> false; +is_safe({hipe_bs_primop, {bs_add, _, _}}) -> false; +is_safe({hipe_bs_primop, bs_bits_to_bytes}) -> false; +is_safe({hipe_bs_primop, bs_bits_to_bytes2}) -> false; +is_safe({hipe_bs_primop, {bs_init, _}}) -> false; +is_safe({hipe_bs_primop, {bs_init, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_init_bits, _}}) -> false; +is_safe({hipe_bs_primop, {bs_init_bits, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_put_binary, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_put_binary_all, _}}) -> false; +is_safe({hipe_bs_primop, {bs_put_float, _, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_put_integer, _, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_put_string, _, _}}) -> false; +is_safe({hipe_bs_primop, bs_put_utf8}) -> false; +is_safe({hipe_bs_primop, bs_utf8_size}) -> true; +is_safe({hipe_bs_primop, bs_get_utf8}) -> false; +is_safe({hipe_bs_primop, bs_utf16_size}) -> true; +is_safe({hipe_bs_primop, {bs_put_utf16, _}}) -> false; +is_safe({hipe_bs_primop, {bs_get_utf16, _}}) -> false; +is_safe({hipe_bs_primop, bs_validate_unicode}) -> false; +is_safe({hipe_bs_primop, bs_validate_unicode_retract}) -> false; +is_safe({hipe_bs_primop, {unsafe_bs_put_integer, _, _, _}}) -> false; +is_safe({hipe_bs_primop, bs_final}) -> true; +is_safe({hipe_bs_primop, bs_context_to_binary}) -> true; +is_safe({hipe_bs_primop, {bs_test_unit, _}}) -> false; +is_safe({hipe_bs_primop, {bs_match_string, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_append, _, _, _, _}}) -> false; +is_safe({hipe_bs_primop, {bs_private_append, _, _}}) -> false; +is_safe({hipe_bs_primop, bs_init_writable}) -> true; +is_safe(#mkfun{}) -> true; +is_safe(#unsafe_element{}) -> true; +is_safe(#unsafe_update_element{}) -> true. + + +-spec fails(icode_funcall()) -> boolean(). + +fails('+') -> true; +fails('-') -> true; +fails('*') -> true; +fails('/') -> true; +fails('bnot') -> true; +fails('band') -> true; +fails('bor') -> true; +fails('bsl') -> true; +fails('bsr') -> true; +fails('bxor') -> true; +fails('div') -> true; +fails('rem') -> true; +fails(call_fun) -> true; +fails(check_get_msg) -> true; +fails(clear_timeout) -> false; +fails(cons) -> false; +fails(conv_to_float) -> true; +fails(extra_unsafe_add) -> false; +fails(extra_unsafe_sub) -> false; +fails(fcheckerror) -> true; +fails(fclearerror) -> false; +fails(fp_add) -> false; +fails(fp_div) -> false; +fails(fp_mul) -> false; +fails(fp_sub) -> false; +fails(mktuple) -> false; +fails(next_msg) -> false; +fails(redtest) -> false; +fails(select_msg) -> false; +fails(self) -> false; +fails(set_timeout) -> true; +fails(suspend_msg) -> false; +fails(unsafe_untag_float) -> false; +fails(unsafe_tag_float) -> false; +fails(unsafe_add) -> false; +fails(unsafe_band) -> false; +fails(unsafe_bnot) -> false; +fails(unsafe_bor) -> false; +fails(unsafe_bsl) -> false; +fails(unsafe_bsr) -> false; +fails(unsafe_bxor) -> false; +fails(unsafe_hd) -> false; +fails(unsafe_sub) -> false; +%% fails(unsafe_tag_float) -> false; +fails(unsafe_tl) -> false; +%% fails(unsafe_untag_float) -> false; +fails(#apply_N{}) -> true; +fails(#closure_element{}) -> false; +fails(#element{}) -> true; +%% fails(#gc_test{}) -> ??? +fails({hipe_bs_primop, {bs_start_match, _}}) -> true; +fails({hipe_bs_primop, {{bs_start_match, bitstr}, _}}) -> true; +fails({hipe_bs_primop, {{bs_start_match, ok_matchstate}, _}}) -> false; +fails({hipe_bs_primop, {bs_get_binary, _, _}}) -> true; +fails({hipe_bs_primop, {bs_get_binary_all, _, _}}) -> true; +fails({hipe_bs_primop, {bs_get_binary_all_2, _, _}}) -> true; +fails({hipe_bs_primop, {bs_get_integer, _, _}}) -> true; +fails({hipe_bs_primop, {bs_get_float, _, _}}) -> true; +fails({hipe_bs_primop, {bs_skip_bits, _}}) -> true; +fails({hipe_bs_primop, {bs_skip_bits_all, _, _}}) -> true; +fails({hipe_bs_primop, {bs_test_tail, _}}) -> true; +fails({hipe_bs_primop, {bs_restore, _}}) -> false; +fails({hipe_bs_primop, {bs_save, _}}) -> false; +fails({hipe_bs_primop, bs_context_to_binary}) -> false; +fails({hipe_bs_primop, {bs_test_unit, _}}) -> true; +fails({hipe_bs_primop, {bs_match_string, _, _}}) -> true; +fails({hipe_bs_primop, {bs_add, _}}) -> true; +fails({hipe_bs_primop, {bs_add, _, _}}) -> true; +fails({hipe_bs_primop, bs_bits_to_bytes}) -> true; +fails({hipe_bs_primop, bs_bits_to_bytes2}) -> true; +fails({hipe_bs_primop, {bs_init, _}}) -> true; +fails({hipe_bs_primop, {bs_init, _, _}}) -> true; +fails({hipe_bs_primop, {bs_init_bits, _}}) -> true; +fails({hipe_bs_primop, {bs_init_bits, _, _}}) -> true; +fails({hipe_bs_primop, {bs_put_binary, _, _}}) -> true; +fails({hipe_bs_primop, {bs_put_binary_all, _}}) -> true; +fails({hipe_bs_primop, {bs_put_float, _, _, _}}) -> true; +fails({hipe_bs_primop, {bs_put_integer, _, _, _}}) -> true; +fails({hipe_bs_primop, {bs_put_string, _, _}}) -> true; +fails({hipe_bs_primop, bs_put_utf8}) -> true; +fails({hipe_bs_primop, bs_utf8_size}) -> false; +fails({hipe_bs_primop, bs_get_utf8}) -> true; +fails({hipe_bs_primop, bs_utf16_size}) -> false; +fails({hipe_bs_primop, {bs_put_utf16, _}}) -> true; +fails({hipe_bs_primop, {bs_get_utf16, _}}) -> true; +fails({hipe_bs_primop, bs_validate_unicode}) -> true; +fails({hipe_bs_primop, bs_validate_unicode_retract}) -> true; +fails({hipe_bs_primop, {unsafe_bs_put_integer, _, _, _}}) -> true; +fails({hipe_bs_primop, bs_final}) -> false; +fails({hipe_bs_primop, {bs_append, _, _, _, _}}) -> true; +fails({hipe_bs_primop, {bs_private_append, _, _}}) -> true; +fails({hipe_bs_primop, bs_init_writable}) -> true; +fails(#mkfun{}) -> false; +fails(#unsafe_element{}) -> false; +fails(#unsafe_update_element{}) -> false; +%% Apparently, we are calling fails/1 for all MFAs which are compiled. +%% This is weird and we should restructure the compiler to avoid +%% calling fails/1 for things that are not primops. +fails({M, F, A}) when is_atom(M), is_atom(F), is_integer(A), 0 =< A, A =< 255 -> + %% Yes, we should move this. + not erl_bifs:is_safe(M, F, A). + +%%===================================================================== +%% Pretty printing +%%===================================================================== + +-spec pp(io:device(), icode_primop()) -> 'ok'. + +pp(Dev, Op) -> + case Op of + #apply_N{arity = N} -> + io:format(Dev, "apply_N<~w>/", [N]); + #closure_element{n = N} -> + io:format(Dev, "closure_element<~w>", [N]); + #element{} -> + io:format(Dev, "element", []); + #gc_test{need = N} -> + io:format(Dev, "gc_test<~w>", [N]); + {hipe_bs_primop, BsOp} -> + case BsOp of + {bs_put_binary_all, Flags} -> + io:format(Dev, "bs_put_binary_all<~w>", [Flags]); + {bs_put_binary, Size} -> + io:format(Dev, "bs_put_binary<~w>", [Size]); + {bs_put_binary, Flags, Size} -> + io:format(Dev, "bs_put_binary<~w, ~w>", [Flags, Size]); + {bs_put_float, Flags, Size, _ConstInfo} -> + io:format(Dev, "bs_put_float<~w, ~w>", [Flags, Size]); + {bs_put_string, String, SizeInBytes} -> + io:format(Dev, "bs_put_string<~w, ~w>", [String, SizeInBytes]); + {bs_put_integer, Bits, Flags, _ConstInfo} -> + io:format(Dev, "bs_put_integer<~w, ~w>", [Bits, Flags]); + {unsafe_bs_put_integer, Bits, Flags, _ConstInfo} -> + io:format(Dev, "unsafe_bs_put_integer<~w, ~w>", [Bits, Flags]); + {bs_skip_bits_all, Unit, Flags} -> + io:format(Dev, "bs_skip_bits_all<~w,~w>", [Unit, Flags]); + {bs_skip_bits, Unit} -> + io:format(Dev, "bs_skip_bits<~w>", [Unit]); + {bs_start_match, Max} -> + io:format(Dev, "bs_start_match<~w>", [Max]); + {{bs_start_match, Type}, Max} -> + io:format(Dev, "bs_start_match<~w,~w>", [Type,Max]); + {bs_match_string, String, SizeInBytes} -> + io:format(Dev, "bs_match_string<~w, ~w>", [String, SizeInBytes]); + {bs_get_integer, Size, Flags} -> + io:format(Dev, "bs_get_integer<~w, ~w>", [Size, Flags]); + {bs_get_float, Size, Flags} -> + io:format(Dev, "bs_get_float<~w, ~w>", [Size, Flags]); + {bs_get_binary, Size, Flags} -> + io:format(Dev, "bs_get_binary<~w, ~w>", [Size, Flags]); + {bs_get_binary_all, Unit, Flags} -> + io:format(Dev, "bs_get_binary_all<~w,~w>", [Unit, Flags]); + {bs_get_binary_all_2, Unit, Flags} -> + io:format(Dev, "bs_get_binary_all<~w,~w>", [Unit, Flags]); + {bs_test_tail, NumBits} -> + io:format(Dev, "bs_test_tail<~w>", [NumBits]); + {bs_test_unit, Unit} -> + io:format(Dev, "bs_test_unit<~w>", [Unit]); + bs_context_to_binary -> + io:format(Dev, "bs_context_to_binary", []); + {bs_restore, Index} -> + io:format(Dev, "bs_restore<~w>", [Index]); + {bs_save, Index} -> + io:format(Dev, "bs_save<~w>", [Index]); + {bs_init, Size, Flags} -> + io:format(Dev, "bs_init<~w, ~w>", [Size, Flags]); + {bs_init,Flags} -> + io:format(Dev, "bs_init<~w>", [Flags]); + {bs_init_bits, Size, Flags} -> + io:format(Dev, "bs_init_bits<~w, ~w>", [Size, Flags]); + {bs_init_bits, Flags} -> + io:format(Dev, "bs_init_bits<~w>", [Flags]); + {bs_add, Unit} -> + io:format(Dev, "bs_add<~w>", [Unit]); + {bs_add, Const, Unit} -> + io:format(Dev, "bs_add<~w, ~w>", [Const, Unit]); + {bs_append, X, Y, Z, W} -> + io:format(Dev, "bs_append<~w, ~w, ~w, ~w>", [X, Y, Z, W]); + {bs_private_append, U, Flags} -> + io:format(Dev, "bs_private_append<~w, ~w>", [U, Flags]); + bs_bits_to_bytes -> + io:format(Dev, "bs_bits_to_bytes", []); + bs_bits_to_bytes2 -> + io:format(Dev, "bs_bits_to_bytes2", []); + bs_utf8_size -> + io:format(Dev, "bs_utf8_size", []); + bs_put_utf8 -> + io:format(Dev, "bs_put_utf8", []); + bs_get_utf8 -> + io:format(Dev, "bs_get_utf8", []); + bs_utf16_size -> + io:format(Dev, "bs_utf16_size", []); + {bs_put_utf16, Flags} -> + io:format(Dev, "bs_put_utf16<~w>", [Flags]); + {bs_get_utf16, Flags} -> + io:format(Dev, "bs_get_utf16<~w>", [Flags]); + bs_validate_unicode -> + io:format(Dev, "bs_validate_unicode", []); + bs_validate_unicode_retract -> + io:format(Dev, "bs_validate_unicode_retract", []); + bs_final -> + io:format(Dev, "bs_final", []); + bs_final2 -> + io:format(Dev, "bs_final2", []); + bs_init_writable -> + io:format(Dev, "bs_init_writable", []) + end; + #mkfun{mfa = {Mod, Fun, Arity}, magic_num = Unique, index = I} -> + io:format(Dev, "mkfun<~w,~w,~w,~w,~w>", [Mod, Fun, Arity, Unique, I]); + #unsafe_element{index = N} -> + io:format(Dev, "unsafe_element<~w>", [N]); + #unsafe_update_element{index = N} -> + io:format(Dev, "unsafe_update_element<~w>", [N]); + Fun when is_atom(Fun) -> + io:format(Dev, "~w", [Fun]) + end. + +%%===================================================================== +%% Type handling +%%===================================================================== + +-spec type(icode_funcall(), [erl_types:erl_type()]) -> erl_types:erl_type(). + +type(Primop, Args) -> + case Primop of +%%% ----------------------------------------------------- +%%% Arithops + '+' -> + erl_bif_types:type(erlang, '+', 2, Args); + '-' -> + erl_bif_types:type(erlang, '-', 2, Args); + '*' -> + erl_bif_types:type(erlang, '*', 2, Args); + '/' -> + erl_bif_types:type(erlang, '/', 2, Args); + 'band' -> + erl_bif_types:type(erlang, 'band', 2, Args); + 'bnot' -> + erl_bif_types:type(erlang, 'bnot', 1, Args); + 'bor' -> + erl_bif_types:type(erlang, 'bor', 2, Args); + 'bxor' -> + erl_bif_types:type(erlang, 'bxor', 2, Args); + 'bsl' -> + erl_bif_types:type(erlang, 'bsl', 2, Args); + 'bsr' -> + erl_bif_types:type(erlang, 'bsr', 2, Args); + 'div' -> + erl_bif_types:type(erlang, 'div', 2, Args); + 'rem' -> + erl_bif_types:type(erlang, 'rem', 2, Args); + extra_unsafe_add -> + erl_bif_types:type(erlang, '+', 2, Args); + unsafe_add -> + erl_bif_types:type(erlang, '+', 2, Args); + unsafe_bnot -> + erl_bif_types:type(erlang, 'bnot', 1, Args); + unsafe_bor -> + erl_bif_types:type(erlang, 'bor', 2, Args); + unsafe_band -> + erl_bif_types:type(erlang, 'band', 2, Args); + unsafe_bxor -> + erl_bif_types:type(erlang, 'bxor', 2, Args); + unsafe_sub -> + erl_bif_types:type(erlang, '-', 2, Args); +%%% ----------------------------------------------------- +%%% Lists + cons -> + [HeadType, TailType] = Args, + erl_types:t_cons(HeadType, TailType); + unsafe_hd -> + [Type] = Args, + case erl_types:t_is_cons(Type) of + true -> erl_types:t_cons_hd(Type); + false -> erl_types:t_none() + end; + unsafe_tl -> + [Type] = Args, + case erl_types:t_is_cons(Type) of + true -> erl_types:t_cons_tl(Type); + false -> erl_types:t_none() + end; +%%% ----------------------------------------------------- +%%% Tuples + mktuple -> + erl_types:t_tuple(Args); + #element{} -> + erl_bif_types:type(erlang, element, 2, Args); + #unsafe_element{index = N} -> + [Type] = Args, + case erl_types:t_is_tuple(Type) of + false -> + erl_types:t_none(); + true -> + Index = erl_types:t_from_term(N), + erl_bif_types:type(erlang, element, 2, [Index|Args]) + end; + #unsafe_update_element{index = N} -> + %% Same, same + erl_bif_types:type(erlang, setelement, 3, [erl_types:t_integer(N)|Args]); +%%% ----------------------------------------------------- +%%% Floats + fclearerror -> + erl_types:t_any(); + fcheckerror -> + erl_types:t_any(); + unsafe_tag_float -> + erl_types:t_float(); + %% These might look surprising, but the return is an untagged + %% float and we have no type for untagged values. + conv_to_float -> + erl_types:t_any(); + unsafe_untag_float -> + erl_types:t_any(); + fp_add -> + erl_types:t_any(); + fp_sub -> + erl_types:t_any(); + fp_mul -> + erl_types:t_any(); + fp_div -> + erl_types:t_any(); + fnegate -> + erl_types:t_any(); +%%% ----------------------------------------------------- +%%% + {hipe_bs_primop, {bs_start_match, Max}} -> + [Type] = Args, + Init = + erl_types:t_sup( + erl_types:t_matchstate_present(Type), + erl_types:t_inf(erl_types:t_bitstr(1, 0), Type)), + case erl_types:t_is_none(Init) of + true -> + erl_types:t_none(); + false -> + erl_types:t_matchstate(Init, Max) + end; + {hipe_bs_primop, {{bs_start_match, _}, Max}} -> + [Type] = Args, + Init = + erl_types:t_sup( + erl_types:t_matchstate_present(Type), + erl_types:t_inf(erl_types:t_bitstr(1, 0), Type)), + case erl_types:t_is_none(Init) of + true -> + erl_types:t_none(); + false -> + erl_types:t_matchstate(Init, Max) + end; + {hipe_bs_primop, {bs_get_integer, Size, Flags}} -> + Signed = Flags band 4, + [MatchState|RestArgs] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + case RestArgs of + [] -> + NewBinType = match_bin(erl_types:t_bitstr(0, Size), BinType), + NewMatchState = + erl_types:t_matchstate_update_present(NewBinType, MatchState), + if Signed =:= 0 -> + erl_types:t_product([erl_types:t_from_range(0, 1 bsl Size - 1), + NewMatchState]); + Signed =:= 4 -> + erl_types:t_product([erl_types:t_from_range(- (1 bsl (Size-1)), + (1 bsl (Size-1)) - 1), + NewMatchState]) + end; + [_Arg] -> + NewBinType = match_bin(erl_types:t_bitstr(Size, 0), BinType), + NewMatchState = + erl_types:t_matchstate_update_present(NewBinType, MatchState), + erl_types:t_product([erl_types:t_integer(), NewMatchState]) + end; + {hipe_bs_primop, {bs_get_float, Size, _Flags}} -> + [MatchState|RestArgs] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + NewBinType = + case RestArgs of + [] -> + match_bin(erl_types:t_bitstr(0,Size),BinType); + [_Arg] -> + erl_types:t_sup(match_bin(erl_types:t_bitstr(0, 32), BinType), + match_bin(erl_types:t_bitstr(0, 64), BinType)) + end, + NewMatchState = erl_types:t_matchstate_update_present(NewBinType, MatchState), + erl_types:t_product([erl_types:t_float(), NewMatchState]); + {hipe_bs_primop, {bs_get_binary, Size, _Flags}} -> + [MatchState|RestArgs] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + case RestArgs of + [] -> + NewBinType = match_bin(erl_types:t_bitstr(0, Size), BinType), + NewMatchState = erl_types:t_matchstate_update_present(NewBinType, MatchState), + erl_types:t_product([erl_types:t_bitstr(0,Size), NewMatchState]); + [ArgType] -> + Posint = erl_types:t_inf(erl_types:t_non_neg_integer(), ArgType), + case erl_types:t_is_none(Posint) of + true -> + erl_types:t_product([erl_types:t_none(), + erl_types:t_matchstate_update_present( + erl_types:t_none(), + MatchState)]); + false -> + OutBinType = + erl_types:t_bitstr(Size,erl_types:number_min(Posint)*Size), + NewBinType = match_bin(OutBinType,BinType), + NewMatchState = erl_types:t_matchstate_update_present(NewBinType, MatchState), + erl_types:t_product([OutBinType, NewMatchState]) + end + end; + {hipe_bs_primop, {bs_get_binary_all, Unit, _Flags}} -> + [MatchState] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + erl_types:t_inf(BinType, erl_types:t_bitstr(Unit, 0)); + {hipe_bs_primop, {bs_get_binary_all_2, Unit, _Flags}} -> + [MatchState] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + erl_types:t_product( + [erl_types:t_inf(BinType,erl_types:t_bitstr(Unit, 0)), + erl_types:t_matchstate_update_present( + erl_types:t_bitstr(0, 0), MatchState)]); + {hipe_bs_primop, {bs_skip_bits_all, _Unit, _Flags}} -> + [MatchState] = Args, + erl_types:t_matchstate_update_present(erl_types:t_bitstr(0,0),MatchState); + {hipe_bs_primop, {bs_skip_bits, Size}} -> + [MatchState|RestArgs] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + NewBinType = + case RestArgs of + [] -> + match_bin(erl_types:t_bitstr(0, Size), BinType); + [_Arg] -> + match_bin(erl_types:t_bitstr(Size, 0), BinType) + end, + erl_types:t_matchstate_update_present(NewBinType, MatchState); + {hipe_bs_primop, {bs_save, Slot}} -> + [MatchState] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + erl_types:t_matchstate_update_slot(BinType, MatchState, Slot); + {hipe_bs_primop, {bs_restore, Slot}} -> + [MatchState] = Args, + BinType = erl_types:t_matchstate_slot(MatchState, Slot), + erl_types:t_matchstate_update_present(BinType, MatchState); + {hipe_bs_primop, bs_context_to_binary} -> + [Type] = Args, + erl_types:t_sup( + erl_types:t_subtract(Type, erl_types:t_matchstate()), + erl_types:t_matchstate_slot( + erl_types:t_inf(Type, erl_types:t_matchstate()), 0)); + {hipe_bs_primop, {bs_match_string,_,Bytes}} -> + [MatchState] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + NewBinType = match_bin(erl_types:t_bitstr(0, Bytes*8), BinType), + erl_types:t_matchstate_update_present(NewBinType, MatchState); + {hipe_bs_primop, {bs_test_unit,Unit}} -> + [MatchState] = Args, + BinType = erl_types:t_matchstate_present(MatchState), + NewBinType = erl_types:t_inf(erl_types:t_bitstr(Unit, 0), BinType), + erl_types:t_matchstate_update_present(NewBinType, MatchState); + {hipe_bs_primop, {bs_add, _, _}} -> + erl_types:t_integer(); + {hipe_bs_primop, {bs_add, _}} -> + erl_types:t_integer(); + {hipe_bs_primop, bs_bits_to_bytes} -> + erl_types:t_integer(); + {hipe_bs_primop, bs_bits_to_bytes2} -> + erl_types:t_integer(); + {hipe_bs_primop, {Name, Size, _Flags, _ConstInfo}} + when Name =:= bs_put_integer; + Name =:= bs_put_float -> + case Args of + [_SrcType, _Base, Type] -> + erl_types:t_bitstr_concat(Type, erl_types:t_bitstr(0, Size)); + [_SrcType,_BitsType, _Base, Type] -> + erl_types:t_bitstr_concat(Type, erl_types:t_bitstr(Size, 0)) + end; + {hipe_bs_primop, {bs_put_binary, Size, _Flags}} -> + case Args of + [_SrcType, _Base, Type] -> + erl_types:t_bitstr_concat(Type, erl_types:t_bitstr(0, Size)); + [_SrcType, _BitsType, _Base, Type] -> + erl_types:t_bitstr_concat(Type, erl_types:t_bitstr(Size, 0)) + end; + {hipe_bs_primop, {bs_put_binary_all, _Flags}} -> + [SrcType, _Base, Type] = Args, + erl_types:t_bitstr_concat(SrcType,Type); + {hipe_bs_primop, {bs_put_string, _, Size}} -> + [_Base, Type] = Args, + erl_types:t_bitstr_concat(Type, erl_types:t_bitstr(0, 8*Size)); + {hipe_bs_primop, bs_utf8_size} -> + [_Arg] = Args, + erl_types:t_from_range(1, 4); + {hipe_bs_primop, bs_utf16_size} -> + [_Arg] = Args, + erl_types:t_from_range(2, 4); % XXX: really 2 | 4 + {hipe_bs_primop, bs_final} -> + [_Base, Type] = Args, + Type; + {hipe_bs_primop, {bs_init, Size, _Flags}} -> + erl_types:t_product( + [erl_types:t_bitstr(0, Size*8), + erl_types:t_any(), + erl_types:t_bitstr(0, 0)]); + {hipe_bs_primop, {bs_init, _Flags}} -> + erl_types:t_product( + [erl_types:t_binary(), + erl_types:t_any(), + erl_types:t_bitstr(0, 0)]); + {hipe_bs_primop, {bs_init_bits, Size, _Flags}} -> + erl_types:t_product( + [erl_types:t_bitstr(0, Size), + erl_types:t_any(), + erl_types:t_bitstr(0, 0)]); + {hipe_bs_primop, {bs_init_bits, _Flags}} -> + erl_types:t_product( + [erl_types:t_bitstr(), + erl_types:t_any(), + erl_types:t_bitstr(0, 0)]); + {hipe_bs_primop, {bs_private_append, _U, _Flags}} -> + erl_types:t_product( + [erl_types:t_bitstr(), + erl_types:t_any(), + erl_types:t_bitstr()]); + {hipe_bs_primop, {bs_append, _W, _R, _U, _Flags}} -> + erl_types:t_product( + [erl_types:t_bitstr(), + erl_types:t_any(), + erl_types:t_bitstr()]); + {hipe_bs_primop, bs_init_writable} -> + erl_types:t_bitstr(0, 0); + {hipe_bs_primop, _BsOp} -> + erl_types:t_any(); +%%% ----------------------------------------------------- +%%% Funs + #mkfun{mfa = {_M, _F, A}} -> + %% Note that the arity includes the bound variables in args + erl_types:t_fun(A - length(Args), erl_types:t_any()); + #apply_N{} -> + erl_types:t_any(); + Op when Op =:= call_fun orelse Op =:= enter_fun -> + [Fun0|TailArgs0] = lists:reverse(Args), + TailArgs = lists:reverse(TailArgs0), + Fun = erl_types:t_inf(erl_types:t_fun(), Fun0), + case erl_types:t_is_fun(Fun) of + true -> + case erl_types:t_fun_args(Fun) of + unknown -> + erl_types:t_any(); + FunArgs -> + case check_fun_args(FunArgs, TailArgs) of + ok -> + erl_types:t_fun_range(Fun); + error -> + erl_types:t_none() + end + end; + false -> + erl_types:t_none() + end; +%%% ----------------------------------------------------- +%%% Communication + check_get_msg -> + erl_types:t_any(); + clear_timeout -> + erl_types:t_any(); + next_msg -> + erl_types:t_any(); + select_msg -> + erl_types:t_any(); + set_timeout -> + erl_types:t_any(); + suspend_msg -> + erl_types:t_any(); +%%% ----------------------------------------------------- +%%% Other + #closure_element{} -> + erl_types:t_any(); + redtest -> + erl_types:t_any(); + {M, F, A} -> + erl_bif_types:type(M, F, A, Args) + end. + + +-spec type(icode_funcall()) -> erl_types:erl_type(). + +type(Primop) -> + case Primop of +%%% ----------------------------------------------------- +%%% Arithops + 'bnot' -> + erl_bif_types:type(erlang, 'bnot', 1); + '+' -> + erl_bif_types:type(erlang, '+', 2); + '-' -> + erl_bif_types:type(erlang, '-', 2); + '*' -> + erl_bif_types:type(erlang, '*', 2); + '/' -> + erl_bif_types:type(erlang, '/', 2); + 'div' -> + erl_bif_types:type(erlang, 'div', 2); + 'rem' -> + erl_bif_types:type(erlang, 'rem', 2); + 'band' -> + erl_bif_types:type(erlang, 'band', 2); + 'bor' -> + erl_bif_types:type(erlang, 'bor', 2); + 'bxor' -> + erl_bif_types:type(erlang, 'bxor', 2); + 'bsr' -> + erl_bif_types:type(erlang, 'bsr', 2); + 'bsl' -> + erl_bif_types:type(erlang, 'bsl', 2); + unsafe_add -> + erl_bif_types:type(erlang, '+', 2); + extra_unsafe_add -> + erl_bif_types:type(erlang, '+', 2); + unsafe_sub -> + erl_bif_types:type(erlang, '-', 2); + unsafe_bor -> + erl_bif_types:type(erlang, 'bor', 2); + unsafe_band -> + erl_bif_types:type(erlang, 'band', 2); + unsafe_bxor -> + erl_bif_types:type(erlang, 'bxor', 2); +%%% ----------------------------------------------------- +%%% Lists + cons -> + erl_types:t_cons(); + unsafe_hd -> + erl_bif_types:type(erlang, hd, 1); + unsafe_tl -> + erl_bif_types:type(erlang, tl, 1); +%%% ----------------------------------------------------- +%%% Tuples + mktuple -> + erl_types:t_tuple(); + #element{} -> + erl_bif_types:type(erlang, element, 2); + #unsafe_element{} -> + erl_bif_types:type(erlang, element, 2); + #unsafe_update_element{} -> + erl_bif_types:type(erlang, setelement, 3); +%%% ----------------------------------------------------- +%%% Floats + fclearerror -> + erl_types:t_any(); + fcheckerror -> + erl_types:t_any(); + unsafe_tag_float -> + erl_types:t_float(); + %% These might look surprising, but the return is an untagged + %% float and we have no type for untagged values. + conv_to_float -> + erl_types:t_any(); + unsafe_untag_float -> + erl_types:t_any(); + fp_add -> + erl_types:t_any(); + fp_sub -> + erl_types:t_any(); + fp_mul -> + erl_types:t_any(); + fp_div -> + erl_types:t_any(); + fnegate -> + erl_types:t_any(); +%%% ----------------------------------------------------- +%%% Binaries + {hipe_bs_primop, bs_get_utf8} -> + erl_types:t_product([erl_types:t_integer(), erl_types:t_matchstate()]); + {hipe_bs_primop, {bs_get_utf16, _Flags}} -> + erl_types:t_product([erl_types:t_integer(), erl_types:t_matchstate()]); + {hipe_bs_primop, {bs_get_integer, _Size, _Flags}} -> + erl_types:t_product([erl_types:t_integer(), erl_types:t_matchstate()]); + {hipe_bs_primop, {bs_get_float, _, _}} -> + erl_types:t_product([erl_types:t_float(), erl_types:t_matchstate()]); + {hipe_bs_primop, {bs_get_binary, _, _}} -> + erl_types:t_product([erl_types:t_bitstr(), erl_types:t_matchstate()]); + {hipe_bs_primop, {bs_get_binary_all, _, _}} -> + erl_types:t_bitstr(); + {hipe_bs_primop, {bs_get_binary_all_2, _, _}} -> + erl_types:t_product([erl_types:t_bitstr(), erl_types:t_matchstate()]); + {hipe_bs_primop, bs_final} -> + erl_types:t_bitstr(); + {hipe_bs_primop, {bs_init, _, _}} -> + erl_types:t_product([erl_types:t_binary(), erl_types:t_bitstr(), + erl_types:t_bitstr()]); + {hipe_bs_primop, {bs_init, _}} -> + erl_types:t_product([erl_types:t_binary(), erl_types:t_bitstr(), + erl_types:t_bitstr()]); + {hipe_bs_primop, {bs_init_bits, Size, _}} -> + erl_types:t_product([erl_types:t_bitstr(0, Size), erl_types:t_bitstr(), + erl_types:t_bitstr()]); + {hipe_bs_primop, {bs_init_bits, _}} -> + erl_types:t_product([erl_types:t_bitstr(), erl_types:t_bitstr(), + erl_types:t_bitstr()]); + {hipe_bs_primop, {bs_add, _, _}} -> + erl_types:t_integer(); + {hipe_bs_primop, {bs_add, _}} -> + erl_types:t_integer(); + {hipe_bs_primop, bs_bits_to_bytes} -> + erl_types:t_integer(); + {hipe_bs_primop, bs_bits_to_bytes2} -> + erl_types:t_integer(); + {hipe_bs_primop, {bs_private_append, _U, _Flags}} -> + erl_types:t_product( + [erl_types:t_bitstr(), + erl_types:t_any(), + erl_types:t_bitstr()]); + {hipe_bs_primop, {bs_append, _W, _R, _U, _Flags}} -> + erl_types:t_product( + [erl_types:t_bitstr(), + erl_types:t_any(), + erl_types:t_bitstr()]); + {hipe_bs_primop, bs_init_writable} -> + erl_types:t_bitstr(); + {hipe_bs_primop, _BsOp} -> + erl_types:t_any(); +%%% ----------------------------------------------------- +%%% Funs + #mkfun{} -> + %% Note that the arity includes the bound variables in args + erl_types:t_fun(); + #apply_N{} -> + erl_types:t_any(); + call_fun -> + erl_types:t_any(); + enter_fun -> + erl_types:t_any(); +%%% ----------------------------------------------------- +%%% Communication + check_get_msg -> + erl_types:t_any(); + clear_timeout -> + erl_types:t_any(); + next_msg -> + erl_types:t_any(); + select_msg -> + erl_types:t_any(); + set_timeout -> + erl_types:t_any(); + suspend_msg -> + erl_types:t_any(); +%%% ----------------------------------------------------- +%%% Other + #closure_element{} -> + erl_types:t_any(); + redtest -> + erl_types:t_any(); + {M, F, A} -> + erl_bif_types:type(M, F, A) + end. + + +%% ===================================================================== +%% @doc +%% function arg_types returns a list of the demanded argument types for +%% a bif to succeed. + +-spec arg_types(icode_funcall()) -> [erl_types:erl_type()] | 'unknown'. + +arg_types(Primop) -> + case Primop of + {M, F, A} -> + erl_bif_types:arg_types(M, F, A); + #element{} -> + [erl_types:t_pos_fixnum(), erl_types:t_tuple()]; + '+' -> + erl_bif_types:arg_types(erlang, '+', 2); + '-' -> + erl_bif_types:arg_types(erlang, '-', 2); + '*' -> + erl_bif_types:arg_types(erlang, '*', 2); + '/' -> + erl_bif_types:arg_types(erlang, '/', 2); + 'band' -> + erl_bif_types:arg_types(erlang, 'band', 2); + 'bnot' -> + erl_bif_types:arg_types(erlang, 'bnot', 1); + 'bor' -> + erl_bif_types:arg_types(erlang, 'bor', 2); + 'bxor' -> + erl_bif_types:arg_types(erlang, 'bxor', 2); + 'bsl' -> + erl_bif_types:arg_types(erlang, 'bsl', 2); + 'bsr' -> + erl_bif_types:arg_types(erlang, 'bsr', 2); + 'div' -> + erl_bif_types:arg_types(erlang, 'div', 2); + 'rem' -> + erl_bif_types:arg_types(erlang, 'rem', 2); + _ -> + unknown % safe approximation for all primops. + end. + +%%===================================================================== +%% Auxiliary functions +%%===================================================================== + +check_fun_args([T1|Left1], [T2|Left2]) -> + Inf = erl_types:t_inf(T1, T2), + case erl_types:t_inf(Inf, T2) of + Inf -> + check_fun_args(Left1, Left2); + _ -> + error + end; +check_fun_args([], []) -> + ok; +check_fun_args(_, _) -> + error. + +match_bin(Pattern, Match) -> + erl_types:t_bitstr_match(Pattern, Match). diff --git a/lib/hipe/icode/hipe_icode_primops.hrl b/lib/hipe/icode/hipe_icode_primops.hrl new file mode 100644 index 0000000000..8a65c5ece0 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_primops.hrl @@ -0,0 +1,40 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%======================================================================= +%% File : hipe_icode_primops.hrl +%% Author : Kostis Sagonas +%% Description : Contains definitions for HiPE's primitive operations. +%%======================================================================= +%% $Id$ +%%======================================================================= + +-record(apply_N, {arity :: arity()}). + +-record(closure_element, {n :: arity()}). + +-record(element, {typeinfo :: list()}). %% XXX: refine? + +-record(gc_test, {need :: non_neg_integer()}). + +-record(mkfun, {mfa :: mfa(), magic_num :: integer(), index :: integer()}). + +-record(unsafe_element, {index :: non_neg_integer()}). + +-record(unsafe_update_element, {index :: non_neg_integer()}). diff --git a/lib/hipe/icode/hipe_icode_range.erl b/lib/hipe/icode/hipe_icode_range.erl new file mode 100644 index 0000000000..bcc857acf4 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_range.erl @@ -0,0 +1,1966 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%------------------------------------------------------------------- +%%% File : hipe_icode_range.erl +%%% Author : Per Gustafsson <[email protected]> +%%% Description : +%%% +%%% Created : 12 Mar 2007 by Per Gustafsson <[email protected]> +%%%------------------------------------------------------------------- +-module(hipe_icode_range). + +-export([cfg/4]). + +%%===================================================================== +%% Icode Coordinator Behaviour Callbacks +%%===================================================================== + +-export([replace_nones/1, + update__info/2, new__info/1, return__info/1, + return_none/0, return_none_args/2, return_any_args/2]). + +%%===================================================================== + +-import(erl_types, [t_any/0, + t_from_range_unsafe/2, + t_inf/2, t_integer/0, + t_to_string/1, t_to_tlist/1, + t_limit/2, t_none/0, + number_min/1, number_max/1]). + +-include("hipe_icode.hrl"). +-include("hipe_icode_primops.hrl"). +-include("../main/hipe.hrl"). +-include("../flow/cfg.hrl"). +-include("../flow/hipe_bb.hrl"). +-include("hipe_icode_type.hrl"). + +-type range_tuple() :: {'neg_inf' | integer(), 'pos_inf' | integer()}. +-type range_rep() :: range_tuple() | 'empty'. +-type fun_name() :: atom() | tuple(). +-type inf_integer() :: 'neg_inf' | 'pos_inf' | integer(). + +-record(range, {range :: range_rep(), + other :: boolean()}). + +-record(ann, {range :: #range{}, + type :: erl_types:erl_type(), + count :: integer()}). + +-type range_anno() :: {range_anno, #ann{}, fun((#ann{}) -> string())}. +-type args_fun() :: fun((mfa(),cfg()) -> [#range{}]). +-type call_fun() :: fun((mfa(),[#range{}]) -> #range{}). +-type final_fun() :: fun((mfa(),[#range{}]) -> ok). +-type data() :: {mfa(), args_fun(), call_fun(), final_fun()}. +-type label() :: non_neg_integer(). +-type info() :: gb_tree(). +-type work_list() :: {[label()], [label()], set()}. +-type variable() :: #icode_variable{}. +-type annotated_variable() :: #icode_variable{}. +-type argument() :: #icode_const{} | variable(). +-type three_range_fun() :: fun((#range{},#range{},#range{}) -> #range{}). +-type instr_split_info() :: {icode_instr(), [{label(),info()}]}. +-type last_instr_return() :: {instr_split_info(), #range{}}. + +-record(state, {info_map = gb_trees:empty() :: info(), + counter = dict:new() :: dict(), + cfg :: cfg(), + liveness = gb_trees:empty() :: gb_tree(), + ret_type :: #range{}, + lookup_fun :: call_fun(), + result_action :: final_fun()}). + +-define(WIDEN, 1). + +-define(TAG_IMMED1_SIZE, 4). + +-define(BITS, 64). + +%%--------------------------------------------------------------------- + +-spec cfg(cfg(), mfa(), comp_options(), #comp_servers{}) -> cfg(). + +cfg(Cfg, MFA, Options, Servers) -> + case proplists:get_bool(concurrent_comp, Options) of + true -> + concurrent_cfg(Cfg, MFA, Servers#comp_servers.range); + false -> + ordinary_cfg(Cfg, MFA) + end. + +-spec concurrent_cfg(cfg(), mfa(), pid()) -> cfg(). + +concurrent_cfg(Cfg, MFA, CompServer) -> + CompServer ! {ready, {MFA,self()}}, + {ArgsFun,CallFun,FinalFun} = do_analysis(Cfg, MFA), + Ans = do_rewrite(Cfg, MFA, ArgsFun, CallFun, FinalFun), + CompServer ! {done_rewrite, MFA}, + Ans. + +-spec do_analysis(cfg(), mfa()) -> {args_fun(), call_fun(), final_fun()}. + +do_analysis(Cfg, MFA) -> + receive + {analyse, {ArgsFun, CallFun, FinalFun}} -> + analyse(Cfg, {MFA, ArgsFun, CallFun, FinalFun}), + do_analysis(Cfg, MFA); + {done, {_NewArgsFun, _NewCallFun, _NewFinalFun} = T} -> + T + end. + +-spec do_rewrite(cfg(), mfa(), args_fun(), call_fun(), final_fun()) -> cfg(). + +do_rewrite(Cfg, MFA, ArgsFun, CallFun, FinalFun) -> + common_rewrite(Cfg, {MFA, ArgsFun, CallFun, FinalFun}). + +-spec ordinary_cfg(cfg(), mfa()) -> cfg(). + +ordinary_cfg(Cfg, MFA) -> + Data = make_data(Cfg,MFA), + common_rewrite(Cfg, Data). + +-spec common_rewrite(cfg(), data()) -> cfg(). + +common_rewrite(Cfg, Data) -> + State = safe_analyse(Cfg, Data), + State2 = rewrite_blocks(State), + Cfg1 = state__cfg(State2), + Cfg2 = hipe_icode_cfg:remove_unreachable_code(Cfg1), + Cfg3 = convert_cfg_to_types(Cfg2), + hipe_icode_type:specialize(Cfg3). + +-spec make_data(cfg(), mfa()) -> data(). + +make_data(Cfg, {_M,_F,A}=MFA) -> + NoArgs = + case hipe_icode_cfg:is_closure(Cfg) of + true -> hipe_icode_cfg:closure_arity(Cfg)+1; + false -> A + end, + Args = lists:duplicate(NoArgs, any_type()), + ArgsFun = fun(_,_) -> Args end, + CallFun = fun(_,_) -> any_type() end, + FinalFun = fun(_,_) -> ok end, + {MFA, ArgsFun, CallFun, FinalFun}. + +-spec analyse(cfg(), data()) -> 'ok'. + +analyse(Cfg, Data) -> + try + #state{} = safe_analyse(Cfg, Data), + ok + catch throw:no_input -> ok + end. + +-spec safe_analyse(cfg(), data()) -> #state{}. + +safe_analyse(CFG, Data={MFA,_,_,_}) -> + State = state__init(CFG, Data), + Work = init_work(State), + NewState = analyse_blocks(State, Work), + (state__result_action(NewState))(MFA, [state__ret_type(NewState)]), + NewState. + +-spec rewrite_blocks(#state{}) -> #state{}. + +rewrite_blocks(State) -> + CFG = state__cfg(State), + Start = hipe_icode_cfg:start_label(CFG), + rewrite_blocks([Start], State, [Start]). + +-spec rewrite_blocks([label()], #state{}, [label()]) -> #state{}. + +rewrite_blocks([Next|Rest], State, Visited) -> + Info = state__info_in(State, Next), + {NewState, NewLabels} = analyse_block(Next, Info, State, true), + NewLabelsSet = ordsets:from_list(NewLabels), + RealNew = ordsets:subtract(NewLabelsSet, Visited), + NewVisited = ordsets:union([RealNew, Visited, [Next]]), + NewWork = ordsets:union([RealNew, Rest]), + rewrite_blocks(NewWork, NewState, NewVisited); +rewrite_blocks([], State, _) -> + State. + +-spec analyse_blocks(#state{}, work_list()) -> #state{}. + +analyse_blocks(State, Work) -> + case get_work(Work) of + fixpoint -> + State; + {Label, NewWork} -> + Info = state__info_in(State, Label), + {NewState, NewLabels} = + try analyse_block(Label, Info, State, false) + catch throw:none_range -> + {State, []} + end, + NewWork2 = add_work(NewWork, NewLabels), + analyse_blocks(NewState, NewWork2) + end. + +-spec analyse_block(label(), info(), #state{}, boolean()) -> {#state{}, [label()]}. + +analyse_block(Label, Info, State, Rewrite) -> + BB = state__bb(State, Label), + Code = hipe_bb:code(BB), + {NewCode, InfoList, RetType} = + analyse_BB(Code, Info, [], Rewrite, state__lookup_fun(State)), + State1 = state__bb_add(State, Label, hipe_bb:mk_bb(NewCode)), + State2 = state__ret_type_update(State1, RetType), + state__update_info(State2, InfoList, Rewrite). + +-spec analyse_BB([icode_instr()], info(), [icode_instr()], boolean(), call_fun()) -> + {[icode_instr()], [{label(),info()}], #range{}}. + +analyse_BB([Last], Info, Code, Rewrite, LookupFun) -> + {{NewI, LabelInfoList}, RetType} = + analyse_last_insn(Last, Info, Rewrite, LookupFun), + {lists:reverse([NewI|Code]), LabelInfoList, RetType}; +analyse_BB([Insn|InsnList], Info, Code, Rewrite, LookupFun) -> + {NewInfo, NewI} = analyse_insn(Insn, Info, LookupFun), + analyse_BB(InsnList, NewInfo, [NewI|Code], Rewrite, LookupFun). + +-spec analyse_insn(icode_instr(), info(), call_fun()) -> {info(), icode_instr()}. + +analyse_insn(I, Info, LookupFun) -> + %% io:format("~w Info: ~p~n", [I, Info]), + NewI = handle_args(I,Info), + FinalI = + case NewI of + #icode_call{} -> analyse_call(NewI, LookupFun); + #icode_move{} -> analyse_move(NewI); + #icode_phi{} -> analyse_phi(NewI); + #icode_begin_handler{} -> analyse_begin_handler(NewI); + #icode_comment{} -> NewI + end, + {enter_vals(FinalI, Info), FinalI}. + +-spec handle_args(icode_instr(), info()) -> icode_instr(). + +handle_args(I, Info) -> + WidenFun = fun update_three/3, + handle_args(I, Info, WidenFun). + +-spec handle_args(icode_instr(), info(), three_range_fun()) -> icode_instr(). + +handle_args(I, Info, WidenFun) -> + Uses = hipe_icode:uses(I), + PresentRanges = [lookup(V, Info) || V <- Uses], + %% io:format("Uses: ~p~nRanges: ~p~n", [Uses, PresentRanges]), + JoinFun = fun(Var, Range) -> update_info(Var, Range, WidenFun) end, + NewUses = lists:zipwith(JoinFun, Uses, PresentRanges), + hipe_icode:subst_uses(lists:zip(Uses, NewUses),I). + +-spec join_info(#ann{}, #range{}, three_range_fun()) -> #ann{}. + +join_info(Ann = #ann{range = R1, type = Type, count = ?WIDEN}, R2, Fun) -> + Ann#ann{range = Fun(R1, R2, range_from_simple_type(Type))}; +join_info(Ann = #ann{range = R1, type = Type, count = C}, R2, _Fun) when C < ?WIDEN -> + case join_three(R1, R2, range_from_simple_type(Type)) of + R1 -> Ann; + NewR -> Ann#ann{range = NewR, count = C+1} + end. + +-spec join_three(#range{}, #range{}, #range{}) -> #range{}. + +join_three(R1, R2, R3) -> + inf(sup(R1, R2), R3). + +-spec update_info(variable(), #range{}) -> annotated_variable(). + +update_info(Var, Range) -> + update_info(Var, Range, fun update_three/3). + +-spec update_info(variable(), #range{}, three_range_fun()) -> annotated_variable(). + +update_info(Arg, R, Fun) -> + case hipe_icode:is_annotated_variable(Arg) of + true -> + Ann = hipe_icode:variable_annotation(Arg), + hipe_icode:annotate_variable(Arg, update_info1(Ann, R, Fun)); + false -> + Arg + end. + +-spec update_info1(any(), #range{}, three_range_fun()) -> range_anno(). + +update_info1({range_anno, Ann, _}, R2, Fun) -> + make_range_anno(update_ann(Ann,R2,Fun)); +update_info1({type_anno, Type, _}, R2, Fun) -> + make_range_anno(update_ann(type_to_ann(Type), R2, Fun)). + +update_ann(Ann = #ann{range = R1, type = Type, count = ?WIDEN}, R2, Fun) -> + Ann#ann{range = Fun(R1,R2,range_from_simple_type(Type))}; +update_ann(Ann = #ann{range = R1, type = Type, count = C}, R2, _Fun) -> + case update_three(R1, R2, range_from_simple_type(Type)) of + R1 -> Ann; + NewR -> Ann#ann{range = NewR, count = C+1} + end. + +-spec type_to_ann(erl_types:erl_type()) -> #ann{}. + +type_to_ann(Type) -> + #ann{range = range_from_simple_type(Type), type = t_limit(Type,1), count=1}. + +-spec make_range_anno(#ann{}) -> range_anno(). + +make_range_anno(Ann) -> + {range_anno, Ann, fun pp_ann/1}. + +-spec update_three(#range{}, #range{}, #range{}) -> #range{}. + +update_three(_R1, R2, R3) -> + inf(R2, R3). + +-spec safe_widen(#range{}, #range{}, #range{}) -> #range{}. + +safe_widen(#range{range=Old}, #range{range=New}, T = #range{range=Wide}) -> + ResRange = + case {Old,New,Wide} of + {{Min,Max1},{Min,Max2},{_,Max}} -> + case inf_geq(OMax = next_up_limit(inf_max([Max1,Max2])),Max) of + true -> {Min,Max}; + false -> {Min,OMax} + end; + {{Min1,Max},{Min2,Max},{Min,_}} -> + case inf_geq(Min, OMin = next_down_limit(inf_min([Min1,Min2]))) of + true -> {Min,Max}; + false -> {OMin,Max} + end; + {{Min1,Max1},{Min2,Max2},{Min,Max}} -> + RealMax = + case inf_geq(OMax = next_up_limit(inf_max([Max1,Max2])),Max) of + true -> Max; + false -> OMax + end, + RealMin = + case inf_geq(Min, OMin = next_down_limit(inf_min([Min1,Min2]))) of + true -> Min; + false -> OMin + end, + {RealMin,RealMax}; + _ -> + Wide + end, + T#range{range=ResRange}. + +-spec widen(#range{}, #range{}, #range{}) -> #range{}. + +widen(#range{range=Old}, #range{range=New}, T = #range{range=Wide}) -> + ResRange = + case {Old,New,Wide} of + {{Min,_},{Min,Max2},{_,Max}} -> + case inf_geq(OMax = next_up_limit(Max2),Max) of + true -> {Min,Max}; + false -> {Min,OMax} + end; + {{_,Max},{Min2,Max},{Min,_}} -> + case inf_geq(Min, OMin = next_down_limit(Min2)) of + true -> {Min,Max}; + false -> {OMin,Max} + end; + {_,{Min2,Max2},{Min,Max}} -> + RealMax = + case inf_geq(OMax = next_up_limit(Max2),Max) of + true -> Max; + false -> OMax + end, + RealMin = + case inf_geq(Min, OMin = next_down_limit(Min2)) of + true -> Min; + false -> OMin + end, + {RealMin,RealMax}; + _ -> + Wide + end, + T#range{range=ResRange}. + +-spec analyse_call(#icode_call{}, call_fun()) -> #icode_call{}. + +analyse_call(Call, LookupFun) -> + case hipe_icode:call_dstlist(Call) of + [] -> + Call; + Dsts -> + Args = hipe_icode:args(Call), + Fun = hipe_icode:call_fun(Call), + Type = hipe_icode:call_type(Call), + DstRanges = analyse_call_or_enter_fun(Fun, Args, Type, LookupFun), + NewDefs = [update_info(Var, R) || {Var,R} <- lists:zip(Dsts, DstRanges)], + hipe_icode:subst_defines(lists:zip(Dsts, NewDefs), Call) + end. + +-spec analyse_move(#icode_move{}) -> #icode_move{}. + +analyse_move(Move) -> + Src = hipe_icode:move_src(Move), + Dst = hipe_icode:move_dst(Move), + Range = get_range_from_arg(Src), + NewDst = update_info(Dst, Range), + hipe_icode:subst_defines([{Dst,NewDst}], Move). + +-spec analyse_begin_handler(#icode_begin_handler{}) -> #icode_begin_handler{}. + +analyse_begin_handler(Handler) -> + SubstList = + [{Dst,update_info(Dst,any_type())} || + Dst <- hipe_icode:begin_handler_dstlist(Handler)], + hipe_icode:subst_defines(SubstList, Handler). + +-spec analyse_phi(#icode_phi{}) -> #icode_phi{}. + +analyse_phi(Phi) -> + {_, Args} = lists:unzip(hipe_icode:phi_arglist(Phi)), + Dst = hipe_icode:phi_dst(Phi), + ArgRanges = get_range_from_args(Args), + %% io:format("Phi-Arg_ranges: ~p ~n", [Arg_ranges]), + DstRange = sup(ArgRanges), + NewDst = update_info(Dst, DstRange, fun widen/3), + hipe_icode:subst_defines([{Dst, NewDst}], Phi). + +-spec analyse_last_insn(icode_instr(), info(), boolean(), call_fun()) -> + last_instr_return(). + +analyse_last_insn(I, Info, Rewrite, LookupFun) -> + %% io:format("~w Info: ~p~n",[I,Info]), + NewI = handle_args(I, Info), + %% io:format("~w -> ~w~n",[NewI,I]), + case NewI of + #icode_return{} -> analyse_return(NewI, Info); + #icode_enter{} -> analyse_enter(NewI, Info, LookupFun); + #icode_switch_val{} -> + {analyse_switch_val(NewI, Info, Rewrite), none_type()}; + #icode_if{} -> {analyse_if(NewI, Info, Rewrite), none_type()}; + #icode_goto{} -> {analyse_goto(NewI, Info), none_type()}; + #icode_type{} -> {analyse_type(NewI, Info, Rewrite), none_type()}; + #icode_fail{} -> {analyse_fail(NewI, Info), none_type()}; + #icode_call{} -> {analyse_last_call(NewI, Info, LookupFun), none_type()}; + #icode_switch_tuple_arity{} -> + {analyse_switch_tuple_arity(NewI, Info), none_type()}; + #icode_begin_try{} -> {analyse_begin_try(NewI, Info), none_type()} + end. + +-spec analyse_return(#icode_return{}, info()) -> last_instr_return(). + +analyse_return(Insn, _Info) -> + [RetRange] = get_range_from_args(hipe_icode:return_vars(Insn)), + {{Insn,[]}, RetRange}. + +-spec analyse_enter(#icode_enter{}, info(), call_fun()) -> last_instr_return(). + +analyse_enter(Insn, _Info, LookupFun) -> + Args = hipe_icode:args(Insn), + Fun = hipe_icode:enter_fun(Insn), + CallType = hipe_icode:enter_type(Insn), + [RetRange] = analyse_call_or_enter_fun(Fun, Args, CallType, LookupFun), + {{Insn,[]}, RetRange}. + +-spec analyse_switch_val(#icode_switch_val{}, info(), boolean()) -> instr_split_info(). + +analyse_switch_val(Switch, Info, Rewrite) -> + Var = hipe_icode:switch_val_term(Switch), + SwitchRange = get_range_from_arg(Var), + Cases = hipe_icode:switch_val_cases(Switch), + {FailRange, LabelRangeList} = get_range_label_list(Cases, SwitchRange, []), + case range__is_none(FailRange) of + true -> + InfoList = update_infos(Var, Info, LabelRangeList), + if Rewrite -> {update_switch(Switch, LabelRangeList, false), InfoList}; + true -> {Switch, InfoList} + end; + false -> + FailLabel = hipe_icode:switch_val_fail_label(Switch), + InfoList = update_infos(Var, Info, [{FailRange, FailLabel}|LabelRangeList]), + if Rewrite -> {update_switch(Switch, LabelRangeList, true), InfoList}; + true -> {Switch, InfoList} + end + end. + +-spec update_infos(argument(), info(), [{#range{},label()}]) -> [{label(),info()}]. + +update_infos(Arg, Info, [{Range, Label}|Rest]) -> + [{Label,enter_define({Arg,Range},Info)} | update_infos(Arg,Info,Rest)]; +update_infos(_, _, []) -> []. + +-spec get_range_label_list([{argument(),label()}], #range{}, [{#range{},label()}]) -> + {#range{},[{#range{},label()}]}. + +get_range_label_list([{Val,Label}|Cases], SRange, Acc) -> + VRange = get_range_from_arg(Val), + None = none_type(), + case inf(SRange, VRange) of + None -> + get_range_label_list(Cases, SRange, Acc); + ResRange -> + get_range_label_list(Cases, SRange, [{ResRange,Label}|Acc]) + end; +get_range_label_list([], SRange, Acc) -> + {PointTypes, _} = lists:unzip(Acc), + {remove_point_types(SRange, PointTypes), Acc}. + +-spec update_switch(#icode_switch_val{}, [{#range{},label()}], boolean()) -> + #icode_switch_val{}. + +update_switch(Switch, LabelRangeList, KeepFail) -> + S2 = + case label_range_list_to_cases(LabelRangeList, []) of + no_update -> + Switch; + Cases -> + hipe_icode:switch_val_cases_update(Switch, Cases) + end, + if KeepFail -> S2; + true -> S2 + end. + +-spec label_range_list_to_cases([{#range{},label()}], [{#icode_const{},label()}]) -> + 'no_update' | [{#icode_const{},label()}]. + +label_range_list_to_cases([{#range{range={C,C},other=false},Label}|Rest], + Acc) when is_integer(C) -> + label_range_list_to_cases(Rest, [{hipe_icode:mk_const(C),Label}|Acc]); +label_range_list_to_cases([{_NotAConstantRange,_Label}|_Rest], _Acc) -> + no_update; +label_range_list_to_cases([], Acc) -> + lists:reverse(Acc). + +-spec analyse_switch_tuple_arity(#icode_switch_tuple_arity{}, info()) -> + {#icode_switch_tuple_arity{}, [{label(),info()}]}. + +analyse_switch_tuple_arity(Switch, Info) -> + Var = hipe_icode:switch_tuple_arity_term(Switch), + NewInfo = enter_define({Var, get_range_from_arg(Var)}, Info), + Cases = hipe_icode:switch_tuple_arity_cases(Switch), + Fail = hipe_icode:switch_tuple_arity_fail_label(Switch), + {_, Case_labels} = lists:unzip(Cases), + Labels = [Fail|Case_labels], + {Switch, [{Label,NewInfo} || Label <- Labels]}. + +-spec analyse_goto(#icode_goto{}, info()) -> {#icode_goto{}, [{label(),info()},...]}. + +analyse_goto(Insn, Info) -> + GotoLabel = hipe_icode:goto_label(Insn), + {Insn, [{GotoLabel,Info}]}. + +-spec analyse_fail(#icode_fail{}, info()) -> {#icode_fail{}, [{label(),info()}]}. + +analyse_fail(Fail, Info) -> + case hipe_icode:fail_label(Fail) of + [] -> {Fail, []}; + Label -> {Fail, [{Label,Info}]} + end. + +-spec analyse_begin_try(#icode_begin_try{}, info()) -> + {#icode_begin_try{}, [{label(),info()},...]}. + +analyse_begin_try(Insn, Info) -> + Label = hipe_icode:begin_try_label(Insn), + Successor = hipe_icode:begin_try_successor(Insn), + {Insn, [{Label,Info},{Successor,Info}]}. + +-spec analyse_last_call(#icode_call{}, info(), call_fun()) -> + {#icode_call{}, [{label(),info()},...]}. + +analyse_last_call(Call, Info, LookupFun) -> + %% hipe_icode_pp:pp_block([Insn]), + NewI = analyse_call(Call, LookupFun), + Continuation = hipe_icode:call_continuation(Call), + NewInfo = enter_vals(NewI, Info), + case hipe_icode:call_fail_label(Call) of + [] -> + {NewI, [{Continuation,NewInfo}]}; + Fail -> + {NewI, [{Continuation,NewInfo}, {Fail,Info}]} + end. + +-spec analyse_if(#icode_if{}, info(), boolean()) -> + {#icode_goto{} | #icode_if{}, [{label(),info()}]}. + +analyse_if(If, Info, Rewrite) -> + case hipe_icode:if_args(If) of + Args = [_,_] -> + analyse_sane_if(If, Info, Args, get_range_from_args(Args), Rewrite); + _ -> + TrueLabel = hipe_icode:if_true_label(If), + FalseLabel = hipe_icode:if_false_label(If), + {If, [{TrueLabel,Info},{FalseLabel,Info}]} + end. + +-spec analyse_sane_if(#icode_if{}, info(), [argument(),...], + [#range{},...], boolean()) -> + {#icode_goto{} | #icode_if{}, [{label(), info()}]}. + +analyse_sane_if(If, Info, [Arg1, Arg2], [Range1, Range2], Rewrite) -> + case normalize_name(hipe_icode:if_op(If)) of + '>' -> + {TrueRange2, TrueRange1, FalseRange2, FalseRange1} = + range_inequality_propagation(Range2, Range1); + '==' -> + {TempTrueRange1, TempTrueRange2, FalseRange1, FalseRange2}= + range_equality_propagation(Range1, Range2), + TrueRange1 = set_other(TempTrueRange1,other(Range1)), + TrueRange2 = set_other(TempTrueRange2,other(Range2)); + '<' -> + {TrueRange1, TrueRange2, FalseRange1, FalseRange2} = + range_inequality_propagation(Range1, Range2); + '>=' -> + {FalseRange1, FalseRange2, TrueRange1, TrueRange2} = + range_inequality_propagation(Range1, Range2); + '=<' -> + {FalseRange2, FalseRange1, TrueRange2, TrueRange1} = + range_inequality_propagation(Range2, Range1); + '=:=' -> + {TrueRange1, TrueRange2, FalseRange1, FalseRange2}= + range_equality_propagation(Range1, Range2); + '=/=' -> + {FalseRange1, FalseRange2, TrueRange1, TrueRange2} = + range_equality_propagation(Range1, Range2); + '/=' -> + {TempFalseRange1, TempFalseRange2, TrueRange1, TrueRange2}= + range_equality_propagation(Range1, Range2), + FalseRange1 = set_other(TempFalseRange1,other(Range1)), + FalseRange2 = set_other(TempFalseRange2,other(Range2)) + end, + TrueLabel = hipe_icode:if_true_label(If), + FalseLabel = hipe_icode:if_false_label(If), + TrueInfo = + enter_defines([{Arg1,TrueRange1}, {Arg2,TrueRange2}],Info), + FalseInfo = + enter_defines([{Arg1,FalseRange1}, {Arg2,FalseRange2}],Info), + True = + case lists:any(fun range__is_none/1,[TrueRange1,TrueRange2]) of + true -> []; + false -> [{TrueLabel,TrueInfo}] + end, + False = + case lists:any(fun range__is_none/1, [FalseRange1,FalseRange2]) of + true -> []; + false -> [{FalseLabel,FalseInfo}] + end, + UpdateInfo = True++False, + NewIF = + if Rewrite -> + %%io:format("~w~n~w~n", [{Arg1,FalseRange1},{Arg2,FalseRange2}]), + %%io:format("Any none: ~w~n", [lists:any(fun range__is_none/1,[FalseRange1,FalseRange2])]), + case UpdateInfo of + [] -> %%This is weird + If; + [{Label,_Info}] -> + hipe_icode:mk_goto(Label); + [_,_] -> + If + end; + true -> + If + end, + {NewIF, UpdateInfo}. + +-spec normalize_name(atom()) -> atom(). + +normalize_name(Name) -> + case Name of + 'fixnum_eq' -> '=:='; + 'fixnum_neq' -> '=/='; + 'fixnum_gt' -> '>'; + 'fixnum_lt' -> '<'; + 'fixnum_ge' -> '>='; + 'fixnum_le' -> '=<'; + Name -> Name + end. + +-spec range_equality_propagation(#range{}, #range{}) -> + {#range{}, #range{}, #range{}, #range{}}. + +range_equality_propagation(Range_1, Range_2) -> + True_range = inf(Range_1, Range_2), + case {range(Range_1), range(Range_2)} of + {{N,N},{ N,N}} -> + False_range_1 = none_range(), + False_range_2 = none_range(); + {{N1,N1}, {N2,N2}} -> + False_range_1 = Range_1, + False_range_2 = Range_2; + {{N,N}, _} -> + False_range_1 = Range_1, + {_,False_range_2} = compare_with_integer(N, Range_2); + {_, {N,N}} -> + False_range_2 = Range_2, + {_,False_range_1} = compare_with_integer(N, Range_1); + {_, _} -> + False_range_1 = Range_1, + False_range_2 = Range_2 + end, + {True_range, True_range, False_range_1, False_range_2}. + +-spec range_inequality_propagation(#range{}, #range{}) -> + {#range{}, #range{}, #range{}, #range{}}. + +%% Range1 < Range2 +range_inequality_propagation(Range1, Range2) -> + R1_other = other(Range1), + R2_other = other(Range2), + {R1_true_range, R1_false_range, R2_true_range, R2_false_range} = + case {range(Range1), range(Range2)} of + {{N1,N1}, {N2,N2}} -> + case inf_geq(N2,inf_add(N1,1)) of + true -> + {{N1,N1},empty,{N2,N2},empty}; + false -> + {empty,{N1,N1},empty,{N2,N2}} + end; + {{N1,N1}, {Min2,Max2}} -> + case inf_geq(Min2,inf_add(N1,1)) of + true -> + {{N1,N1},empty,{inf_add(N1,1),Max2},empty}; + false -> + case inf_geq(N1,Max2) of + true -> + {empty,{N1,N1},empty,{Min2,N1}}; + false -> + {{N1,N1},{N1,N1},{inf_add(N1,1),Max2},{Min2,N1}} + end + end; + {{Min1,Max1}, {N2,N2}} -> + case inf_geq(N2,inf_add(Max1,1)) of + true -> + {{Min1,inf_add(N2,-1)},empty,{N2,N2},empty}; + false -> + case inf_geq(Min1,N2) of + true -> + {empty,{N2,Max1},empty,{N2,N2}}; + false -> + {{Min1,inf_add(N2,-1)},{N2,Max1},{N2,N2},{N2,N2}} + end + end; + {empty, {Min2,Max2}} -> + {empty,empty,{Min2,Max2},{Min2,Max2}}; + {{Min1,Max1}, empty} -> + {{Min1,Max1},{Min1,Max1},empty,empty}; + {empty, empty} -> + {empty,empty,empty,empty}; + {{Min1,Max1}, {Min2,Max2}} -> + {{Min1,inf_min([Max1,inf_add(Max2,-1)])}, + {inf_max([Min1,Min2]),Max1}, + {inf_max([inf_add(Min1,1),Min2]),Max2}, + {Min2,inf_min([Max1,Max2])}} + end, + {range_init(R1_true_range, R1_other), + range_init(R2_true_range, R2_other), + range_init(R1_false_range, R1_other), + range_init(R2_false_range, R2_other)}. + +-spec analyse_type(#icode_type{}, info(), boolean()) -> + {#icode_goto{} | #icode_type{}, [{label(),info()}]}. + +analyse_type(Type, Info, Rewrite) -> + TypeTest = hipe_icode:type_test(Type), + [Arg|_] = hipe_icode:type_args(Type), + OldVarRange = get_range_from_arg(Arg), + case TypeTest of + {integer, N} -> + {TrueRange,FalseRange} = compare_with_integer(N,OldVarRange); + integer -> + TrueRange = inf(any_range(), OldVarRange), + FalseRange = inf(none_range(), OldVarRange); + _ -> + TrueRange = inf(none_range(),OldVarRange), + FalseRange = OldVarRange + end, + TrueLabel = hipe_icode:type_true_label(Type), + FalseLabel = hipe_icode:type_false_label(Type), + TrueInfo = + enter_define({Arg,TrueRange},Info), + FalseInfo = + enter_define({Arg,FalseRange},Info), + True = + case range__is_none(TrueRange) of + true -> []; + false -> [{TrueLabel,TrueInfo}] + end, + False = + case range__is_none(FalseRange) of + true -> []; + false -> [{FalseLabel,FalseInfo}] + end, + UpdateInfo = True++False, + NewType = + if Rewrite -> + case UpdateInfo of + [] -> %% This is weird + Type; + [{Label,_Info}] -> + hipe_icode:mk_goto(Label); + [_,_] -> + Type + end; + true -> + Type + end, + {NewType,True ++ False}. + +-spec compare_with_integer(integer(), #range{}) -> {#range{}, #range{}}. + +compare_with_integer(N, OldVarRange) -> + TestRange = range_init({N, N}, false), + TrueRange = inf(TestRange, OldVarRange), + %% False range + TempFalseRange = range__remove_constant(OldVarRange, TestRange), + BetterRange = + case range(TempFalseRange) of + {Min, Max} = MM -> + New_small = inf_geq(Min, N), + New_large = inf_geq(N, Max), + if New_small and not New_large -> + {N + 1, Max}; + New_large and not New_small -> + {Min, N - 1}; + true -> + MM + end; + Not_tuple -> + Not_tuple + end, + FalseRange = range_init(BetterRange, other(TempFalseRange)), + {TrueRange, FalseRange}. + +%%== Ranges ================================================================== + +-spec pp_ann(#ann{} | erl_types:erl_type()) -> [string()]. + +pp_ann(#ann{range=#range{range=R, other=false}}) -> + pp_range(R); +pp_ann(#ann{range=#range{range=empty, other=true}, type=Type}) -> + t_to_string(Type); +pp_ann(#ann{range=#range{range=R, other=true}, type=Type}) -> + pp_range(R) ++ " | " ++ t_to_string(Type); +pp_ann(Type) -> + t_to_string(Type). + +-spec pp_range(range_rep()) -> nonempty_string(). + +pp_range(empty) -> + "none"; +pp_range({Min, Max}) -> + val_to_string(Min) ++ ".." ++ val_to_string(Max). + +-spec val_to_string('pos_inf' | 'neg_inf' | integer()) -> string(). + +val_to_string(pos_inf) -> "inf"; +val_to_string(neg_inf) -> "-inf"; +val_to_string(X) when is_integer(X) -> integer_to_list(X). + +-spec range_from_type(erl_types:erl_type()) -> [#range{}]. + +range_from_type(Type) -> + [range_from_simple_type(T) || T <- t_to_tlist(Type)]. + +-spec range_from_simple_type(erl_types:erl_type()) -> #range{}. + +range_from_simple_type(Type) -> + None = t_none(), + case t_inf(t_integer(), Type) of + None -> + #range{range = empty, other = true}; + Type -> + Range = {number_min(Type), number_max(Type)}, + #range{range = Range, other = false}; + NewType -> + Range = {number_min(NewType), number_max(NewType)}, + #range{range = Range, other = true} + end. + +-spec range_init(range_rep(), boolean()) -> #range{}. + +range_init({Min, Max} = Range, Other) -> + case inf_geq(Max, Min) of + true -> + #range{range = Range, other = Other}; + false -> + #range{range = empty, other = Other} + end; +range_init(empty, Other) -> + #range{range = empty, other = Other}. + +-spec range(#range{}) -> range_rep(). + +range(#range{range = R}) -> R. + +-spec other(#range{}) -> boolean(). + +other(#range{other = O}) -> O. + +-spec set_other(#range{}, boolean()) -> #range{}. + +set_other(R, O) -> R#range{other = O}. + +-spec range__min(#range{}) -> 'empty' | 'neg_inf' | integer(). + +range__min(#range{range=empty}) -> empty; +range__min(#range{range={Min,_}}) -> Min. + +-spec range__max(#range{}) -> 'empty' | 'pos_inf' | integer(). + +range__max(#range{range=empty}) -> empty; +range__max(#range{range={_,Max}}) -> Max. + +-spec range__is_none(#range{}) -> boolean(). + +range__is_none(#range{range=empty, other=false}) -> true; +range__is_none(#range{}) -> false. + +-spec range__is_empty(#range{}) -> boolean(). + +range__is_empty(#range{range=empty}) -> true; +range__is_empty(#range{range={_,_}}) -> false. + +-spec remove_point_types(#range{}, [#range{}]) -> #range{}. + +remove_point_types(Range, Ranges) -> + Sorted = lists:sort(Ranges), + FoldFun = fun (R, Acc) -> range__remove_constant(Acc,R) end, + Range1 = lists:foldl(FoldFun, Range, Sorted), + lists:foldl(FoldFun, Range1, lists:reverse(Sorted)). + +-spec range__remove_constant(#range{}, #range{}) -> #range{}. + +range__remove_constant(R = #range{range={C,C}}, #range{range={C,C}}) -> + R#range{range=empty}; +range__remove_constant(R = #range{range={C,H}}, #range{range={C,C}}) -> + R#range{range={C+1,H}}; +range__remove_constant(R = #range{range={L,C}}, #range{range={C,C}}) -> + R#range{range={L,C-1}}; +range__remove_constant(R = #range{}, #range{range={C,C}}) -> + R; +range__remove_constant(R = #range{}, _) -> + R. + +-spec any_type() -> #range{}. + +any_type() -> + #range{range=any_r(), other=true}. + +-spec any_range() -> #range{}. + +any_range() -> + #range{range=any_r(), other=false}. + +-spec none_range() -> #range{}. + +none_range() -> + #range{range=empty, other=true}. + +-spec none_type() -> #range{}. + +none_type() -> + #range{range = empty, other = false}. + +-spec any_r() -> {'neg_inf','pos_inf'}. + +any_r() -> {neg_inf, pos_inf}. + +-spec get_range_from_args([argument()]) -> [#range{}]. + +get_range_from_args(Args) -> + [get_range_from_arg(Arg) || Arg <- Args]. + +-spec get_range_from_arg(argument()) -> #range{}. + +get_range_from_arg(Arg) -> + case hipe_icode:is_const(Arg) of + true -> + Value = hipe_icode:const_value(Arg), + case is_integer(Value) of + true -> + #range{range={Value,Value}, other=false}; + false -> + #range{range=empty, other=true} + end; + false -> + case hipe_icode:is_annotated_variable(Arg) of + true -> + case hipe_icode:variable_annotation(Arg) of + {range_anno, #ann{range=Range}, _} -> + Range; + {type_anno, Type, _} -> + range_from_simple_type(Type) + end; + false -> + any_type() + end + end. + +%% inf([R]) -> +%% R; +%% inf([R1,R2|Rest]) -> +%% inf([inf(R1,R2)|Rest]). + +-spec inf(#range{}, #range{}) -> #range{}. + +inf(#range{range=R1, other=O1}, #range{range=R2, other=O2}) -> + #range{range=range_inf(R1,R2), other=other_inf(O1,O2)}. + +-spec range_inf(range_rep(), range_rep()) -> range_rep(). + +range_inf(empty, _) -> empty; +range_inf(_, empty) -> empty; +range_inf({Min1,Max1}, {Min2,Max2}) -> + NewMin = inf_max([Min1,Min2]), + NewMax = inf_min([Max1,Max2]), + case inf_geq(NewMax, NewMin) of + true -> + {NewMin, NewMax}; + false -> + empty + end. + +-spec other_inf(boolean(), boolean()) -> boolean(). + +other_inf(O1, O2) -> O1 and O2. + +-spec sup([#range{},...]) -> #range{}. + +sup([R]) -> + R; +sup([R1,R2|Rest]) -> + sup([sup(R1, R2)|Rest]). + +-spec sup(#range{}, #range{}) -> #range{}. + +sup(#range{range=R1,other=O1}, #range{range=R2,other=O2}) -> + #range{range=range_sup(R1,R2), other=other_sup(O1,O2)}. + +-spec range_sup(range_rep(), range_rep()) -> range_rep(). + +range_sup(empty, R) -> R; +range_sup(R, empty) -> R; +range_sup({Min1,Max1}, {Min2,Max2}) -> + NewMin = inf_min([Min1,Min2]), + NewMax = inf_max([Max1,Max2]), + {NewMin,NewMax}. + +-spec other_sup(boolean(), boolean()) -> boolean(). + +other_sup(O1, O2) -> O1 or O2. + +%%== Call Support ============================================================= + +-spec analyse_call_or_enter_fun(fun_name(), [argument()], + icode_call_type(), call_fun()) -> [#range{}]. + +analyse_call_or_enter_fun(Fun, Args, CallType, LookupFun) -> + %%io:format("Fun: ~p~n Args: ~p~n CT: ~p~n LF: ~p~n", [Fun, Args, CallType, LookupFun]), + case basic_type(Fun) of + {bin, Operation} -> + [Arg_range1,Arg_range2] = get_range_from_args(Args), + A1_is_empty = range__is_empty(Arg_range1), + A2_is_empty = range__is_empty(Arg_range2), + case A1_is_empty orelse A2_is_empty of + true -> + [none_type()]; + false -> + [Operation(Arg_range1, Arg_range2)] + end; + {unary, Operation} -> + [Arg_range] = get_range_from_args(Args), + case range__is_empty(Arg_range) of + true -> + [none_type()]; + false -> + [Operation(Arg_range)] + end; + {fcall, MFA} -> + case CallType of + local -> + Range = LookupFun(MFA, get_range_from_args(Args)), + case range__is_none(Range) of + true -> + throw(none_range); + false -> + [Range] + end; + remote -> + [any_type()] + end; + not_int -> + [any_type()]; + not_analysed -> + [any_type()]; + {hipe_bs_primop, {bs_get_integer, Size, Flags}} -> + {Min, Max} = analyse_bs_get_integer(Size, Flags, length(Args) =:= 1), + [#range{range={Min, Max}, other=false}, any_type()]; + {hipe_bs_primop, _} = Primop -> + Type = hipe_icode_primops:type(Primop), + range_from_type(Type) + end. + +-type bin_operation() :: fun((#range{},#range{}) -> #range{}). +-type unary_operation() :: fun((#range{}) -> #range{}). + +-spec basic_type(fun_name()) -> 'not_int' | 'not_analysed' + | {bin, bin_operation()} + | {unary, unary_operation()} + | {fcall, mfa()} | {hipe_bs_primop, _}. + +%% Arithmetic operations +basic_type('+') -> {bin, fun(R1, R2) -> range_add(R1, R2) end}; +basic_type('-') -> {bin, fun(R1, R2) -> range_sub(R1, R2) end}; +basic_type('*') -> {bin, fun(R1, R2) -> range_mult(R1, R2) end}; +basic_type('/') -> not_int; +basic_type('div') -> {bin, fun(R1, R2) -> range_div(R1, R2) end}; +basic_type('rem') -> {bin, fun(R1, R2) -> range_rem(R1, R2) end}; +basic_type('bor') -> {bin, fun(R1, R2) -> range_bor(R1, R2) end}; +basic_type('band') -> {bin, fun(R1, R2) -> range_band(R1, R2) end}; +basic_type('bxor') -> {bin, fun(R1, R2) -> range_bxor(R1, R2) end}; +basic_type('bnot') -> {unary, fun(R1) -> range_bnot(R1) end}; +basic_type('bsl') -> {bin, fun(R1, R2) -> range_bsl(R1, R2) end}; +basic_type('bsr') -> {bin, fun(R1, R2) -> range_bsr(R1, R2) end}; +%% unsafe_* +basic_type('unsafe_bor') -> + {bin, fun(R1, R2) -> range_bor(R1, R2) end}; +basic_type('unsafe_band') -> + {bin, fun(R1, R2) -> range_band(R1, R2) end}; +basic_type('unsafe_bxor') -> + {bin, fun(R1, R2) -> range_bxor(R1, R2) end}; +basic_type('unsafe_bnot') -> + {unary, fun(R1) -> range_bnot(R1) end}; +basic_type('unsafe_bsl') -> + {bin, fun(R1, R2) -> range_bsl(R1, R2) end}; +basic_type('unsafe_bsr') -> + {bin, fun(R1, R2) -> range_bsr(R1, R2) end}; +basic_type('unsafe_add') -> + {bin, fun(R1, R2) -> range_add(R1, R2) end}; +basic_type('unsafe_sub') -> + {bin, fun(R1, R2) -> range_sub(R1, R2) end}; +basic_type('extra_unsafe_add') -> + {bin, fun(R1, R2) -> range_add(R1, R2) end}; +basic_type('extra_unsafe_sub') -> + {bin, fun(R1, R2) -> range_sub(R1, R2) end}; +%% Binaries +basic_type({hipe_bs_primop, _} = Primop) -> Primop; +%% Unknown, other +basic_type(call_fun) -> not_analysed; +basic_type(clear_timeout) -> not_analysed; +basic_type(redtest) -> not_analysed; +basic_type(set_timeout) -> not_analysed; +basic_type(#apply_N{}) -> not_analysed; +basic_type(#closure_element{}) -> not_analysed; +basic_type(#gc_test{}) -> not_analysed; +%% Message handling +basic_type(check_get_msg) -> not_analysed; +basic_type(next_msg) -> not_analysed; +basic_type(select_msg) -> not_analysed; +basic_type(suspend_msg) -> not_analysed; +%% Functions +basic_type(enter_fun) -> not_analysed; +basic_type(#mkfun{}) -> not_int; +basic_type({_M,_F,_A} = MFA) -> {fcall, MFA}; +%% Floats +basic_type(conv_to_float) -> not_int; +basic_type(fclearerror) -> not_analysed; +basic_type(fcheckerror) -> not_analysed; +basic_type(fnegate) -> not_int; +basic_type(fp_add) -> not_int; +basic_type(fp_div) -> not_int; +basic_type(fp_mul) -> not_int; +basic_type(fp_sub) -> not_int; +basic_type(unsafe_tag_float) -> not_int; +basic_type(unsafe_untag_float) -> not_int; +%% Lists, tuples, records +basic_type(cons) -> not_int; +basic_type(mktuple) -> not_int; +basic_type(unsafe_hd) -> not_analysed; +basic_type(unsafe_tl) -> not_int; +basic_type(#element{}) -> not_analysed; +basic_type(#unsafe_element{}) -> not_analysed; +basic_type(#unsafe_update_element{}) -> not_analysed. + +-spec analyse_bs_get_integer(integer(), integer(), boolean()) -> range_tuple(). + +analyse_bs_get_integer(Size, Flags, true) -> + Signed = Flags band 4, + if Signed =:= 0 -> + Max = 1 bsl Size - 1, + Min = 0; + true -> + Max = 1 bsl (Size-1) - 1, + Min = -(1 bsl (Size-1)) + end, + {Min, Max}; +analyse_bs_get_integer(Size, Flags, false) when is_integer(Size), + is_integer(Flags) -> + any_r(). + +%%--------------------------------------------------------------------------- +%% Range operations +%%--------------------------------------------------------------------------- + +%% Arithmetic + +-spec range_add(#range{}, #range{}) -> #range{}. + +range_add(Range1, Range2) -> + NewMin = inf_add(range__min(Range1), range__min(Range2)), + NewMax = inf_add(range__max(Range1), range__max(Range2)), + Other = other(Range1) orelse other(Range2), + range_init({NewMin, NewMax}, Other). + +-spec range_sub(#range{}, #range{}) -> #range{}. + +range_sub(Range1, Range2) -> + Min_sub = inf_min([inf_inv(range__max(Range2)), + inf_inv(range__min(Range2))]), + Max_sub = inf_max([inf_inv(range__max(Range2)), + inf_inv(range__min(Range2))]), + NewMin = inf_add(range__min(Range1), Min_sub), + NewMax = inf_add(range__max(Range1), Max_sub), + Other = other(Range1) orelse other(Range2), + range_init({NewMin, NewMax}, Other). + +-spec range_mult(#range{}, #range{}) -> #range{}. + +range_mult(#range{range=empty, other=true}, _Range2) -> + range_init(empty, true); +range_mult(_Range1, #range{range=empty, other=true}) -> + range_init(empty, true); +range_mult(Range1, Range2) -> + Min1 = range__min(Range1), + Min2 = range__min(Range2), + Max1 = range__max(Range1), + Max2 = range__max(Range2), + GreaterMin1 = inf_greater_zero(Min1), + GreaterMin2 = inf_greater_zero(Min2), + GreaterMax1 = inf_greater_zero(Max1), + GreaterMax2 = inf_greater_zero(Max2), + Range = + if GreaterMin1 -> + if GreaterMin2 -> {inf_mult(Min1, Min2), inf_mult(Max1, Max2)}; + GreaterMax2 -> {inf_mult(Min2, Max1), inf_mult(Max2, Max1)}; + true -> {inf_mult(Min2, Max1), inf_mult(Max2, Min1)} + end; + %% Column 1 or 2 + GreaterMin2 -> % Column 1 or 2 row 3 + range(range_mult(Range2, Range1)); + GreaterMax1 -> % Column 2 Row 1 or 2 + if GreaterMax2 -> % Column 2 Row 2 + NewMin = inf_min([inf_mult(Min2, Max1), inf_mult(Max2, Min1)]), + NewMax = inf_max([inf_mult(Min2, Min1), inf_mult(Max2, Max1)]), + {NewMin, NewMax}; + true -> % Column 2 Row 1 + {inf_mult(Min2, Max1), inf_mult(Min2, Min1)} + end; + GreaterMax2 -> % Column 1 Row 2 + range(range_mult(Range2, Range1)); + true -> % Column 1 Row 1 + {inf_mult(Max1, Max2), inf_mult(Min2, Min1)} + end, + Other = other(Range1) orelse other(Range2), + range_init(Range, Other). + +-spec extreme_divisors(#range{}) -> range_tuple(). + +extreme_divisors(#range{range={0,0}}) -> {0,0}; +extreme_divisors(#range{range={0,Max}}) -> {1,Max}; +extreme_divisors(#range{range={Min,0}}) -> {Min,-1}; +extreme_divisors(#range{range={Min,Max}}) -> + case inf_geq(Min, 0) of + true -> {Min, Max}; + false -> % Min < 0 + case inf_geq(0, Max) of + true -> {Min,Max}; % Max < 0 + false -> {-1,1} % Max > 0 + end + end. + +-spec range_div(#range{}, #range{}) -> #range{}. + +%% this is div, not /. +range_div(_, #range{range={0,0}}) -> + range_init(empty, false); +range_div(#range{range=empty}, _) -> + range_init(empty, false); +range_div(_, #range{range=empty}) -> + range_init(empty, false); +range_div(Range1, Den) -> + Min1 = range__min(Range1), + Max1 = range__max(Range1), + {Min2, Max2} = extreme_divisors(Den), + Min_max_list = [inf_div(Min1, Min2), inf_div(Min1, Max2), + inf_div(Max1, Min2), inf_div(Max1, Max2)], + range_init({inf_min(Min_max_list), inf_max(Min_max_list)}, false). + +-spec range_rem(#range{}, #range{}) -> #range{}. + +range_rem(Range1, Range2) -> + %% Range1 desides the sign of the answer. + Min1 = range__min(Range1), + Max1 = range__max(Range1), + Min2 = range__min(Range2), + Max2 = range__max(Range2), + Min1_geq_zero = inf_geq(Min1, 0), + Max1_leq_zero = inf_geq(0, Max1), + Max_range2 = inf_max([inf_abs(Min2), inf_abs(Max2)]), + Max_range2_leq_zero = inf_geq(0, Max_range2), + New_min = + if Min1_geq_zero -> 0; + Max_range2_leq_zero -> Max_range2; + true -> inf_inv(Max_range2) + end, + New_max = + if Max1_leq_zero -> 0; + Max_range2_leq_zero -> inf_inv(Max_range2); + true -> Max_range2 + end, + range_init({New_min, New_max}, false). + +%%--- Bit operations ---------------------------- + +-spec range_bsr(#range{}, #range{}) -> #range{}. + +range_bsr(Range1, Range2=#range{range={Min, Max}}) -> + New_Range2 = range_init({inf_inv(Max), inf_inv(Min)}, other(Range2)), + Ans = range_bsl(Range1, New_Range2), + %% io:format("bsr res:~w~nInput:= ~w~n", [Ans, {Range1,Range2}]), + Ans. + +-spec range_bsl(#range{}, #range{}) -> #range{}. + +range_bsl(Range1, Range2) -> + Min1 = range__min(Range1), + Min2 = range__min(Range2), + Max1 = range__max(Range1), + Max2 = range__max(Range2), + Min1Geq0 = inf_geq(Min1, 0), + Max1Less0 = not inf_geq(Max1, 0), + MinMax = + if Min1Geq0 -> + {inf_bsl(Min1, Min2), inf_bsl(Max1, Max2)}; + true -> + if Max1Less0 -> {inf_bsl(Min1, Max2), inf_bsl(Max1, Min2)}; + true -> {inf_bsl(Min1, Max2), inf_bsl(Max1, Max2)} + end + end, + range_init(MinMax, false). + +-spec range_bnot(#range{}) -> #range{}. + +range_bnot(Range) -> + Minus_one = range_init({-1,-1}, false), + range_add(range_mult(Range, Minus_one), Minus_one). + +-spec width(range_rep() | integer()) -> 'pos_inf' | non_neg_integer(). + +width({Min, Max}) -> inf_max([width(Min), width(Max)]); +width(pos_inf) -> pos_inf; +width(neg_inf) -> pos_inf; +width(X) when is_integer(X), X >= 0 -> poswidth(X, 0); +width(X) when is_integer(X), X < 0 -> negwidth(X, 0). + +-spec poswidth(non_neg_integer(), non_neg_integer()) -> non_neg_integer(). + +poswidth(X, N) -> + case X < (1 bsl N) of + true -> N; + false -> poswidth(X, N+1) + end. + +-spec negwidth(neg_integer(), non_neg_integer()) -> non_neg_integer(). + +negwidth(X, N) -> + case X > (-1 bsl N) of + true -> N; + false -> negwidth(X, N+1) + end. + +-spec range_band(#range{}, #range{}) -> #range{}. + +range_band(R1, R2) -> + {_Min1, Max1} = MM1 = range(R1), + {_Min2, Max2} = MM2 = range(R2), + Width1 = width(MM1), + Width2 = width(MM2), + Range = + case {classify_range(R1), classify_range(R2)} of + {minus_minus, minus_minus} -> + Width = inf_max([Width1, Width2]), + {inf_bsl(-1, Width), -1}; + {minus_minus, minus_plus} -> + Width = inf_max([Width1, Width2]), + {inf_bsl(-1, Width), Max2}; + {minus_minus, plus_plus} -> + {0, Max2}; + {minus_plus, minus_minus} -> + Width = inf_max([Width1, Width2]), + {inf_bsl(-1, Width), Max1}; + {minus_plus, minus_plus} -> + Width = inf_max([Width1, Width2]), + {inf_bsl(-1, Width), inf_max([Max1, Max2])}; + {minus_plus, plus_plus} -> + {0, Max2}; + {plus_plus, minus_minus} -> + {0, Max1}; + {plus_plus, minus_plus} -> + {0, Max1}; + {plus_plus, plus_plus} -> + {0, inf_min([Max1, Max2])} + end, + range_init(Range, false). + +-spec range_bor(#range{}, #range{}) -> #range{}. + +range_bor(R1, R2) -> + {Min1, _Max1} = MM1 = range(R1), + {Min2, _Max2} = MM2 = range(R2), + Width1 = width(MM1), + Width2 = width(MM2), + Range = + case {classify_range(R1), classify_range(R2)} of + {minus_minus, minus_minus} -> + {inf_max([Min1, Min2]), -1}; + {minus_minus, minus_plus} -> + {Min1, -1}; + {minus_minus, plus_plus} -> + {Min1, -1}; + {minus_plus, minus_minus} -> + {Min2, -1}; + {minus_plus, minus_plus} -> + Width = inf_max([Width1, Width2]), + {inf_min([Min1, Min2]), inf_add(-1, inf_bsl(1, Width))}; + {minus_plus, plus_plus} -> + Width = inf_max([Width1, Width2]), + {Min1, inf_add(-1, inf_bsl(1, Width))}; + {plus_plus, minus_minus} -> + {Min2, -1}; + {plus_plus, minus_plus} -> + Width = inf_max([Width1, Width2]), + {Min2, inf_add(-1, inf_bsl(1, Width))}; + {plus_plus, plus_plus} -> + Width = inf_max([Width1, Width2]), + {0, inf_add(-1, inf_bsl(1, Width))} + end, + range_init(Range, false). + +-spec classify_range(#range{}) -> 'minus_minus' | 'minus_plus' | 'plus_plus'. + +classify_range(Range) -> + case range(Range) of + {neg_inf, Number} when is_integer(Number), Number < 0 -> minus_minus; + {neg_inf, Number} when is_integer(Number), Number >= 0 -> minus_plus; + {Number, pos_inf} when is_integer(Number), Number < 0 -> minus_plus; + {Number, pos_inf} when is_integer(Number), Number >= 0 -> plus_plus; + {neg_inf, pos_inf} -> minus_plus; + {Number1,Number2} when is_integer(Number1), is_integer(Number2) -> + classify_int_range(Number1, Number2) + end. + +-spec classify_int_range(integer(), integer()) -> + 'minus_minus' | 'minus_plus' | 'plus_plus'. + +classify_int_range(Number1, _Number2) when Number1 >= 0 -> + plus_plus; +classify_int_range(_Number1, Number2) when Number2 < 0 -> + minus_minus; +classify_int_range(_Number1, _Number2) -> + minus_plus. + +-spec range_bxor(#range{}, #range{}) -> #range{}. + +range_bxor(R1, R2) -> + {Min1, Max1} = MM1 = range(R1), + {Min2, Max2} = MM2 = range(R2), + Width1 = width(MM1), + Width2 = width(MM2), + Range = + case {classify_range(R1), classify_range(R2)} of + {minus_minus, minus_minus} -> + Width = inf_max([Width1, Width2]), + {0, inf_add(-1, inf_bsl(1, Width))}; + {minus_minus, minus_plus} -> + MinWidth = inf_max([Width1, width({0,Max2})]), + MaxWidth = inf_max([Width1, width({Min2,-1})]), + {inf_bsl(-1, MinWidth), inf_add(-1, inf_bsl(1, MaxWidth))}; + {minus_minus, plus_plus} -> + Width = inf_max([Width1, Width2]), + {inf_bsl(-1, Width), -1}; + {minus_plus, minus_minus} -> + MinWidth = inf_max([Width2,width({0,Max1})]), + MaxWidth = inf_max([Width2,width({Min1,-1})]), + {inf_bsl(-1, MinWidth), inf_add(-1, inf_bsl(1, MaxWidth))}; + {minus_plus, minus_plus} -> + Width = inf_max([Width1, Width2]), + {inf_bsl(-1, Width), inf_add(-1, inf_bsl(1, Width))}; + {minus_plus, plus_plus} -> + MinWidth = inf_max([Width2,width({Min1,-1})]), + MaxWidth = inf_max([Width2,width({0,Max1})]), + {inf_bsl(-1, MinWidth), inf_add(-1, inf_bsl(1, MaxWidth))}; + {plus_plus, minus_minus} -> + Width = inf_max([Width1, Width2]), + {inf_bsl(-1, Width), -1}; + {plus_plus, minus_plus} -> + MinWidth = inf_max([Width1,width({Min2,-1})]), + MaxWidth = inf_max([Width1,width({0,Max2})]), + {inf_bsl(-1, MinWidth), inf_add(-1, inf_bsl(1, MaxWidth))}; + {plus_plus, plus_plus} -> + Width = inf_max([Width1, Width2]), + {0, inf_add(-1, inf_bsl(1, Width))} + end, + range_init(Range, false). + +%%--------------------------------------------------------------------------- +%% Inf operations +%%--------------------------------------------------------------------------- + +-spec inf_max([inf_integer(),...]) -> inf_integer(). + +inf_max([H|T]) -> + lists:foldl(fun (Elem, Max) -> + case inf_geq(Elem, Max) of + false -> Max; + true -> Elem + end + end, H, T). + +-spec inf_min([inf_integer(),...]) -> inf_integer(). + +inf_min([H|T]) -> + lists:foldl(fun (Elem, Min) -> + case inf_geq(Elem, Min) of + true -> Min; + false -> Elem + end + end, H, T). + +-spec inf_abs(inf_integer()) -> 'pos_inf' | integer(). + +inf_abs(pos_inf) -> pos_inf; +inf_abs(neg_inf) -> pos_inf; +inf_abs(Number) when is_integer(Number), (Number < 0) -> - Number; +inf_abs(Number) when is_integer(Number) -> Number. + +-spec inf_add(inf_integer(), inf_integer()) -> inf_integer(). + +inf_add(pos_inf, _Number) -> pos_inf; +inf_add(neg_inf, _Number) -> neg_inf; +inf_add(_Number, pos_inf) -> pos_inf; +inf_add(_Number, neg_inf) -> neg_inf; +inf_add(Number1, Number2) when is_integer(Number1), is_integer(Number2) -> + Number1 + Number2. + +-spec inf_inv(inf_integer()) -> inf_integer(). + +inf_inv(pos_inf) -> neg_inf; +inf_inv(neg_inf) -> pos_inf; +inf_inv(Number) -> -Number. + +-spec inf_geq(inf_integer(), inf_integer()) -> boolean(). + +inf_geq(pos_inf, _) -> true; +inf_geq(_, pos_inf) -> false; +inf_geq(_, neg_inf) -> true; +inf_geq(neg_inf, _) -> false; +inf_geq(A, B) -> A >= B. + +-spec inf_greater_zero(inf_integer()) -> boolean(). + +inf_greater_zero(pos_inf) -> true; +inf_greater_zero(neg_inf) -> false; +inf_greater_zero(Number) when is_integer(Number), Number >= 0 -> true; +inf_greater_zero(Number) when is_integer(Number), Number < 0 -> false. + +-spec inf_div(inf_integer(), inf_integer()) -> inf_integer(). + +inf_div(Number, 0) -> + Greater = inf_greater_zero(Number), + if Greater -> pos_inf; + true -> neg_inf + end; +inf_div(pos_inf, Number) -> + Greater = inf_greater_zero(Number), + if Greater -> pos_inf; + true -> neg_inf + end; +inf_div(neg_inf, Number) -> + Greater = inf_greater_zero(Number), + if Greater -> neg_inf; + true -> pos_inf + end; +inf_div(Number, pos_inf) -> + Greater = inf_greater_zero(Number), + if Greater -> pos_inf; + true -> neg_inf + end; +inf_div(Number, neg_inf) -> + Greater = inf_greater_zero(Number), + if Greater -> neg_inf; + true -> pos_inf + end; +inf_div(Number1, Number2) -> Number1 div Number2. + +-spec inf_mult(inf_integer(), inf_integer()) -> inf_integer(). + +inf_mult(neg_inf, Number) -> + Greater = inf_greater_zero(Number), + if Greater -> neg_inf; + true -> pos_inf + end; +inf_mult(pos_inf, Number) -> + Greater = inf_greater_zero(Number), + if Greater -> pos_inf; + true -> neg_inf + end; +inf_mult(Number, pos_inf) -> inf_mult(pos_inf, Number); +inf_mult(Number, neg_inf) -> inf_mult(neg_inf, Number); +inf_mult(Number1, Number2) -> Number1 * Number2. + +-spec inf_bsl(inf_integer(), inf_integer()) -> inf_integer(). + +inf_bsl(pos_inf, _) -> pos_inf; +inf_bsl(neg_inf, _) -> neg_inf; +inf_bsl(Number, pos_inf) when is_integer(Number), Number >= 0 -> pos_inf; +inf_bsl(_, pos_inf) -> neg_inf; +inf_bsl(Number, neg_inf) when is_integer(Number), Number >= 0 -> 0; +inf_bsl(_Number, neg_inf) -> -1; +inf_bsl(Number1, Number2) when is_integer(Number1), is_integer(Number2) -> + %% We can not shift left with a number which is not a fixnum. We + %% don't have enough memory. + Bits = ?BITS, + if Number2 > (Bits bsl 1) -> inf_bsl(Number1, pos_inf); + Number2 < (-Bits bsl 1) -> inf_bsl(Number1, neg_inf); + true -> Number1 bsl Number2 + end. + +%% State + +-spec state__init(cfg(), data()) -> #state{}. + +state__init(Cfg, {MFA, ArgsFun, CallFun, FinalFun}) -> + Start = hipe_icode_cfg:start_label(Cfg), + Params = hipe_icode_cfg:params(Cfg), + Ranges = ArgsFun(MFA, Cfg), + %% io:format("MFA: ~w~nRanges: ~w~n", [MFA, Ranges]), + Liveness = + hipe_icode_ssa:ssa_liveness__analyze(hipe_icode_type:unannotate_cfg(Cfg)), + case lists:any(fun range__is_none/1, Ranges) of + true -> + FinalFun(MFA, [none_type()]), + throw(no_input); + false -> + NewParams = lists:zipwith(fun update_info/2, Params, Ranges), + NewCfg = hipe_icode_cfg:params_update(Cfg, NewParams), + Info = enter_defines(NewParams, gb_trees:empty()), + InfoMap = gb_trees:insert({Start, in}, Info, gb_trees:empty()), + #state{info_map=InfoMap, cfg=NewCfg, liveness=Liveness, + ret_type=none_type(), + lookup_fun=CallFun, result_action=FinalFun} + end. + +-spec state__cfg(#state{}) -> cfg(). + +state__cfg(#state{cfg=Cfg}) -> + Cfg. + +-spec state__bb(#state{}, label()) -> bb(). + +state__bb(#state{cfg=Cfg}, Label) -> + BB = hipe_icode_cfg:bb(Cfg, Label), + true = hipe_bb:is_bb(BB), % Just an assert + BB. + +-spec state__bb_add(#state{}, label(), bb()) -> #state{}. + +state__bb_add(S=#state{cfg=Cfg}, Label, BB) -> + NewCfg = hipe_icode_cfg:bb_add(Cfg, Label, BB), + S#state{cfg=NewCfg}. + +state__lookup_fun(#state{lookup_fun=LF}) -> LF. + +state__result_action(#state{result_action=RA}) -> RA. + +state__ret_type(#state{ret_type=RT}) -> RT. + +state__ret_type_update(#state{ret_type=RT} = State, NewType) -> + TotType = sup(RT, NewType), + State#state{ret_type=TotType}. + +state__info_in(S, Label) -> + state__info(S, {Label, in}). + +state__info(#state{info_map=IM}, Key) -> + gb_trees:get(Key, IM). + +state__update_info(State, LabelInfo, Rewrite) -> + update_info(LabelInfo, State, [], Rewrite). + +update_info([{Label,InfoIn}|Rest], State, LabelAcc, Rewrite) -> + case state__info_in_update(State, Label, InfoIn) of + fixpoint -> + if Rewrite -> + update_info(Rest, State, [Label|LabelAcc], Rewrite); + true -> + update_info(Rest, State, LabelAcc, Rewrite) + end; + NewState -> + update_info(Rest, NewState, [Label|LabelAcc], Rewrite) + end; +update_info([], State, LabelAcc, _Rewrite) -> + {State, LabelAcc}. + +state__info_in_update(S=#state{info_map=IM,liveness=Liveness}, Label, Info) -> + LabelIn = {Label, in}, + case gb_trees:lookup(LabelIn, IM) of + none -> + LiveIn = hipe_icode_ssa:ssa_liveness__livein(Liveness, Label), + NamesLiveIn = [hipe_icode:var_name(Var) || Var <- LiveIn, + hipe_icode:is_var(Var)], + OldInfo = gb_trees:empty(), + case join_info_in(NamesLiveIn, OldInfo, Info) of + fixpoint -> + S#state{info_map=gb_trees:insert(LabelIn, OldInfo, IM)}; + NewInfo -> + S#state{info_map=gb_trees:enter(LabelIn, NewInfo, IM)} + end; + {value, OldInfo} -> + OldVars = gb_trees:keys(OldInfo), + case join_info_in(OldVars, OldInfo, Info) of + fixpoint -> + fixpoint; + NewInfo -> + S#state{info_map=gb_trees:update(LabelIn, NewInfo, IM)} + end + end. + +join_info_in(Vars, OldInfo, NewInfo) -> + case join_info_in(Vars, OldInfo, NewInfo, gb_trees:empty(), false) of + {Res, true} -> Res; + {_, false} -> fixpoint + end. + +join_info_in([Var|Left], Info1, Info2, Acc, Changed) -> + Type1 = gb_trees:lookup(Var, Info1), + Type2 = gb_trees:lookup(Var, Info2), + case {Type1, Type2} of + {none, none} -> + NewTree = gb_trees:insert(Var, none_type(), Acc), + join_info_in(Left, Info1, Info2, NewTree, true); + {none, {value, Val}} -> + NewTree = gb_trees:insert(Var, Val, Acc), + join_info_in(Left, Info1, Info2, NewTree, true); + {{value, Val}, none} -> + NewTree = gb_trees:insert(Var, Val, Acc), + join_info_in(Left, Info1, Info2, NewTree, Changed); + {{value, Val}, {value, Val}} -> + NewTree = gb_trees:insert(Var, Val, Acc), + join_info_in(Left, Info1, Info2, NewTree, Changed); + {{value, Val1}, {value, Val2}} -> + NewVal = + case sup(Val1, Val2) of + Val1 -> + NewChanged = Changed, + Val1; + Val -> + NewChanged = true, + Val + end, + NewTree = gb_trees:insert(Var, NewVal, Acc), + join_info_in(Left, Info1, Info2, NewTree, NewChanged) + end; +join_info_in([], _Info1, _Info2, Acc, NewChanged) -> + {Acc, NewChanged}. + +enter_defines([Def|Rest], Info) -> + enter_defines(Rest, enter_define(Def, Info)); +enter_defines([], Info) -> Info. + +enter_define({PossibleVar, Range = #range{}}, Info) -> + case hipe_icode:is_var(PossibleVar) of + true -> + gb_trees:enter(hipe_icode:var_name(PossibleVar), Range, Info); + false -> + Info + end; +enter_define(PossibleVar, Info) -> + case hipe_icode:is_var(PossibleVar) of + true -> + case hipe_icode:variable_annotation(PossibleVar) of + {range_anno, #ann{range=Range}, _} -> + gb_trees:enter(hipe_icode:var_name(PossibleVar), Range, Info); + _ -> + Info + end; + false -> + Info + end. + +enter_vals(Ins, Info) -> + NewInfo = enter_defines(hipe_icode:args(Ins), Info), + enter_defines(hipe_icode:defines(Ins), NewInfo). + +lookup(PossibleVar, Info) -> + case hipe_icode:is_var(PossibleVar) of + true -> + case gb_trees:lookup(hipe_icode:var_name(PossibleVar), Info) of + none -> + none_type(); + {value, Val} -> + Val + end; + false -> + none_type() + end. + +%% _________________________________________________________________ +%% +%% The worklist. +%% + +init_work(State) -> + %% Labels = hipe_icode_cfg:reverse_postorder(state__cfg(State)), + Labels = [hipe_icode_cfg:start_label(state__cfg(State))], + {Labels, [], sets:from_list(Labels)}. + +get_work({[Label|Left], List, Set}) -> + NewWork = {Left, List, sets:del_element(Label, Set)}, + {Label, NewWork}; +get_work({[], [], _Set}) -> + fixpoint; +get_work({[], List, Set}) -> + get_work({lists:reverse(List), [], Set}). + +add_work(Work = {List1, List2, Set}, [Label|Left]) -> + case sets:is_element(Label, Set) of + true -> + add_work(Work, Left); + false -> + %% io:format("Adding work: ~w\n", [Label]), + add_work({List1, [Label|List2], sets:add_element(Label, Set)}, Left) + end; +add_work(Work, []) -> + Work. + +convert_cfg_to_types(Cfg) -> + Lbls = hipe_icode_cfg:reverse_postorder(Cfg), + lists:foldl(fun convert_lbl_to_type/2, Cfg, Lbls). + +convert_lbl_to_type(Lbl, Cfg) -> + BB = hipe_icode_cfg:bb(Cfg, Lbl), + Code = hipe_bb:code(BB), + NewCode = [convert_instr_to_type(I) || I <- Code], + hipe_icode_cfg:bb_add(Cfg, Lbl, hipe_bb:mk_bb(NewCode)). + +convert_instr_to_type(I) -> + Uses = hipe_icode:uses(I), + UseSubstList = [{Use, convert_to_types(Use)} || + Use <- Uses, hipe_icode:is_annotated_variable(Use)], + NewI = hipe_icode:subst_uses(UseSubstList, I), + Defs = hipe_icode:defines(NewI), + DefSubstList = [{Def, convert_to_types(Def)} || + Def <- Defs, hipe_icode:is_annotated_variable(Def)], + hipe_icode:subst_defines(DefSubstList, NewI). + +convert_to_types(VarOrReg) -> + Annotation = + case hipe_icode:variable_annotation(VarOrReg) of + {range_anno, Ann, _} -> + {type_anno, convert_ann_to_types(Ann), fun erl_types:t_to_string/1}; + {type_anno, _, _} = TypeAnn -> + TypeAnn + end, + hipe_icode:annotate_variable(VarOrReg, Annotation). + +convert_ann_to_types(#ann{range=#range{range={Min,Max}, other=false}}) -> + t_from_range_unsafe(Min, Max); +convert_ann_to_types(#ann{range=#range{range=empty, other=false}}) -> + t_none(); +convert_ann_to_types(#ann{range=#range{other=true}, type=Type}) -> + Type. + +%%===================================================================== +%% Icode Coordinator Callbacks +%%===================================================================== + +-spec replace_nones([#range{}]) -> [#range{}]. +replace_nones(Args) -> + [replace_none(Arg) || Arg <- Args]. + +replace_none(Arg) -> + case range__is_none(Arg) of + true -> any_type(); + false -> Arg + end. + +-spec update__info([#range{}], [#range{}]) -> {boolean(), [#ann{}]}. +update__info(NewRanges, OldRanges) -> + SupFun = fun (Ann, Range) -> + join_info(Ann, Range, fun safe_widen/3) + end, + EqFun = fun (X, Y) -> X =:= Y end, + ResRanges = lists:zipwith(SupFun, OldRanges, NewRanges), + Change = lists:zipwith(EqFun, ResRanges, OldRanges), + {lists:all(fun (X) -> X end, Change), ResRanges}. + +-spec new__info/1 :: ([#range{}]) -> [#ann{}]. +new__info(NewRanges) -> + [#ann{range=Range,count=1,type=t_any()} || Range <- NewRanges]. + +-spec return__info/1 :: ([#ann{}]) -> [#range{}]. +return__info(Ranges) -> + [Range || #ann{range=Range} <- Ranges]. + +-spec return_none/0 :: () -> [#range{},...]. +return_none() -> + [none_type()]. + +-spec return_none_args/2 :: (#cfg{}, mfa()) -> [#range{}]. +return_none_args(Cfg, {_M,_F,A}) -> + NoArgs = + case hipe_icode_cfg:is_closure(Cfg) of + true -> hipe_icode_cfg:closure_arity(Cfg) + 1; + false -> A + end, + lists:duplicate(NoArgs, none_type()). + +-spec return_any_args/2 :: (#cfg{}, mfa()) -> [#range{}]. +return_any_args(Cfg, {_M,_F,A}) -> + NoArgs = + case hipe_icode_cfg:is_closure(Cfg) of + true -> hipe_icode_cfg:closure_arity(Cfg) + 1; + false -> A + end, + lists:duplicate(NoArgs, any_type()). + +%%===================================================================== + +next_up_limit(X) when is_integer(X), X < 0 -> 0; +next_up_limit(X) when is_integer(X), X < 255 -> 255; +next_up_limit(X) when is_integer(X), X < 16#10ffff -> 16#10ffff; +next_up_limit(X) when is_integer(X), X < 16#7ffffff -> 16#7ffffff; +next_up_limit(X) when is_integer(X), X < 16#7fffffff -> 16#7fffffff; +next_up_limit(X) when is_integer(X), X < 16#ffffffff -> 16#ffffffff; +next_up_limit(X) when is_integer(X), X < 16#fffffffffff -> 16#fffffffffff; +next_up_limit(X) when is_integer(X), X < 16#7fffffffffffffff -> 16#7fffffffffffffff; +next_up_limit(_X) -> pos_inf. + +next_down_limit(X) when is_integer(X), X > 0 -> 0; +next_down_limit(X) when is_integer(X), X > -256 -> -256; +next_down_limit(X) when is_integer(X), X > -16#10ffff -> -16#10ffff; +next_down_limit(X) when is_integer(X), X > -16#8000000 -> -16#8000000; +next_down_limit(X) when is_integer(X), X > -16#80000000 -> -16#80000000; +next_down_limit(X) when is_integer(X), X > -16#800000000000000 -> -16#800000000000000; +next_down_limit(_X) -> neg_inf. diff --git a/lib/hipe/icode/hipe_icode_split_arith.erl b/lib/hipe/icode/hipe_icode_split_arith.erl new file mode 100644 index 0000000000..d59f9247fa --- /dev/null +++ b/lib/hipe/icode/hipe_icode_split_arith.erl @@ -0,0 +1,553 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%------------------------------------------------------------------- +%% File : hipe_icode_split_arith.erl +%% Author : Tobias Lindahl <[email protected]> +%% Description : +%% +%% Created : 12 Nov 2003 by Tobias Lindahl <[email protected]> +%%------------------------------------------------------------------- +-module(hipe_icode_split_arith). + +-export([cfg/3]). + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). + +-define(MIN_RATIO, 0.005). + +%%------------------------------------------------------------------- + +-spec cfg(#cfg{}, mfa(), comp_options()) -> #cfg{}. + +cfg(Cfg, _MFA, Options) -> + Icode = hipe_icode_cfg:cfg_to_linear(Cfg), + case proplists:get_bool(split_arith_unsafe, Options) of + true -> make_split_unsafe(Icode); + _ -> + case preprocess(Icode) of + {do_not_split, _Ratio} -> + Cfg; + {split, _Ratio, Icode1} -> + NewCfg = split(Icode1), + %% hipe_icode_cfg:pp(NewCfg), + NewCfg + end + end. + +check_nofix_const([Arg1|Arg2]) -> + case hipe_icode:is_const(Arg1) of + true -> + Val1 = hipe_tagscheme:fixnum_val(hipe_icode:const_value(Arg1)), + case hipe_tagscheme:is_fixnum(Val1) of + true -> + check_nofix_const(Arg2); + false -> {no} + end; + false -> + check_nofix_const(Arg2) + end; +check_nofix_const([]) -> true. + +check_const([I|Left]) -> + case I of + #icode_call{} -> + case is_arith(I) of + true -> + Args = hipe_icode:call_args(I), + case check_nofix_const(Args) of + {no} -> {do_not_split}; + _ -> check_const(Left) + end; + _ -> check_const(Left) + end; + _ -> check_const(Left) + end; +check_const([]) -> {yes}. + +make_split_unsafe(Icode) -> + LinearCode = hipe_icode:icode_code(Icode), + NewLinearCode = change_unsafe(LinearCode), + NewIcode = hipe_icode:icode_code_update(Icode, NewLinearCode), + hipe_icode_cfg:linear_to_cfg(NewIcode). + +change_unsafe([I|Is]) -> + case I of + #icode_call{} -> + case is_arith_extra_unsafe(I) of + true -> + NewOp = arithop_to_extra_unsafe(hipe_icode:call_fun(I)), + NewI1 = hipe_icode:call_fun_update(I, NewOp), + [NewI1|change_unsafe(Is)]; + false -> + [I|change_unsafe(Is)] + end; + _ -> + [I|change_unsafe(Is)] + end; +change_unsafe([]) -> []. + +preprocess(Icode) -> + LinearCode = hipe_icode:icode_code(Icode), + case check_const(LinearCode) of + {do_not_split} -> %%io:format("NO FIXNUM....."), + {do_not_split, 1.9849}; % Ratio val is ignored + _ -> + {NofArith, NofIns, NewLinearCode} = preprocess_code(LinearCode), + case NofArith / NofIns of + X when X >= ?MIN_RATIO -> + NewIcode = hipe_icode:icode_code_update(Icode, NewLinearCode), + {split, X, NewIcode}; + Y -> + {do_not_split, Y} + end + end. + +preprocess_code([H|Code]) -> + preprocess_code(Code, 0, 0, [H]). + +preprocess_code([I|Left], NofArith, NofIns, CodeAcc = [PrevI|_]) -> + case I of + #icode_call{} -> + case is_arith(I) of + true -> + %% Note that we need to put these instructions in a separate + %% basic block since we need the ability to fail to these + %% instructions, but also fail from them. The basic block + %% merger will take care of unnecessary splits. + + %% If call is an arithmetic operation replace the operation + %% with the specified replacement operator. + NewOp = arithop_to_split(hipe_icode:call_fun(I)), + NewI = hipe_icode:call_fun_update(I, NewOp), + case hipe_icode:is_label(PrevI) of + true -> + case (Left =:= []) orelse hipe_icode:is_label(hd(Left)) of + true -> + preprocess_code(Left, NofArith+1, NofIns+1, [NewI|CodeAcc]); + false -> + NewLabel = hipe_icode:mk_new_label(), + NewLabelName = hipe_icode:label_name(NewLabel), + NewI1 = hipe_icode:call_set_continuation(NewI, NewLabelName), + preprocess_code(Left, NofArith+1, NofIns+1, + [NewLabel, NewI1|CodeAcc]) + end; + false -> + RevPreCode = + case hipe_icode:is_branch(PrevI) of + true -> + [hipe_icode:mk_new_label()]; + false -> + NewLabel1 = hipe_icode:mk_new_label(), + NewLabelName1 = hipe_icode:label_name(NewLabel1), + [NewLabel1, hipe_icode:mk_goto(NewLabelName1)] + end, + case (Left =:= []) orelse hipe_icode:is_label(hd(Left)) of + true -> + preprocess_code(Left, NofArith+1, NofIns+1, + [NewI|RevPreCode] ++ CodeAcc); + false -> + NewLabel2 = hipe_icode:mk_new_label(), + NewLabelName2 = hipe_icode:label_name(NewLabel2), + NewI1 = hipe_icode:call_set_continuation(NewI, NewLabelName2), + preprocess_code(Left, NofArith+1, NofIns+1, + [NewLabel2, NewI1|RevPreCode] ++ CodeAcc) + end + end; + false -> + preprocess_code(Left, NofArith, NofIns + 1, [I|CodeAcc]) + end; + #icode_label{} -> + %% Don't count labels as instructions. + preprocess_code(Left, NofArith, NofIns, [I|CodeAcc]); + _ -> + preprocess_code(Left, NofArith, NofIns+1, [I|CodeAcc]) + end; +preprocess_code([], NofArith, NofIns, CodeAcc) -> + {NofArith, NofIns, lists:reverse(CodeAcc)}. + +split(Icode) -> + LinearCode = hipe_icode:icode_code(Icode), + %% create a new icode label for each existing icode label + %% create mappings, NewToOld and OldToNew. + AllLabels = lists:foldl(fun(I, Acc) -> + case hipe_icode:is_label(I) of + true -> [hipe_icode:label_name(I)|Acc]; + false -> Acc + end + end, [], LinearCode), + {OldToNewMap, NewToOldMap} = new_label_maps(AllLabels), + + %% the call below doubles the number of basic blocks with the new + %% labels instead of the old. + + NewLinearCode = map_code(LinearCode, OldToNewMap), + NewIcode = hipe_icode:icode_code_update(Icode, NewLinearCode), + NewCfg = hipe_icode_cfg:linear_to_cfg(NewIcode), + NewCfg2 = + insert_tests(NewCfg, [gb_trees:get(X, OldToNewMap) || X<-AllLabels], + NewToOldMap, OldToNewMap), + %% io:format("split(Cfg): Inserting testsL Done\n", []), + NewCfg2. + +map_code(OldCode, LabelMap) -> + AddedCode = map_code(OldCode, none, LabelMap, []), + OldCode ++ AddedCode. + +map_code([I|Left], ArithFail, LabelMap, Acc) -> + case I of + #icode_call{} -> + case is_arith(I) of + true -> + case hipe_icode:defines(I) of + []-> + map_code(Left, ArithFail, LabelMap, [redirect(I, LabelMap)|Acc]); + _ -> + NewOp = split_to_unsafe(I), + NewI1 = hipe_icode:call_fun_update(I, NewOp), + NewI2 = redirect(NewI1, LabelMap), + NewI3 = hipe_icode:call_set_fail_label(NewI2, ArithFail), + map_code(Left, ArithFail, LabelMap, [NewI3|Acc]) + end; + false -> + map_code(Left, ArithFail, LabelMap, [redirect(I, LabelMap)|Acc]) + end; + #icode_label{} -> + LabelName = hipe_icode:label_name(I), + NewLabel = hipe_icode:mk_label(gb_trees:get(LabelName, LabelMap)), + map_code(Left, LabelName, LabelMap, [NewLabel|Acc]); + _ -> + map_code(Left, ArithFail, LabelMap, [redirect(I, LabelMap)|Acc]) + end; +map_code([], _ArithFail, _LabelMap, Acc) -> + lists:reverse(Acc). + +insert_tests(Cfg, Labels,NewToOldMap, OldToNewMap) -> + InfoMap = infomap_init(Labels), + %%io:format("insert_tests/3: Finding testpoints ...\n", []), + NewInfoMap = find_testpoints(Cfg, Labels, InfoMap), + %%io:format("insert_tests/3: Finding testpoints: Done\n", []), + %%io:format("insert_tests/3: Infomap: ~w\n", [gb_trees:to_list(NewInfoMap)]), + make_tests(Cfg, NewInfoMap, NewToOldMap, OldToNewMap). + +find_testpoints(Cfg, Labels, InfoMap) -> + case find_testpoints(Labels, InfoMap, Cfg, false) of + {dirty, NewInfoMap} -> + %%io:format("find_testpoints/3: Looping\n", []), + find_testpoints(Cfg, Labels, NewInfoMap); + fixpoint -> + InfoMap + end. + +find_testpoints([Lbl|Left], InfoMap, Cfg, Dirty) -> + Code = hipe_bb:code(hipe_icode_cfg:bb(Cfg, Lbl)), + InfoOut = join_info(hipe_icode_cfg:succ(Cfg, Lbl), InfoMap), + OldInfoIn = infomap_get_all(Lbl, InfoMap), + NewInfoIn = traverse_code(lists:reverse(Code), InfoOut), + case (gb_sets:is_subset(OldInfoIn, NewInfoIn) andalso + gb_sets:is_subset(NewInfoIn, OldInfoIn)) of + true -> + find_testpoints(Left, InfoMap, Cfg, Dirty); + false -> + %%io:format("find_testpoints/4: Label: ~w: OldMap ~w\nNewMap: ~w\n", + %% [Lbl, gb_sets:to_list(OldInfoIn), gb_sets:to_list(NewInfoIn)]), + NewInfoMap = gb_trees:update(Lbl, NewInfoIn, InfoMap), + find_testpoints(Left, NewInfoMap, Cfg, true) + end; +find_testpoints([], InfoMap, _Cfg, Dirty) -> + if Dirty -> {dirty, InfoMap}; + true -> fixpoint + end. + +traverse_code([I|Left], Info) -> + NewInfo = kill_defines(I, Info), + case I of + #icode_call{} -> + case is_unsafe_arith(I) of + true -> + %% The dst is sure to be a fixnum. Remove the 'killed' mark. + Dst = hd(hipe_icode:call_dstlist(I)), + NewInfo1 = gb_sets:delete_any({killed, Dst}, NewInfo), + NewInfo2 = + gb_sets:union(NewInfo1, gb_sets:from_list(hipe_icode:uses(I))), + traverse_code(Left, NewInfo2); + false -> + traverse_code(Left, NewInfo) + end; + #icode_move{} -> + Dst = hipe_icode:move_dst(I), + case gb_sets:is_member(Dst, Info) of + true -> + %% The dst is an argument to an arith op. Transfer the test + %% to the src and remove the 'killed' mark from the dst. + NewInfo1 = gb_sets:delete({killed, Dst}, NewInfo), + Src = hipe_icode:move_src(I), + case hipe_icode:is_const(Src) of + true -> + traverse_code(Left, NewInfo1); + false -> + NewInfo2 = gb_sets:add(Src, NewInfo1), + traverse_code(Left, NewInfo2) + end; + false -> + traverse_code(Left, NewInfo) + end; + _ -> + traverse_code(Left, NewInfo) + end; +traverse_code([], Info) -> + Info. + +kill_defines(I, Info) -> + Defines = hipe_icode:defines(I), + case [X || X<-Defines, gb_sets:is_member(X, Info)] of + [] -> + Info; + List -> + TmpInfo = gb_sets:difference(Info, gb_sets:from_list(List)), + gb_sets:union(gb_sets:from_list([{killed, X} || X <- List]), TmpInfo) + end. + +make_tests(Cfg, InfoMap, NewToOldMap, OldToNewMap) -> + %%io:format("make_tests 0:\n",[]), + WorkList = make_worklist(gb_trees:keys(NewToOldMap), InfoMap, + NewToOldMap, Cfg, []), + %%io:format("make_tests 1:Worklist: ~w\n",[WorkList]), + NewCfg = make_tests(WorkList, Cfg), + %%io:format("make_tests 2\n",[]), + %% If the arguments to this function are used in unsafe arith + %% they should be marked as killed by a new start block. + Args = hipe_icode_cfg:params(NewCfg), + Start = hipe_icode_cfg:start_label(NewCfg), + AltStart = gb_trees:get(Start, OldToNewMap), + UnsafeIn = gb_sets:to_list(infomap_get(AltStart, InfoMap)), + case [X || X <- UnsafeIn, Y <- Args, X =:= Y] of + [] -> + hipe_icode_cfg:start_label_update(NewCfg, AltStart); + KilledArgs -> + NewStart = hipe_icode:label_name(hipe_icode:mk_new_label()), + NewCfg1 = insert_test_block(NewStart, AltStart, Start, + KilledArgs, NewCfg), + hipe_icode_cfg:start_label_update(NewCfg1, NewStart) + end. + +make_worklist([Lbl|Left], InfoMap, LabelMap, Cfg, Acc) -> + Vars = infomap_get_killed(Lbl, InfoMap), + case gb_sets:is_empty(Vars) of + true -> make_worklist(Left, InfoMap, LabelMap, Cfg, Acc); + false -> + %% io:format("make_worklist 1 ~w\n", [Vars]), + NewAcc0 = + [{Lbl, Succ, gb_trees:get(Succ, LabelMap), + gb_sets:intersection(infomap_get(Succ, InfoMap), Vars)} + || Succ <- hipe_icode_cfg:succ(Cfg, Lbl)], + NewAcc = [{Label, Succ, FailLbl, gb_sets:to_list(PrunedVars)} + || {Label, Succ, FailLbl, PrunedVars} <- NewAcc0, + gb_sets:is_empty(PrunedVars) =:= false] ++ Acc, + %% io:format("make_worklist 2\n", []), + make_worklist(Left, InfoMap, LabelMap, Cfg, NewAcc) + end; +make_worklist([], _InfoMap, _LabelMap, _Cfg, Acc) -> + Acc. + +make_tests([{FromLbl, ToLbl, FailLbl, Vars}|Left], Cfg) -> + NewLbl = hipe_icode:label_name(hipe_icode:mk_new_label()), + TmpCfg = insert_test_block(NewLbl, ToLbl, FailLbl, Vars, Cfg), + NewCfg = hipe_icode_cfg:redirect(TmpCfg, FromLbl, ToLbl, NewLbl), + make_tests(Left, NewCfg); +make_tests([], Cfg) -> + Cfg. + +insert_test_block(NewLbl, Succ, FailLbl, Vars, Cfg) -> + Code = [hipe_icode:mk_type(Vars, fixnum, Succ, FailLbl, 0.99)], + BB = hipe_bb:mk_bb(Code), + hipe_icode_cfg:bb_add(Cfg, NewLbl, BB). + +infomap_init(Labels) -> + infomap_init(Labels, gb_trees:empty()). + +infomap_init([Lbl|Left], Map) -> + infomap_init(Left, gb_trees:insert(Lbl, gb_sets:empty(), Map)); +infomap_init([], Map) -> + Map. + +join_info(Labels, Map) -> + join_info(Labels, Map, gb_sets:empty()). + +join_info([Lbl|Left], Map, Set) -> + join_info(Left, Map, gb_sets:union(Set, infomap_get(Lbl, Map))); +join_info([], _Map, Set) -> + Set. + +infomap_get(Lbl, Map) -> + case gb_trees:lookup(Lbl, Map) of + none -> gb_sets:empty(); + {value, Val} -> + gb_sets:filter(fun(X) -> case X of + {killed, _} -> false; + _ -> true + end + end, + Val) + end. + +infomap_get_all(Lbl, Map) -> + case gb_trees:lookup(Lbl, Map) of + none -> gb_sets:empty(); + {value, Val} -> Val + end. + +infomap_get_killed(Lbl, Map) -> + case gb_trees:lookup(Lbl, Map) of + none -> gb_sets:empty(); + {value, Val} -> + Fun = fun(X, Acc) -> + case X of + {killed, Var} -> [Var|Acc]; + _ -> Acc + end + end, + gb_sets:from_list(lists:foldl(Fun, [], gb_sets:to_list(Val))) + end. + +%%%------------------------------------------------------------------- +%%% General replace of '+'/'-' to super safe version + +arithop_to_split(Op) -> + case Op of + '+' -> gen_add; + '-' -> gen_sub; + _ -> Op + end. + +%%%------------------------------------------------------------------- +%%% Check if it's an arith op that needs to be split + +is_arith(I) -> + case hipe_icode:call_fun(I) of + '+' -> true; + '-' -> true; + gen_add -> true; + gen_sub -> true; + 'bor' -> true; + 'bxor' -> true; + 'bsr' -> + %% Need to check that the second argument is a non-negative + %% fixnum. We only allow for constants to simplify things. + [_, Arg2] = hipe_icode:args(I), + hipe_icode:is_const(Arg2) andalso (hipe_icode:const_value(Arg2) >= 0); + 'bsl' -> + %% There are major issues with bsl since it doesn't flag + %% overflow. We cannot allow for this in this optimization pass. + false; + 'bnot' -> true; + 'band' -> true; + _ -> false + end. + +%%%------------------------------------------------------------------- + +is_unsafe_arith(I) -> + case hipe_icode:call_fun(I) of + unsafe_add -> true; + unsafe_sub -> true; + unsafe_bor -> true; + unsafe_bxor -> true; + unsafe_bsr -> true; + unsafe_bsl -> true; + unsafe_bnot -> true; + unsafe_band -> true; + _ -> false + end. + +split_to_unsafe(I) -> + case hipe_icode:call_fun(I) of + gen_add -> unsafe_add; + gen_sub -> unsafe_sub; + 'bor' -> unsafe_bor; + 'bxor' -> unsafe_bxor; + 'bsr' -> + case is_arith(I) of + true -> unsafe_bsr; + false -> 'bsr' + end; + 'bsl' -> + %% There are major issues with bsl since it doesn't flag + %% overflow. We cannot allow for this in this optimization pass. + 'bsl'; + 'bnot' -> unsafe_bnot; + 'band' -> unsafe_band; + Op -> Op + end. + +%%%------------------------------------------------------------------- +%%% FLAG = split_arith_unsafe + +is_arith_extra_unsafe(I) -> + case hipe_icode:call_fun(I) of + '+' -> true; + '-' -> true; + 'bor' -> true; + 'bxor' -> true; + 'bsr' -> is_arith(I); + 'bsl' -> false; %% See comment in is_arith/1 + 'bnot' -> true; + 'band' -> true; + _ -> false + end. + +arithop_to_extra_unsafe(Op) -> + case Op of + '+' -> extra_unsafe_add; + '-' -> extra_unsafe_sub; + 'bor' -> unsafe_bor; + 'bxor' -> unsafe_bxor; + 'bsr' -> unsafe_bsr; + 'bsl' -> 'bsl'; %% See comment in split_to_unsafe/1 + 'bnot' -> unsafe_bnot; + 'band' -> unsafe_band + end. + +%%%------------------------------------------------------------------- + +redirect(I, LabelMap) -> + case hipe_icode:successors(I) of + [] -> I; + Successors -> + RedirectMap = [{X, gb_trees:get(X, LabelMap)} || X <- Successors], + redirect_1(RedirectMap, I) + end. + +redirect_1([{From, To}|Left], I) -> + redirect_1(Left, hipe_icode:redirect_jmp(I, From, To)); +redirect_1([], I) -> + I. + +new_label_maps(Labels) -> + new_label_maps(Labels, gb_trees:empty(), gb_trees:empty()). + +new_label_maps([Lbl|Left], Map1, Map2) -> + NewLabel = hipe_icode:label_name(hipe_icode:mk_new_label()), + NewMap1 = gb_trees:insert(Lbl, NewLabel, Map1), + NewMap2 = gb_trees:insert(NewLabel, Lbl, Map2), + new_label_maps(Left, NewMap1, NewMap2); +new_label_maps([], Map1, Map2) -> + {Map1, Map2}. diff --git a/lib/hipe/icode/hipe_icode_ssa.erl b/lib/hipe/icode/hipe_icode_ssa.erl new file mode 100755 index 0000000000..719d5d8f45 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_ssa.erl @@ -0,0 +1,98 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%---------------------------------------------------------------------- +%% File : hipe_icode_ssa.erl +%% Author : +%% Created : +%% Purpose : Provides interface functions for converting Icode into +%% SSA form and back using the generic SSA converter. +%%---------------------------------------------------------------------- + +-module(hipe_icode_ssa). + +%% The following defines are needed by the included file below +-define(CODE, hipe_icode). +-define(CFG, hipe_icode_cfg). +-define(LIVENESS, hipe_icode_liveness). +-define(LIVENESS_NEEDED, true). + +-include("hipe_icode.hrl"). +-include("../ssa/hipe_ssa.inc"). + +%% Declarations for exported functions which are Icode-specific. +-spec ssa_liveness__analyze(#cfg{}) -> gb_tree(). +-spec ssa_liveness__livein(_, icode_lbl()) -> [#icode_variable{}]. +%% -spec ssa_liveness__livein(_, icode_lbl(), _) -> [#icode_var{}]. + +%%---------------------------------------------------------------------- +%% Auxiliary operations which seriously differ between Icode and RTL. +%%---------------------------------------------------------------------- + +defs_to_rename(Statement) -> + hipe_icode:defines(Statement). + +uses_to_rename(Statement) -> + hipe_icode:uses(Statement). + +liveout_no_succ() -> + []. + +%%---------------------------------------------------------------------- + +reset_var_indx() -> + hipe_gensym:set_var(icode, 0). + +%%---------------------------------------------------------------------- + +is_fp_temp(Temp) -> + hipe_icode:is_fvar(Temp). + +mk_new_fp_temp() -> + hipe_icode:mk_new_fvar(). + +%%---------------------------------------------------------------------- +%% Procedure : makePhiMove +%% Purpose : Create an ICode-specific version of a move instruction +%% depending on the type of the arguments. +%% Arguments : Dst, Src - the arguments of a Phi instruction that is +%% to be moved up the predecessor block as part +%% of the SSA unconvert phase. +%% Returns : Code +%%---------------------------------------------------------------------- + +makePhiMove(Dst, Src) -> + case hipe_icode:is_fvar(Dst) of + false -> + case hipe_icode:is_fvar(Src) of + false -> + hipe_icode:mk_move(Dst, Src); + true -> + hipe_icode:mk_primop([Dst], unsafe_tag_float, [Src]) + end; + true -> + case hipe_icode:is_fvar(Src) of + true -> + hipe_icode:mk_move(Dst, Src); + false -> + hipe_icode:mk_primop([Dst], conv_to_float, [Src]) + end + end. + +%%---------------------------------------------------------------------- diff --git a/lib/hipe/icode/hipe_icode_ssa_const_prop.erl b/lib/hipe/icode/hipe_icode_ssa_const_prop.erl new file mode 100644 index 0000000000..f1640b1cee --- /dev/null +++ b/lib/hipe/icode/hipe_icode_ssa_const_prop.erl @@ -0,0 +1,728 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% ============================================================================ +%% Filename : hipe_icode_ssa_const_prop.erl +%% Authors : Daniel Luna, Erik Andersson +%% Purpose : Perform sparse conditional constant propagation on Icode. +%% Notes : Works on the control-flow graph. +%% +%% History : * 2003-03-05: Created. +%% * 2003-08-11: Passed simple testsuite. +%% * 2003-10-01: Passed compiler testsuite. +%% ============================================================================ +%% +%% Exports: propagate/1. +%% +%% ============================================================================ +%% +%% TODO: +%% +%% Take care of failures in call and replace operation with appropriate +%% failure. +%% +%% Handle ifs with non-binary operators +%% +%% We want multisets for easier (and faster) creation of env->ssa_edges +%% +%% Maybe do things with begin_handler, begin_try if possible +%% +%% Propagation of constant arguments when some of the arguments are bottom +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(hipe_icode_ssa_const_prop). +-export([propagate/1]). + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). +-include("hipe_icode_primops.hrl"). + +-define(CONST_PROP_MSG(Str,L), ok). +%%-define(CONST_PROP_MSG(Str,L), io:format(Str,L)). + +%%-define(DEBUG, 1). + +%%----------------------------------------------------------------------------- +%% Include stuff shared between SCCP on Icode and RTL. +%% NOTE: Needs to appear after DEBUG is possibly defined. +%%----------------------------------------------------------------------------- + +-define(CODE, hipe_icode). +-define(CFG, hipe_icode_cfg). + +-include("../ssa/hipe_ssa_const_prop.inc"). + +%%----------------------------------------------------------------------------- + +visit_expression(Instruction, Environment) -> + EvaluatedArguments = [lookup_lattice_value(Argument, Environment) + || Argument <- hipe_icode:args(Instruction)], + case Instruction of + #icode_move{} -> + visit_move (Instruction, EvaluatedArguments, Environment); + #icode_if{} -> + visit_if (Instruction, EvaluatedArguments, Environment); + #icode_goto{} -> + visit_goto (Instruction, EvaluatedArguments, Environment); + #icode_type{} -> + visit_type (Instruction, EvaluatedArguments, Environment); + #icode_call{} -> + visit_call (Instruction, EvaluatedArguments, Environment); + #icode_switch_val{} -> + visit_switch_val (Instruction, EvaluatedArguments, Environment); + #icode_switch_tuple_arity{} -> + visit_switch_tuple_arity(Instruction, EvaluatedArguments, Environment); + #icode_begin_handler{} -> + visit_begin_handler (Instruction, EvaluatedArguments, Environment); + #icode_begin_try{} -> + visit_begin_try (Instruction, EvaluatedArguments, Environment); + #icode_fail{} -> + visit_fail (Instruction, EvaluatedArguments, Environment); + _ -> + %% label, end_try, comment, return, + {[], [], Environment} + end. + +%%----------------------------------------------------------------------------- + +visit_begin_try(Instruction, [], Environment) -> + Label = hipe_icode:begin_try_label(Instruction), + Successor = hipe_icode:begin_try_successor(Instruction), + {[Label, Successor], [], Environment}. + +%%----------------------------------------------------------------------------- + +visit_begin_handler(Instruction, _Arguments, Environment) -> + Destinations = hipe_icode:begin_handler_dstlist(Instruction), + {Environment1, SSAWork} = + lists:foldl(fun (Dst, {Env0,Work0}) -> + {Env, Work} = update_lattice_value({Dst, bottom}, Env0), + {Env, Work ++ Work0} + end, + {Environment, []}, + Destinations), + {[], SSAWork, Environment1}. + +%%----------------------------------------------------------------------------- + +visit_switch_val(Instruction, [Argument], Environment) -> + Cases = hipe_icode:switch_val_cases(Instruction), + FailLabel = hipe_icode:switch_val_fail_label(Instruction), + case Argument of + bottom -> + FlowWork = [Label || {_Value, Label} <- Cases], + FlowWork1 = [FailLabel | FlowWork], + {FlowWork1, [], Environment}; + _ -> + Target = get_switch_target(Cases, Argument, FailLabel), + {[Target], [], Environment} + end. + +%%----------------------------------------------------------------------------- + +visit_switch_tuple_arity(Instruction, [Argument], Environment) -> + Cases = hipe_icode:switch_tuple_arity_cases(Instruction), + FailLabel = hipe_icode:switch_tuple_arity_fail_label(Instruction), + case Argument of + bottom -> + FlowWork = [Label || {_Value, Label} <- Cases], + FlowWork1 = [FailLabel | FlowWork], + {FlowWork1, [], Environment}; + Constant -> + UnTagged = hipe_icode:const_value(Constant), + case is_tuple(UnTagged) of + true -> + Target = get_switch_target(Cases, tuple_size(UnTagged), FailLabel), + {[Target], [], Environment}; + false -> + {[FailLabel], [], Environment} + end + end. + +%%----------------------------------------------------------------------------- + +get_switch_target([], _Argument, FailLabel) -> + FailLabel; +get_switch_target([{CaseValue, Target} | CaseList], Argument, FailLabel) -> + case CaseValue =:= Argument of + true -> + Target; + false -> + get_switch_target(CaseList, Argument, FailLabel) + end. + +%%----------------------------------------------------------------------------- + +visit_move(Instruction, [SourceValue], Environment) -> + Destination = hipe_icode:move_dst(Instruction), + {Environment1, SSAWork} = update_lattice_value({Destination, SourceValue}, + Environment), + {[], SSAWork, Environment1}. + +%%----------------------------------------------------------------------------- + +visit_if(Instruction, Arguments, Environment) -> + FlowWork = + case evaluate_if(hipe_icode:if_op(Instruction), Arguments) of + true -> + TrueLabel = hipe_icode:if_true_label(Instruction), + [TrueLabel]; + false -> + FalseLabel = hipe_icode:if_false_label(Instruction), + [FalseLabel]; + bottom -> + TrueLabel = hipe_icode:if_true_label(Instruction), + FalseLabel = hipe_icode:if_false_label(Instruction), + [TrueLabel, FalseLabel] + end, + {FlowWork, [], Environment}. + +%%----------------------------------------------------------------------------- + +visit_goto(Instruction, _Arguments, Environment) -> + GotoLabel = hipe_icode:goto_label(Instruction), + FlowWork = [GotoLabel], + {FlowWork, [], Environment}. + +%%----------------------------------------------------------------------------- + +visit_fail(Instruction, _Arguments, Environment) -> + FlowWork = hipe_icode:successors(Instruction), + {FlowWork, [], Environment}. + +%%----------------------------------------------------------------------------- + +visit_type(Instruction, Values, Environment) -> + FlowWork = + case evaluate_type(hipe_icode:type_test(Instruction), Values) of + true -> + TrueLabel = hipe_icode:type_true_label(Instruction), + [TrueLabel]; + false -> + FalseLabel = hipe_icode:type_false_label(Instruction), + [FalseLabel]; + bottom -> + TrueLabel = hipe_icode:type_true_label(Instruction), + FalseLabel = hipe_icode:type_false_label(Instruction), + [TrueLabel, FalseLabel] + end, + {FlowWork, [], Environment}. + +%%----------------------------------------------------------------------------- + +visit_call(Ins, Args, Environment) -> + Dsts = hipe_icode:call_dstlist(Ins), + Fun = hipe_icode:call_fun(Ins), + Fail = call_fail_labels(Ins), + Cont = call_continuation_labels(Ins), + visit_call(Dsts, Args, Fun, Cont, Fail, Environment). + +visit_call(Dst, Args, Fun, Cont, Fail, Environment) -> + {FlowWork, {Environment1, SSAWork}} = + case lists:any(fun(X) -> (X =:= bottom) end, Args) of + true -> + {Fail ++ Cont, update_lattice_value({Dst, bottom}, Environment)}; + false -> + ConstArgs = [hipe_icode:const_value(Argument) || Argument <- Args], + try evaluate_call_or_enter(ConstArgs, Fun) of + bottom -> + {Fail ++ Cont, update_lattice_value({Dst, bottom}, Environment)}; + Constant -> + {Cont, update_lattice_value({Dst, Constant}, Environment)} + catch + _:_ -> + {Fail, update_lattice_value({Dst, bottom}, Environment)} + end + end, + {FlowWork, SSAWork, Environment1}. + +%%----------------------------------------------------------------------------- + +call_fail_labels(I) -> + case hipe_icode:call_fail_label(I) of + [] -> []; + Label -> [Label] + end. + +call_continuation_labels(I) -> + case hipe_icode:call_continuation(I) of + [] -> []; + Label -> [Label] + end. + +%%----------------------------------------------------------------------------- + +%% Unary calls +evaluate_call_or_enter([Argument], Fun) -> + case Fun of + mktuple -> + hipe_icode:mk_const(list_to_tuple([Argument])); + unsafe_untag_float -> + hipe_icode:mk_const(float(Argument)); + conv_to_float -> + hipe_icode:mk_const(float(Argument)); + fnegate -> + hipe_icode:mk_const(0.0 - Argument); + 'bnot' -> + hipe_icode:mk_const(Argument); + #unsafe_element{index=N} -> + hipe_icode:mk_const(element(N, Argument)); + {erlang, hd, 1} -> + hipe_icode:mk_const(hd(Argument)); + {erlang, tl, 1} -> + hipe_icode:mk_const(tl(Argument)); + {erlang, atom_to_list, 1} -> + hipe_icode:mk_const(atom_to_list(Argument)); + {erlang, list_to_atom, 1} -> + hipe_icode:mk_const(list_to_atom(Argument)); + {erlang, tuple_to_list, 1} -> + hipe_icode:mk_const(tuple_to_list(Argument)); + {erlang, list_to_tuple, 1} -> + hipe_icode:mk_const(list_to_tuple(Argument)); + {erlang, length, 1} -> + hipe_icode:mk_const(length(Argument)); + {erlang, size, 1} -> + hipe_icode:mk_const(size(Argument)); + {erlang, bit_size, 1} -> + hipe_icode:mk_const(bit_size(Argument)); + {erlang, byte_size, 1} -> + hipe_icode:mk_const(byte_size(Argument)); + {erlang, tuple_size, 1} -> + hipe_icode:mk_const(tuple_size(Argument)); + {erlang, abs, 1} -> + hipe_icode:mk_const(abs(Argument)); + {erlang, round, 1} -> + hipe_icode:mk_const(round(Argument)); + {erlang, trunc, 1} -> + hipe_icode:mk_const(trunc(Argument)); + _ -> + bottom + end; +%% Binary calls +evaluate_call_or_enter([Argument1,Argument2], Fun) -> + case Fun of + '+' -> + hipe_icode:mk_const(Argument1 + Argument2); + '-' -> + hipe_icode:mk_const(Argument1 - Argument2); + '*' -> + hipe_icode:mk_const(Argument1 * Argument2); + '/' -> + hipe_icode:mk_const(Argument1 / Argument2); + 'band' -> + hipe_icode:mk_const(Argument1 band Argument2); + 'bor' -> + hipe_icode:mk_const(Argument1 bor Argument2); + 'bsl' -> + hipe_icode:mk_const(Argument1 bsl Argument2); + 'bsr' -> + hipe_icode:mk_const(Argument1 bsr Argument2); + 'bxor' -> + hipe_icode:mk_const(Argument1 bxor Argument2); + fp_add -> + hipe_icode:mk_const(float(Argument1 + Argument2)); + fp_sub -> + hipe_icode:mk_const(float(Argument1 - Argument2)); + fp_mul -> + hipe_icode:mk_const(float(Argument1 * Argument2)); + fp_div -> + hipe_icode:mk_const(Argument1 / Argument2); + cons -> + hipe_icode:mk_const([Argument1 | Argument2]); + mktuple -> + hipe_icode:mk_const(list_to_tuple([Argument1,Argument2])); + #unsafe_update_element{index=N} -> + hipe_icode:mk_const(setelement(N, Argument1, Argument2)); + {erlang, '++', 2} -> + hipe_icode:mk_const(Argument1 ++ Argument2); + {erlang, '--', 2} -> + hipe_icode:mk_const(Argument1 -- Argument2); + {erlang, 'div', 2} -> + hipe_icode:mk_const(Argument1 div Argument2); + {erlang, 'rem', 2} -> + hipe_icode:mk_const(Argument1 rem Argument2); + {erlang, append_element, 2} -> + hipe_icode:mk_const(erlang:append_element(Argument1, Argument2)); + {erlang, element, 2} -> + hipe_icode:mk_const(element(Argument1, Argument2)); + _Other -> + %% io:format("In ~w(~w,~w)~n", [_Other,Argument1,Argument2]), + bottom + end; + +%% The rest of the calls +evaluate_call_or_enter(Arguments, Fun) -> + case Fun of + mktuple -> + hipe_icode:mk_const(list_to_tuple(Arguments)); + {erlang, setelement, 3} -> + [Argument1, Argument2, Argument3] = Arguments, + hipe_icode:mk_const(setelement(Argument1, Argument2, Argument3)); + _ -> + bottom + end. + +%%----------------------------------------------------------------------------- + +evaluate_if(Conditional, [Argument1, Argument2]) -> + case ((Argument1 =:= bottom) or (Argument2 =:= bottom)) of + true -> bottom; + false -> evaluate_if_const(Conditional, Argument1, Argument2) + end; +evaluate_if(_Conditional, _Arguments) -> + bottom. + +%%----------------------------------------------------------------------------- + +evaluate_if_const(Conditional, Argument1, Argument2) -> + case Conditional of + '=:=' -> Argument1 =:= Argument2; + '==' -> Argument1 == Argument2; + '=/=' -> Argument1 =/= Argument2; + '/=' -> Argument1 /= Argument2; + '<' -> Argument1 < Argument2; + '>=' -> Argument1 >= Argument2; + '=<' -> Argument1 =< Argument2; + '>' -> Argument1 > Argument2; + _ -> bottom + end. + +%%----------------------------------------------------------------------------- + +evaluate_type(Type, Vals) -> + case [X || X <- Vals, X =:= bottom] of + [] -> evaluate_type_const(Type, Vals); + _ -> bottom + end. + +%%----------------------------------------------------------------------------- + +evaluate_type_const(Type, [Arg|Left]) -> + Test = + case {Type, hipe_icode:const_value(Arg)} of + {nil, [] } -> true; + {nil, _ } -> false; + {cons, [_|_]} -> true; + {cons, _ } -> false; + {{tuple, N}, T} when tuple_size(T) =:= N -> true; + {atom, A} when is_atom(A) -> true; + {{atom, A}, A} when is_atom(A) -> true; + {{record, A, S}, R} when tuple_size(R) =:= S, + element(1, R) =:= A -> true; + {{record, _, _}, _} -> false; + _ -> bottom + end, + case Test of + bottom -> bottom; + false -> false; + true -> evaluate_type_const(Type, Left) + end; +evaluate_type_const(_Type, []) -> + true. + +%%----------------------------------------------------------------------------- +%% Icode-specific code below +%%----------------------------------------------------------------------------- + +update_instruction(Instruction, Environment) -> + case Instruction of + #icode_call{} -> + update_call(Instruction, Environment); + #icode_enter{} -> + update_enter(Instruction, Environment); + #icode_if{} -> + update_if(Instruction, Environment); + #icode_move{} -> + update_move(Instruction, Environment); + #icode_phi{} -> + update_phi(Instruction, Environment); + #icode_switch_val{} -> + update_switch_val(Instruction, Environment); + #icode_type{} -> + update_type(Instruction, Environment); + #icode_switch_tuple_arity{} -> + update_switch_tuple_arity(Instruction, Environment); + _ -> + %% goto, comment, label, return, begin_handler, end_try, + %% begin_try, fail + %% We could but don't handle: catch?, fail? + [Instruction] + end. + +%%----------------------------------------------------------------------------- + +update_call(Instruction, Environment) -> + DestList = hipe_icode:call_dstlist(Instruction), + case DestList of + [Destination] -> + case lookup_lattice_value(Destination, Environment) of + bottom -> + NewArguments = update_arguments( + hipe_icode:call_args(Instruction), + Environment), + [hipe_icode:call_args_update(Instruction, NewArguments)]; + X -> + NewInstructions = + case is_call_to_fp_op(Instruction) of + true -> + TmpIns = + hipe_icode:call_fun_update(Instruction, unsafe_untag_float), + [hipe_icode:call_args_update(TmpIns, [X])]; + false -> + case hipe_icode:call_continuation(Instruction) of + [] -> + [hipe_icode:mk_move(Destination, X)]; + ContinuationLabel -> + [hipe_icode:mk_move(Destination, X), + hipe_icode:mk_goto(ContinuationLabel)] + end + end, + ?CONST_PROP_MSG("call: ~w ---> ~w\n", + [Instruction, NewInstructions]), + NewInstructions + end; +%% %% [] -> %% No destination; we don't touch this +%% [] -> +%% NewArguments = update_arguments(hipe_icode:call_args(Instruction), +%% Environment), +%% [hipe_icode:call_args_update(Instruction, NewArguments)]; + %% List-> %% Means register allocation; not implemented at this point + _ -> + [Instruction] + end. + +%%----------------------------------------------------------------------------- + +is_call_to_fp_op(Instruction) -> + case hipe_icode:call_fun(Instruction) of + fp_add -> true; + fp_sub -> true; + fp_mul -> true; + fp_div -> true; + fnegate -> true; + conv_to_float -> true; + unsafe_untag_float -> true; + _ -> false + end. + +%%----------------------------------------------------------------------------- + +update_enter(Instruction, Environment) -> + Args = hipe_icode:enter_args(Instruction), + EvalArgs = [lookup_lattice_value(X, Environment) || X <- Args], + Fun = hipe_icode:enter_fun(Instruction), + case lists:any(fun(X) -> (X =:= bottom) end, EvalArgs) of + true -> + update_enter_arguments(Instruction, Environment); + false -> + ConstVals = [hipe_icode:const_value(X) || X <- EvalArgs], + try evaluate_call_or_enter(ConstVals, Fun) of + bottom -> + update_enter_arguments(Instruction, Environment); + Const -> + Dst = hipe_icode:mk_new_var(), + [hipe_icode:mk_move(Dst, Const), + hipe_icode:mk_return([Dst])] + catch + _:_ -> + update_enter_arguments(Instruction, Environment) + end + end. + +update_enter_arguments(Instruction, Env) -> + NewArguments = update_arguments(hipe_icode:enter_args(Instruction), Env), + [hipe_icode:enter_args_update(Instruction, NewArguments)]. + +%%----------------------------------------------------------------------------- + +update_if(Instruction, Environment) -> + Args = hipe_icode:if_args(Instruction), + EvaluatedArguments = [lookup_lattice_value(Argument, Environment) + || Argument <- Args], + Op = hipe_icode:if_op(Instruction), + case evaluate_if(Op, EvaluatedArguments) of + true -> + TrueLabel = hipe_icode:if_true_label(Instruction), + ?CONST_PROP_MSG("ifT: ~w ---> goto ~w\n", [Instruction, TrueLabel]), + [hipe_icode:mk_goto(TrueLabel)]; + false -> + FalseLabel = hipe_icode:if_false_label(Instruction), + ?CONST_PROP_MSG("ifF: ~w ---> goto ~w\n", [Instruction, FalseLabel]), + [hipe_icode:mk_goto(FalseLabel)]; + bottom -> + %% Convert the if-test to a type test if possible. + Op = hipe_icode:if_op(Instruction), + case Op =:= '=:=' orelse Op =:= '=/=' of + false -> [Instruction]; + true -> + [Arg1, Arg2] = Args, + case EvaluatedArguments of + [bottom, bottom] -> + [Instruction]; + [bottom, X] -> + conv_if_to_type(Instruction, hipe_icode:const_value(X), Arg1); + [X, bottom] -> + conv_if_to_type(Instruction, hipe_icode:const_value(X), Arg2) + end + end + end. + +conv_if_to_type(I, Const, Arg) when is_atom(Const); + is_integer(Const); + Const =:= [] -> + Test = + if is_atom(Const) -> {atom, Const}; + is_integer(Const) -> {integer, Const}; + true -> nil + end, + {T, F} = + case hipe_icode:if_op(I) of + '=:=' -> {hipe_icode:if_true_label(I),hipe_icode:if_false_label(I)}; + '=/=' -> {hipe_icode:if_false_label(I),hipe_icode:if_true_label(I)} + end, + NewI = hipe_icode:mk_type([Arg], Test, T, F), + ?CONST_PROP_MSG("if: ~w ---> type ~w\n", [I, NewI]), + [NewI]; +conv_if_to_type(I, _, _) -> + [I]. + +%%----------------------------------------------------------------------------- + +update_move(Instruction, Environment) -> + Destination = hipe_icode:move_dst(Instruction), + case lookup_lattice_value(Destination, Environment) of + bottom -> + [Instruction]; + X -> + case hipe_icode:move_src(Instruction) of + X -> + [Instruction]; + _ -> + ?CONST_PROP_MSG("move: ~w ---> ~w\n", [Instruction, X]), + [hipe_icode:move_src_update(Instruction, X)] + end + %% == [hipe_icode:mk_move(Destination, X)] + end. + +%%----------------------------------------------------------------------------- + +update_phi(Instruction, Environment) -> + Destination = hipe_icode:phi_dst(Instruction), + case lookup_lattice_value(Destination, Environment) of + bottom -> + [Instruction]; + X -> + ?CONST_PROP_MSG("phi: ~w ---> ~w\n", [Instruction, X]), + [hipe_icode:mk_move(Destination, X)] + end. + +%%----------------------------------------------------------------------------- + +update_type(Instruction, Environment) -> + EvaluatedArguments = [lookup_lattice_value(Argument, Environment) || + Argument <- hipe_icode:type_args(Instruction)], + case evaluate_type(hipe_icode:type_test(Instruction), EvaluatedArguments) of + true -> + TrueLabel = hipe_icode:type_true_label(Instruction), + ?CONST_PROP_MSG("typeT: ~w ---> goto ~w\n", [Instruction, TrueLabel]), + [hipe_icode:mk_goto(TrueLabel)]; + false -> + FalseLabel = hipe_icode:type_false_label(Instruction), + ?CONST_PROP_MSG("typeF: ~w ---> goto ~w\n", [Instruction, FalseLabel]), + [hipe_icode:mk_goto(FalseLabel)]; + bottom -> + [Instruction] + end. + +%%----------------------------------------------------------------------------- + +update_switch_val(Instruction, Environment) -> + Argument = hipe_icode:switch_val_term(Instruction), + Value = lookup_lattice_value(Argument, Environment), + case Value of + bottom -> + [Instruction]; + _ -> + Cases = hipe_icode:switch_val_cases(Instruction), + FailLabel = hipe_icode:switch_val_fail_label(Instruction), + Target = get_switch_target(Cases, Value, FailLabel), + ?CONST_PROP_MSG("sv: ~w ---> goto ~w\n", [Instruction, Target]), + [hipe_icode:mk_goto(Target)] + end. + +%%----------------------------------------------------------------------------- + +update_switch_tuple_arity(Instruction, Environment) -> + Argument = hipe_icode:switch_tuple_arity_term(Instruction), + Value = lookup_lattice_value(Argument, Environment), + case Value of + bottom -> + [Instruction]; + Constant -> + UnTagged = hipe_icode:const_value(Constant), + case is_tuple(UnTagged) of + true -> + Cases = hipe_icode:switch_tuple_arity_cases(Instruction), + FailLabel = hipe_icode:switch_tuple_arity_fail_label(Instruction), + Target = get_switch_target(Cases, tuple_size(UnTagged), FailLabel), + ?CONST_PROP_MSG("sta: ~w ---> goto ~w\n", [Instruction, Target]), + [hipe_icode:mk_goto(Target)]; + false -> + [Instruction] + %% TODO: Can the above be replaced with below??? Perhaps + %% together with some sort of "generate failure". + %% [hipe_icode:mk_goto(FailLabel)] + end + end. + +%%----------------------------------------------------------------------------- + +lookup_lattice_value(X, Environment) -> + LatticeValues = env__lattice_values(Environment), + case hipe_icode:is_const(X) of + true -> + X; + false -> + case gb_trees:lookup(X, LatticeValues) of + none -> + ?WARNING_MSG("Earlier compiler steps generated erroneous " + "code for X = ~w. We are ignoring this.\n",[X]), + bottom; + {value, top} -> + ?EXIT({"lookup_lattice_value, top", X}); + {value, Y} -> + Y + end + end. + +%%----------------------------------------------------------------------------- + +update_arguments(ArgumentList, Environment) -> + [case lookup_lattice_value(X, Environment) of + bottom -> + X; + Constant -> + Constant + end || X <- ArgumentList]. + +%%----------------------------- End of file ----------------------------------- diff --git a/lib/hipe/icode/hipe_icode_ssa_copy_prop.erl b/lib/hipe/icode/hipe_icode_ssa_copy_prop.erl new file mode 100644 index 0000000000..1899c09715 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_ssa_copy_prop.erl @@ -0,0 +1,41 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%------------------------------------------------------------------- +%% File : hipe_icode_ssa_copy_prop.erl +%% Author : Tobias Lindahl <[email protected]> +%% Description : Performs copy propagation on SSA form. +%% +%% Created : 4 Apr 2003 by Tobias Lindahl <[email protected]> +%%------------------------------------------------------------------- + +-module(hipe_icode_ssa_copy_prop). + +%% +%% modules given as parameters +%% +-define(code, hipe_icode). +-define(cfg, hipe_icode_cfg). + +%% +%% appropriate include files +%% +-include("hipe_icode.hrl"). +-include("../flow/cfg.hrl"). +-include("../ssa/hipe_ssa_copy_prop.inc"). diff --git a/lib/hipe/icode/hipe_icode_ssa_struct_reuse.erl b/lib/hipe/icode/hipe_icode_ssa_struct_reuse.erl new file mode 100644 index 0000000000..675c8c1ad8 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_ssa_struct_reuse.erl @@ -0,0 +1,1444 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%======================================================================= +%% File : hipe_icode_ssa_struct_reuse.erl +%% Author : Ragnar Osterlund <[email protected]> +%% student at the compiler techniques 2 course at UU 2007 +%% Description : HiPE module that removes redundant or partially redundant +%% structure creations from Icode. +%% It does so by inserting redundant expressions as late +%% as possible in the CFG, with the exception of loops where +%% expressions are moved to just before the loop head. +%% Current Icode instructions that can be moved are mktuple() +%% and cons() primop calls. It also handles cases like +%% f({Z}) -> {Z}. It does so by looking at the structure of +%% the match, and recognizes tuples and conses. +%%======================================================================= + +-module(hipe_icode_ssa_struct_reuse). + +-export([struct_reuse/1]). + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("hipe_icode_primops.hrl"). +-include("../flow/cfg.hrl"). + +-define(SET, ordset). +-define(SETS, ordsets). +%%-define(DEBUG, true). + +-define(MKTUPLE, mktuple). +-define(CONS, cons). +-define(SR_INSTR_TYPE, sr_instr_type). +-define(SR_STRUCT_INSTR_TYPE, sr_struct_instr_type). + +-type struct_type() :: {?CONS | ?MKTUPLE, icode_term_arg(), any()}. +-type struct_elems() :: {icode_var(), non_neg_integer(), icode_term_arg()}. + +%% DATATYPE AREA + +%%----------------------------------------------------------------------------- +%% maps +%% The maps are used to identify variables and expressions. +%% The maps are: +%% +%% expr - a map that contains value numbered structure expressions, ie +%% mktuple and cons expression. The key is the value number and the value +%% is an expr record. +%% +%% instr - maps the semantic instruction to an expression value number, +%% that is, a key in the expr map above. +%% +%% var - maps variables to expression value numbers. These variables are +%% defined or used by the structure expressions. + +-record(maps, {var = gb_trees:empty() :: gb_tree(), + instr = gb_trees:empty() :: gb_tree(), + expr = gb_trees:empty() :: gb_tree()}). + +maps_var(#maps{var = Out}) -> Out. +maps_instr(#maps{instr = Out}) -> Out. +maps_expr(#maps{expr = Out}) -> Out. + +maps_expr_keys(Maps) -> gb_trees:keys(maps_expr(Maps)). +maps_expr_values(Maps) -> gb_trees:values(maps_expr(Maps)). + +maps_instr_lookup(Instr, Maps) -> gb_trees:lookup(Instr, maps_instr(Maps)). +maps_instr_enter(Instr, ExprId, Maps) -> + NewInstr = gb_trees:enter(Instr, ExprId, maps_instr(Maps)), + Maps#maps{instr = NewInstr}. + +maps_expr_get(Id, Maps) -> gb_trees:get(Id, maps_expr(Maps)). +maps_expr_enter(Expr, Maps) -> + NewExprMap = gb_trees:enter(expr_id(Expr), Expr, maps_expr(Maps)), + Maps#maps{expr = NewExprMap}. + +maps_var_get(Var, Maps) -> gb_trees:get(Var, maps_var(Maps)). +maps_var_lookup(Var, #maps{var = VarMap}) -> gb_trees:lookup(Var, VarMap). +maps_var_enter(Var, Info, Maps = #maps{var = VarMap}) -> + NewMap = gb_trees:enter(Var, Info, VarMap), + Maps#maps{var = NewMap}. +maps_var_insert(Var, Info, Maps = #maps{var = VarMap}) -> + NewMap = gb_trees:insert(Var, Info, VarMap), + Maps#maps{var = NewMap}. + +maps_balance(Maps) -> + Maps#maps{instr = gb_trees:balance(maps_instr(Maps)), + expr = gb_trees:balance(maps_expr(Maps)), + var = gb_trees:balance(maps_var(Maps))}. + +maps_expr_key_enter(Expr, Maps) -> + NewMaps = maps_instr_enter(expr_key(Expr), expr_id(Expr), Maps), + maps_expr_enter(Expr, NewMaps). + +%%----------------------------------------------------------------------------- +%% expr +%% An expression record. Contains information about a structure expression. +%% The fields are: +%% +%% id - the value number of the expression +%% key - the semantic instruction, as defined in icode, with destination +%% removed and arguments rewritten. +%% defs - destination variable to hold the value of the expression. +%% direct_replace - indicates whether the expression shall be replaced wherever +%% it occurs, although it might not have been inserted. This is used for +%% the expressions that are detected by the icode type constructs. +%% inserts - a list of node labels that will insert this expression +%% use - a list of expression value numbers that use the value of this +%% expression + +-record(expr, {id = none :: 'none' | non_neg_integer(), + key = none :: 'none' | tuple(), % illegal_icode_instr() + defs = none :: 'none' | [icode_var()], + direct_replace = false :: boolean(), + inserts = ?SETS:new() :: ?SET(_), + use = ?SETS:new() :: ?SET(_)}). + +expr_id(#expr{id = Out}) -> Out. +expr_defs(#expr{defs = Out}) -> Out. +expr_key(#expr{key = Out}) -> Out. +expr_inserts(#expr{inserts = Out}) -> Out. +expr_use(#expr{use = Out}) -> Out. +expr_direct_replace(#expr{direct_replace = Out}) -> Out. + +expr_use_add(Expr = #expr{use = UseSet}, Use) -> + Expr#expr{use = ?SETS:add_element(Use, UseSet)}. + +%% expr_key_set(Expr, In) -> Expr#expr{key = In}. +expr_direct_replace_set(Expr, In) -> Expr#expr{direct_replace = In}. +expr_inserts_set(Expr, In) -> Expr#expr{inserts = In}. + +expr_create(Key, Defs) -> + NewExprId = new_expr_id(), + #expr{id = NewExprId, key = Key, defs = Defs}. + +%%----------------------------------------------------------------------------- +%% varinfo +%% A variable mapping info. Contains info about variable references. +%% The fields are: +%% +%% use - a set of expression value numbers that use this variable +%% ref - the variable which value this variable will be assigned +%% when expression is replaced. This is encoded as {N, M} where +%% N is the expression value number and M is the nth destination +%% variable defined by the expression N. +%% elem - indicates that this variable has been detected to be a part of +%% a tuple. The field contains a {V, N} tuple where V is the variable +%% that refers to the structure that this variable is an element in +%% and N is the position that the element occurs on in the tuple. Eg. +%% {{var, 3}, 2} means that the variable {var, 3} refers to a tuple +%% in which this variable is on second place. +%% exprid - a expression value number which is the expression that +%% the variable is defined by. + +-record(varinfo, {use = ?SETS:new() :: ?SET(_), + ref = none :: 'none' | {non_neg_integer(), non_neg_integer()}, + elem = none :: 'none' | {icode_var(), non_neg_integer()}, + exprid = none :: 'none' | non_neg_integer()}). + +varinfo_exprid(#varinfo{exprid = Out}) -> Out. + +varinfo_use_add(#varinfo{use = UseSet} = I, Use) -> + I#varinfo{use = ?SETS:add_element(Use, UseSet)}. + +%%----------------------------------------------------------------------------- +%% node - a node in the temp CFG. +%% +%% label - the label of the node in the original CFG +%% pred - a list of predecessors to this node +%% succ - a list of successors to this node +%% code - code from CFG filtered to only contain structure instructions +%% non_struct_defs - a list of variable definitions that are not defined +%% by structures +%% up_expr - upwards exposed expression value numbers +%% killed_expr - killed expressions value numbers +%% sub_inserts - a set of labels of nodes that defines one or more +%% expressions and that are in a subtree of this node +%% inserts - a set of expression value numbers to be inserted into the node +%% antic_in - a set of expression value numbers that are anticipated into +%% the node +%% antic_out - a set of expression value numbers that are anticipated out of +%% the node +%% phi - a tree of node labels which is defined in phi functions in the node +%% varmap - a list of variable tuples {V1, V2} that maps a variable that are +%% the output of phi functions in sub blocks, V1, into a variable +%% flowing from the block of this node, V2. +%% struct_type - a list of {V, N} tuples that indicates that V is a tuple +%% with N elements. These are added from the icode primop type(). +%% struct_elems - a list of {VD, N, VS} tuples where VD is a variable in the N'th position +%% in VS. These are added from the icode primop unsafe_element() + +-record(node, { + label = none :: 'none' | icode_lbl(), + pred = none :: 'none' | [icode_lbl()], + succ = none :: 'none' | [icode_lbl()], + code = [] :: [tuple()], % [illegal_icode_instr()] + phi = gb_trees:empty() :: gb_tree(), + varmap = [] :: [{icode_var(), icode_var()}], + pre_loop = false :: boolean(), + non_struct_defs = gb_sets:new() :: gb_set(), + up_expr = none :: 'none' | ?SET(_), + killed_expr = none :: 'none' | ?SET(_), + sub_inserts = ?SETS:new() :: ?SET(_), + inserts = ?SETS:new() :: ?SET(_), + antic_in = none :: 'none' | ?SET(_), + antic_out = none :: 'none' | ?SET(_), + struct_type = [] :: [struct_type()], + struct_elems = [] :: [struct_elems()]}). + +node_sub_inserts(#node{sub_inserts = Out}) -> Out. +node_inserts(#node{inserts = Out}) -> Out. +node_antic_out(#node{antic_out = Out}) -> Out. +node_antic_in(#node{antic_in = Out}) -> Out. +node_killed_expr(#node{killed_expr = Out}) -> Out. +node_pred(#node{pred = Out}) -> Out. +node_succ(#node{succ = Out}) -> Out. +node_label(#node{label = Out}) -> Out. +node_code(#node{code = Out}) -> Out. +node_non_struct_defs(#node{non_struct_defs = Out}) -> Out. +node_up_expr(#node{up_expr = Out}) -> Out. +node_pre_loop(#node{pre_loop = Out}) -> Out. +node_struct_type(#node{struct_type = Out}) -> Out. +%% node_atom_type(#node{atom_type = Out}) -> Out. +node_struct_elems(#node{struct_elems = Out}) -> Out. + +node_pre_loop_set(Node) -> Node#node{pre_loop = true}. + +node_phi_add(Node = #node{phi = Phi}, Pred, Value) -> + NewList = + case gb_trees:lookup(Pred, Phi) of + {value, List} -> [Value | List]; + none -> [Value] + end, + Node#node{phi = gb_trees:enter(Pred, NewList, Phi)}. + +node_phi_get(#node{phi = Phi}, Pred) -> + case gb_trees:lookup(Pred, Phi) of + {value, List} -> List; + none -> [] + end. + +node_code_add(Node = #node{code = Code}, Instr) -> + Node#node{code = [Instr | Code]}. + +node_code_rev(Node = #node{code = Code}) -> + Node#node{code = lists:reverse(Code)}. + +node_struct_type_add(Node = #node{struct_type = T}, Value) -> + Node#node{struct_type = [Value | T]}. + +%% node_atom_type_add(Node = #node{atom_type = T}, Value) -> +%% Node#node{atom_type = [Value | T]}. + +node_struct_elems_add(Node = #node{struct_elems = T}, Value) -> + Node#node{struct_elems = [Value | T]}. + +node_non_struct_defs_list(Node) -> + gb_sets:to_list(node_non_struct_defs(Node)). + +node_non_struct_instr_add(Node, Instr) -> + DefList = hipe_icode:defines(Instr), + Tmp = gb_sets:union(node_non_struct_defs(Node), gb_sets:from_list(DefList)), + Node#node{non_struct_defs = Tmp}. + +node_set_sub_inserts(Node, In) -> Node#node{sub_inserts = In}. + +node_add_insert(Node, In) -> + NewIns = ?SETS:add_element(In, node_inserts(Node)), + Node#node{inserts = NewIns}. + +node_union_sub_inserts(Node, SubIns) -> + NewSubIns = ?SETS:union(SubIns, node_sub_inserts(Node)), + node_set_sub_inserts(Node, NewSubIns). + +node_varmap_set(Node, Vars) -> + Node#node{varmap = Vars}. + +node_varmap_lookup(#node{varmap = Varmap}, Var) -> + case lists:keyfind(Var, 1, Varmap) of + {_, NewVar} -> NewVar; + false -> Var + end. + +node_create(Label, Pred, Succ) -> + #node{label = Label, pred = Pred, succ = Succ}. + +%%----------------------------------------------------------------------------- +%% nodes - describes the new temporary CFG +%% +%% domtree - the dominator tree of the original CFG +%% labels - the labels of the original CFG, filtered to only contain non fail trace paths +%% postorder - the postorder walk of labels of the original CFG, filtered to only contain non fail trace paths +%% rev_postorder - reverse of postorder. +%% start_label - the start basic block label. +%% all_expr - all expression value numbers that the CFG defines +%% tree - the tree of nodes, with labels as keys and node records as values + +-record(nodes, { + domtree :: hipe_dominators:domTree(), + labels = none :: 'none' | [icode_lbl()], + postorder = none :: 'none' | [icode_lbl()], + start_label = none :: 'none' | icode_lbl(), + rev_postorder = none :: 'none' | [icode_lbl()], + all_expr = none :: 'none' | [non_neg_integer()], + tree = gb_trees:empty() :: gb_tree()}). + +nodes_postorder(#nodes{postorder = Out}) -> Out. +nodes_rev_postorder(#nodes{rev_postorder = Out}) -> Out. +nodes_tree(#nodes{tree = Out}) -> Out. +nodes_domtree(#nodes{domtree = Out}) -> Out. +nodes_start_label(#nodes{start_label = Out}) -> Out. + +nodes_tree_is_empty(#nodes{tree = Tree}) -> + gb_trees:is_empty(Tree). + +nodes_tree_set(Tree, Nodes) -> Nodes#nodes{tree = Tree}. +nodes_all_expr_set(AllExpr, Nodes) -> Nodes#nodes{all_expr = AllExpr}. + +nodes_tree_values(Nodes) -> + gb_trees:values(nodes_tree(Nodes)). + +get_node(Label, Nodes) -> + gb_trees:get(Label, nodes_tree(Nodes)). + +enter_node(Node, Nodes) -> + nodes_tree_set(gb_trees:enter(node_label(Node), Node, nodes_tree(Nodes)), Nodes). + +remove_node(Node, Nodes) -> + nodes_tree_set(gb_trees:delete(node_label(Node), nodes_tree(Nodes)), Nodes). + +nodes_create() -> #nodes{}. + +%%----------------------------------------------------------------------------- +%% update +%% record used when updating the CFG, keeping track of which expressions +%% have been inserted and their mappings to variable names. +%% +%% inserted - maps an expression to a list of variables +%% del_red_test - flag that is set to true when the reduction test +%% has been inserted is used to move the reduction test. + +-record(update, {inserted = gb_trees:empty() :: gb_tree(), + del_red_test = false :: boolean()}). + +update_inserted_lookup(#update{inserted = Inserted}, ExprId) -> + gb_trees:lookup(ExprId, Inserted). + +update_inserted_add_new(Update = #update{inserted = Inserted}, ExprId, Defs) -> + VarList = [case hipe_icode:is_var(Def) of + true -> hipe_icode:mk_new_var(); + false -> + case hipe_icode:is_reg(Def) of + true -> hipe_icode:mk_new_reg(); + false -> + true = hipe_icode:is_fvar(Def), + hipe_icode:mk_new_fvar() + end + end || Def <- Defs], + NewInserted = gb_trees:enter(ExprId, VarList, Inserted), + {Update#update{inserted = NewInserted}, VarList}. + +update_inserted_add(Update = #update{inserted = Inserted}, ExprId, Defs) -> + Update#update{inserted = gb_trees:enter(ExprId, Defs, Inserted)}. + +update_del_red_test(#update{del_red_test = DelRed}) -> DelRed. +update_del_red_test_set(Update) -> + Update#update{del_red_test = true}. + +%%----------------------------------------------------------------------------- +%% CODE AREA + +%%----------------------------------------------------------------------------- +%% Main function called from the hipe_main module + +-spec struct_reuse(#cfg{}) -> #cfg{}. + +struct_reuse(CFG) -> + %% debug_init_case_count(?SR_INSTR_TYPE), + %% debug_init_case_count(?SR_STRUCT_INSTR_TYPE), + + %% debug_function({wings_ask,ask_unzip,3}, CFG), + %% debug_function(nil, CFG), + %% set_debug_flag(true), + %% debug_struct("CFG In: ", CFG), + %% debug_cfg_pp(CFG), + + init_expr_id(), + + Nodes = construct_nodes(CFG), + + case nodes_tree_is_empty(Nodes) of + false -> + Maps = create_maps(Nodes), + + Nodes3 = init_nodes(Nodes, Maps), + Nodes4 = calc_anticipated(Nodes3), + + {Nodes5, Maps3} = calc_inserts(Nodes4, Maps), + + Nodes6 = update_nodes_inserts(Nodes5, Maps3), + + %% debug_list("ExprMap: ", gb_trees:to_list(Maps3#maps.expr)), + %% debug_list("VarMap: ", gb_trees:to_list(maps_var(Maps3))), + %% debug_nodes(Nodes6), + + %% update the cfg + CFG1 = rewrite_cfg(CFG, Nodes6, Maps3), + CFG2 = hipe_icode_ssa:remove_dead_code(CFG1), + CFGOut = hipe_icode_ssa_copy_prop:cfg(CFG2), + %% CFGOut = CFG1, + + %% print_struct("CFG: ", CFG), + %% debug_cfg_pp(CFG), + %% debug_cfg_pp(CFGOut), + + %% debug_print_case_count(?SR_STRUCT_INSTR_TYPE), + %% debug_print_case_count(?SR_INSTR_TYPE), + %% debug("Done~n"), + %% debug_struct("CFG Out: ", CFGOut), + CFGOut; + true -> + CFG + end. + +%%----------------------------------------------------------------------------- +%% Calculate simplified CFG with all fail paths removed + +construct_nodes(CFG) -> + %% all important dominator tree + DomTree = hipe_dominators:domTree_create(CFG), + + %% construct initial nodes + {Nodes, NonFailSet} = nodes_from_cfg(CFG, DomTree), + + %% remove nodes on fail paths + NewNodes = prune_nodes(Nodes, NonFailSet), + + %% fill in misc node tree info + Postorder = [Label || Label <- hipe_icode_cfg:postorder(CFG), + gb_sets:is_member(Label, NonFailSet)], + + %% check postorder is valid + PostOrderTmp = hipe_icode_cfg:postorder(CFG), + LabelsTmp = hipe_icode_cfg:labels(CFG), + case length(PostOrderTmp) =/= length(LabelsTmp) of + true -> + print("Warning, Postorder and Labels differ!~n"), + print_struct("Postorder: ", PostOrderTmp), + print_struct("Labels: ", LabelsTmp); + false -> + done + end, + + RevPostorder = lists:reverse(Postorder), + + StartLabel = hipe_icode_cfg:start_label(CFG), + NewTree = gb_trees:balance(nodes_tree(NewNodes)), + + NewNodes#nodes{postorder = Postorder, + rev_postorder = RevPostorder, + start_label = StartLabel, + tree = NewTree, + domtree = DomTree}. + +%%----------------------------------------------------------------------------- +%% Constructs a tree of nodes, one node for each basic block in CFG + +nodes_from_cfg(CFG, DomTree) -> + lists:foldl(fun(Label, {NodesAcc, NonFailAcc}) -> + Code = hipe_bb:code(hipe_icode_cfg:bb(CFG, Label)), + Pred = hipe_icode_cfg:pred(CFG, Label), + Succ = hipe_icode_cfg:succ(CFG, Label), + %% debug_struct("Label: ", Label), + %% debug_struct("Code: ", Code), + + %% Find all structures and phi functions. + %% Find all defines in this bb that are not from structures + %% and add them to NonStructDefs, later to be used for calculating upwards + %% exposed expressions, and killed expressions. + %% Also find all non fail blocks, ie backtrace from return blocks, + %% and add them to NewNonFailAcc + + Node = node_create(Label, Pred, Succ), + + {NewNode, NewNonFailAcc, PreLoopPreds} = + lists:foldl(fun(Instr, {NodeAcc, NFAcc, PLPAcc}) -> + case instr_type(Instr) of + struct -> + {node_code_add(NodeAcc, Instr), NFAcc, PLPAcc}; + return -> + {NodeAcc, get_back_trace_rec(CFG, Label, NFAcc), PLPAcc}; + {struct_elems, NumElem, DstVar, SrcVar} -> + NewNodeAcc = node_struct_elems_add(NodeAcc, {DstVar, NumElem, SrcVar}), + {node_non_struct_instr_add(NewNodeAcc, Instr), NFAcc, PLPAcc}; + {struct_type, NumElems, Var, Type} -> + {node_struct_type_add(NodeAcc, {Type, Var, NumElems}), NFAcc, PLPAcc}; + {tuple_arity, Var, Cases} -> + NewNodeAcc = + lists:foldl(fun(Case, NAcc) -> + case Case of + {{const, {flat, Arity}}, _} -> + Tuple = {?MKTUPLE, Var, Arity}, + node_struct_type_add(NAcc, Tuple); + _ -> NAcc + end + end, NodeAcc, Cases), + {NewNodeAcc, NFAcc, PLPAcc}; + %% {atom_type, Atom, Var} -> + %% {node_atom_type_add(NodeAcc, {Var, Atom}), NFAcc, PLPAcc}; + phi -> + Def = hipe_icode:phi_dst(Instr), + Part = lists:foldl(fun(P = {Pr, PredVar}, {IsDef, NotDom}) -> + case hipe_dominators:domTree_dominates(Label, Pr, DomTree) of + false -> + {IsDef, [P | NotDom]}; + true -> + {IsDef andalso PredVar =:= Def, NotDom} + end + end, {true, []}, hipe_icode:phi_arglist(Instr)), + + case Part of + {true, [{P, V}]} -> + %% This is the only case recognized so far. All phi + %% sub block references a static variable that is + %% assigned the same value again in the phi function. + {node_phi_add(NodeAcc, P, {Def, V}), + NFAcc, ?SETS:add_element(P, PLPAcc)}; + + {false, [{P, _}]} -> + {node_non_struct_instr_add(NodeAcc, Instr), + NFAcc, ?SETS:add_element(P, PLPAcc)}; + + _ -> + {node_non_struct_instr_add(NodeAcc, Instr), NFAcc, PLPAcc} + end; + _ -> + {node_non_struct_instr_add(NodeAcc, Instr), NFAcc, PLPAcc} + end + end, {Node, NonFailAcc, ?SETS:new()}, Code), + + %% insert the new node + NewNodesAcc = enter_node(node_code_rev(NewNode), NodesAcc), + + %% Set the pre loop flag of all nodes that are predecessor to this node + %% and that are the first nodes prior to a loop. + NewNodesAcc2 = + lists:foldl(fun(Lbl, NsAcc) -> + PredNode = get_node(Lbl, NsAcc), + NewPredNode = node_pre_loop_set(PredNode), + NewPredNode2 = node_varmap_set(NewPredNode, node_phi_get(NewNode, Lbl)), + + enter_node(NewPredNode2, NsAcc) + end, NewNodesAcc, PreLoopPreds), + + {NewNodesAcc2, NewNonFailAcc} + end, {nodes_create(), gb_sets:new()}, hipe_icode_cfg:reverse_postorder(CFG)). + +%%----------------------------------------------------------------------------- +%% Get all labels from Label to root of CFG, ie backtraces from Label. + +get_back_trace_rec(CFG, Label, LabelSet) -> + %% debug_struct("Label :", Label), + %% debug_struct("Set :", gb_sets:to_list(LabelSet)), + case gb_sets:is_member(Label, LabelSet) of + false -> + Preds = hipe_icode_cfg:pred(CFG, Label), + lists:foldl(fun(Lbl, SetAcc) -> + get_back_trace_rec(CFG, Lbl, SetAcc) + end, gb_sets:add(Label, LabelSet), Preds); + true -> LabelSet + end. + +%%----------------------------------------------------------------------------- +%% Remove all fail block paths and successors and predecessors +%% That are on fail paths + +prune_nodes(Nodes, NonFailSet) -> + lists:foldl(fun(Node, NodesAcc) -> + case gb_sets:is_member(node_label(Node), NonFailSet) of + true -> + NewSucc = [L || L <- node_succ(Node), gb_sets:is_member(L, NonFailSet)], + NewPred = [L || L <- node_pred(Node), gb_sets:is_member(L, NonFailSet)], + enter_node(Node#node{succ = NewSucc, pred = NewPred}, NodesAcc); + false -> + remove_node(Node, NodesAcc) + end + end, Nodes, nodes_tree_values(Nodes)). + +%%----------------------------------------------------------------------------- +%% Map calculations. + +%%----------------------------------------------------------------------------- +%% Create a maps structure from the Nodes record + +create_maps(Nodes) -> + Maps = lists:foldl(fun(Label, MapsAcc) -> + Node = get_node(Label, Nodes), + NewMapsAcc = maps_from_node_struct_type(MapsAcc, Node), + NewMapsAcc2 = maps_from_node_struct_elems(NewMapsAcc, Node), + %% NewMapsAcc3 = maps_from_node_atom_type(NewMapsAcc2, Node), + maps_from_node_code(NewMapsAcc2, Node) + end, #maps{}, nodes_rev_postorder(Nodes)), + maps_balance(Maps). + +%%----------------------------------------------------------------------------- +%% Add all elements in the struct_type list of Node to Maps as expressions + +maps_from_node_struct_type(Maps, Node) -> + %% debug_struct("Node Label: ", node_label(Node)), + %% debug_struct("Node Tuple Type: ", node_struct_type(Node)), + lists:foldl(fun({Type, Var, Size}, MapsAcc) -> + Key = create_elem_expr_key(Size, Var, []), + InstrKey = hipe_icode:mk_primop([], Type, Key), + NewExpr2 = expr_create(InstrKey, [Var]), + NewExpr3 = expr_direct_replace_set(NewExpr2, true), + maps_expr_key_enter(NewExpr3, MapsAcc) + end, Maps, node_struct_type(Node)). + +create_elem_expr_key(0, _, Key) -> Key; +create_elem_expr_key(N, Var, Key) -> + create_elem_expr_key(N - 1, Var, [{Var, N} | Key]). + +%%----------------------------------------------------------------------------- +%%maps_from_node_atom_type(Maps, Node) -> +%% lists:foldl(fun({Var, Atom}, MapsAcc) -> +%% case maps_var_lookup(Var, MapsAcc) of +%% none -> +%% MapsAcc; +%% {value, #varinfo{elem = none}} -> +%% MapsAcc; +%% {value, #varinfo{elem = {Src, Num, ExprId}}} -> +%% Expr = maps_expr_get(ExprId, MapsAcc), +%% Key = expr_key(Expr), +%% +%% Filter = fun(Arg) -> +%% case Arg of +%% {Src, Num, ExprId} -> +%% hipe_icode:mk_const(Atom); +%% _ -> +%% Arg +%% end end, +%% +%% NewKey = replace_call_variables(Filter, Key), +%% NewExpr = expr_create(NewKey, expr_defs(Expr)), +%% maps_expr_key_enter(NewExpr, MapsAcc) +%% end +%% end, Maps, node_atom_type(Node)). + +%%----------------------------------------------------------------------------- +%% Add all struct_elemns in Node to Maps as variables + +maps_from_node_struct_elems(Maps, Node) -> + lists:foldl(fun({Dst, Num, Src}, MapsAcc) -> + VarInfo = #varinfo{elem = {Src, Num}}, + maps_var_insert(Dst, VarInfo, MapsAcc) + end, Maps, node_struct_elems(Node)). + +%%----------------------------------------------------------------------------- +%% Get all expressions defined by the Node and insert them into Maps. +%% Also insert information about all affected variables into Maps. + +maps_from_node_code(Maps, Node) -> + %% debug_struct("Node Label: ", Label), + %% debug_struct("Node Code: ", Code), + %% Label = node_label(Node), + lists:foldl(fun(Instr, MapsAcc) -> + %% create two keys that are used to reference this structure creation + %% instruction, so that we can lookup its expression value number + %% later. + InstrKey = hipe_icode:call_dstlist_update(Instr, []), + + %% Fetch the two keys from the instruction + {HasElems, RefKey, ElemKey} = + replace_call_vars_elems(MapsAcc, InstrKey), + + %% create a new expr record or lookup an existing one. + case HasElems of + true -> + %% The instruction contains uses of variables that are + %% part of another structure. + case maps_instr_lookup(ElemKey, MapsAcc) of + {value, ExprId} -> + %% The instruction is equal to a structure that has + %% already been created. This is the f({Z}) -> {Z} + %% optimization. I.e. there is no need to create {Z} again. + %% Also lookup if ExprId is defining a variable that is + %% already an element in another structure. If so, + %% use that element. This takes care of nested structures + %% such as f({X, {Y, Z}}) -> {X, {Y, Z}}. + + #expr{defs = [Var]} = maps_expr_get(ExprId, MapsAcc), + StructElem = + case maps_var_lookup(Var, MapsAcc) of + {value, #varinfo{elem = Elem, exprid = none}} when Elem =/= none -> + Elem; + _ -> none + end, + Defines = hipe_icode:defines(Instr), + maps_varinfos_create(Defines, ExprId, StructElem, MapsAcc); + none -> + %% create a new expression + maps_expr_varinfos_create(Instr, RefKey, MapsAcc) + end; + false -> + %% create a new expression + maps_expr_varinfos_create(Instr, RefKey, MapsAcc) + end + end, Maps, node_code(Node)). + +%%----------------------------------------------------------------------------- +%% Creates varinfo structures with exprid set to ExprId for all +%% variables contained in Defines. These are put into MapsIn. + +maps_varinfos_create(Defines, ExprId, Elem, MapsIn) -> + VarInfo = #varinfo{exprid = ExprId, elem = Elem}, + {MapsOut, _} = + lists:foldl(fun (Def, {Maps, NumAcc}) -> + NewVarInfo = VarInfo#varinfo{ref = {ExprId, NumAcc}}, + {maps_var_insert(Def, NewVarInfo, Maps), NumAcc + 1} + end, {MapsIn, 1}, Defines), + MapsOut. + +%%----------------------------------------------------------------------------- +%% Creates a new expression from RefKey if RefKey is not already reffering +%% to an expression. Also creates varinfo structures for all variables defined +%% and used by Instr. Result is put in Maps. + +maps_expr_varinfos_create(Instr, RefKey, Maps) -> + Defines = hipe_icode:defines(Instr), + {ExprId, Maps2} = + case maps_instr_lookup(RefKey, Maps) of + {value, EId} -> + {EId, Maps}; + none -> + NewExpr = expr_create(RefKey, Defines), + {expr_id(NewExpr), maps_expr_key_enter(NewExpr, Maps)} + end, + Maps3 = maps_varinfos_create(Defines, ExprId, none, Maps2), + update_maps_var_use(Instr, ExprId, Maps3). + +%%----------------------------------------------------------------------------- +%% A variable replacement function that returns a tuple of three elements +%% {T, K1, K2}, where T indicates if Instr contained variables that where +%% elements of other structures, K1 is the Instr with all variables that +%% references another structure replaced, and K2 is K1 but also with all +%% variables that are elements of other structures replaced. + +replace_call_vars_elems(Maps, Instr) -> + VarMap = maps_var(Maps), + {HasElems, Vars, Elems} = + lists:foldr(fun(Arg, {HasElems, Vars, Elems}) -> + case hipe_icode:is_const(Arg) of + false -> + case gb_trees:lookup(Arg, VarMap) of + none -> + {HasElems, [Arg | Vars], [Arg | Elems]}; + {value, #varinfo{ref = none, elem = none}} -> + {HasElems, [Arg | Vars], [Arg | Elems]}; + {value, #varinfo{ref = Ref, elem = none}} -> + {HasElems, [Ref | Vars], [Ref | Elems]}; + {value, #varinfo{ref = none, elem = Elem}} -> + {true, [Arg | Vars], [Elem | Elems]}; + {value, #varinfo{ref = Ref, elem = Elem}} -> + {true, [Ref | Vars], [Elem | Elems]} + end; + true -> + {HasElems, [Arg | Vars], [Arg | Elems]} + end end, {false, [], []}, hipe_icode:args(Instr)), + {HasElems, hipe_icode:call_args_update(Instr, Vars), + hipe_icode:call_args_update(Instr, Elems)}. + +%%----------------------------------------------------------------------------- +%% Updates the usage information of all variables used by Instr to also +%% contain Id and updates Maps to contain the new variable information. +%% Also updates the expressions where the updated variables are used to +%% contain the use information. + +update_maps_var_use(Instr, Id, Maps) -> + lists:foldl(fun(Use, MapsAcc) -> + VarInfo = get_varinfo(Use, MapsAcc), + NewVarInfo = varinfo_use_add(VarInfo, Id), + MapsAcc2 = maps_var_enter(Use, NewVarInfo, MapsAcc), + case varinfo_exprid(VarInfo) of + none -> + MapsAcc2; + VarExprId -> + Expr = maps_expr_get(VarExprId, MapsAcc2), + NewExpr = expr_use_add(Expr, Id), + maps_expr_enter(NewExpr, MapsAcc2) + end + end, Maps, hipe_icode:uses(Instr)). + +%%----------------------------------------------------------------------------- +%% Looks up an old variable info or creates a new one if none is found. + +get_varinfo(Var, Maps) -> + case maps_var_lookup(Var, Maps) of + {value, Info} -> + Info; + none -> + #varinfo{} + end. + +%%----------------------------------------------------------------------------- +%% filters all arguments to a function call Instr that are not constants +%% through the Filter function, and replaces the arguments in Instr with +%% the result. + +replace_call_variables(Filter, Instr) -> + NewArgs = [case hipe_icode:is_const(Arg) of + false -> Filter(Arg); + true -> Arg + end || Arg <- hipe_icode:args(Instr)], + hipe_icode:call_args_update(Instr, NewArgs). + +%%----------------------------------------------------------------------------- +%% Init nodes from node local expression information + +init_nodes(Nodes, Maps) -> + AllExpr = maps_expr_keys(Maps), + lists:foldl(fun(Node, NodesAcc) -> + UEExpr = calc_up_exposed_expr(maps_var(Maps), Node), + %% print_list("Up ExprSet: ", ?SETS:to_list(UEExpr)), + + KilledExpr = calc_killed_expr(Node, Maps), + %% print_list("Killed: ", ?SETS:to_list(KilledExpr)), + + %% End nodes have no anticipated out + AnticOut = + case node_succ(Node) of + [] -> + ?SETS:new(); + _ -> + AllExpr + end, + enter_node(Node#node{up_expr = UEExpr, + killed_expr = KilledExpr, + antic_out = AnticOut}, NodesAcc) + end, nodes_all_expr_set(AllExpr, Nodes), nodes_tree_values(Nodes)). + +%%----------------------------------------------------------------------------- +%% Calculate the upwards exposed expressions for a node. + +calc_up_exposed_expr(VarMap, Node) -> + %% debug_struct("UpExpr label: ", node_label(Node)), + NonStructDefs = node_non_struct_defs(Node), + {_, ExprIdSet} = + lists:foldl(fun(Instr, {NotToUseAcc, ExprIdAcc}) -> + Defs = hipe_icode:defines(Instr), + Uses = hipe_icode:uses(Instr), + IsNotToUse = + lists:any(fun(Use) -> gb_sets:is_member(Use, NotToUseAcc) end, Uses), + case IsNotToUse of + false -> + NewExprIdAcc = + lists:foldl(fun(Def, Acc) -> + #varinfo{exprid = Id} = gb_trees:get(Def, VarMap), + ?SETS:add_element(Id, Acc) end, ExprIdAcc, Defs), + {NotToUseAcc, NewExprIdAcc}; + true -> + NewNotToUse = + gb_sets:union(gb_sets:from_list(Defs), NotToUseAcc), + {NewNotToUse, ExprIdAcc} + end + end, {NonStructDefs, ?SETS:new()}, node_code(Node)), + ExprIdSet. + +%%----------------------------------------------------------------------------- +%% Calculate killed expression for node + +calc_killed_expr(Node, Maps) -> + calc_killed_expr_defs(node_non_struct_defs_list(Node), ?SETS:new(), Maps). + +calc_killed_expr_defs(Defs, UseSet, Maps) -> + lists:foldl(fun(Def, Acc) -> + case maps_var_lookup(Def, Maps) of + none -> + Acc; + {value, #varinfo{use = Use}} -> + ?SETS:union(Acc, calc_killed_expr_use(Use, Maps)) + end + end, UseSet, Defs). + +calc_killed_expr_use(ExprIds, Maps) -> + ?SETS:fold(fun(Id, Acc) -> + Expr = maps_expr_get(Id, Maps), + ?SETS:union(Acc, calc_killed_expr_use(expr_use(Expr), Maps)) + end, ExprIds, ExprIds). + +%%----------------------------------------------------------------------------- +%% Calculate the anticipated in and anticipated out sets for each node + +calc_anticipated(NodesIn) -> + calc_anticipated_rec(NodesIn, nodes_postorder(NodesIn)). + +calc_anticipated_rec(NodesIn, []) -> NodesIn; +calc_anticipated_rec(NodesIn, WorkIn) -> + {NodesOut, WorkOut} = + lists:foldl(fun(Label, {NodesAcc, WorkAcc}) -> + Node = get_node(Label, NodesAcc), + + %debug_struct("~nNode Label: ", Label), + + AnticIn = ?SETS:union(node_up_expr(Node), + ?SETS:subtract(node_antic_out(Node), node_killed_expr(Node))), + + %debug_struct("AnticIn: ", AnticIn), + case (node_antic_in(Node) =:= AnticIn) of + false -> + NewNodes1 = enter_node(Node#node{antic_in = AnticIn}, NodesAcc), + Preds = node_pred(Node), + %debug_struct("Preds: ", Preds), + + NewNodes2 = + lists:foldl(fun(Label2, NodesAcc2) -> + PredNode = get_node(Label2, NodesAcc2), + AnticOut = ?SETS:intersection(AnticIn, node_antic_out(PredNode)), + %debug_struct("Pred Node Label: ", Label2), + %debug_struct("Pred AnticOut: ", AnticOut), + + enter_node(PredNode#node{antic_out = AnticOut}, NodesAcc2) + end, NewNodes1, Preds), + + NewWork = add_work_list(Preds, WorkAcc), + %debug_struct("New Work: ", NewWork), + + {NewNodes2, NewWork}; + true -> + {NodesAcc, WorkAcc} + end + end, {NodesIn, new_work()}, WorkIn), + + calc_anticipated_rec(NodesOut, get_work_list(WorkOut)). + +%%----------------------------------------------------------------------------- +%% Function that adds inserts to expressions from nodes which either +%% have an upwards exposed expression or dominate more than one node +%% that inserts the same expression or the node is a prior to loop +%% node. The inserted info is stored in the #expr records in the expr +%% map of the #maps structure. + +calc_inserts(NodesIn, MapsIn) -> + DomTree = nodes_domtree(NodesIn), + + lists:foldl(fun(Label, {NodesAcc, MapsAcc}) -> + Node = get_node(Label, NodesAcc), + + %% get some basic properties. + UpExpr = node_up_expr(Node), + AnticOut = node_antic_out(Node), + SubIns = node_sub_inserts(Node), + + %% debug_struct("Label: ", Label), + + {HasIns, NewMapsAcc} = + ?SETS:fold(fun(ExprId, {HasInsAcc, MapsAcc2}) -> + Expr = maps_expr_get(ExprId, MapsAcc2), + + ExprIns = expr_inserts(Expr), + ExprSubIns = ?SETS:intersection(ExprIns, SubIns), + + %% There are three cases when to insert an expression + %% 1. The expression is defined at least twice in the subtree of this + %% node, that is length(ExprSubIns) > 1. + %% 2. It is defined in the node and is upwards exposed. + %% 3. The node is a block just above a loop, so we should move + %% all anticipated expressions to the node. + + case length(ExprSubIns) > 1 orelse ?SETS:is_element(ExprId, UpExpr) + orelse node_pre_loop(Node) of + true -> + %% get labels of all sub blocks that inserts the expression and + %% that are dominated by the current node. + Dominates = + ?SETS:filter(fun(SubLabel) -> + hipe_dominators:domTree_dominates(Label, SubLabel, DomTree) + end, ExprSubIns), + + %% remove inserts labels from insert labelset. + NewIns = ?SETS:subtract(ExprIns, Dominates), + NewIns2 = ?SETS:add_element(Label, NewIns), + + %% update the node. + NewMaps = + maps_expr_enter(expr_inserts_set(Expr, NewIns2), MapsAcc2), + {true, NewMaps}; + false -> + {HasInsAcc, MapsAcc2} + end + end, {false, MapsAcc}, ?SETS:union(AnticOut, UpExpr)), + + %% Check if there was an insert into this node, + %% and if so add to the sub inserts set. + NewSubIns = + case HasIns of + true -> + ?SETS:add_element(Label, SubIns); + false -> + SubIns + end, + + %% update sub inserts for all predecessors to the node. + NewNodes2 = + lists:foldl(fun(PredLabel, NodesAcc2) -> + PredNode = get_node(PredLabel, NodesAcc2), + enter_node(node_union_sub_inserts(PredNode, NewSubIns), NodesAcc2) + end, NodesAcc, node_pred(Node)), + + {NewNodes2, NewMapsAcc} + + end, {NodesIn, MapsIn}, nodes_postorder(NodesIn)). + +%%----------------------------------------------------------------------------- +%% Update the insert sets of each node in the node tree. +%% That is, move the insert information from the expressions to +%% the actual nodes that perform the inserts. + +update_nodes_inserts(Nodes, Maps) -> + lists:foldl(fun(Expr, NodesAcc) -> + ExprId = expr_id(Expr), + ?SETS:fold(fun(Label, NsAcc) -> + Nd = get_node(Label, NsAcc), + enter_node(node_add_insert(Nd, ExprId), NsAcc) + end, NodesAcc, expr_inserts(Expr)) + end, Nodes, maps_expr_values(Maps)). + +%%----------------------------------------------------------------------------- +%% Rewrite CFG functions + +%%----------------------------------------------------------------------------- +%% Do the code updating from the info in the nodes and maps structures. This +%% is a proxy function for rewrite_cfg/6 +rewrite_cfg(CFG, Nodes, Maps) -> + {NewCFG, _Visited} = + rewrite_cfg(CFG, ?SETS:new(), #update{}, Nodes, Maps, [nodes_start_label(Nodes)]), + %% debug_struct("Visited: ", _Visited), + NewCFG. + +%%----------------------------------------------------------------------------- +%% rewrite_cfg +%% traverse the CFG in reverse postorder and rewrite each basic block before +%% rewriteing its children. Pass along to each BB update the mappings of +%% inserted expressions in the Update record. + +rewrite_cfg(CFG, Visited, Update, Nodes, Maps, Labels) -> + lists:foldl(fun(Label, {CFGAcc, VisitedAcc}) -> + case ?SETS:is_element(Label, VisitedAcc) of + false -> + %% debug_struct("Visit: ", Label), + Node = get_node(Label, Nodes), + NewVisitedAcc = ?SETS:add_element(Label, VisitedAcc), + {NewCFGAcc, NewUpdate} = rewrite_bb(CFGAcc, Update, Maps, Node), + %% debug_struct("Update inserted: ", update_inserted_list(NewUpdate)), + rewrite_cfg(NewCFGAcc, NewVisitedAcc, NewUpdate, Nodes, Maps, node_succ(Node)); + true -> + {CFGAcc, VisitedAcc} + end + end, {CFG, Visited}, Labels). + +%%----------------------------------------------------------------------------- +%% rewrite one single basic block in the CFG as described by the properties +%% in the Node for that block. Uses the Maps and Update info to lookup +%% the instructions and expressions to insert or delete. + +rewrite_bb(CFG, Update, Maps, Node) -> + #node{pre_loop = PreLoop, label = Label, up_expr = UpExpr, inserts = Inserts} = Node, + + Code = hipe_bb:code(hipe_icode_cfg:bb(CFG, Label)), + + %debug_struct("RW Label: ", Label), + %debug_struct("Inserts", Inserts), + + DelRed = update_del_red_test(Update), + Delete = ?SETS:subtract(UpExpr, Inserts), + + %% local function that gets the instruction and defines list of an + %% expression id in the current node and and returns them. + GetInstrFunc = fun(Expr) -> + Instr = expr_key(Expr), + Defs = expr_defs(Expr), + NewInstr = + if + PreLoop -> + replace_call_variables(fun(Var) -> + node_varmap_lookup(Node, + Var) + end, + Instr); + true -> + Instr + end, + {NewInstr, Defs} + end, + + %% go through all expressions defined by the node and replace + %% or remove them as indicated by the delete set. Also perform + %% reduction test replacement if neccessary. + {[CodeLast | CodeRest], NewUpdate, LocalAcc} = + lists:foldl(fun(Instr, {CodeAcc, UpdateAcc, LocalAcc}) -> + case struct_instr_type(Instr) of + struct -> + Defs = hipe_icode:defines(Instr), + + #varinfo{exprid = ExprId} = maps_var_get(hd(Defs), Maps), + + Expr = maps_expr_get(ExprId, Maps), + DirectReplace = expr_direct_replace(Expr), + + %% Creates move intstructions from Vars to Defs + RemoveFuncVars = fun(Vars) -> + CodeAcc2 = mk_defs_moves(CodeAcc, Defs, Vars), + {CodeAcc2, UpdateAcc, LocalAcc} end, + + %% Looks up an already inserted ExprId and makes moves + %% of variables from that expression to this expression. + RemoveFunc = fun() -> + {value, Vars} = update_inserted_lookup(UpdateAcc, ExprId), + RemoveFuncVars(Vars) end, + + %% Is ExprId already inserted? + IsLocal = ?SETS:is_element(ExprId, LocalAcc), + + case DirectReplace of + true -> + %% The Instr is reffering to an expression that is + %% defined as an identical already present instruction, + %% and can be removed directly. + RemoveFuncVars(expr_defs(Expr)); + false when IsLocal -> + %% The instruction has already been inserted. + RemoveFunc(); + _ -> + case ?SETS:is_element(ExprId, Delete) of + true -> + %% should not be inserted + RemoveFunc(); + _ -> + %% Should remain + UpdateAcc2 = update_inserted_add(UpdateAcc, ExprId, Defs), + LocalAcc2 = ?SETS:add_element(ExprId, LocalAcc), + {[Instr | CodeAcc], UpdateAcc2, LocalAcc2} + end + end; + redtest when DelRed -> + %% delete reduction test + {CodeAcc, UpdateAcc, LocalAcc}; + _ -> + {[Instr | CodeAcc], UpdateAcc, LocalAcc} + end + end, {[], Update, ?SETS:new()}, Code), + + %debug_struct("RW Label 2: ", Label), + + %% calculate the inserts that are new to this node, that is + %% the expressions that are in Inserts but not in UpExpr, + %% and that have not been added already, + %% that is not present in LocalAcc + NewInserts = ?SETS:subtract(?SETS:subtract(Inserts, UpExpr), LocalAcc), + + {NewCodeRest, NewUpdate2} = + ?SETS:fold(fun(ExprId, {CodeAcc, UpdateAcc}) -> + Expr = maps_expr_get(ExprId, Maps), + {ExprInstr, Defs} = GetInstrFunc(Expr), + {UpdateAcc2, NewDefs} = update_inserted_add_new(UpdateAcc, ExprId, Defs), + + %% check if there exists an identical expression, so that + %% this expression can be replaced directly. + CodeAcc2 = + case expr_direct_replace(Expr) of + false -> + NewInstr = rewrite_expr(UpdateAcc2, ExprInstr, NewDefs), + [NewInstr | CodeAcc]; + true -> + mk_defs_moves(CodeAcc, NewDefs, Defs) + end, + {CodeAcc2, UpdateAcc2} + end, {CodeRest, NewUpdate}, NewInserts), + + NewCode = lists:reverse([CodeLast | NewCodeRest]), + + %% Check if we are to insert new reduction test here... + {NewCode2, NewUpdate3} = + case PreLoop andalso ?SETS:size(Inserts) > 0 andalso not DelRed of + true -> + {[hipe_icode:mk_primop([], redtest, []) | NewCode], update_del_red_test_set(NewUpdate2)}; + false -> + {NewCode, NewUpdate2} + end, + + NewBB = hipe_bb:mk_bb(NewCode2), + NewCFG = hipe_icode_cfg:bb_add(CFG, Label, NewBB), + + {NewCFG, NewUpdate3}. + +%%----------------------------------------------------------------------------- +%% Create a new structure instruction from Instr with destination Defs +%% from the insert mapping in Update. + +rewrite_expr(Update, Instr, Defs) -> + NewInstr = + replace_call_variables(fun(Ref) -> + case Ref of + {ExprId, Num} when is_integer(ExprId) -> + {value, DefList} = update_inserted_lookup(Update, ExprId), + lists:nth(Num, DefList); + _ -> Ref + end end, Instr), + hipe_icode:call_dstlist_update(NewInstr, Defs). + +%%----------------------------------------------------------------------------- +%% Make move instructions from Defs list to all variables in +%% the Refs list and insert into Code. + +mk_defs_moves(Code, [], []) -> Code; +mk_defs_moves(Code, [Ref | Refs], [Def | Defs]) -> + mk_defs_moves([hipe_icode:mk_move(Ref, Def) | Code], Refs, Defs). + +%%----------------------------------------------------------------------------- +%% Utilities + +new_work() -> + {[], gb_sets:new()}. + +add_work_list(List, Work) -> + lists:foldl(fun(Label, WorkAcc) -> + add_work_label(Label, WorkAcc) end, Work, List). + +add_work_label(Label, {List, Set}) -> + case gb_sets:is_member(Label, Set) of + false -> + {[Label | List], gb_sets:add(Label, Set)}; + true -> + {List, Set} + end. + +get_work_list({List, _}) -> + lists:reverse(List). + +%%----------------------------------------------------------------------------- +%% instr_type +%% gets a tag for the type of instruction that is passed in I + +struct_instr_type(I) -> + case I of + #icode_call{type = primop, 'fun' = mktuple} -> + %%debug_count_case(?SR_STRUCT_INSTR_TYPE, #call{type = primop, 'fun' = mktuple}), + struct; + #icode_call{type = primop, 'fun' = cons} -> + %%debug_count_case(?SR_STRUCT_INSTR_TYPE, #call{type = primop, 'fun' = cons}), + struct; + #icode_call{type = primop, 'fun' = redtest} -> + %%debug_count_case(?SR_STRUCT_INSTR_TYPE, #call{type = primop, 'fun' = redtest}), + redtest; + _ -> + %%debug_count_case(?SR_STRUCT_INSTR_TYPE, other), + other + end. + +instr_type(I) -> + case I of + %#call{type = primop, dstlist = List} when length(List) >= 1 -> struct; + #icode_call{type = primop, 'fun' = {unsafe_element, Elem}, dstlist = [DstVar], args = [SrcVar]} -> + %%debug_count_case(?SR_INSTR_TYPE, #call{type = primop, 'fun' = {unsafe_element, num}}), + {struct_elems, Elem, DstVar, SrcVar}; + #icode_phi{} -> + %%debug_count_case(?SR_INSTR_TYPE,#phi{}), + phi; + #icode_enter{} -> + %%debug_count_case(?SR_INSTR_TYPE,#enter{}), + return; + #icode_return{} -> + %%debug_count_case(?SR_INSTR_TYPE,#return{}), + return; + #icode_call{type = primop, 'fun' = mktuple} -> + %%debug_count_case(?SR_INSTR_TYPE, #call{type = primop, 'fun' = mktuple}), + struct; + #icode_call{type = primop, 'fun' = cons} -> + %%debug_count_case(?SR_INSTR_TYPE, #call{type = primop, 'fun' = cons}), + struct; + #icode_call{type = primop, 'fun' = redtest} -> + %%debug_count_case(?SR_INSTR_TYPE, #call{type = primop, 'fun' = redtest}), + redtest; + #icode_type{test = {tuple, Size}, args = [Var]} -> + %%debug_count_case(?SR_INSTR_TYPE, #type{type = {tuple, size}}), + {struct_type, Size, Var, ?MKTUPLE}; + #icode_type{test = cons, args = [Var]} -> + %%debug_count_case(?SR_INSTR_TYPE,#type{type = cons}), + {struct_type, 2, Var, ?CONS}; + %#type{type = {atom, Atom}, args = [Var]} -> {atom_type, Atom, Var}; + #icode_call{type = primop, 'fun' = unsafe_hd, + dstlist = [DstVar], args = [SrcVar]} -> + %%debug_count_case(?SR_INSTR_TYPE,#call{type = primop, 'fun' = unsafe_hd}), + {struct_elems, 1, DstVar, SrcVar}; + #icode_call{type = primop, 'fun' = unsafe_tl, + dstlist = [DstVar], args = [SrcVar]} -> + %%debug_count_case(?SR_INSTR_TYPE, #call{type = primop, 'fun' = unsafe_tl}), + {struct_elems, 2, DstVar, SrcVar}; + #icode_switch_tuple_arity{term = Var, cases = Cases} -> + %%debug_count_case(?SR_INSTR_TYPE,#switch_tuple_arity{}), + {tuple_arity, Var, Cases}; + _ -> other + end. + +%%----------------------------------------------------------------------------- +%% Expression ID counter + +init_expr_id() -> + put({struct_reuse, expr_id_count}, 0). + +-spec new_expr_id() -> non_neg_integer(). +new_expr_id() -> + V = get({struct_reuse, expr_id_count}), + put({struct_reuse, expr_id_count}, V+1), + V. + +%%----------------------------------------------------------------------------- +%% Debug and print functions + +print_struct(String, Struct) -> + io:format(String), + erlang:display(Struct). + +print(String) -> + io:format(String). + +-ifdef(DEBUG). + +debug_count_case(Type, Case) -> + Cases = get(Type), + NewCases = + case gb_trees:lookup(Case, Cases) of + {value, Value} -> gb_trees:enter(Case, Value + 1, Cases); + none -> gb_trees:insert(Case, 1, Cases) + end, + put(Type, NewCases). + +debug_init_case_count(Type) -> + case get(Type) of + undefined -> put(Type, gb_trees:empty()); + _ -> ok + end. + +debug_print_case_count(Type) -> + Cases = get(Type), + debug_struct("Case type: ", Type), + debug_list("Cases: ", gb_trees:to_list(Cases)). + +set_debug_flag(Value) -> + put({struct_reuse, debug}, Value). + +get_debug_flag() -> get({struct_reuse, debug}). + +debug_function(FuncName, CFG) -> + Linear = hipe_icode_cfg:cfg_to_linear(CFG), + Func = hipe_icode:icode_fun(Linear), + case Func =:= FuncName orelse FuncName =:= nil of + true -> + set_debug_flag(true), + %% debug_struct("Code: ", hipe_icode_cfg:bb(CFG, 15)), + debug_struct("~nFunction name :", Func); + false -> + set_debug_flag(undefined) + end. + +debug_cfg_pp(CFG) -> + case get_debug_flag() of + true -> hipe_icode_cfg:pp(CFG); + _ -> none + end. + +debug_struct(String, Struct) -> + case get_debug_flag() of + true -> + io:format(String), + erlang:display(Struct); + _ -> none + end. + +debug(String) -> + case get_debug_flag() of + true -> io:format(String); + _ -> none + end. + +debug_list(String, List) -> + case get_debug_flag() of + true -> print_list(String, List); + _ -> none + end. + +print_list(String, List) -> + io:format(String), + io:format("~n"), + print_list_rec(List), + io:format("~n"). + +print_list_rec([]) -> ok; +print_list_rec([Struct | List]) -> + erlang:display(Struct), + print_list_rec(List). + +debug_nodes(Nodes) -> + lists:foreach(fun(Node) -> debug_node(Node) end, nodes_tree_values(Nodes)). + +debug_node(Node) -> + case get_debug_flag() of + true -> + print_struct("Node Label: ", Node#node.label), + print_struct("Code: ", Node#node.code), + print_struct("Phi: ", Node#node.phi), + print_struct("PreLoop: ", Node#node.pre_loop), + print_struct("Preds: ", Node#node.pred), + print_struct("Succ: ", Node#node.succ), + print_struct("Up Expr: ", Node#node.up_expr), + print_struct("Kill : ", Node#node.killed_expr), + print_struct("AnticIn: ", Node#node.antic_in), + print_struct("AnticOut: ", Node#node.antic_out), + print_struct("SubInserts: ", Node#node.sub_inserts), + print_struct("Inserts: ", Node#node.inserts), + print_struct("NonStructDefs: ", Node#node.non_struct_defs), + print_struct("Params: ", Node#node.struct_type), + print_struct("Elems: ", Node#node.struct_elems), + io:format("~n"); + _ -> none + end. + +-endif. diff --git a/lib/hipe/icode/hipe_icode_type.erl b/lib/hipe/icode/hipe_icode_type.erl new file mode 100644 index 0000000000..28198467f7 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_type.erl @@ -0,0 +1,2266 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%%-------------------------------------------------------------------- +%%% File : hipe_icode_type.erl +%%% Author : Tobias Lindahl <[email protected]> +%%% Description : Propagate type information. +%%% +%%% Created : 25 Feb 2003 by Tobias Lindahl <[email protected]> +%%% +%%% $Id$ +%%%-------------------------------------------------------------------- + +-module(hipe_icode_type). + +-export([cfg/4, unannotate_cfg/1, specialize/1]). + +%%===================================================================== +%% Icode Coordinator Callbacks +%%===================================================================== + +-export([replace_nones/1, + update__info/2, new__info/1, return__info/1, + return_none/0, return_none_args/2, return_any_args/2]). + +%%===================================================================== + +-include("../main/hipe.hrl"). +-include("hipe_icode.hrl"). +-include("hipe_icode_primops.hrl"). +-include("hipe_icode_type.hrl"). +-include("../flow/cfg.hrl"). + +-type args_fun() :: fun((mfa(), cfg()) -> [erl_types:erl_type()]). +-type call_fun() :: fun((mfa(), [_]) -> erl_types:erl_type()). +-type final_fun() :: fun((mfa(), [_]) -> 'ok'). +-type data() :: {mfa(), args_fun(), call_fun(), final_fun()}. + +%-define(DO_HIPE_ICODE_TYPE_TEST, false). + +-ifdef(DO_HIPE_ICODE_TYPE_TEST). +-export([test/0]). +-endif. + +-define(MFA_debug, fun(_, _, _) -> ok end). + +%-define(debug, fun(X, Y) -> io:format("~s ~p~n", [X, Y]) end). +-define(debug, fun(_, _) -> ok end). + +%-define(flow_debug, fun(X, Y) -> io:format("flow: ~s ~p~n", [X, Y]) end). +-define(flow_debug, fun(_, _) -> ok end). + +%-define(widening_debug, fun(X, Y) -> io:format("wid: ~s ~p~n", [X, Y]) end). +-define(widening_debug, fun(_, _) -> ok end). + +%-define(call_debug, fun(X, Y) -> io:format("call: ~s ~p~n", [X, Y]) end). +-define(call_debug, fun(_, _) -> ok end). + +%-define(ineq_debug, fun(X, Y) -> io:format("ineq: ~s ~p~n", [X, Y]) end). +-define(ineq_debug, fun(_, _) -> ok end). + +%-define(server_debug, fun(X, Y) -> io:format("~p server: ~s ~p~n", [self(), X, Y]) end). +-define(server_debug, fun(_, _) -> ok end). + +-import(erl_types, [min/2, max/2, number_min/1, number_max/1, + t_any/0, t_atom/1, t_atom/0, t_atom_vals/1, + t_binary/0, t_bitstr/0, t_bitstr_base/1, t_bitstr_unit/1, + t_boolean/0, t_cons/0, t_constant/0, + t_float/0, t_from_term/1, t_from_range/2, + t_fun/0, t_fun/1, t_fun/2, t_fun_args/1, t_fun_arity/1, + t_inf/2, t_inf_lists/2, t_integer/0, + t_integer/1, t_is_atom/1, t_is_any/1, + t_is_binary/1, t_is_bitstr/1, t_is_bitwidth/1, t_is_boolean/1, + t_is_fixnum/1, t_is_cons/1, t_is_constant/1, + t_is_maybe_improper_list/1, t_is_equal/2, t_is_float/1, + t_is_fun/1, t_is_integer/1, t_is_non_neg_integer/1, + t_is_number/1, t_is_matchstate/1, + t_is_nil/1, t_is_none/1, t_is_port/1, t_is_pid/1, + t_is_reference/1, t_is_subtype/2, t_is_tuple/1, + t_limit/2, t_matchstate_present/1, t_matchstate/0, + t_matchstate_slots/1, t_maybe_improper_list/0, + t_nil/0, t_none/0, t_number/0, t_number/1, t_number_vals/1, + t_pid/0, t_port/0, t_reference/0, t_subtract/2, t_sup/2, + t_to_tlist/1, t_tuple/0, t_tuple/1, t_tuple_sizes/1]). + +-record(state, {info_map = gb_trees:empty() :: gb_tree(), + cfg :: cfg(), + liveness = gb_trees:empty() :: gb_tree(), + arg_types :: [erl_types:erl_type()], + ret_type = [t_none()] :: [erl_types:erl_type()], + lookupfun :: call_fun(), + resultaction :: final_fun()}). + +%%----------------------------------------------------------------------- +%% The main exported function +%%----------------------------------------------------------------------- + +-spec cfg(cfg(), mfa(), comp_options(), #comp_servers{}) -> cfg(). + +cfg(Cfg, MFA, Options, Servers) -> + case proplists:get_bool(concurrent_comp, Options) of + true -> + concurrent_cfg(Cfg, MFA, Servers#comp_servers.type); + false -> + ordinary_cfg(Cfg, MFA) + end. + +concurrent_cfg(Cfg, MFA, CompServer) -> + CompServer ! {ready, {MFA, self()}}, + {ArgsFun, CallFun, FinalFun} = do_analysis(Cfg, MFA), + Ans = do_rewrite(Cfg, MFA, ArgsFun, CallFun, FinalFun), + CompServer ! {done_rewrite, MFA}, + Ans. + +do_analysis(Cfg, MFA) -> + receive + {analyse, {ArgsFun,CallFun,FinalFun}} -> + analyse(Cfg, {MFA,ArgsFun,CallFun,FinalFun}), + do_analysis(Cfg, MFA); + {done, {_NewArgsFun,_NewCallFun,_NewFinalFun} = Done} -> + Done + end. + +do_rewrite(Cfg, MFA, ArgsFun, CallFun, FinalFun) -> + common_rewrite(Cfg, {MFA,ArgsFun,CallFun,FinalFun}). + +ordinary_cfg(Cfg, MFA) -> + Data = make_data(Cfg,MFA), + common_rewrite(Cfg, Data). + +common_rewrite(Cfg, Data) -> + State = safe_analyse(Cfg, Data), + NewState = simplify_controlflow(State), + NewCfg = state__cfg(annotate_cfg(NewState)), + hipe_icode_cfg:remove_unreachable_code(specialize(NewCfg)). + +make_data(Cfg, {_M,_F,A}=MFA) -> + NoArgs = + case hipe_icode_cfg:is_closure(Cfg) of + true -> hipe_icode_cfg:closure_arity(Cfg); + false -> A + end, + Args = lists:duplicate(NoArgs, t_any()), + ArgsFun = fun(_,_) -> Args end, + CallFun = fun(_,_) -> t_any() end, + FinalFun = fun(_,_) -> ok end, + {MFA,ArgsFun,CallFun,FinalFun}. + +%%debug_make_data(Cfg, {_M,_F,A}=MFA) -> +%% NoArgs = +%% case hipe_icode_cfg:is_closure(Cfg) of +%% true -> hipe_icode_cfg:closure_arity(Cfg); +%% false -> A +%% end, +%% Args = lists:duplicate(NoArgs, t_any()), +%% ArgsFun = fun(MFA,_Cfg) -> io:format("Start:~p~n",[MFA]),Args end, +%% CallFun = fun(MFA,Types) -> io:format("Call With:~p~nTo:~p~n",[Types,MFA]), t_any() end, +%% FinalFun = fun(MFA,Type) -> io:format("ResType:~p~nFor:~p~n",[Type,MFA]),ok end, +%% {MFA,ArgsFun,CallFun,FinalFun}. + + +%%------------------------------------------------------------------- +%% Global type analysis on the whole function. Demands that the code +%% is in SSA form. When we encounter a phi node, the types of the +%% arguments are joined. At the end of a block the information out is +%% joined with the current information in for all _valid_ successors, +%% that is, of all successors that actually can be reached. If the +%% join produces new information in for the successor, this +%% information is added to the worklist. +%%------------------------------------------------------------------- + +-spec analyse(cfg(), data()) -> 'ok'. + +analyse(Cfg, Data) -> + try + #state{} = safe_analyse(Cfg, Data), + ok + catch throw:no_input -> ok % No need to do anything since we have no input + end. + +-spec safe_analyse(cfg(), data()) -> #state{}. + +safe_analyse(Cfg, {MFA,_,_,_}=Data) -> + State = new_state(Cfg, Data), + NewState = analyse_blocks(State,MFA), + (state__resultaction(NewState))(MFA,state__ret_type(NewState)), + NewState. + +analyse_blocks(State, MFA) -> + Work = init_work(State), + analyse_blocks(Work, State, MFA). + +analyse_blocks(Work, State, MFA) -> + case get_work(Work) of + fixpoint -> + State; + {Label, NewWork} -> + Info = state__info_in(State, Label), + {NewState, NewLabels} = + try analyse_block(Label, Info, State) + catch throw:none_type -> + %% io:format("received none type at label: ~p~n",[Label]), + {State,[]} + end, + NewWork2 = add_work(NewWork, NewLabels), + analyse_blocks(NewWork2, NewState, MFA) + end. + +analyse_block(Label, InfoIn, State) -> + BB = state__bb(State, Label), + Code = hipe_bb:butlast(BB), + Last = hipe_bb:last(BB), + InfoOut = analyse_insns(Code, InfoIn, state__lookupfun(State)), + NewState = state__info_out_update(State, Label, InfoOut), + case Last of + #icode_if{} -> + UpdateInfo = do_if(Last, InfoOut), + do_updates(NewState, UpdateInfo); + #icode_type{} -> + UpdateInfo = do_type(Last, InfoOut), + do_updates(NewState, UpdateInfo); + #icode_switch_tuple_arity{} -> + UpdateInfo = do_switch_tuple_arity(Last, InfoOut), + do_updates(NewState, UpdateInfo); + #icode_switch_val{} -> + UpdateInfo = do_switch_val(Last, InfoOut), + do_updates(NewState, UpdateInfo); + #icode_enter{} -> + NewState1 = do_enter(Last, InfoOut, NewState, state__lookupfun(NewState)), + do_updates(NewState1,[]); + #icode_call{} -> + {NewState1,UpdateInfo} = do_last_call(Last, InfoOut, NewState, Label), + do_updates(NewState1, UpdateInfo); + #icode_return{} -> + NewState1 = do_return(Last, InfoOut, NewState), + do_updates(NewState1,[]); + _ -> + UpdateInfo = [{X, InfoOut} || X <- state__succ(NewState, Label)], + do_updates(NewState, UpdateInfo) + end. + +analyse_insns([I|Insns], Info, LookupFun) -> + NewInfo = analyse_insn(I, Info, LookupFun), + analyse_insns(Insns, NewInfo, LookupFun); +analyse_insns([], Info, _) -> + Info. + +analyse_insn(I, Info, LookupFun) -> + case I of + #icode_move{} -> + do_move(I, Info); + #icode_call{} -> + NewInfo = do_call(I, Info, LookupFun), + %%io:format("Analysing Call: ~w~n~w~n", [I,NewInfo]), + update_call_arguments(I, NewInfo); + #icode_phi{} -> + Type = t_limit(join_list(hipe_icode:args(I), Info), ?TYPE_DEPTH), + enter_defines(I, Type, Info); + #icode_begin_handler{} -> + enter_defines(I, t_any(), Info); + _ -> + %% Just an assert + case defines(I) of + [] -> Info; + _ -> exit({"Instruction with destination not analysed", I}) + end + end. + +do_move(I, Info) -> + %% Can't use uses/1 since we must keep constants. + [Src] = hipe_icode:args(I), + enter_defines(I, lookup(Src, Info), Info). + +do_basic_call(I, Info, LookupFun) -> + case hipe_icode:call_type(I) of + primop -> + Fun = hipe_icode:call_fun(I), + ArgTypes = lookup_list(hipe_icode:args(I), Info), + primop_type(Fun, ArgTypes); + remote -> + {M, F, A} = hipe_icode:call_fun(I), + ArgTypes = lookup_list(hipe_icode:args(I), Info), + None = t_none(), + case erl_bif_types:type(M, F, A, ArgTypes) of + None -> + NewArgTypes = add_funs_to_arg_types(ArgTypes), + erl_bif_types:type(M, F, A, NewArgTypes); + Other -> + Other + end; + local -> + MFA = hipe_icode:call_fun(I), + ArgTypes = lookup_list(hipe_icode:args(I), Info), + %% io:format("Call:~p~nTypes: ~p~n",[I,ArgTypes]), + LookupFun(MFA,ArgTypes) + end. + +do_call(I, Info, LookupFun) -> + RetType = do_basic_call(I, Info, LookupFun), + IsNone = t_is_none(RetType), + %% io:format("RetType ~p~nIsNone ~p~n~p~n",[RetType,IsNone,I]), + if IsNone -> throw(none_type); + true -> enter_defines(I, RetType, Info) + end. + +do_safe_call(I, Info, LookupFun) -> + RetType = do_basic_call(I, Info, LookupFun), + enter_defines(I, RetType, Info). + +do_last_call(Last, InfoOut, State, Label) -> + try + NewInfoOut = do_call(Last, InfoOut, state__lookupfun(State)), + NewState = state__info_out_update(State, Label, NewInfoOut), + ContInfo = update_call_arguments(Last, NewInfoOut), + Cont = hipe_icode:call_continuation(Last), + Fail = hipe_icode:call_fail_label(Last), + ?call_debug("Continfo, NewInfoOut", {ContInfo, NewInfoOut}), + UpdateInfo = + case Fail of + [] -> + [{Cont, ContInfo}]; + _ -> + case call_always_fails(Last, InfoOut) of + true -> + [{Fail, NewInfoOut}]; + false -> + Fun = hipe_icode:call_fun(Last), + case hipe_icode_primops:fails(Fun) of + true -> + [{Cont, ContInfo}, {Fail, NewInfoOut}]; + false -> + [{Cont, ContInfo}] + end + end + end, + {NewState,UpdateInfo} + catch throw:none_type -> + State2 = state__info_out_update(State, Label, InfoOut), + case hipe_icode:call_fail_label(Last) of + [] -> throw(none_type); + FailLbl -> + {State2,[{FailLbl, InfoOut}]} + end + end. + +call_always_fails(#icode_call{} = I, Info) -> + case hipe_icode:call_fun(I) of + %% These can actually be calls too. + {erlang, halt, 0} -> false; + {erlang, halt, 1} -> false; + {erlang, exit, 1} -> false; + {erlang, error, 1} -> false; + {erlang, error, 2} -> false; + {erlang, throw, 1} -> false; + {erlang, hibernate, 3} -> false; + Fun -> + case hipe_icode:call_type(I) of + primop -> + Args = safe_lookup_list(hipe_icode:call_args(I), Info), + ReturnType = primop_type(Fun, Args), + t_is_none(ReturnType); + _ -> false + end + end. + +do_enter(I, Info, State, LookupFun) -> + %% io:format("Enter:~p~n",[I]), + ArgTypes = lookup_list(hipe_icode:args(I), Info), + RetTypes = + case hipe_icode:enter_type(I) of + local -> + MFA = hipe_icode:enter_fun(I), + LookupFun(MFA,ArgTypes); + remote -> + {M, F, A} = hipe_icode:enter_fun(I), + None = t_none(), + case erl_bif_types:type(M, F, A, ArgTypes) of + None -> + NewArgTypes = add_funs_to_arg_types(ArgTypes), + erl_bif_types:type(M, F, A, NewArgTypes); + Other -> + Other + end; + primop -> + Fun = hipe_icode:enter_fun(I), + primop_type(Fun, ArgTypes) + end, + state__ret_type_update(State, RetTypes). + +do_return(I, Info, State) -> + RetTypes = lookup_list(hipe_icode:args(I), Info), + state__ret_type_update(State, RetTypes). + +do_if(I, Info) -> + %% XXX: Could probably do better than this. + TrueLab = hipe_icode:if_true_label(I), + FalseLab = hipe_icode:if_false_label(I), + case hipe_icode:if_args(I) of + [Arg1, Arg2] = Args -> + [Type1, Type2] = lookup_list(Args, Info), + case t_is_none(Type1) orelse t_is_none(Type2) of + true -> + [{TrueLab, Info}, {FalseLab, Info}]; + false -> + Inf = t_inf(Type1, Type2), + case hipe_icode:if_op(I) of + '=:='-> + case t_is_none(Inf) of + true -> + [{FalseLab, Info}]; + false -> + [{TrueLab, enter(Arg1, Inf, enter(Arg2, Inf, Info))}, + {FalseLab, Info}] + end; + '=/=' -> + case t_is_none(Inf) of + true -> + [{TrueLab, Info}]; + false -> + [{FalseLab, enter(Arg1, Inf, enter(Arg2, Inf, Info))}, + {TrueLab, Info}] + end; + '==' -> + [{TrueLab, Info}, {FalseLab, Info}]; + '/=' -> + [{TrueLab, Info}, {FalseLab, Info}]; + Op -> + integer_range_inequality_propagation(Op, Arg1, Arg2, + TrueLab, FalseLab, Info) + %%_ -> + %% [{TrueLab, Info}, {FalseLab, Info}] + end + end; + _ -> + %% Only care for binary if:s + [{TrueLab, Info}, {FalseLab, Info}] + end. + +integer_range_inequality_propagation(Op, A1, A2, TrueLab, FalseLab, Info) -> + Arg1 = lookup(A1, Info), + Arg2 = lookup(A2, Info), + ?ineq_debug("args", [Arg1,Arg2]), + IntArg1 = t_inf(Arg1, t_integer()), + IntArg2 = t_inf(Arg2, t_integer()), + NonIntArg1 = t_subtract(Arg1, t_integer()), + NonIntArg2 = t_subtract(Arg2, t_integer()), + ?ineq_debug("nonintargs", [NonIntArg1,NonIntArg2]), + case t_is_none(IntArg1) or t_is_none(IntArg2) of + true -> + ?ineq_debug("one is none", [IntArg1,IntArg2]), + [{TrueLab, Info}, {FalseLab, Info}]; + false -> + case Op of + '>=' -> + {FalseArg1, FalseArg2, TrueArg1, TrueArg2} = + integer_range_less_then_propagator(IntArg1, IntArg2); + '>' -> + {TrueArg2, TrueArg1, FalseArg2, FalseArg1} = + integer_range_less_then_propagator(IntArg2, IntArg1); + '<' -> + {TrueArg1, TrueArg2, FalseArg1, FalseArg2} = + integer_range_less_then_propagator(IntArg1, IntArg2); + '=<' -> + {FalseArg2, FalseArg1, TrueArg2, TrueArg1} = + integer_range_less_then_propagator(IntArg2, IntArg1) + end, + ?ineq_debug("int res", [TrueArg1, TrueArg2, FalseArg1, FalseArg2]), + False = {FalseLab, enter(A1, t_sup(FalseArg1, NonIntArg1), + enter(A2, t_sup(FalseArg2, NonIntArg2), Info))}, + True = {TrueLab, enter(A1, t_sup(TrueArg1, NonIntArg1), + enter(A2, t_sup(TrueArg2, NonIntArg2), Info))}, + [True, False] + end. + +integer_range_less_then_propagator(IntArg1, IntArg2) -> + Min1 = number_min(IntArg1), + Max1 = number_max(IntArg1), + Min2 = number_min(IntArg2), + Max2 = number_max(IntArg2), + %% is this the same as erl_types:t_subtract?? no ... ?? + TrueMax1 = min(Max1, erl_bif_types:infinity_add(Max2, -1)), + TrueMin2 = max(erl_bif_types:infinity_add(Min1, 1), Min2), + FalseMin1 = max(Min1, Min2), + FalseMax2 = min(Max1, Max2), + {t_from_range(Min1, TrueMax1), + t_from_range(TrueMin2, Max2), + t_from_range(FalseMin1, Max1), + t_from_range(Min2, FalseMax2)}. + +do_type(I, Info) -> + case hipe_icode:args(I) of + [Var] -> do_type(I, Info, Var); + [Var1,Var2] -> do_type2(I, Info, Var1, Var2) + end. + +do_type2(I, Info, FunVar, ArityVar) -> % function2(Fun,Arity) + %% Just for sanity. + function2 = hipe_icode:type_test(I), + FunType = lookup(FunVar, Info), + ArityType = lookup(ArityVar, Info), + TrueLab = hipe_icode:type_true_label(I), + FalseLab = hipe_icode:type_false_label(I), + SuccType1 = t_inf(t_fun(), FunType), + case combine_test(test_type(function, FunType), + test_type(integer, ArityType)) of + true -> + case t_number_vals(ArityType) of + [Arity] -> + case t_fun_arity(SuccType1) of + unknown -> + SuccType = t_inf(t_fun(Arity,t_any()),FunType), + [{TrueLab, enter(FunVar, SuccType, Info)}, + {FalseLab, Info}]; + Arity when is_integer(Arity) -> + FalseType = t_subtract(FunType, t_fun(Arity, t_any())), + [{TrueLab, enter(FunVar, SuccType1, Info)}, + {FalseLab, enter(FunVar, FalseType, Info)}] + end; + _ -> + case t_fun_arity(SuccType1) of + unknown -> + [{TrueLab, enter(FunVar,SuccType1,Info)}, + {FalseLab, Info}]; + Arity when is_integer(Arity) -> + T = t_from_term(Arity), + NewInfo = enter(ArityVar, T, Info), + [{TrueLab, enter(FunVar, SuccType1, NewInfo)}, + {FalseLab, enter(ArityVar, t_subtract(T, ArityType), Info)}] + end + end; + false -> + [{FalseLab, Info}]; + maybe -> + GenTrueArity = t_inf(t_integer(), ArityType), + GenTrueFun = t_inf(t_fun(), FunType), + case {t_number_vals(GenTrueArity), t_fun_arity(GenTrueFun)} of + {unknown, unknown} -> + TrueInfo = enter_list([FunVar, ArityVar], + [GenTrueFun, GenTrueArity], Info), + [{TrueLab, TrueInfo}, {FalseLab, Info}]; + {unknown, Arity} when is_integer(Arity) -> + TrueInfo = enter_list([FunVar, ArityVar], + [GenTrueFun, t_integer(Arity)], Info), + [{TrueLab, TrueInfo}, {FalseLab, Info}]; + {[Val], unknown} when is_integer(Val) -> + TrueInfo = enter_list([FunVar, ArityVar], + [t_inf(GenTrueFun, t_fun(Val, t_any())), + GenTrueArity], Info), + [{TrueLab, TrueInfo}, {FalseLab, Info}]; + {Vals, unknown} when is_list(Vals) -> + %% The function type gets widened when we have more than one arity. + TrueInfo = enter_list([FunVar, ArityVar], + [GenTrueFun, GenTrueArity], Info), + [{TrueLab, TrueInfo}, {FalseLab, Info}]; + {Vals, Arity} when is_list(Vals), is_integer(Arity) -> + case lists:member(Arity, Vals) of + false -> + [{FalseLab, Info}]; + true -> + TrueInfo = enter_list([FunVar, ArityVar], + [GenTrueFun, t_integer(Arity)], Info), + [{TrueLab, TrueInfo}, {FalseLab, Info}] + end + end + end. + +combine_test(true, true) -> true; +combine_test(false, _) -> false; +combine_test(_, false) -> false; +combine_test(_, _) -> maybe. + +do_type(I, Info, Var) -> + TrueLab = hipe_icode:type_true_label(I), + FalseLab = hipe_icode:type_false_label(I), + None = t_none(), + + case lookup(Var, Info) of + None -> + [{TrueLab, Info}, {FalseLab, Info}]; + VarInfo -> + case hipe_icode:type_test(I) of + cons -> + test_cons_or_nil(t_cons(), Var, VarInfo, TrueLab, FalseLab, Info); + nil -> + test_cons_or_nil(t_nil(), Var, VarInfo, TrueLab, FalseLab, Info); + {atom, A} = Test -> + test_number_or_atom(fun(X) -> t_atom(X) end, + A, Var, VarInfo, Test, TrueLab, FalseLab, Info); + {integer, N} = Test -> + test_number_or_atom(fun(X) -> t_number(X) end, + N, Var, VarInfo, Test, TrueLab, FalseLab, Info); + {record, Atom, Size} -> + test_record(Atom, Size, Var, VarInfo, TrueLab, FalseLab, Info); + Other -> + case t_is_any(VarInfo) of + true -> + TrueType = t_inf(true_branch_info(Other), VarInfo), + TrueInfo = enter(Var, TrueType, Info), + [{TrueLab, TrueInfo}, {FalseLab, Info}]; + false -> + case test_type(Other, VarInfo) of + true -> + [{TrueLab, Info}]; + false -> + [{FalseLab, Info}]; + maybe -> + TrueType = t_inf(true_branch_info(Other), VarInfo), + TrueInfo = enter(Var, TrueType, Info), + FalseType = t_subtract(VarInfo, TrueType), + FalseInfo = enter(Var, FalseType, Info), + [{TrueLab, TrueInfo}, {FalseLab, FalseInfo}] + end + end + end + end. + +do_switch_tuple_arity(I, Info) -> + Var = hipe_icode:switch_tuple_arity_term(I), + VarType = lookup(Var, Info), + Cases = hipe_icode:switch_tuple_arity_cases(I), + FailLabel = hipe_icode:switch_tuple_arity_fail_label(I), + case legal_switch_tuple_arity_cases(Cases, VarType) of + [] -> + [{FailLabel, Info}]; + LegalCases -> + {Fail, UpdateInfo} = + switch_tuple_arity_update_info(LegalCases, Var, VarType, + FailLabel, VarType, Info, []), + case switch_tuple_arity_can_fail(LegalCases, VarType) of + true -> [Fail|UpdateInfo]; + false -> UpdateInfo + end + end. + +legal_switch_tuple_arity_cases(Cases, Type) -> + case t_is_tuple(Type) of + false -> + Inf = t_inf(t_tuple(), Type), + case t_is_tuple(Inf) of + true -> legal_switch_tuple_arity_cases_1(Cases, Inf); + false -> [] + end; + true -> + legal_switch_tuple_arity_cases_1(Cases, Type) + end. + +legal_switch_tuple_arity_cases_1(Cases, Type) -> + case t_tuple_sizes(Type) of + unknown -> + Cases; + TupleSizes -> + [Case || {Size, _Label} = Case <- Cases, + lists:member(hipe_icode:const_value(Size), TupleSizes)] + end. + +switch_tuple_arity_can_fail(LegalCases, ArgType) -> + case t_is_tuple(ArgType) of + false -> true; + true -> + case t_tuple_sizes(ArgType) of + unknown -> true; + Sizes1 -> + Sizes2 = [hipe_icode:const_value(X) || {X, _} <- LegalCases], + Set1 = sets:from_list(Sizes1), + Set2 = sets:from_list(Sizes2), + not sets:is_subset(Set1, Set2) + end + end. + +switch_tuple_arity_update_info([{Arity, Label}|Left], Var, TupleType, + FailLabel, FailType, Info, Acc) -> + Inf = t_inf(TupleType, t_tuple(hipe_icode:const_value(Arity))), + NewInfo = enter(Var, Inf, Info), + NewFailType = t_subtract(FailType, Inf), + switch_tuple_arity_update_info(Left, Var, TupleType, FailLabel, NewFailType, + Info, [{Label, NewInfo}|Acc]); +switch_tuple_arity_update_info([], Var, _TupleType, + FailLabel, FailType, Info, Acc) -> + {{FailLabel, enter(Var, FailType, Info)}, Acc}. + +do_switch_val(I, Info) -> + Var = hipe_icode:switch_val_term(I), + VarType = lookup(Var, Info), + Cases = hipe_icode:switch_val_cases(I), + FailLabel = hipe_icode:switch_val_fail_label(I), + case legal_switch_val_cases(Cases, VarType) of + [] -> + [{FailLabel, Info}]; + LegalCases -> + switch_val_update_info(LegalCases, Var, VarType, + FailLabel, VarType, Info, []) + end. + +legal_switch_val_cases(Cases, Type) -> + legal_switch_val_cases(Cases, Type, []). + +legal_switch_val_cases([{Val, _Label} = VL|Left], Type, Acc) -> + ConstType = t_from_term(hipe_icode:const_value(Val)), + case t_is_subtype(ConstType, Type) of + true -> + legal_switch_val_cases(Left, Type, [VL|Acc]); + false -> + legal_switch_val_cases(Left, Type, Acc) + end; +legal_switch_val_cases([], _Type, Acc) -> + lists:reverse(Acc). + +switch_val_update_info([{Const, Label}|Left], Arg, ArgType, + FailLabel, FailType, Info, Acc) -> + TrueType = t_from_term(hipe_icode:const_value(Const)), + NewInfo = enter(Arg, TrueType, Info), + NewFailType = t_subtract(FailType, TrueType), + switch_val_update_info(Left, Arg, ArgType, FailLabel, NewFailType, + Info, [{Label, NewInfo}|Acc]); +switch_val_update_info([], Arg, _ArgType, FailLabel, FailType,Info, Acc) -> + [{FailLabel, enter(Arg, FailType, Info)}|Acc]. + +test_cons_or_nil(Type, Var, VarInfo, TrueLab, FalseLab, Info) -> + case t_is_any(VarInfo) of + true -> + [{TrueLab, enter(Var, Type, Info)}, + {FalseLab, Info}]; + false -> + TrueType = t_inf(VarInfo, Type), + FalseType = t_subtract(VarInfo, TrueType), + case t_is_none(FalseType) of + true -> + [{TrueLab, Info}]; + false -> + case t_is_none(TrueType) of + true -> + [{FalseLab, Info}]; + false -> + [{TrueLab, enter(Var, TrueType, Info)}, + {FalseLab, enter(Var, FalseType, Info)}] + end + end + end. + +test_number_or_atom(Fun, X, Var, VarInfo, TypeTest, + TrueLab, FalseLab, Info) -> + case t_is_any(VarInfo) of + true -> + [{TrueLab, enter(Var, Fun(X), Info)}, + {FalseLab, Info}]; + false -> + case test_type(TypeTest, VarInfo) of + false -> + [{FalseLab, Info}]; + true -> + [{TrueLab, Info}]; + maybe -> + FalseType = t_subtract(VarInfo, Fun(X)), + [{TrueLab, enter(Var, Fun(X), Info)}, + {FalseLab, enter(Var, FalseType, Info)}] + end + end. + +test_record(Atom, Size, Var, VarInfo, TrueLab, FalseLab, Info) -> + AnyList = lists:duplicate(Size - 1, t_any()), + RecordType = t_tuple([t_atom(Atom)|AnyList]), + Inf = t_inf(RecordType, VarInfo), + case t_is_none(Inf) of + true -> + [{FalseLab, Info}]; + false -> + Sub = t_subtract(VarInfo, Inf), + case t_is_none(Sub) of + true -> + [{TrueLab, enter(Var, Inf, Info)}]; + false -> + [{TrueLab, enter(Var, Inf, Info)}, + {FalseLab, enter(Var, Sub, Info)}] + end + end. + +test_type(Test, Type) -> + %%io:format("Test is: ~w\n", [Test]), + %%io:format("Type is: ~s\n", [format_type(Type)]), + Ans = + case t_is_any(Type) of + true -> maybe; + false -> + TrueTest = true_branch_info(Test), + Inf = t_inf(TrueTest, Type), + %%io:format("TrueTest is: ~s\n", [format_type(TrueTest)]), + %%io:format("Inf is: ~s\n", [format_type(Inf)]), + case t_is_equal(Type, Inf) of + true -> + not t_is_none(Type); + false -> + case t_is_equal(TrueTest, Inf) of + true -> + case test_type0(Test, Type) of + false -> + maybe; + true -> + true; + maybe -> + maybe + end; + false -> + case test_type0(Test, Inf) of + true -> + maybe; + false -> + false; + maybe -> + maybe + end + end + end + end, + %% io:format("Result is: ~s\n\n", [Ans]), + Ans. + +test_type0(integer, T) -> + t_is_integer(T); +test_type0({integer, N}, T) -> + case t_is_integer(T) of + true -> + case t_number_vals(T) of + unknown -> maybe; + [N] -> true; + List when is_list(List) -> + case lists:member(N, List) of + true -> maybe; + false -> false + end + end; + false -> false + end; +test_type0(float, T) -> + t_is_float(T); +test_type0(number, T) -> + t_is_number(T); +test_type0(atom, T) -> + t_is_atom(T); +test_type0({atom, A}, T) -> + case t_is_atom(T) of + true -> + case t_atom_vals(T) of + unknown -> maybe; + [A] -> true; + List when is_list(List) -> + case lists:member(A, List) of + true -> maybe; + false -> false + end + end; + false -> false + end; +test_type0(tuple, T) -> + t_is_tuple(T); +test_type0({tuple, N}, T) -> + case t_is_tuple(T) of + true -> + case t_tuple_sizes(T) of + unknown -> maybe; + [X] when is_integer(X) -> X =:= N; + List when is_list(List) -> + case lists:member(N, List) of + true -> maybe; + false -> false + end + end; + false -> false + end; +test_type0(pid, T) -> + t_is_pid(T); +test_type0(port, T) -> + t_is_port(T); +test_type0(binary, T) -> + t_is_binary(T); +test_type0(bitstr, T) -> + t_is_bitstr(T); +test_type0(reference, T) -> + t_is_reference(T); +test_type0(function, T) -> + t_is_fun(T); +test_type0(boolean, T) -> + t_is_boolean(T); +test_type0(list, T) -> + t_is_maybe_improper_list(T); +test_type0(cons, T) -> + t_is_cons(T); +test_type0(nil, T) -> + t_is_nil(T); +test_type0(constant, T) -> + t_is_constant(T). + + +true_branch_info(integer) -> + t_integer(); +true_branch_info({integer, N}) -> + t_integer(N); +true_branch_info(float) -> + t_float(); +true_branch_info(number) -> + t_number(); +true_branch_info(atom) -> + t_atom(); +true_branch_info({atom, A}) -> + t_atom(A); +true_branch_info(list) -> + t_maybe_improper_list(); +true_branch_info(tuple) -> + t_tuple(); +true_branch_info({tuple, N}) -> + t_tuple(N); +true_branch_info(pid) -> + t_pid(); +true_branch_info(port) -> + t_port(); +true_branch_info(binary) -> + t_binary(); +true_branch_info(bitstr) -> + t_bitstr(); +true_branch_info(reference) -> + t_reference(); +true_branch_info(function) -> + t_fun(); +true_branch_info(cons) -> + t_cons(); +true_branch_info(nil) -> + t_nil(); +true_branch_info(boolean) -> + t_boolean(); +true_branch_info(constant) -> + t_constant(); +true_branch_info(T) -> + exit({?MODULE,unknown_typetest,T}). + + +%% _________________________________________________________________ +%% +%% Remove the redundant type tests. If a test is removed, the trace +%% that isn't taken is explicitly removed from the CFG to simpilify +%% the handling of Phi nodes. If a Phi node is left and at least one +%% branch into it has disappeared, the SSA propagation pass can't +%% handle it. +%% +%% If the CFG has changed at the end of this pass, the analysis is +%% done again since we might be able to find more information because +%% of the simplification of the CFG. +%% + +simplify_controlflow(State) -> + Cfg = state__cfg(State), + simplify_controlflow(hipe_icode_cfg:reverse_postorder(Cfg), State). + +simplify_controlflow([Label|Left], State) -> + Info = state__info_out(State, Label), + NewState = + case state__bb(State, Label) of + not_found -> State; + BB -> + I = hipe_bb:last(BB), + case I of + #icode_if{} -> + rewrite_if(State,I,BB,Info,Label); + #icode_type{} -> + rewrite_type(State,I,BB,Info,Label); + #icode_switch_tuple_arity{} -> + rewrite_switch_tuple_arity(State,I,BB,Info,Label); + #icode_switch_val{} -> + rewrite_switch_val(State,I,BB,Info,Label); + #icode_call{} -> + rewrite_call(State,I,BB,Info,Label); + _ -> + State + end + end, + simplify_controlflow(Left, NewState); +simplify_controlflow([], State) -> + State. + +rewrite_if(State, I, BB, Info, Label) -> + case do_if(I, Info) of + [{Lab, _}] -> + mk_goto(State, BB, Label, Lab); + [_,_] -> + State + end. + +rewrite_type(State, I, BB, Info, Label) -> + FalseLab = hipe_icode:type_false_label(I), + case hipe_icode:type_true_label(I) of + FalseLab -> + %% true label = false label, this can occur! + mk_goto(State, BB, Label, FalseLab); + TrueLab -> + case do_type(I, Info) of + [{TrueLab, _}] -> + mk_goto(State, BB, Label, TrueLab); + [{FalseLab, _}] -> + mk_goto(State, BB, Label, FalseLab); + [_,_] -> %% Maybe + State + end + end. + +rewrite_switch_tuple_arity(State, I, BB, Info, Label) -> + Cases = hipe_icode:switch_tuple_arity_cases(I), + Var = hipe_icode:switch_tuple_arity_term(I), + Type = safe_lookup(Var, Info), + case legal_switch_tuple_arity_cases(Cases, Type) of + [] -> + Fail = hipe_icode:switch_tuple_arity_fail_label(I), + mk_goto(State, BB, Label, Fail); + Cases -> + %% Nothing changed. + case switch_tuple_arity_can_fail(Cases, Type) of + true -> State; + false -> + NewCases = butlast(Cases), + {_Arity, NewFail} = lists:last(Cases), + TmpI = + hipe_icode:switch_tuple_arity_fail_label_update(I, NewFail), + NewI = + hipe_icode:switch_tuple_arity_cases_update(TmpI, NewCases), + NewBB = hipe_bb:code_update(BB, hipe_bb:butlast(BB) ++ [NewI]), + state__bb_add(State, Label, NewBB) + end; + LegalCases -> + NewI = + case switch_tuple_arity_can_fail(LegalCases, Type) of + true -> + hipe_icode:switch_tuple_arity_cases_update(I, LegalCases); + false -> + NewCases = butlast(LegalCases), + {_Arity, NewFail} = lists:last(LegalCases), + TmpI = + hipe_icode:switch_tuple_arity_cases_update(I, NewCases), + hipe_icode:switch_tuple_arity_fail_label_update(TmpI, NewFail) + end, + NewBB = hipe_bb:code_update(BB, hipe_bb:butlast(BB) ++ [NewI]), + state__bb_add(State, Label, NewBB) + end. + +rewrite_switch_val(State, I, BB, Info, Label) -> + Cases = hipe_icode:switch_val_cases(I), + Var = hipe_icode:switch_val_term(I), + VarType = safe_lookup(Var, Info), + case legal_switch_val_cases(Cases, VarType) of + [] -> + Fail = hipe_icode:switch_val_fail_label(I), + mk_goto(State, BB, Label, Fail); + Cases -> + State; + %% TODO: Find out whether switch_val can fail + %% just as switch_tuple_arity + LegalCases -> + NewI = hipe_icode:switch_val_cases_update(I, LegalCases), + NewBB = hipe_bb:code_update(BB, hipe_bb:butlast(BB) ++ [NewI]), + state__bb_add(State, Label, NewBB) + end. + +rewrite_call(State,I,BB,Info,Label) -> + case call_always_fails(I, Info) of + false -> + Fun = hipe_icode:call_fun(I), + case hipe_icode_primops:fails(Fun) of + false -> + case hipe_icode:call_fail_label(I) of + [] -> State; + _ -> unset_fail(State, BB, Label, I) + end; + true -> State + end; + true -> + case hipe_icode:call_in_guard(I) of + false -> State; + true -> + FailLabel = hipe_icode:call_fail_label(I), + mk_goto(State, BB, Label, FailLabel) + end + end. + +mk_goto(State, BB, Label, Succ) -> + NewI = hipe_icode:mk_goto(Succ), + NewBB = hipe_bb:code_update(BB, hipe_bb:butlast(BB) ++ [NewI]), + state__bb_add(State, Label, NewBB). + +unset_fail(State, BB, Label, I) -> + %%io:format("Setting a guard that cannot fail\n", []), + NewI = hipe_icode:call_set_fail_label(I, []), + NewBB = hipe_bb:code_update(BB, hipe_bb:butlast(BB) ++ [NewI]), + state__bb_add(State, Label, NewBB). + +%% _________________________________________________________________ +%% +%% Make transformations (specialisations) based on the type knowledge. +%% +%% Annotate the variables with the local information. Since we have +%% the code in SSA form and the type information can only depend on +%% assignments or branches (type tests), we can use the information +%% out of the block to annotate all variables in it. +%% + +-spec specialize(cfg()) -> cfg(). + +specialize(Cfg) -> + Labels = hipe_icode_cfg:reverse_postorder(Cfg), + transform_bbs(Labels, Cfg). + +transform_bbs([Label|Left], Cfg) -> + BB = hipe_icode_cfg:bb(Cfg, Label), + Code = hipe_bb:code(BB), + NewCode = make_transformations(Code), + NewBB = hipe_bb:code_update(BB, NewCode), + NewCfg = hipe_icode_cfg:bb_add(Cfg, Label, NewBB), + transform_bbs(Left, NewCfg); +transform_bbs([], Cfg) -> + Cfg. + +make_transformations(Is) -> + lists:flatten([transform_insn(I) || I <- Is]). + +transform_insn(I) -> + case I of + #icode_call{} -> + handle_call_and_enter(I); + #icode_enter{} -> + handle_call_and_enter(I); + #icode_if{} -> + CurrentIfOp = hipe_icode:if_op(I), + UsesFixnums = all_fixnums([get_type(A) || A <- hipe_icode:args(I)]), + AnyImmediate = any_immediate([get_type(A) || A <- hipe_icode:args(I)]), + ExactComp = is_exact_comp(CurrentIfOp), + if UsesFixnums -> + hipe_icode:if_op_update(I, fixnum_ifop(CurrentIfOp)); + AnyImmediate andalso ExactComp -> + hipe_icode:if_op_update(I, fixnum_ifop(CurrentIfOp)); + true -> + I + end; + _ -> + I + end. + +handle_call_and_enter(I) -> + case call_or_enter_fun(I) of + #element{} -> + transform_insn(update_call_or_enter(I, {erlang, element, 2})); + {erlang, element, 2} -> + NewI1 = transform_element2(I), + case is_record(I, icode_call) andalso hipe_icode:call_in_guard(I) of + true -> + case hipe_icode:call_fun(NewI1) of + #unsafe_element{} -> NewI1; + _ -> I + end; + false -> + NewI1 + end; + {erlang, hd, 1} -> transform_hd_or_tl(I, unsafe_hd); + {erlang, tl, 1} -> transform_hd_or_tl(I, unsafe_tl); + {hipe_bs_primop, BsOP} -> + NewBsOp = + bit_opts(BsOP, get_type_list(hipe_icode:args(I))), + update_call_or_enter(I, {hipe_bs_primop, NewBsOp}); + conv_to_float -> + [Src] = hipe_icode:args(I), + case t_is_float(get_type(Src)) of + true -> + update_call_or_enter(I, unsafe_untag_float); + false -> + I + end; + FunName -> + case is_arith_function(FunName) of + true -> + case strength_reduce(I, FunName) of + NewIs when is_list(NewIs) -> + [pos_transform_arith(NewI) || NewI <- NewIs]; + NewI -> + pos_transform_arith(NewI) + end; + false -> + I + end + end. + +pos_transform_arith(I) -> + case hipe_icode:is_enter(I) orelse hipe_icode:is_call(I) of + true -> + FunName = call_or_enter_fun(I), + transform_arith(I, FunName); + false -> + I + end. + +is_arith_function(Name) -> + case Name of + 'band' -> true; + 'bor' -> true; + 'bxor' -> true; + 'bnot' -> true; + 'bsl' -> true; + 'bsr' -> true; + '+' -> true; + '-' -> true; + '*' -> true; + 'div' -> true; + 'rem' -> true; + _ -> false + end. + +%%--------------------------------------------------------------------- +%% Perform a limited form of strength reduction for multiplication and +%% division of an integer with constants which are multiples of 2. +%%--------------------------------------------------------------------- + +strength_reduce(I, Op) -> + case Op of + '*' -> + [Arg1, Arg2] = mult_args_const_second(I), + ArgT1 = get_type(Arg1), + case t_is_integer(ArgT1) of + true -> + case hipe_icode:is_const(Arg2) of + true -> + case hipe_icode:const_value(Arg2) of + 0 -> case call_dstlist(I) of + [] -> remove_useless_arithmetic_instruction(I); + [Dst] -> create_strength_reduce_move(I, Dst, Arg2) + end; + 1 -> case call_dstlist(I) of + [] -> remove_useless_arithmetic_instruction(I); + [Dst] -> create_strength_reduce_move(I, Dst, Arg1) + end; + 2 -> strength_reduce_imult(I, Arg1, 1); + 4 -> strength_reduce_imult(I, Arg1, 2); + 8 -> strength_reduce_imult(I, Arg1, 3); + 16 -> strength_reduce_imult(I, Arg1, 4); + 32 -> strength_reduce_imult(I, Arg1, 5); + 64 -> strength_reduce_imult(I, Arg1, 6); + 128 -> strength_reduce_imult(I, Arg1, 7); + 256 -> strength_reduce_imult(I, Arg1, 8); + ___ -> I + end; + false -> I + end; + false -> I + end; + 'div' -> + [Arg1, Arg2] = hipe_icode:args(I), + ArgT1 = get_type(Arg1), + case t_is_non_neg_integer(ArgT1) of + true -> %% the optimization is NOT valid for negative integers + case hipe_icode:is_const(Arg2) of + true -> + case hipe_icode:const_value(Arg2) of + 0 -> io:fwrite("Integer division by 0 detected!\n"), I; + 1 -> case call_dstlist(I) of + [] -> remove_useless_arithmetic_instruction(I); + [Dst] -> create_strength_reduce_move(I, Dst, Arg1) + end; + 2 -> strength_reduce_div(I, Arg1, 1); + 4 -> strength_reduce_div(I, Arg1, 2); + 8 -> strength_reduce_div(I, Arg1, 3); + 16 -> strength_reduce_div(I, Arg1, 4); + 32 -> strength_reduce_div(I, Arg1, 5); + 64 -> strength_reduce_div(I, Arg1, 6); + 128 -> strength_reduce_div(I, Arg1, 7); + 256 -> strength_reduce_div(I, Arg1, 8); + ___ -> I + end; + false -> I + end; + false -> I + end; + 'rem' -> + [Arg1, Arg2] = hipe_icode:args(I), + ArgT1 = get_type(Arg1), + case t_is_non_neg_integer(ArgT1) of + true -> %% the optimization is NOT valid for negative integers + case hipe_icode:is_const(Arg2) of + true -> + case hipe_icode:const_value(Arg2) of + 0 -> io:fwrite("Remainder with 0 detected!\n"), I; + 1 -> case call_dstlist(I) of + [] -> remove_useless_arithmetic_instruction(I); + [Dst] -> create_strength_reduce_move( + I, Dst, hipe_icode:mk_const(0)) + end; + 2 -> strength_reduce_rem(I, Arg1, 1); + 4 -> strength_reduce_rem(I, Arg1, 3); + 8 -> strength_reduce_rem(I, Arg1, 7); + 16 -> strength_reduce_rem(I, Arg1, 15); + 32 -> strength_reduce_rem(I, Arg1, 31); + 64 -> strength_reduce_rem(I, Arg1, 63); + 128 -> strength_reduce_rem(I, Arg1, 127); + 256 -> strength_reduce_rem(I, Arg1, 255); + ___ -> I + end; + false -> I + end; + false -> I + end; + _ -> I + end. + +remove_useless_arithmetic_instruction(_) -> + []. + +create_strength_reduce_move(I, Dst, Val) -> + case hipe_icode:call_continuation(I) of + [] -> + hipe_icode:mk_move(Dst, Val); + Lbl -> + [hipe_icode:mk_move(Dst, Val), + hipe_icode:mk_goto(Lbl)] + end. + +%% Puts the args of a multiplication in a form where the constant +%% (if present) is always the second argument. +mult_args_const_second(I) -> + [Arg1, Arg2] = Args = hipe_icode:args(I), + case hipe_icode:is_const(Arg1) of + true -> [Arg2, Arg1]; + false -> Args + end. + +%% In all three functions below: +%% - Arg1 is a variable of integer type +%% - N is a small positive integer that will be used in a bit shift operation +strength_reduce_imult(I, Arg1, N) -> + case t_number_vals(get_type(Arg1)) of + [X] when is_integer(X) -> + %% io:format("Multiplication with constant arguments:\n ~w\n", [I]), + case call_dstlist(I) of + [] -> remove_useless_arithmetic_instruction(I); + [D] -> create_strength_reduce_move(I, D, hipe_icode:mk_const(X bsl N)) + end; + _ -> + update_call_or_enter(I, 'bsl', [Arg1, hipe_icode:mk_const(N)]) + end. + +strength_reduce_div(I, Arg1, N) -> + case t_number_vals(get_type(Arg1)) of + [X] when is_integer(X) -> + %% io:format("Division with constant arguments:\n ~w\n", [I]), + case call_dstlist(I) of + [] -> remove_useless_arithmetic_instruction(I); + [D] -> create_strength_reduce_move(I, D, hipe_icode:mk_const(X bsr N)) + end; + _ -> + update_call_or_enter(I, 'bsr', [Arg1, hipe_icode:mk_const(N)]) + end. + +strength_reduce_rem(I, Arg1, N) -> + case t_number_vals(get_type(Arg1)) of + [X] when is_integer(X) -> + %% io:format("Remainder with constant arguments:\n ~w\n", [I]), + case call_dstlist(I) of + [] -> remove_useless_arithmetic_instruction(I); + [D] -> create_strength_reduce_move(I, D, hipe_icode:mk_const(X band N)) + end; + _ -> + update_call_or_enter(I, 'band', [Arg1, hipe_icode:mk_const(N)]) + end. + +%%--------------------------------------------------------------------- + +call_or_enter_fun(I) -> + case hipe_icode:is_call(I) of + true -> hipe_icode:call_fun(I); + false -> hipe_icode:enter_fun(I) + end. + +update_call_or_enter(I, NewFun) -> + case hipe_icode:is_call(I) of + true -> + case hipe_icode_primops:fails(NewFun) of + false -> + NewI = hipe_icode:call_fun_update(I, NewFun), + hipe_icode:call_set_fail_label(NewI, []); + true -> + hipe_icode:call_fun_update(I, NewFun) + end; + false -> hipe_icode:enter_fun_update(I, NewFun) + end. + +update_call_or_enter(I, NewFun, NewArgs) -> + case hipe_icode:is_call(I) of + true -> + I1 = hipe_icode:call_args_update(I, NewArgs), + hipe_icode:call_fun_update(I1, NewFun); + false -> + I1 = hipe_icode:enter_args_update(I, NewArgs), + hipe_icode:enter_fun_update(I1, NewFun) + end. + +transform_element2(I) -> + [Index, Tuple] = hipe_icode:args(I), + IndexType = get_type(Index), + TupleType = get_type(Tuple), + ?debug("Tuple", TupleType), + NewIndex = + case test_type(integer, IndexType) of + true -> + case t_number_vals(IndexType) of + unknown -> unknown; + [_|_] = Vals -> {number, Vals} + end; + _ -> unknown + end, + MinSize = + case test_type(tuple, TupleType) of + true -> + ?debug("is tuple", TupleType), + case t_tuple_sizes(TupleType) of + unknown -> unknown; + Sizes -> {tuple, lists:min(Sizes)} + end; + _ -> unknown + end, + case {NewIndex, MinSize} of + {{number, [_|_] = Ns}, {tuple, A}} when is_integer(A) -> + case lists:all(fun(X) -> 0 < X andalso X =< A end, Ns) of + true -> + case Ns of + [Idx] -> + [_, Tuple] = hipe_icode:args(I), + update_call_or_enter(I, #unsafe_element{index = Idx}, [Tuple]); + [_|_] -> + NewFun = {element, [MinSize, valid]}, + update_call_or_enter(I, NewFun) + end; + false -> + case lists:all(fun(X) -> hipe_tagscheme:is_fixnum(X) end, Ns) of + true -> + NewFun = {element, [MinSize, fixnums]}, + update_call_or_enter(I, NewFun); + false -> + NewFun = {element, [MinSize, unknown]}, + update_call_or_enter(I, NewFun) + end + end; + _ when (NewIndex =:= unknown) orelse (MinSize =:= unknown) -> + case t_is_fixnum(IndexType) of + true -> + NewFun = {element, [MinSize, fixnums]}, + update_call_or_enter(I, NewFun); + false -> + NewFun = {element, [MinSize, NewIndex]}, + update_call_or_enter(I, NewFun) + end + end. + +transform_hd_or_tl(I, Primop) -> + [Arg] = hipe_icode:args(I), + case t_is_cons(get_type(Arg)) of + true -> update_call_or_enter(I, Primop); + false -> I + end. + +transform_arith(I, Op) -> + ArgTypes = get_type_list(hipe_icode:args(I)), + %% io:format("Op = ~w, Args = ~w\n", [Op, ArgTypes]), + DstTypes = + case hipe_icode:is_call(I) of + true -> get_type_list(call_dstlist(I)); + false -> [erl_bif_types:type(erlang, Op, length(ArgTypes), ArgTypes)] + end, + case valid_unsafe_args(ArgTypes, Op) of + true -> + case all_is_fixnum(DstTypes) of + true -> + update_call_or_enter(I, arithop_to_extra_unsafe(Op)); + false -> + update_call_or_enter(I, arithop_to_unsafe(Op)) + end; + false -> + I + end. + +all_is_fixnum(Types) -> + lists:all(fun erl_types:t_is_fixnum/1, Types). + +valid_unsafe_args(Args, Op) -> + if Op =:= 'bnot' -> + [Arg] = Args, + t_is_fixnum(Arg); + true -> + [LeftArg, RightArg] = Args, + case Op of + 'bsl' -> t_is_fixnum(LeftArg) and t_is_bitwidth(RightArg); + 'bsr' -> t_is_fixnum(LeftArg) and t_is_bitwidth(RightArg); + _ -> t_is_fixnum(LeftArg) and t_is_fixnum(RightArg) + end + end. + +arithop_to_extra_unsafe(Op) -> + case Op of + '+' -> extra_unsafe_add; + '-' -> extra_unsafe_sub; + '*' -> '*'; %% XXX: Revise? + 'div' -> 'div'; %% XXX: Revise? + 'rem' -> 'rem'; %% XXX: Revise? + 'band' -> unsafe_band; + 'bor' -> unsafe_bor; + 'bxor' -> unsafe_bxor; + 'bnot' -> unsafe_bnot; + 'bsl' -> unsafe_bsl; + 'bsr' -> unsafe_bsr + end. + +arithop_to_unsafe(Op) -> + case Op of + '+' -> unsafe_add; + '-' -> unsafe_sub; + _ -> Op + end. + +fixnum_ifop(Op) -> + case Op of + '=:=' -> 'fixnum_eq'; + '=/=' -> 'fixnum_neq'; + '==' -> 'fixnum_eq'; + '/=' -> 'fixnum_neq'; + '>' -> 'fixnum_gt'; + '<' -> 'fixnum_lt'; + '>=' -> 'fixnum_ge'; + '=<' -> 'fixnum_le'; + Op -> Op + end. + +bit_opts({Name, Size, Flags} = I, [MSType]) when Name =:= bs_get_integer; + Name =:= bs_get_float; + Name =:= bs_get_binary -> + Bits = t_matchstate_present(MSType), + case t_is_bitstr(Bits) of + true -> + Base = t_bitstr_base(Bits), + if Base >= Size -> + {Name, Size, Flags bor 16}; + true -> I + end; + false -> I + end; +bit_opts({bs_get_binary_all, Size, Flags} = I, [MSType]) -> + Bits = t_matchstate_present(MSType), + case t_is_bitstr(Bits) of + true -> + Base = t_bitstr_base(Bits), + Unit = t_bitstr_unit(Bits), + if (Base rem Size) =:= 0 andalso (Unit rem Size) =:= 0 -> + {bs_get_binary_all, Size, Flags bor 16}; + true -> I + end; + false -> I + end; +bit_opts({bs_test_unit, Size} = I, [MSType]) -> + Bits = t_matchstate_present(MSType), + case t_is_bitstr(Bits) of + true -> + Base = t_bitstr_base(Bits), + Unit = t_bitstr_unit(Bits), + if (Base rem Size) =:= 0 andalso (Unit rem Size) =:= 0 -> + {bs_test_unit, 1}; + true -> I + end; + false -> I + end; +bit_opts({bs_put_integer, Size, Flags, ConstInfo} = I, [Src|_]) -> + case t_is_fixnum(Src) of + true -> + {unsafe_bs_put_integer, Size, Flags, ConstInfo}; + false -> I + end; +bit_opts({bs_start_match, Max} = I, [Src]) -> + case t_is_bitstr(Src) of + true -> {{bs_start_match, bitstr}, Max}; + false -> + MSorNone = t_inf(t_matchstate(), Src), + case t_is_matchstate(MSorNone) of + true -> + Slots = t_matchstate_slots(MSorNone), + case t_is_any(Slots) orelse (length(t_to_tlist(Slots)) =< Max) of + true -> I; + false -> {{bs_start_match, ok_matchstate}, Max} + end; + false -> I + end + end; +bit_opts(I, _) -> I. + +is_exact_comp(Op) -> + case Op of + '=:=' -> true; + '=/=' -> true; + _Op -> false + end. + +all_fixnums([Type|Types]) -> + t_is_fixnum(Type) andalso all_fixnums(Types); +all_fixnums([]) -> + true. + +any_immediate([Type|Types]) -> + t_is_fixnum(Type) orelse t_is_atom(Type) orelse any_immediate(Types); +any_immediate([]) -> false. + +get_standard_primop(unsafe_bsl) -> 'bsl'; +get_standard_primop(unsafe_bsr) -> 'bsr'; +get_standard_primop(unsafe_add) -> '+'; +get_standard_primop(extra_unsafe_add) -> '+'; +get_standard_primop(unsafe_bnot) -> 'bnot'; +get_standard_primop(unsafe_bxor) -> 'bxor'; +get_standard_primop(unsafe_band) -> 'band'; +get_standard_primop(unsafe_bor) -> 'bor'; +get_standard_primop(unsafe_sub) -> '-'; +get_standard_primop(extra_unsafe_sub) -> '-'; +get_standard_primop(Op) -> Op. + +primop_type(Op, Args) -> + case Op of + #mkfun{mfa = MFA} -> + t_inf(t_fun(), find_signature_mfa(MFA)); + _ -> + None = t_none(), + Primop = get_standard_primop(Op), + RetType = hipe_icode_primops:type(Primop, Args), + case RetType of + None -> + hipe_icode_primops:type(Primop, add_funs_to_arg_types(Args)); + Other -> + Other + end + end. + +%%------------------------------------------------------------------ +%% Various help functions. +%%------------------------------------------------------------------ + +add_arg_types(Args, Types) -> + add_arg_types(Args, Types, gb_trees:empty()). + +add_arg_types([Arg|Args], [Type|Types], Acc) -> + Type1 = + case t_is_none(Type) of + true -> t_any(); + false -> Type + end, + add_arg_types(Args,Types, enter(Arg, Type1, Acc)); +add_arg_types(_, [], Acc) -> + Acc. + +get_type_list(ArgList) -> + [get_type(Arg) || Arg <- ArgList]. + +get_type(Arg) -> + case hipe_icode:is_annotated_variable(Arg) of + true -> + None = t_none(), + case hipe_icode:variable_annotation(Arg) of + {type_anno, None, _} -> t_any(); + {type_anno, Type, _} -> Type + end; + false -> + case hipe_icode:is_const(Arg) of + true -> const_type(Arg); + false -> t_any() + end + end. + +%% Lookup treats anything that is neither in the map or a constant as +%% t_none(). Use this during type propagation! + +lookup(Var, Tree) -> + case gb_trees:lookup(Var, Tree) of + none -> + case hipe_icode:is_const(Var) of + true -> const_type(Var); + false -> t_none() + end; + {value, Type} -> + Type + end. + +lookup_list(List, Info) -> + lookup_list0(List, Info, []). + +lookup_list0([H|T], Info, Acc) -> + lookup_list0(T, Info, [lookup(H, Info)|Acc]); +lookup_list0([], _, Acc) -> + lists:reverse(Acc). + + +%% safe_lookup treats anything that is neither in the map nor a +%% constant as t_any(). Use this during transformations. + +safe_lookup(Var, Tree) -> + case gb_trees:lookup(Var, Tree) of + none -> + case hipe_icode:is_const(Var) of + true -> const_type(Var); + false -> + %% io:format("Expression has undefined type\n",[]), + t_any() + end; + {value, Type} -> + Type + end. + +safe_lookup_list(List, Info) -> + safe_lookup_list0(List, Info, []). + +safe_lookup_list0([H|T], Info, Acc) -> + safe_lookup_list0(T, Info, [safe_lookup(H, Info)|Acc]); +safe_lookup_list0([], _, Acc) -> + lists:reverse(Acc). + +enter_list([Var|VarLeft], [Type|TypeLeft], Info) -> + NewInfo = enter(Var, Type, Info), + enter_list(VarLeft, TypeLeft, NewInfo); +enter_list([], [], Info) -> + Info. + +enter([Key], Value, Tree) -> + enter(Key, Value, Tree); +enter(Key, Value, Tree) -> + case is_var_or_reg(Key) of + true -> + case t_is_none(Value) of + true -> + gb_trees:delete_any(Key, Tree); + false -> + gb_trees:enter(Key, Value, Tree) + end; + false -> + Tree + end. + +join_list(List, Info) -> + join_list(List, Info, t_none()). + +join_list([H|T], Info, Acc) -> + Type = t_sup(lookup(H, Info), Acc), + join_list(T, Info, Type); +join_list([], _, Acc) -> + Acc. + +join_info_in([], _OldInfo, _NewInfo) -> + %% No variables are live in. The information must be at a fixpoint. + fixpoint; +join_info_in(Vars, OldInfo, NewInfo) -> + NewInfo2 = join_info_in(Vars, Vars, OldInfo, NewInfo, gb_trees:empty()), + case info_is_equal(NewInfo2, OldInfo) of + true -> fixpoint; + false -> NewInfo2 + end. + +%% NOTE: Variables can be bound to other variables. Joining these is +%% only possible if the binding is the same from both traces and this +%% variable is still live. + +join_info_in([Var|Left], LiveIn, Info1, Info2, Acc) -> + Type1 = gb_trees:lookup(Var, Info1), + Type2 = gb_trees:lookup(Var, Info2), + case {Type1, Type2} of + {none, none} -> + join_info_in(Left, LiveIn, Info1, Info2, Acc); + {none, {value, Val}} -> + NewTree = gb_trees:insert(Var, Val, Acc), + join_info_in(Left, LiveIn, Info1, Info2, NewTree); + {{value, Val}, none} -> + NewTree = gb_trees:insert(Var, Val, Acc), + join_info_in(Left, LiveIn, Info1, Info2, NewTree); + {{value, Val1}, {value, Val2}} -> + NewTree = gb_trees:insert(Var, t_sup(Val1, Val2), Acc), + join_info_in(Left, LiveIn, Info1, Info2, NewTree) + end; +join_info_in([], _LiveIn, _Info1, _Info2, Acc) -> + Acc. + +info_is_equal(Info1, Info2) -> + compare(gb_trees:to_list(Info1), gb_trees:to_list(Info2)). + +compare([{Var, Type1}|Left1], [{Var, Type2}|Left2]) -> + t_is_equal(Type1, Type2) andalso compare(Left1, Left2); +compare([], []) -> + true; +compare(_, _) -> + false. + +const_type(Const) -> + t_from_term(hipe_icode:const_value(Const)). + +do_updates(State, List) -> + do_updates(State, List, []). + +do_updates(State, [{Label, Info}|Tail], Worklist) -> + case state__info_in_update(State, Label, Info) of + fixpoint -> + %% io:format("Info in for ~w is: fixpoint\n", [Label]), + do_updates(State, Tail, Worklist); + NewState -> + %% io:format("Info in for ~w is:\n", [Label]), + %% [io:format("~w: ~p\n", [X, format_type(Y)]) + %% || {X, Y} <- gb_trees:to_list(state__info_in(NewState, Label))], + do_updates(NewState, Tail, [Label|Worklist]) + end; +do_updates(State, [], Worklist) -> + {State, Worklist}. + +enter_defines(I, Type, Info) -> + case defines(I) of + [] -> Info; + [Def] -> + enter(Def, Type, Info); + Defs -> + Pairs = case t_is_any(Type) of + true -> + [{Def, t_any()} || Def <- Defs]; + false -> + case t_is_none(Type) of + true -> + [{Def, t_none()} || Def <- Defs]; + false -> + lists:zip(Defs, t_to_tlist(Type)) + end + end, + lists:foldl(fun({X, T}, Inf) -> enter(X, T, Inf) end, Info, Pairs) + end. + +defines(I) -> + keep_vars_and_regs(hipe_icode:defines(I)). + +call_dstlist(I) -> + hipe_icode:call_dstlist(I). + +uses(I) -> + keep_vars_and_regs(hipe_icode:uses(I)). + +keep_vars_and_regs(Vars) -> + [V || V <- Vars, is_var_or_reg(V)]. + +butlast([_]) -> + []; +butlast([H|T]) -> + [H|butlast(T)]. + +-spec any_is_none([erl_types:erl_type()]) -> boolean(). + +any_is_none(Types) -> + lists:any(fun (T) -> t_is_none(T) end, Types). + +is_var_or_reg(X) -> + hipe_icode:is_var(X) orelse hipe_icode:is_reg(X). + +%% _________________________________________________________________ +%% +%% Handling the state +%% + +new_state(Cfg, {MFA, GetCallFun, GetResFun, FinalAction}) -> + Start = hipe_icode_cfg:start_label(Cfg), + Params = hipe_icode_cfg:params(Cfg), + ParamTypes = GetCallFun(MFA, Cfg), + case any_is_none(ParamTypes) of + true -> + FinalAction(MFA, [t_none()]), + throw(no_input); + false -> + Info = add_arg_types(Params, ParamTypes), + InfoMap = gb_trees:insert({Start, in}, Info, gb_trees:empty()), + Liveness = hipe_icode_ssa:ssa_liveness__analyze(Cfg), + #state{info_map = InfoMap, cfg = Cfg, liveness = Liveness, + arg_types = ParamTypes, lookupfun = GetResFun, + resultaction = FinalAction} + end. + +state__cfg(#state{cfg = Cfg}) -> + Cfg. + +state__succ(#state{cfg = Cfg}, Label) -> + hipe_icode_cfg:succ(Cfg, Label). + +state__bb(#state{cfg = Cfg}, Label) -> + hipe_icode_cfg:bb(Cfg, Label). + +state__bb_add(S = #state{cfg = Cfg}, Label, BB) -> + NewCfg = hipe_icode_cfg:bb_add(Cfg, Label, BB), + S#state{cfg=NewCfg}. + +state__params_update(S = #state{cfg = Cfg}, NewParams) -> + NewCfg = hipe_icode_cfg:params_update(Cfg, NewParams), + S#state{cfg = NewCfg}. + +state__ret_type(#state{ret_type = RT}) -> RT. + +state__lookupfun(#state{lookupfun = LF}) -> LF. + +state__resultaction(#state{resultaction = RA}) -> RA. + +state__info_in(S, Label) -> + state__info(S, {Label, in}). + +state__info_out(S, Label) -> + state__info(S, {Label, out}). + +state__info(#state{info_map = IM}, Label) -> + case gb_trees:lookup(Label, IM) of + {value, Info} -> Info; + none -> gb_trees:empty() + end. + +state__ret_type_update(#state{ret_type = RT} = State, NewType) when + is_list(NewType) -> + TotType = lists:zipwith(fun erl_types:t_sup/2, RT, NewType), + State#state{ret_type = TotType}; +state__ret_type_update(#state{ret_type = RT} = State, NewType) -> + state__ret_type_update(State, [NewType || _ <- RT]). + +state__info_in_update(S=#state{info_map=IM, liveness=Liveness}, Label, Info) -> + LiveIn = hipe_icode_ssa:ssa_liveness__livein(Liveness, Label), + LabelIn = {Label, in}, + case gb_trees:lookup(LabelIn, IM) of + none -> + OldInfo = gb_trees:empty(), + case join_info_in(LiveIn, OldInfo, Info) of + fixpoint -> + %% If the BB has not been handled we ignore the fixpoint. + S#state{info_map = gb_trees:enter(LabelIn, OldInfo, IM)}; + NewInfo -> + S#state{info_map = gb_trees:enter(LabelIn, NewInfo, IM)} + end; + {value, OldInfo} -> + case join_info_in(LiveIn, OldInfo, Info) of + fixpoint -> + fixpoint; + NewInfo -> + S#state{info_map = gb_trees:enter(LabelIn, NewInfo, IM)} + end + end. + +state__info_out_update(#state{info_map = IM} = State, Label, Info) -> + State#state{info_map = gb_trees:enter({Label, out}, Info, IM)}. + +%% _________________________________________________________________ +%% +%% The worklist. +%% + +init_work(State) -> + %% Labels = hipe_icode_cfg:reverse_postorder(state__cfg(State)), + Labels = [hipe_icode_cfg:start_label(state__cfg(State))], + {Labels, [], gb_sets:from_list(Labels)}. + +get_work({[Label|Left], List, Set}) -> + NewWork = {Left, List, gb_sets:delete(Label, Set)}, + {Label, NewWork}; +get_work({[], [], _Set}) -> + fixpoint; +get_work({[], List, Set}) -> + get_work({lists:reverse(List), [], Set}). + +add_work(Work = {List1, List2, Set}, [Label|Left]) -> + case gb_sets:is_member(Label, Set) of + true -> + add_work(Work, Left); + false -> + %% io:format("Adding work: ~w\n", [Label]), + add_work({List1, [Label|List2], gb_sets:insert(Label, Set)}, Left) + end; +add_work(Work, []) -> + Work. + +%% _________________________________________________________________ +%% +%% Annotator +%% + +annotate_cfg(State) -> + Cfg = state__cfg(State), + NewState = annotate_params(hipe_icode_cfg:params(Cfg), State, + hipe_icode_cfg:start_label(Cfg)), + Labels = hipe_icode_cfg:reverse_postorder(Cfg), + annotate_bbs(Labels, NewState). + +annotate_params(Params, State, Start) -> + Info = state__info_in(State, Start), + AnnoFun = fun hipe_icode:annotate_variable/2, + NewParams = + lists:zipwith(AnnoFun, Params, [make_annotation(P,Info) || P <- Params]), + state__params_update(State,NewParams). + +annotate_bbs([Label|Left], State) -> + BB = state__bb(State, Label), + Code = hipe_bb:code(BB), + Info = state__info_in(State, Label), + NewCode = annotate_instr_list(Code, Info, state__lookupfun(State), []), + NewBB = hipe_bb:code_update(BB, NewCode), + NewState = state__bb_add(State, Label, NewBB), + annotate_bbs(Left, NewState); +annotate_bbs([], State) -> + State. + +annotate_instr_list([I], Info, LookupFun, Acc) -> + NewInfo = + case I of + #icode_call{} -> + do_safe_call(I, Info, LookupFun); + _ -> + analyse_insn(I, Info, LookupFun) + end, + NewI = annotate_instr(I, NewInfo, Info), + lists:reverse([NewI|Acc]); +annotate_instr_list([I|Left], Info, LookupFun, Acc) -> + NewInfo = + case I of + #icode_call{} -> + do_safe_call(I, Info, LookupFun); + _ -> + analyse_insn(I, Info, LookupFun) + end, + NewI = annotate_instr(I, NewInfo, Info), + annotate_instr_list(Left, NewInfo, LookupFun, [NewI|Acc]). + +annotate_instr(I, DefInfo, UseInfo) -> + Def = defines(I), + Use = uses(I), + Fun = fun hipe_icode:annotate_variable/2, + DefSubst = [{X, Fun(X, make_annotation(X, DefInfo))} || X <- Def], + UseSubst = [{X, Fun(X, make_annotation(X, UseInfo))} || X <- Use], + case DefSubst ++ UseSubst of + [] -> + I; + Subst -> + hipe_icode:subst(Subst, I) + end. + +make_annotation(X, Info) -> + {type_anno, safe_lookup(X, Info), fun erl_types:t_to_string/1}. + +-spec unannotate_cfg(cfg()) -> cfg(). + +unannotate_cfg(Cfg) -> + NewCfg = unannotate_params(Cfg), + Labels = hipe_icode_cfg:labels(NewCfg), + unannotate_bbs(Labels, NewCfg). + +unannotate_params(Cfg) -> + Params = hipe_icode_cfg:params(Cfg), + NewParams = [hipe_icode:unannotate_variable(X) + || X <- Params, hipe_icode:is_variable(X)], + hipe_icode_cfg:params_update(Cfg, NewParams). + +unannotate_bbs([Label|Left], Cfg) -> + BB = hipe_icode_cfg:bb(Cfg, Label), + Code = hipe_bb:code(BB), + NewCode = unannotate_instr_list(Code, []), + NewBB = hipe_bb:code_update(BB, NewCode), + NewCfg = hipe_icode_cfg:bb_add(Cfg, Label, NewBB), + unannotate_bbs(Left, NewCfg); +unannotate_bbs([], Cfg) -> + Cfg. + +unannotate_instr_list([I|Left], Acc) -> + NewI = unannotate_instr(I), + unannotate_instr_list(Left, [NewI|Acc]); +unannotate_instr_list([], Acc) -> + lists:reverse(Acc). + +unannotate_instr(I) -> + DefUses = hipe_icode:defines(I) ++ hipe_icode:uses(I), + Subst = [{X, hipe_icode:unannotate_variable(X)} || X <- DefUses, + hipe_icode:is_variable(X)], + if Subst =:= [] -> I; + true -> hipe_icode:subst(Subst, I) + end. + +%% _________________________________________________________________ +%% +%% Find the types of the arguments to a call +%% + +update_call_arguments(I, Info) -> + Args = hipe_icode:call_args(I), + ArgTypes = lookup_list(Args, Info), + Signature = find_signature(hipe_icode:call_fun(I), length(Args)), + case t_fun_args(Signature) of + unknown -> + Info; + PltArgTypes -> + NewArgTypes = t_inf_lists(ArgTypes, PltArgTypes), + enter_list(Args, NewArgTypes, Info) + end. + +%% _________________________________________________________________ +%% +%% PLT info +%% + +find_signature(MFA = {_, _, _}, _) -> find_signature_mfa(MFA); +find_signature(Primop, Arity) -> find_signature_primop(Primop, Arity). + +find_signature_mfa(MFA) -> + case get_mfa_arg_types(MFA) of + any -> + t_fun(get_mfa_type(MFA)); + BifArgs -> + t_fun(BifArgs, get_mfa_type(MFA)) + end. + +find_signature_primop(Primop, Arity) -> + case get_primop_arg_types(Primop) of + any -> + t_fun(Arity, get_primop_type(Primop)); + ArgTypes -> + t_fun(ArgTypes, get_primop_type(Primop)) + end. + +get_primop_arg_types(Primop) -> + case hipe_icode_primops:arg_types(Primop) of + unknown -> any; + ArgTypes -> add_tuple_to_args(ArgTypes) + end. + +get_mfa_arg_types({M, F, A}) -> + case erl_bif_types:arg_types(M, F, A) of + unknown -> + any; + BifArgs -> + add_tuple_to_args(BifArgs) + end. + +get_mfa_type({M, F, A}) -> + erl_bif_types:type(M, F, A). + +get_primop_type(Primop) -> + hipe_icode_primops:type(get_standard_primop(Primop)). + +add_tuple_to_args(Types) -> + [add_tuple_to_type(T) || T <- Types]. + +add_tuple_to_type(T) -> + None = t_none(), + case t_inf(t_fun(), T) of + None -> T; + _Other -> t_sup(T, t_tuple([t_atom(),t_atom()])) + end. + +add_funs_to_arg_types(Types) -> + [add_fun_to_arg_type(T) || T <- Types]. + +add_fun_to_arg_type(T) -> + None = t_none(), + case t_inf(t_tuple([t_atom(),t_atom()]), T) of + None -> T; + _Other -> t_sup(T, t_fun()) + end. + +%%===================================================================== +%% Icode Coordinator Callbacks +%%===================================================================== + +-spec replace_nones([erl_types:erl_type()] | erl_types:erl_type()) -> + [erl_types:erl_type()]. + +replace_nones(Types) when is_list(Types) -> + [replace_none(T) || T <- Types]; +replace_nones(Type) -> + [replace_none(Type)]. + +-spec replace_none(erl_types:erl_type()) -> erl_types:erl_type(). + +replace_none(Type) -> + case erl_types:t_is_none(Type) of + true -> + erl_types:t_any(); + false -> + Type + end. + +-spec update__info([erl_types:erl_type()], [erl_types:erl_type()]) -> + {boolean(), [erl_types:erl_type()]}. + +update__info(NewTypes, OldTypes) -> + SupFun = + fun(T1, T2) -> erl_types:t_limit(erl_types:t_sup(T1,T2), ?TYPE_DEPTH) end, + EqFun = fun erl_types:t_is_equal/2, + ResTypes = lists:zipwith(SupFun, NewTypes, OldTypes), + Change = lists:zipwith(EqFun, ResTypes, OldTypes), + {lists:all(fun(X) -> X end, Change), ResTypes}. + +-spec new__info([erl_types:erl_type()]) -> [erl_types:erl_type()]. + +new__info(NewTypes) -> + [erl_types:t_limit(T, ?TYPE_DEPTH) || T <- NewTypes]. + +-spec return__info(erl_types:erl_type()) -> erl_types:erl_type(). + +return__info(Types) -> + Types. + +-spec return_none() -> [erl_types:erl_type(),...]. + +return_none() -> + [erl_types:t_none()]. + +-spec return_none_args(cfg(), mfa()) -> [erl_types:erl_type()]. + +return_none_args(Cfg, {_M,_F,A}) -> + NoArgs = + case hipe_icode_cfg:is_closure(Cfg) of + true -> hipe_icode_cfg:closure_arity(Cfg) - 1; + false -> A + end, + lists:duplicate(NoArgs, erl_types:t_none()). + +-spec return_any_args(cfg(), mfa()) -> [erl_types:erl_type()]. + +return_any_args(Cfg, {_M,_F,A}) -> + NoArgs = + case hipe_icode_cfg:is_closure(Cfg) of + true -> hipe_icode_cfg:closure_arity(Cfg); + false -> A + end, + lists:duplicate(NoArgs, erl_types:t_any()). + +%%===================================================================== +%% Testing function below +%%===================================================================== + +-ifdef(DO_HIPE_ICODE_TYPE_TEST). + +test() -> + Range1 = t_from_range(1, pos_inf), + Range2 = t_from_range(0, 5), + Var1 = hipe_icode:mk_var(1), + Var2 = hipe_icode:mk_var(2), + + Info = enter(Var1, Range1, enter(Var2, Range2, gb_trees:empty())), + io:format("A1 ~p~n", [Info]), + A = integer_range_inequality_propagation('<', Var1, Var2, 1, 2, Info), + B = integer_range_inequality_propagation('>=', Var1, Var2, 1, 2, Info), + C = integer_range_inequality_propagation('=<', Var1, Var2, 1, 2, Info), + D = integer_range_inequality_propagation('>', Var1, Var2, 1, 2, Info), + + io:format("< ~p~n", [A]), + io:format(">= ~p~n", [B]), + io:format("<= ~p~n", [C]), + io:format("> ~p~n", [D]). + +-endif. diff --git a/lib/hipe/icode/hipe_icode_type.hrl b/lib/hipe/icode/hipe_icode_type.hrl new file mode 100644 index 0000000000..dd69c1e8b2 --- /dev/null +++ b/lib/hipe/icode/hipe_icode_type.hrl @@ -0,0 +1,25 @@ +%%% +%%% %CopyrightBegin% +%%% +%%% Copyright Ericsson AB 2004-2009. All Rights Reserved. +%%% +%%% The contents of this file are subject to the Erlang Public License, +%%% Version 1.1, (the "License"); you may not use this file except in +%%% compliance with the License. You should have received a copy of the +%%% Erlang Public License along with this software. If not, it can be +%%% retrieved online at http://www.erlang.org/. +%%% +%%% Software distributed under the License is distributed on an "AS IS" +%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%%% the License for the specific language governing rights and limitations +%%% under the License. +%%% +%%% %CopyrightEnd% +%%% +%%%------------------------------------------------------------------- +%%% File : hipe_icode_type.hrl +%%% Author : Tobias Lindahl <[email protected]> +%%% Created : 2 Sep 2004 by Tobias Lindahl <[email protected]> +%%%------------------------------------------------------------------- + +-define(TYPE_DEPTH, 3). |