%% -*- erlang-indent-level: 2 -*-
%%======================================================================
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2001-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%
%%

-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 params(cfg()) -> hipe_icode:params().
-spec params_update(cfg(), hipe_icode:params()) -> cfg().

-spec is_visited(icode_lbl(), gb_sets:set()) -> boolean().
-spec visit(icode_lbl(), gb_sets:set()) -> gb_sets: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.
