%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2018. 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%
%%
%% Purpose: Type definitions and utilities for the SSA format.
-module(beam_ssa).
-export([add_anno/3,get_anno/2,get_anno/3,
clobbers_xregs/1,def/2,def_unused/3,
definitions/1,
dominators/1,common_dominators/3,
flatmapfold_instrs_rpo/4,
fold_po/3,fold_po/4,fold_rpo/3,fold_rpo/4,
fold_instrs_rpo/4,
linearize/1,
mapfold_blocks_rpo/4,
mapfold_instrs_rpo/4,
normalize/1,
no_side_effect/1,
predecessors/1,
rename_vars/3,
rpo/1,rpo/2,
split_blocks/3,
successors/1,successors/2,
trim_unreachable/1,
update_phi_labels/4,used/1,
uses/1,uses/2]).
-export_type([b_module/0,b_function/0,b_blk/0,b_set/0,
b_ret/0,b_br/0,b_switch/0,terminator/0,
b_var/0,b_literal/0,b_remote/0,b_local/0,
value/0,argument/0,label/0,
var_name/0,var_base/0,literal_value/0,
op/0,anno/0,block_map/0,dominator_map/0,
rename_map/0,rename_proplist/0,usage_map/0,
definition_map/0]).
-include("beam_ssa.hrl").
-type b_module() :: #b_module{}.
-type b_function() :: #b_function{}.
-type b_blk() :: #b_blk{}.
-type b_set() :: #b_set{}.
-type b_br() :: #b_br{}.
-type b_ret() :: #b_ret{}.
-type b_switch() :: #b_switch{}.
-type terminator() :: b_br() | b_ret() | b_switch().
-type construct() :: b_module() | b_function() | b_blk() | b_set() |
terminator().
-type b_var() :: #b_var{}.
-type b_literal() :: #b_literal{}.
-type b_remote() :: #b_remote{}.
-type b_local() :: #b_local{}.
-type value() :: b_var() | b_literal().
-type phi_value() :: {value(),label()}.
-type argument() :: value() | b_remote() | b_local() | phi_value().
-type label() :: non_neg_integer().
-type var_name() :: var_base() | {var_base(),non_neg_integer()}.
-type var_base() :: atom() | non_neg_integer().
-type literal_value() :: atom() | integer() | float() | list() |
nil() | tuple() | map() | binary() | fun().
-type op() :: {'bif',atom()} | {'float',float_op()} | prim_op() | cg_prim_op().
-type anno() :: #{atom() := any()}.
-type block_map() :: #{label():=b_blk()}.
-type dominator_map() :: #{label():=[label()]}.
-type numbering_map() :: #{label():=non_neg_integer()}.
-type usage_map() :: #{b_var():=[{label(),b_set() | terminator()}]}.
-type definition_map() :: #{b_var():=b_set()}.
-type rename_map() :: #{b_var():=value()}.
-type rename_proplist() :: [{b_var(),value()}].
%% Note: By default, dialyzer will collapse this type to atom().
%% To avoid the collapsing, change the value of SET_LIMIT to 50 in the
%% file erl_types.erl in the hipe application.
-type prim_op() :: 'bs_add' | 'bs_extract' | 'bs_get_tail' |
'bs_init' | 'bs_init_writable' |
'bs_match' | 'bs_put' | 'bs_start_match' | 'bs_test_tail' |
'bs_utf16_size' | 'bs_utf8_size' | 'build_stacktrace' |
'call' | 'catch_end' |
'extract' | 'exception_trampoline' |
'get_hd' | 'get_map_element' | 'get_tl' | 'get_tuple_element' |
'has_map_field' |
'is_nonempty_list' | 'is_tagged_tuple' |
'kill_try_tag' |
'landingpad' |
'make_fun' | 'new_try_tag' |
'peek_message' | 'phi' | 'put_list' | 'put_map' | 'put_tuple' |
'raw_raise' | 'recv_next' | 'remove_message' | 'resume' |
'succeeded' |
'timeout' |
'wait' | 'wait_timeout'.
-type float_op() :: 'checkerror' | 'clearerror' | 'convert' | 'get' | 'put' |
'+' | '-' | '*' | '/'.
%% Primops only used internally during code generation.
-type cg_prim_op() :: 'bs_get' | 'bs_get_position' | 'bs_match_string' |
'bs_restore' | 'bs_save' | 'bs_set_position' | 'bs_skip' |
'copy' | 'match_fail' | 'put_tuple_arity' |
'put_tuple_element' | 'put_tuple_elements' |
'set_tuple_element'.
-import(lists, [foldl/3,keyfind/3,mapfoldl/3,member/2,reverse/1]).
-spec add_anno(Key, Value, Construct) -> Construct when
Key :: atom(),
Value :: any(),
Construct :: construct().
add_anno(Key, Val, #b_function{anno=Anno}=Bl) ->
Bl#b_function{anno=Anno#{Key=>Val}};
add_anno(Key, Val, #b_blk{anno=Anno}=Bl) ->
Bl#b_blk{anno=Anno#{Key=>Val}};
add_anno(Key, Val, #b_set{anno=Anno}=Bl) ->
Bl#b_set{anno=Anno#{Key=>Val}};
add_anno(Key, Val, #b_br{anno=Anno}=Bl) ->
Bl#b_br{anno=Anno#{Key=>Val}};
add_anno(Key, Val, #b_ret{anno=Anno}=Bl) ->
Bl#b_ret{anno=Anno#{Key=>Val}};
add_anno(Key, Val, #b_switch{anno=Anno}=Bl) ->
Bl#b_switch{anno=Anno#{Key=>Val}}.
-spec get_anno(atom(), construct()) -> any().
get_anno(Key, Construct) ->
map_get(Key, get_anno(Construct)).
-spec get_anno(atom(), construct(),any()) -> any().
get_anno(Key, Construct, Default) ->
maps:get(Key, get_anno(Construct), Default).
get_anno(#b_function{anno=Anno}) -> Anno;
get_anno(#b_blk{anno=Anno}) -> Anno;
get_anno(#b_set{anno=Anno}) -> Anno;
get_anno(#b_br{anno=Anno}) -> Anno;
get_anno(#b_ret{anno=Anno}) -> Anno;
get_anno(#b_switch{anno=Anno}) -> Anno.
%% clobbers_xregs(#b_set{}) -> true|false.
%% Test whether the instruction invalidates all X registers.
-spec clobbers_xregs(b_set()) -> boolean().
clobbers_xregs(#b_set{op=Op}) ->
case Op of
bs_init_writable -> true;
build_stacktrace -> true;
call -> true;
landingpad -> true;
make_fun -> true;
peek_message -> true;
raw_raise -> true;
_ -> false
end.
%% no_side_effect(#b_set{}) -> true|false.
%% Test whether this instruction has no side effect and thus is safe
%% not to execute if its value is not used. Note that even if `true`
%% is returned, the instruction could still be impure (e.g. bif:get).
-spec no_side_effect(b_set()) -> boolean().
no_side_effect(#b_set{op=Op}) ->
case Op of
{bif,_} -> true;
{float,get} -> true;
bs_init -> true;
bs_extract -> true;
bs_match -> true;
bs_start_match -> true;
bs_test_tail -> true;
bs_get_tail -> true;
bs_put -> true;
extract -> true;
get_hd -> true;
get_tl -> true;
get_map_element -> true;
get_tuple_element -> true;
has_map_field -> true;
is_nonempty_list -> true;
is_tagged_tuple -> true;
make_fun -> true;
put_map -> true;
put_list -> true;
put_tuple -> true;
succeeded -> true;
_ -> false
end.
-spec predecessors(Blocks) -> #{BlockNumber:=[Predecessor]} when
Blocks :: block_map(),
BlockNumber :: label(),
Predecessor :: label().
predecessors(Blocks) ->
P0 = [{S,L} || {L,Blk} <- maps:to_list(Blocks),
S <- successors(Blk)],
P1 = sofs:relation(P0),
P2 = sofs:rel2fam(P1),
P3 = sofs:to_external(P2),
P = [{0,[]}|P3],
maps:from_list(P).
-spec successors(b_blk()) -> [label()].
successors(#b_blk{last=Terminator}) ->
case Terminator of
#b_br{bool=#b_literal{val=true},succ=Succ} ->
[Succ];
#b_br{bool=#b_literal{val=false},fail=Fail} ->
[Fail];
#b_br{succ=Succ,fail=Fail} ->
[Fail,Succ];
#b_switch{fail=Fail,list=List} ->
[Fail|[L || {_,L} <- List]];
#b_ret{} ->
[]
end.
%% normalize(Instr0) -> Instr.
%% Normalize instructions to help optimizations.
%%
%% For commutative operators (such as '+' and 'or'), always
%% place a variable operand before a literal operand.
%%
%% Normalize #b_br{} to one of the following forms:
%%
%% #b_br{b_literal{val=true},succ=Label,fail=Label}
%% #b_br{b_var{},succ=Label1,fail=Label2} where Label1 =/= Label2
%%
%% Simplify a #b_switch{} with a literal argument to a #b_br{}.
%%
%% Simplify a #b_switch{} with a variable argument and an empty
%% switch list to a #b_br{}.
-spec normalize(b_set() | terminator()) ->
b_set() | terminator().
normalize(#b_set{op={bif,Bif},args=Args}=Set) ->
case {is_commutative(Bif),Args} of
{false,_} ->
Set;
{true,[#b_literal{}=Lit,#b_var{}=Var]} ->
Set#b_set{args=[Var,Lit]};
{true,_} ->
Set
end;
normalize(#b_set{}=Set) ->
Set;
normalize(#b_br{}=Br) ->
case Br of
#b_br{bool=Bool,succ=Same,fail=Same} ->
case Bool of
#b_literal{val=true} ->
Br;
_ ->
Br#b_br{bool=#b_literal{val=true}}
end;
#b_br{bool=#b_literal{val=true},succ=Succ} ->
Br#b_br{fail=Succ};
#b_br{bool=#b_literal{val=false},fail=Fail} ->
Br#b_br{bool=#b_literal{val=true},succ=Fail};
#b_br{} ->
Br
end;
normalize(#b_switch{arg=Arg,fail=Fail,list=List}=Sw) ->
case Arg of
#b_literal{} ->
case keyfind(Arg, 1, List) of
false ->
#b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail};
{Arg,L} ->
#b_br{bool=#b_literal{val=true},succ=L,fail=L}
end;
#b_var{} when List =:= [] ->
#b_br{bool=#b_literal{val=true},succ=Fail,fail=Fail};
#b_var{} ->
Sw
end;
normalize(#b_ret{}=Ret) ->
Ret.
-spec successors(label(), block_map()) -> [label()].
successors(L, Blocks) ->
successors(map_get(L, Blocks)).
-spec def(Ls, Blocks) -> Def when
Ls :: [label()],
Blocks :: block_map(),
Def :: ordsets:ordset(var_name()).
def(Ls, Blocks) ->
Top = rpo(Ls, Blocks),
Blks = [map_get(L, Blocks) || L <- Top],
def_1(Blks, []).
-spec def_unused(Ls, Used, Blocks) -> {Def,Unused} when
Ls :: [label()],
Used :: ordsets:ordset(var_name()),
Blocks :: block_map(),
Def :: ordsets:ordset(var_name()),
Unused :: ordsets:ordset(var_name()).
def_unused(Ls, Unused, Blocks) ->
Top = rpo(Ls, Blocks),
Blks = [map_get(L, Blocks) || L <- Top],
Preds = cerl_sets:from_list(Top),
def_unused_1(Blks, Preds, [], Unused).
%% dominators(BlockMap) -> {Dominators,Numbering}.
%% Calculate the dominator tree, returning a map where each entry
%% in the map is a list that gives the path from that block to
%% the top of the dominator tree. (Note that the suffixes of the
%% paths are shared with each other, which make the representation
%% of the dominator tree highly memory-efficient.)
%%
%% The implementation is based on:
%%
%% http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
%% Cooper, Keith D.; Harvey, Timothy J; Kennedy, Ken (2001).
%% A Simple, Fast Dominance Algorithm.
-spec dominators(Blocks) -> Result when
Blocks :: block_map(),
Result :: {dominator_map(), numbering_map()}.
dominators(Blocks) ->
Preds = predecessors(Blocks),
Top0 = rpo(Blocks),
Df = maps:from_list(number(Top0, 0)),
[{0,[]}|Top] = [{L,map_get(L, Preds)} || L <- Top0],
%% The flow graph for an Erlang function is reducible, and
%% therefore one traversal in reverse postorder is sufficient.
Acc = #{0=>[0]},
{dominators_1(Top, Df, Acc),Df}.
%% common_dominators([Label], Dominators, Numbering) -> [Label].
%% Calculate the common dominators for the given list of blocks
%% and Dominators and Numbering as returned from dominators/1.
-spec common_dominators([label()], dominator_map(), numbering_map()) -> [label()].
common_dominators(Ls, Dom, Numbering) ->
Doms = [map_get(L, Dom) || L <- Ls],
dom_intersection(Doms, Numbering).
-spec fold_instrs_rpo(Fun, From, Acc0, Blocks) -> any() when
Fun :: fun((b_blk()|terminator(), any()) -> any()),
From :: [label()],
Acc0 :: any(),
Blocks :: block_map().
fold_instrs_rpo(Fun, From, Acc0, Blocks) ->
Top = rpo(From, Blocks),
fold_instrs_rpo_1(Top, Fun, Blocks, Acc0).
%% Like mapfold_instrs_rpo but at the block level to support lookahead and
%% scope-dependent transformations.
-spec mapfold_blocks_rpo(Fun, From, Acc, Blocks) -> Result when
Fun :: fun((label(), b_blk(), any()) -> {b_blk(), any()}),
From :: [label()],
Acc :: any(),
Blocks :: block_map(),
Result :: {block_map(), any()}.
mapfold_blocks_rpo(Fun, From, Acc, Blocks) ->
Successors = rpo(From, Blocks),
foldl(fun(Lbl, A) ->
mapfold_blocks_rpo_1(Fun, Lbl, A)
end, {Blocks, Acc}, Successors).
mapfold_blocks_rpo_1(Fun, Lbl, {Blocks0, Acc0}) ->
Block0 = map_get(Lbl, Blocks0),
{Block, Acc} = Fun(Lbl, Block0, Acc0),
Blocks = Blocks0#{Lbl:=Block},
{Blocks, Acc}.
-spec mapfold_instrs_rpo(Fun, From, Acc0, Blocks0) -> {Blocks,Acc} when
Fun :: fun((b_blk()|terminator(), any()) -> any()),
From :: [label()],
Acc0 :: any(),
Acc :: any(),
Blocks0 :: block_map(),
Blocks :: block_map().
mapfold_instrs_rpo(Fun, From, Acc0, Blocks) ->
Top = rpo(From, Blocks),
mapfold_instrs_rpo_1(Top, Fun, Blocks, Acc0).
-spec flatmapfold_instrs_rpo(Fun, From, Acc0, Blocks0) -> {Blocks,Acc} when
Fun :: fun((b_blk()|terminator(), any()) -> any()),
From :: [label()],
Acc0 :: any(),
Acc :: any(),
Blocks0 :: block_map(),
Blocks :: block_map().
flatmapfold_instrs_rpo(Fun, From, Acc0, Blocks) ->
Top = rpo(From, Blocks),
flatmapfold_instrs_rpo_1(Top, Fun, Blocks, Acc0).
-type fold_fun() :: fun((label(), b_blk(), any()) -> any()).
%% fold_rpo(Fun, [Label], Acc0, Blocks) -> Acc.
%% Fold over all blocks a reverse postorder traversal of the block
%% graph; that is, first visit a block, then visit its successors.
-spec fold_rpo(Fun, Acc0, Blocks) -> any() when
Fun :: fold_fun(),
Acc0 :: any(),
Blocks :: #{label():=b_blk()}.
fold_rpo(Fun, Acc0, Blocks) ->
fold_rpo(Fun, [0], Acc0, Blocks).
%% fold_rpo(Fun, [Label], Acc0, Blocks) -> Acc. Fold over all blocks
%% reachable from a given set of labels in a reverse postorder
%% traversal of the block graph; that is, first visit a block, then
%% visit its successors.
-spec fold_rpo(Fun, Labels, Acc0, Blocks) -> any() when
Fun :: fold_fun(),
Labels :: [label()],
Acc0 :: any(),
Blocks :: #{label():=b_blk()}.
fold_rpo(Fun, From, Acc0, Blocks) ->
Top = rpo(From, Blocks),
fold_rpo_1(Top, Fun, Blocks, Acc0).
%% fold_po(Fun, Acc0, Blocks) -> Acc.
%% Fold over all blocks in a postorder traversal of the block graph;
%% that is, first visit all successors of block, then the block
%% itself.
-spec fold_po(Fun, Acc0, Blocks) -> any() when
Fun :: fold_fun(),
Acc0 :: any(),
Blocks :: #{label():=b_blk()}.
%% fold_po(Fun, From, Acc0, Blocks) -> Acc.
%% Fold over the blocks reachable from the block numbers given
%% by From in a postorder traversal of the block graph.
fold_po(Fun, Acc0, Blocks) ->
fold_po(Fun, [0], Acc0, Blocks).
-spec fold_po(Fun, Labels, Acc0, Blocks) -> any() when
Fun :: fold_fun(),
Labels :: [label()],
Acc0 :: any(),
Blocks :: block_map().
fold_po(Fun, From, Acc0, Blocks) ->
Top = reverse(rpo(From, Blocks)),
fold_rpo_1(Top, Fun, Blocks, Acc0).
%% linearize(Blocks) -> [{BlockLabel,#b_blk{}}].
%% Linearize the intermediate representation of the code.
%% Unreachable blocks will be discarded, and phi nodes will
%% be adjusted so that they no longer refers to discarded
%% blocks or to blocks that no longer are predecessors of
%% the phi node block.
-spec linearize(Blocks) -> Linear when
Blocks :: block_map(),
Linear :: [{label(),b_blk()}].
linearize(Blocks) ->
Seen = cerl_sets:new(),
{Linear0,_} = linearize_1([0], Blocks, Seen, []),
Linear = fix_phis(Linear0, #{}),
Linear.
-spec rpo(Blocks) -> [Label] when
Blocks :: block_map(),
Label :: label().
rpo(Blocks) ->
rpo([0], Blocks).
-spec rpo(From, Blocks) -> Labels when
From :: [label()],
Blocks :: block_map(),
Labels :: [label()].
rpo(From, Blocks) ->
Seen = cerl_sets:new(),
{Ls,_} = rpo_1(From, Blocks, Seen, []),
Ls.
-spec rename_vars(Rename, [label()], block_map()) -> block_map() when
Rename :: rename_map() | rename_proplist().
rename_vars(Rename, From, Blocks) when is_list(Rename) ->
rename_vars(maps:from_list(Rename), From, Blocks);
rename_vars(Rename, From, Blocks) when is_map(Rename)->
Top = rpo(From, Blocks),
Preds = cerl_sets:from_list(Top),
F = fun(#b_set{op=phi,args=Args0}=Set) ->
Args = rename_phi_vars(Args0, Preds, Rename),
Set#b_set{args=Args};
(#b_set{args=Args0}=Set) ->
Args = [rename_var(A, Rename) || A <- Args0],
Set#b_set{args=Args};
(#b_switch{arg=Bool}=Sw) ->
Sw#b_switch{arg=rename_var(Bool, Rename)};
(#b_br{bool=Bool}=Br) ->
Br#b_br{bool=rename_var(Bool, Rename)};
(#b_ret{arg=Arg}=Ret) ->
Ret#b_ret{arg=rename_var(Arg, Rename)}
end,
map_instrs_1(Top, F, Blocks).
%% split_blocks(Predicate, Blocks0, Count0) -> {Blocks,Count}.
%% Call Predicate(Instruction) for each instruction in all
%% blocks. If Predicate/1 returns true, split the block
%% before this instruction.
-spec split_blocks(Pred, Blocks0, Count0) -> {Blocks,Count} when
Pred :: fun((b_set()) -> boolean()),
Blocks :: block_map(),
Count0 :: beam_ssa:label(),
Blocks0 :: block_map(),
Blocks :: block_map(),
Count :: beam_ssa:label().
split_blocks(P, Blocks, Count) ->
Ls = beam_ssa:rpo(Blocks),
split_blocks_1(Ls, P, Blocks, Count).
-spec trim_unreachable(Blocks0) -> Blocks when
Blocks0 :: block_map(),
Blocks :: block_map().
%% trim_unreachable(Blocks0) -> Blocks.
%% Remove all unreachable blocks. Adjust all phi nodes so
%% they don't refer to blocks that has been removed or no
%% no longer branch to the phi node in question.
trim_unreachable(Blocks) ->
%% Could perhaps be optimized if there is any need.
maps:from_list(linearize(Blocks)).
%% update_phi_labels([BlockLabel], Old, New, Blocks0) -> Blocks.
%% In the given blocks, replace label Old in with New in all
%% phi nodes. This is useful after merging or splitting
%% blocks.
-spec update_phi_labels(From, Old, New, Blocks0) -> Blocks when
From :: [label()],
Old :: label(),
New :: label(),
Blocks0 :: block_map(),
Blocks :: block_map().
update_phi_labels([L|Ls], Old, New, Blocks0) ->
case Blocks0 of
#{L:=#b_blk{is=[#b_set{op=phi}|_]=Is0}=Blk0} ->
Is = update_phi_labels_is(Is0, Old, New),
Blk = Blk0#b_blk{is=Is},
Blocks = Blocks0#{L:=Blk},
update_phi_labels(Ls, Old, New, Blocks);
#{L:=#b_blk{}} ->
%% No phi nodes in this block.
update_phi_labels(Ls, Old, New, Blocks0)
end;
update_phi_labels([], _, _, Blocks) -> Blocks.
-spec used(b_blk() | b_set() | terminator()) -> [var_name()].
used(#b_blk{is=Is,last=Last}) ->
used_1([Last|Is], ordsets:new());
used(#b_br{bool=#b_var{}=V}) ->
[V];
used(#b_ret{arg=#b_var{}=V}) ->
[V];
used(#b_set{op=phi,args=Args}) ->
ordsets:from_list([V || {#b_var{}=V,_} <- Args]);
used(#b_set{args=Args}) ->
ordsets:from_list(used_args(Args));
used(#b_switch{arg=#b_var{}=V}) ->
[V];
used(_) -> [].
-spec definitions(Blocks :: block_map()) -> definition_map().
definitions(Blocks) ->
fold_instrs_rpo(fun(#b_set{ dst = Var }=I, Acc) ->
Acc#{Var => I};
(_Terminator, Acc) ->
Acc
end, [0], #{}, Blocks).
-spec uses(Blocks :: block_map()) -> usage_map().
uses(Blocks) ->
uses([0], Blocks).
-spec uses(From, Blocks) -> usage_map() when
From :: [label()],
Blocks :: block_map().
uses(From, Blocks) ->
fold_rpo(fun fold_uses_block/3, From, #{}, Blocks).
fold_uses_block(Lbl, #b_blk{is=Is,last=Last}, UseMap0) ->
F = fun(I, UseMap) ->
foldl(fun(Var, Acc) ->
Uses0 = maps:get(Var, Acc, []),
Uses = [{Lbl, I} | Uses0],
maps:put(Var, Uses, Acc)
end, UseMap, used(I))
end,
F(Last, foldl(F, UseMap0, Is)).
%%%
%%% Internal functions.
%%%
is_commutative('and') -> true;
is_commutative('or') -> true;
is_commutative('xor') -> true;
is_commutative('band') -> true;
is_commutative('bor') -> true;
is_commutative('bxor') -> true;
is_commutative('+') -> true;
is_commutative('*') -> true;
is_commutative('=:=') -> true;
is_commutative('==') -> true;
is_commutative('=/=') -> true;
is_commutative('/=') -> true;
is_commutative(_) -> false.
def_unused_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, Unused0) ->
Unused1 = ordsets:subtract(Unused0, used(Last)),
{Def,Unused} = def_unused_is(Is, Preds, Def0, Unused1),
def_unused_1(Bs, Preds, Def, Unused);
def_unused_1([], _Preds, Def, Unused) ->
{ordsets:from_list(Def), Unused}.
def_unused_is([#b_set{op=phi,dst=Dst,args=Args}|Is],
Preds, Def0, Unused0) ->
Def = [Dst|Def0],
%% We must be careful to only include variables that will
%% be used when arriving from one of the predecessor blocks
%% in Preds.
Unused1 = [V || {#b_var{}=V,L} <- Args, cerl_sets:is_element(L, Preds)],
Unused = ordsets:subtract(Unused0, ordsets:from_list(Unused1)),
def_unused_is(Is, Preds, Def, Unused);
def_unused_is([#b_set{dst=Dst}=I|Is], Preds, Def0, Unused0) ->
Def = [Dst|Def0],
Unused = ordsets:subtract(Unused0, used(I)),
def_unused_is(Is, Preds, Def, Unused);
def_unused_is([], _Preds, Def, Unused) ->
{Def,Unused}.
def_1([#b_blk{is=Is}|Bs], Def0) ->
Def = def_is(Is, Def0),
def_1(Bs, Def);
def_1([], Def) ->
ordsets:from_list(Def).
def_is([#b_set{dst=Dst}|Is], Def) ->
def_is(Is, [Dst|Def]);
def_is([], Def) -> Def.
dominators_1([{L,Preds}|Ls], Df, Doms) ->
DomPreds = [map_get(P, Doms) || P <- Preds, is_map_key(P, Doms)],
Dom = [L|dom_intersection(DomPreds, Df)],
dominators_1(Ls, Df, Doms#{L=>Dom});
dominators_1([], _Df, Doms) -> Doms.
dom_intersection([S], _Df) ->
S;
dom_intersection([S|Ss], Df) ->
dom_intersection(S, Ss, Df).
dom_intersection(S1, [S2|Ss], Df) ->
dom_intersection(dom_intersection_1(S1, S2, Df), Ss, Df);
dom_intersection(S, [], _Df) -> S.
dom_intersection_1([E1|Es1]=Set1, [E2|Es2]=Set2, Df) ->
%% Blocks are numbered in the order they are found in
%% reverse postorder.
#{E1:=Df1,E2:=Df2} = Df,
if Df1 > Df2 ->
dom_intersection_1(Es1, Set2, Df);
Df2 > Df1 ->
dom_intersection_1(Es2, Set1, Df); %switch arguments!
true -> %Set1 == Set2
%% The common suffix of the sets is the intersection.
Set1
end.
number([L|Ls], N) ->
[{L,N}|number(Ls, N+1)];
number([], _) -> [].
fold_rpo_1([L|Ls], Fun, Blocks, Acc0) ->
Block = map_get(L, Blocks),
Acc = Fun(L, Block, Acc0),
fold_rpo_1(Ls, Fun, Blocks, Acc);
fold_rpo_1([], _, _, Acc) -> Acc.
fold_instrs_rpo_1([L|Ls], Fun, Blocks, Acc0) ->
#b_blk{is=Is,last=Last} = map_get(L, Blocks),
Acc1 = foldl(Fun, Acc0, Is),
Acc = Fun(Last, Acc1),
fold_instrs_rpo_1(Ls, Fun, Blocks, Acc);
fold_instrs_rpo_1([], _, _, Acc) -> Acc.
mapfold_instrs_rpo_1([L|Ls], Fun, Blocks0, Acc0) ->
#b_blk{is=Is0,last=Last0} = Block0 = map_get(L, Blocks0),
{Is,Acc1} = mapfoldl(Fun, Acc0, Is0),
{Last,Acc} = Fun(Last0, Acc1),
Block = Block0#b_blk{is=Is,last=Last},
Blocks = Blocks0#{L:=Block},
mapfold_instrs_rpo_1(Ls, Fun, Blocks, Acc);
mapfold_instrs_rpo_1([], _, Blocks, Acc) ->
{Blocks,Acc}.
flatmapfold_instrs_rpo_1([L|Ls], Fun, Blocks0, Acc0) ->
#b_blk{is=Is0,last=Last0} = Block0 = map_get(L, Blocks0),
{Is,Acc1} = flatmapfoldl(Fun, Acc0, Is0),
{[Last],Acc} = Fun(Last0, Acc1),
Block = Block0#b_blk{is=Is,last=Last},
Blocks = Blocks0#{L:=Block},
flatmapfold_instrs_rpo_1(Ls, Fun, Blocks, Acc);
flatmapfold_instrs_rpo_1([], _, Blocks, Acc) ->
{Blocks,Acc}.
linearize_1([L|Ls], Blocks, Seen0, Acc0) ->
case cerl_sets:is_element(L, Seen0) of
true ->
linearize_1(Ls, Blocks, Seen0, Acc0);
false ->
Seen1 = cerl_sets:add_element(L, Seen0),
Block = map_get(L, Blocks),
Successors = successors(Block),
{Acc,Seen} = linearize_1(Successors, Blocks, Seen1, Acc0),
linearize_1(Ls, Blocks, Seen, [{L,Block}|Acc])
end;
linearize_1([], _, Seen, Acc) ->
{Acc,Seen}.
fix_phis([{L,Blk0}|Bs], S) ->
Blk = case Blk0 of
#b_blk{is=[#b_set{op=phi}|_]=Is0} ->
Is = fix_phis_1(Is0, L, S),
Blk0#b_blk{is=Is};
#b_blk{} ->
Blk0
end,
Successors = successors(Blk),
[{L,Blk}|fix_phis(Bs, S#{L=>Successors})];
fix_phis([], _) -> [].
fix_phis_1([#b_set{op=phi,args=Args0}=I|Is], L, S) ->
Args = [{Val,Pred} || {Val,Pred} <- Args0,
is_successor(L, Pred, S)],
[I#b_set{args=Args}|fix_phis_1(Is, L, S)];
fix_phis_1(Is, _, _) -> Is.
is_successor(L, Pred, S) ->
case S of
#{Pred:=Successors} ->
member(L, Successors);
#{} ->
%% This block has been removed.
false
end.
rpo_1([L|Ls], Blocks, Seen0, Acc0) ->
case cerl_sets:is_element(L, Seen0) of
true ->
rpo_1(Ls, Blocks, Seen0, Acc0);
false ->
Block = map_get(L, Blocks),
Seen1 = cerl_sets:add_element(L, Seen0),
Successors = successors(Block),
{Acc,Seen} = rpo_1(Successors, Blocks, Seen1, Acc0),
rpo_1(Ls, Blocks, Seen, [L|Acc])
end;
rpo_1([], _, Seen, Acc) ->
{Acc,Seen}.
rename_var(#b_var{}=Old, Rename) ->
case Rename of
#{Old:=New} -> New;
#{} -> Old
end;
rename_var(#b_remote{mod=Mod0,name=Name0}=Remote, Rename) ->
Mod = rename_var(Mod0, Rename),
Name = rename_var(Name0, Rename),
Remote#b_remote{mod=Mod,name=Name};
rename_var(Old, _) -> Old.
rename_phi_vars([{Var,L}|As], Preds, Ren) ->
case cerl_sets:is_element(L, Preds) of
true ->
[{rename_var(Var, Ren),L}|rename_phi_vars(As, Preds, Ren)];
false ->
[{Var,L}|rename_phi_vars(As, Preds, Ren)]
end;
rename_phi_vars([], _, _) -> [].
map_instrs_1([L|Ls], Fun, Blocks0) ->
#b_blk{is=Is0,last=Last0} = Blk0 = map_get(L, Blocks0),
Is = [Fun(I) || I <- Is0],
Last = Fun(Last0),
Blk = Blk0#b_blk{is=Is,last=Last},
Blocks = Blocks0#{L:=Blk},
map_instrs_1(Ls, Fun, Blocks);
map_instrs_1([], _, Blocks) -> Blocks.
flatmapfoldl(F, Accu0, [Hd|Tail]) ->
{R,Accu1} = F(Hd, Accu0),
{Rs,Accu2} = flatmapfoldl(F, Accu1, Tail),
{R++Rs,Accu2};
flatmapfoldl(_, Accu, []) -> {[],Accu}.
split_blocks_1([L|Ls], P, Blocks0, Count0) ->
#b_blk{is=Is0} = Blk = map_get(L, Blocks0),
case split_blocks_is(Is0, P, []) of
{yes,Bef,Aft} ->
NewLbl = Count0,
Count = Count0 + 1,
Br = #b_br{bool=#b_literal{val=true},succ=NewLbl,fail=NewLbl},
BefBlk = Blk#b_blk{is=Bef,last=Br},
NewBlk = Blk#b_blk{is=Aft},
Blocks1 = Blocks0#{L:=BefBlk,NewLbl=>NewBlk},
Successors = successors(NewBlk),
Blocks = update_phi_labels(Successors, L, NewLbl, Blocks1),
split_blocks_1([NewLbl|Ls], P, Blocks, Count);
no ->
split_blocks_1(Ls, P, Blocks0, Count0)
end;
split_blocks_1([], _, Blocks, Count) ->
{Blocks,Count}.
split_blocks_is([I|Is], P, []) ->
split_blocks_is(Is, P, [I]);
split_blocks_is([I|Is], P, Acc) ->
case P(I) of
true ->
{yes,reverse(Acc),[I|Is]};
false ->
split_blocks_is(Is, P, [I|Acc])
end;
split_blocks_is([], _, _) -> no.
update_phi_labels_is([#b_set{op=phi,args=Args0}=I0|Is], Old, New) ->
Args = [{Arg,rename_label(Lbl, Old, New)} || {Arg,Lbl} <- Args0],
I = I0#b_set{args=Args},
[I|update_phi_labels_is(Is, Old, New)];
update_phi_labels_is(Is, _, _) -> Is.
rename_label(Old, Old, New) -> New;
rename_label(Lbl, _Old, _New) -> Lbl.
used_args([#b_var{}=V|As]) ->
[V|used_args(As)];
used_args([#b_remote{mod=Mod,name=Name}|As]) ->
used_args([Mod,Name|As]);
used_args([_|As]) ->
used_args(As);
used_args([]) -> [].
used_1([H|T], Used0) ->
Used = ordsets:union(used(H), Used0),
used_1(T, Used);
used_1([], Used) -> Used.