<commit_before>%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%%--------------------------------------------------------------------
%%% File    : hipe_icode_type.erl
%%% Author  : <NAME> <<EMAIL>>
%%% Description : Propagate type information.
%%%
%%% Created : 25 Feb 2003 by <NAME> <<EMAIL>>
%%%--------------------------------------------------------------------

-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, [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_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_map/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_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_map/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_trees:tree(),
		cfg                          :: cfg(),
		liveness  = gb_trees:empty() :: gb_trees: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<commit_msg>hipe: Add halt/2 to functions that always fail<commit_after>%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%%--------------------------------------------------------------------
%%% File    : hipe_icode_type.erl
%%% Author  : <NAME> <<EMAIL>>
%%% Description : Propagate type information.
%%%
%%% Created : 25 Feb 2003 by <NAME> <<EMAIL>>
%%%--------------------------------------------------------------------

-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, [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_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_map/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_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_map/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_trees:tree(),
		cfg                          :: cfg(),
		liveness  = gb_trees:empty() :: gb_trees: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, halt, 2} -> 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
{'fraction_non_alphanumeric': 0.20426137456251683, 'fraction_numerical': 0.00892273374100996, 'mean_word_length': 3.2999834628741525, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 218, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6940537', 'n_tokens_mistral': 9952, 'n_tokens_neox': 9632, 'n_words': 1980}