%% -*- 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.

-module(hipe_ppc_ra_finalise).
-export([finalise/3]).
-include("hipe_ppc.hrl").

finalise(CFG, TempMap, FPMap0) ->
  {_, SpillLimit} = hipe_gensym:var_range(ppc),
  Map = mk_ra_map(TempMap, SpillLimit),
  FPMap1 = mk_ra_map_fp(FPMap0, SpillLimit),
  hipe_ppc_cfg:map_bbs(fun(_Lbl, BB) -> ra_bb(BB, Map, FPMap1) end, CFG).

ra_bb(BB, Map, FpMap) ->
  hipe_bb:code_update(BB, ra_code(hipe_bb:code(BB), Map, FpMap, [])).

ra_code([I|Insns], Map, FPMap, Accum) ->
  ra_code(Insns, Map, FPMap, [ra_insn(I, Map, FPMap) | Accum]);
ra_code([], _Map, _FPMap, Accum) ->
  lists:reverse(Accum).

ra_insn(I, Map, FPMap) ->
  case I of
    #alu{} -> ra_alu(I, Map);
    #cmp{} -> ra_cmp(I, Map);
    #load{} -> ra_load(I, Map);
    #loadx{} -> ra_loadx(I, Map);
    #mfspr{} -> ra_mfspr(I, Map);
    #mtcr{} -> ra_mtcr(I, Map);
    #mtspr{} -> ra_mtspr(I, Map);
    #pseudo_li{} -> ra_pseudo_li(I, Map);
    #pseudo_move{} -> ra_pseudo_move(I, Map);
    #pseudo_spill_move{} -> ra_pseudo_spill_move(I, Map);
    #pseudo_tailcall{} -> ra_pseudo_tailcall(I, Map);
    #store{} -> ra_store(I, Map);
    #storex{} -> ra_storex(I, Map);
    #unary{} -> ra_unary(I, Map);
    #lfd{} -> ra_lfd(I, Map, FPMap);
    #lfdx{} -> ra_lfdx(I, Map, FPMap);
    #stfd{} -> ra_stfd(I, Map, FPMap);
    #stfdx{} -> ra_stfdx(I, Map, FPMap);
    #fp_binary{} -> ra_fp_binary(I, FPMap);
    #fp_unary{} -> ra_fp_unary(I, FPMap);
    #pseudo_fmove{} -> ra_pseudo_fmove(I, FPMap);
    #pseudo_spill_fmove{} -> ra_pseudo_spill_fmove(I, FPMap);
    _ -> I
  end.

