aboutsummaryrefslogblamecommitdiffstats
path: root/lib/hipe/x86/hipe_x86_spill_restore.erl
blob: 590ec43d23854a3fa0d82355941e3b810c85efcf (plain) (tree)
1
2
3
4
5
6
7
8
9


                                 


                                                        


                                                                   
  






                                                                           
  























































                                                                              


                                                                               








                                                               
                                                                                                                                   

























                                                                            

                                                                           

                                                                             
            
                                                         







                                                                              
                                                              





                                                                 
                                                                   
                                                                          

























                                                                                          
                                             














































































































































                                                                                                                    

                                                                     















                                                                              

                                

















                                        
%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2010. 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%
%%
%% ====================================================================
%% Authors : Dogan Yazar and Erdem Aksu (KT2 project of 2008)
%% ====================================================================

-ifdef(HIPE_AMD64).
-define(HIPE_X86_SPILL_RESTORE, hipe_amd64_spill_restore).
-define(HIPE_X86_LIVENESS,      hipe_amd64_liveness).
-define(HIPE_X86_SPECIFIC,      hipe_amd64_specific).
-define(HIPE_X86_REGISTERS,	hipe_amd64_registers).
-define(X86STR, "amd64").
-else.
-define(HIPE_X86_SPILL_RESTORE, hipe_x86_spill_restore).
-define(HIPE_X86_LIVENESS,      hipe_x86_liveness).
-define(HIPE_X86_SPECIFIC,      hipe_x86_specific).
-define(HIPE_X86_REGISTERS,     hipe_x86_registers).
-define(X86STR, "x86").
-endif.

-module(?HIPE_X86_SPILL_RESTORE).

-export([spill_restore/2]).

%% controls which set library is used to keep temp variables. 
-define(SET_MODULE, ordsets).

%% Turn on instrumentation.
-define(HIPE_INSTRUMENT_COMPILER, true). 

-include("../main/hipe.hrl").
-include("../x86/hipe_x86.hrl"). % Added for the definition of #pseudo_call{}
-include("../flow/cfg.hrl").     % Added for the definition of #cfg{}

%% Main function
spill_restore(Defun, Options) ->
  CFG = ?option_time(firstPass(Defun), ?X86STR" First Pass", Options),
  CFGFinal = ?option_time(secondPass(CFG), ?X86STR" Second Pass", Options),
  hipe_x86_cfg:linearise(CFGFinal).

%% Performs the first pass of the algorithm.
%% By working bottom up, introduce the pseudo_spills.
firstPass(Defun) ->
  CFG0 = ?HIPE_X86_SPECIFIC:defun_to_cfg(Defun),
  %% get the labels bottom up
  Labels = hipe_x86_cfg:postorder(CFG0),
  Liveness = ?HIPE_X86_LIVENESS:analyse(CFG0),
  %% spill around the function will be introduced below the move
  %% formals, so get all labels except it.
  LabelsExceptMoveFormals = lists:sublist(Labels, length(Labels)-1),
  %% all work is done by the helper function firstPassHelper
  %% saveTree keeps the all newly introduced spills. Keys are the labels.
  {CFG1, SaveTree} = firstPassHelper(LabelsExceptMoveFormals, Liveness, CFG0),
  case hipe_x86_cfg:reverse_postorder(CFG0) of
    [Label1, Label2|_] ->
      SaveTreeElement = saveTreeLookup(Label2, SaveTree),
      %% FilteredSaveTreeElement is the to be spilled temps around the
      %% function call. They are spilled just before move formals.
      FilteredSaveTreeElement = [T || T <- SaveTreeElement, temp_is_pseudo(T)],
      Block = hipe_x86_cfg:bb(CFG1, Label1),
      Code = hipe_bb:code(Block),
      %% The following statements are tedious but work ok.
      %% Put spills between move formals and the jump code.
      %% This disgusting thing is done because spills should be
      %% introduced after move formals. 
      %% Another solution may be to introduce another block.
      MoveCodes = lists:sublist(Code, length(Code)-1),
      JumpCode = lists:last(Code),
      hipe_x86_cfg:bb_add(CFG1, Label1, hipe_bb:mk_bb(MoveCodes ++ [hipe_x86:mk_pseudo_spill(FilteredSaveTreeElement), JumpCode]));
    _ ->
      CFG1
  end.

%% helper function of firstPass

