%% -*- 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.
%%
%%--------------------------------------------------------------------
%% File    : hipe_icode_inline_bifs.erl
%% Author  : Per Gustafsson <pergu@it.uu.se>
%% Purpose : Inlines BIFs which can be expressed easily in ICode.
%%           This allows for optimizations in later ICode passes
%%           and makes the code faster.
%%
%% Created : 14 May 2007 by Per Gustafsson <pergu@it.uu.se>
%%--------------------------------------------------------------------

%% Currently inlined BIFs:
%% and, or, xor, not, <, >, >=, =<, ==, /=, =/=, =:=
%% is_atom, is_binary, is_bitstring, is_boolean, is_float,
%% is_function, is_integer, is_list, is_map, is_number,
%% is_pid, is_port, is_reference, is_tuple

-module(hipe_icode_inline_bifs).

-export([cfg/1]).

-include("hipe_icode.hrl").
-include("../flow/cfg.hrl").

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

-spec cfg(#cfg{}) -> #cfg{}.

cfg(Cfg) ->
  Linear =  hipe_icode_cfg:cfg_to_linear(Cfg),
  #icode{code = StraightCode} = Linear,
  FinalCode = lists:flatten([inline_bif(I) || I <- StraightCode]),
  Cfg1 = hipe_icode_cfg:linear_to_cfg(Linear#icode{code = FinalCode}),
  hipe_icode_cfg:remove_unreachable_code(Cfg1).

inline_bif(I = #icode_call{}) ->
  try_conditional(I);
inline_bif(I) ->
  I.

try_conditional(I = #icode_call{dstlist = [Dst], 'fun' = {erlang, Name, 2},
				args = [Arg1, Arg2],
				continuation = Cont}) ->
  case is_conditional(Name) of
    true ->
      inline_conditional(Dst, Name, Arg1, Arg2, Cont);
    false ->
      try_bool(I)
  end;
try_conditional(I) ->
  try_bool(I).

is_conditional(Name) ->
  case Name of
    '=:=' -> true;
    '=/=' -> true;
    '=='  -> true;
    '/='  -> true;
    '>'   -> true;
    '<'   -> true;
    '>='  -> true;
    '=<'  -> true;
    _     -> false
  end.

try_bool(I = #icode_call{dstlist = [Dst], 'fun' = Name,
			 args = [Arg1, Arg2],
			 continuation = Cont, fail_label = Fail}) ->
  case is_binary_bool(Name) of
    {true, Results, ResLbls} ->
      inline_binary_bool(Dst, Results, ResLbls, Arg1, Arg2, Cont, Fail, I); 
    false ->
      try_type_tests(I)
  end;
try_bool(I = #icode_call{dstlist = [Dst], 'fun' = {erlang, 'not', 1},
			 args = [Arg1],
			 continuation = Cont,
			 fail_label = Fail}) ->
  inline_unary_bool(Dst, {false, true}, Arg1, Cont, Fail, I);
try_bool(I) -> try_type_tests(I).

is_binary_bool({erlang, Name, 2}) -> 
  ResTLbl = hipe_icode:mk_new_label(),
  ResFLbl = hipe_icode:mk_new_label(),
  ResTL = hipe_icode:label_name(ResTLbl),
  ResFL = hipe_icode:label_name(ResFLbl),
  case Name of
    'and' -> {true, {ResTL, ResFL, ResFL}, {ResTLbl, ResFLbl}};
    'or' -> {true, {ResTL, ResTL, ResFL}, {ResTLbl, ResFLbl}};
    'xor' -> {true, {ResFL, ResTL, ResFL}, {ResTLbl, ResFLbl}};
    _ -> false
  end;
is_binary_bool(_) -> false.

try_type_tests(I = #icode_call{dstlist=[Dst], 'fun' = {erlang, Name, 1},
			       args = Args, continuation = Cont}) ->
  case is_type_test(Name) of
    {true, Type} ->
      inline_type_test(Dst, Type, Args, Cont);
    false ->
      I
  end;
try_type_tests(I) -> I.

is_type_test(Name) ->
  case Name of
    is_atom -> {true, atom};
    is_binary -> {true, binary};
    is_bitstring -> {true, bitstr};
    is_boolean -> {true, boolean};
    is_float -> {true, float};
    is_function -> {true, function};
    is_integer -> {true, integer};
    is_list -> {true, list};
    is_map -> {true, map};
    is_number -> {true, number};
    is_pid -> {true, pid};
    is_port -> {true, port};
    is_reference -> {true, reference};
    is_tuple -> {true, tuple};
    _ -> false
  end.
 
