%% -*- 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_range.erl
%%% Author : Per Gustafsson <[email protected]>
%%% Description :
%%%
%%% Created : 12 Mar 2007 by Per Gustafsson <[email protected]>
%%%-------------------------------------------------------------------
-module(hipe_icode_range).
-export([cfg/4]).
%%=====================================================================
%% Icode Coordinator Behaviour Callbacks
%%=====================================================================
-export([replace_nones/1,
update__info/2, new__info/1, return__info/1,
return_none/0, return_none_args/2, return_any_args/2]).
%%=====================================================================
-import(erl_types, [t_any/0,
t_from_range_unsafe/2,
t_inf/2, t_integer/0,
t_to_string/1, t_to_tlist/1,
t_limit/2, t_none/0,
number_min/1, number_max/1]).
-include("hipe_icode.hrl").
-include("hipe_icode_primops.hrl").
-include("../main/hipe.hrl").
-include("../flow/cfg.hrl").
-include("../flow/hipe_bb.hrl").
-include("hipe_icode_type.hrl").
-type range_tuple() :: {'neg_inf' | integer(), 'pos_inf' | integer()}.
-type range_rep() :: range_tuple() | 'empty'.
-type fun_name() :: atom() | tuple().
-type inf_integer() :: 'neg_inf' | 'pos_inf' | integer().
-record(range, {range :: range_rep(),
other :: boolean()}).
-type range() :: #range{}.
-record(ann, {range :: range(),
type :: erl_types:erl_type(),
count :: integer()}).
-type ann() :: #ann{}.
-type range_anno() :: {'range_anno', ann(), fun((ann()) -> string())}.
-type args_fun() :: fun((mfa(), cfg()) -> [range()]).
-type call_fun() :: fun((mfa(), [range()]) -> range()).
-type final_fun() :: fun((mfa(), [range()]) -> 'ok').
-type data() :: {mfa(), args_fun(), call_fun(), final_fun()}.
-type label() :: non_neg_integer().
-type info() :: map().
-type work_list() :: {[label()], [label()], set(label())}.
-type variable() :: #icode_variable{}.
-type annotated_variable() :: #icode_variable{}.
-type argument() :: #icode_const{} | variable().
-type three_range_fun() :: fun((range(),range(),range()) -> range()).
-type instr_split_info() :: {icode_instr(), [{label(),info()}]}.
-type last_instr_return() :: {instr_split_info(), range()}.
-record(state, {info_map = #{} :: info(),
cfg :: cfg(),
liveness :: hipe_icode_ssa:liveness(),
ret_type :: range(),
lookup_fun :: call_fun(),
result_action :: final_fun()}).
-type state() :: #state{}.
-define(WIDEN, 1).
-define(TAG_IMMED1_SIZE, 4).
-define(BITS, 64).
%%---------------------------------------------------------------------
-spec cfg(cfg(), mfa(), comp_options(), #comp_servers{}) -> cfg().
cfg(Cfg, MFA, Options, Servers) ->
case proplists:get_bool(concurrent_comp, Options) of
true ->
concurrent_cfg(Cfg, MFA, Servers#comp_servers.range);
false ->
ordinary_cfg(Cfg, MFA)
end.
-spec concurrent_cfg(cfg(), mfa(), pid()) -> cfg().
concurrent_cfg(Cfg, MFA, CompServer) ->
CompServer ! {ready, {MFA, self()}},
{ArgsFun, CallFun, FinalFun} = do_analysis(Cfg, MFA),
Ans = do_rewrite(Cfg, MFA, ArgsFun, CallFun, FinalFun),
CompServer ! {done_rewrite, MFA},
Ans.
-spec do_analysis(cfg(), mfa()) -> {args_fun(), call_fun(), final_fun()}.
do_analysis(Cfg, MFA) ->
receive
{analyse, {ArgsFun, CallFun, FinalFun}} ->
analyse(Cfg, {MFA, ArgsFun, CallFun, FinalFun}),
do_analysis(Cfg, MFA);
{done, {_NewArgsFun, _NewCallFun, _NewFinalFun} = T} ->
T
end.
-spec do_rewrite(cfg(), mfa(), args_fun(), call_fun(), final_fun()) -> cfg().
do_rewrite(Cfg, MFA, ArgsFun, CallFun, FinalFun) ->
common_rewrite(Cfg, {MFA, ArgsFun, CallFun, FinalFun}).
-spec ordinary_cfg(cfg(), mfa()) -> cfg().
ordinary_cfg(Cfg, MFA) ->
Data = make_data(Cfg,MFA),
common_rewrite(Cfg, Data).
-spec common_rewrite(cfg(), data()) -> cfg().
common_rewrite(Cfg, Data) ->
State = safe_analyse(Cfg, Data),
State2 = rewrite_blocks(State),
Cfg1 = state__cfg(State2),
Cfg2 = hipe_icode_cfg:remove_unreachable_code(Cfg1),
Cfg3 = convert_cfg_to_types(Cfg2),
hipe_icode_type:specialize(Cfg3).
-spec make_data(cfg(), mfa()) -> data().
make_data(Cfg, {_M,_F,A}=MFA) ->
NoArgs =
case hipe_icode_cfg:is_closure(Cfg) of
true -> hipe_icode_cfg:closure_arity(Cfg)+1;
false -> A
end,
Args = lists:duplicate(NoArgs, any_type()),
ArgsFun = fun(_,_) -> Args end,
CallFun = fun(_,_) -> any_type() end,
FinalFun = fun(_,_) -> ok end,
{MFA, ArgsFun, CallFun, FinalFun}.
-spec analyse(cfg(), data()) -> 'ok'.
analyse(Cfg, Data) ->
try
#state{} = safe_analyse(Cfg, Data),
ok
catch throw:no_input -> ok
end.
-spec safe_analyse(cfg(), data()) -> state().
safe_analyse(CFG, Data={MFA,_,_,_}) ->
State = state__init(CFG, Data),
Work = init_work(State),
NewState = analyse_blocks(State, Work),
(state__result_action(NewState))(MFA, [state__ret_type(NewState)]),
NewState.
-spec rewrite_blocks(state()) -> state().
rewrite_blocks(State) ->
CFG = state__cfg(State),
Start = hipe_icode_cfg:start_label(CFG),
rewrite_blocks([Start], State, set_from_list([Start])).
-spec rewrite_blocks([label()], state(), set(label())) -> state().
rewrite_blocks([Next|Rest], State, Visited) ->
Info = state__info_in(State, Next),
{NewState, NewLabels} = analyse_block(Next, Info, State, true),
RealNew = not_visited(NewLabels, Visited),
NewVisited = set_union(set_from_list(RealNew), Visited),
NewWork = RealNew ++ Rest,
rewrite_blocks(NewWork, NewState, NewVisited);
rewrite_blocks([], State, _) ->
State.
-spec analyse_blocks(state(), work_list()) -> state().
analyse_blocks(State, Work) ->
case get_work(Work) of
fixpoint ->
State;
{Label, NewWork} ->
Info = state__info_in(State, Label),
{NewState, NewLabels} =
try analyse_block(Label, Info, State, false)
catch throw:none_range ->
{State, []}
end,
NewWork2 = add_work(NewWork, NewLabels),
analyse_blocks(NewState, NewWork2)
end.
-spec analyse_block(label(), info(), state(), boolean()) -> {state(), [label()]}.
analyse_block(Label, Info, State, Rewrite) ->
BB = state__bb(State, Label),
Code = hipe_bb:code(BB),
{NewCode, InfoList, RetType} =
analyse_BB(Code, Info, [], Rewrite, state__lookup_fun(State)),
State1 = state__bb_add(State, Label, hipe_bb:mk_bb(NewCode)),
State2 = state__ret_type_update(State1, RetType),
state__update_info(State2, InfoList, Rewrite).
-spec analyse_BB([icode_instr()], info(), [icode_instr()], boolean(), call_fun()) ->
{[icode_instr()], [{label(),info()}], range()}.
analyse_BB([Last], Info, Code, Rewrite, LookupFun) ->
{{NewI, LabelInfoList}, RetType} =
analyse_last_insn(Last, Info, Rewrite, LookupFun),
{lists:reverse([NewI|Code]), LabelInfoList, RetType};
analyse_BB([Insn|InsnList], Info, Code, Rewrite, LookupFun) ->
{NewInfo, NewI} = analyse_insn(Insn, Info, LookupFun),
analyse_BB(InsnList, NewInfo, [NewI|Code], Rewrite, LookupFun).
-spec analyse_insn(icode_instr(), info(), call_fun()) -> {info(), icode_instr()}.
analyse_insn(I, Info, LookupFun) ->
%% io:format("~w Info: ~p~n", [I, Info]),
NewI = handle_args(I,Info),
FinalI =
case NewI of
#icode_call{} -> analyse_call(NewI, LookupFun);
#icode_move{} -> analyse_move(NewI);
#icode_phi{} -> analyse_phi(NewI);
#icode_begin_handler{} -> analyse_begin_handler(NewI);
#icode_comment{} -> NewI
end,
{enter_vals(FinalI, Info), FinalI}.
-spec handle_args(icode_instr(), info()) -> icode_instr().
handle_args(I, Info) ->
WidenFun = fun update_three/3,
handle_args(I, Info, WidenFun).
-spec handle_args(icode_instr(), info(), three_range_fun()) -> icode_instr().
handle_args(I, Info, WidenFun) ->
Uses = hipe_icode:uses(I),
PresentRanges = [lookup(V, Info) || V <- Uses],
%% io:format("Uses: ~p~nRanges: ~p~n", [Uses, PresentRanges]),
JoinFun = fun(Var, Range) -> update_info(Var, Range, WidenFun) end,
NewUses = lists:zipwith(JoinFun, Uses, PresentRanges),
hipe_icode:subst_uses(lists:zip(Uses, NewUses), I).
-spec join_info(ann(), range(), three_range_fun()) -> ann().
join_info(Ann = #ann{range = R1, type = Type, count = ?WIDEN}, R2, Fun) ->
Ann#ann{range = Fun(R1, R2, range_from_simple_type(Type))};
join_info(Ann = #ann{range = R1, type = Type, count = C}, R2, _Fun) when C < ?WIDEN ->
case join_three(R1, R2, range_from_simple_type(Type)) of
R1 -> Ann;
NewR -> Ann#ann{range = NewR, count = C+1}
end.
-spec join_three(range(), range(), range()) -> range().
join_three(R1, R2, R3) ->
inf(sup(R1, R2), R3).
-spec update_info(variable(), range()) -> annotated_variable().
update_info(Var, Range) ->
update_info(Var, Range, fun update_three/3).
-spec update_info(variable(), range(), three_range_fun()) -> annotated_variable().
update_info(Arg, R, Fun) ->
case hipe_icode:is_annotated_variable(Arg) of
true ->
Ann = hipe_icode:variable_annotation(Arg),
hipe_icode:annotate_variable(Arg, update_info1(Ann, R, Fun));
false ->
Arg
end.
-spec update_info1(any(), range(), three_range_fun()) -> range_anno().
update_info1({range_anno, Ann, _}, R2, Fun) ->
make_range_anno(update_ann(Ann,R2,Fun));
update_info1({type_anno, Type, _}, R2, Fun) ->
make_range_anno(update_ann(type_to_ann(Type), R2, Fun)).
update_ann(Ann = #ann{range = R1, type = Type, count = ?WIDEN}, R2, Fun) ->
Ann#ann{range = Fun(R1,R2,range_from_simple_type(Type))};
update_ann(Ann = #ann{range = R1, type = Type, count = C}, R2, _Fun) ->
case update_three(R1, R2, range_from_simple_type(Type)) of
R1 -> Ann;
NewR -> Ann#ann{range = NewR, count = C+1}
end.
-spec type_to_ann(erl_types:erl_type()) -> ann().
type_to_ann(Type) ->
#ann{range = range_from_simple_type(Type), type = t_limit(Type,1), count = 1}.
-spec make_range_anno(ann()) -> range_anno().
make_range_anno(Ann) ->
{range_anno, Ann, fun pp_ann/1}.
-spec update_three(range(), range(), range()) -> range().
update_three(_R1, R2, R3) ->
inf(R2, R3).
-spec safe_widen(range(), range(), range()) -> range().
safe_widen(#range{range=Old}, #range{range=New}, T = #range{range=Wide}) ->
ResRange =
case {Old, New, Wide} of
{{Min,Max1}, {Min,Max2}, {_,Max}} ->
case inf_geq(OMax = next_up_limit(inf_max([Max1, Max2])), Max) of
true -> {Min,Max};
false -> {Min,OMax}
end;
{{Min1,Max}, {Min2,Max}, {Min,_}} ->
case inf_geq(Min, OMin = next_down_limit(inf_min([Min1, Min2]))) of
true -> {Min,Max};
false -> {OMin,Max}
end;
{{Min1,Max1}, {Min2,Max2}, {Min,Max}} ->
RealMax =
case inf_geq(OMax = next_up_limit(inf_max([Max1, Max2])), Max) of
true -> Max;
false -> OMax
end,
RealMin =
case inf_geq(Min, OMin = next_down_limit(inf_min([Min1, Min2]))) of
true -> Min;
false -> OMin
end,
{RealMin, RealMax};
_ ->
Wide
end,
T#range{range = ResRange}.
-spec widen(range(), range(), range()) -> range().
widen(#range{range=Old}, #range{range=New}, T = #range{range=Wide}) ->
ResRange =
case {Old, New, Wide} of
{{Min,_}, {Min,Max2}, {_,Max}} ->
case inf_geq(OMax = next_up_limit(Max2), Max) of
true -> {Min,Max};
false -> {Min,OMax}
end;
{{_,Max}, {Min2,Max}, {Min,_}} ->
case inf_geq(Min, OMin = next_down_limit(Min2)) of
true -> {Min,Max};
false -> {OMin,Max}
end;
{_, {Min2,Max2}, {Min,Max}} ->
RealMax =
case inf_geq(OMax = next_up_limit(Max2), Max) of
true -> Max;
false -> OMax
end,
RealMin =
case inf_geq(Min, OMin = next_down_limit(Min2)) of
true -> Min;
false -> OMin
end,
{RealMin, RealMax};
_ ->
Wide
end,
T#range{range = ResRange}.
-spec analyse_call(#icode_call{}, call_fun()) -> #icode_call{}.
analyse_call(Call, LookupFun) ->
Args = hipe_icode:args(Call),
Fun = hipe_icode:call_fun(Call),
Type = hipe_icode:call_type(Call),
DstRanges = analyse_call_or_enter_fun(Fun, Args, Type, LookupFun),
case hipe_icode:call_dstlist(Call) of
[] ->
Call;
Dsts ->
NewDefs = [update_info(Var, R) || {Var,R} <- lists:zip(Dsts, DstRanges)],
hipe_icode:subst_defines(lists:zip(Dsts, NewDefs), Call)
end.
-spec analyse_move(#icode_move{}) -> #icode_move{}.
analyse_move(Move) ->
Src = hipe_icode:move_src(Move),
Dst = hipe_icode:move_dst(Move),
Range = get_range_from_arg(Src),
NewDst = update_info(Dst, Range),
hipe_icode:subst_defines([{Dst,NewDst}], Move).
-spec analyse_begin_handler(#icode_begin_handler{}) -> #icode_begin_handler{}.
analyse_begin_handler(Handler) ->
SubstList =
[{Dst, update_info(Dst, any_type())} ||
Dst <- hipe_icode:begin_handler_dstlist(Handler)],
hipe_icode:subst_defines(SubstList, Handler).
-spec analyse_phi(#icode_phi{}) -> #icode_phi{}.
analyse_phi(Phi) ->
{_, Args} = lists:unzip(hipe_icode:phi_arglist(Phi)),
Dst = hipe_icode:phi_dst(Phi),
ArgRanges = get_range_from_args(Args),
%% io:format("Phi-Arg_ranges: ~p ~n", [Arg_ranges]),
DstRange = sup(ArgRanges),
NewDst = update_info(Dst, DstRange, fun widen/3),
hipe_icode:subst_defines([{Dst, NewDst}], Phi).
-spec analyse_last_insn(icode_instr(), info(), boolean(), call_fun()) ->
last_instr_return().
analyse_last_insn(I, Info, Rewrite, LookupFun) ->
%% io:format("~w Info: ~p~n",[I,Info]),
NewI = handle_args(I, Info),
%% io:format("~w -> ~w~n",[NewI,I]),
case NewI of
#icode_return{} -> analyse_return(NewI, Info);
#icode_enter{} -> analyse_enter(NewI, Info, LookupFun);
#icode_switch_val{} ->
{analyse_switch_val(NewI, Info, Rewrite), none_type()};
#icode_if{} -> {analyse_if(NewI, Info, Rewrite), none_type()};
#icode_goto{} -> {analyse_goto(NewI, Info), none_type()};
#icode_type{} -> {analyse_type(NewI, Info, Rewrite), none_type()};
#icode_fail{} -> {analyse_fail(NewI, Info), none_type()};
#icode_call{} -> {analyse_last_call(NewI, Info, LookupFun), none_type()};
#icode_switch_tuple_arity{} ->
{analyse_switch_tuple_arity(NewI, Info), none_type()};
#icode_begin_try{} -> {analyse_begin_try(NewI, Info), none_type()}
end.
-spec analyse_return(#icode_return{}, info()) -> last_instr_return().
analyse_return(Insn, _Info) ->
[RetRange] = get_range_from_args(hipe_icode:return_vars(Insn)),
{{Insn,[]}, RetRange}.
-spec analyse_enter(#icode_enter{}, info(), call_fun()) -> last_instr_return().
analyse_enter(Insn, _Info, LookupFun) ->
Args = hipe_icode:args(Insn),
Fun = hipe_icode:enter_fun(Insn),
CallType = hipe_icode:enter_type(Insn),
[RetRange] = analyse_call_or_enter_fun(Fun, Args, CallType, LookupFun),
{{Insn,[]}, RetRange}.
-spec analyse_switch_val(#icode_switch_val{}, info(), boolean()) -> instr_split_info().
analyse_switch_val(Switch, Info, Rewrite) ->
Var = hipe_icode:switch_val_term(Switch),
SwitchRange = get_range_from_arg(Var),
Cases = hipe_icode:switch_val_cases(Switch),
{FailRange, LabelRangeList} = get_range_label_list(Cases, SwitchRange, []),
case range__is_none(FailRange) of
true ->
InfoList = update_infos(Var, Info, LabelRangeList),
if Rewrite -> {update_switch(Switch, LabelRangeList, false), InfoList};
true -> {Switch, InfoList}
end;
false ->
FailLabel = hipe_icode:switch_val_fail_label(Switch),
InfoList = update_infos(Var, Info, [{FailRange, FailLabel}|LabelRangeList]),
if Rewrite -> {update_switch(Switch, LabelRangeList, true), InfoList};
true -> {Switch, InfoList}
end
end.
-spec update_infos(argument(), info(), [{range(),label()}]) -> [{label(),info()}].
update_infos(Arg, Info, [{Range, Label}|Rest]) ->
[{Label,enter_define({Arg,Range},Info)} | update_infos(Arg, Info, Rest)];
update_infos(_, _, []) -> [].
-spec get_range_label_list([{argument(),label()}], range(), [{range(),label()}]) ->
{range(),[{range(),label()}]}.
get_range_label_list([{Val,Label}|Cases], SRange, Acc) ->
VRange = get_range_from_arg(Val),
None = none_type(),
case inf(SRange, VRange) of
None ->
get_range_label_list(Cases, SRange, Acc);
ResRange ->
get_range_label_list(Cases, SRange, [{ResRange,Label}|Acc])
end;
get_range_label_list([], SRange, Acc) ->
{PointTypes, _} = lists:unzip(Acc),
{remove_point_types(SRange, PointTypes), Acc}.
-spec update_switch(#icode_switch_val{}, [{range(),label()}], boolean()) ->
#icode_switch_val{}.
update_switch(Switch, LabelRangeList, KeepFail) ->
S2 =
case label_range_list_to_cases(LabelRangeList, []) of
no_update ->
Switch;
Cases ->
hipe_icode:switch_val_cases_update(Switch, Cases)
end,
if KeepFail -> S2;
true -> S2
end.
-spec label_range_list_to_cases([{range(),label()}], [{#icode_const{},label()}]) ->
'no_update' | [{#icode_const{},label()}].
label_range_list_to_cases([{#range{range={C,C},other=false},Label}|Rest],
Acc) when is_integer(C) ->
label_range_list_to_cases(Rest, [{hipe_icode:mk_const(C),Label}|Acc]);
label_range_list_to_cases([{_NotAConstantRange,_Label}|_Rest], _Acc) ->
no_update;
label_range_list_to_cases([], Acc) ->
lists:reverse(Acc).
-spec analyse_switch_tuple_arity(#icode_switch_tuple_arity{}, info()) ->
{#icode_switch_tuple_arity{}, [{label(),info()}]}.
analyse_switch_tuple_arity(Switch, Info) ->
Var = hipe_icode:switch_tuple_arity_term(Switch),
NewInfo = enter_define({Var, get_range_from_arg(Var)}, Info),
Cases = hipe_icode:switch_tuple_arity_cases(Switch),
Fail = hipe_icode:switch_tuple_arity_fail_label(Switch),
{_, Case_labels} = lists:unzip(Cases),
Labels = [Fail|Case_labels],
{Switch, [{Label,NewInfo} || Label <- Labels]}.
-spec analyse_goto(#icode_goto{}, info()) -> {#icode_goto{}, [{label(),info()},...]}.
analyse_goto(Insn, Info) ->
GotoLabel = hipe_icode:goto_label(Insn),
{Insn, [{GotoLabel,Info}]}.
-spec analyse_fail(#icode_fail{}, info()) -> {#icode_fail{}, [{label(),info()}]}.
analyse_fail(Fail, Info) ->
case hipe_icode:fail_label(Fail) of
[] -> {Fail, []};
Label -> {Fail, [{Label,Info}]}
end.
-spec analyse_begin_try(#icode_begin_try{}, info()) ->
{#icode_begin_try{}, [{label(),info()},...]}.
analyse_begin_try(Insn, Info) ->
Label = hipe_icode:begin_try_label(Insn),
Successor = hipe_icode:begin_try_successor(Insn),
{Insn, [{Label,Info},{Successor,Info}]}.
-spec analyse_last_call(#icode_call{}, info(), call_fun()) ->
{#icode_call{}, [{label(),info()},...]}.
analyse_last_call(Call, Info, LookupFun) ->
%% hipe_icode_pp:pp_block([Insn]),
NewI = analyse_call(Call, LookupFun),
Continuation = hipe_icode:call_continuation(Call),
NewInfo = enter_vals(NewI, Info),
case hipe_icode:call_fail_label(Call) of
[] ->
{NewI, [{Continuation, NewInfo}]};
Fail ->
{NewI, [{Continuation, NewInfo}, {Fail, Info}]}
end.
-spec analyse_if(#icode_if{}, info(), boolean()) ->
{#icode_goto{} | #icode_if{}, [{label(),info()}]}.
analyse_if(If, Info, Rewrite) ->
case hipe_icode:if_args(If) of
[_, _] = Args ->
analyse_sane_if(If, Info, Args, get_range_from_args(Args), Rewrite);
_ ->
TrueLabel = hipe_icode:if_true_label(If),
FalseLabel = hipe_icode:if_false_label(If),
{If, [{TrueLabel, Info}, {FalseLabel, Info}]}
end.
-spec analyse_sane_if(#icode_if{}, info(), [argument(),...],
[range(),...], boolean()) ->
{#icode_goto{} | #icode_if{}, [{label(), info()}]}.
analyse_sane_if(If, Info, [Arg1, Arg2], [Range1, Range2], Rewrite) ->
{TrueRange1, TrueRange2, FalseRange1, FalseRange2} =
case normalize_name(hipe_icode:if_op(If)) of
'>' ->
{TR2, TR1, FR2, FR1} = range_inequality_propagation(Range2, Range1),
{TR1, TR2, FR1, FR2};
'<' ->
range_inequality_propagation(Range1, Range2);
'>=' ->
{FR1, FR2, TR1, TR2} = range_inequality_propagation(Range1, Range2),
{TR1, TR2, FR1, FR2};
'=<' ->
{FR2, FR1, TR2, TR1} = range_inequality_propagation(Range2, Range1),
{TR1, TR2, FR1, FR2};
'=:=' ->
{TR1, TR2, FR1, FR2} = range_equality_propagation(Range1, Range2),
{TR1, TR2, FR1, FR2};
'=/=' ->
{FR1, FR2, TR1, TR2} = range_equality_propagation(Range1, Range2),
{TR1, TR2, FR1, FR2};
'==' ->
{TR1, TR2, FR1, FR2} = range_equality_propagation(Range1, Range2),
{set_other(TR1,other(Range1)), set_other(TR2,other(Range2)), FR1, FR2};
'/=' ->
{FR1, FR2, TR1, TR2} = range_equality_propagation(Range1, Range2),
{TR1, TR2, set_other(FR1,other(Range1)), set_other(FR2,other(Range2))}
end,
%% io:format("TR1 = ~w\nTR2 = ~w\n", [TrueRange1, TrueRange2]),
True =
case lists:all(fun range__is_none/1, [TrueRange1, TrueRange2]) of
true -> [];
false ->
TrueLabel = hipe_icode:if_true_label(If),
TrueArgRanges = [{Arg1, TrueRange1}, {Arg2, TrueRange2}],
TrueInfo = enter_defines(TrueArgRanges, Info),
[{TrueLabel, TrueInfo}]
end,
%% io:format("FR1 = ~w\nFR2 = ~w\n", [FalseRange1, FalseRange2]),
False =
case lists:all(fun range__is_none/1, [FalseRange1, FalseRange2]) of
true -> [];
false ->
FalseLabel = hipe_icode:if_false_label(If),
FalseArgRanges = [{Arg1, FalseRange1}, {Arg2, FalseRange2}],
FalseInfo = enter_defines(FalseArgRanges, Info),
[{FalseLabel, FalseInfo}]
end,
UpdateInfo = True ++ False,
NewIF =
if Rewrite ->
case UpdateInfo of
[] -> %% This is weird
If;
[{Label, _Info}] ->
hipe_icode:mk_goto(Label);
[_, _] ->
If
end;
true ->
If
end,
{NewIF, UpdateInfo}.
-spec normalize_name(atom()) -> atom().
normalize_name(Name) ->
case Name of
'fixnum_eq' -> '=:=';
'fixnum_neq' -> '=/=';
'fixnum_gt' -> '>';
'fixnum_lt' -> '<';
'fixnum_ge' -> '>=';
'fixnum_le' -> '=<';
Name -> Name
end.
-spec range_equality_propagation(range(), range()) ->
{range(), range(), range(), range()}.
range_equality_propagation(Range1, Range2) ->
TrueRange = inf(Range1, Range2),
{FalseRange1, FalseRange2} =
case {range(Range1), range(Range2)} of
{{N,N}, {N,N}} ->
{none_range(), none_range()};
{{N1,N1}, {N2,N2}} ->
{Range1, Range2};
{{N,N}, _} ->
{_,FR2} = compare_with_integer(N, Range2),
{Range1, FR2};
{_, {N,N}} ->
{_,FR1} = compare_with_integer(N, Range1),
{FR1, Range2};
{_, _} ->
{Range1, Range2}
end,
{TrueRange, TrueRange, FalseRange1, FalseRange2}.
-spec range_inequality_propagation(range(), range()) ->
{range(), range(), range(), range()}.
%% Range1 < Range2
range_inequality_propagation(Range1, Range2) ->
R1_other = other(Range1),
R2_other = other(Range2),
{R1_true_range, R1_false_range, R2_true_range, R2_false_range} =
case {range(Range1), range(Range2)} of
{{N1,N1}, {N2,N2}} ->
case inf_geq(N2,inf_add(N1,1)) of
true ->
{{N1,N1},empty,{N2,N2},empty};
false ->
{empty,{N1,N1},empty,{N2,N2}}
end;
{{N1,N1}, {Min2,Max2}} ->
case inf_geq(Min2,inf_add(N1,1)) of
true ->
{{N1,N1},empty,{inf_add(N1,1),Max2},empty};
false ->
case inf_geq(N1,Max2) of
true ->
{empty,{N1,N1},empty,{Min2,N1}};
false ->
{{N1,N1},{N1,N1},{inf_add(N1,1),Max2},{Min2,N1}}
end
end;
{{Min1,Max1}, {N2,N2}} ->
case inf_geq(N2,inf_add(Max1,1)) of
true ->
{{Min1,inf_add(N2,-1)},empty,{N2,N2},empty};
false ->
case inf_geq(Min1,N2) of
true ->
{empty,{N2,Max1},empty,{N2,N2}};
false ->
{{Min1,inf_add(N2,-1)},{N2,Max1},{N2,N2},{N2,N2}}
end
end;
{empty, {Min2,Max2}} ->
{empty,empty,{Min2,Max2},{Min2,Max2}};
{{Min1,Max1}, empty} ->
{{Min1,Max1},{Min1,Max1},empty,empty};
{empty, empty} ->
{empty,empty,empty,empty};
{{Min1,Max1}, {Min2,Max2}} ->
{{Min1,inf_min([Max1,inf_add(Max2,-1)])},
{inf_max([Min1,Min2]),Max1},
{inf_max([inf_add(Min1,1),Min2]),Max2},
{Min2,inf_min([Max1,Max2])}}
end,
{range_init(R1_true_range, R1_other),
range_init(R2_true_range, R2_other),
range_init(R1_false_range, R1_other),
range_init(R2_false_range, R2_other)}.
-spec analyse_type(#icode_type{}, info(), boolean()) ->
{#icode_goto{} | #icode_type{}, [{label(),info()}]}.
analyse_type(Type, Info, Rewrite) ->
TypeTest = hipe_icode:type_test(Type),
[Arg|_] = hipe_icode:type_args(Type),
OldVarRange = get_range_from_arg(Arg),
{TrueRange, FalseRange} =
case TypeTest of
{integer, N} ->
compare_with_integer(N, OldVarRange);
integer ->
{inf(any_range(), OldVarRange), inf(none_range(), OldVarRange)};
number ->
{OldVarRange, OldVarRange};
_ ->
{inf(none_range(), OldVarRange), OldVarRange}
end,
TrueLabel = hipe_icode:type_true_label(Type),
FalseLabel = hipe_icode:type_false_label(Type),
TrueInfo = enter_define({Arg, TrueRange}, Info),
FalseInfo = enter_define({Arg, FalseRange}, Info),
True =
case range__is_none(TrueRange) of
true -> [];
false -> [{TrueLabel, TrueInfo}]
end,
False =
case range__is_none(FalseRange) of
true -> [];
false -> [{FalseLabel, FalseInfo}]
end,
UpdateInfo = True ++ False,
NewType =
if Rewrite ->
case UpdateInfo of
[] -> %% This is weird
Type;
[{Label,_Info}] ->
hipe_icode:mk_goto(Label);
[_, _] ->
Type
end;
true ->
Type
end,
{NewType, True ++ False}.
-spec compare_with_integer(integer(), range()) -> {range(), range()}.
compare_with_integer(N, OldVarRange) ->
TestRange = range_init({N, N}, false),
TrueRange = inf(TestRange, OldVarRange),
%% False range
TempFalseRange = range__remove_constant(OldVarRange, TestRange),
BetterRange =
case range(TempFalseRange) of
{Min, Max} = MM ->
New_small = inf_geq(Min, N),
New_large = inf_geq(N, Max),
if New_small and not New_large ->
{N + 1, Max};
New_large and not New_small ->
{Min, N - 1};
true ->
MM
end;
Not_tuple ->
Not_tuple
end,
FalseRange = range_init(BetterRange, other(TempFalseRange)),
{TrueRange, FalseRange}.
%%== Ranges ==================================================================
-spec pp_ann(ann() | erl_types:erl_type()) -> string().
pp_ann(#ann{range = #range{range = R, other = false}}) ->
pp_range(R);
pp_ann(#ann{range = #range{range = empty, other = true}, type = Type}) ->
t_to_string(Type);
pp_ann(#ann{range = #range{range = R, other = true}, type = Type}) ->
pp_range(R) ++ " | " ++ t_to_string(Type);
pp_ann(Type) ->
t_to_string(Type).
-spec pp_range(range_rep()) -> nonempty_string().
pp_range(empty) ->
"none";
pp_range({Min, Max}) ->
val_to_string(Min) ++ ".." ++ val_to_string(Max).
-spec val_to_string('pos_inf' | 'neg_inf' | integer()) -> string().
val_to_string(pos_inf) -> "inf";
val_to_string(neg_inf) -> "-inf";
val_to_string(X) when is_integer(X) -> integer_to_list(X).
-spec range_from_type(erl_types:erl_type()) -> [range()].
range_from_type(Type) ->
[range_from_simple_type(T) || T <- t_to_tlist(Type)].
-spec range_from_simple_type(erl_types:erl_type()) -> range().
range_from_simple_type(Type) ->
None = t_none(),
case t_inf(t_integer(), Type) of
None ->
#range{range = empty, other = true};
Type ->
Range = {number_min(Type), number_max(Type)},
#range{range = Range, other = false};
NewType ->
Range = {number_min(NewType), number_max(NewType)},
#range{range = Range, other = true}
end.
-spec range_init(range_rep(), boolean()) -> range().
range_init({Min, Max} = Range, Other) ->
case inf_geq(Max, Min) of
true ->
#range{range = Range, other = Other};
false ->
#range{range = empty, other = Other}
end;
range_init(empty, Other) ->
#range{range = empty, other = Other}.
-spec range(range()) -> range_rep().
range(#range{range = R}) -> R.
-spec other(range()) -> boolean().
other(#range{other = O}) -> O.
-spec set_other(range(), boolean()) -> range().
set_other(R, O) -> R#range{other = O}.
-spec range__min(range()) -> 'empty' | 'neg_inf' | integer().
range__min(#range{range = empty}) -> empty;
range__min(#range{range = {Min,_}}) -> Min.
-spec range__max(range()) -> 'empty' | 'pos_inf' | integer().
range__max(#range{range = empty}) -> empty;
range__max(#range{range = {_,Max}}) -> Max.
-spec range__is_none(range()) -> boolean().
range__is_none(#range{range = empty, other = false}) -> true;
range__is_none(#range{}) -> false.
-spec range__is_empty(range()) -> boolean().
range__is_empty(#range{range = empty}) -> true;
range__is_empty(#range{range = {_,_}}) -> false.
-spec remove_point_types(range(), [range()]) -> range().
remove_point_types(Range, Ranges) ->
Sorted = lists:sort(Ranges),
FoldFun = fun (R, Acc) -> range__remove_constant(Acc,R) end,
Range1 = lists:foldl(FoldFun, Range, Sorted),
lists:foldl(FoldFun, Range1, lists:reverse(Sorted)).
-spec range__remove_constant(range(), range()) -> range().
range__remove_constant(#range{range = {C, C}} = R, #range{range = {C, C}}) ->
R#range{range = empty};
range__remove_constant(#range{range = {C, H}} = R, #range{range = {C, C}}) ->
R#range{range = {C+1, H}};
range__remove_constant(#range{range = {L, C}} = R, #range{range = {C, C}}) ->
R#range{range = {L, C-1}};
range__remove_constant(#range{} = R, #range{range = {C,C}}) ->
R;
range__remove_constant(#range{} = R, _) ->
R.
-spec any_type() -> range().
any_type() ->
#range{range = any_r(), other = true}.
-spec any_range() -> range().
any_range() ->
#range{range = any_r(), other = false}.
-spec none_range() -> range().
none_range() ->
#range{range = empty, other = true}.
-spec none_type() -> range().
none_type() ->
#range{range = empty, other = false}.
-spec any_r() -> {'neg_inf','pos_inf'}.
any_r() -> {neg_inf, pos_inf}.
-spec get_range_from_args([argument()]) -> [range()].
get_range_from_args(Args) ->
[get_range_from_arg(Arg) || Arg <- Args].
-spec get_range_from_arg(argument()) -> range().
get_range_from_arg(Arg) ->
case hipe_icode:is_const(Arg) of
true ->
Value = hipe_icode:const_value(Arg),
case is_integer(Value) of
true ->
#range{range = {Value, Value}, other = false};
false ->
#range{range = empty, other = true}
end;
false ->
case hipe_icode:is_annotated_variable(Arg) of
true ->
case hipe_icode:variable_annotation(Arg) of
{range_anno, #ann{range = Range}, _} ->
Range;
{type_anno, Type, _} ->
range_from_simple_type(Type)
end;
false ->
any_type()
end
end.
%% inf([R]) ->
%% R;
%% inf([R1,R2|Rest]) ->
%% inf([inf(R1,R2)|Rest]).
-spec inf(range(), range()) -> range().
inf(#range{range=R1, other=O1}, #range{range=R2, other=O2}) ->
#range{range=range_inf(R1,R2), other=other_inf(O1,O2)}.
-spec range_inf(range_rep(), range_rep()) -> range_rep().
range_inf(empty, _) -> empty;
range_inf(_, empty) -> empty;
range_inf({Min1,Max1}, {Min2,Max2}) ->
NewMin = inf_max([Min1, Min2]),
NewMax = inf_min([Max1, Max2]),
case inf_geq(NewMax, NewMin) of
true ->
{NewMin, NewMax};
false ->
empty
end.
-spec other_inf(boolean(), boolean()) -> boolean().
other_inf(O1, O2) -> O1 and O2.
-spec sup([range(),...]) -> range().
sup([R]) ->
R;
sup([R1,R2|Rest]) ->
sup([sup(R1, R2)|Rest]).
-spec sup(range(), range()) -> range().
sup(#range{range=R1,other=O1}, #range{range=R2,other=O2}) ->
#range{range=range_sup(R1,R2), other=other_sup(O1,O2)}.
-spec range_sup(range_rep(), range_rep()) -> range_rep().
range_sup(empty, R) -> R;
range_sup(R, empty) -> R;
range_sup({Min1,Max1}, {Min2,Max2}) ->
NewMin = inf_min([Min1,Min2]),
NewMax = inf_max([Max1,Max2]),
{NewMin,NewMax}.
-spec other_sup(boolean(), boolean()) -> boolean().
other_sup(O1, O2) -> O1 or O2.
%%== Call Support =============================================================
-spec analyse_call_or_enter_fun(fun_name(), [argument()],
icode_call_type(), call_fun()) -> [range()].
analyse_call_or_enter_fun(Fun, Args, CallType, LookupFun) ->
%%io:format("Fun: ~p~n Args: ~p~n CT: ~p~n LF: ~p~n", [Fun, Args, CallType, LookupFun]),
case basic_type(Fun) of
{bin, Operation} ->
[Arg_range1,Arg_range2] = get_range_from_args(Args),
A1_is_empty = range__is_empty(Arg_range1),
A2_is_empty = range__is_empty(Arg_range2),
case A1_is_empty orelse A2_is_empty of
true ->
[none_type()];
false ->
[Operation(Arg_range1, Arg_range2)]
end;
{unary, Operation} ->
[Arg_range] = get_range_from_args(Args),
case range__is_empty(Arg_range) of
true ->
[none_type()];
false ->
[Operation(Arg_range)]
end;
{fcall, MFA} ->
case CallType of
local ->
Range = LookupFun(MFA, get_range_from_args(Args)),
case range__is_none(Range) of
true ->
throw(none_range);
false ->
[Range]
end;
remote ->
[any_type()]
end;
not_int ->
[any_type()];
not_analysed ->
[any_type()];
{hipe_bs_primop, {bs_get_integer, Size, Flags}} ->
{Min, Max} = analyse_bs_get_integer(Size, Flags, length(Args) =:= 1),
[#range{range = {Min, Max}, other = false}, any_type()];
{hipe_bs_primop, _} = Primop ->
Type = hipe_icode_primops:type(Primop),
range_from_type(Type)
end.
-type bin_operation() :: fun((range(), range()) -> range()).
-type unary_operation() :: fun((range()) -> range()).
-spec basic_type(fun_name()) -> 'not_int' | 'not_analysed'
| {'bin', bin_operation()}
| {'unary', unary_operation()}
| {'fcall', mfa()} | {'hipe_bs_primop', _}.
%% Arithmetic operations
basic_type('+') -> {bin, fun(R1, R2) -> range_add(R1, R2) end};
basic_type('-') -> {bin, fun(R1, R2) -> range_sub(R1, R2) end};
basic_type('*') -> {bin, fun(R1, R2) -> range_mult(R1, R2) end};
basic_type('/') -> not_int;
basic_type('div') -> {bin, fun(R1, R2) -> range_div(R1, R2) end};
basic_type('rem') -> {bin, fun(R1, R2) -> range_rem(R1, R2) end};
basic_type('bor') -> {bin, fun(R1, R2) -> range_bor(R1, R2) end};
basic_type('band') -> {bin, fun(R1, R2) -> range_band(R1, R2) end};
basic_type('bxor') -> {bin, fun(R1, R2) -> range_bxor(R1, R2) end};
basic_type('bnot') -> {unary, fun(R1) -> range_bnot(R1) end};
basic_type('bsl') -> {bin, fun(R1, R2) -> range_bsl(R1, R2) end};
basic_type('bsr') -> {bin, fun(R1, R2) -> range_bsr(R1, R2) end};
%% unsafe_*
basic_type('unsafe_bor') ->
{bin, fun(R1, R2) -> range_bor(R1, R2) end};
basic_type('unsafe_band') ->
{bin, fun(R1, R2) -> range_band(R1, R2) end};
basic_type('unsafe_bxor') ->
{bin, fun(R1, R2) -> range_bxor(R1, R2) end};
basic_type('unsafe_bnot') ->
{unary, fun(R1) -> range_bnot(R1) end};
basic_type('unsafe_bsl') ->
{bin, fun(R1, R2) -> range_bsl(R1, R2) end};
basic_type('unsafe_bsr') ->
{bin, fun(R1, R2) -> range_bsr(R1, R2) end};
basic_type('unsafe_add') ->
{bin, fun(R1, R2) -> range_add(R1, R2) end};
basic_type('unsafe_sub') ->
{bin, fun(R1, R2) -> range_sub(R1, R2) end};
basic_type('extra_unsafe_add') ->
{bin, fun(R1, R2) -> range_add(R1, R2) end};
basic_type('extra_unsafe_sub') ->
{bin, fun(R1, R2) -> range_sub(R1, R2) end};
%% Binaries
basic_type({hipe_bs_primop, _} = Primop) -> Primop;
%% Unknown, other
basic_type(call_fun) -> not_analysed;
basic_type(clear_timeout) -> not_analysed;
basic_type(redtest) -> not_analysed;
basic_type(set_timeout) -> not_analysed;
basic_type(#apply_N{}) -> not_analysed;
basic_type(#closure_element{}) -> not_analysed;
basic_type(#gc_test{}) -> not_analysed;
%% Message handling
basic_type(check_get_msg) -> not_analysed;
basic_type(next_msg) -> not_analysed;
basic_type(select_msg) -> not_analysed;
basic_type(suspend_msg) -> not_analysed;
%% Functions
basic_type(enter_fun) -> not_analysed;
basic_type(#mkfun{}) -> not_int;
basic_type({_M,_F,_A} = MFA) -> {fcall, MFA};
%% Floats
basic_type(conv_to_float) -> not_int;
basic_type(fclearerror) -> not_analysed;
basic_type(fcheckerror) -> not_analysed;
basic_type(fnegate) -> not_int;
basic_type(fp_add) -> not_int;
basic_type(fp_div) -> not_int;
basic_type(fp_mul) -> not_int;
basic_type(fp_sub) -> not_int;
basic_type(unsafe_tag_float) -> not_int;
basic_type(unsafe_untag_float) -> not_int;
%% Lists, tuples, records
basic_type(cons) -> not_int;
basic_type(mktuple) -> not_int;
basic_type(unsafe_hd) -> not_analysed;
basic_type(unsafe_tl) -> not_int;
basic_type(#element{}) -> not_analysed;
basic_type(#unsafe_element{}) -> not_analysed;
basic_type(#unsafe_update_element{}) -> not_analysed.
-spec analyse_bs_get_integer(integer(), integer(), boolean()) -> range_tuple().
analyse_bs_get_integer(Size, Flags, true) ->
Signed = Flags band 4,
case Signed =:= 0 of
true ->
{0, inf_add(inf_bsl(1, Size), -1)}; % return {Min, Max}
false ->
{inf_inv(inf_bsl(1, Size-1)), inf_add(inf_bsl(1, Size-1), -1)}
end;
analyse_bs_get_integer(Size, Flags, false) when is_integer(Size),
is_integer(Flags) ->
any_r().
%%---------------------------------------------------------------------------
%% Range operations
%%---------------------------------------------------------------------------
%% Arithmetic
-spec range_add(range(), range()) -> range().
range_add(Range1, Range2) ->
NewMin = inf_add(range__min(Range1), range__min(Range2)),
NewMax = inf_add(range__max(Range1), range__max(Range2)),
Other = other(Range1) orelse other(Range2),
range_init({NewMin, NewMax}, Other).
-spec range_sub(range(), range()) -> range().
range_sub(Range1, Range2) ->
Min_sub = inf_min([inf_inv(range__max(Range2)),
inf_inv(range__min(Range2))]),
Max_sub = inf_max([inf_inv(range__max(Range2)),
inf_inv(range__min(Range2))]),
NewMin = inf_add(range__min(Range1), Min_sub),
NewMax = inf_add(range__max(Range1), Max_sub),
Other = other(Range1) orelse other(Range2),
range_init({NewMin, NewMax}, Other).
-spec range_mult(range(), range()) -> range().
range_mult(#range{range=empty, other=true}, _Range2) ->
range_init(empty, true);
range_mult(_Range1, #range{range=empty, other=true}) ->
range_init(empty, true);
range_mult(Range1, Range2) ->
Min1 = range__min(Range1),
Min2 = range__min(Range2),
Max1 = range__max(Range1),
Max2 = range__max(Range2),
GreaterMin1 = inf_greater_zero(Min1),
GreaterMin2 = inf_greater_zero(Min2),
GreaterMax1 = inf_greater_zero(Max1),
GreaterMax2 = inf_greater_zero(Max2),
Range =
if GreaterMin1 ->
if GreaterMin2 -> {inf_mult(Min1, Min2), inf_mult(Max1, Max2)};
GreaterMax2 -> {inf_mult(Min2, Max1), inf_mult(Max2, Max1)};
true -> {inf_mult(Min2, Max1), inf_mult(Max2, Min1)}
end;
%% Column 1 or 2
GreaterMin2 -> % Column 1 or 2 row 3
range(range_mult(Range2, Range1));
GreaterMax1 -> % Column 2 Row 1 or 2
if GreaterMax2 -> % Column 2 Row 2
NewMin = inf_min([inf_mult(Min2, Max1), inf_mult(Max2, Min1)]),
NewMax = inf_max([inf_mult(Min2, Min1), inf_mult(Max2, Max1)]),
{NewMin, NewMax};
true -> % Column 2 Row 1
{inf_mult(Min2, Max1), inf_mult(Min2, Min1)}
end;
GreaterMax2 -> % Column 1 Row 2
range(range_mult(Range2, Range1));
true -> % Column 1 Row 1
{inf_mult(Max1, Max2), inf_mult(Min2, Min1)}
end,
Other = other(Range1) orelse other(Range2),
range_init(Range, Other).
-spec extreme_divisors(range()) -> range_tuple().
extreme_divisors(#range{range={0,0}}) -> {0,0};
extreme_divisors(#range{range={0,Max}}) -> {1,Max};
extreme_divisors(#range{range={Min,0}}) -> {Min,-1};
extreme_divisors(#range{range={Min,Max}}) ->
case inf_geq(Min, 0) of
true -> {Min, Max};
false -> % Min < 0
case inf_geq(0, Max) of
true -> {Min,Max}; % Max < 0
false -> {-1,1} % Max > 0
end
end.
-spec range_div(range(), range()) -> range().
%% this is div, not /.
range_div(_, #range{range={0,0}}) ->
range_init(empty, false);
range_div(#range{range=empty}, _) ->
range_init(empty, false);
range_div(_, #range{range=empty}) ->
range_init(empty, false);
range_div(Range1, Den) ->
Min1 = range__min(Range1),
Max1 = range__max(Range1),
{Min2, Max2} = extreme_divisors(Den),
Min_max_list = [inf_div(Min1, Min2), inf_div(Min1, Max2),
inf_div(Max1, Min2), inf_div(Max1, Max2)],
range_init({inf_min(Min_max_list), inf_max(Min_max_list)}, false).
-spec range_rem(range(), range()) -> range().
range_rem(Range1, Range2) ->
%% Range1 desides the sign of the answer.
Min1 = range__min(Range1),
Max1 = range__max(Range1),
Min2 = range__min(Range2),
Max2 = range__max(Range2),
Min1_geq_zero = inf_geq(Min1, 0),
Max1_leq_zero = inf_geq(0, Max1),
Max_range2 = inf_max([inf_abs(Min2), inf_abs(Max2)]),
New_min =
if Min1_geq_zero -> 0;
Max_range2 =:= 0 -> 0;
true -> inf_add(inf_inv(Max_range2), 1)
end,
New_max =
if Max1_leq_zero -> 0;
Max_range2 =:= 0 -> 0;
true -> inf_add(Max_range2, -1)
end,
range_init({New_min, New_max}, false).
%%--- Bit operations ----------------------------
-spec range_bsr(range(), range()) -> range().
range_bsr(Range1, Range2=#range{range={Min, Max}}) ->
New_Range2 = range_init({inf_inv(Max), inf_inv(Min)}, other(Range2)),
Ans = range_bsl(Range1, New_Range2),
%% io:format("bsr res:~w~nInput:= ~w~n", [Ans, {Range1,Range2}]),
Ans.
-spec range_bsl(range(), range()) -> range().
range_bsl(Range1, Range2) ->
Min1 = range__min(Range1),
Min2 = range__min(Range2),
Max1 = range__max(Range1),
Max2 = range__max(Range2),
Min1Geq0 = inf_geq(Min1, 0),
Max1Less0 = not inf_geq(Max1, 0),
MinMax =
if Min1Geq0 ->
{inf_bsl(Min1, Min2), inf_bsl(Max1, Max2)};
true ->
if Max1Less0 -> {inf_bsl(Min1, Max2), inf_bsl(Max1, Min2)};
true -> {inf_bsl(Min1, Max2), inf_bsl(Max1, Max2)}
end
end,
range_init(MinMax, false).
-spec range_bnot(range()) -> range().
range_bnot(Range) ->
Minus_one = range_init({-1,-1}, false),
range_add(range_mult(Range, Minus_one), Minus_one).
-spec width(range_rep() | inf_integer()) -> 'pos_inf' | non_neg_integer().
width({Min, Max}) -> inf_max([width(Min), width(Max)]);
width(pos_inf) -> pos_inf;
width(neg_inf) -> pos_inf;
width(X) when is_integer(X), X >= 0 -> poswidth(X, 0);
width(X) when is_integer(X), X < 0 -> negwidth(X, 0).
-spec poswidth(non_neg_integer(), non_neg_integer()) -> non_neg_integer().
poswidth(X, N) ->
case X < (1 bsl N) of
true -> N;
false -> poswidth(X, N+1)
end.
-spec negwidth(neg_integer(), non_neg_integer()) -> non_neg_integer().
negwidth(X, N) ->
case X > (-1 bsl N) of
true -> N;
false -> negwidth(X, N+1)
end.
-spec range_band(range(), range()) -> range().
range_band(R1, R2) ->
{_Min1, Max1} = MM1 = range(R1),
{_Min2, Max2} = MM2 = range(R2),
Width1 = width(MM1),
Width2 = width(MM2),
Range =
case {classify_range(R1), classify_range(R2)} of
{minus_minus, minus_minus} ->
Width = inf_max([Width1, Width2]),
{inf_bsl(-1, Width), -1};
{minus_minus, minus_plus} ->
Width = inf_max([Width1, Width2]),
{inf_bsl(-1, Width), Max2};
{minus_minus, plus_plus} ->
{0, Max2};
{minus_plus, minus_minus} ->
Width = inf_max([Width1, Width2]),
{inf_bsl(-1, Width), Max1};
{minus_plus, minus_plus} ->
Width = inf_max([Width1, Width2]),
{inf_bsl(-1, Width), inf_max([Max1, Max2])};
{minus_plus, plus_plus} ->
{0, Max2};
{plus_plus, minus_minus} ->
{0, Max1};
{plus_plus, minus_plus} ->
{0, Max1};
{plus_plus, plus_plus} ->
{0, inf_min([Max1, Max2])}
end,
range_init(Range, false).
-spec range_bor(range(), range()) -> range().
range_bor(R1, R2) ->
{Min1, _Max1} = MM1 = range(R1),
{Min2, _Max2} = MM2 = range(R2),
Width1 = width(MM1),
Width2 = width(MM2),
Range =
case {classify_range(R1), classify_range(R2)} of
{minus_minus, minus_minus} ->
{inf_max([Min1, Min2]), -1};
{minus_minus, minus_plus} ->
{Min1, -1};
{minus_minus, plus_plus} ->
{Min1, -1};
{minus_plus, minus_minus} ->
{Min2, -1};
{minus_plus, minus_plus} ->
Width = inf_max([Width1, Width2]),
{inf_min([Min1, Min2]), inf_add(-1, inf_bsl(1, Width))};
{minus_plus, plus_plus} ->
Width = inf_max([Width1, Width2]),
{Min1, inf_add(-1, inf_bsl(1, Width))};
{plus_plus, minus_minus} ->
{Min2, -1};
{plus_plus, minus_plus} ->
Width = inf_max([Width1, Width2]),
{Min2, inf_add(-1, inf_bsl(1, Width))};
{plus_plus, plus_plus} ->
Width = inf_max([Width1, Width2]),
{0, inf_add(-1, inf_bsl(1, Width))}
end,
range_init(Range, false).
-spec classify_range(range()) -> 'minus_minus' | 'minus_plus' | 'plus_plus'.
classify_range(Range) ->
case range(Range) of
{neg_inf, Number} when is_integer(Number), Number < 0 -> minus_minus;
{neg_inf, Number} when is_integer(Number), Number >= 0 -> minus_plus;
{Number, pos_inf} when is_integer(Number), Number < 0 -> minus_plus;
{Number, pos_inf} when is_integer(Number), Number >= 0 -> plus_plus;
{neg_inf, pos_inf} -> minus_plus;
{Number1,Number2} when is_integer(Number1), is_integer(Number2) ->
classify_int_range(Number1, Number2)
end.
-spec classify_int_range(integer(), integer()) ->
'minus_minus' | 'minus_plus' | 'plus_plus'.
classify_int_range(Number1, _Number2) when Number1 >= 0 ->
plus_plus;
classify_int_range(_Number1, Number2) when Number2 < 0 ->
minus_minus;
classify_int_range(_Number1, _Number2) ->
minus_plus.
-spec range_bxor(range(), range()) -> range().
range_bxor(R1, R2) ->
{Min1, Max1} = MM1 = range(R1),
{Min2, Max2} = MM2 = range(R2),
Width1 = width(MM1),
Width2 = width(MM2),
Range =
case {classify_range(R1), classify_range(R2)} of
{minus_minus, minus_minus} ->
Width = inf_max([Width1, Width2]),
{0, inf_add(-1, inf_bsl(1, Width))};
{minus_minus, minus_plus} ->
MinWidth = inf_max([Width1, width({0,Max2})]),
MaxWidth = inf_max([Width1, width({Min2,-1})]),
{inf_bsl(-1, MinWidth), inf_add(-1, inf_bsl(1, MaxWidth))};
{minus_minus, plus_plus} ->
Width = inf_max([Width1, Width2]),
{inf_bsl(-1, Width), -1};
{minus_plus, minus_minus} ->
MinWidth = inf_max([Width2,width({0,Max1})]),
MaxWidth = inf_max([Width2,width({Min1,-1})]),
{inf_bsl(-1, MinWidth), inf_add(-1, inf_bsl(1, MaxWidth))};
{minus_plus, minus_plus} ->
Width = inf_max([Width1, Width2]),
{inf_bsl(-1, Width), inf_add(-1, inf_bsl(1, Width))};
{minus_plus, plus_plus} ->
MinWidth = inf_max([Width2,width({Min1,-1})]),
MaxWidth = inf_max([Width2,width({0,Max1})]),
{inf_bsl(-1, MinWidth), inf_add(-1, inf_bsl(1, MaxWidth))};
{plus_plus, minus_minus} ->
Width = inf_max([Width1, Width2]),
{inf_bsl(-1, Width), -1};
{plus_plus, minus_plus} ->
MinWidth = inf_max([Width1,width({Min2,-1})]),
MaxWidth = inf_max([Width1,width({0,Max2})]),
{inf_bsl(-1, MinWidth), inf_add(-1, inf_bsl(1, MaxWidth))};
{plus_plus, plus_plus} ->
Width = inf_max([Width1, Width2]),
{0, inf_add(-1, inf_bsl(1, Width))}
end,
range_init(Range, false).
%%---------------------------------------------------------------------------
%% Inf operations
%%---------------------------------------------------------------------------
-spec inf_max([inf_integer(),...]) -> inf_integer().
inf_max([H|T]) ->
lists:foldl(fun (Elem, Max) ->
case inf_geq(Elem, Max) of
false -> Max;
true -> Elem
end
end, H, T).
-spec inf_min([inf_integer(),...]) -> inf_integer().
inf_min([H|T]) ->
lists:foldl(fun (Elem, Min) ->
case inf_geq(Elem, Min) of
true -> Min;
false -> Elem
end
end, H, T).
-spec inf_abs(inf_integer()) -> 'pos_inf' | integer().
inf_abs(pos_inf) -> pos_inf;
inf_abs(neg_inf) -> pos_inf;
inf_abs(Number) when is_integer(Number), (Number < 0) -> - Number;
inf_abs(Number) when is_integer(Number) -> Number.
-spec inf_add(inf_integer(), inf_integer()) -> inf_integer().
inf_add(pos_inf, _Number) -> pos_inf;
inf_add(neg_inf, _Number) -> neg_inf;
inf_add(_Number, pos_inf) -> pos_inf;
inf_add(_Number, neg_inf) -> neg_inf;
inf_add(Number1, Number2) when is_integer(Number1), is_integer(Number2) ->
Number1 + Number2.
-spec inf_inv(inf_integer()) -> inf_integer().
inf_inv(pos_inf) -> neg_inf;
inf_inv(neg_inf) -> pos_inf;
inf_inv(Number) -> -Number.
-spec inf_geq(inf_integer(), inf_integer()) -> boolean().
inf_geq(pos_inf, _) -> true;
inf_geq(_, pos_inf) -> false;
inf_geq(_, neg_inf) -> true;
inf_geq(neg_inf, _) -> false;
inf_geq(A, B) -> A >= B.
-spec inf_greater_zero(inf_integer()) -> boolean().
inf_greater_zero(pos_inf) -> true;
inf_greater_zero(neg_inf) -> false;
inf_greater_zero(Number) when is_integer(Number), Number >= 0 -> true;
inf_greater_zero(Number) when is_integer(Number), Number < 0 -> false.
-spec inf_div(inf_integer(), inf_integer()) -> inf_integer().
inf_div(Number, 0) ->
Greater = inf_greater_zero(Number),
if Greater -> pos_inf;
true -> neg_inf
end;
inf_div(pos_inf, Number) ->
Greater = inf_greater_zero(Number),
if Greater -> pos_inf;
true -> neg_inf
end;
inf_div(neg_inf, Number) ->
Greater = inf_greater_zero(Number),
if Greater -> neg_inf;
true -> pos_inf
end;
inf_div(Number, pos_inf) ->
Greater = inf_greater_zero(Number),
if Greater -> pos_inf;
true -> neg_inf
end;
inf_div(Number, neg_inf) ->
Greater = inf_greater_zero(Number),
if Greater -> neg_inf;
true -> pos_inf
end;
inf_div(Number1, Number2) -> Number1 div Number2.
-spec inf_mult(inf_integer(), inf_integer()) -> inf_integer().
inf_mult(neg_inf, Number) ->
Greater = inf_greater_zero(Number),
if Greater -> neg_inf;
true -> pos_inf
end;
inf_mult(pos_inf, Number) ->
Greater = inf_greater_zero(Number),
if Greater -> pos_inf;
true -> neg_inf
end;
inf_mult(Number, pos_inf) -> inf_mult(pos_inf, Number);
inf_mult(Number, neg_inf) -> inf_mult(neg_inf, Number);
inf_mult(Number1, Number2) -> Number1 * Number2.
-spec inf_bsl(inf_integer(), inf_integer()) -> inf_integer().
inf_bsl(pos_inf, _) -> pos_inf;
inf_bsl(neg_inf, _) -> neg_inf;
inf_bsl(Number, pos_inf) when is_integer(Number), Number >= 0 -> pos_inf;
inf_bsl(_, pos_inf) -> neg_inf;
inf_bsl(Number, neg_inf) when is_integer(Number), Number >= 0 -> 0;
inf_bsl(_Number, neg_inf) -> -1;
inf_bsl(Number1, Number2) when is_integer(Number1), is_integer(Number2) ->
%% We can not shift left with a number which is not a fixnum. We
%% don't have enough memory.
Bits = ?BITS,
if Number2 > (Bits bsl 1) -> inf_bsl(Number1, pos_inf);
Number2 < (-Bits bsl 1) -> inf_bsl(Number1, neg_inf);
true -> Number1 bsl Number2
end.
%% State
-spec state__init(cfg(), data()) -> state().
state__init(Cfg, {MFA, ArgsFun, CallFun, FinalFun}) ->
Start = hipe_icode_cfg:start_label(Cfg),
Params = hipe_icode_cfg:params(Cfg),
Ranges = ArgsFun(MFA, Cfg),
%% io:format("MFA: ~w~nRanges: ~w~n", [MFA, Ranges]),
Liveness =
hipe_icode_ssa:ssa_liveness__analyze(hipe_icode_type:unannotate_cfg(Cfg)),
case lists:any(fun range__is_none/1, Ranges) of
true ->
FinalFun(MFA, [none_type()]),
throw(no_input);
false ->
NewParams = lists:zipwith(fun update_info/2, Params, Ranges),
NewCfg = hipe_icode_cfg:params_update(Cfg, NewParams),
Info = enter_defines(NewParams, #{}),
InfoMap = #{{Start, in} => Info},
#state{info_map=InfoMap, cfg=NewCfg, liveness=Liveness,
ret_type=none_type(),
lookup_fun=CallFun, result_action=FinalFun}
end.
-spec state__cfg(state()) -> cfg().
state__cfg(#state{cfg=Cfg}) ->
Cfg.
-spec state__bb(state(), label()) -> bb().
state__bb(#state{cfg=Cfg}, Label) ->
BB = hipe_icode_cfg:bb(Cfg, Label),
true = hipe_bb:is_bb(BB), % Just an assert
BB.
-spec state__bb_add(state(), label(), bb()) -> state().
state__bb_add(S=#state{cfg=Cfg}, Label, BB) ->
NewCfg = hipe_icode_cfg:bb_add(Cfg, Label, BB),
S#state{cfg=NewCfg}.
state__lookup_fun(#state{lookup_fun=LF}) -> LF.
state__result_action(#state{result_action=RA}) -> RA.
state__ret_type(#state{ret_type=RT}) -> RT.
state__ret_type_update(#state{ret_type=RT} = State, NewType) ->
TotType = sup(RT, NewType),
State#state{ret_type=TotType}.
state__info_in(S, Label) ->
state__info(S, {Label, in}).
state__info(#state{info_map=IM}, Key) ->
maps:get(Key, IM).
state__update_info(State, LabelInfo, Rewrite) ->
update_info(LabelInfo, State, [], Rewrite).
update_info([{Label,InfoIn}|Rest], State, LabelAcc, Rewrite) ->
case state__info_in_update(State, Label, InfoIn) of
fixpoint ->
if Rewrite ->
update_info(Rest, State, [Label|LabelAcc], Rewrite);
true ->
update_info(Rest, State, LabelAcc, Rewrite)
end;
NewState ->
update_info(Rest, NewState, [Label|LabelAcc], Rewrite)
end;
update_info([], State, LabelAcc, _Rewrite) ->
{State, LabelAcc}.
state__info_in_update(S=#state{info_map=IM,liveness=Liveness}, Label, Info) ->
LabelIn = {Label, in},
case IM of
#{LabelIn := OldInfo} ->
OldVars = maps:keys(OldInfo),
case join_info_in(OldVars, OldInfo, Info) of
fixpoint ->
fixpoint;
NewInfo ->
S#state{info_map=IM#{LabelIn := NewInfo}}
end;
_ ->
LiveIn = hipe_icode_ssa:ssa_liveness__livein(Liveness, Label),
NamesLiveIn = [hipe_icode:var_name(Var) || Var <- LiveIn,
hipe_icode:is_var(Var)],
OldInfo = #{},
case join_info_in(NamesLiveIn, OldInfo, Info) of
fixpoint ->
S#state{info_map=IM#{LabelIn => OldInfo}};
NewInfo ->
S#state{info_map=IM#{LabelIn => NewInfo}}
end
end.
join_info_in(Vars, OldInfo, NewInfo) ->
case join_info_in(Vars, OldInfo, NewInfo, #{}, false) of
{Res, true} -> Res;
{_, false} -> fixpoint
end.
join_info_in([Var|Left], Info1, Info2, Acc, Changed) ->
case {Info1, Info2} of
{#{Var := Val}, #{Var := Val}} ->
NewTree = Acc#{Var => Val},
join_info_in(Left, Info1, Info2, NewTree, Changed);
{#{Var := Val1}, #{Var := Val2}} ->
{NewChanged, NewVal} =
case sup(Val1, Val2) of
Val1 ->
{Changed, Val1};
Val ->
{true, Val}
end,
NewTree = Acc#{Var => NewVal},
join_info_in(Left, Info1, Info2, NewTree, NewChanged);
{_, #{Var := Val}} ->
NewTree = Acc#{Var => Val},
join_info_in(Left, Info1, Info2, NewTree, true);
{#{Var := Val}, _} ->
NewTree = Acc#{Var => Val},
join_info_in(Left, Info1, Info2, NewTree, Changed);
{_, _} ->
NewTree = Acc#{Var => none_type()},
join_info_in(Left, Info1, Info2, NewTree, true)
end;
join_info_in([], _Info1, _Info2, Acc, NewChanged) ->
{Acc, NewChanged}.
enter_defines([Def|Rest], Info) ->
enter_defines(Rest, enter_define(Def, Info));
enter_defines([], Info) -> Info.
enter_define({PossibleVar, Range = #range{}}, Info) ->
case hipe_icode:is_var(PossibleVar) of
true ->
Info#{hipe_icode:var_name(PossibleVar) => Range};
false ->
Info
end;
enter_define(PossibleVar, Info) ->
case hipe_icode:is_var(PossibleVar) of
true ->
case hipe_icode:variable_annotation(PossibleVar) of
{range_anno, #ann{range=Range}, _} ->
Info#{hipe_icode:var_name(PossibleVar) => Range};
_ ->
Info
end;
false ->
Info
end.
enter_vals(Ins, Info) ->
NewInfo = enter_defines(hipe_icode:args(Ins), Info),
enter_defines(hipe_icode:defines(Ins), NewInfo).
lookup(PossibleVar, Info) ->
case hipe_icode:is_var(PossibleVar) of
true ->
PossibleVarName = hipe_icode:var_name(PossibleVar),
case Info of
#{PossibleVarName := Val} -> Val;
_ -> none_type()
end;
false ->
none_type()
end.
%% _________________________________________________________________
%%
%% The worklist.
%%
init_work(State) ->
%% Labels = hipe_icode_cfg:reverse_postorder(state__cfg(State)),
Labels = [hipe_icode_cfg:start_label(state__cfg(State))],
{Labels, [], set_from_list(Labels)}.
get_work({[Label|Left], List, Set}) ->
NewWork = {Left, List, maps:remove(Label, Set)},
{Label, NewWork};
get_work({[], [], _Set}) ->
fixpoint;
get_work({[], List, Set}) ->
get_work({lists:reverse(List), [], Set}).
add_work(Work = {List1, List2, Set}, [Label|Left]) ->
case Set of
#{Label := _} ->
add_work(Work, Left);
_ ->
%% io:format("Adding work: ~w\n", [Label]),
add_work({List1, [Label|List2], Set#{Label => []}}, Left)
end;
add_work(Work, []) ->
Work.
convert_cfg_to_types(Cfg) ->
Lbls = hipe_icode_cfg:reverse_postorder(Cfg),
lists:foldl(fun convert_lbl_to_type/2, Cfg, Lbls).
convert_lbl_to_type(Lbl, Cfg) ->
BB = hipe_icode_cfg:bb(Cfg, Lbl),
Code = hipe_bb:code(BB),
NewCode = [convert_instr_to_type(I) || I <- Code],
hipe_icode_cfg:bb_add(Cfg, Lbl, hipe_bb:mk_bb(NewCode)).
convert_instr_to_type(I) ->
Uses = hipe_icode:uses(I),
UseSubstList = [{Use, convert_to_types(Use)} ||
Use <- Uses, hipe_icode:is_annotated_variable(Use)],
NewI = hipe_icode:subst_uses(UseSubstList, I),
Defs = hipe_icode:defines(NewI),
DefSubstList = [{Def, convert_to_types(Def)} ||
Def <- Defs, hipe_icode:is_annotated_variable(Def)],
hipe_icode:subst_defines(DefSubstList, NewI).
convert_to_types(VarOrReg) ->
Annotation =
case hipe_icode:variable_annotation(VarOrReg) of
{range_anno, Ann, _} ->
{type_anno, convert_ann_to_types(Ann), fun erl_types:t_to_string/1};
{type_anno, _, _} = TypeAnn ->
TypeAnn
end,
hipe_icode:annotate_variable(VarOrReg, Annotation).
convert_ann_to_types(#ann{range=#range{range={Min,Max}, other=false}}) ->
t_from_range_unsafe(Min, Max);
convert_ann_to_types(#ann{range=#range{range=empty, other=false}}) ->
t_none();
convert_ann_to_types(#ann{range=#range{other=true}, type=Type}) ->
Type.
%%=====================================================================
%% Icode Coordinator Callbacks
%%=====================================================================
-spec replace_nones([range()]) -> [range()].
replace_nones(Args) ->
[replace_none(Arg) || Arg <- Args].
replace_none(Arg) ->
case range__is_none(Arg) of
true -> any_type();
false -> Arg
end.
-spec update__info([range()], [range()]) -> {boolean(), [ann()]}.
update__info(NewRanges, OldRanges) ->
SupFun = fun (Ann, Range) ->
join_info(Ann, Range, fun safe_widen/3)
end,
EqFun = fun (X, Y) -> X =:= Y end,
ResRanges = lists:zipwith(SupFun, OldRanges, NewRanges),
Change = lists:zipwith(EqFun, ResRanges, OldRanges),
{lists:all(fun (X) -> X end, Change), ResRanges}.
-spec new__info([range()]) -> [ann()].
new__info(NewRanges) ->
[#ann{range=Range,count=1,type=t_any()} || Range <- NewRanges].
-spec return__info([ann()]) -> [range()].
return__info(Ranges) ->
[Range || #ann{range=Range} <- Ranges].
-spec return_none() -> [range(),...].
return_none() ->
[none_type()].
-spec return_none_args(cfg(), mfa()) -> [range()].
return_none_args(Cfg, {_M,_F,A}) ->
NoArgs =
case hipe_icode_cfg:is_closure(Cfg) of
true -> hipe_icode_cfg:closure_arity(Cfg) + 1;
false -> A
end,
lists:duplicate(NoArgs, none_type()).
-spec return_any_args(cfg(), mfa()) -> [range()].
return_any_args(Cfg, {_M,_F,A}) ->
NoArgs =
case hipe_icode_cfg:is_closure(Cfg) of
true -> hipe_icode_cfg:closure_arity(Cfg) + 1;
false -> A
end,
lists:duplicate(NoArgs, any_type()).
%%=====================================================================
next_up_limit(X) when is_integer(X), X < 0 -> 0;
next_up_limit(X) when is_integer(X), X < 255 -> 255;
next_up_limit(X) when is_integer(X), X < 16#10ffff -> 16#10ffff;
next_up_limit(X) when is_integer(X), X < 16#7ffffff -> 16#7ffffff;
next_up_limit(X) when is_integer(X), X < 16#7fffffff -> 16#7fffffff;
next_up_limit(X) when is_integer(X), X < 16#ffffffff -> 16#ffffffff;
next_up_limit(X) when is_integer(X), X < 16#fffffffffff -> 16#fffffffffff;
next_up_limit(X) when is_integer(X), X < 16#7fffffffffffffff -> 16#7fffffffffffffff;
next_up_limit(_X) -> pos_inf.
next_down_limit(X) when is_integer(X), X > 0 -> 0;
next_down_limit(X) when is_integer(X), X > -256 -> -256;
next_down_limit(X) when is_integer(X), X > -16#10ffff -> -16#10ffff;
next_down_limit(X) when is_integer(X), X > -16#8000000 -> -16#8000000;
next_down_limit(X) when is_integer(X), X > -16#80000000 -> -16#80000000;
next_down_limit(X) when is_integer(X), X > -16#800000000000000 -> -16#800000000000000;
next_down_limit(_X) -> neg_inf.
%%--------------------------------------------------------------------
%% Sets
-type set(E) :: #{E => []}.
set_from_list([]) -> #{};
set_from_list(L) ->
maps:from_list([{E, []} || E <- L]).
not_visited([], _) -> [];
not_visited([E|T], M) ->
case M of
#{E := []} -> not_visited(T, M);
_ -> [E|not_visited(T, M)]
end.
set_union(A, B) -> maps:merge(A, B).