Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compiler: Fix some vestigial +0.0/-0.0 issues #7902

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 77 additions & 35 deletions lib/compiler/src/beam_types.erl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
make_boolean/0,
make_cons/2,
make_float/1,
make_float/2,
make_integer/1,
make_integer/2]).

Expand Down Expand Up @@ -169,10 +168,16 @@ mts_records([{Key, A} | RsA], [{Key, B} | RsB], Acc) ->
none -> mts_records(RsA, RsB, Acc);
T -> mts_records(RsA, RsB, [{Key, T} | Acc])
end;
mts_records([{KeyA, _} | _ ]=RsA, [{KeyB, _} | RsB], Acc) when KeyA > KeyB ->
mts_records(RsA, RsB, Acc);
mts_records([{KeyA, _} | RsA], [{KeyB, _} | _] = RsB, Acc) when KeyA < KeyB ->
mts_records(RsA, RsB, Acc);
mts_records([{KeyA, _} | _]=RsA, [{KeyB, _} | _]=RsB, Acc) ->
%% We must use total ordering rather than plain '<' as -0.0 differs from
%% +0.0
case total_compare(KeyA, KeyB, fun erlang:'<'/2) of
true ->
mts_records(tl(RsA), RsB, Acc);
false ->
true = KeyA =/= KeyB, %Assertion.
mts_records(RsA, tl(RsB), Acc)
end;
mts_records(_RsA, [], [_|_]=Acc) ->
reverse(Acc);
mts_records([], _RsB, [_|_]=Acc) ->
Expand Down Expand Up @@ -320,10 +325,16 @@ jts_records(RsA, RsB, N, Acc) when N > ?TUPLE_SET_LIMIT ->
#t_tuple{} = normalize_tuple_set(Acc, B);
jts_records([{Key, A} | RsA], [{Key, B} | RsB], N, Acc) ->
jts_records(RsA, RsB, N + 1, [{Key, lub(A, B)} | Acc]);
jts_records([{KeyA, _} | _]=RsA, [{KeyB, B} | RsB], N, Acc) when KeyA > KeyB ->
jts_records(RsA, RsB, N + 1, [{KeyB, B} | Acc]);
jts_records([{KeyA, A} | RsA], [{KeyB, _} | _] = RsB, N, Acc) when KeyA < KeyB ->
jts_records(RsA, RsB, N + 1, [{KeyA, A} | Acc]);
jts_records([{KeyA, A} | _]=RsA, [{KeyB, B} | _]=RsB, N, Acc) ->
%% We must use total ordering rather than plain '<' as -0.0 differs from
%% +0.0
case total_compare(KeyA, KeyB, fun erlang:'<'/2) of
true ->
jts_records(tl(RsA), RsB, N + 1, [{KeyA, A} | Acc]);
false ->
true = KeyA =/= KeyB, %Assertion.
jts_records(RsA, tl(RsB), N + 1, [{KeyB, B} | Acc])
end;
jts_records([{KeyA, A} | RsA], [], N, Acc) ->
jts_records(RsA, [], N + 1, [{KeyA, A} | Acc]);
jts_records([], [{KeyB, B} | RsB], N, Acc) ->
Expand Down Expand Up @@ -479,8 +490,7 @@ is_bs_matchable_type(Type) ->
Result :: {ok, term()} | error.
get_singleton_value(#t_atom{elements=[Atom]}) ->
{ok, Atom};
get_singleton_value(#t_float{elements={Float,Float}}) when Float /= 0 ->
%% 0.0 is not actually a singleton as it has two encodings: 0.0 and -0.0
get_singleton_value(#t_float{elements={Float,Float}}) ->
{ok, Float};
get_singleton_value(#t_integer{elements={Int,Int}}) ->
{ok, Int};
Expand Down Expand Up @@ -697,11 +707,7 @@ make_cons(Head0, Tail) ->

-spec make_float(float()) -> type().
make_float(Float) when is_float(Float) ->
make_float(Float, Float).

-spec make_float(float(), float()) -> type().
make_float(Min, Max) when is_float(Min), is_float(Max), Min =< Max ->
#t_float{elements={Min, Max}}.
#t_float{elements={Float,Float}}.

-spec make_integer(integer()) -> type().
make_integer(Int) when is_integer(Int) ->
Expand Down Expand Up @@ -882,7 +888,7 @@ glb(#t_integer{elements=R1}, #t_integer{elements=R2}) ->
glb(#t_integer{elements=R1}, #t_number{elements=R2}) ->
integer_from_range(glb_ranges(R1, R2));
glb(#t_float{elements=R1}, #t_number{elements=R2}) ->
float_from_range(glb_ranges(R1, R2));
float_from_range(glb_ranges(R1, number_to_float_range(R2)));
glb(#t_list{type=TypeA,terminator=TermA},
#t_list{type=TypeB,terminator=TermB}) ->
%% A list is a union of `[type() | _]` and `[]`, so we're left with
Expand All @@ -903,7 +909,7 @@ glb(#t_number{elements=R1}, #t_number{elements=R2}) ->
glb(#t_number{elements=R1}, #t_integer{elements=R2}) ->
integer_from_range(glb_ranges(R1, R2));
glb(#t_number{elements=R1}, #t_float{elements=R2}) ->
float_from_range(glb_ranges(R1, R2));
float_from_range(glb_ranges(number_to_float_range(R1), R2));
glb(#t_map{super_key=SKeyA,super_value=SValueA},
#t_map{super_key=SKeyB,super_value=SValueB}) ->
%% Note the use of meet/2; elements don't need to be normal types.
Expand Down Expand Up @@ -1132,6 +1138,14 @@ lub_ranges({MinA,MaxA}, {MinB,MaxB}) ->
lub_ranges(_, _) ->
any.

%% Expands integer 0 to `-0.0 .. +0.0`
number_to_float_range({Min, 0}) ->
number_to_float_range({Min, +0.0});
number_to_float_range({0, Max}) ->
number_to_float_range({-0.0, Max});
number_to_float_range(Other) ->
Other.

lub_bs_matchable(UnitA, UnitB) ->
#t_bs_matchable{tail_unit=gcd(UnitA, UnitB)}.

Expand Down Expand Up @@ -1179,12 +1193,13 @@ float_from_range(none) ->
none;
float_from_range(any) ->
#t_float{};
float_from_range({Min0,Max0}) ->
case {safe_float(Min0),safe_float(Max0)} of
float_from_range({Min0, Max0}) ->
true = inf_le(Min0, Max0), %Assertion.
case {safe_float(Min0), safe_float(Max0)} of
{'-inf','+inf'} ->
#t_float{};
{Min,Max} ->
#t_float{elements={Min,Max}}
{Min, Max} ->
#t_float{elements={Min, Max}}
end.

safe_float(N) when is_number(N) ->
Expand Down Expand Up @@ -1218,21 +1233,48 @@ number_from_range(N) ->
none
end.

inf_le('-inf', _) -> true;
inf_le(A, B) -> A =< B.

inf_ge(_, '-inf') -> true;
inf_ge('-inf', _) -> false;
inf_ge(A, B) -> A >= B.
inf_le('-inf', _) ->
true;
inf_le(A, B) when is_float(A), is_float(B) ->
%% When float ranges are compared to float ranges, the total ordering
%% function must be used to preserve `-0.0 =/= +0.0`.
total_compare(A, B, fun erlang:'=<'/2);
inf_le(A, B) ->
A =< B.

inf_ge(_, '-inf') ->
true;
inf_ge('-inf', _) ->
false;
inf_ge(A, B) when is_float(A), is_float(B) ->
total_compare(A, B, fun erlang:'>='/2);
inf_ge(A, B) ->
A >= B.

inf_min(A, B) when A =:= '-inf'; B =:= '-inf' ->
'-inf';
inf_min(A, B) when is_float(A), is_float(B) ->
case total_compare(A, B, fun erlang:'=<'/2) of
true -> A;
false -> B
end;
inf_min(A, B) ->
min(A, B).

inf_min(A, B) when A =:= '-inf'; B =:= '-inf' -> '-inf';
inf_min(A, B) when A =< B -> A;
inf_min(A, B) when A > B -> B.
inf_max('-inf', B) ->
B;
inf_max(A, '-inf') ->
A;
inf_max(A, B) when is_float(A), is_float(B) ->
case total_compare(A, B, fun erlang:'>='/2) of
true -> A;
false -> B
end;
inf_max(A, B) ->
max(A, B).

inf_max('-inf', B) -> B;
inf_max(A, '-inf') -> A;
inf_max(A, B) when A >= B -> A;
inf_max(A, B) when A < B -> B.
total_compare(A, B, Order) ->
Order(erts_internal:cmp_term(A, B), 0).

%%

Expand Down
46 changes: 44 additions & 2 deletions lib/compiler/test/beam_type_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
switch_fail_inference/1,failures/1,
cover_maps_functions/1,min_max_mixed_types/1,
not_equal/1,infer_relops/1,binary_unit/1,premature_concretization/1,
funs/1,will_succeed/1]).
funs/1,will_succeed/1,float_confusion/1]).

%% Force id/1 to return 'any'.
-export([id/1]).
Expand Down Expand Up @@ -76,7 +76,8 @@ groups() ->
binary_unit,
premature_concretization,
funs,
will_succeed
will_succeed,
float_confusion
]}].

init_per_suite(Config) ->
Expand Down Expand Up @@ -1505,6 +1506,47 @@ will_succeed_1(_V0, _V1)
will_succeed_1(_, _) ->
b.

%% GH-7901: Range operations did not honor the total order of floats.
float_confusion(_Config) ->
ok = float_confusion_1(catch (true = ok), -0.0),
ok = float_confusion_1(ok, 0.0),
{'EXIT', _} = catch float_confusion_2(),
{'EXIT', _} = catch float_confusion_3(id(0.0)),
ok = float_confusion_4(id(1)),
{'EXIT', _} = catch float_confusion_5(),
ok.

float_confusion_1(_, _) ->
ok.

float_confusion_2() ->
[ok || _ := _ <- ok,
float_confusion_crash(catch float_confusion_crash(ok, -1), -0.0)].

float_confusion_crash(_, 18446744073709551615) ->
ok.

float_confusion_3(V) ->
-0.0 = abs(V),
ok.

float_confusion_4(V) when -0.0 < floor(V band 1) ->
ok.

float_confusion_5() ->
-0.0 =
case
fun() ->
ok
end
of
_V2 when (_V2 > ok) ->
2147483647.0;
_ ->
-2147483648
end * 0,
ok.

%%%
%%% Common utilities.
%%%
Expand Down
Loading