%% processes all labels recursively and decides the spills to be put.
%% spills are introduced before each function call (pseudo_call) as well as 
%% global spill is found
firstPassHelper(Labels, Liveness, CFG) ->
  firstPassHelper(Labels, Liveness, CFG, gb_trees:empty()).

firstPassHelper([Label|Labels], Liveness, CFG, SaveTree) ->
  LiveOut = from_list(?HIPE_X86_LIVENESS:liveout(Liveness, Label)),
  Block = hipe_x86_cfg:bb(CFG, Label),
  Code = hipe_bb:code(Block),
  Succ = hipe_x86_cfg:succ(CFG, Label),
  IntersectedSaveList = findIntersectedSaveList(Succ,SaveTree),
  %% call firstPassDoBlock which will give the updated block
  %% code(including spills) as well as Intersected Save List which
  %% should be passed above blocks
  {_,NewIntersectedList,NewCode} = 
    firstPassDoBlock(Code, LiveOut,IntersectedSaveList),
  NewBlock = hipe_bb:code_update(Block, NewCode),
  NewCFG = hipe_x86_cfg:bb_add(CFG, Label, NewBlock), 
  SizeOfSet = setSize(NewIntersectedList),
  %% if the Intersected Save List is not empty, insert it in the save tree.
  if SizeOfSet =/= 0 ->
      UpdatedSaveTree = gb_trees:insert(Label, NewIntersectedList, SaveTree),
      firstPassHelper(Labels, Liveness, NewCFG, UpdatedSaveTree);
     true ->
      firstPassHelper(Labels, Liveness, NewCFG, SaveTree)
  end;
firstPassHelper([], _, CFG, SaveTree) ->
  {CFG, SaveTree}.

%% handle each instruction in the block bottom up
firstPassDoBlock(Insts, LiveOut, IntersectedSaveList) -> 
  lists:foldr(fun firstPassDoInsn/2, {LiveOut,IntersectedSaveList,[]}, Insts).

firstPassDoInsn(I, {LiveOut,IntersectedSaveList,PrevInsts}) ->
  case I of
    #pseudo_call{} ->
      do_pseudo_call(I, {LiveOut,IntersectedSaveList,PrevInsts});
    _ -> % other instructions
      DefinedList = from_list( ?HIPE_X86_LIVENESS:defines(I)),
      UsedList = from_list(?HIPE_X86_LIVENESS:uses(I)),
      NewLiveOut = subtract(union(LiveOut, UsedList), DefinedList),
      NewIntersectedSaveList = subtract(IntersectedSaveList, DefinedList),
      {NewLiveOut, NewIntersectedSaveList, [I|PrevInsts]}
  end.

do_pseudo_call(I, {LiveOut,IntersectedSaveList,PrevInsts}) ->
  LiveTemps = [Temp || Temp <- to_list(LiveOut), temp_is_pseudo(Temp)],
  NewIntersectedSaveList = union(IntersectedSaveList, LiveOut),
  {LiveOut, NewIntersectedSaveList, [hipe_x86:mk_pseudo_spill(LiveTemps), I | PrevInsts]}.
    
findIntersectedSaveList(LabelList, SaveTree) -> 
  findIntersectedSaveList([saveTreeLookup(Label,SaveTree) || Label <- LabelList]).

findIntersectedSaveList([]) ->
  [];
findIntersectedSaveList([List1]) ->
  List1;
findIntersectedSaveList([List1,List2|Rest]) ->
  findIntersectedSaveList([intersection(List1, List2)|Rest]).

saveTreeLookup(Label, SaveTree) ->
  case gb_trees:lookup(Label, SaveTree) of
    {value, SaveList} ->
      SaveList;
    _ ->
      []
  end.

%% Performs the second pass of the algorithm.
%% It basically eliminates the unnecessary spills and introduces restores.
%% Works top down
secondPass(CFG0) ->
  Labels = hipe_x86_cfg:reverse_postorder(CFG0),
  Liveness = ?HIPE_X86_LIVENESS:analyse(CFG0),
  secondPassHelper(Labels,Liveness,CFG0).

%% helper function of secondPass.

%% recursively handle all labels given.
secondPassHelper(Labels, Liveness, CFG) ->
  secondPassHelper(Labels, Liveness, CFG, gb_trees:empty(), CFG).

%% AccumulatedCFG stands for the CFG that has restore edges incrementally.
%% UnmodifiedCFG is the CFG created after first pass.

