%% -*- erlang-indent-level: 2 -*-
%%
%% 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.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% ============================================================================
%%  Filename :  hipe_rtl_ssa_const_prop.erl
%%  Authors  :  Bjorn Bergman, Bjarni Juliusson
%%  Purpose  :  Perform sparse conditional constant propagation on RTL.
%%  Notes    :  Works on an SSA-converted control-flow graph.
%%
%%  History  :  * 2004-03-14: Blatantly stolen from Icode (code by
%%                  Daniel Luna and Erik Andersson) and query-replaced for RTL.
%%              * 2004-04-30: Added in the repository.
%% ============================================================================
%%
%% Exports: propagate/1.
%%
%% ============================================================================
%%
%% Some things to note:
%%
%% 1. All precoloured registers are assumed to contain bottom. We can not
%%    do anything with them since they are not in SSA-form. This might be
%%    possible to resolve in some way, but we decided to not go there.
%%
%% 2. const_labels are assumed to be bottom, we can not find the address
%%    in any nice way (that I know of, maybe someone can help ?). I
%%    suppose they don't get a value until linking (or some step that
%%    resembles it). They are only affecting bignums and floats (at least
%%    as far as I can tell), which are both stored in memory and hence
%%    not handled very well by us anyway.
%%
%% 3. can v <- Constant be removed ? I think so. all uses of v will be
%%    replaced with an immediate. So why not ?
%%
%% ============================================================================
%%
%% TODO: 
%%
%% Take care of failures in call and replace operation with apropriate
%% failure.
%%
%% Handle ifs with non-binary operators
%%
%% We want multisets for easier (and faster) creation of env->ssa_edges
%% 
%% Propagation of constant arguments when some of the arguments are bottom
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


-module(hipe_rtl_ssa_const_prop).
-export([propagate/1]).

-include("../main/hipe.hrl").
-include("hipe_rtl.hrl").
-include("../flow/cfg.hrl").

%-define(DEBUG, true).

-ifdef(DEBUG).
-define(SCCPDBG(W), W).
-define(DEBUG_TST, true).  % make sure that we can use ?DEBUG in if-cases...
-else.
-define(DEBUG_TST, false).  % make sure that we can use ?DEBUG in if-cases...
-define(SCCPDBG(W), ok).
-endif.

%%-----------------------------------------------------------------------------
%% Include stuff shared between SCCP on Icode and RTL.
%% NOTE: Needs to appear after DEBUG is possibly defined.
%%-----------------------------------------------------------------------------

-define(CODE, hipe_rtl).
-define(CFG,  hipe_rtl_cfg).
-include("../ssa/hipe_ssa_const_prop.inc").

-type bool_lattice() :: 'true' | 'false' | 'top' | 'bottom'.

%%-----------------------------------------------------------------------------
%% Procedure : visit_expression/2
%% Purpose   : do a symbolic execution of the given instruction.  This is just
%%	       a wrapper that chooses the right function to handle a particular
%%	       instruction.
%% Arguments : Instructions - the instruction
%%             Environment  - have a guess.
%% Returns   : {FlowWorkList, SSAWorkList, Environment}
%%-----------------------------------------------------------------------------
visit_expression(Instruction, Environment) ->
  case Instruction of
    #alu{} ->
      visit_alu(Instruction, Environment);
    #alub{} ->
      visit_alub(Instruction, Environment);
    #call{} ->
      visit_call(Instruction, Environment);
%%    #comment{} ->
%%      visit_comment(Instruction, Environment);
%%    #enter{} ->
%%      visit_enter(Instruction, Environment);
    #fconv{} ->
      visit_fconv(Instruction, Environment);
    #fixnumop{} ->
      visit_fixnumop(Instruction, Environment);
    #fload{} ->
      visit_fload(Instruction, Environment);
    #fmove{} ->
      visit_fmove(Instruction, Environment);
    #fp{} ->
      visit_fp(Instruction, Environment);
    #fp_unop{} ->
      visit_fp_unop(Instruction, Environment);
%%    #fstore{} ->
%%      visit_fstore(Instruction, Environment);
%%    #gctest{} ->
%%      visit_gctest(Instruction, Environment);
    #goto{} ->
      visit_goto(Instruction, Environment);
    #goto_index{} ->
      visit_goto_index(Instruction, Environment);
%%    #label{} ->
%%      visit_label(Instruction, Environment);
    #load{} ->
      visit_load(Instruction, Environment);
    #load_address{} ->
      visit_load_address(Instruction, Environment);
    #load_atom{} ->
      visit_load_atom(Instruction, Environment);
    #load_word_index{} ->
      visit_load_word_index(Instruction, Environment);
    #move{} ->
      visit_move(Instruction, Environment);
    #multimove{} ->
      visit_multimove(Instruction, Environment);
