diff --git a/lib/compiler/src/beam_bounds.erl b/lib/compiler/src/beam_bounds.erl index a85044604c81..ee6753d19b4d 100644 --- a/lib/compiler/src/beam_bounds.erl +++ b/lib/compiler/src/beam_bounds.erl @@ -42,172 +42,87 @@ -type bool_result() :: 'true' | 'false' | 'maybe'. -type op() :: atom(). +-import(lists, [foldl/3]). + %% Maximum size of integers in bits to keep ranges for. -define(NUM_BITS, 128). -spec bounds(op(), range()) -> range_result(). bounds('bnot', R0) -> - case R0 of - {A,B} -> - R = {inf_add(inf_neg(B), -1), inf_add(inf_neg(A), -1)}, - normalize(R); - _ -> - any - end; + [A,B] = canonical_arg(R0), + R = {inf_add(inf_neg(B), -1), inf_add(inf_neg(A), -1)}, + normalize(R); bounds(abs, R) -> - case R of - {A,B} when is_integer(A), is_integer(B) -> - Min = 0, - Max = max(abs(A), abs(B)), - {Min,Max}; - _ -> - {0,'+inf'} - end. + [A,B] = canonical_arg(R), + AbsA = inf_abs(A), + AbsB = inf_abs(B), + Min = case {inf_sign(A),inf_sign(B)} of + {'-','+'} -> 0; + {_,_} -> inf_min(AbsA, AbsB) + end, + Max = inf_max(AbsA, AbsB), + normalize({Min,Max}). -spec bounds(op(), range(), range()) -> range_result(). bounds('+', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> - normalize({A+C,B+D}); - {{'-inf',B}, {_C,D}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> - normalize({'-inf',B+D}); - {{_A,B}, {'-inf',D}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> - normalize({'-inf',B+D}); - {{A,'+inf'}, {C,_D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0 -> - normalize({A+C,'+inf'}); - {{A,_B}, {C,'+inf'}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0 -> - normalize({A+C,'+inf'}); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + normalize({inf_add(A, C), inf_add(B, D)}); bounds('-', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> + case canonical_args(R1, R2) of + [A,B,C,D] when is_integer(A), + is_integer(B), + is_integer(C), + is_integer(D) -> normalize({A-D,B-C}); - {{A,'+inf'}, {_C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> + [A,'+inf',_C,D] when is_integer(A), is_integer(D) -> normalize({A-D,'+inf'}); - {{_A,B}, {C,'+inf'}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0 -> + [_A,B,C,'+inf'] when is_integer(B), is_integer(C) -> normalize({'-inf',B-C}); - {{'-inf',B}, {C,_D}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0 -> + ['-inf',B,C,_D] when is_integer(B), is_integer(C) -> normalize({'-inf',B-C}); - {{A,_B}, {'-inf',D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> + [A,_B,'-inf',D] when is_integer(A), is_integer(D) -> normalize({A-D,'+inf'}); - {_, _} -> + [_,_,_,_] -> any end; bounds('*', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> - All = [X * Y || X <- [A,B], Y <- [C,D]], - Min = lists:min(All), - Max = lists:max(All), - normalize({Min,Max}); - {{A,'+inf'}, {C,'+inf'}} when abs(A) bsr ?NUM_BITS =:= 0, A >= 0, - abs(C) bsr ?NUM_BITS =:= 0, C >= 0 -> - {A*C,'+inf'}; - {{A,'+inf'}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0, - C >= 0 -> - {min(A*C, A*D),'+inf'}; - {{'-inf',B}, {C,D}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0, - C >= 0 -> - {'-inf',max(B*C, B*D)}; - {{A,B}, {'-inf',_}} when is_integer(A), is_integer(B) -> - bounds('*', R2, R1); - {{A,B}, {_,'+inf'}} when is_integer(A), is_integer(B) -> - bounds('*', R2, R1); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + All = [inf_mul(X, Y) || X <- [A,B], Y <- [C,D]], + Min = foldl(fun inf_min/2, '+inf', All), + Max = foldl(fun inf_max/2, '-inf', All), + normalize({Min,Max}); bounds('div', R1, R2) -> - div_bounds(R1, R2); + [A,B,C,D] = canonical_args(R1, R2), + div_bounds({A,B}, {C,D}); bounds('rem', R1, R2) -> - rem_bounds(R1, R2); + [A,B,C,D] = canonical_args(R1, R2), + rem_bounds({A,B}, {C,D}); bounds('band', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when A bsr ?NUM_BITS =:= 0, A >= 0, - C bsr ?NUM_BITS =:= 0, C >= 0, - is_integer(B), is_integer(D) -> - Min = min_band(A, B, C, D), - Max = max_band(A, B, C, D), - {Min,Max}; - {_, {C,D}} when is_integer(C), C >= 0 -> - {0,D}; - {{A,B}, _} when is_integer(A), A >= 0 -> - {0,B}; - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + normalize(min_max_band(A, B, C, D)); bounds('bor', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when A =:= '-inf' orelse abs(A) bsr ?NUM_BITS =:= 0, - C =:= '-inf' orelse abs(C) bsr ?NUM_BITS =:= 0, - B =:= '+inf' orelse abs(B) bsr ?NUM_BITS =:= 0, - D =:= '+inf' orelse abs(D) bsr ?NUM_BITS =:= 0 -> - Min = min_bor(A, B, C, D), - Max = max_bor(A, B, C, D), - normalize({Min,Max}); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + normalize(min_max_bor(A, B, C, D)); bounds('bxor', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when A bsr ?NUM_BITS =:= 0, A >= 0, - C bsr ?NUM_BITS =:= 0, C >= 0, - is_integer(B), is_integer(D) -> - Max = max_bxor(A, B, C, D), - {0,Max}; - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + normalize(min_max_bxor(A, B, C, D)); bounds('bsr', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when is_integer(C), C >= 0 -> - Min = inf_min(inf_bsr(A, C), inf_bsr(A, D)), - Max = inf_max(inf_bsr(B, C), inf_bsr(B, D)), - normalize({Min,Max}); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + Min = inf_min(inf_bsr(A, C), inf_bsr(A, D)), + Max = inf_max(inf_bsr(B, C), inf_bsr(B, D)), + normalize({Min,Max}); bounds('bsl', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when A =:= '-inf' orelse abs(A) bsr ?NUM_BITS =:= 0, - B =:= '+inf' orelse abs(B) bsr ?NUM_BITS =:= 0 -> - Min = inf_min(inf_bsl(A, C), inf_bsl(A, D)), - Max = inf_max(inf_bsl(B, C), inf_bsl(B, D)), - normalize({Min,Max}); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + Min = inf_min(inf_bsl(A, C), inf_bsl(A, D)), + Max = inf_max(inf_bsl(B, C), inf_bsl(B, D)), + normalize({Min,Max}); bounds(max, R1, R2) -> - {A,B} = expand(R1), - {C,D} = expand(R2), + [A,B,C,D] = canonical_args(R1, R2), normalize({inf_max(A, C),inf_max(B, D)}); bounds(min, R1, R2) -> - {A,B} = expand(R1), - {C,D} = expand(R2), + [A,B,C,D] = canonical_args(R1, R2), normalize({inf_min(A, C),inf_min(B, D)}). -spec relop(relop(), range(), range()) -> bool_result(). @@ -298,13 +213,15 @@ div_bounds({A,B}, {C,D}) when is_integer(A), is_integer(B), Min = lists:min(All), Max = lists:max(All), normalize({Min,Max}); -div_bounds({A,'+inf'}, {C,D}) when is_integer(C), C > 0, is_integer(D) -> - Min = min(A div C, A div D), +div_bounds({A,'+inf'}, {C,D}) when is_integer(A), + is_integer(C), C > 0 -> + Min = min(A div C, inf_div(A, D)), Max = '+inf', normalize({Min,Max}); -div_bounds({'-inf',B}, {C,D}) when is_integer(C), C > 0, is_integer(D) -> +div_bounds({'-inf',B}, {C,D}) when is_integer(B), + is_integer(C), C > 0 -> Min = '-inf', - Max = max(B div C, B div D), + Max = max(B div C, inf_div(B, D)), normalize({Min,Max}); div_bounds({A,B}, _) when is_integer(A), is_integer(B) -> Max = max(abs(A), abs(B)), @@ -334,65 +251,51 @@ rem_bounds({A,B}, _) -> %% include zero. Min = inf_min(0, A), Max = inf_max(0, B), - normalize({Min,Max}); -rem_bounds(_, _) -> - any. + normalize({Min,Max}). -min_band(A, B, C, D) -> - M = 1 bsl (upper_bit(A bor C) + 1), - min_band(A, B, C, D, M). +min_max_band(A, B, C, D) -> + {Min,Max} = min_max_bor(inf_bnot(B), inf_bnot(A), + inf_bnot(D), inf_bnot(C)), + {inf_bnot(Max),inf_bnot(Min)}. -min_band(A, _B, C, _D, 0) -> - A band C; -min_band(A, B, C, D, M) -> - if - (bnot A) band (bnot C) band M =/= 0 -> - case (A bor M) band -M of - NewA when NewA =< B -> - min_band(NewA, B, C, D, 0); - _ -> - case (C bor M) band -M of - NewC when NewC =< D -> - min_band(A, B, NewC, D, 0); - _ -> - min_band(A, B, C, D, M bsr 1) - end - end; +min_max_bor(A, B, C, D) -> + case inf_le(A, C) of true -> - min_band(A, B, C, D, M bsr 1) + do_min_max_bor(A, B, C, D); + false -> + do_min_max_bor(C, D, A, B) end. -max_band(A, B, C, D) -> - M = 1 bsl upper_bit(B bxor D), - max_band(A, B, C, D, M). - -max_band(_A, B, _C, D, 0) -> - B band D; -max_band(A, B, C, D, M) -> - if - B band (bnot D) band M =/= 0 -> - case (B band (bnot M)) bor (M - 1) of - NewB when NewB >= A -> - max_band(A, NewB, C, D, 0); - _ -> - max_band(A, B, C, D, M bsr 1) - end; - (bnot B) band D band M =/= 0 -> - case (D band (bnot M)) bor (M - 1) of - NewD when NewD >= C -> - max_band(A, B, C, NewD, 0); - _ -> - max_band(A, B, C, D, M bsr 1) - end; - true -> - max_band(A, B, C, D, M bsr 1) +do_min_max_bor(A, B, C, D) -> + case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of + {'-','-','-','-'} -> + {min_bor(A, B, C, D), + max_bor(A, B, C, D)}; + {'-','-','-','+'} -> + {A, -1}; + {'-','-','+','+'} -> + {min_bor(A, B, C, D), + max_bor(A, B, C, D)}; + {'-','+','-','-'} -> + {C, -1}; + {'-','+','-','+'} -> + {inf_min(A, C), max_bor(0, B, 0, D)}; + {'-','+','+','+'} -> + {min_bor(A, -1, C, D), + max_bor(A, B, C, D)}; + {'+','+','+','+'} -> + {min_bor(A, B, C, D), + max_bor(A, B, C, D)} end. +min_bor('-inf', B, C, D) when is_integer(C), C < 0 -> + M = 1 bsl upper_bit(C), + min_bor(0, B, C, D, M); min_bor(A, B, C, D) -> - case inf_lt(inf_min(A, C), 0) of - true -> + case inf_min(A, C) of + '-inf' -> '-inf'; - false -> + _ -> M = 1 bsl upper_bit(A bxor C), min_bor(A, B, C, D, M) end. @@ -419,20 +322,20 @@ min_bor(A, B, C, D, M) -> min_bor(A, B, C, D, M bsr 1) end. -max_bor(A0, B, C0, D) -> - A = inf_max(A0, 0), - C = inf_max(C0, 0), - case inf_max(B, D) of +max_bor(A, B0, C, D0) -> + {Intersection,B,D} = + case {B0,D0} of + {_,'+inf'} when B0 < 0 -> + {B0,B0,-1}; + {_,_} when is_integer(B0), is_integer(D0) -> + {B0 band D0,B0,D0}; + {_,_} -> + {'+inf',B0,D0} + end, + case Intersection of '+inf' -> '+inf'; - Max when Max < 0 -> - %% Both B and D are negative. The intersection would be - %% infinite. - -1; _ -> - %% At least one of B and D are positive. The intersection - %% has a finite size. - Intersection = B band D, M = 1 bsl upper_bit(Intersection), max_bor(Intersection, A, B, C, D, M) end. @@ -442,14 +345,17 @@ max_bor(_Intersection, _A, B, _C, D, 0) -> max_bor(Intersection, A, B, C, D, M) -> if Intersection band M =/= 0 -> - case (B - M) bor (M - 1) of - NewB when NewB >= A -> + NewM = M - 1, + NewB = (B - M) bor NewM, + case inf_ge(NewB, A) of + true -> max_bor(Intersection, A, NewB, C, D, 0); - _ -> - case (D - M) bor (M - 1) of - NewD when NewD >= C -> + false -> + NewD = (D - M) bor NewM, + case inf_ge(NewD, C) of + true -> max_bor(Intersection, A, B, C, NewD, 0); - _ -> + false -> max_bor(Intersection, A, B, C, D, M bsr 1) end end; @@ -457,6 +363,89 @@ max_bor(Intersection, A, B, C, D, M) -> max_bor(Intersection, A, B, C, D, M bsr 1) end. +min_max_bxor(A, B, C, D) -> + case inf_le(A, C) of + true -> + do_min_max_bxor(A, B, C, D); + false -> + do_min_max_bxor(C, D, A, B) + end. + +do_min_max_bxor(A, B, C, D) when is_integer(A), + is_integer(B), + is_integer(C), + is_integer(D) -> + case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of + {'-','-','-','-'} -> + {min_bxor(A, B, C, D), + max_bxor(A, B, C, D)}; + {'-','-','-','+'} -> + %% Bounds are not tight. + MinMin = min(A, C), + Max = max(bnot MinMin, D), + {min_bxor(MinMin, -1, 0, D), + max_bxor(0, Max, 0, Max)}; + {'-','-','+','+'} -> + {min_bxor(A, B, C, D), + max_bxor(A, B, C, D)}; + {'-','+','-','-'} -> + %% Bounds are not tight. + MinMin = min(A, C), + Max = max(bnot MinMin, B), + {min_bxor(MinMin, -1, 0, B), + max_bxor(0, Max, 0, Max)}; + {'-','+','-','+'} -> + %% Bounds are not tight. + MinMin = min(A, C), + MaxMax = max(B, D), + Max = max(bnot MinMin, MaxMax), + {min_bxor(MinMin, -1, 0, MaxMax), + max_bxor(0, Max, 0, Max)}; + {'-','+','+','+'} -> + {min_bxor(A, -1, C, D), + max_bxor(A, B, C, D)}; + {'+','+','+','+'} -> + {min_bxor(A, B, C, D), + max_bxor(A, B, C, D)} + end; +do_min_max_bxor(A, B, C, D) -> + case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of + {'+','+','+','+'} -> + {min_bxor(A, B, C, D), '+inf'}; + {'-','-','+','+'} when is_integer(D) -> + {'-inf',max_bxor(A, B, C, D)}; + {'-','-','+','+'} -> + {'-inf',-1}; + _ -> + {'-inf','+inf'} + end. + +min_bxor(A, B, C, D) -> + M = 1 bsl upper_bit(A bor C), + min_bxor(A, B, C, D, M). + +min_bxor(A, _B, C, _D, 0) -> + A bxor C; +min_bxor(A, B, C, D, M) -> + if + (bnot A) band C band M =/= 0 -> + case (A bor M) band -M of + NewA when NewA =< B -> + min_bxor(NewA, B, C, D, M bsr 1); + _ -> + min_bxor(A, B, C, D, M bsr 1) + end; + A band (bnot C) band M =/= 0 -> + case (C bor M) band -M of + NewC when NewC =< D -> + min_bxor(A, B, NewC, D, M bsr 1); + _ -> + min_bxor(A, B, C, D, M bsr 1) + end; + true -> + min_bxor(A, B, C, D, M bsr 1) + end. + max_bxor(A, B, C, D) -> M = 1 bsl upper_bit(B band D), max_bxor(A, B, C, D, M). @@ -466,14 +455,16 @@ max_bxor(_A, B, _C, D, 0) -> max_bxor(A, B, C, D, M) -> if B band D band M =/= 0 -> - case (B - M) bor (M - 1) of - NewB when NewB >= A -> + NewB = (B - M) bor (M - 1), + case inf_ge(NewB, A) of + true -> max_bxor(A, NewB, C, D, M bsr 1); - _ -> - case (D - M) bor (M - 1) of - NewD when NewD >= C -> + false -> + NewD = (D - M) bor (M - 1), + case inf_ge(NewD, C) of + true -> max_bxor(A, B, C, NewD, M bsr 1); - _ -> + false -> max_bxor(A, B, C, D, M bsr 1) end end; @@ -481,6 +472,8 @@ max_bxor(A, B, C, D, M) -> max_bxor(A, B, C, D, M bsr 1) end. +upper_bit(Val) when Val < 0 -> + ?NUM_BITS + 1; upper_bit(Val) -> upper_bit_1(Val, 0). @@ -507,6 +500,20 @@ infer_relop_types_1('>', {A,B}, {C,D}) -> Right = normalize({C,clamp(inf_add(B, -1), C, D)}), {Left,Right}. +canonical_args(R1, R2) -> + canonical_arg(R1) ++ canonical_arg(R2). + +canonical_arg(R0) -> + {A,B} = expand(R0), + case [inf_cap(A),inf_cap(B)] of + ['-inf','-inf'] -> + ['-inf',-1]; + ['+inf','+inf'] -> + [0,'+inf']; + R -> + R + end. + %%% %%% Handling of ranges. %%% @@ -545,8 +552,28 @@ inf_neg('-inf') -> '+inf'; inf_neg('+inf') -> '-inf'; inf_neg(N) -> -N. -inf_add(Int, N) when is_integer(Int) -> Int + N; -inf_add(Inf, _N) -> Inf. +inf_add(A, B) when is_integer(A), is_integer(B) -> A + B; +inf_add(Inf, Inf) when is_atom(Inf) -> Inf; +inf_add(Inf, _) when is_atom(Inf) -> Inf; +inf_add(_, Inf) when is_atom(Inf) -> Inf. + +inf_mul(A, B) when is_integer(A), is_integer(B) -> + A * B; +inf_mul(A, B) -> + case {inf_sign(A),inf_sign(B)} of + {'-','-'} -> '+inf'; + {'+','+'} -> '+inf'; + {_,_} -> '-inf' + end. + +inf_div(A, B) when is_integer(A), is_integer(B) -> + A div B; +inf_div(A, '+inf') when is_integer(A) -> + 0. + +inf_abs('-inf') -> '+inf'; +inf_abs('+inf') -> '+inf'; +inf_abs(N) when is_integer(N) -> abs(N). inf_bsr('-inf', _S) -> '-inf'; @@ -581,3 +608,20 @@ inf_ge(A, B) -> A >= B. inf_le(A, B) -> inf_ge(B, A). inf_gt(A, B) -> inf_lt(B, A). + +inf_sign('-inf') -> '-'; +inf_sign('+inf') -> '+'; +inf_sign(N) when N < 0 -> '-'; +inf_sign(_) -> '+'. + +inf_cap(N) when abs(N) bsr ?NUM_BITS =/= 0 -> + case inf_lt(N, 0) of + true -> '-inf'; + false -> '+inf' + end; +inf_cap(N) -> + N. + +inf_bnot('-inf') -> '+inf'; +inf_bnot('+inf') -> '-inf'; +inf_bnot(N) -> bnot N. diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl index c982e2f4015c..8bd68cdf6ab8 100644 --- a/lib/compiler/src/beam_call_types.erl +++ b/lib/compiler/src/beam_call_types.erl @@ -492,9 +492,7 @@ types(erlang, 'bnot', [_]) -> %% between the ranges calculated by the type pass and by %% beam_validator. %% - %% Therefore, don't attempt to calculate a range now. Save the - %% range calculation for the opt_ranges pass (arith_type/2), which - %% is only run once. + %% Therefore, don't attempt to calculate a range now. sub_unsafe(#t_integer{}, [#t_integer{}]); %% Fixed-type arithmetic @@ -1280,10 +1278,14 @@ beam_bounds_type(Op, Type, [LHS, RHS]) -> get_range(LHS, RHS, Type) -> get_range(meet(LHS, Type), meet(RHS, Type)). -get_range(#t_float{}=LHS, #t_float{}=RHS) -> - {float, get_range(LHS), get_range(RHS)}; get_range(#t_integer{}=LHS, #t_integer{}=RHS) -> {integer, get_range(LHS), get_range(RHS)}; +get_range(#t_float{}=LHS, #t_float{}=RHS) -> + {float, get_range(LHS), get_range(RHS)}; +get_range(#t_float{}=LHS, RHS) -> + {float, get_range(LHS), get_range(RHS)}; +get_range(LHS, #t_float{}=RHS) -> + {float, get_range(LHS), get_range(RHS)}; get_range(LHS, RHS) -> {number, get_range(LHS), get_range(RHS)}. diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index d50350a10c36..9c3f59862098 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -272,6 +272,7 @@ module_passes(Opts) -> %% are repeated as required. repeated_passes(Opts) -> Ps = [?PASS(ssa_opt_live), + ?PASS(ssa_opt_sw), ?PASS(ssa_opt_is_between), ?PASS(ssa_opt_ne), ?PASS(ssa_opt_bs_create_bin), @@ -301,7 +302,6 @@ epilogue_module_passes(Opts) -> early_epilogue_passes(Opts) -> Ps = [?PASS(ssa_opt_type_finish), ?PASS(ssa_opt_float), - ?PASS(ssa_opt_sw), ?PASS(ssa_opt_no_reuse), ?PASS(ssa_opt_deoptimize_update_tuple)], passes_1(Ps, Opts). @@ -321,8 +321,7 @@ late_epilogue_passes(Opts) -> ?PASS(ssa_opt_get_tuple_element), ?PASS(ssa_opt_tail_literals), ?PASS(ssa_opt_trim_unreachable), - ?PASS(ssa_opt_unfold_literals), - ?PASS(ssa_opt_ranges)], + ?PASS(ssa_opt_unfold_literals)], passes_1(Ps, Opts). passes_1(Ps, Opts0) -> @@ -461,9 +460,6 @@ ssa_opt_merge_blocks({#opt_st{ssa=Blocks0}=St, FuncDb}) -> Blocks = beam_ssa:merge_blocks(RPO, Blocks0), {St#opt_st{ssa=Blocks}, FuncDb}. -ssa_opt_ranges({#opt_st{ssa=Blocks}=St, FuncDb}) -> - {St#opt_st{ssa=beam_ssa_type:opt_ranges(Blocks)}, FuncDb}. - %%% %%% Merges updates that cannot fail, for example two consecutive updates of the %%% same record. diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 7b1da625398b..22fe205d234d 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -30,7 +30,7 @@ -module(beam_ssa_type). -moduledoc false. --export([opt_start/2, opt_continue/4, opt_finish/3, opt_ranges/1]). +-export([opt_start/2, opt_continue/4, opt_finish/3]). -include("beam_ssa_opt.hrl"). -include("beam_types.hrl"). @@ -133,11 +133,15 @@ opt_start_1([], _CommittedArgs, StMap, FuncDb, _MetaCache) -> %% [1] http://www.it.uu.se/research/group/hipe/papers/succ_types.pdf %% +-type uvs() :: #{beam_ssa:b_var() => {_,non_neg_integer()}}. + -record(sig_st, { wl = wl_new() :: worklist(), committed = #{} :: #{ func_id() => [type()] }, updates = #{} :: #{ func_id() => [type()] }, - meta_cache = #{} :: meta_cache()}). + meta_cache = #{} :: meta_cache(), + unstable = #{} :: #{beam_ssa:label() => uvs()}, + uvs = #{} :: uvs()}). signatures(StMap, FuncDb0) -> State0 = init_sig_st(StMap, FuncDb0), @@ -220,7 +224,15 @@ sig_function_1(Id, StMap, State0, FuncDb) -> Wl0 = State1#sig_st.wl, - {State, SuccTypes} = sig_bs(Linear, Ds, Ls, FuncDb, #{}, [], Meta, State2), + Unstable0 = State1#sig_st.unstable, + Uvs0 = maps:get(Id, Unstable0, #{}), + State3 = State2#sig_st{uvs=Uvs0}, + + {State4, SuccTypes} = sig_bs(Linear, Ds, Ls, FuncDb, #{}, [], Meta, State3), + + Uvs = State4#sig_st.uvs, + Unstable = Unstable0#{Id => Uvs}, + State = State4#sig_st{unstable=Unstable,uvs=#{}}, WlChanged = wl_changed(Wl0, State#sig_st.wl), #{ Id := #func_info{succ_types=SuccTypes0}=Entry0 } = FuncDb, @@ -307,12 +319,16 @@ sig_is([#b_set{op=make_fun,args=Args0,dst=Dst}=I0|Is], Ts = update_types(I, Ts0, Ds0), Ds = Ds0#{ Dst => I }, sig_is(Is, Ts, Ds, Ls, Fdb, Sub0, State); -sig_is([I0 | Is], Ts0, Ds0, Ls, Fdb, Sub0, State) -> - case simplify(I0, Ts0, Ds0, Ls, Sub0) of +sig_is([I0 | Is], Ts0, Ds0, Ls, Fdb, Sub0, State0) -> + Uvs0 = State0#sig_st.uvs, + case simplify(I0, Uvs0, Ts0, Ds0, Ls, Sub0) of {#b_set{}, Ts, Ds} -> + sig_is(Is, Ts, Ds, Ls, Fdb, Sub0, State0); + {#b_set{}, Ts, Ds, Uvs} -> + State = State0#sig_st{uvs=Uvs}, sig_is(Is, Ts, Ds, Ls, Fdb, Sub0, State); Sub when is_map(Sub) -> - sig_is(Is, Ts0, Ds0, Ls, Fdb, Sub, State) + sig_is(Is, Ts0, Ds0, Ls, Fdb, Sub, State0) end; sig_is([], Ts, Ds, _Ls, _Fdb, Sub, State) -> {Ts, Ds, Sub, State}. @@ -579,10 +595,13 @@ opt_is([#b_set{op=make_fun,args=Args0,dst=Dst}=I0|Is], Ds = Ds0#{ Dst => I }, opt_is(Is, Ts, Ds, Ls, Fdb, Sub0, Meta, [I|Acc]); opt_is([I0 | Is], Ts0, Ds0, Ls, Fdb, Sub0, Meta, Acc) -> - case simplify(I0, Ts0, Ds0, Ls, Sub0) of + case simplify(I0, none, Ts0, Ds0, Ls, Sub0) of {#b_set{}=I1, Ts, Ds} -> I = opt_anno_types(I1, Ts), opt_is(Is, Ts, Ds, Ls, Fdb, Sub0, Meta, [I | Acc]); + {#b_set{}=I1, Ts, Ds, _} -> + I = opt_anno_types(I1, Ts), + opt_is(Is, Ts, Ds, Ls, Fdb, Sub0, Meta, [I | Acc]); Sub when is_map(Sub) -> opt_is(Is, Ts0, Ds0, Ls, Fdb, Sub, Meta, Acc) end; @@ -659,12 +678,6 @@ benefits_from_type_anno(is_tagged_tuple, _Args) -> true; benefits_from_type_anno(call, [#b_var{} | _]) -> true; -benefits_from_type_anno({float,convert}, _Args) -> - %% Note: The {float,convert} instruction does not exist when - %% the main type optimizer pass is run. It is created and - %% annotated by ssa_opt_float1 in beam_ssa_opt, and can also - %% be annotated by opt_ranges/1. - true; benefits_from_type_anno(get_map_element, _Args) -> true; benefits_from_type_anno(has_map_field, _Args) -> @@ -802,165 +815,6 @@ opt_finish_1([Arg | Args], [TypeMap | TypeMaps], Acc0) -> opt_finish_1([], [], Acc) -> Acc. -%%% -%%% This sub pass is run once after the main type sub pass -%%% to annotate more instructions with integer ranges. -%%% -%%% The main type sub pass annotates certain instructions with -%%% their types to help the JIT generate better code. -%%% -%%% Example: -%%% -%%% foo(N0) -> -%%% N1 = N0 band 3, -%%% N = N1 + 1, % N1 is in 0..3 -%%% element(N, -%%% {zero,one,two,three}). -%%% -%%% The main type pass is able to figure out the range for `N1` but -%%% not for `N`. The reason is that the type pass iterates until it -%%% reaches a fixpoint. To guarantee that it will converge, ranges for -%%% results must only be calculated for operations that retain or -%%% shrink the ranges of their arguments. -%%% -%%% Therefore, to ensure convergence, the main type pass can only -%%% safely calculate ranges for results of operations such as `and`, -%%% `bsr`, and `rem`, but not for operations such as `+`, '-', '*', -%%% and `bsl`. -%%% -%%% This sub pass will start from the types found in the annotations -%%% and propagate them forward through arithmetic instructions within -%%% the same function. -%%% -%%% For the example, this sub pass adds a new annotation for `N`: -%%% -%%% foo(N0) -> -%%% N1 = N0 band 3, -%%% N = N1 + 1, % N1 is in 0..3 -%%% element(N, % N is in 1..4 -%%% {zero,one,two,three}). -%%% -%%% With a known range and known tuple size, the JIT is able to remove -%%% all range checks for the `element/2` instruction. -%%% - --spec opt_ranges(Blocks0) -> Blocks when - Blocks0 :: beam_ssa:block_map(), - Blocks :: beam_ssa:block_map(). - -opt_ranges(Blocks) -> - RPO = beam_ssa:rpo(Blocks), - Tss = #{0 => #{}, ?EXCEPTION_BLOCK => #{}}, - ranges(RPO, Tss, Blocks). - -ranges([L|Ls], Tss0, Blocks0) -> - #b_blk{is=Is0} = Blk0 = map_get(L, Blocks0), - Ts0 = map_get(L, Tss0), - {Is,Ts} = ranges_is(Is0, Ts0, []), - Blk = Blk0#b_blk{is=Is}, - Blocks = Blocks0#{L := Blk}, - Tss = ranges_successors(beam_ssa:successors(Blk), Ts, Tss0), - ranges(Ls, Tss, Blocks); -ranges([], _Tss, Blocks) -> Blocks. - -ranges_is([#b_set{op=Op,args=Args}=I0|Is], Ts0, Acc) -> - case benefits_from_type_anno(Op, Args) of - false -> - ranges_is(Is, Ts0, [I0|Acc]); - true -> - I = update_anno_types(I0, Ts0), - Ts = ranges_propagate_types(I, Ts0), - ranges_is(Is, Ts, [I|Acc]) - end; -ranges_is([], Ts, Acc) -> - {reverse(Acc),Ts}. - -ranges_successors([?EXCEPTION_BLOCK|Ls], Ts, Tss) -> - ranges_successors(Ls, Ts, Tss); -ranges_successors([L|Ls], Ts0, Tss0) -> - case Tss0 of - #{L := Ts1} -> - Ts = join_types(Ts0, Ts1), - Tss = Tss0#{L := Ts}, - ranges_successors(Ls, Ts0, Tss); - #{} -> - Tss = Tss0#{L => Ts0}, - ranges_successors(Ls, Ts0, Tss) - end; -ranges_successors([], _, Tss) -> Tss. - -ranges_propagate_types(#b_set{anno=Anno,op={bif,_}=Op,args=Args,dst=Dst}, Ts) -> - case Anno of - #{arg_types := ArgTypes0} -> - ArgTypes = ranges_get_arg_types(Args, 0, ArgTypes0), - case beam_call_types:arith_type(Op, ArgTypes) of - any -> Ts; - T -> Ts#{Dst => T} - end; - #{} -> - Ts - end; -ranges_propagate_types(_, Ts) -> Ts. - -ranges_get_arg_types([#b_var{}|As], Index, ArgTypes) -> - case ArgTypes of - #{Index := Type} -> - [Type|ranges_get_arg_types(As, Index + 1, ArgTypes)]; - #{} -> - [any|ranges_get_arg_types(As, Index + 1, ArgTypes)] - end; -ranges_get_arg_types([#b_literal{val=Value}|As], Index, ArgTypes) -> - Type = beam_types:make_type_from_value(Value), - [Type|ranges_get_arg_types(As, Index + 1, ArgTypes)]; -ranges_get_arg_types([], _, _) -> []. - -update_anno_types(#b_set{anno=Anno,args=Args}=I, Ts) -> - ArgTypes1 = case Anno of - #{arg_types := ArgTypes0} -> ArgTypes0; - #{} -> #{} - end, - ArgTypes = update_anno_types_1(Args, Ts, 0, ArgTypes1), - case Anno of - #{arg_types := ArgTypes} -> - I; - #{} when map_size(ArgTypes) =/= 0 -> - I#b_set{anno=Anno#{arg_types => ArgTypes}}; - #{} -> - I - end. - -update_anno_types_1([#b_var{}=V|As], Ts, Index, ArgTypes) -> - T0 = case ArgTypes of - #{Index := T00} -> T00; - #{} -> any - end, - T1 = case Ts of - #{V := T11} -> T11; - #{} -> any - end, - case beam_types:meet(T0, T1) of - any -> - update_anno_types_1(As, Ts, Index + 1, ArgTypes); - none -> - %% This instruction will never be reached. This happens when - %% compiling code such as the following: - %% - %% f(X) when is_integer(X), 0 =< X, X < 64 -> - %% (X = bnot X) + 1. - %% - %% The main type optimization sub pass will not find out - %% that `(X = bnot X)` will never succeed and that the `+` - %% operator is never executed, but this sub pass will. - %% This happens very rarely; therefore, don't bother removing - %% the unreachable instruction. - update_anno_types_1(As, Ts, Index + 1, ArgTypes); - T -> - update_anno_types_1(As, Ts, Index + 1, ArgTypes#{Index => T}) - end; -update_anno_types_1([_|As], Ts, Index, ArgTypes) -> - update_anno_types_1(As, Ts, Index + 1, ArgTypes); -update_anno_types_1([], _, _, ArgTypes) -> ArgTypes. - %%% %%% Optimization helpers %%% @@ -1012,7 +866,7 @@ simplify_terminator(#b_ret{arg=Arg,anno=Anno0}=Ret0, Ts, Ds, Sub) -> %% was redundant. %% -simplify(#b_set{op=phi,dst=Dst,args=Args0}=I0, Ts0, Ds0, Ls, Sub) -> +simplify(#b_set{op=phi,dst=Dst,args=Args0}=I0, _Uvs, Ts0, Ds0, Ls, Sub) -> %% Simplify the phi node by removing all predecessor blocks that no %% longer exists or no longer branches to this block. {Type, Args} = simplify_phi_args(Args0, Ls, Sub, none, []), @@ -1030,7 +884,7 @@ simplify(#b_set{op=phi,dst=Dst,args=Args0}=I0, Ts0, Ds0, Ls, Sub) -> {I, Ts, Ds} end; simplify(#b_set{op={succeeded,Kind},args=[Arg],dst=Dst}=I, - Ts0, Ds0, _Ls, Sub) -> + _Uvs, Ts0, Ds0, _Ls, Sub) -> Type = case will_succeed(I, Ts0, Ds0, Sub) of yes -> beam_types:make_atom(true); no -> beam_types:make_atom(false); @@ -1056,7 +910,7 @@ simplify(#b_set{op={succeeded,Kind},args=[Arg],dst=Dst}=I, Ds = Ds0#{ Dst => I }, {I, Ts, Ds} end; -simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> +simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, _Uvs, Ts0, Ds0, _Ls, Sub) -> Args = simplify_args(Args0, Ts0, Sub), I1 = I0#b_set{args=Args}, I2 = case {Args0,Args} of @@ -1075,7 +929,7 @@ simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> Ds = Ds0#{ Dst => I }, {I, Ts, Ds}; simplify(#b_set{op=bs_create_bin=Op,dst=Dst,args=Args0,anno=Anno}=I0, - Ts0, Ds0, _Ls, Sub) -> + _Uvs, Ts0, Ds0, _Ls, Sub) -> Args = simplify_args(Args0, Ts0, Sub), case Args of @@ -1099,10 +953,15 @@ simplify(#b_set{op=bs_create_bin=Op,dst=Dst,args=Args0,anno=Anno}=I0, Ds = Ds0#{ Dst => I }, {I, Ts, Ds} end; -simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> +simplify(#b_set{dst=Dst,args=Args0}=I0, Uvs0, Ts0, Ds0, _Ls, Sub) -> Args = simplify_args(Args0, Ts0, Sub), I1 = beam_ssa:normalize(I0#b_set{args=Args}), case simplify(I1, Ts0, Ds0) of + #b_set{op={bif,Op}}=I when Op =:= '+'; Op =:= '-'; + Op =:= '*'; Op =:= 'bnot' -> + {Ts,Uvs} = update_arith_types(I, Ts0, Ds0, Uvs0), + Ds = Ds0#{ Dst => I }, + {I, Ts, Ds, Uvs}; #b_set{}=I -> Ts = update_types(I, Ts0, Ds0), Ds = Ds0#{ Dst => I }, @@ -1113,6 +972,140 @@ simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> Sub#{ Dst => Var } end. +update_arith_types(#b_set{dst=Dst}=I, Ts0, Ds, UnstableVars0) -> + %% "Arith types" can be more exact, but can diverge if used for + %% computing the range for one of `+`, `-`, '*`, or `bnot` in a + %% recursive function. For example: + %% + %% len(L) -> len(L, 0). + %% + %% len([], N) -> N; + %% len([_|T], N) -> len(N + 1). + %% + %% The initial range for `N` will be {0,0}. The range after + %% evaluating `N + 1` when using arith types will be {1,1}, then + %% {2,2}, and so on forever. + %% + %% The conservative range calculation done by update_types/3 will + %% set the range to {1,'+inf'}. + %% + %% Arith types will work when the new range is not fed back to + %% the operation, or if there is some constraint that prevents + %% the range from growing forever. For example: + %% + %% intsum(N) when is_integer(N, 0, 1 bsl 59) -> + %% {sum,intsum(0, N, 0)}. + %% + %% intsum(I, N, Sum) when I < N -> + %% intsum(I + 1, N, Sum + I); + %% intsum(_, _, Sum) -> + %% Sum. + %% + case update_arith_types_1(I, Ts0, UnstableVars0) of + {any,UnstableVars} -> + %% The arithmetic type is either `any` or diverging. + Ts = update_types(I, Ts0, Ds), + {Ts,UnstableVars}; + {Type,UnstableVars} -> + %% The arithmetic type is stable. + Ts = Ts0#{Dst => Type}, + {Ts,UnstableVars} + end. + +update_arith_types_1(#b_set{op={bif,_}=Op,args=BifArgs}=I, + Ts0, UnstableVars0) -> + ArgTypes = concrete_types(BifArgs, Ts0), + case beam_call_types:arith_type(Op, ArgTypes) of + any -> + {any,UnstableVars0}; + #t_float{elements=any} -> + {any,UnstableVars0}; + #t_integer{elements=any} -> + {any,UnstableVars0}; + #t_number{elements=any} -> + {any,UnstableVars0}; + Type -> + case update_arith_types_safe(I, ArgTypes, Type, UnstableVars0) of + {safe,UnstableVars} -> + %% Safe (permanently or temporarily). + {Type,UnstableVars}; + {unsafe,UnstableVars} -> + %% This variable doesn't seem to converge to a + %% stable range. + {update_arith_types_2(I, ArgTypes),UnstableVars} + end + end. + +update_arith_types_2(#b_set{op={bif,'-'}=Op,args=[_,#b_literal{val=1}]}, + [#t_integer{elements={Min,_Max}}=ArgType|_]) + when is_integer(Min), Min > 0 -> + %% We have almost given up on this operation. As a final attempt, + %% subtract a number that will set the minimum value to 0. + Args = [ArgType,#t_integer{elements={Min,Min}}], + beam_call_types:arith_type(Op, Args); +update_arith_types_2(#b_set{}, _) -> + %% Fall back to using more conservative update_types/3 approach + %% (setting one end of the range to infinity). + any. + +update_arith_types_safe(#b_set{}, _ArgTypes, _Type, none) -> + %% Not running the signatures sub pass; it is always safe to + %% propagate types, because they will only be propagated within + %% the current function. + {safe,none}; +update_arith_types_safe(#b_set{dst=Dst}=I, ArgTypes, Type, UnstableVars0) -> + case UnstableVars0 of + #{Dst := {Type,_}} -> + %% No change since last time. + {safe,UnstableVars0}; + #{Dst := {_,0}} -> + %% The counter has run down. Give up on using the more + %% exact arith types. + {unsafe,UnstableVars0}; + #{Dst := {_,Count}} when is_integer(Count) -> + %% Try using this type. + UnstableVars = UnstableVars0#{Dst := {Type,Count-1}}, + {safe,UnstableVars}; + #{} -> + %% We have not seen this variable before. Initialize + %% a counter for the number of times to try. + Counter = init_counter(I, ArgTypes), + UnstableVars = UnstableVars0#{Dst => {Type,Counter}}, + {safe,UnstableVars} + end. + +init_counter(#b_set{op=Op}, ArgTypes) -> + case Op of + {bif,'+'} -> 64; + {bif,'-'} -> + Def = 64, + case ArgTypes of + [#t_integer{elements={_Min,Max}}, + #t_integer{elements={1,1}}] -> + %% Avoid passing zero because it is unlikely to + %% improve the range. + max(1, min(Def, Max)); + _ -> + Def + end; + {bif,'bnot'} -> 60; + {bif,'*'} -> + case ArgTypes of + [_,#t_integer{elements={_,Max}}] when Max > 0 -> + %% Use a conservative number of attempts to + %% avoid overflowing a small. + try floor(32.0 / math:log2(abs(Max))) of + Log -> + max(1, min(32, Log)) + catch + _:_ -> + 1 + end; + _ -> + 1 + end + end. + simplify(#b_set{op={bif,'band'},args=Args}=I, Ts, Ds) -> case normalized_types(Args, Ts) of [#t_integer{elements=R},#t_integer{elements={M,M}}] -> diff --git a/lib/compiler/test/beam_bounds_SUITE.erl b/lib/compiler/test/beam_bounds_SUITE.erl index 42436f5b8b8a..241df9059e03 100644 --- a/lib/compiler/test/beam_bounds_SUITE.erl +++ b/lib/compiler/test/beam_bounds_SUITE.erl @@ -93,17 +93,22 @@ end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> addition_bounds(_Config) -> test_commutative('+', {-12,12}), - {'-inf',-15} = beam_bounds:bounds('+', {'-inf',-20}, {2,5}), - {'-inf',55} = beam_bounds:bounds('+', {'-inf',50}, {'-inf',5}), - {'-inf',110} = beam_bounds:bounds('+', {1,10}, {'-inf',100}), - any = beam_bounds:bounds('+', {1,'+inf'}, {'-inf',100}), + {'-inf',-15} = do_add({'-inf',-20}, {2,5}), + {'-inf',55} = do_add({'-inf',50}, {'-inf',5}), + {'-inf',110} = do_add({1,10}, {'-inf',100}), + any = do_add({1,'+inf'}, {'-inf',100}), - {-8,'+inf'} = beam_bounds:bounds('+', {2,'+inf'}, {-10,20}), - {6,'+inf'} = beam_bounds:bounds('+', {1,10}, {5,'+inf'}), - {9,'+inf'} = beam_bounds:bounds('+', {2,'+inf'}, {7,'+inf'}), + {-8,'+inf'} = do_add({2,'+inf'}, {-10,20}), + {6,'+inf'} = do_add({1,10}, {5,'+inf'}), + {9,'+inf'} = do_add({2,'+inf'}, {7,'+inf'}), ok. +do_add(A, B) -> + Res = beam_bounds:bounds('+', A, B), + Res = beam_bounds:bounds('+', B, A), + Res. + subtraction_bounds(_Config) -> test_noncommutative('-', {-12,12}), @@ -155,6 +160,28 @@ division_bounds(_Config) -> any = beam_bounds:bounds('div', {'-inf',10}, any), any = beam_bounds:bounds('div', {0,'+inf'}, any), + any = beam_bounds:bounds('div', any, {'-inf',10}), + any = beam_bounds:bounds('div', any, {0,'+inf'}), + + {-32,32} = beam_bounds:bounds('div', {16,32}, any), + any = beam_bounds:bounds('div', any, {7,13}), + + Big = 1 bsl 512, + {-100,100} = beam_bounds:bounds('div', {0,100}, {1,Big}), + {-100,100} = beam_bounds:bounds('div', {0,100}, {-Big,10}), + + {-100,'+inf'} = beam_bounds:bounds('div', {-100,'+inf'}, {1,Big}), + {-25,'+inf'} = beam_bounds:bounds('div', {-100,'+inf'}, {4,Big}), + {'-inf',0} = beam_bounds:bounds('div', {'-inf',-5}, {1,Big}), + {'-inf',5} = beam_bounds:bounds('div', {'-inf',5}, {1,Big}), + {0,'+inf'} = beam_bounds:bounds('div', {5,'+inf'}, {1,Big}), + + {'-inf',0} = beam_bounds:bounds('div', {-Big,-1}, {5,10}), + {'-inf',-2} = beam_bounds:bounds('div', {-Big,-20}, {5,10}), + {'-inf',0} = beam_bounds:bounds('div', {-Big,-1}, {7,Big}), + {'-inf',8} = beam_bounds:bounds('div', {-Big,17}, {2,Big}), + {2,'+inf'} = beam_bounds:bounds('div', {20,Big}, {5,10}), + {0,'+inf'} = beam_bounds:bounds('div', {1,Big}, {1,Big}), ok. @@ -194,47 +221,239 @@ rem_bounds(_Config) -> {-7,0} = beam_bounds:bounds('rem', {-7,-7}, any), {-6,0} = beam_bounds:bounds('rem', {-6,-4}, any), + {0,'+inf'} = beam_bounds:bounds('rem', {0,'+inf'}, any), + {0,'+inf'} = beam_bounds:bounds('rem', {7,'+inf'}, any), + {'-inf',0} = beam_bounds:bounds('rem', {'-inf',-5}, any), + {'-inf',11} = beam_bounds:bounds('rem', {'-inf',11}, any), + + any = beam_bounds:bounds('rem', any, {'-inf',-7}), + any = beam_bounds:bounds('rem', any, {'-inf',0}), + any = beam_bounds:bounds('rem', any, {'-inf',1}), + any = beam_bounds:bounds('rem', any, {'-inf',5}), + any = beam_bounds:bounds('rem', any, {0,'+inf'}), + any = beam_bounds:bounds('rem', any, {1,'+inf'}), + any = beam_bounds:bounds('rem', any, {100,'+inf'}), + ok. band_bounds(_Config) -> - test_commutative('band'), - - %% Coverage. - {0,17} = beam_bounds:bounds('band', any, {7,17}), - {0,42} = beam_bounds:bounds('band', {0,42}, any), - any = beam_bounds:bounds('band', {-1,1}, any), - any = beam_bounds:bounds('band', any, {-10,0}), - any = beam_bounds:bounds('band', {-10,0}, {-1,10}), - any = beam_bounds:bounds('band', {-20,-10}, {-1,10}), + test_commutative('band', {-15,15}), + + Big = 1 bsl 512, + NegBig = -Big, + + %% -- -- + {'-inf',-7} = do_band({'-inf',-7}, {'-inf',-1}), + {'-inf',-155} = do_band({'-inf',-7}, {-300,-155}), + {-116,'+inf'} = do_band({-99,'+inf'}, {-18,-5}), + {-256,'+inf'} = do_band({-99,'+inf'}, {-179,-5}), + {'-inf',-1} = do_band({NegBig,NegBig}, {-10,-1}), + do_rand_band('-inf', '-', '-inf', '-'), + do_rand_band('-inf', '-', '-', '-'), + + %% -- -+ + {'-inf',25} = do_band({'-inf',-37}, {'-inf',25}), + {'-inf',15} = do_band({'-inf',-10}, {-11,15}), + any = do_band({-8,-8}, any), + do_rand_band('-', '-', '-inf', '+inf'), + do_rand_band('-inf', '-', '-inf', '+'), + do_rand_band('-inf', '-', '-', '+'), + + %% -+ -+ + {'-inf',37} = do_band({'-inf',37}, {'-inf',25}), + + {'-inf',15} = do_band({'-inf',10}, {-11,15}), + {'-inf',66} = do_band({'-inf',66}, {-11,17}), + + {-2,'+inf'} = do_band({-1,1}, {-2,'+inf'}), + {-112,'+inf'} = do_band({-99,10}, {-15,'+inf'}), + {-512,'+inf'} = do_band({-500,7}, {-57,'+inf'}), + {0,'+inf'} = do_band({-10,10}, {27,'+inf'}), + {0,'+inf'} = do_band({-10,27}, {10,'+inf'}), + + any = do_band({'-inf',0}, {-1,'+inf'}), + any = do_band({'-inf',11}, {-17,'+inf'}), + any = do_band({-10,27}, any), + any = do_band(rand_r('-', '+'), any), + + do_rand_band('-inf', '+', '-inf', '+'), + do_rand_band('-inf', '+', '-', '+'), + do_rand_band('-', '+', '-', '+inf'), + any = do_band(rand_r('-inf', '+'), rand_r('-', '+inf')), + + %% -- ++ + {0,'+inf'} = do_band({'-inf',-17}, {1,'+inf'}), + {0,'+inf'} = do_band({'-inf',-17}, {7,'+inf'}), + {0,14} = do_band({'-inf',-17}, {7,14}), + {0,255} = do_band({'-inf',-1}, {255,255}), + {0,'+inf'} = do_band({-77,-10}, {15,'+inf'}), + do_rand_band('-inf', '-', '+', '+'), + do_rand_band('-', '-', '+', '+inf'), + do_rand_band('-inf', '-', '+', '+inf'), + do_rand_band('-', '+', '+', '+inf'), + + %% -+ ++ + {0,13} = do_band({-10,'+inf'}, {7,13}), + {0,'+inf'} = do_band({-10,'+inf'}, {7,'+inf'}), + {0,'+inf'} = do_band(any, {7,'+inf'}), + do_rand_band('-inf', '+', '+', '+'), + do_rand_band('-', '+', '+', '+inf'), + do_rand_band('-', '+inf', '+', '+inf'), + do_rand_band('-inf', '+inf', '+', '+inf'), + + %% ++++ + {0,'+inf'} = do_band({1,'+inf'}, {1,'+inf'}), + {0,10} = do_band({7,'+inf'}, {5,10}), + {0,'+inf'} = do_band({7,'+inf'}, {Big,Big}), + do_rand_band('+', '+inf', '+', '+inf'), + do_rand_band('+', '+inf', '+', '+'), ok. -bor_bounds(_Config) -> - test_commutative('bor'), +do_band(R0, R1) -> + test_commutative_inf('band', R0, R1). - {'-inf',15} = beam_bounds:bounds('bor', {-10,7},{3,10}), - {'-inf',11} = beam_bounds:bounds('bor', {-10,1},{-1,10}), - {'-inf',-1} = beam_bounds:bounds('bor', {-20,-10}, {-2,10}), +do_rand_band(A, B, C, D) -> + test_rand('band', A, B, C, D). - {'-inf',15} = beam_bounds:bounds('bor', {'-inf',10}, {3,5}), - {'-inf',-1} = beam_bounds:bounds('bor', {-20,-10}, {-100,-50}), +bor_bounds(_Config) -> + test_commutative('bor', {-15,15}), + + %% -- -- + {'-inf',-1} = do_bor({'-inf',-1}, {'-inf',-1}), + {'-inf',-1} = do_bor({'-inf',-1}, {'-inf',-10}), + {'-inf',-1} = do_bor({'-inf',-177}, {'-inf',-17}), + do_rand_bor('-inf', '-', '-inf', '-'), + do_rand_bor('-inf', '-', '-', '-'), + + %% -- -+ and -+ -- + {'-inf',-1} = do_bor({'-inf',-1}, {'-inf',0}), + {'-inf',-1} = do_bor({'-inf',-1}, {'-inf',1}), + {'-inf',-1} = do_bor({'-inf',-9}, {'-inf',58}), + {'-inf',-1} = do_bor({'-inf',-177}, {'-inf',19}), + {-500,-1} = do_bor({-500,-100}, {'-inf',10}), + {-20,-1} = do_bor({-20,-10}, {-2,'+inf'}), + {'-inf',-1} = do_bor({'-inf',-177}, {-5,'+inf'}), + do_rand_bor('-inf', '-', '-inf', '+'), + do_rand_bor('-inf', '-', '-', '+'), + + %% -+ -+ + {'-inf',1} = do_bor({'-inf',1}, {'-inf',1}), + {'-inf',7} = do_bor({'-inf',1}, {'-inf',7}), + {'-inf',63} = do_bor({'-inf',63}, {'-inf',21}), + {'-inf',1} = do_bor({'-inf',1}, {-500,1}), + {'-inf',7} = do_bor({'-inf',5}, {-500,2}), + {-12,'+inf'} = do_bor({-1,10}, {-12,'+inf'}), + {-16,'+inf'} = do_bor({-7,'+inf'}, {-16,'+inf'}), + any = do_bor({'-inf',1}, {-1,'+inf'}), + any = do_bor({'-inf',37}, {-8,'+inf'}), + + do_rand_bor('-inf', '+', '-inf', '+'), + do_rand_bor('-inf', '+', '-', '+'), + do_rand_bor('-', '+', '-', '+inf'), + any = do_bor(rand_r('-inf', '+'), rand_r('-', '+inf')), + + %% -- ++ and ++ -- + {-7,-1} = do_bor({-7,-7}, {0,'+inf'}), + {-256,-1} = do_bor({-256,-256}, {200,'+inf'}), + {'-inf',-9} = do_bor({'-inf',-10}, {3,5}), + {-20,-1} = do_bor({-20,-10}, {5,'+inf'}), + {-1,'+inf'} = do_bor({1,10}, {-1,'+inf'}), + {'-inf',-1} = do_bor({'-inf',-97}, {42,'+inf'}), + {'-inf',-1} = do_bor({'-inf',-1}, {1,'+inf'}), + {'-inf',-1} = do_bor({'-inf',-777}, {100,'+inf'}), + do_rand_bor('-inf', '-', '+', '+'), + do_rand_bor('-', '-', '+', '+inf'), + do_rand_bor('-inf', '-', '+', '+inf'), + + %% -+ ++ and ++ -+ + {'-inf',15} = do_bor({'-inf',10}, {3,5}), + {-3,'+inf'} = do_bor({-3,10}, {17,'+inf'}), + {-8,'+inf'} = do_bor({-8,'+inf'}, {17,'+inf'}), + {-32,'+inf'} = do_bor({-32,'+inf'}, {5,'+inf'}), + do_rand_bor('-inf', '+', '+', '+'), + do_rand_bor('-', '+', '+', '+inf'), + do_rand_bor('-', '+inf', '+', '+inf'), + + any = do_bor({'-inf',0}, {0,'+inf'}), + any = do_bor({'-inf',1}, {1,'+inf'}), + any = do_bor({'-inf',47}, {99,'+inf'}), + any = do_bor(rand_r('-inf', '+'), rand_r('+', '+inf')), + + %% ++ ++ + {52,'+inf'} = do_bor({20,25}, {33,'+inf'}), + {16,'+inf'} = do_bor({3,'+inf'}, {16,'+inf'}), + do_rand_bor('+', '+inf', '+', '+inf'), + do_rand_bor('+', '+inf', '+', '+'), - any = beam_bounds:bounds('bor', {-20,-10}, {-2,'+inf'}), - any = beam_bounds:bounds('bor', {-20,'+inf'}, {-7,-3}), + ok. - {16,'+inf'} = beam_bounds:bounds('bor', {0,8}, {16,'+inf'}), - {16,'+inf'} = beam_bounds:bounds('bor', {3,'+inf'}, {16,'+inf'}), +do_bor(R0, R1) -> + test_commutative_inf('bor', R0, R1). - ok. +do_rand_bor(A, B, C, D) -> + test_rand('bor', A, B, C, D). bxor_bounds(_Config) -> - test_commutative('bxor'), - - any = beam_bounds:bounds('bxor', {-10,0}, {-1,10}), - any = beam_bounds:bounds('bxor', {-20,-10}, {-1,10}), + test_commutative('bxor', {-15,15}), + + %% -- -- + any = do_bxor({'-inf',-177}, {'-inf',-17}), + any = do_bxor({'-inf',-103}, {-17,-5}), + any = do_bxor(rand_r('-inf', '-'), rand_r('-inf', '-')), + any = do_bxor(rand_r('-inf', '-'), rand_r('-', '-')), + + %% -- -+ + any = do_bxor({'-inf',-177}, {'-inf',19}), + any = do_bxor({'-inf',-9}, {'-inf',58}), + any = do_bxor({-500,-100}, {'-inf',10}), + any = do_bxor({-20,-10}, {-2,'+inf'}), + any = do_bxor({'-inf',-177}, {-5,'+inf'}), + any = do_bxor(rand_r('-inf', '-'), rand_r('-inf', '+')), + any = do_bxor(rand_r('-inf', '-'), rand_r('-', '+')), + + %% -+ -+ + any = do_bxor({'-inf',63}, {'-inf',21}), + any = do_bxor({'-inf',5}, {-500,2}), + any = do_bxor({-1,10}, {-12,'+inf'}), + any = do_bxor({-7,'+inf'}, {-16,'+inf'}), + any = do_bxor({-8,'+inf'}, {17,'+inf'}), + any = do_bxor({'-inf',37}, {-8,'+inf'}), + any = do_bxor(rand_r('-inf', '+'), rand_r('-inf', '+')), + any = do_bxor(rand_r('-inf', '+'), rand_r('-', '+')), + + %% -- ++ + {'-inf',-9} = do_bxor({'-inf',-10}, {3,5}), + {'-inf',-97} = do_bxor({'-inf',-100}, {10,20}), + {'-inf',-1} = do_bxor({-20,-10}, {5,'+inf'}), + {'-inf',-1} = do_bxor({'-inf',-97}, {42,'+inf'}), + do_rand_bxor('-inf', '+', '-inf', '+'), + do_rand_bxor('-inf', '+', '-', '+'), + + %% -+ ++ + any = do_bxor({'-inf',10}, {3,5}), + any = do_bxor({-3,10}, {17,'+inf'}), + any = do_bxor({-1,'+inf'}, {1,10}), + any = do_bxor({-32,'+inf'}, {5,'+inf'}), + any = do_bxor({'-inf',47}, {99,'+inf'}), + any = do_bxor(rand_r('-inf', '+'), rand_r('+', '+')), + + %% ++ ++ + {32,'+inf'} = do_bxor({20,25}, {33,'+inf'}), + {0,'+inf'} = do_bxor({3,'+inf'}, {16,'+inf'}), + do_rand_bxor('+', '+inf', '+', '+inf'), + do_rand_bxor('+', '+inf', '+', '+'), + + any = do_bxor(any, {1,10}), ok. +do_rand_bxor(A, B, C, D) -> + test_rand('bxor', A, B, C, D). + +do_bxor(R0, R1) -> + test_commutative_inf('bxor', R0, R1). + bnot_bounds(_Config) -> Min = -7, Max = 7, @@ -249,6 +468,14 @@ bnot_bounds(_Config) -> {'-inf',9} = beam_bounds:bounds('bnot', {-10,'+inf'}), {-1114111,'+inf'} = beam_bounds:bounds('bnot', {'-inf', 1114110}), + Big = 1 bsl 512, + {1,'+inf'} = beam_bounds:bounds('bnot', {-Big, -2}), + {'-inf',-11} = beam_bounds:bounds('bnot', {10, Big}), + {'-inf',9} = beam_bounds:bounds('bnot', {-10, Big}), + {'-inf',-1} = beam_bounds:bounds('bnot', {Big, Big}), + + any = beam_bounds:bounds('bnot', any), + -1 = bnot_bounds_2(0), -43 = bnot_bounds_2_coverage(id(42)), ?assertError(badarith, bnot_bounds_2_coverage(id(bad))), @@ -265,7 +492,7 @@ bnot_bounds_1(R) -> {HighestMin,LowestMax} = min_max_unary_op('bnot', R), {Min,Max} = beam_bounds:bounds('bnot', R), if - Min =< HighestMin, LowestMax =< Max -> + Min =:= HighestMin, LowestMax =:= Max -> ok; true -> io:format("bnot(~p) evaluates to ~p; should be ~p\n", @@ -289,51 +516,64 @@ bnot_bounds_4() -> bsr_bounds(_Config) -> - test_noncommutative('bsr', {-12,12}, {0,7}), + test_noncommutative('bsr', {-12,12}, {-7,7}), - {0,10} = beam_bounds:bounds('bsr', {0,10}, {0,'+inf'}), - {0,2} = beam_bounds:bounds('bsr', {0,10}, {2,'+inf'}), + {0,10} = do_bsr({0,10}, {0,'+inf'}), + {0,2} = do_bsr({0,10}, {2,'+inf'}), - {-1,10} = beam_bounds:bounds('bsr', {-1,10}, {0,'+inf'}), - {-100,900} = beam_bounds:bounds('bsr', {-100,900}, {0,'+inf'}), - {-50,450} = beam_bounds:bounds('bsr', {-100,900}, {1,'+inf'}), + {-1,10} = do_bsr({-1,10}, {0,'+inf'}), + {-100,900} = do_bsr({-100,900}, {0,'+inf'}), + {-50,450} = do_bsr({-100,900}, {1,'+inf'}), - {'-inf',16} = beam_bounds:bounds('bsr', {'-inf',32}, {1,10}), - {-5,'+inf'} = beam_bounds:bounds('bsr', {-10,'+inf'}, {1,10}), + {'-inf',16} = do_bsr({'-inf',32}, {1,10}), + {-5,'+inf'} = do_bsr({-10,'+inf'}, {1,10}), + + {0,'+inf'} = do_bsr({17,'+inf'}, any), + {'-inf',-1} = do_bsr({'-inf',-10}, any), ok. +do_bsr(R0, R1) -> + test_noncommutative_inf('bsr', R0, R1). + bsl_bounds(_Config) -> test_noncommutative('bsl', {-12,12}, {-7,7}), - {2,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {1,10_000}), - {0,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {-10,10_000}), - {'-inf',-20} = beam_bounds:bounds('bsl', {-30,-10}, {1,10_000}), - {'-inf',-2} = beam_bounds:bounds('bsl', {-9,-1}, {1,10_000}), - any = beam_bounds:bounds('bsl', {-7,10}, {1,10_000}), + {2,'+inf'} = do_bsl({1,10}, {1,10_000}), + {0,'+inf'} = do_bsl({1,10}, {-10,10_000}), + {'-inf',-20} = do_bsl({-30,-10}, {1,10_000}), + {'-inf',-2} = do_bsl({-9,-1}, {1,10_000}), + any = do_bsl({-7,10}, {1,10_000}), - {0,'+inf'} = beam_bounds:bounds('bsl', {0,'+inf'}, {0,'+inf'}), - {20,'+inf'} = beam_bounds:bounds('bsl', {20,30}, {0,'+inf'}), + {0,'+inf'} = do_bsl({0,'+inf'}, {0,'+inf'}), + {20,'+inf'} = do_bsl({20,30}, {0,'+inf'}), - any = beam_bounds:bounds('bsl', {-10,100}, {0,'+inf'}), - any = beam_bounds:bounds('bsl', {-10,100}, {1,'+inf'}), - any = beam_bounds:bounds('bsl', {-10,100}, {-1,'+inf'}), + any = do_bsl({-10,100}, {0,'+inf'}), + any = do_bsl({-10,100}, {1,'+inf'}), + any = do_bsl({-10,100}, {-1,'+inf'}), - {0,10} = beam_bounds:bounds('bsl', {1,10}, {'-inf',0}), - {0,20} = beam_bounds:bounds('bsl', {1,10}, {'-inf',1}), - {-7,10} = beam_bounds:bounds('bsl', {-7,10}, {'-inf',0}), - {-28,40} = beam_bounds:bounds('bsl', {-7,10}, {'-inf',2}), + {0,10} = do_bsl({1,10}, {'-inf',0}), + {0,20} = do_bsl({1,10}, {'-inf',1}), + {-7,10} = do_bsl({-7,10}, {'-inf',0}), + {-28,40} = do_bsl({-7,10}, {'-inf',2}), - {'-inf',-1} = beam_bounds:bounds('bsl', {-10,-1}, {500,1024}), - {0,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {500,1024}), + {'-inf',-1} = do_bsl({-10,-1}, {500,1024}), + {0,'+inf'} = do_bsl({1,10}, {500,1024}), - {'-inf',-40} = beam_bounds:bounds('bsl', {'-inf',-10}, {2,64}), - {'-inf',224} = beam_bounds:bounds('bsl', {'-inf',7}, {3,5}), + {'-inf',-40} = do_bsl({'-inf',-10}, {2,64}), + {'-inf',224} = do_bsl({'-inf',7}, {3,5}), - any = beam_bounds:bounds('bsl', {'-inf',7}, {3,'+inf'}), + {'-inf',-88} = do_bsl({'-inf',-11}, {3,'+inf'}), + any = do_bsl({'-inf',7}, {3,'+inf'}), + + {0,'+inf'} = do_bsl({17,'+inf'}, any), + {'-inf',-1} = do_bsl({'-inf',-10}, any), ok. +do_bsl(R0, R1) -> + test_noncommutative_inf('bsl', R0, R1). + lt_bounds(_Config) -> test_relop('<'). @@ -364,13 +604,12 @@ min_bounds(_Config) -> {'-inf',10} = min_bounds({1,10}, any), any = min_bounds({1,'+inf'}, any), - {'-inf',777} = min_bounds(any, {'-inf',777}), + {'-inf',777} = min_bounds({'-inf',777}, any), ok. -min_bounds(R1, R2) -> - Result = beam_bounds:bounds(min, R1, R2), - Result = beam_bounds:bounds(min, R2, R1). +min_bounds(R0, R1) -> + test_commutative_inf('min', R0, R1). max_bounds(_Config) -> test_commutative(max, {-12,12}), @@ -394,9 +633,8 @@ max_bounds(_Config) -> ok. -max_bounds(R1, R2) -> - Result = beam_bounds:bounds(max, R1, R2), - Result = beam_bounds:bounds(max, R2, R1). +max_bounds(R0, R1) -> + test_commutative_inf('max', R0, R1). abs_bounds(_Config) -> Min = -7, @@ -405,13 +643,34 @@ abs_bounds(_Config) -> _ = [abs_bounds_1({A,B}) || A <- Seq, B <- lists:nthtail(A-Min, Seq)], + + Big = 1 bsl 512, + + {1,'+inf'} = do_abs({'-inf',-1}), + {10,'+inf'} = do_abs({'-inf',-10}), + + {0,'+inf'} = do_abs({'-inf',0}), + {0,'+inf'} = do_abs({'-inf',1}), + {0,'+inf'} = do_abs({'-inf',10}), + {0,'+inf'} = do_abs(any), + {1,'+inf'} = do_abs({-Big,-Big}), + + {0,'+inf'} = do_abs({-5,'+inf'}), + {0,'+inf'} = do_abs({0,'+inf'}), + {1,'+inf'} = do_abs({1,'+inf'}), + {17,'+inf'} = do_abs({17,'+inf'}), + {0,'+inf'} = do_abs({Big,Big}), + ok. +do_abs(R) -> + beam_bounds:bounds('abs', R). + abs_bounds_1(R) -> {HighestMin,LowestMax} = min_max_unary_op('abs', R), {Min,Max} = beam_bounds:bounds(abs, R), if - Min =< HighestMin, LowestMax =< Max -> + Min =:= HighestMin, LowestMax =:= Max -> ok; true -> io:format("~p(~p) evaluates to ~p; should be ~p\n", @@ -515,6 +774,119 @@ do_bench(F) -> %%% Common utilities. %%% +test_rand(Op, A, B, C, D) -> + R0 = rand_r(A, B), + R1 = rand_r(C, D), + Res = beam_bounds:bounds(Op, R0, R1), + Res = beam_bounds:bounds(Op, R1, R0), + test_random_pairs(Op, R0, R1, Res). + +test_commutative_inf(Op, {A0,B0}=R0, {C0,D0}=R1) -> + Res = beam_bounds:bounds(Op, R0, R1), + Res = beam_bounds:bounds(Op, R1, R0), + [A,B,C,D] = [case N of + '-inf' -> -1 bsl 512; + '+inf' -> 1 bsl 512; + _ -> N + end || N <- [A0,B0,C0,D0]], + Res = beam_bounds:bounds(Op, {A,B}, {C,D}), + Res = beam_bounds:bounds(Op, {C,D}, {A,B}), + test_random_pairs(Op, R0, R1, Res), + Res; +test_commutative_inf(Op, {A0,B0}=R0, R1) -> + Res = beam_bounds:bounds(Op, R0, R1), + Res = beam_bounds:bounds(Op, R1, R0), + [A,B] = [case N of + '-inf' -> -1 bsl 512; + '+inf' -> 1 bsl 512; + _ -> N + end || N <- [A0,B0]], + Res = beam_bounds:bounds(Op, {A,B}, R1), + Res = beam_bounds:bounds(Op, R1, {A,B}), + test_random_pairs(Op, R0, R1, Res), + Res; +test_commutative_inf(Op, any, R) -> + test_commutative_inf(Op, R, any). + +test_noncommutative_inf(Op, {A0,B0}=R0, {C0,D0}=R1) -> + Res = beam_bounds:bounds(Op, R0, R1), + [A,B,C,D] = [case N of + '-inf' -> -1 bsl 512; + '+inf' -> 1 bsl 512; + _ -> N + end || N <- [A0,B0,C0,D0]], + Res = beam_bounds:bounds(Op, {A,B}, {C,D}), + test_random_pairs(Op, R0, R1, Res), + Res; +test_noncommutative_inf(Op, {A0,B0}=R0, R1) -> + Res = beam_bounds:bounds(Op, R0, R1), + [A,B] = [case N of + '-inf' -> -1 bsl 512; + '+inf' -> 1 bsl 512; + _ -> N + end || N <- [A0,B0]], + Res = beam_bounds:bounds(Op, {A,B}, R1), + test_random_pairs(Op, R0, R1, Res), + Res; +test_noncommutative_inf(Op, any, R) -> + test_commutative_inf(Op, R, any). + +rand_r(A0, B0) -> + A = case A0 of + '-' -> -rand:uniform(1000); + '+' -> rand:uniform(1000); + _ -> A0 + end, + B = case B0 of + '-' when is_integer(A) -> + -rand:uniform(-A); + '-' -> + -rand:uniform(1000); + '+' when is_integer(A), A >= 0 -> + A + rand:uniform(1000); + '+' -> + rand:uniform(1000); + _ -> + B0 + end, + {A,B}. + +test_random_pairs(_Op, _R0, _R1, any) -> + ok; +test_random_pairs(Op, R0, R1, Range) -> + {A,B} = eliminate_infinity(R0), + {C,D} = eliminate_infinity(R1), + L0 = [{A,C}, {A,D}, {B,C}, {B,D} | + [random_pair(A, B, C, D) || + _ <- lists:seq(1, 10)]], + L1 = lists:usort(L0), + L = [{N0,N1,Result,Range} || + {N0,N1} <- L1, + Result = erlang:Op(N0, N1), + not inf_in_range(Result, Range)], + [] = L, + ok. + +eliminate_infinity({'-inf','+inf'}) -> + A = rand:uniform(100), + B = A + rand:uniform(100), + {A,B}; +eliminate_infinity({'-inf',B}) -> + {B - rand:uniform(100), B}; +eliminate_infinity({A,'+inf'}) -> + {A, A + rand:uniform(100)}; +eliminate_infinity(any) -> + eliminate_infinity({'-inf','+inf'}); +eliminate_infinity(R) -> + R. + +random_pair(A, B, C, D) -> + {random_in_range(A, B), + random_in_range(C, D)}. + +random_in_range(A, B) -> + A + rand:uniform(B - A + 1) - 1. + infer_lt_gt(R1, R2) -> case beam_bounds:infer_relop_types('>', R2, R1) of {Rb,Ra} -> @@ -523,15 +895,13 @@ infer_lt_gt(R1, R2) -> any = beam_bounds:infer_relop_types('<', R1, R2) end. -test_commutative(Op) -> - test_commutative(Op, {0,32}). - test_commutative(Op, {Min,Max}) -> Seq = lists:seq(Min, Max), _ = [test_commutative_1(Op, {A,B}, {C,D}) || A <- Seq, - B <- lists:nthtail(A-Min, Seq), - C <- lists:nthtail(A-Min, Seq), + Reduced = lists:nthtail(A-Min, Seq), + B <- Reduced, + C <- Reduced, D <- lists:nthtail(C-Min, Seq), {A,B} =< {C,D}], ok. @@ -541,8 +911,29 @@ test_commutative_1(Op, R1, R2) -> {Min,Max} = beam_bounds:bounds(Op, R1, R2), {Min,Max} = beam_bounds:bounds(Op, R2, R1), if - Min =< HighestMin, LowestMax =< Max -> + Min =:= HighestMin, LowestMax =:= Max -> + %% The bounds are as tight as possible. ok; + Min =< HighestMin, LowestMax =< Max -> + %% The bounds are correct, but not as tight as possible. + %% + %% We haven't figured out how to compute tight bounds for + %% bxor when one range includes zero and the other range + %% can be negative. + case {Op,R1,R2} of + {'bxor',{A,B},{C,D}} when A < 0, B < 0, C < 0, D >= 0 -> + ok; + {'bxor',{A,B},{C,D}} when A < 0, B >= 0, C < 0, D < 0 -> + ok; + {'bxor',{A,B},{C,D}} when A < 0, B >= 0, C < 0, D >= 0 -> + ok; + _ -> + io:format("~p(~p, ~p): bounds are not tight;\n" + " evaluates to ~p; should be ~p\n", + [Op,R1,R2,{Min,Max}, + {HighestMin,LowestMax}]), + ct:fail(bounds_are_not_tight) + end; true -> io:format("~p(~p, ~p) evaluates to ~p; should be ~p\n", [Op,R1,R2,{Min,Max},{HighestMin,LowestMax}]), @@ -667,6 +1058,13 @@ test_infer_relop('maybe', Op, {A0,B0}=R1, {C0,D0}=R2) -> in_range(Int, {A,B}) -> A =< Int andalso Int =< B. +inf_in_range(Int, {'-inf',B}) -> + Int =< B; +inf_in_range(Int, {A,'+inf'}) -> + A =< Int; +inf_in_range(Int, {A,B}) -> + A =< Int andalso Int =< B. + rel_op(Op, {A,B}, {C,D}) -> rel_op_1(Op, A, B, C, D, none).