%% -*- 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.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% HiPE Intermediate Code
%% ====================================================================
%% Filename : hipe_icode.erl
%% Module : hipe_icode
%% Purpose : Provide primops for the Icode data structure.
%% History : 1997-? Erik Johansson ([email protected]): Created.
%% 2001-01-30 EJ ([email protected]):
%% Apply, primop, guardop removed
%% 2003-03-15 ES ([email protected]):
%% Started commenting in Edoc.
%% Moved pretty printer to separate file.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%@doc
%% This module implements "Linear Icode" and Icode instructions.
%%
%% <p> Icode is a simple (in that it has few instructions) imperative
%% language, used as the first Intermediate Code in the HiPE compiler.
%% Icode is closely related to Erlang, and Icode instructions operate
%% on Erlang terms. </p>
%%
%% <h2><a href="#type-icode">Icode</a></h2>
%%
%% <p> Linear Icode for a function consists of:
%% <ul>
%% <li> the function's name (`{M,F,A}'), </li>
%% <li> a list of parameters, </li>
%% <li> a list of instructions, </li>
%% <li> data, </li>
%% <li> information about whether the function is a leaf function, </li>
%% <li> information about whether the function is a closure, and </li>
%% <li> the range for labels and variables in the code. </li>
%% </ul>
%% </p>
%%
%% <h2><a href="#type-icode_instruction">Icode Instructions</a> (and
%% their components)</h2>
%%
%% Control flow:
%% <dl>
%% <dt><code><a href="#type-if">'if'</a>
%% {Cond::<a href="#type-cond">cond()</a>,
%% Args::[<a href="#type-arg">arg()</a>],
%% TrueLabel::<a href="#type-label_name">label_name()</a>,
%% FalseLabel::<a href="#type-label_name">label_name()</a>
%% } ::
%% <a href="#type-icode_instruction">icode_instr()</a></code></dt>
%% <dd>
%% The if instruction compares the arguments (Args) with
%% condition (Cond) and jumps to either TrueLabel or
%% FalseLabel. (At the moment...) There are only binary
%% conditions so the number of arguments should be two.
%% <p>
%% An if instructions ends a basic block and should be followed
%% by a label (or be the last instruction of the code).
%% </p></dd>
%%
%% <dt><code><a href="#type-switch_val">switch_val</a>
%% {Term::<a href="#type-arg">var()</a>,
%% FailLabel::<a href="#type-label_name">label_name()</a>,
%% Length::integer(),
%% Cases::[{<a href="#type-symbol">symbol()</a>,<a
%% href="#type-label_name">label_name()</a>}]
%% }::
%% <a href="#type-icode_instruction">icode_instr()</a></code></dt>
%% <dd>
%% The switch_val instruction compares the argument Term to the
%% symbols in the lists Cases, control is transfered to the label
%% that corresponds to the first symbol that matches. If no
%% symbol matches control is transfered to FailLabel. (NOTE: The
%% length argument is not currently in use.)
%% <p>
%% The switch_val instruction can be assumed to be implemented as
%% efficiently as possible given the symbols in the case
%% list. (Jump-table, bianry-serach, or nested ifs)
%% </p><p>
%% A switch_val instructions ends a basic block and should be
%% followed by a label (or be the last instruction of the code).
%% </p></dd>
%%
%% <dt><code><a href="#type-switch_tuple_arity">switch_tuple_arity</a>
%% {Term::<a href="#type-arg">var()</a>,
%% FailLabel::<a href="#type-label_name">label_name()</a>,
%% Length::integer(),
%% Cases::[{integer(),<a href="#type-label_name">label_name()</a>}]
%% }::
%% <a href="#type-icode_instruction">icode_instr()</a></code></dt>
%% <dd>
%% The switch_tuple_arity instruction compares the size of the
%% tuple in the argument Term to the integers in the lists Cases,
%% control is transfered to the label that corresponds to the
%% first integer that matches. If no integer matches control is
%% transfered to FailLabel. (NOTE: The length argument is not
%% currently in use.)
%% <p>
%% The switch_tuple_arity instruction can be assumed to be
%% implemented as efficently as possible given the symbols in the
%% case list. (Jump-table, bianry-serach, or nested ifs)
%% </p><p>
%% A switch_tuple_arity instructions ends a basic block and
%% should be followed by a label (or be the last instruction of
%% the code).
%% </p></dd>
%%
%% <dt>`type {typ_expr, arg, true_label, false_label}}'</dt>
%% <dt>`goto {label}'</dt>
%% <dt>`label {name}'</dt>
%% </dl>
%%
%% Moves:
%% <dl>
%% <dt>`move {dst, src}'</dt>
%% <dt>`phi {dst, arglist}'</dt>
%% </dl>
%%
%% Function application:
%% <dl>
%% <dt>`call {[dst], fun, [arg], type, continuation, fail,
%% in_guard}'</dt>
%% <dd>
%% Where `type' is one of {`local', `remote', `primop'}
%% and `in_guard' is either `true' or `false'.</dd>
%% <dt>`enter {fun, [arg], type}'</dt>
%% <dd>
%% Where `type' is one of {`local', `remote', `primop'}
%% and `in_guard' is either `true' or `false'.</dd>
%% <dt>`return {[var]}'</dt>
%% <dd>
%% <strong>WARNING:</strong> Multiple return values are yet not
%% fully implemented and tested.
%% </dd>
%% </dl>
%%
%% Error handling:
%% <dl>
%% <dt>`begin_try {label, successor}'</dt>
%% <dt>`end_try'</dt>
%% <dt>`begin_handler {dstlist}'</dt>
%% <dt>`fail {Args, Class}'</dt>
%% <dd>Where `Class' is one of
%% {`exit', `throw', `error', `rethrow'}. For `error/2', `[args]'
%% is `[Reason,Trace]'. For `rethrow', `Args' is
%% `[Exception,Reason]' - this only occurs in autogenerated code.
%% </dd>
%% </dl>
%%
%% Comments:
%% <dl>
%% <dt>`comment{Text::string()}'</dt>
%% </dl>
%%
%% <h4>Notes</h4>
%%
%% <p> A constant can only show up on the RHS of a `move' instruction
%% and in `if' and `switch_*'</p>
%% <p>
%% Classification of primops should be like this:
%% <ul>
%% <li> `erlang:exit/1, erlang:throw/1, erlang:error/1,
%% erlang:error/2, erlang:fault/1',
%% and `erlang:fault/2' should use the
%% {@link fail(). fail-instruction} in Icode.</li>
%% <li> Calls or tail-recursive calls to BIFs, operators, or internal
%% functions should be implemented with `call' or `enter'
%% respectively, with the primop flag set.</li>
%% <li> All other Erlang functions should be implemented with `call'
%% or `enter' respectively, without the primop flag set.</li>
%% </ul>
%% </p>
%%
%% <h4>Primops</h4>
%%
%% <pre>
%% Constructors:
%% cons - [Car, Cdr]
%% mktuple - [Element1, Element2, ..., ElementN]
%% call_fun - [BoundArg1, ..., BoundArgN, Fun]
%% enter_fun - [BoundArg1, ..., BoundArgN, Fun]
%% #mkfun{} - [FreeVar1, FreeVar2, ..., FreeVarN]
%%
%% Binaries:
%% bs_init
%% {bs_put_string, Bytes, Size}
%% bs_final
%%
%% Selectors:
%% element - [Index, Tuple]
%% unsafe_hd - [List]
%% unsafe_tl - [List]
%% #unsafe_element{} - [Tuple]
%% #unsafe_update_element{} - [Tuple, Val]
%% #closure_element{} - [Fun]
%%
%% Arithmetic: [Arg1, Arg2]
%% '+', '-', '*', '/', 'div', 'rem',
%% 'band', 'bor', 'bxor', 'bnot', 'bsl', 'bsr'
%%
%% Receive:
%% check_get_msg - []
%% next_msg - []
%% select_msg - []
%% set_timeout - [Timeout]
%% clear_timeout - []
%% suspend_msg - []
%%
%% </pre>
%%
%% <h4>Guardops: (primops that can be used in guards and can fail)</h4>
%% <pre>
%% Selectors:
%% unsafe_hd - [List]
%% unsafe_tl - [List]
%% #element{} - [Index, Tuple]
%% #unsafe_element{} - [Tuple]
%%
%% Arithmetic: [Arg1, Arg2]
%% '+', '-', '*', '/', 'div', 'rem',
%% 'band', 'bor', 'bxor', 'bnot', 'bsl', 'bsr',
%% fix_add, fix_sub %% Do these exist?
%%
%% Concurrency:
%% {erlang,self,0} - []
%% </pre>
%%
%%
%% <h4>Relational Operations (Cond in if instruction)</h4>
%% <pre>
%% gt, lt, geq, leq,
%% eqeq, neq, exact_eqeq, exact_neq
%% </pre>
%%
%% <h4>Type tests</h4>
%% <pre>
%% list
%% nil
%% cons
%% tuple
%% {tuple, N}
%% atom
%% {atom, Atom}
%% number
%% integer
%% {integer, N}
%% fixnum
%% bignum
%% float
%% pid
%% port
%% {record, Atom, Size}
%% reference
%% binary
%% function
%% </pre>
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%=====================================================================
-module(hipe_icode).
-include("../main/hipe.hrl").
-include("hipe_icode.hrl").
%% @type icode(Fun, Params, IsClosure, IsLeaf, Code, Data, VarRange,LabelRange)
%% Fun = mfa()
%% Params = [var()]
%% IsClosure = boolean()
%% IsLeaf = boolean()
%% Code = [icode_instr()]
%% Data = data()
%% VarRange = {integer(),integer()}
%% LabelRange = {integer(),integer()}
%%
%% @type icode_instr(I)
%% I = if() | switch_val() | switch_tuple_arity() | type() | goto()
%% | label() | move() | phi() | call() | enter() | return()
%% | begin_try() | end_try() | begin_handler() | fail() | comment()
%%
%% @type if(Cond, Args, TrueLabel, FalseLabel)
%% Cond = cond()
%% Args = [arg()]
%% TrueLabel = label_name()
%% FalseLabel = label_name()
%%
%% @type switch_val(Term, FailLabel, Length, Cases)
%% Term = var()
%% FailLabel = label_name()
%% Length = integer()
%% Cases = [{symbol(),label_name()}]
%%
%% @type switch_tuple_arity(Arg, FailLabel, Length, Cases)
%% Term = var()
%% FailLabel = label_name()
%% Length = integer()
%% Cases = [{symbol(),label_name()}]
%%
%% @type type(TypeTest, Arg, True_label, False_label)
%% TypeTest = type_test()
%% Args = [arg()]
%% TrueLabel = label_name()
%% FalseLabel = label_name()
%%
%% @type goto(Label) Label = label_name()
%%
%% @type label(Name) Name = label_name()
%%
%% @type move(Dst, Src) Dst = var() Src = arg()
%%
%% @type phi(Dst, Id, Arglist)
%% Dst = var() | fvar()
%% Id = var() | fvar()
%% Arglist = [{Pred, Src}]
%% Pred = label_name()
%% Src = var() | fvar()
%%
%% @type call(Dst, Fun, Arg, Type, Continuation, FailLabel, InGuard)
%% Dst = [var()]
%% Fun = mfa() | primop() | closure()
%% Arg = [var()]
%% Type = call_type()
%% Continuation = [] | label_name()
%% FailLabel = [] | label_name()
%% InGuard = boolean()
%%
%% @type enter(Fun, Arg, Type)
%% Fun = mfa() | primop() | closure()
%% Arg = [var()]
%% Type = call_type()
%%
%% @type return (Vars) Vars = [var()]
%%
%% @type begin_try(FailLabel, Successor)
%% FailLabel = label_name()
%% Successor = label_name()
%%
%% @type end_try()
%%
%% @type begin_handler(Dst)
%% Dst = [var()]
%%
%% @type fail(Class, Args, Label)
%% Class = exit_class()
%% Args = [var()]
%% Label = label_name()
%%
%% @type comment(Text) Text = string()
%% @type call_type() = 'local' | 'remote' | 'primop'
%% @type exit_class() = 'exit' | 'throw' | 'error' | 'rethrow'
%% @type cond() = gt | lt | geq | leq | eqeq | neq | exact_eqeq | exact_neq
%% @type type_test() =
%% list
%% | nil
%% | cons
%% | tuple
%% | {tuple, integer()}
%% | atom
%% | {atom, atom()}
%% | number
%% | integer
%% | {integer, integer()}
%% | fixnum
%% | bignum
%% | float
%% | pid
%% | port
%% | {record, atom(), integer()}
%% | reference
%% | binary
%% | function
%%
%% @type mfa(Mod,Fun,Arity) = {atom(),atom(),arity()}
%% @type arg() = var() | const()
%% @type farg() = fvar() | float()
%% @type var(Name) Name = integer()
%% @type fvar(Name) Name = integer()
%% @type label_name(Name) Name = integer()
%% @type symbol(S) = atom() | number()
%% @type const(C) C = immediate()
%% @type immediate(I) = I
%% I = term()
%% @end
%% ____________________________________________________________________
%%
%% Exports
%%
-export([mk_icode/7, %% mk_icode(Fun, Params, IsClosure, IsLeaf,
%% Code, VarRange, LabelRange)
mk_icode/8, %% mk_icode(Fun, Params, IsClosure, IsLeaf,
%% Code, Data, VarRange, LabelRange)
icode_fun/1,
icode_params/1,
icode_params_update/2,
icode_is_closure/1,
icode_closure_arity/1,
icode_closure_arity_update/2,
icode_is_leaf/1,
icode_code/1,
icode_code_update/2,
icode_data/1,
%% icode_data_update/2,
icode_var_range/1,
icode_label_range/1,
icode_info/1,
icode_info_update/2]).
-export([mk_if/4, %% mk_if(Op, Args, TrueLbl, FalseLbl)
%% mk_if/5, %% mk_if(Op, Args, TrueLbl, FalseLbl, Prob)
if_op/1,
if_op_update/2,
if_true_label/1,
if_false_label/1,
if_args/1,
if_args_update/2,
if_pred/1,
%% is_if/1,
mk_switch_val/4,
%% mk_switch_val/5,
switch_val_term/1,
switch_val_fail_label/1,
%% switch_val_length/1,
switch_val_cases/1,
switch_val_cases_update/2,
%% is_switch_val/1,
mk_switch_tuple_arity/4,
%% mk_switch_tuple_arityl/5,
switch_tuple_arity_term/1,
switch_tuple_arity_fail_label/1,
switch_tuple_arity_fail_label_update/2,
%% switch_tuple_arity_length/1,
switch_tuple_arity_cases/1,
switch_tuple_arity_cases_update/2,
%% is_switch_tuple_arity/1,
mk_type/4, %% mk_type(Args, Type, TrueLbl, FalseLbl)
mk_type/5, %% mk_type(Args, Type, TrueLbl, FalseLbl, P)
type_args/1,
%% type_args_update/2,
type_test/1,
type_true_label/1,
type_false_label/1,
type_pred/1,
is_type/1,
mk_guardop/5, %% mk_guardop(Dst, Fun, Args, Continuation, Fail)
mk_primop/3, %% mk_primop(Dst, Fun, Args)
mk_primop/5, %% mk_primop(Dst, Fun, Args, Cont, Fail)
mk_call/5, %% mk_call(Dst, Mod, Fun, Args, Type)
%% mk_call/7, %% mk_call(Dst, Mod, Fun, Args, Type,
%% Continuation, Fail)
mk_call/8, %% mk_call(Dst, Mod, Fun, Args, Type,
%% Continuation, Fail, Guard)
call_dstlist/1,
call_dstlist_update/2,
%% call_dst_type/1,
call_args/1,
call_args_update/2,
call_fun/1,
call_fun_update/2,
call_type/1,
call_continuation/1,
call_fail_label/1,
call_set_fail_label/2,
call_set_continuation/2,
is_call/1,
call_in_guard/1,
mk_goto/1, %% mk_goto(Lbl)
goto_label/1,
mk_enter/4, %% mk_enter(Mod, Fun, Args, Type)
mk_enter_primop/2, %% mk_enter_primop(Op, Type)
enter_fun/1,
enter_fun_update/2,
enter_args/1,
enter_args_update/2,
enter_type/1,
is_enter/1,
mk_return/1, %% mk_return(Vars)
%% mk_fail/1, %% mk_fail(Args) class = exit
mk_fail/2, %% mk_fail(Args, Class)
%% mk_fail/3, %% mk_fail(Args, Class, Label)
mk_move/2, %% mk_move(Dst, Src)
%% mk_moves/2, %% mk_moves(DstList, SrcList)
mk_begin_try/2, %% mk_begin_try(Label, Successor)
mk_begin_handler/1, %% mk_begin_handler(ReasonDst)
mk_end_try/0, %% mk_end_try()
%% mk_elements/2, %% mk_elements(Tuple, Vars)
mk_label/1, %% mk_label(Name)
mk_new_label/0, %% mk_new_label()
mk_comment/1, %% mk_comment(Text)
mk_const/1, %% mk_const(Const)
mk_var/1, %% mk_var(Id)
annotate_variable/2, %% annotate_var_or_reg(VarOrReg, Type)
unannotate_variable/1,%% unannotate_var_or_reg(VarOrReg)
mk_reg/1, %% mk_reg(Id)
mk_reg_gcsafe/1, %% mk_reg_gcsafe(Id)
mk_fvar/1, %% mk_fvar(Id)
mk_new_var/0, %% mk_new_var()
mk_new_fvar/0, %% mk_new_fvar()
mk_new_reg/0, %% mk_new_reg()
mk_new_reg_gcsafe/0, %% mk_new_reg_gcsafe()
mk_phi/1, %% mk_phi(Id)
mk_phi/2 %% mk_phi(Id, ArgList)
]).
%%
%% Identifiers
%%
-export([%% is_fail/1,
is_return/1,
is_move/1,
%% is_begin_try/1,
%% is_begin_handler/1,
%% is_end_try/1,
is_goto/1,
is_label/1,
is_comment/1,
is_const/1,
is_var/1,
is_fvar/1,
is_reg/1,
is_variable/1,
is_annotated_variable/1,
%% is_uncond/1,
is_phi/1]).
%%
%% Selectors
%%
-export([phi_dst/1,
phi_id/1,
%% phi_args/1,
phi_arg/2,
phi_arglist/1,
phi_enter_pred/3,
phi_remove_pred/2,
phi_redirect_pred/3,
move_dst/1,
move_src/1,
move_src_update/2,
begin_try_label/1,
begin_try_successor/1,
begin_handler_dstlist/1,
label_name/1,
comment_text/1,
return_vars/1,
fail_args/1,
fail_class/1,
fail_label/1,
fail_set_label/2,
var_name/1,
variable_annotation/1,
fvar_name/1,
reg_name/1,
reg_is_gcsafe/1,
const_value/1
]).
%%
%% Misc
%%
-export([args/1,
uses/1,
defines/1,
is_safe/1,
reduce_unused/1,
strip_comments/1,
subst/2,
subst_uses/2,
subst_defines/2,
redirect_jmp/3,
successors/1,
fails_to/1,
is_branch/1
]).
-export([highest_var/1, highest_label/1]).
%%
%% Exported types
%%
-export_type([icode/0, params/0]).
-type params() :: [icode_var()].
%%---------------------------------------------------------------------
%%
%% Icode
%%
%%---------------------------------------------------------------------
-spec mk_icode(mfa(), params(), boolean(), boolean(), [icode_instr()],
{non_neg_integer(),non_neg_integer()},
{icode_lbl(),icode_lbl()}) -> icode().
mk_icode(Fun, Params, IsClosure, IsLeaf, Code, VarRange, LabelRange) ->
#icode{'fun'=Fun, params=Params, code=Code,
is_closure=IsClosure,
is_leaf=IsLeaf,
data=hipe_consttab:new(),
var_range=VarRange,
label_range=LabelRange}.
-spec mk_icode(mfa(), params(), boolean(), boolean(), [icode_instr()],
hipe_consttab(), {non_neg_integer(),non_neg_integer()},
{icode_lbl(),icode_lbl()}) -> icode().
mk_icode(Fun, Params, IsClosure, IsLeaf, Code, Data, VarRange, LabelRange) ->
#icode{'fun'=Fun, params=Params, code=Code,
data=Data, is_closure=IsClosure, is_leaf=IsLeaf,
var_range=VarRange, label_range=LabelRange}.
-spec icode_fun(icode()) -> mfa().
icode_fun(#icode{'fun' = MFA}) -> MFA.
-spec icode_params(icode()) -> params().
icode_params(#icode{params = Params}) -> Params.
-spec icode_params_update(icode(), params()) -> icode().
icode_params_update(Icode, Params) ->
Icode#icode{params = Params}.
-spec icode_is_closure(icode()) -> boolean().
icode_is_closure(#icode{is_closure = Closure}) -> Closure.
-spec icode_is_leaf(icode()) -> boolean().
icode_is_leaf(#icode{is_leaf = Leaf}) -> Leaf.
-spec icode_code(icode()) -> icode_instrs().
icode_code(#icode{code = Code}) -> Code.
-spec icode_code_update(icode(), icode_instrs()) -> icode().
icode_code_update(Icode, NewCode) ->
Vmax = highest_var(NewCode),
Lmax = highest_label(NewCode),
Icode#icode{code = NewCode, var_range = {0,Vmax}, label_range = {0,Lmax}}.
-spec icode_data(icode()) -> hipe_consttab().
icode_data(#icode{data=Data}) -> Data.
%% %% -spec icode_data_update(icode(), hipe_consttab()) -> icode().
%% icode_data_update(Icode, NewData) -> Icode#icode{data=NewData}.
-spec icode_var_range(icode()) -> {non_neg_integer(), non_neg_integer()}.
icode_var_range(#icode{var_range = VarRange}) -> VarRange.
-spec icode_label_range(icode()) -> {non_neg_integer(), non_neg_integer()}.
icode_label_range(#icode{label_range = LabelRange}) -> LabelRange.
-spec icode_info(icode()) -> icode_info().
icode_info(#icode{info = Info}) -> Info.
-spec icode_info_update(icode(), icode_info()) -> icode().
icode_info_update(Icode, Info) -> Icode#icode{info = Info}.
-spec icode_closure_arity(icode()) -> arity().
icode_closure_arity(#icode{closure_arity = Arity}) -> Arity.
-spec icode_closure_arity_update(icode(), arity()) -> icode().
icode_closure_arity_update(Icode, Arity) -> Icode#icode{closure_arity = Arity}.
%%----------------------------------------------------------------------------
%% Instructions
%%----------------------------------------------------------------------------
%%----
%% if
%%----
-spec mk_if(icode_if_op(), [icode_term_arg()],
icode_lbl(), icode_lbl()) -> #icode_if{}.
mk_if(Op, Args, TrueLbl, FalseLbl) ->
#icode_if{op=Op, args=Args, true_label=TrueLbl, false_label=FalseLbl, p=0.5}.
%% mk_if(Op, Args, TrueLbl, FalseLbl, P) ->
%% #icode_if{op=Op, args=Args, true_label=TrueLbl, false_label=FalseLbl, p=P}.
-spec if_op(#icode_if{}) -> icode_if_op().
if_op(#icode_if{op=Op}) -> Op.
-spec if_op_update(#icode_if{}, icode_if_op()) -> #icode_if{}.
if_op_update(IF, NewOp) -> IF#icode_if{op=NewOp}.
-spec if_args(#icode_if{}) -> [icode_term_arg()].
if_args(#icode_if{args=Args}) -> Args.
-spec if_args_update(#icode_if{}, [icode_term_arg()]) -> #icode_if{}.
if_args_update(IF, Args) -> IF#icode_if{args=Args}.
-spec if_true_label(#icode_if{}) -> icode_lbl().
if_true_label(#icode_if{true_label=TrueLbl}) -> TrueLbl.
-spec if_true_label_update(#icode_if{}, icode_lbl()) -> #icode_if{}.
if_true_label_update(IF, TrueLbl) -> IF#icode_if{true_label=TrueLbl}.
-spec if_false_label(#icode_if{}) -> icode_lbl().
if_false_label(#icode_if{false_label=FalseLbl}) -> FalseLbl.
-spec if_false_label_update(#icode_if{}, icode_lbl()) -> #icode_if{}.
if_false_label_update(IF, FalseLbl) -> IF#icode_if{false_label=FalseLbl}.
-spec if_pred(#icode_if{}) -> float().
if_pred(#icode_if{p=P}) -> P.
%%------------
%% switch_val
%%------------
-spec mk_switch_val(icode_var(), icode_lbl(),
non_neg_integer(), [icode_switch_case()]) ->
#icode_switch_val{}.
mk_switch_val(Term = #icode_variable{kind='var'}, FailLbl, Length, Cases) ->
#icode_switch_val{term=Term, fail_label=FailLbl, length=Length, cases=Cases}.
-spec switch_val_term(#icode_switch_val{}) -> icode_var().
switch_val_term(#icode_switch_val{term=Term}) -> Term.
-spec switch_val_fail_label(#icode_switch_val{}) -> icode_lbl().
switch_val_fail_label(#icode_switch_val{fail_label=FailLbl}) -> FailLbl.
-spec switch_val_fail_label_update(#icode_switch_val{}, icode_lbl()) ->
#icode_switch_val{}.
switch_val_fail_label_update(SV, FailLbl) ->
SV#icode_switch_val{fail_label=FailLbl}.
%% switch_val_length(#icode_switch_val{length=Length}) -> Length.
-spec switch_val_cases(#icode_switch_val{}) -> [icode_switch_case()].
switch_val_cases(#icode_switch_val{cases=Cases}) -> Cases.
-spec switch_val_cases_update(#icode_switch_val{}, [icode_switch_case()]) ->
#icode_switch_val{}.
switch_val_cases_update(SV, NewCases) ->
SV#icode_switch_val{cases = NewCases}.
%%--------------------
%% switch_tuple_arity
%%--------------------
-spec mk_switch_tuple_arity(icode_var(), icode_lbl(),
non_neg_integer(), [icode_switch_case()]) ->
#icode_switch_tuple_arity{}.
mk_switch_tuple_arity(Term = #icode_variable{kind='var'}, FailLbl, Length, Cases) ->
#icode_switch_tuple_arity{term=Term, fail_label=FailLbl,
length=Length, cases=Cases}.
-spec switch_tuple_arity_term(#icode_switch_tuple_arity{}) -> icode_var().
switch_tuple_arity_term(#icode_switch_tuple_arity{term=Term}) -> Term.
-spec switch_tuple_arity_fail_label(#icode_switch_tuple_arity{}) -> icode_lbl().
switch_tuple_arity_fail_label(#icode_switch_tuple_arity{fail_label=FailLbl}) ->
FailLbl.
-spec switch_tuple_arity_fail_label_update(#icode_switch_tuple_arity{}, icode_lbl()) ->
#icode_switch_tuple_arity{}.
switch_tuple_arity_fail_label_update(S, FailLbl) ->
S#icode_switch_tuple_arity{fail_label=FailLbl}.
%% switch_tuple_arity_length(#icode_switch_tuple_arity{length=Length}) -> Length.
-spec switch_tuple_arity_cases(#icode_switch_tuple_arity{}) -> [icode_switch_case()].
switch_tuple_arity_cases(#icode_switch_tuple_arity{cases=Cases}) -> Cases.
-spec switch_tuple_arity_cases_update(#icode_switch_tuple_arity{},
[icode_switch_case()]) ->
#icode_switch_tuple_arity{}.
switch_tuple_arity_cases_update(Cond, NewCases) ->
Cond#icode_switch_tuple_arity{cases = NewCases}.
%%------
%% type
%%------
-spec mk_type([icode_term_arg()], icode_type_test(), icode_lbl(), icode_lbl()) ->
#icode_type{}.
mk_type(Args, Test, TrueLbl, FalseLbl) ->
mk_type(Args, Test, TrueLbl, FalseLbl, 0.5).
-spec mk_type([icode_term_arg()], icode_type_test(),
icode_lbl(), icode_lbl(), float()) -> #icode_type{}.
mk_type(Args, Test, TrueLbl, FalseLbl, P) ->
#icode_type{test=Test, args=Args,
true_label=TrueLbl, false_label=FalseLbl, p=P}.
-spec type_test(#icode_type{}) -> icode_type_test().
type_test(#icode_type{test=Test}) -> Test.
-spec type_args(#icode_type{}) -> [icode_term_arg()].
type_args(#icode_type{args=Args}) -> Args.
%% type_args_update(T, Args) -> T#icode_type{args=Args}.
-spec type_true_label(#icode_type{}) -> icode_lbl().
type_true_label(#icode_type{true_label=TrueLbl}) -> TrueLbl.
-spec type_false_label(#icode_type{}) -> icode_lbl().
type_false_label(#icode_type{false_label=FalseLbl}) -> FalseLbl.
-spec type_pred(#icode_type{}) -> float().
type_pred(#icode_type{p=P}) -> P.
-spec is_type(icode_instr()) -> boolean().
is_type(#icode_type{}) -> true;
is_type(_) -> false.
%%------
%% goto
%%------
-spec mk_goto(icode_lbl()) -> #icode_goto{}.
mk_goto(Lbl) -> #icode_goto{label=Lbl}.
-spec goto_label(#icode_goto{}) -> icode_lbl().
goto_label(#icode_goto{label=Lbl}) -> Lbl.
-spec is_goto(icode_instr()) -> boolean().
is_goto(#icode_goto{}) -> true;
is_goto(_) -> false.
%%--------
%% return
%%--------
-spec mk_return([icode_var()]) -> #icode_return{}.
mk_return(Vars) -> #icode_return{vars=Vars}.
-spec return_vars(#icode_return{}) -> [icode_var()].
return_vars(#icode_return{vars=Vars}) -> Vars.
-spec is_return(icode_instr()) -> boolean().
is_return(#icode_return{}) -> true;
is_return(_) -> false.
%%------
%% fail
%%------
%% mk_fail(Args) when is_list(Args) -> mk_fail(Args, error).
-spec mk_fail([icode_term_arg()], icode_exit_class()) -> #icode_fail{}.
mk_fail(Args, Class) when is_list(Args) ->
case Class of
error -> ok;
exit -> ok;
rethrow -> ok;
throw -> ok
end,
#icode_fail{class=Class, args=Args}.
%% mk_fail(Args, Class, Label) when is_list(Args) ->
%% #icode_fail{class=Class, args=Args, fail_label=Label}.
-spec fail_class(#icode_fail{}) -> icode_exit_class().
fail_class(#icode_fail{class=Class}) -> Class.
-spec fail_args(#icode_fail{}) -> [icode_term_arg()].
fail_args(#icode_fail{args=Args}) -> Args.
-spec fail_label(#icode_fail{}) -> [] | icode_lbl().
fail_label(#icode_fail{fail_label=Label}) -> Label.
-spec fail_set_label(#icode_fail{}, [] | icode_lbl()) -> #icode_fail{}.
fail_set_label(I=#icode_fail{}, Label) ->
I#icode_fail{fail_label = Label}.
%%------
%% move
%%------
-spec mk_move(#icode_variable{}, #icode_variable{} | #icode_const{}) ->
#icode_move{}.
mk_move(Dst, Src) ->
case Src of
#icode_variable{} -> ok;
#icode_const{} -> ok
end,
#icode_move{dst=Dst, src=Src}.
-spec move_dst(#icode_move{}) -> #icode_variable{}.
move_dst(#icode_move{dst=Dst}) -> Dst.
-spec move_src(#icode_move{}) -> #icode_variable{} | #icode_const{}.
move_src(#icode_move{src=Src}) -> Src.
-spec move_src_update(#icode_move{}, #icode_variable{} | #icode_const{}) ->
#icode_move{}.
move_src_update(M, NewSrc) -> M#icode_move{src=NewSrc}.
-spec is_move(icode_instr()) -> boolean().
is_move(#icode_move{}) -> true;
is_move(_) -> false.
%%-----
%% phi
%%-----
%% The id field is not entirely redundant. It is used in mappings
%% in the SSA pass since the dst field can change.
-spec mk_phi(#icode_variable{}) -> #icode_phi{}.
mk_phi(Var) -> #icode_phi{dst=Var, id=Var, arglist=[]}.
-spec mk_phi(#icode_variable{}, [{icode_lbl(), #icode_variable{}}]) ->
#icode_phi{}.
mk_phi(Var, ArgList) -> #icode_phi{dst=Var, id=Var, arglist=ArgList}.
-spec phi_dst(#icode_phi{}) -> #icode_variable{}.
phi_dst(#icode_phi{dst=Dst}) -> Dst.
-spec phi_id(#icode_phi{}) -> #icode_variable{}.
phi_id(#icode_phi{id=Id}) -> Id.
-spec phi_arglist(#icode_phi{}) -> [{icode_lbl(), #icode_variable{}}].
phi_arglist(#icode_phi{arglist=ArgList}) -> ArgList.
-spec phi_args(#icode_phi{}) -> [#icode_variable{}].
phi_args(P) -> [Var || {_, Var} <- phi_arglist(P)].
-spec phi_arg(#icode_phi{}, icode_lbl()) -> #icode_variable{}.
phi_arg(P, Pred) ->
case lists:keyfind(Pred, 1, phi_arglist(P)) of
{_, Var} -> Var;
false -> exit({'No such predecessor to phi', {Pred, P}})
end.
-spec is_phi(icode_instr()) -> boolean().
is_phi(#icode_phi{}) -> true;
is_phi(_) -> false.
-spec phi_enter_pred(#icode_phi{}, icode_lbl(), #icode_variable{}) ->
#icode_phi{}.
phi_enter_pred(Phi, Pred, Var) ->
NewArg = {Pred, Var},
Phi#icode_phi{arglist=[NewArg|lists:keydelete(Pred, 1, phi_arglist(Phi))]}.
-spec phi_remove_pred(#icode_phi{}, icode_lbl()) -> #icode_move{} | #icode_phi{}.
phi_remove_pred(Phi, Pred) ->
NewArgList = lists:keydelete(Pred, 1, phi_arglist(Phi)),
case NewArgList of
[Arg] -> %% the Phi should be turned into an appropriate move instruction
{_Label, Var = #icode_variable{}} = Arg,
mk_move(phi_dst(Phi), Var);
[_|_] ->
Phi#icode_phi{arglist=NewArgList}
end.
phi_argvar_subst(P, Subst) ->
NewArgList = [{Pred, subst1(Subst, Var)} || {Pred,Var} <- phi_arglist(P)],
P#icode_phi{arglist=NewArgList}.
-spec phi_redirect_pred(#icode_phi{}, icode_lbl(), icode_lbl()) -> #icode_phi{}.
phi_redirect_pred(P, OldPred, NewPred) ->
Subst = [{OldPred, NewPred}],
NewArgList = [{subst1(Subst, Pred), Var} || {Pred,Var} <- phi_arglist(P)],
P#icode_phi{arglist=NewArgList}.
%%
%% primop and guardop
%%
%% Whether a function is a "primop" - i.e., an internal thing - or not,
%% is really only shown by its name. An {M,F,A} always represents a
%% function in some Erlang module (although it might be a BIF, and
%% could possibly be inline expanded). It is convenient to let the
%% constructor functions check the name and set the type automatically,
%% especially for guardops - some guardops are primitives and some are
%% MFA:s, and this way we won't have to rewrite all calls to mk_guardop
%% to flag whether they are primops or not.
-spec mk_primop([#icode_variable{}], icode_funcall(),
[icode_argument()]) -> #icode_call{}.
mk_primop(DstList, Fun, ArgList) ->
mk_primop(DstList, Fun, ArgList, [], []).
-spec mk_primop([#icode_variable{}], icode_funcall(),
[icode_argument()], [] | icode_lbl(), [] | icode_lbl()) ->
#icode_call{}.
mk_primop(DstList, Fun, ArgList, Continuation, Fail) ->
Type = op_type(Fun),
make_call(DstList, Fun, ArgList, Type, Continuation, Fail, false).
%% Note that a 'guardop' is just a call that occurred in a guard. In
%% this case, we should always have continuation labels True and False.
-spec mk_guardop([#icode_variable{}], icode_funcall(),
[icode_argument()], icode_lbl(), icode_lbl()) -> #icode_call{}.
mk_guardop(DstList, Fun, ArgList, True, False) ->
Type = op_type(Fun),
make_call(DstList, Fun, ArgList, Type, True, False, true).
op_type(Fun) ->
case is_mfa(Fun) of
true -> remote;
false -> primop
end.
is_mfa({M,F,A}) when is_atom(M), is_atom(F),
is_integer(A), 0 =< A, A =< 255 -> true;
is_mfa(_) -> false.
%%------
%% call
%%------
-spec mk_call([#icode_variable{}], atom(), atom(),
[icode_argument()], 'local' | 'remote') -> #icode_call{}.
mk_call(DstList, M, F, ArgList, Type) ->
mk_call(DstList, M, F, ArgList, Type, [], [], false).
%% mk_call(DstList, M, F, ArgList, Type, Continuation, Fail) ->
%% mk_call(DstList, M, F, ArgList, Type, Continuation, Fail, false).
-spec mk_call([#icode_variable{}], atom(), atom(), [icode_argument()],
'local' | 'remote', [] | icode_lbl(), [] | icode_lbl(), boolean()) ->
#icode_call{}.
mk_call(DstList, M, F, ArgList, Type, Continuation, Fail, InGuard)
when is_atom(M), is_atom(F) ->
case Type of
local -> ok;
remote -> ok
end,
Fun = {M,F,length(ArgList)},
make_call(DstList, Fun, ArgList, Type, Continuation, Fail, InGuard).
%% The common constructor for all calls (for internal use only)
%%
%% Note: If the "guard" flag is `true', it means that if the call fails,
%% we can simply jump to the Fail label (if it exists) without
%% generating any additional exception information - it isn't needed.
-spec make_call([#icode_variable{}], icode_funcall(), [icode_argument()],
icode_call_type(), [] | icode_lbl(), [] | icode_lbl(), boolean()) ->
#icode_call{}.
make_call(DstList, Fun, ArgList, Type, Continuation, Fail, InGuard) ->
#icode_call{dstlist=DstList, 'fun'=Fun, args=ArgList, type=Type,
continuation=Continuation, fail_label=Fail, in_guard=InGuard}.
-spec call_dstlist(#icode_call{}) -> [#icode_variable{}].
call_dstlist(#icode_call{dstlist=DstList}) -> DstList.
-spec call_dstlist_update(#icode_call{}, [#icode_variable{}]) -> #icode_call{}.
call_dstlist_update(C, Dest) -> C#icode_call{dstlist=Dest}.
-spec call_type(#icode_call{}) -> icode_call_type().
call_type(#icode_call{type=Type}) -> Type.
%% -spec call_dst_type(#icode_call{}) -> erl_type().
%% call_dst_type(#icode_call{dst_type=DstType}) -> DstType.
-spec call_args(#icode_call{}) -> [icode_argument()].
call_args(#icode_call{args=Args}) -> Args.
-spec call_args_update(#icode_call{}, [icode_argument()]) -> #icode_call{}.
call_args_update(C, Args) -> C#icode_call{args=Args}.
-spec call_fun(#icode_call{}) -> icode_funcall().
call_fun(#icode_call{'fun'=Fun}) -> Fun.
%% Note that updating the name field requires recomputing the call type,
%% in case it changes from a remote/local call to a primop call.
-spec call_fun_update(#icode_call{}, icode_funcall()) -> #icode_call{}.
call_fun_update(C, Fun) ->
Type = case is_mfa(Fun) of
true -> call_type(C);
false -> primop
end,
C#icode_call{'fun'=Fun, type=Type}.
-spec call_continuation(#icode_call{}) -> [] | icode_lbl().
call_continuation(#icode_call{continuation=Continuation}) -> Continuation.
-spec call_fail_label(#icode_call{}) -> [] | icode_lbl().
call_fail_label(#icode_call{fail_label=Fail}) -> Fail.
-spec call_set_continuation(#icode_call{}, [] | icode_lbl()) -> #icode_call{}.
call_set_continuation(I, Continuation) ->
I#icode_call{continuation = Continuation}.
-spec call_set_fail_label(#icode_call{}, [] | icode_lbl()) -> #icode_call{}.
call_set_fail_label(I=#icode_call{}, Fail) ->
case Fail of
[] ->
I#icode_call{fail_label=Fail, in_guard=false};
_ ->
I#icode_call{fail_label=Fail}
end.
-spec is_call(icode_instr()) -> boolean().
is_call(#icode_call{}) -> true;
is_call(_) -> false.
-spec call_in_guard(#icode_call{}) -> boolean().
call_in_guard(#icode_call{in_guard=InGuard}) -> InGuard.
%%-------
%% enter
%%-------
-spec mk_enter(atom(), atom(), [icode_term_arg()], 'local' | 'remote') ->
#icode_enter{}.
mk_enter(M, F, Args, Type) when is_atom(M), is_atom(F) ->
case Type of
local -> ok;
remote -> ok
end,
#icode_enter{'fun'={M,F,length(Args)}, args=Args, type=Type}.
-spec enter_fun(#icode_enter{}) -> icode_funcall().
enter_fun(#icode_enter{'fun'=Fun}) -> Fun.
-spec enter_fun_update(#icode_enter{}, icode_funcall()) ->
#icode_enter{}.
enter_fun_update(E, Fun) ->
Type = case is_mfa(Fun) of
true -> enter_type(E);
false -> primop
end,
E#icode_enter{'fun'=Fun, type=Type}.
-spec enter_args(#icode_enter{}) -> [icode_term_arg()].
enter_args(#icode_enter{args=Args}) -> Args.
-spec enter_args_update(#icode_enter{}, [icode_term_arg()]) -> #icode_enter{}.
enter_args_update(E, Args) -> E#icode_enter{args=Args}.
-spec enter_type(#icode_enter{}) -> icode_call_type().
enter_type(#icode_enter{type=Type}) -> Type.
-spec is_enter(icode_instr()) -> boolean().
is_enter(#icode_enter{}) -> true;
is_enter(_) -> false.
-spec mk_enter_primop(icode_primop(), [icode_term_arg()]) ->
#icode_enter{type::'primop'}.
mk_enter_primop(Op, Args) ->
#icode_enter{'fun'=Op, args=Args, type=primop}.
%%-----------
%% begin_try
%%-----------
%% The reason that begin_try is a branch instruction is just so that it
%% keeps the fail-to block linked into the CFG, until the exception
%% handling instructions are eliminated.
-spec mk_begin_try(icode_lbl(), icode_lbl()) -> #icode_begin_try{}.
mk_begin_try(Label, Successor) ->
#icode_begin_try{label=Label, successor=Successor}.
-spec begin_try_label(#icode_begin_try{}) -> icode_lbl().
begin_try_label(#icode_begin_try{label=Label}) -> Label.
-spec begin_try_successor(#icode_begin_try{}) -> icode_lbl().
begin_try_successor(#icode_begin_try{successor=Successor}) -> Successor.
%%---------
%% end_try
%%---------
-spec mk_end_try() -> #icode_end_try{}.
mk_end_try() -> #icode_end_try{}.
%%---------------
%% begin_handler
%%---------------
-spec mk_begin_handler([icode_var()]) -> #icode_begin_handler{}.
mk_begin_handler(Dstlist) ->
#icode_begin_handler{dstlist=Dstlist}.
-spec begin_handler_dstlist(#icode_begin_handler{}) -> [icode_var()].
begin_handler_dstlist(#icode_begin_handler{dstlist=Dstlist}) -> Dstlist.
%% -spec is_begin_handler(icode_instr()) -> boolean().
%% is_begin_handler(#icode_begin_handler{}) -> true;
%% is_begin_handler(_) -> false.
%%-------
%% label
%%-------
-spec mk_label(icode_lbl()) -> #icode_label{}.
mk_label(Name) when is_integer(Name), Name >= 0 -> #icode_label{name=Name}.
-spec label_name(#icode_label{}) -> icode_lbl().
label_name(#icode_label{name=Name}) -> Name.
-spec is_label(icode_instr()) -> boolean().
is_label(#icode_label{}) -> true;
is_label(_) -> false.
%%---------
%% comment
%%---------
-spec mk_comment(icode_comment_text()) -> #icode_comment{}.
%% @doc If `Txt' is a list of characters (possibly deep), it will be
%% printed as a string; otherwise, `Txt' will be printed as a term.
mk_comment(Txt) -> #icode_comment{text=Txt}.
-spec comment_text(#icode_comment{}) -> icode_comment_text().
comment_text(#icode_comment{text=Txt}) -> Txt.
-spec is_comment(icode_instr()) -> boolean().
is_comment(#icode_comment{}) -> true;
is_comment(_) -> false.
%%---------------------------------------------------------------------
%% Arguments (variables and constants)
%%---------------------------------------------------------------------
%%-------
%% const
%%-------
-spec mk_const(simple_const() | structured_const() | binary()) -> #icode_const{}.
mk_const(C) -> #icode_const{value=#flat{value=C}}.
-spec const_value(#icode_const{}) -> simple_const() | structured_const() | binary().
const_value(#icode_const{value=#flat{value=X}}) -> X.
-spec is_const(icode_argument()) -> boolean().
is_const(#icode_const{}) -> true;
is_const(_) -> false.
%%-----
%% var
%%-----
-spec mk_var(non_neg_integer()) -> #icode_variable{kind::'var'}.
mk_var(V) -> #icode_variable{name=V, kind=var}.
-spec var_name(#icode_variable{kind::'var'}) -> non_neg_integer().
var_name(#icode_variable{name=Name, kind=var}) -> Name.
-spec is_var(icode_argument()) -> boolean().
is_var(#icode_variable{kind=var}) -> true;
is_var(_) -> false.
-spec mk_reg(non_neg_integer()) -> #icode_variable{kind::'reg'}.
mk_reg(V) -> #icode_variable{name=V, kind=reg}.
-spec mk_reg_gcsafe(non_neg_integer()) -> #icode_variable{kind::'reg_gcsafe'}.
mk_reg_gcsafe(V) -> #icode_variable{name=V, kind=reg_gcsafe}.
-spec reg_name(#icode_variable{kind::'reg'|'reg_gcsafe'})
-> non_neg_integer().
reg_name(#icode_variable{name=Name, kind=reg}) -> Name;
reg_name(#icode_variable{name=Name, kind=reg_gcsafe}) -> Name.
-spec reg_is_gcsafe(#icode_variable{kind::'reg'}) -> 'false';
(#icode_variable{kind::'reg_gcsafe'}) -> 'true'.
reg_is_gcsafe(#icode_variable{kind=reg}) -> false;
reg_is_gcsafe(#icode_variable{kind=reg_gcsafe}) -> true.
-spec is_reg(icode_argument()) -> boolean().
is_reg(#icode_variable{kind=reg}) -> true;
is_reg(#icode_variable{kind=reg_gcsafe}) -> true;
is_reg(_) -> false.
-spec mk_fvar(non_neg_integer()) -> #icode_variable{kind::'fvar'}.
mk_fvar(V) -> #icode_variable{name=V, kind=fvar}.
-spec fvar_name(#icode_variable{kind::'fvar'}) -> non_neg_integer().
fvar_name(#icode_variable{name=Name, kind=fvar}) -> Name.
-spec is_fvar(icode_argument()) -> boolean().
is_fvar(#icode_variable{kind=fvar}) -> true;
is_fvar(_) -> false.
-spec is_variable(icode_argument()) -> boolean().
is_variable(#icode_variable{}) -> true;
is_variable(_) -> false.
-spec annotate_variable(#icode_variable{}, variable_annotation()) ->
#icode_variable{}.
annotate_variable(X, Anno) ->
X#icode_variable{annotation = Anno}.
-spec is_annotated_variable(icode_argument()) -> boolean().
is_annotated_variable(#icode_variable{annotation=[]}) ->
false;
is_annotated_variable(#icode_variable{}) ->
true;
is_annotated_variable(_) ->
false.
-spec unannotate_variable(#icode_variable{}) -> #icode_variable{}.
unannotate_variable(X) ->
X#icode_variable{annotation=[]}.
-spec variable_annotation(#icode_variable{}) -> variable_annotation().
variable_annotation(#icode_variable{annotation=Anno}) ->
Anno.
%%
%% Floating point Icode instructions.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Liveness info
%%
-spec uses(icode_instr()) -> [#icode_variable{}].
uses(Instr) ->
remove_constants(args(Instr)).
-spec args(icode_instr()) -> [icode_argument()].
args(I) ->
case I of
#icode_if{} -> if_args(I);
#icode_switch_val{} -> [switch_val_term(I)];
#icode_switch_tuple_arity{} -> [switch_tuple_arity_term(I)];
#icode_type{} -> type_args(I);
#icode_move{} -> [move_src(I)];
#icode_fail{} -> fail_args(I);
#icode_call{} -> call_args(I);
#icode_enter{} -> enter_args(I);
#icode_return{} -> return_vars(I);
#icode_phi{} -> phi_args(I);
#icode_goto{} -> [];
#icode_begin_try{} -> [];
#icode_begin_handler{} -> [];
#icode_end_try{} -> [];
#icode_comment{} -> [];
#icode_label{} -> []
end.
-spec defines(icode_instr()) -> [#icode_variable{}].
defines(I) ->
case I of
#icode_move{} -> remove_constants([move_dst(I)]);
#icode_call{} -> remove_constants(call_dstlist(I));
#icode_begin_handler{} -> remove_constants(begin_handler_dstlist(I));
#icode_phi{} -> remove_constants([phi_dst(I)]);
#icode_if{} -> [];
#icode_switch_val{} -> [];
#icode_switch_tuple_arity{} -> [];
#icode_type{} -> [];
#icode_goto{} -> [];
#icode_fail{} -> [];
#icode_enter{} -> [];
#icode_return{} -> [];
#icode_begin_try{} -> [];
#icode_end_try{} -> [];
#icode_comment{} -> [];
#icode_label{} -> []
end.
-spec remove_constants([icode_argument()]) -> [#icode_variable{}].
remove_constants(L) ->
[V || V <- L, (not is_const(V))].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Utilities
%%
%%
%% Substitution: replace occurrences of X by Y if {X,Y} is in the
%% Subst_list.
-spec subst([{_,_}], I) -> I when I :: icode_instr().
subst(Subst, I) ->
subst_defines(Subst, subst_uses(Subst, I)).
-spec subst_uses([{_,_}], I) -> I when I :: icode_instr().
subst_uses(Subst, I) ->
case I of
#icode_if{} -> I#icode_if{args = subst_list(Subst, if_args(I))};
#icode_switch_val{} ->
I#icode_switch_val{term = subst1(Subst, switch_val_term(I))};
#icode_switch_tuple_arity{} ->
I#icode_switch_tuple_arity{term = subst1(Subst, switch_tuple_arity_term(I))};
#icode_type{} -> I#icode_type{args = subst_list(Subst, type_args(I))};
#icode_move{} -> I#icode_move{src = subst1(Subst, move_src(I))};
#icode_fail{} -> I#icode_fail{args = subst_list(Subst, fail_args(I))};
#icode_call{} -> I#icode_call{args = subst_list(Subst, call_args(I))};
#icode_enter{} -> I#icode_enter{args = subst_list(Subst, enter_args(I))};
#icode_return{} -> I#icode_return{vars = subst_list(Subst, return_vars(I))};
#icode_phi{} -> phi_argvar_subst(I, Subst);
#icode_goto{} -> I;
#icode_begin_try{} -> I;
#icode_begin_handler{} -> I;
#icode_end_try{} -> I;
#icode_comment{} -> I;
#icode_label{} -> I
end.
-spec subst_defines([{_,_}], I) -> I when I :: icode_instr().
subst_defines(Subst, I) ->
case I of
#icode_move{} -> I#icode_move{dst = subst1(Subst, move_dst(I))};
#icode_call{} ->
I#icode_call{dstlist = subst_list(Subst, call_dstlist(I))};
#icode_begin_handler{} ->
I#icode_begin_handler{dstlist = subst_list(Subst,
begin_handler_dstlist(I))};
#icode_phi{} -> I#icode_phi{dst = subst1(Subst, phi_dst(I))};
#icode_if{} -> I;
#icode_switch_val{} -> I;
#icode_switch_tuple_arity{} -> I;
#icode_type{} -> I;
#icode_goto{} -> I;
#icode_fail{} -> I;
#icode_enter{} -> I;
#icode_return{} -> I;
#icode_begin_try{} -> I;
#icode_end_try{} -> I;
#icode_comment{} -> I;
#icode_label{} -> I
end.
subst_list(S, Is) ->
[subst1(S, I) || I <- Is].
subst1([], I) -> I;
subst1([{I,Y}|_], I) -> Y;
subst1([_|Pairs], I) -> subst1(Pairs, I).
%%
%% @doc Returns the successors of an Icode instruction.
%% In CFG form only branch instructions have successors,
%% but in linear form other instructions like e.g. moves
%% might be the last instruction of some basic block.
%%
-spec successors(icode_instr()) -> [icode_lbl()].
successors(I) ->
case I of
#icode_if{} ->
[if_true_label(I), if_false_label(I)];
#icode_goto{} ->
[goto_label(I)];
#icode_switch_val{} ->
CaseLabels = [L || {_,L} <- switch_val_cases(I)],
[switch_val_fail_label(I) | CaseLabels];
#icode_switch_tuple_arity{} ->
CaseLabels = [L || {_,L} <- switch_tuple_arity_cases(I)],
[switch_tuple_arity_fail_label(I) | CaseLabels];
#icode_type{} ->
[type_true_label(I), type_false_label(I)];
#icode_call{} ->
case call_continuation(I) of [] -> []; L when is_integer(L) -> [L] end
++
case call_fail_label(I) of [] -> []; L when is_integer(L) -> [L] end;
#icode_begin_try{} ->
[begin_try_successor(I), begin_try_label(I)];
#icode_fail{} ->
case fail_label(I) of [] -> []; L when is_integer(L) -> [L] end;
#icode_enter{} -> [];
#icode_return{} -> [];
#icode_comment{} -> [];
%% the following are included here for handling linear code
#icode_move{} -> [];
#icode_begin_handler{} -> []
end.
%%
%% @doc Returns the fail labels of an Icode instruction.
%%
-spec fails_to(icode_instr()) -> [icode_lbl()].
fails_to(I) ->
case I of
#icode_switch_val{} -> [switch_val_fail_label(I)];
#icode_switch_tuple_arity{} -> [switch_tuple_arity_fail_label(I)];
#icode_call{} ->
case call_fail_label(I) of [] -> []; L when is_integer(L) -> [L] end;
#icode_begin_try{} -> [begin_try_label(I)]; % just for safety
#icode_fail{} ->
case fail_label(I) of [] -> []; L when is_integer(L) -> [L] end;
#icode_if{} -> []; % XXX: Correct?
#icode_enter{} -> []; % XXX: Correct?
#icode_goto{} -> [];
#icode_type{} -> []; % XXX: Correct?
#icode_return{} -> []
end.
%%
%% @doc Redirects jumps from label Old to label New.
%% If the instruction does not jump to Old, it remains unchanged.
%% The New label can be the special [] label used for calls with
%% fall-throughs.
%%
-spec redirect_jmp(icode_instr(), icode_lbl(), [] | icode_lbl()) -> icode_instr().
redirect_jmp(Jmp, ToOld, ToOld) ->
Jmp; % no need to do anything
redirect_jmp(Jmp, ToOld, ToNew) ->
NewI =
case Jmp of
#icode_if{} ->
NewJmp = case if_true_label(Jmp) of
ToOld -> if_true_label_update(Jmp, ToNew);
_ -> Jmp
end,
case if_false_label(NewJmp) of
ToOld -> if_false_label_update(NewJmp, ToNew);
_ -> NewJmp
end;
#icode_goto{} ->
case goto_label(Jmp) of
ToOld -> Jmp#icode_goto{label=ToNew};
_ -> Jmp
end;
#icode_switch_val{} ->
NewJmp = case switch_val_fail_label(Jmp) of
ToOld -> switch_val_fail_label_update(Jmp, ToNew);
_ -> Jmp
end,
Cases = [case Pair of
{Val,ToOld} -> {Val,ToNew};
Unchanged -> Unchanged
end || Pair <- switch_val_cases(NewJmp)],
NewJmp#icode_switch_val{cases = Cases};
#icode_switch_tuple_arity{} ->
NewJmp = case switch_tuple_arity_fail_label(Jmp) of
ToOld ->
Jmp#icode_switch_tuple_arity{fail_label=ToNew};
_ -> Jmp
end,
Cases = [case Pair of
{Val,ToOld} -> {Val,ToNew};
Unchanged -> Unchanged
end || Pair <- switch_tuple_arity_cases(NewJmp)],
NewJmp#icode_switch_tuple_arity{cases = Cases};
#icode_type{} ->
NewJmp = case type_true_label(Jmp) of
ToOld -> Jmp#icode_type{true_label=ToNew};
_ -> Jmp
end,
case type_false_label(NewJmp) of
ToOld -> NewJmp#icode_type{false_label=ToNew};
_ -> NewJmp
end;
#icode_call{} ->
NewCont = case call_continuation(Jmp) of
ToOld -> ToNew;
OldCont -> OldCont
end,
NewFail = case call_fail_label(Jmp) of
ToOld -> ToNew;
OldFail -> OldFail
end,
Jmp#icode_call{continuation = NewCont,
fail_label = NewFail};
#icode_begin_try{} ->
NewLabl = case begin_try_label(Jmp) of
ToOld -> ToNew;
OldLab -> OldLab
end,
NewSucc = case begin_try_successor(Jmp) of
ToOld -> ToNew;
OldSucc -> OldSucc
end,
Jmp#icode_begin_try{label=NewLabl, successor=NewSucc};
#icode_fail{} ->
case fail_label(Jmp) of
ToOld -> Jmp#icode_fail{fail_label=ToNew};
_ -> Jmp
end
end,
%% Turn a branch into a goto if it has only one successor and it is
%% safe to do so.
case ordsets:from_list(successors(NewI)) of
[Label] ->
Goto = mk_goto(Label),
case NewI of
#icode_if{} -> Goto;
#icode_switch_tuple_arity{} -> Goto;
#icode_switch_val{} -> Goto;
#icode_type{} -> Goto;
_ -> NewI
end;
_ -> NewI
end.
%%
%% Is this an unconditional jump (causes a basic block not to have a
%% fallthrough successor).
%%
%% is_uncond(I) ->
%% case I of
%% #icode_goto{} -> true;
%% #icode_fail{} -> true;
%% #icode_enter{} -> true;
%% #icode_return{} -> true;
%% #icode_call{} ->
%% case call_fail_label(I) of
%% [] ->
%% case call_continuation(I) of
%% [] -> false;
%% _ -> true
%% end;
%% _ -> true
%% end;
%% _ -> false
%% end.
%% @spec is_branch(icode_instr()) -> boolean()
%%
%% @doc Succeeds if the Icode instruction is a branch. I.e. a
%% (possibly conditional) discontinuation of linear control flow.
%% @end
-spec is_branch(icode_instr()) -> boolean().
is_branch(Instr) ->
case Instr of
#icode_if{} -> true;
#icode_switch_val{} -> true;
#icode_switch_tuple_arity{} -> true;
#icode_type{} -> true;
#icode_goto{} -> true;
#icode_fail{} -> true;
#icode_call{} ->
case call_fail_label(Instr) of
[] -> call_continuation(Instr) =/= [];
_ -> true
end;
#icode_enter{} -> true;
#icode_return{} -> true;
#icode_begin_try{} -> true;
%% false cases below
#icode_move{} -> false;
#icode_begin_handler{} -> false;
#icode_end_try{} -> false;
#icode_comment{} -> false;
#icode_label{} -> false;
#icode_phi{} -> false
end.
%%
%% @doc Makes a new variable.
%%
-spec mk_new_var() -> icode_var().
mk_new_var() ->
mk_var(hipe_gensym:get_next_var(icode)).
%%
%% @doc Makes a new fp variable.
%%
-spec mk_new_fvar() -> icode_fvar().
mk_new_fvar() ->
mk_fvar(hipe_gensym:get_next_var(icode)).
%%
%% @doc Makes a new register.
%%
-spec mk_new_reg() -> icode_reg().
mk_new_reg() ->
mk_reg(hipe_gensym:get_next_var(icode)).
%%
%% @doc Makes a new gcsafe register; that is, a register that is allowed to be
%% live over calls and other operations that might cause GCs and thus move heap
%% data around.
%%
-spec mk_new_reg_gcsafe() -> icode_reg().
mk_new_reg_gcsafe() ->
mk_reg_gcsafe(hipe_gensym:get_next_var(icode)).
%%
%% @doc Makes a new label.
%%
-spec mk_new_label() -> #icode_label{}.
mk_new_label() ->
mk_label(hipe_gensym:get_next_label(icode)).
%% %%
%% %% @doc Makes a bunch of move operations.
%% %%
%%
%% -spec mk_moves([_], [_]) -> [#icode_move{}].
%% mk_moves([], []) ->
%% [];
%% mk_moves([X|Xs], [Y|Ys]) ->
%% [mk_move(X, Y) | mk_moves(Xs, Ys)].
%%
%% Makes a series of element operations.
%%
%% mk_elements(_, []) ->
%% [];
%% mk_elements(Tuple, [X|Xs]) ->
%% [mk_primop([X], #unsafe_element{index=length(Xs)+1}, [Tuple]) |
%% mk_elements(Tuple, Xs)].
%%
%% @doc Removes comments from Icode.
%%
-spec strip_comments(icode()) -> icode().
strip_comments(ICode) ->
icode_code_update(ICode, no_comments(icode_code(ICode))).
%% The following spec is underspecified: the resulting list does not
%% contain any #comment{} instructions
-spec no_comments(icode_instrs()) -> icode_instrs().
no_comments([]) ->
[];
no_comments([I|Xs]) ->
case is_comment(I) of
true -> no_comments(Xs);
false -> [I|no_comments(Xs)]
end.
%%-----------------------------------------------------------------------
%% @doc True if an Icode instruction is safe (can be removed if the
%% result is not used). Note that pure control flow instructions
%% cannot be regarded as safe, as they are not defining anything.
-spec is_safe(icode_instr()) -> boolean().
is_safe(Instr) ->
case Instr of
%% Instructions that are safe, or might be safe to remove.
#icode_move{} -> true;
#icode_phi{} -> true;
#icode_begin_handler{} -> true;
#icode_call{} ->
case call_fun(Instr) of
{M,F,A} ->
erl_bifs:is_safe(M,F,A);
Op ->
hipe_icode_primops:is_safe(Op)
end;
%% Control flow instructions.
#icode_if{} -> false;
#icode_switch_val{} -> false;
#icode_switch_tuple_arity{} -> false;
#icode_type{} -> false;
#icode_goto{} -> false;
#icode_label{} -> false;
%% Returning instructions without defines.
#icode_return{} -> false;
#icode_fail{} -> false;
#icode_enter{} -> false;
%% Internal auxiliary instructions that should not be removed
%% unless you really know what you are doing.
#icode_comment{} -> false;
#icode_begin_try{} -> false;
#icode_end_try{} -> false
end.
%% @doc Produces a simplified instruction sequence that is equivalent to [Instr]
%% under the assumption that all results of Instr are unused, or 'false' if
%% there is no such sequence (other than [Instr] itself).
-spec reduce_unused(icode_instr()) -> false | [icode_instr()].
reduce_unused(Instr) ->
case is_safe(Instr) of
true -> [];
false -> false
end.
%%-----------------------------------------------------------------------
-spec highest_var(icode_instrs()) -> non_neg_integer().
highest_var(Instrs) ->
highest_var(Instrs, 0).
-spec highest_var(icode_instrs(), non_neg_integer()) -> non_neg_integer().
highest_var([I|Is], Max) ->
Defs = defines(I),
Uses = uses(I),
highest_var(Is, new_max(Defs++Uses, Max));
highest_var([], Max) ->
Max.
-spec new_max([#icode_variable{}], non_neg_integer()) -> non_neg_integer().
new_max([V|Vs], Max) ->
VName =
case is_var(V) of
true ->
var_name(V);
false ->
case is_fvar(V) of
true ->
fvar_name(V);
_ ->
reg_name(V)
end
end,
new_max(Vs, erlang:max(VName, Max));
new_max([], Max) when is_integer(Max) ->
Max.
%%-----------------------------------------------------------------------
-spec highest_label(icode_instrs()) -> icode_lbl().
highest_label(Instrs) ->
highest_label(Instrs, 0).
-spec highest_label(icode_instrs(), icode_lbl()) -> icode_lbl().
highest_label([I|Is], Max) ->
case is_label(I) of
true ->
L = label_name(I),
NewMax = erlang:max(L, Max),
highest_label(Is, NewMax);
false ->
highest_label(Is, Max)
end;
highest_label([], Max) when is_integer(Max) ->
Max.
%%-----------------------------------------------------------------------