%% phi-nodes are handled in scc
%%    #phi{} ->
%%      visit_phi(Instruction, Environment);
%%    #return{} ->
%%      visit_return(Instruction, Environment);
%%    #store{} ->
%%      visit_store(Instruction, Environment);
    #switch{} ->
      visit_switch(Instruction, Environment);
    _ ->
      %% label, end_try, comment, return, fail, et al
      {[], [], Environment}
  end.


%%-----------------------------------------------------------------------------
%% Procedure : set_to/3
%% Purpose   : many of the visit_<inst> functions ends in a update of the 
%%             environment (and resulting SSA-edges) this function does the 
%%             update in a nice way and formats the result so that it can be
%%             imediatly returned to visit_expression
%% Arguments : Dst - the destination may be a list of destinations.
%%             Val - the new value (bottom, or some constant).
%%             Env - the environment in which the update should be done.
%% Returns   : { FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

set_to(Dst, Val, Env) ->
  {Env1, SSAWork} = update_lattice_value({Dst, Val}, Env),
  {[], SSAWork, Env1}.

%%-----------------------------------------------------------------------------
%% Procedure : evaluate_fixnumop/2 
%% Purpose   : try to evaluate a fixnumop.
%% Arguments : Val1 - operand (an integer, 'top' or 'bottom')
%%             Op - the operation.
%% Returns   : Result
%%              where result is an integer, 'top' or 'bottom'
%%-----------------------------------------------------------------------------

evaluate_fixnumop(Val1, Op) ->
  if Val1 =:= top ->
      top;
     Val1 =:= bottom ->
      bottom;
     is_integer(Val1) ->
      case Op of
	tag ->
	  hipe_tagscheme:mk_fixnum(Val1);
	untag ->
	  hipe_tagscheme:fixnum_val(Val1)
      end
  end.	

%%-----------------------------------------------------------------------------
%% Procedure : visit_alu/2
%% Purpose   : do symbolic exection of a alu
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : { FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_alu(Inst, Env) ->
  Val1 = lookup_lattice_value(hipe_rtl:alu_src1(Inst), Env),
  Val2 = lookup_lattice_value(hipe_rtl:alu_src2(Inst), Env),
  {NewVal, _, _, _, _} = evaluate_alu(Val1, hipe_rtl:alu_op(Inst), Val2),
  set_to(hipe_rtl:alu_dst(Inst), NewVal, Env).

%% Here follows the alu-evaluation stuff. This is the most involved part I
%% guess. The function that you may want to use is evaluate_alu/3. The 
%% evaluation functions returns 
%%  { Result, SignFlag, ZeroFlag, Overflow flag, CarryBit}
%% it uses some helpers which are explained breifly:
%% lattice_meet/2 - handles the general case of most alu-operations, called 
%%                  when at least one of the operands is nonconstant, and the
%%                  operation-specifics have been taken care of.
%% all_ones/0     - returns the value of a rtl-word set to all 1 bits.
%% partial_eval_alu - tries to catch some operation specific special cases 
%%                    when one (or both) of the operands is nonconstant.

lattice_meet(Val1, Val2) ->
  M = if (Val1 =:= top) or (Val2 =:= top) -> top;
         (Val1 =:= bottom) or (Val2 =:= bottom) -> bottom
	 % the check is realy just sanity
      end,
  {M, M, M, M, M}.

all_ones() ->
  (1 bsl ?bytes_to_bits(hipe_rtl_arch:word_size())) - 1.

%% when calling partial_eval*() we know that at least one of the Values 
%% are bottom or top. They return { Value, Sign, Zero, Overflow, Carry }. 
%% (just like hipe_rtl_arch:eval_alu)

%% logic shifts are very similar each other. Limit is the number of
%% bits in the words.
partial_eval_shift(Limit, Val1, Val2) ->
  if 
    Val2 =:= 0 -> {Val1, Val1, Val1, Val1, Val1};
    Val1 =:= 0 -> {0, false, true, false, false};
    is_integer(Val2), Val2 >= Limit -> % (Val2 =/= top) and (Val2 =/= bottom)
      {0, false, true, Val1, Val1}; % OVerflow & carry we dont know about.
    true -> lattice_meet(Val1, Val2)
  end.

%%-----------------------------------------------------------------------------
%% Procedure : partial_eval_alu/3
%% Purpose   : try to evaluate as much as possible an alu operation where at 
%%             least one of the operands is not constant.
%% Arguments : Val1, Val2 - operands (integer, top or bottom)
%%             Op  - the operation.
%% Returns   : {Result, Sign, Zero, Overflow, Carry}
%%              where Result is an integer, 'top' or 'bottom'
%%              and the others are bool, 'top' or 'bottom'.
%%-----------------------------------------------------------------------------

partial_eval_alu(Val1, add, Val2) ->
  if 
    (Val1 == 0) -> {Val2,  Val2, Val2, false, false};
    (Val2 == 0) -> {Val1,  Val1, Val1, false, false};
    true -> lattice_meet(Val1, Val2)
  end;
partial_eval_alu(Val1, sub, Val2) ->
  if 
    (Val2 == 0) -> {Val1,  Val1, Val1, false, false};
    true -> lattice_meet(Val1, Val2)
  end;
partial_eval_alu(Val1, 'or', Val2) ->
  All_ones = all_ones(),
  if 
    (Val1 == 0) -> {Val2,  Val2, Val2, false, false};
    (Val2 == 0) -> {Val1,  Val1, Val1, false, false};
    (Val1 == All_ones) or (Val2 == All_ones) -> 
      {All_ones,  true, false, false, false};
    true -> lattice_meet(Val1, Val2)
  end;
partial_eval_alu(Val1, 'and', Val2) ->
  All_ones = all_ones(),
  if 
    Val1 == All_ones -> {Val2,  Val2, Val2, false, false};
    Val2 == All_ones -> {Val1,  Val1, Val1, false, false};
    (Val1 == 0) or (Val2 == 0) -> {0,  false, true, false, false};
    true -> lattice_meet(Val1, Val2)
  end;
partial_eval_alu(Val1, 'xor', Val2) ->
  if
    (Val1 == 0) -> {Val2,  Val2, Val2, false, false};
    (Val2 == 0) -> {Val1,  Val1, Val1, false, false};
    true -> lattice_meet(Val1, Val2)
  end;
partial_eval_alu(Val1, 'xornot', Val2) ->
  All_ones = all_ones(),
  if
    Val1 == All_ones -> {Val2,  Val2, Val2, false, false};
    Val2 == All_ones -> {Val1,  Val1, Val1, false, false};
    true -> lattice_meet(Val1, Val2)
  end;
partial_eval_alu(Val1, andnot, Val2) ->
  All_ones = all_ones(),
  if 
    (Val2 == 0) -> {Val1,  Val1, Val1, false, false};
    (Val1 == 0) or (Val2 == All_ones) -> {0,  false, true, false, false};
    true -> lattice_meet(Val1, Val2)
  end;
partial_eval_alu(Val1, Op, Val2) when (Op =:= 'sll') or (Op =:= 'srl') ->
  BitSize = ?bytes_to_bits(hipe_rtl_arch:word_size()),
  partial_eval_shift(BitSize, Val1, Val2);
partial_eval_alu(Val1, Op, Val2) when (Op =:= 'sllx') or (Op =:= 'srlx') ->
  partial_eval_shift(64, Val1, Val2);
partial_eval_alu(Val1, mul, Val2) -> lattice_meet(Val1, Val2); % XXX: suboptimal

% arithmetic shifts are more tricky, shifting something unknown can
% generate all_ones() and 0 depenging on the sign of Val1.
partial_eval_alu(Val1, Op, Val2) when (Op =:= 'sra') or (Op =:= 'srax') ->
  if 
    (Val2 == 0) -> {Val1,  Val1, Val1, false, false};
    (Val1 == 0) -> {0, false, true, false, false};
    true -> lattice_meet(Val1, Val2)
  end.

%%-----------------------------------------------------------------------------
%% Procedure : evaluate_alu/3 
%% Purpose   : try to evaluate as much as possible of a alu operation.
%% Arguments : Val1, Val2 - operands (an integer, 'top' or 'bottom')
%%             Op - the operation.
%% Returns   : {Result, Sign, Zero, Overflow, Carry}
%%              where result is an integer, 'top' or 'bottom'
%%              and the others are Bool, 'top' or 'bottom'.
%%-----------------------------------------------------------------------------

evaluate_alu(Val1, Op, Val2) ->
  if 
    (Val1 =:= top) or (Val2 =:= top) or 
    (Val1 =:= bottom) or (Val2 =:= bottom) -> partial_eval_alu(Val1, Op, Val2);
    true ->
      case Op of
        sllx -> hipe_rtl_arith_64:eval_alu('sll', Val1, Val2);
        srlx -> hipe_rtl_arith_64:eval_alu('srl', Val1, Val2); 
        srax -> hipe_rtl_arith_64:eval_alu('sra', Val1, Val2);
        _    -> hipe_rtl_arch:eval_alu(Op, Val1, Val2)
      end
  end.

maybe_top_or_bottom(List) ->
  maybe_top_or_bottom(List, false).

maybe_top_or_bottom([],          TB) -> TB;
maybe_top_or_bottom([top | Rest], _) -> maybe_top_or_bottom(Rest, top);
maybe_top_or_bottom([bottom | _], _) -> bottom;
maybe_top_or_bottom([_ | Rest],  TB) -> maybe_top_or_bottom(Rest, TB).

-spec partial_eval_branch(hipe_rtl:alub_cond(), bool_lattice(), bool_lattice(),
			  bool_lattice() | 0, bool_lattice() | 0) ->
	 bool_lattice().
partial_eval_branch(Cond, N0, Z0, V0, C0) ->
  {N, Z, V, C} =
    if Cond =:= 'eq';
       Cond =:= 'ne'           -> {true, Z0,   true, true};
       Cond =:= 'gt';
       Cond =:= 'le'           -> {N0,   Z0,   V0,   true};
       Cond =:= 'leu';
       Cond =:= 'gtu'          -> {true, Z0,   true, C0  };
       Cond =:= 'lt';
       Cond =:= 'ge'           -> {N0,   true, V0,   true};
       Cond =:= 'geu';
       Cond =:= 'ltu'          -> {true, true, true, C0  };
       Cond =:= 'overflow';
       Cond =:= 'not_overflow' -> {true, true, V0,   true}
    end,
  case maybe_top_or_bottom([N, Z, V, C]) of
    false  -> hipe_rtl_arch:eval_cond_bits(Cond, N, Z, V, C);
    top    -> top;
    bottom -> bottom
  end.

%%-----------------------------------------------------------------------------
%% Procedure : visit_alub/2
%% Purpose   : do symbolic exection of a alub instruction
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : { FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_alub(Inst, Env) ->
  Val1 = lookup_lattice_value(hipe_rtl:alub_src1(Inst), Env),
  Val2 = lookup_lattice_value(hipe_rtl:alub_src2(Inst), Env),
  {NewVal, N, Z, C, V} = evaluate_alu(Val1, hipe_rtl:alub_op(Inst), Val2),
  Labels = 
    case NewVal of
      bottom -> [hipe_rtl:alub_true_label(Inst), 
                 hipe_rtl:alub_false_label(Inst)];
      top    -> [];
      _      ->
        %% if the partial branch cannot be evaluated we must execute the
        %% instruction at runtime.
        case partial_eval_branch(hipe_rtl:alub_cond(Inst), N, Z, C, V) of
          bottom -> [hipe_rtl:alub_true_label(Inst), 
                     hipe_rtl:alub_false_label(Inst)];
          top    -> [];
          true   -> [hipe_rtl:alub_true_label(Inst)];
          false  -> [hipe_rtl:alub_false_label(Inst)]
        end
     end,
  {[], NewSSA, NewEnv} =
    case hipe_rtl:alub_has_dst(Inst) of
      false -> {[], [], Env};
      true -> set_to(hipe_rtl:alub_dst(Inst), NewVal, Env)
    end,
  {Labels, NewSSA, NewEnv}.
      
%%-----------------------------------------------------------------------------
%% Procedure : visit_fixnumop/2
%% Purpose   : do symbolic exection of a fixnumop instruction.
%%             fixnumop is like a specialized alu.
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : { FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_fixnumop(Inst, Env) ->
  Val = lookup_lattice_value(hipe_rtl:fixnumop_src(Inst), Env),
  Res = evaluate_fixnumop(Val, hipe_rtl:fixnumop_type(Inst)),
  set_to(hipe_rtl:fixnumop_dst(Inst), Res, Env).

%%-----------------------------------------------------------------------------
%% Procedure : visit_f*
%% Purpose   : Do symbolic execution of floating point instructions.
%%             All floating-point hitngs are mapped to bottom. In order to 
%%             implement them we would have to add hipe_rtl_arch:eval_f* 
%%             instructions since floating point is no exact science.
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_fconv(Inst, Env) ->
  set_to(hipe_rtl:fconv_dst(Inst), bottom, Env).

visit_fp(Inst, Env) ->
  set_to(hipe_rtl:fp_dst(Inst), bottom, Env).

visit_fp_unop(Inst, Env) ->
  set_to(hipe_rtl:fp_unop_dst(Inst), bottom, Env).

visit_fload(Inst, Env) ->
  set_to(hipe_rtl:fload_dst(Inst), bottom, Env).

visit_fmove(Inst, Env) ->
  set_to(hipe_rtl:fmove_dst(Inst), bottom, Env).

%%-----------------------------------------------------------------------------
%% Procedure : visit_move/2
%% Purpose   : execute a register-copy
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_move(Inst, Env) ->
  Src = hipe_rtl:move_src(Inst),
  Dst = hipe_rtl:move_dst(Inst),
  set_to(Dst, lookup_lattice_value(Src, Env), Env).

%%-----------------------------------------------------------------------------
%% Procedure : visit_goto/2
%% Purpose   : execute a goto
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_goto(Instruction, Environment) ->
  GotoLabel = hipe_rtl:goto_label(Instruction),
  {[GotoLabel], [], Environment}.

%%-----------------------------------------------------------------------------
%% Procedure : visit_goto_index/2
%% Purpose   : execute a goto_index
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_goto_index(Inst, Env) ->
  Index = hipe_rtl:goto_index_index(Inst),
  case lookup_lattice_value(Index, Env) of 
    top    ->   { [], [], Env };
    bottom -> %% everything is reachable
      { hipe_rtl:goto_index_labels(Inst), [], Env };
    I   -> %% only the ith label will be taken.
      io:format("hipe_rtl_ssa_const_prop foud goto-index with constant index ~w in ~w\n",
                [I, Inst]),      
      { [ lists:nth(hipe_rtl:goto_index_labels(Inst), I) ], [], Env }
  end.

%%-----------------------------------------------------------------------------
%% Procedure : visit_load/2
%% Purpose   : do a visit_load. Its hard to track whats in memory, and it's 
%%             not in ssa form, so let's assume bottom-values !
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_load(Inst, Env) ->
  set_to(hipe_rtl:load_dst(Inst), bottom, Env).

%%-----------------------------------------------------------------------------
%% Procedure : visit_load_address/2
%% Purpose   : execute a load_address instruction, while there might be things 
%%             here that are runtime-constant they are not compile-time
%%             constant since code loading interferes with addresses.
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_load_address(Inst, Env) ->
  Dst = hipe_rtl:load_address_dst(Inst),
  Val = bottom, %% all these are probably run-time, but not
                %% compile-time constants
  set_to(Dst, Val, Env).

%%-----------------------------------------------------------------------------
%% Procedure : visit_load_atom/2
%% Purpose   : Like loadadress this one gets something that is not 
%%             compiletime-constant
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_load_atom(Inst, Env) ->
  set_to(hipe_rtl:load_atom_dst(Inst), bottom, Env).
  
%%-----------------------------------------------------------------------------
%% Procedure : visit_load_word_index/2
%% Purpose   : execute a load_word_index. Here is probably room for 
%%             improvement, we should be able to find some constants here, 
%%             since we can get the labeled values from the environment, and 
%%             then find the value with the given index.
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_load_word_index(Inst, Env) ->
  io:format(" this is load word index: ~w\n", [Inst]),
  set_to(hipe_rtl:load_word_index_dst(Inst), bottom, Env).

%%-----------------------------------------------------------------------------
%% Procedure : visit_multimove/2 & visit_multimove/4
%% Purpose   : execute a multimove instruction. 
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_multimove([Dst | Dsts], [Val | Vals], MyEnv, MySSA) ->
  {NewEnv, NewSSA} = update_lattice_value({Dst, Val}, MyEnv),
  visit_multimove(Dsts, Vals, NewEnv, MySSA ++ NewSSA);
visit_multimove([], [], MyEnv, MySSA) ->
  {MyEnv, MySSA}.

visit_multimove(Inst, Env) ->
  Srcs = [lookup_lattice_value(S, Env) || 
	   S <- hipe_rtl:multimove_srclist(Inst)],
  {NewEnv, NewSSA} = visit_multimove(hipe_rtl:multimove_dstlist(Inst),
				     Srcs, Env, []),
  {[], NewSSA, NewEnv}.
  
%%-----------------------------------------------------------------------------
%% Procedure : visit_call/2
%% Purpose   : execute a call-instruction. All calls return bottom. We make 
%%             this assumption since the icode-leel have taken care of BIF's
%%             and we belive that we are left with the things that can not be
%%             done att compile time.
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

visit_call(Inst, Env) ->
  {Env1, SSAWork} =
    update_lattice_value({hipe_rtl:call_dstlist(Inst), bottom}, Env),
    % remeber to add both continuation & failto things to the cfgwl
  Cont = case hipe_rtl:call_continuation(Inst) of
	   [] -> [];
	   C  -> [C]
         end,
  Succ = case hipe_rtl:call_fail(Inst) of
	   [] -> Cont;
	   Fail -> [Fail | Cont]
         end,
  {Succ, SSAWork, Env1}.

%%-----------------------------------------------------------------------------
%% Procedure : visit_switch/2
%% Purpose   : execute a switch-statement. 
%% Arguments : Inst - The instruction
%%             Env  - The environment
%% Returns   : {FlowWorkList, SSAWorkList, NewEnvironment}
%%-----------------------------------------------------------------------------

%% first two helpers that are used to handle the mapping from value to label.
%% why isn't there a function that does this ?

find_switch_label(Inst, Val) ->
  Labels = hipe_rtl:switch_labels(Inst),
  ?SCCPDBG(io:format("finding switch_label, ~w in ~w\n", [Val,Inst])),
  %% it seems like the index is zero based. nth uses 1-based indexing.
  lists:nth(Val + 1, Labels).

%% Switches seem tricky. the sort-order is a list of key-values to be
%% tested in order. (if elem i matches then we should jump to elem i of
%% the labels-list)
visit_switch(Inst, Env) ->
  case lookup_lattice_value(hipe_rtl:switch_src(Inst), Env) of
    top ->
      {[], [], Env};
    bottom ->
      {hipe_rtl:switch_labels(Inst), [], Env};
    Val ->
      {[find_switch_label(Inst, Val) ], [], Env}
  end.

%%-----------------------------------------------------------------------------
%% Procedure : update_instruction/2
%% Purpose   : update the given instruction using any information found in 
%%             the environment.
%% Arguments : Inst - the instruction
%%             Environment - in which everything happens.
%% Returns   : list of new instructions.
%%-----------------------------------------------------------------------------

%% idea: what to do with vi <- Constant. wouldn't it be possible to
%% remove those ? (and similarily for alu-instructions. and alub
%% instructions also ! (of course this will be done in some later step dead
%%  code elimination ? but it's a simple check.)
update_instruction(Inst, Env) ->
  case Inst of 
    #alu{} ->
      update_alu(Inst, Env);
    #alub{} ->
      update_alub(Inst, Env);
    #call{} ->
      subst_all_uses(Inst, Env);
%%    #comment{} ->
%%      [Inst];
    #enter{} ->
      subst_all_uses(Inst, Env);
    #fconv{} ->
      subst_all_uses(Inst, Env);
    #fload{} ->
      subst_all_uses(Inst, Env);
    #fmove{} ->
      subst_all_uses(Inst, Env);
    #fp{} ->
      subst_all_uses(Inst, Env);
    #fp_unop{} ->
      subst_all_uses(Inst, Env);
    #fstore{} ->
      subst_all_uses(Inst, Env);
    #gctest{} ->
      subst_all_uses(Inst, Env);
%%    #goto{} ->
%%       [ Inst ];
    #goto_index{} ->
      update_goto_index(Inst, Env);
%%    #label{} ->
%%      [ Inst ];
    #load{} ->
      subst_all_uses(Inst, Env);
    #load_address{} ->
      subst_all_uses(Inst, Env);
    #load_atom{} ->
      subst_all_uses(Inst, Env);
    #load_word_index{} ->
      subst_all_uses(Inst, Env);
    #move{} ->
      subst_all_uses(Inst, Env);
    #multimove{} ->
      subst_all_uses(Inst, Env);
    #return{} ->
      subst_all_uses(Inst, Env);
    #store{} ->
      subst_all_uses(Inst, Env);
    #switch{} ->
      update_switch(Inst, Env);
    #phi{} ->
      update_phi(Inst, Env);
    _ ->  % for the others it's sufficient to just update any thing they use.
      [ Inst ]
  end.

%%-----------------------------------------------------------------------------
%% Procedure : subst_uses/2
%% Purpose   : looks up all things that an instruction uses and replaces
%%             anything that is determined to be constant.
%% Arguments : Inst - the instruction
%%             Env - in which everything happen.
%% Returns   : list of instructions to replace Inst with.
%%-----------------------------------------------------------------------------

subst_all_uses(Inst, Env) ->
  Uses = hipe_rtl_ssa:uses_to_rename(Inst),
  [ hipe_rtl:subst_uses(update_srcs(Uses, Env), Inst) ].

%%-----------------------------------------------------------------------------
%% Procedure : update_srcs/2
%% Purpose   : given the things that a instruction use return a list 
%%             {Src, NewValue} pairs that can be sent to subs_uses.
%% Arguments : Srcs - list of uses
%%             Env - in which everything happens.
%% Returns   : list of {Src, NewValue} pairs.
%%-----------------------------------------------------------------------------

update_srcs(Srcs, Env) ->
  Update = 
    fun(Src, Os) ->
      case lookup_lattice_value(Src, Env) of 
        bottom -> Os;
        top -> % this would be realy strange.
          ?EXIT({"update_src, top", Src });
        Constant ->
          [ {Src, hipe_rtl:mk_imm(Constant)} | Os]
      end
    end,
  lists:foldl(Update, [], Srcs ).
  
%%-----------------------------------------------------------------------------
%% functions for performing partial evaluation of alu-operations. They can 
%% return either an integer (the actual result), move_src1 or move_src2 in 
%% which case the alu-operation can be replace with a move, or keep_it in 
%% which case the instruction must be kept.

%%-----------------------------------------------------------------------------
%% Procedure : partial_update_shift/3
%% Purpose   : perform a shift
%% Arguments : Limit - the number of bits in the word to shift.
%%             Val1 - the shiftee
%%             Val2 - number of bits to shift
%% Returns   : Integer, move_src1, keep_it
%%-----------------------------------------------------------------------------

partial_update_shift(Limit, Val1, Val2) ->
  if
    (Val1 =:= bottom) and (Val2 =:= 0) -> move_src1;
    (Val1 =:= 0) or ((Val2 =/= bottom) and (Val2 >= Limit)) -> 0;
    true -> keep_it
  end.

%%-----------------------------------------------------------------------------
%% Procedure : partial_update_alu/3
%% Purpose   : perform as much of alu-operations where exatcly one of the
%%             operands is bottom.
%% Arguments : Val1, Val2 - operands
%%             Op - the operation.
%% Returns   : Integer, move_src1, move_src2, keep_it
%%-----------------------------------------------------------------------------

%% we know that exactly one of the operands are bottom this one
%% returns what to do with the instruction (it's either replace with
%% src1, replace src2 replace with constant or keep it.

partial_update_alu(Val1, 'add', Val2) ->
  if
    (Val1 == 0) -> move_src2;
    (Val2 == 0) -> move_src1;
    true -> keep_it
  end;
partial_update_alu(_Val1, 'sub', Val2) ->
  if
    (Val2 == 0) -> move_src1;
    true -> keep_it
  end;
partial_update_alu(Val1, 'or', Val2) ->
  All_ones = all_ones(),
  if 
    (Val1 == 0) -> move_src2;
    (Val2 == 0) -> move_src1;
    (Val1 == All_ones) or (Val2 == All_ones) -> All_ones;
    true -> keep_it
  end;
partial_update_alu(Val1, 'and', Val2) ->
  All_ones = all_ones(),
  if 
    Val1 == All_ones -> move_src2;
    Val2 == All_ones -> move_src1;
    (Val1 == 0) or (Val2 == 0) -> 0;
    true -> keep_it
  end;
partial_update_alu(Val1, 'xor', Val2) ->
  if 
    (Val1 == 0) -> move_src2;
    (Val2 == 0) -> move_src1;
    true -> keep_it
  end;
partial_update_alu(Val1, 'xornot', Val2) ->
  All_ones = all_ones(),
  if 
    (Val1 == All_ones) -> move_src2;
    (Val2 == All_ones) -> move_src1;
    true -> keep_it
  end;
partial_update_alu(Val1, andnot, Val2) ->
  All_ones = all_ones(),
  if 
    Val2 == 0 -> move_src1;
    (Val1 == 0) or (Val2 == All_ones) -> 0;
    true -> keep_it
  end;
partial_update_alu(Val1, Op, Val2) when (Op =:= 'sll') or (Op =:= 'srl') ->
  BitSize = ?bytes_to_bits(hipe_rtl_arch:word_size()),
  partial_update_shift(BitSize, Val1, Val2);
partial_update_alu(Val1, Op, Val2) when (Op =:= 'sllx') or (Op =:= 'srlx') ->
  partial_update_shift(64, Val1, Val2);
partial_update_alu(Val1, Op, Val2) when (Op =:= 'sra') or (Op =:= 'srax') ->
  if 
    Val2 == 0 -> move_src1;
    Val1 == 0 -> 0;
    true -> keep_it
  end.

%%-----------------------------------------------------------------------------
%% Procedure : update_alu/2
%% Purpose   : update an alu-instruction.
%% Arguments : Inst - the instruction.
%%             Env - in which everything happens.
%% Returns   : list of new instruction
%%-----------------------------------------------------------------------------

update_alu(Inst, Env) ->
  Val1 = lookup_lattice_value(hipe_rtl:alu_src1(Inst), Env),
  Val2 = lookup_lattice_value(hipe_rtl:alu_src2(Inst), Env),
  if 
    (Val1 =:= bottom) and (Val2 =:= bottom) ->
      [Inst];
    (Val1 =:= bottom) or (Val2 =:= bottom) ->
      NewInst =
	case partial_update_alu(Val1, hipe_rtl:alu_op(Inst), Val2) of
          move_src1 -> 
            hipe_rtl:mk_move(hipe_rtl:alu_dst(Inst), hipe_rtl:alu_src1(Inst));
          move_src2 ->
            hipe_rtl:mk_move(hipe_rtl:alu_dst(Inst), hipe_rtl:alu_src2(Inst));
          keep_it ->
            S1 = make_alub_subst_list(Val1, hipe_rtl:alu_src1(Inst), []),
            S2 = make_alub_subst_list(Val2, hipe_rtl:alu_src2(Inst), S1),
            hipe_rtl:subst_uses(S2, Inst);
          Constant ->
            hipe_rtl:mk_move(hipe_rtl:alu_dst(Inst), hipe_rtl:mk_imm(Constant))
        end,
      [NewInst];
    true ->
      {Val,_,_,_,_} = evaluate_alu(Val1, hipe_rtl:alu_op(Inst), Val2),
      [hipe_rtl:mk_move(hipe_rtl:alu_dst(Inst), hipe_rtl:mk_imm(Val))]
  end.

%%-----------------------------------------------------------------------------
%% Procedure : update_alub/2
%% Purpose   : update an alub-instruction. Here are some finer points, we might
%%             be able to do the math (think b = a+0), but it's hard to replace
%%             the branch, since the mapping b/w AluOp,RelOp to BranchInstr is
%%             boring to do. (lazyness is a bliss).
%% Arguments : Inst - the instruction.
%%             Env - in which everything happens.
%% Returns   : list of new instructions
%%-----------------------------------------------------------------------------

%% some small helpers.
alub_to_move(Inst, Res, Lab) ->
  Goto = [hipe_rtl:mk_goto(Lab)],
  case hipe_rtl:alub_has_dst(Inst) of
    false -> Goto;
    true ->
      [hipe_rtl:mk_move(hipe_rtl:alub_dst(Inst), Res) | Goto]
  end.

make_alub_subst_list(bottom, _, Tail) ->  Tail;
make_alub_subst_list(top, Src, _) ->
  ?EXIT({"~w is top during update",Src });
make_alub_subst_list(Val, Src, Tail)  -> 
  case hipe_rtl:is_imm(Src) of
    true -> Tail;
    false -> [{Src, hipe_rtl:mk_imm(Val)} | Tail]
  end.

update_alub(Inst, Env) ->
  Src1 = hipe_rtl:alub_src1(Inst),
  Src2 = hipe_rtl:alub_src2(Inst),
  Val1 = lookup_lattice_value(Src1, Env),
  Val2 = lookup_lattice_value(Src2, Env),
  {ResVal, N, Z, C, V}  = evaluate_alu(Val1, hipe_rtl:alub_op(Inst), Val2),  
  CondRes = partial_eval_branch(hipe_rtl:alub_cond(Inst), N, Z, C, V),
  case CondRes of
    bottom -> 
      %% if we can't evaluate the branch, we have to keep it as a alub isnt
      %% since other optimizations might insert other instructions b/w the 
      %% move and the branch. We can however replace variable with constants:
      S1 = make_alub_subst_list(Val1, Src1, []),
      S2 = make_alub_subst_list(Val2, Src2, S1),
      [hipe_rtl:subst_uses(S2, Inst)];
    _ -> %% we know where we will be going, let's find out what Dst should be.
         %% knowing where we are going means that at most one of the values is
         %% bottom, hence we can replace the alu-instr with a move.
         %% remember, a = b + 0 can give us enough info to know what jump to
         %% do without knowing the value of a. (I wonder if this will ever
         %% actualy happen ;)
      Res = case ResVal of 
              bottom ->  % something nonconstant.
                if (Val1 =:= bottom) -> Src1;
	           (Val2 =:= bottom) -> Src2 
                end;
              _ -> hipe_rtl:mk_imm(ResVal)
            end,
      case CondRes of 
        top ->
	  io:format("oops. something VERY bad: ~w ~w V1 & 2 ~w ~w\n",
		    [Inst, {ResVal, N, Z, C, V} , Val1, Val2]),
	  [Inst];
        true  -> alub_to_move(Inst, Res, hipe_rtl:alub_true_label(Inst));
        false -> alub_to_move(Inst, Res, hipe_rtl:alub_false_label(Inst))
      end
  end.

%%-----------------------------------------------------------------------------
%% Procedure : update_goto_index/2
%% Purpose   : update a goto_index instruction.
%% Arguments : Inst - the instruction.
%%             Env  - in which everything happens.
%% Returns   : list of new instructions.
%%-----------------------------------------------------------------------------

update_goto_index(Inst, Env) ->
  Index = hipe_rtl:goto_index_index(Inst),
  case lookup_lattice_value(Index, Env) of 
    bottom -> %% everything is reachable
      [Inst];
    I -> %% only the ith label will be taken.
      [hipe_rtl:mk_goto(lists:nth(hipe_rtl:goto_index_labels(Inst), I))]
  end.

%%-----------------------------------------------------------------------------
%% Procedure : update_switch/2
%% Purpose   : update a switch instruction.
%% Arguments : Inst - the instruction.
%%             Env - in which everything happens.
%% Returns   : list of new instructions.
%%-----------------------------------------------------------------------------

update_switch(Inst, Env) ->
  case lookup_lattice_value(hipe_rtl:switch_src(Inst), Env) of
    bottom ->
      [Inst];
    Const ->
      Lab = find_switch_label(Inst, Const),
      [hipe_rtl:mk_goto(Lab)]
  end.

%%-----------------------------------------------------------------------------
%% Procedure : update_phi/3
%% Purpose   : Update a phi-function w.r.t. constants. do nothing for now.
%% Arguments : Instruction - The instruction
%%             Environment - The environment
%% Returns   : [NewInstruction]
%%-----------------------------------------------------------------------------

update_phi(Instruction, Environment) ->
  Destination = hipe_rtl:phi_dst(Instruction),
  case lookup_lattice_value(Destination, Environment) of
    bottom -> 
      [Instruction];
    top -> 
      ?WARNING_MSG("The dst of ~w is top after SCCP. Strange\n",[Instruction]),
      ?EXIT({"bang !", Instruction}),
      [Instruction];
    Value ->
      [hipe_rtl:mk_move(Destination, hipe_rtl:mk_imm(Value))]
  end.

%%-----------------------------------------------------------------------------

%% make sure that all precoloured registers are taken out of the equation.
lookup_lattice_value(X, Environment) ->
  case hipe_rtl_arch:is_precoloured(X) or hipe_rtl:is_const_label(X) of 
    true ->
      bottom;
    false ->
      lookup_lattice_value2(X, Environment)
  end.

lookup_lattice_value2(X, Environment) ->
  LatticeValues = env__lattice_values(Environment),
  case hipe_rtl:is_imm(X) of
    true ->
      hipe_rtl:imm_value(X);
    false ->
      case gb_trees:lookup(X, LatticeValues) of
        none ->
	  io:format("~w~n",[LatticeValues]),
          ?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}),
          top;
        {value, Y} ->
          Y
      end
  end.

%%----------------------------- End of file -----------------------------------