ra_alu(I=#alu{dst=Dst,src1=Src1,src2=Src2}, Map) ->
  NewDst = ra_temp(Dst, Map),
  NewSrc1 = ra_temp(Src1, Map),
  NewSrc2 = ra_temp_or_imm(Src2, Map),
  I#alu{dst=NewDst,src1=NewSrc1,src2=NewSrc2}.

ra_cmp(I=#cmp{src1=Src1,src2=Src2}, Map) ->
  NewSrc1 = ra_temp(Src1, Map),
  NewSrc2 = ra_temp_or_imm(Src2, Map),
  I#cmp{src1=NewSrc1,src2=NewSrc2}.

ra_load(I=#load{dst=Dst,base=Base}, Map) ->
  NewDst = ra_temp(Dst, Map),
  NewBase = ra_temp(Base, Map),
  I#load{dst=NewDst,base=NewBase}.

ra_loadx(I=#loadx{dst=Dst,base1=Base1,base2=Base2}, Map) ->
  NewDst = ra_temp(Dst, Map),
  NewBase1 = ra_temp(Base1, Map),
  NewBase2 = ra_temp(Base2, Map),
  I#loadx{dst=NewDst,base1=NewBase1,base2=NewBase2}.

ra_mfspr(I=#mfspr{dst=Dst}, Map) ->
  NewDst = ra_temp(Dst, Map),
  I#mfspr{dst=NewDst}.

ra_mtcr(I=#mtcr{src=Src}, Map) ->
  NewSrc = ra_temp(Src, Map),
  I#mtcr{src=NewSrc}.

ra_mtspr(I=#mtspr{src=Src}, Map) ->
  NewSrc = ra_temp(Src, Map),
  I#mtspr{src=NewSrc}.

ra_pseudo_li(I=#pseudo_li{dst=Dst}, Map) ->
  NewDst = ra_temp(Dst, Map),
  I#pseudo_li{dst=NewDst}.

ra_pseudo_move(I=#pseudo_move{dst=Dst,src=Src}, Map) ->
  NewDst = ra_temp(Dst, Map),
  NewSrc = ra_temp(Src, Map),
  I#pseudo_move{dst=NewDst,src=NewSrc}.

ra_pseudo_spill_move(I=#pseudo_spill_move{dst=Dst,temp=Temp,src=Src}, Map) ->
  NewDst = ra_temp(Dst, Map),
  NewTemp = ra_temp(Temp, Map),
  NewSrc = ra_temp(Src, Map),
  I#pseudo_spill_move{dst=NewDst,temp=NewTemp,src=NewSrc}.

ra_pseudo_tailcall(I=#pseudo_tailcall{stkargs=StkArgs}, Map) ->
  NewStkArgs = ra_args(StkArgs, Map),
  I#pseudo_tailcall{stkargs=NewStkArgs}.

ra_store(I=#store{src=Src,base=Base}, Map) ->
  NewSrc = ra_temp(Src, Map),
  NewBase = ra_temp(Base, Map),
  I#store{src=NewSrc,base=NewBase}.

ra_storex(I=#storex{src=Src,base1=Base1,base2=Base2}, Map) ->
  NewSrc = ra_temp(Src, Map),
  NewBase1 = ra_temp(Base1, Map),
  NewBase2 = ra_temp(Base2, Map),
  I#storex{src=NewSrc,base1=NewBase1,base2=NewBase2}.

ra_unary(I=#unary{dst=Dst,src=Src}, Map) ->
  NewDst = ra_temp(Dst, Map),
  NewSrc = ra_temp(Src, Map),
  I#unary{dst=NewDst,src=NewSrc}.

ra_lfd(I=#lfd{dst=Dst,base=Base}, Map, FPMap) ->
  NewDst = ra_temp_fp(Dst, FPMap),
  NewBase = ra_temp(Base, Map),
  I#lfd{dst=NewDst,base=NewBase}.

ra_lfdx(I=#lfdx{dst=Dst,base1=Base1,base2=Base2}, Map, FPMap) ->
  NewDst = ra_temp_fp(Dst, FPMap),
  NewBase1 = ra_temp(Base1, Map),
  NewBase2 = ra_temp(Base2, Map),
  I#lfdx{dst=NewDst,base1=NewBase1,base2=NewBase2}.

ra_stfd(I=#stfd{src=Src,base=Base}, Map, FPMap) ->
  NewSrc = ra_temp_fp(Src, FPMap),
  NewBase = ra_temp(Base, Map),
  I#stfd{src=NewSrc,base=NewBase}.

ra_stfdx(I=#stfdx{src=Src,base1=Base1,base2=Base2}, Map, FPMap) ->
  NewSrc = ra_temp_fp(Src, FPMap),
  NewBase1 = ra_temp(Base1, Map),
  NewBase2 = ra_temp(Base2, Map),
  I#stfdx{src=NewSrc,base1=NewBase1,base2=NewBase2}.

ra_fp_binary(I=#fp_binary{dst=Dst,src1=Src1,src2=Src2}, FPMap) ->
  NewDst = ra_temp_fp(Dst, FPMap),
  NewSrc1 = ra_temp_fp(Src1, FPMap),
  NewSrc2 = ra_temp_fp(Src2, FPMap),
  I#fp_binary{dst=NewDst,src1=NewSrc1,src2=NewSrc2}.

ra_fp_unary(I=#fp_unary{dst=Dst,src=Src}, FPMap) ->
  NewDst = ra_temp_fp(Dst, FPMap),
  NewSrc = ra_temp_fp(Src, FPMap),
  I#fp_unary{dst=NewDst,src=NewSrc}.

ra_pseudo_fmove(I=#pseudo_fmove{dst=Dst,src=Src}, FPMap) ->
  NewDst = ra_temp_fp(Dst, FPMap),
  NewSrc = ra_temp_fp(Src, FPMap),
  I#pseudo_fmove{dst=NewDst,src=NewSrc}.

ra_pseudo_spill_fmove(I=#pseudo_spill_fmove{dst=Dst,temp=Temp,src=Src},
		      FPMap) ->
  NewDst = ra_temp_fp(Dst, FPMap),
  NewTemp = ra_temp_fp(Temp, FPMap),
  NewSrc = ra_temp_fp(Src, FPMap),
  I#pseudo_spill_fmove{dst=NewDst,temp=NewTemp,src=NewSrc}.

ra_args([Arg|Args], Map) ->
  [ra_temp_or_imm(Arg, Map) | ra_args(Args, Map)];
ra_args([], _) ->
  [].

ra_temp_or_imm(Arg, Map) ->
  case hipe_ppc:is_temp(Arg) of
    true ->
      ra_temp(Arg, Map);
    false ->
      Arg
  end.

ra_temp_fp(Temp, FPMap) ->
  Reg = hipe_ppc:temp_reg(Temp),
  case hipe_ppc:temp_type(Temp) of
    'double' ->
      case hipe_ppc_registers:is_precoloured_fpr(Reg) of
	true -> Temp;
	_ -> ra_temp_common(Reg, Temp, FPMap)
      end
  end.

ra_temp(Temp, Map) ->
  Reg = hipe_ppc:temp_reg(Temp),
  case hipe_ppc:temp_type(Temp) of
    'double' ->
      exit({?MODULE,ra_temp,Temp});
    _ ->
      case hipe_ppc_registers:is_precoloured_gpr(Reg) of
	true -> Temp;
	_ -> ra_temp_common(Reg, Temp, Map)
      end
  end.

ra_temp_common(Reg, Temp, Map) ->
  case gb_trees:lookup(Reg, Map) of
    {value,NewReg} -> Temp#ppc_temp{reg=NewReg};
    _ -> Temp
  end.

mk_ra_map(TempMap, SpillLimit) ->
  %% Build a partial map from pseudo to reg or spill.
  %% Spills are represented as pseudos with indices above SpillLimit.
  %% (I'd prefer to use negative indices, but that breaks
  %% hipe_ppc_registers:is_precoloured/1.)
  %% The frame mapping proper is unchanged, since spills look just like
  %% ordinary (un-allocated) pseudos.
  lists:foldl(fun(MapLet, Map) ->
		  {Key,Val} = conv_ra_maplet(MapLet, SpillLimit, is_precoloured_gpr),
		  gb_trees:insert(Key, Val, Map)
	      end,
	      gb_trees:empty(),
	      TempMap).

conv_ra_maplet(MapLet = {From,To}, SpillLimit, IsPrecoloured) ->
  %% From should be a pseudo, or a hard reg mapped to itself.
  if is_integer(From), From =< SpillLimit ->
      case hipe_ppc_registers:IsPrecoloured(From) of
	false -> [];
	_ ->
	  case To of
	    {reg, From} -> [];
	    _ -> exit({?MODULE,conv_ra_maplet,MapLet})
	  end
      end;
     true -> exit({?MODULE,conv_ra_maplet,MapLet})
  end,
  %% end of From check
  case To of
    {reg, NewReg} ->
      %% NewReg should be a hard reg, or a pseudo mapped
      %% to itself (formals are handled this way).
      if is_integer(NewReg) ->
	  case hipe_ppc_registers:IsPrecoloured(NewReg) of
	    true -> [];
	    _ -> if From =:= NewReg -> [];
		    true ->
		     exit({?MODULE,conv_ra_maplet,MapLet})
		 end
	  end;
	 true -> exit({?MODULE,conv_ra_maplet,MapLet})
      end,
      %% end of NewReg check
      {From, NewReg};
    {spill, SpillIndex} ->
      %% SpillIndex should be >= 0.
      if is_integer(SpillIndex), SpillIndex >= 0 -> [];
	 true -> exit({?MODULE,conv_ra_maplet,MapLet})
      end,
      %% end of SpillIndex check
      ToTempNum = SpillLimit+SpillIndex+1,
      MaxTempNum = hipe_gensym:get_var(ppc),
      if MaxTempNum >= ToTempNum -> ok;
	 true -> hipe_gensym:set_var(ppc, ToTempNum)
      end,
      {From, ToTempNum};
    _ -> exit({?MODULE,conv_ra_maplet,MapLet})
  end.

mk_ra_map_fp(FPMap, SpillLimit) ->
  lists:foldl(fun(MapLet, Map) ->
		  {Key,Val} = conv_ra_maplet(MapLet, SpillLimit,
					     is_precoloured_fpr),
		  gb_trees:insert(Key, Val, Map)
	      end,
	      gb_trees:empty(),
	      FPMap).
