summaryrefslogtreecommitdiff
path: root/lib/compiler/test/beam_bounds_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compiler/test/beam_bounds_SUITE.erl')
-rw-r--r--lib/compiler/test/beam_bounds_SUITE.erl342
1 files changed, 319 insertions, 23 deletions
diff --git a/lib/compiler/test/beam_bounds_SUITE.erl b/lib/compiler/test/beam_bounds_SUITE.erl
index 42e7f2bc27..e10f25c688 100644
--- a/lib/compiler/test/beam_bounds_SUITE.erl
+++ b/lib/compiler/test/beam_bounds_SUITE.erl
@@ -25,7 +25,12 @@
multiplication_bounds/1, division_bounds/1, rem_bounds/1,
band_bounds/1, bor_bounds/1, bxor_bounds/1,
bsr_bounds/1, bsl_bounds/1,
- lt_bounds/1, le_bounds/1, gt_bounds/1, ge_bounds/1]).
+ bnot_bounds/1,
+ lt_bounds/1, le_bounds/1, gt_bounds/1, ge_bounds/1,
+ min_bounds/1, max_bounds/1,
+ abs_bounds/1,
+ infer_lt_gt_bounds/1,
+ redundant_masking/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -42,12 +47,18 @@ groups() ->
band_bounds,
bor_bounds,
bxor_bounds,
+ bnot_bounds,
bsr_bounds,
bsl_bounds,
lt_bounds,
le_bounds,
gt_bounds,
- ge_bounds]}].
+ ge_bounds,
+ min_bounds,
+ max_bounds,
+ abs_bounds,
+ infer_lt_gt_bounds,
+ redundant_masking]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -63,17 +74,59 @@ end_per_group(_GroupName, Config) ->
Config.
addition_bounds(_Config) ->
- test_commutative('+', {-12,12}).
+ 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}),
+
+ {-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'}),
+
+ ok.
subtraction_bounds(_Config) ->
- test_noncommutative('-', {-12,12}).
+ test_noncommutative('-', {-12,12}),
+
+ {'-inf',18} = beam_bounds:bounds('-', {'-inf',20}, {2,9}),
+ any = beam_bounds:bounds('-', {'-inf',20}, {'-inf',17}),
+ {-99,'+inf'} = beam_bounds:bounds('-', {1,10}, {'-inf',100}),
+ {-93,'+inf'} = beam_bounds:bounds('-', {7,'+inf'}, {'-inf',100}),
+
+ {-18,'+inf'} = beam_bounds:bounds('-', {2,'+inf'}, {-10,20}),
+ {'-inf',6} = beam_bounds:bounds('-', {1,11}, {5,'+inf'}),
+ any = beam_bounds:bounds('-', {2,'+inf'}, {7,'+inf'}),
+
+ ok.
multiplication_bounds(_Config) ->
- test_commutative('*', {-12,12}).
+ test_commutative('*', {-12,12}),
+
+ {'-inf',-40} = beam_bounds:bounds('*', {'-inf',-20}, {2,5}),
+ {'-inf',1000} = beam_bounds:bounds('*', {'-inf',100}, {1,10}),
+ any = beam_bounds:bounds('*', {'-inf',100}, {-10,10}),
+
+ {-100,'+inf'} = beam_bounds:bounds('*', {-10,'+inf'}, {1,10}),
+ {7,'+inf'} = beam_bounds:bounds('*', {7,'+inf'}, {1,10}),
+ any = beam_bounds:bounds('*', {-10,'+inf'}, {-5,5}),
+
+ {'-inf',1000} = beam_bounds:bounds('*', {1,10}, {'-inf',100}),
+ {-100,'+inf'} = beam_bounds:bounds('*', {1,10}, {-10,'+inf'}),
+
+ ok.
division_bounds(_Config) ->
test_noncommutative('div', {-12,12}),
+ {'-inf',-5} = beam_bounds:bounds('div', {'-inf',-20}, {2,4}),
+ {'-inf',50} = beam_bounds:bounds('div', {'-inf',100}, {2,4}),
+
+ {-5,'+inf'} = beam_bounds:bounds('div', {-10,'+inf'}, {2,4}),
+ {2,'+inf'} = beam_bounds:bounds('div', {10,'+inf'}, {2,4}),
+
+ any = beam_bounds:bounds('div', {10,'+inf'}, {0,0}),
{'EXIT', {badarith, _}} = catch division_bounds_1([], ok),
ok.
@@ -88,8 +141,17 @@ division_bounds_1(_, _) ->
rem_bounds(_Config) ->
test_noncommutative('rem', {-12,12}),
- {-7,7} = beam_bounds:'rem'(any, {1,8}),
- {-11,11} = beam_bounds:'rem'(any, {-12,8}),
+ {-7,7} = beam_bounds:bounds('rem', any, {1,8}),
+ {-11,11} = beam_bounds:bounds('rem', any, {-12,8}),
+
+ {-7,7} = beam_bounds:bounds('rem', {'-inf',10}, {1,8}),
+ {0,7} = beam_bounds:bounds('rem', {10,'+inf'}, {1,8}),
+
+ any = beam_bounds:bounds('rem', {1,10}, {'-inf',10}),
+ any = beam_bounds:bounds('rem', {1,10}, {10,'+inf'}),
+
+ any = beam_bounds:bounds('rem', {-10,10}, {'-inf',10}),
+ any = beam_bounds:bounds('rem', {-10,10}, {10,'+inf'}),
ok.
@@ -97,36 +159,99 @@ band_bounds(_Config) ->
test_commutative('band'),
%% Coverage.
- {0,17} = beam_bounds:'band'(any, {7,17}),
- {0,42} = beam_bounds:'band'({0,42}, any),
- any = beam_bounds:'band'({-1,1}, any),
- any = beam_bounds:'band'(any, {-10,0}),
- any = beam_bounds:'band'({-10,0},{-1,10}),
- any = beam_bounds:'band'({-20,-10},{-1,10}),
+ {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}),
ok.
bor_bounds(_Config) ->
test_commutative('bor'),
- any = beam_bounds:'bor'({-10,0},{-1,10}),
- any = beam_bounds:'bor'({-20,-10},{-1,10}),
+ any = beam_bounds:bounds('bor', {-10,0},{-1,10}),
+ any = beam_bounds:bounds('bor', {-20,-10}, {-1,10}),
ok.
bxor_bounds(_Config) ->
test_commutative('bxor'),
- any = beam_bounds:'bxor'({-10,0},{-1,10}),
- any = beam_bounds:'bxor'({-20,-10},{-1,10}),
+ any = beam_bounds:bounds('bxor', {-10,0}, {-1,10}),
+ any = beam_bounds:bounds('bxor', {-20,-10}, {-1,10}),
ok.
+bnot_bounds(_Config) ->
+ Min = -7,
+ Max = 7,
+ Seq = lists:seq(Min, Max),
+ _ = [bnot_bounds_1({A,B}) ||
+ A <- Seq,
+ B <- lists:nthtail(A-Min, Seq)],
+
+ {-43,'+inf'} = beam_bounds:bounds('bnot', {'-inf',42}),
+ {99,'+inf'} = beam_bounds:bounds('bnot', {'-inf',-100}),
+ {'-inf',-8} = beam_bounds:bounds('bnot', {7,'+inf'}),
+ {'-inf',9} = beam_bounds:bounds('bnot', {-10,'+inf'}),
+
+ -1 = bnot_bounds_2(0),
+
+ ok.
+
+bnot_bounds_1(R) ->
+ {HighestMin,LowestMax} = min_max_unary_op('bnot', R),
+ {Min,Max} = beam_bounds:bounds('bnot', R),
+ if
+ Min =< HighestMin, LowestMax =< Max ->
+ ok;
+ true ->
+ io:format("bnot(~p) evaluates to ~p; should be ~p\n",
+ [R,{Min,Max},{HighestMin,LowestMax}]),
+ ct:fail(bad_min_or_max)
+ end.
+
+%% GH-7145: 'bnot' converged too slowly, effectively hanging the compiler.
+bnot_bounds_2(0) -> -1;
+bnot_bounds_2(N) -> abs(bnot bnot_bounds_2(N)).
+
bsr_bounds(_Config) ->
- test_noncommutative('bsr', {-12,12}, {0,7}).
+ test_noncommutative('bsr', {-12,12}, {0,7}),
+
+ {0,10} = beam_bounds:bounds('bsr', {0,10}, {0,'+inf'}),
+ {0,2} = beam_bounds:bounds('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'}),
+
+ {'-inf',16} = beam_bounds:bounds('bsr', {'-inf',32}, {1,10}),
+ {-5,'+inf'} = beam_bounds:bounds('bsr', {-10,'+inf'}, {1,10}),
+
+ ok.
bsl_bounds(_Config) ->
- test_noncommutative('bsl', {-12,12}, {0,7}).
+ 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}),
+ any = beam_bounds:bounds('bsl', {-7,10}, {1,10_000}),
+
+ 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'}),
+
+ {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}),
+
+ {'-inf',-1} = beam_bounds:bounds('bsl', {-10,-1}, {500,1024}),
+ {0,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {500,1024}),
+
+ ok.
lt_bounds(_Config) ->
test_relop('<').
@@ -140,8 +265,98 @@ gt_bounds(_Config) ->
ge_bounds(_Config) ->
test_relop('>=').
+min_bounds(_Config) ->
+ test_commutative(min, {-12,12}),
+
+ {'-inf',-10} = min_bounds({'-inf',-10}, {1,100}),
+ {'-inf',1} = min_bounds({'-inf',1}, {1,100}),
+ {'-inf',50} = min_bounds({'-inf',50}, {1,100}),
+ {'-inf',100} = min_bounds({'-inf',500}, {1,100}),
+
+ {'-inf',-10} = min_bounds({'-inf',-10}, {1,'+inf'}),
+ {'-inf',1} = min_bounds({'-inf',1}, {1,'+inf'}),
+ {'-inf',700} = min_bounds({'-inf',700}, {1,'+inf'}),
+
+ {1,99} = min_bounds({1,99}, {100,'+inf'}),
+ {1,100} = min_bounds({1,100}, {100,'+inf'}),
+ {100,200} = min_bounds({150,200}, {100,'+inf'}),
+
+ ok.
+
+min_bounds(R1, R2) ->
+ Result = beam_bounds:bounds(min, R1, R2),
+ Result = beam_bounds:bounds(min, R2, R1).
+
+max_bounds(_Config) ->
+ test_commutative(max, {-12,12}),
+
+ {1,100} = max_bounds({'-inf',-10}, {1,100}),
+ {1,100} = max_bounds({'-inf',1}, {1,100}),
+ {1,100} = max_bounds({'-inf',50}, {1,100}),
+ {1,500} = max_bounds({'-inf',500}, {1,100}),
+
+ {1,'+inf'} = max_bounds({'-inf',-10}, {1,'+inf'}),
+ {1,'+inf'} = max_bounds({'-inf',1}, {1,'+inf'}),
+ {1,'+inf'} = max_bounds({'-inf',700}, {1,'+inf'}),
+
+ {100,'+inf'} = max_bounds({1,99}, {100,'+inf'}),
+ {100,'+inf'} = max_bounds({1,100}, {100,'+inf'}),
+ {150,'+inf'} = max_bounds({150,200}, {100,'+inf'}),
+
+ ok.
+
+max_bounds(R1, R2) ->
+ Result = beam_bounds:bounds(max, R1, R2),
+ Result = beam_bounds:bounds(max, R2, R1).
+
+abs_bounds(_Config) ->
+ Min = -7,
+ Max = 7,
+ Seq = lists:seq(Min, Max),
+ _ = [abs_bounds_1({A,B}) ||
+ A <- Seq,
+ B <- lists:nthtail(A-Min, Seq)],
+ ok.
+
+abs_bounds_1(R) ->
+ {HighestMin,LowestMax} = min_max_unary_op('abs', R),
+ {Min,Max} = beam_bounds:bounds(abs, R),
+ if
+ Min =< HighestMin, LowestMax =< Max ->
+ ok;
+ true ->
+ io:format("~p(~p) evaluates to ~p; should be ~p\n",
+ [bif_abs,R,{Min,Max},{HighestMin,LowestMax}]),
+ ct:fail(bad_min_or_max)
+ end.
+
+infer_lt_gt_bounds(_Config) ->
+ {{'-inf',-1}, {'-inf',0}} = infer_lt_gt({'-inf',0}, {'-inf',0}),
+ {{'-inf',1}, {'-inf',2}} = infer_lt_gt({'-inf',1}, {'-inf',2}),
+ {{'-inf',-2}, {'-inf',-1}} = infer_lt_gt({'-inf',1}, {'-inf',-1}),
+ {{'-inf',2}, {1,3}} = infer_lt_gt({'-inf',2}, {1,3}),
+
+ any = infer_lt_gt({'-inf',2}, {3,10}),
+ any = infer_lt_gt({'-inf',2}, {3,'+inf'}),
+
+ {{0,10}, {1,84}} = infer_lt_gt({0,10}, {'-inf',84}),
+ {{0,83}, {1,84}} = infer_lt_gt({0,'+inf'}, {'-inf',84}),
+
+ {{0,'+inf'}, {42, '+inf'}} = infer_lt_gt({0,'+inf'}, {42, '+inf'}),
+ {{100,'+inf'}, {101, '+inf'}} = infer_lt_gt({100,'+inf'}, {42, '+inf'}),
+
+ ok.
+
%%% Utilities
+infer_lt_gt(R1, R2) ->
+ case beam_bounds:infer_relop_types('>', R2, R1) of
+ {Rb,Ra} ->
+ {Ra,Rb} = beam_bounds:infer_relop_types('<', R1, R2);
+ any ->
+ any = beam_bounds:infer_relop_types('<', R1, R2)
+ end.
+
test_commutative(Op) ->
test_commutative(Op, {0,32}).
@@ -157,8 +372,8 @@ test_commutative(Op, {Min,Max}) ->
test_commutative_1(Op, R1, R2) ->
{HighestMin,LowestMax} = min_max_op(Op, R1, R2),
- {Min,Max} = beam_bounds:Op(R1, R2),
- {Min,Max} = beam_bounds:Op(R2, R1),
+ {Min,Max} = beam_bounds:bounds(Op, R1, R2),
+ {Min,Max} = beam_bounds:bounds(Op, R2, R1),
if
Min =< HighestMin, LowestMax =< Max ->
ok;
@@ -167,6 +382,7 @@ test_commutative_1(Op, R1, R2) ->
[Op,R1,R2,{Min,Max},{HighestMin,LowestMax}]),
ct:fail(bad_min_or_max)
end.
+
test_noncommutative(Op, Range) ->
test_noncommutative(Op, Range, Range).
@@ -182,7 +398,7 @@ test_noncommutative(Op, {Min1,Max1}, {Min2,Max2}) ->
test_noncommutative_1(Op, R1, R2) ->
{HighestMin,LowestMax} = min_max_op(Op, R1, R2),
- case beam_bounds:Op(R1, R2) of
+ case beam_bounds:bounds(Op, R1, R2) of
any ->
case {Op,R2} of
{'div',{0,0}} -> ok;
@@ -218,6 +434,20 @@ min_max_op_2(Op, A, C, D, MinMax) when C =< D ->
min_max_op_2(_Op, _, _, _, MinMax) ->
MinMax.
+min_max_unary_op(Op, {A,B}) ->
+ min_max_unary_op_1(Op, A, B, {infinity,-(1 bsl 24)}).
+
+min_max_unary_op_1(Op, A, B, {Min,Max}) when A =< B ->
+ Val = erlang:Op(A),
+ if
+ Min =< Val, Val =< Max ->
+ min_max_unary_op_1(Op, A + 1, B, {Min,Max});
+ true ->
+ min_max_unary_op_1(Op, A + 1, B, {min(Min, Val),max(Max, Val)})
+ end;
+min_max_unary_op_1(_Op, _, _, MinMax) ->
+ MinMax.
+
test_relop(Op) ->
Max = 15,
Seq = lists:seq(0, Max),
@@ -232,13 +462,45 @@ test_relop_1(Op, R1, R2) ->
Bool = rel_op(Op, R1, R2),
case beam_bounds:relop(Op, R1, R2) of
Bool ->
- ok;
+ test_infer_relop(Bool, Op, R1, R2);
Wrong ->
io:format("~p(~p, ~p) evaluates to ~p; should be ~p\n",
[Op,R1,R2,Wrong,Bool]),
ct:fail(bad_bool_result)
end.
+test_infer_relop(true, Op, R1, R2) ->
+ any = beam_bounds:infer_relop_types(Op, R1, R2);
+test_infer_relop(false, Op, R1, R2) ->
+ none = beam_bounds:infer_relop_types(Op, R1, R2);
+test_infer_relop('maybe', Op, {A0,B0}=R1, {C0,D0}=R2) ->
+ {{A,B},{C,D}} = beam_bounds:infer_relop_types(Op, R1, R2),
+ if
+ A =< B, C =< D, A0 =< A, B0 >= B, C0 =< C, D0 >= D ->
+ ok;
+ true ->
+ io:format("~p ~p infers as ~p ~p\n",
+ [R1,R2,{A,B},{C,D}]),
+ ct:fail(ranges_grew)
+ end,
+ _ = [begin
+ case in_range(X, {A,B}) andalso in_range(Y, {C,D}) of
+ true ->
+ ok;
+ false ->
+ io:format("X = ~p; Y = ~p\n", [X,Y]),
+ io:format("~p ~p infers as ~p ~p\n",
+ [R1,R2,{A,B},{C,D}]),
+ ct:fail(bad_inference)
+ end
+ end || X <- lists:seq(A0, B0),
+ Y <- lists:seq(C0, D0),
+ erlang:Op(X, Y)],
+ ok.
+
+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).
@@ -258,3 +520,37 @@ rel_op_2(Op, A, C, D, BoolResult0) when C =< D ->
rel_op_2(Op, A, C + 1, D, BoolResult);
rel_op_2(_Op, _, _, _, BoolResult) ->
BoolResult.
+
+redundant_masking(_Config) ->
+ Min = -7,
+ Max = 15,
+ Seq = lists:seq(Min, Max),
+ _ = [test_redundant_masking({A,B}, M) ||
+ A <- Seq,
+ B <- lists:nthtail(A-Min, Seq),
+ M <- Seq],
+
+ false = beam_bounds:is_masking_redundant({'-inf',10}, 16#ff),
+ false = beam_bounds:is_masking_redundant({0,'+inf'}, 16#ff),
+ ok.
+
+test_redundant_masking({A,B}=R, M) ->
+ ShouldBe = test_redundant_masking(A, B, M),
+ case beam_bounds:is_masking_redundant(R, M) of
+ ShouldBe ->
+ ok;
+ false when M band (M + 1) =/= 0 ->
+ %% M + 1 is not a power of two.
+ ok;
+ false when A =:= B ->
+ ok;
+ Unexpected ->
+ io:format("beam_bounds:is_masking_redundant(~p, ~p) "
+ "evaluates to ~p; should be ~p\n",
+ [R,M,Unexpected,ShouldBe]),
+ ct:fail(bad_boolean)
+ end.
+
+test_redundant_masking(A, B, M) when A =< B ->
+ A band M =:= A andalso test_redundant_masking(A + 1, B, M);
+test_redundant_masking(_, _, _) -> true.