%% AccumulatedSaveTree is used to eliminate the unnecessary saves. The
%% saves (spills) in above blocks are traversed down (if still live
%% and not redefined) and redundant saves are eliminated in the lower
%% blocks.
%% For memory efficiency, it may be better not to maintain the
%% AccumulatedSaveTree but traverse the tree recursively and pass the
%% save lists to the childs individually.
%% But current approach may be faster even though it needs bigger memory.

secondPassHelper([Label|RestOfLabels], Liveness,
		 AccumulatedCFG, AccumulatedSaveTree, UnmodifiedCFG) ->
  LiveOut = ?HIPE_X86_LIVENESS:liveout(Liveness, Label),
  Block = hipe_x86_cfg:bb(AccumulatedCFG, Label),
  Code = hipe_bb:code(Block),
  
  %% UnmodifiedCFG is needed for getting the correct predecessors.
  %% (i.e. not to get the restore edge blocks)
  PredList = hipe_x86_cfg:pred(UnmodifiedCFG, Label),
  %% find the spills coming from all the parents by intersecting 
  InitialAccumulatedSaveList = 
    findIntersectedSaveList(PredList, AccumulatedSaveTree),
  AccumulatedSaveList =
    keepLiveVarsInAccumSaveList(InitialAccumulatedSaveList, LiveOut),
  
  {NewCode, CFGUpdateWithRestores, NewAccumulatedSaveList} =
    secondPassDoBlock(Label, Code, AccumulatedCFG, AccumulatedSaveList),
  
  UpdatedAccumulatedSaveTree =
    gb_trees:insert(Label, NewAccumulatedSaveList, AccumulatedSaveTree),
  NewBlock = hipe_bb:code_update(Block, NewCode),
  NewCFG = hipe_x86_cfg:bb_add(CFGUpdateWithRestores, Label, NewBlock),
  secondPassHelper(RestOfLabels, Liveness, NewCFG,
		   UpdatedAccumulatedSaveTree, UnmodifiedCFG);
secondPassHelper([], _, AccumulatedCFG, _, _) ->
  AccumulatedCFG.

secondPassDoBlock(CurrentLabel, Insts, CFG, AccumulatedSaveList) -> 
  {NewAccumulatedSaveList,NewInsts,_,_,CFGUpdateWithRestores} = 
    lists:foldl(fun secondPassDoInsn/2, {AccumulatedSaveList,[],[],CurrentLabel,CFG}, Insts),
  {NewInsts, CFGUpdateWithRestores, NewAccumulatedSaveList}.