inline_type_test(BifRes, Type, Src, Cont) ->
  {NewCont, NewEnd} = get_cont_lbl(Cont),
  TLbl  = hipe_icode:mk_new_label(),
  FLbl = hipe_icode:mk_new_label(),
  TL  = hipe_icode:label_name(TLbl),
  FL = hipe_icode:label_name(FLbl),
  [hipe_icode:mk_type(Src, Type, TL, FL),
   TLbl,
   hipe_icode:mk_move(BifRes, hipe_icode:mk_const(true)),
   hipe_icode:mk_goto(NewCont),
   FLbl,
   hipe_icode:mk_move(BifRes, hipe_icode:mk_const(false)),
   hipe_icode:mk_goto(NewCont)|
   NewEnd].  

inline_conditional(BifRes, Op, Src1, Src2, Cont) ->
  {NewCont, NewEnd} = get_cont_lbl(Cont),
  TLbl  = hipe_icode:mk_new_label(),
  FLbl = hipe_icode:mk_new_label(),
  TL  = hipe_icode:label_name(TLbl),
  FL = hipe_icode:label_name(FLbl),
  [hipe_icode:mk_if(Op, [Src1, Src2], TL, FL),
   TLbl,
   hipe_icode:mk_move(BifRes, hipe_icode:mk_const(true)),
   hipe_icode:mk_goto(NewCont),
   FLbl,
   hipe_icode:mk_move(BifRes, hipe_icode:mk_const(false)),
   hipe_icode:mk_goto(NewCont)|
   NewEnd].

%%
%% The TTL TFL FFL labelnames points to either ResTLbl or ResFLbl
%% Depending on what boolean expression we are inlining
%%

inline_binary_bool(Dst, {TTL, TFL, FFL}, {ResTLbl, ResFLbl},
		   Arg1, Arg2, Cont, Fail, I) ->
  {NewCont, NewEnd} = get_cont_lbl(Cont),
  {NewFail, FailCode} = get_fail_lbl(Fail, I), 
  EndCode = FailCode++NewEnd,
  TLbl = hipe_icode:mk_new_label(),
  FLbl = hipe_icode:mk_new_label(),
  NotTLbl = hipe_icode:mk_new_label(),
  NotTTLbl = hipe_icode:mk_new_label(),
  NotTFLbl = hipe_icode:mk_new_label(),
  TL = hipe_icode:label_name(TLbl),
  FL = hipe_icode:label_name(FLbl),
  NotTL = hipe_icode:label_name(NotTLbl),
  NotTTL = hipe_icode:label_name(NotTTLbl),
  NotTFL = hipe_icode:label_name(NotTFLbl),
  [hipe_icode:mk_type([Arg1], {atom, true}, TL, NotTL, 0.5),
   NotTLbl,
   hipe_icode:mk_type([Arg1], {atom, false}, FL, NewFail, 0.99),
   TLbl,
   hipe_icode:mk_type([Arg2], {atom, true}, TTL, NotTTL, 0.5),
   NotTTLbl,
   hipe_icode:mk_type([Arg2], {atom, false}, TFL, NewFail, 0.99),
   FLbl,
   hipe_icode:mk_type([Arg2], {atom, true}, TFL, NotTFL, 0.5),
   NotTFLbl,
   hipe_icode:mk_type([Arg2], {atom, false}, FFL, NewFail, 0.99),
   ResTLbl,
   hipe_icode:mk_move(Dst, hipe_icode:mk_const(true)),
   hipe_icode:mk_goto(NewCont),
   ResFLbl,
   hipe_icode:mk_move(Dst, hipe_icode:mk_const(false)),
   hipe_icode:mk_goto(NewCont)|
   EndCode].
   
inline_unary_bool(Dst, {T,F}, Arg1, Cont, Fail, I) ->
  TLbl  = hipe_icode:mk_new_label(),
  NotTLbl  = hipe_icode:mk_new_label(),
  FLbl = hipe_icode:mk_new_label(),
  TL  = hipe_icode:label_name(TLbl),
  NotTL = hipe_icode:label_name(NotTLbl),
  FL = hipe_icode:label_name(FLbl),
  {NewCont, NewEnd} = get_cont_lbl(Cont),
  {NewFail, FailCode} = get_fail_lbl(Fail, I),
  EndCode = FailCode ++ NewEnd,
  Arg1L = [Arg1],
  [hipe_icode:mk_type(Arg1L, {atom, true}, TL, NotTL, 0.5),
   NotTLbl,
   hipe_icode:mk_type(Arg1L, {atom, false}, FL, NewFail, 0.99),
   TLbl,
   hipe_icode:mk_move(Dst, hipe_icode:mk_const(T)),
   hipe_icode:mk_goto(NewCont),
   FLbl,
   hipe_icode:mk_move(Dst, hipe_icode:mk_const(F)),
   hipe_icode:mk_goto(NewCont)|
   EndCode].

get_cont_lbl([]) ->
  NL = hipe_icode:mk_new_label(),
  {hipe_icode:label_name(NL), [NL]};
get_cont_lbl(Cont) ->
  {Cont, []}.

get_fail_lbl([], I) ->
  NL = hipe_icode:mk_new_label(),
  {hipe_icode:label_name(NL), [NL, I]};
get_fail_lbl(Fail, _) ->
  {Fail, []}.