secondPassDoInsn(I, {AccumulatedSaveList,PrevInsts,SpillList,CurrentLabel,CFG}) -> 
  case I of
    #pseudo_spill{} ->
      %% spill variables that are not accumulated from top down
      %% (which are not already saved)
      VariablesAlreadySaved = [X || {X,_} <- to_list(AccumulatedSaveList)],
      VariablesToBeSpilled = I#pseudo_spill.args -- VariablesAlreadySaved,
      NewSpillList = [{Temp, hipe_x86:mk_new_temp(Temp#x86_temp.type)} || Temp <- VariablesToBeSpilled],
      %% update accumulated saved list by adding the newly spilled variables.
      NewAccumulatedSaveList = union(AccumulatedSaveList, from_list(NewSpillList)),
      {NewAccumulatedSaveList, PrevInsts ++ secondPassDoPseudoSpill(NewSpillList), NewSpillList, CurrentLabel, CFG};
    #pseudo_call{} ->
      {CFGUpdateWithRestores, NewPseudoCall} =
	secondPassDoPseudoCall(I, AccumulatedSaveList, CFG),
      %% spill list is emptied after use
      {AccumulatedSaveList, PrevInsts ++ [NewPseudoCall], CurrentLabel, [], CFGUpdateWithRestores};
    _ ->
      %% remove the defined variables from the accumulated save
      %% list since they need to be saved again in later occasions.
      DefinedList = from_list(?HIPE_X86_LIVENESS:defines(I)),
      NewAccumulatedSaveList = removeRedefVarsFromAccumSaveList(AccumulatedSaveList, DefinedList),
      {NewAccumulatedSaveList, PrevInsts ++ [I], SpillList, CurrentLabel, CFG}
  end.

%% remove dead vars from accumulated save list so that they are not restored.  
keepLiveVarsInAccumSaveList([], _) ->
  [];
keepLiveVarsInAccumSaveList([{Var,Temp}|Rest], DefinedList) ->
  IsDefined = is_element(Var, DefinedList),
  case IsDefined of 
    true  -> [{Var,Temp}|keepLiveVarsInAccumSaveList(Rest, DefinedList)];
    false -> keepLiveVarsInAccumSaveList(Rest, DefinedList)    
  end.

%% remove the redefined variables from accumulated save list since
%% they are changed.
removeRedefVarsFromAccumSaveList([], _) ->
  [];
removeRedefVarsFromAccumSaveList([{Var,Temp}|Rest], DefinedList) ->
  IsDefined = is_element(Var, DefinedList),
  case IsDefined of 
    true  -> removeRedefVarsFromAccumSaveList(Rest, DefinedList);
    false -> [{Var,Temp}|removeRedefVarsFromAccumSaveList(Rest, DefinedList)]
  end.
        
%% convert pseudo_spills to move instructions. 
secondPassDoPseudoSpill(SpillList) ->
  lists:foldl(fun convertPseudoSpillToMov/2, [], SpillList).

%% if there are variables to be restored, then call addRestoreBlockToEdge to 
%% place them in a new block on the edge of the blocks.
secondPassDoPseudoCall(I, RestoreList, CFG) ->
  ContLabel = I#pseudo_call.contlab,
  SizeOfSet = setSize(RestoreList),
  if SizeOfSet =/= 0 ->
      addRestoreBlockToEdge(I, ContLabel, CFG, RestoreList);
     true ->
      {CFG, I}
  end.

%% prepares the moves for the spills.    
convertPseudoSpillToMov({Temp, NewTemp}, OtherMoves) ->
  OtherMoves ++ [mkMove(Temp, NewTemp)].

%% prepares the moves for the restores.
%% Called by addRestoreBlockToEdge while introducing the restores.
convertPseudoRestoreToMov({Temp, NewTemp}, OtherMoves) ->
  OtherMoves ++ [mkMove(NewTemp, Temp)].

%% makes the move record, special care is taken for doubles.    
mkMove(NewTemp,Temp) ->
  if Temp#x86_temp.type =:= 'double' ->
      hipe_x86:mk_fmove(NewTemp, Temp);
     true ->
      hipe_x86:mk_move(NewTemp, Temp)
  end.

%% adds a new block (on the edge) that includes introduced restore moves.
addRestoreBlockToEdge(PseudoCall, ContLabel, CFG, TempArgsList) ->
  NextLabel = hipe_gensym:get_next_label(x86),
  NewCode = lists:foldl(fun convertPseudoRestoreToMov/2, [], TempArgsList) ++ [hipe_x86:mk_jmp_label(ContLabel)],
  NewBlock = hipe_bb:mk_bb(NewCode),
  NewPseudoCall = redirect_pseudo_call(PseudoCall, ContLabel, NextLabel),
  NewCFG = hipe_x86_cfg:bb_add(CFG, NextLabel, NewBlock),
  {NewCFG, NewPseudoCall}.

%% used instead of hipe_x86_cfg:redirect_jmp since it does not handle
%% pseudo_call calls.
redirect_pseudo_call(I = #pseudo_call{contlab=ContLabel}, Old, New) ->
  case Old =:= ContLabel of
    true  -> I#pseudo_call{contlab=New};
    false -> I
  end.

temp_is_pseudo(Temp) ->
  case hipe_x86:is_temp(Temp) of
    true  -> not(?HIPE_X86_REGISTERS:is_precoloured(hipe_x86:temp_reg(Temp)));
    false -> false
  end.

%%---------------------------------------------------------------------
%% Set operations where the module name is an easily changeable macro
%%---------------------------------------------------------------------

union(Set1, Set2) ->
  ?SET_MODULE:union(Set1, Set2).

setSize(Set) ->
  ?SET_MODULE:size(Set).

from_list(List) ->
  ?SET_MODULE:from_list(List).

to_list(Set) ->
  ?SET_MODULE:to_list(Set).

subtract(Set1, Set2) ->
  ?SET_MODULE:subtract(Set1, Set2).

intersection(Set1, Set2) ->
  ?SET_MODULE:intersection(Set1, Set2). 

is_element(Element, Set) ->
  ?SET_MODULE:is_element(Element, Set).