diff options
Diffstat (limited to 'lib/compiler/src/beam_ssa_type.erl')
-rw-r--r-- | lib/compiler/src/beam_ssa_type.erl | 866 |
1 files changed, 628 insertions, 238 deletions
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 77b723caa0..725eb9ee29 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2018-2022. All Rights Reserved. +%% Copyright Ericsson AB 2018-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -207,8 +207,8 @@ sig_function_1(Id, StMap, State0, FuncDb) -> name=#b_literal{val=unknown}, arity=0}]}, - Ds = maps:from_list([{Var, FakeCall#b_set{dst=Var}} || - #b_var{}=Var <- Args]), + Ds = #{Var => FakeCall#b_set{dst=Var} || + #b_var{}=Var <- Args}, Ls = #{ ?EXCEPTION_BLOCK => {incoming, Ts}, 0 => {incoming, Ts} }, @@ -378,11 +378,7 @@ init_sig_st(StMap, FuncDb) -> wl=wl_defer_list(Roots, wl_new()) }. init_sig_roots(FuncDb) -> - maps:fold(fun(Id, #func_info{exported=true}, Acc) -> - [Id | Acc]; - (_, _, Acc) -> - Acc - end, [], FuncDb). + [Id || Id := #func_info{exported=true} <- FuncDb]. init_sig_args([Root | Roots], StMap, Acc) -> #opt_st{args=Args0} = map_get(Root, StMap), @@ -438,14 +434,14 @@ opt_continue(Linear0, Args, Anno, FuncDb) when FuncDb =/= #{} -> #{ Id := #func_info{exported=true} } -> %% We can't infer the parameter types of exported functions, but %% running the pass again could still help other functions. - Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]), + Ts = #{V => any || #b_var{}=V <- Args}, opt_function(Linear0, Args, Id, Ts, FuncDb) end; opt_continue(Linear0, Args, Anno, _FuncDb) -> %% Module-level optimization is disabled, pass an empty function database %% so we only perform local optimizations. Id = get_func_id(Anno), - Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]), + Ts = #{V => any || #b_var{}=V <- Args}, {Linear, _} = opt_function(Linear0, Args, Id, Ts, #{}), {Linear, #{}}. @@ -491,8 +487,8 @@ do_opt_function(Linear0, Args, Id, Ts, FuncDb0, MetaCache) -> name=#b_literal{val=unknown}, arity=0}]}, - Ds = maps:from_list([{Var, FakeCall#b_set{dst=Var}} || - #b_var{}=Var <- Args]), + Ds = #{Var => FakeCall#b_set{dst=Var} || + #b_var{}=Var <- Args}, Ls = #{ ?EXCEPTION_BLOCK => {incoming, Ts}, 0 => {incoming, Ts} }, @@ -533,7 +529,9 @@ opt_bs([{L, #b_blk{is=Is0,last=Last0}=Blk0} | Bs], SuccTypes = update_success_types(Last1, Ts, Ds, Meta, SuccTypes0), UsedOnce = Meta#metadata.used_once, - {Last, Ls1} = update_successors(Last1, Ts, Ds, Ls0, UsedOnce), + {Last2, Ls1} = update_successors(Last1, Ts, Ds, Ls0, UsedOnce), + + Last = opt_anno_types(Last2, Ts), Ls = Ls1#{ L := {outgoing, Ts} }, %Assertion. @@ -598,7 +596,17 @@ opt_anno_types(#b_set{op=Op,args=Args}=I, Ts) -> case benefits_from_type_anno(Op, Args) of true -> opt_anno_types_1(I, Args, Ts, 0, #{}); false -> I - end. + end; +opt_anno_types(#b_switch{anno=Anno0,arg=Arg}=I, Ts) -> + case concrete_type(Arg, Ts) of + any -> + I; + Type -> + Anno = Anno0#{arg_types => #{0 => Type}}, + I#b_switch{anno=Anno} + end; +opt_anno_types(I, _Ts) -> + I. opt_anno_types_1(I, [#b_var{}=Var | Args], Ts, Index, Acc0) -> case concrete_type(Var, Ts) of @@ -626,14 +634,12 @@ opt_anno_types_1(#b_set{anno=Anno0}=I, [], _Ts, _Index, Acc) -> end. %% Only add type annotations when we know we'll make good use of them. -benefits_from_type_anno({bif,'=:='}, _Args) -> +benefits_from_type_anno({bif,_Op}, _Args) -> true; -benefits_from_type_anno({bif,'=/='}, _Args) -> - true; -benefits_from_type_anno({bif,Op}, Args) -> - not erl_internal:bool_op(Op, length(Args)); benefits_from_type_anno(bs_create_bin, _Args) -> true; +benefits_from_type_anno(bs_match, _Args) -> + true; benefits_from_type_anno(is_tagged_tuple, _Args) -> true; benefits_from_type_anno(call, [#b_var{} | _]) -> @@ -906,8 +912,20 @@ update_anno_types_1([#b_var{}=V|As], Ts, Index, ArgTypes) -> 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 -> - true = T =/= none, %Assertion. update_anno_types_1(As, Ts, Index + 1, ArgTypes#{Index => T}) end; update_anno_types_1([_|As], Ts, Index, ArgTypes) -> @@ -936,12 +954,27 @@ simplify_terminator(#b_switch{arg=Arg0,fail=Fail,list=List0}=Sw0, #b_br{}=Br -> simplify_terminator(Br, Ts, Ds, Sub) end; -simplify_terminator(#b_ret{arg=Arg}=Ret, Ts, Ds, Sub) -> +simplify_terminator(#b_ret{arg=Arg,anno=Anno0}=Ret0, Ts, Ds, Sub) -> %% Reducing the result of a call to a literal (fairly common for 'ok') %% breaks tail call optimization. - case Ds of - #{ Arg := #b_set{op=call}} -> Ret; - #{} -> Ret#b_ret{arg=simplify_arg(Arg, Ts, Sub)} + Ret = case Ds of + #{ Arg := #b_set{op=call}} -> Ret0; + #{} -> Ret0#b_ret{arg=simplify_arg(Arg, Ts, Sub)} + end, + %% Annotate the terminator with the type it returns, skip the + %% annotation if nothing is yet known about the variable. The + %% annotation is used by the alias analysis pass. + Type = case {Arg, Ts} of + {#b_literal{},_} -> concrete_type(Arg, Ts); + {_,#{Arg:=_}} -> concrete_type(Arg, Ts); + _ -> any + end, + case Type of + any -> + Ret; + _ -> + Anno = Anno0#{ result_type => Type }, + Ret#b_ret{anno=Anno} end. %% @@ -1012,10 +1045,24 @@ simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> Ts = update_types(I, Ts0, Ds0), 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) -> + Args = simplify_args(Args0, Ts0, Sub), + I1 = I0#b_set{args=Args}, + #t_bitstring{size_unit=Unit} = T = type(Op, Args, Anno, Ts0, Ds0), + I2 = case T of + #t_bitstring{appendable=true} -> + beam_ssa:add_anno(result_type, T, I1); + _ -> I1 + end, + I = beam_ssa:add_anno(unit, Unit, I2), + Ts = Ts0#{ Dst => T }, + Ds = Ds0#{ Dst => I }, + {I, Ts, Ds}; simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> Args = simplify_args(Args0, Ts0, Sub), I1 = beam_ssa:normalize(I0#b_set{args=Args}), - case simplify(I1, Ts0) of + case simplify(I1, Ts0, Ds0) of #b_set{}=I -> Ts = update_types(I, Ts0, Ds0), Ds = Ds0#{ Dst => I }, @@ -1026,54 +1073,67 @@ simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> Sub#{ Dst => Var } end. -simplify(#b_set{op={bif,'and'},args=Args}=I, Ts) -> +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}}] -> + case beam_bounds:is_masking_redundant(R, M) of + true -> + %% M is mask that will have no effect. + hd(Args); + false -> + eval_bif(I, Ts, Ds) + end; + [_,_] -> + eval_bif(I, Ts, Ds) + end; +simplify(#b_set{op={bif,'and'},args=Args}=I, Ts, Ds) -> case is_safe_bool_op(Args, Ts) of true -> case Args of [_,#b_literal{val=false}=Res] -> Res; [Res,#b_literal{val=true}] -> Res; - _ -> eval_bif(I, Ts) + _ -> eval_bif(I, Ts, Ds) end; false -> I end; -simplify(#b_set{op={bif,'or'},args=Args}=I, Ts) -> +simplify(#b_set{op={bif,'or'},args=Args}=I, Ts, Ds) -> case is_safe_bool_op(Args, Ts) of true -> case Args of [Res,#b_literal{val=false}] -> Res; [_,#b_literal{val=true}=Res] -> Res; - _ -> eval_bif(I, Ts) + _ -> eval_bif(I, Ts, Ds) end; false -> I end; -simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I0, Ts) -> +simplify(#b_set{op={bif,element},args=[#b_literal{val=Index},Tuple]}=I0, Ts, Ds) -> case normalized_type(Tuple, Ts) of #t_tuple{size=Size} when is_integer(Index), 1 =< Index, Index =< Size -> I = I0#b_set{op=get_tuple_element, args=[Tuple,#b_literal{val=Index-1}]}, - simplify(I, Ts); + simplify(I, Ts, Ds); _ -> - eval_bif(I0, Ts) + eval_bif(I0, Ts, Ds) end; -simplify(#b_set{op={bif,hd},args=[List]}=I, Ts) -> +simplify(#b_set{op={bif,hd},args=[List]}=I, Ts, Ds) -> case normalized_type(List, Ts) of #t_cons{} -> I#b_set{op=get_hd}; _ -> - eval_bif(I, Ts) + eval_bif(I, Ts, Ds) end; -simplify(#b_set{op={bif,tl},args=[List]}=I, Ts) -> +simplify(#b_set{op={bif,tl},args=[List]}=I, Ts, Ds) -> case normalized_type(List, Ts) of #t_cons{} -> I#b_set{op=get_tl}; _ -> - eval_bif(I, Ts) + eval_bif(I, Ts, Ds) end; -simplify(#b_set{op={bif,size},args=[Term]}=I, Ts) -> +simplify(#b_set{op={bif,size},args=[Term]}=I, Ts, Ds) -> case normalized_type(Term, Ts) of #t_tuple{} -> simplify(I#b_set{op={bif,tuple_size}}, Ts); @@ -1081,49 +1141,57 @@ simplify(#b_set{op={bif,size},args=[Term]}=I, Ts) -> %% If the bitstring is a binary (the size in bits is %% evenly divisibly by 8), byte_size/1 gives %% the same result as size/1. - simplify(I#b_set{op={bif,byte_size}}, Ts); + simplify(I#b_set{op={bif,byte_size}}, Ts, Ds); _ -> - eval_bif(I, Ts) + eval_bif(I, Ts, Ds) end; -simplify(#b_set{op={bif,tuple_size},args=[Term]}=I, Ts) -> +simplify(#b_set{op={bif,tuple_size},args=[Term]}=I, Ts, _Ds) -> case normalized_type(Term, Ts) of #t_tuple{size=Size,exact=true} -> #b_literal{val=Size}; _ -> I end; -simplify(#b_set{op={bif,is_map_key},args=[Key,Map]}=I, Ts) -> +simplify(#b_set{op={bif,is_map_key},args=[Key,Map]}=I, Ts, _Ds) -> case normalized_type(Map, Ts) of #t_map{} -> I#b_set{op=has_map_field,args=[Map,Key]}; _ -> I end; -simplify(#b_set{op={bif,Op0},args=Args}=I, Ts) when Op0 =:= '=='; - Op0 =:= '/=' -> - Types = normalized_types(Args, Ts), - EqEq0 = case {beam_types:meet(Types),beam_types:join(Types)} of - {none,any} -> true; - {#t_integer{},#t_integer{}} -> true; - {#t_float{},#t_float{}} -> true; - {#t_bitstring{},_} -> true; - {#t_atom{},_} -> true; - {_,_} -> false - end, - EqEq = EqEq0 orelse any_non_numeric_argument(Args, Ts), +simplify(#b_set{op={bif,Op0},args=[A,B]}=I, Ts, Ds) when Op0 =:= '=='; + Op0 =:= '/=' -> + EqEq = case {number_type(A, Ts),number_type(B, Ts)} of + {none,_} -> + %% The LHS does not contain any numbers. A strict + %% test is always safe. + true; + {_,none} -> + %% The RHS does not contain any numbers. A strict + %% test is always safe. + true; + {#t_integer{},#t_integer{}} -> + %% Both side contain integers but no floats. + true; + {#t_float{},#t_float{}} -> + %% Both side contain floats but no integers. + true; + {_,_} -> + false + end, case EqEq of true -> Op = case Op0 of '==' -> '=:='; '/=' -> '=/=' end, - simplify(I#b_set{op={bif,Op}}, Ts); + simplify(I#b_set{op={bif,Op}}, Ts, Ds); false -> - eval_bif(I, Ts) + eval_bif(I, Ts, Ds) end; -simplify(#b_set{op={bif,'=:='},args=[Same,Same]}, _Ts) -> +simplify(#b_set{op={bif,'=:='},args=[Same,Same]}, _Ts, _Ds) -> #b_literal{val=true}; -simplify(#b_set{op={bif,'=:='},args=[LHS,RHS]}=I, Ts) -> +simplify(#b_set{op={bif,'=:='},args=[LHS,RHS]}=I, Ts, Ds) -> LType = concrete_type(LHS, Ts), RType = concrete_type(RHS, Ts), case beam_types:meet(LType, RType) of @@ -1145,12 +1213,12 @@ simplify(#b_set{op={bif,'=:='},args=[LHS,RHS]}=I, Ts) -> %% comparison operator (such as >=) that can be %% translated to test instruction, this %% optimization will eliminate one instruction. - simplify(I#b_set{op={bif,'not'},args=[LHS]}, Ts); + simplify(I#b_set{op={bif,'not'},args=[LHS]}, Ts, Ds); {_,_} -> - eval_bif(I, Ts) + eval_bif(I, Ts, Ds) end end; -simplify(#b_set{op={bif,is_list},args=[Src]}=I0, Ts) -> +simplify(#b_set{op={bif,is_list},args=[Src]}=I0, Ts, Ds) -> case concrete_type(Src, Ts) of #t_union{list=#t_cons{}} -> I = I0#b_set{op=is_nonempty_list,args=[Src]}, @@ -1161,17 +1229,26 @@ simplify(#b_set{op={bif,is_list},args=[Src]}=I0, Ts) -> #t_union{list=nil} -> I0#b_set{op={bif,'=:='},args=[Src,#b_literal{val=[]}]}; _ -> - eval_bif(I0, Ts) + eval_bif(I0, Ts, Ds) end; -simplify(#b_set{op={bif,Op},args=Args}=I, Ts) -> +simplify(#b_set{op={bif,Op},args=[Term]}=I, Ts, _Ds) + when Op =:= trunc; Op =:= round; Op =:= ceil; Op =:= floor -> + case normalized_type(Term, Ts) of + #t_integer{} -> Term; + _ -> I + end; +simplify(#b_set{op={bif,Op},args=Args}=I, Ts, Ds) -> Types = normalized_types(Args, Ts), case is_float_op(Op, Types) of false -> - eval_bif(I, Ts); + eval_bif(I, Ts, Ds); true -> AnnoArgs = [anno_float_arg(A) || A <- Types], - eval_bif(beam_ssa:add_anno(float_op, AnnoArgs, I), Ts) + eval_bif(beam_ssa:add_anno(float_op, AnnoArgs, I), Ts, Ds) end; +simplify(I, Ts, _Ds) -> + simplify(I, Ts). + simplify(#b_set{op=bs_extract,args=[Ctx]}=I, Ts) -> case concrete_type(Ctx, Ts) of #t_bitstring{} -> @@ -1306,6 +1383,26 @@ simplify(#b_set{op=peek_message,args=[#b_literal{val=Val}]}=I, _Ts) -> simplify(#b_set{op=recv_marker_clear,args=[#b_literal{}]}, _Ts) -> %% Not a receive marker: see the 'peek_message' case. #b_literal{val=none}; +simplify(#b_set{op=update_tuple,args=[Src | Updates]}=I, Ts) -> + case {normalized_type(Src, Ts), update_tuple_highest_index(Updates, -1)} of + {#t_tuple{exact=true,size=Size}, Highest} when Highest =< Size, + Size < 512 -> + Args = [#b_literal{val=reuse}, + #b_literal{val=Size}, + Src | Updates], + simplify(I#b_set{op=update_record,args=Args}, Ts); + {_, _} -> + I + end; +simplify(#b_set{op=update_record,args=[Hint0, Size, Src | Updates0]}=I, Ts) -> + case simplify_update_record(Src, Hint0, Updates0, Ts) of + {changed, _, []} -> + Src; + {changed, Hint, [_|_]=Updates} -> + I#b_set{op=update_record,args=[Hint, Size, Src | Updates]}; + unchanged -> + I + end; simplify(I, _Ts) -> I. @@ -1345,14 +1442,14 @@ will_succeed_1(#b_set{op=bs_start_match,args=[_, Arg]}, _Src, Ts) -> end; will_succeed_1(#b_set{op={bif,Bif},args=BifArgs}, _Src, Ts) -> - ArgTypes = normalized_types(BifArgs, Ts), + ArgTypes = concrete_types(BifArgs, Ts), beam_call_types:will_succeed(erlang, Bif, ArgTypes); will_succeed_1(#b_set{op=call, args=[#b_remote{mod=#b_literal{val=Mod}, name=#b_literal{val=Func}} | CallArgs]}, _Src, Ts) -> - ArgTypes = normalized_types(CallArgs, Ts), + ArgTypes = concrete_types(CallArgs, Ts), beam_call_types:will_succeed(Mod, Func, ArgTypes); will_succeed_1(#b_set{op=get_hd}, _Src, _Ts) -> @@ -1363,8 +1460,14 @@ will_succeed_1(#b_set{op=has_map_field}, _Src, _Ts) -> yes; will_succeed_1(#b_set{op=get_tuple_element}, _Src, _Ts) -> yes; -will_succeed_1(#b_set{op=put_tuple}, _Src, _Ts) -> +will_succeed_1(#b_set{op=update_tuple,args=[Tuple | Updates]}, _Src, Ts) -> + TupleType = concrete_type(Tuple, Ts), + HighestIndex = update_tuple_highest_index(Updates, -1), + Args = [beam_types:make_integer(HighestIndex), TupleType, any], + beam_call_types:will_succeed(erlang, setelement, Args); +will_succeed_1(#b_set{op=update_record}, _Src, _Ts) -> yes; + will_succeed_1(#b_set{op=bs_create_bin}, _Src, _Ts) -> %% Intentionally don't try to determine whether construction will %% fail. Construction is unlikely to fail, and if it fails, the @@ -1401,6 +1504,46 @@ will_succeed_1(#b_set{op=wait_timeout}, _Src, _Ts) -> will_succeed_1(#b_set{}, _Src, _Ts) -> 'maybe'. +simplify_update_record(Src, Hint0, Updates, Ts) -> + case sur_1(Updates, concrete_type(Src, Ts), Ts, Hint0, []) of + {Hint0, []} -> + unchanged; + {Hint, Skipped} -> + {changed, Hint, sur_skip(Updates, Skipped)} + end. + +sur_1([Index, Arg | Updates], RecordType, Ts, Hint, Skipped) -> + FieldType = concrete_type(Arg, Ts), + IndexType = concrete_type(Index, Ts), + Singleton = beam_types:is_singleton_type(FieldType), + case beam_call_types:types(erlang, element, [IndexType, RecordType]) of + {FieldType, _, _} when Singleton -> + %% We can skip setting fields when we KNOW that they already have + %% the value we're trying to set. + sur_1(Updates, RecordType, Ts, Hint, Skipped ++ [Index]); + {ElementType, _, _} -> + case beam_types:meet(FieldType, ElementType) of + none -> + %% The element at the index can never have the same value + %% as the value we're trying to set. Tell the instruction + %% not to bother checking whether it's possible to reuse + %% the source. + sur_1(Updates, RecordType, Ts, + #b_literal{val=copy}, Skipped); + _ -> + sur_1(Updates, RecordType, Ts, Hint, Skipped) + end + end; +sur_1([], _RecordType, _Ts, Hint, Skipped) -> + {Hint, Skipped}. + +sur_skip([Index, _Arg | Updates], [Index | Skipped]) -> + sur_skip(Updates, Skipped); +sur_skip([Index, Arg | Updates], Skipped) -> + [Index, Arg | sur_skip(Updates, Skipped)]; +sur_skip([], []) -> + []. + simplify_is_record(I, #t_tuple{exact=Exact, size=Size, elements=Es}, @@ -1489,17 +1632,6 @@ simplify_remote_call(erlang, throw, [Term], Ts, I) -> beam_ssa:add_anno(thrown_type, Type, I); simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _Ts, _I) -> Tl; -simplify_remote_call(erlang, setelement, - [#b_literal{val=Pos}, - #b_literal{val=Tuple}, - #b_var{}=Value], _Ts, I) - when is_integer(Pos), 1 =< Pos, Pos =< tuple_size(Tuple) -> - %% Position is a literal integer and the shape of the - %% tuple is known. - Els0 = [#b_literal{val=El} || El <- tuple_to_list(Tuple)], - {Bef,[_|Aft]} = split(Pos - 1, Els0), - Els = Bef ++ [Value|Aft], - I#b_set{op=put_tuple,args=Els}; simplify_remote_call(Mod, Name, Args, _Ts, I) -> case erl_bifs:is_pure(Mod, Name, length(Args)) of true -> @@ -1533,51 +1665,58 @@ simplify_pure_call(Mod, Name, Args0, I) -> end end. -any_non_numeric_argument([#b_literal{val=Lit}|_], _Ts) -> - is_non_numeric(Lit); -any_non_numeric_argument([#b_var{}=V|T], Ts) -> - is_non_numeric_type(concrete_type(V, Ts)) orelse - any_non_numeric_argument(T, Ts); -any_non_numeric_argument([], _Ts) -> false. - -is_non_numeric([H|T]) -> - is_non_numeric(H) andalso is_non_numeric(T); -is_non_numeric(Tuple) when is_tuple(Tuple) -> - is_non_numeric_tuple(Tuple, tuple_size(Tuple)); -is_non_numeric(Map) when is_map(Map) -> - %% Starting from OTP 18, map keys are compared using `=:=`. - %% Therefore, we only need to check that the values in the map are - %% non-numeric. (Support for compiling BEAM files for OTP releases - %% older than OTP 18 has been dropped.) - is_non_numeric(maps:values(Map)); -is_non_numeric(Num) when is_number(Num) -> - false; -is_non_numeric(_) -> true. - -is_non_numeric_tuple(Tuple, El) when El >= 1 -> - is_non_numeric(element(El, Tuple)) andalso - is_non_numeric_tuple(Tuple, El-1); -is_non_numeric_tuple(_Tuple, 0) -> true. - -is_non_numeric_type(#t_atom{}) -> true; -is_non_numeric_type(#t_bitstring{}) -> true; -is_non_numeric_type(#t_cons{type=Type,terminator=Terminator}) -> - is_non_numeric_type(Type) andalso is_non_numeric_type(Terminator); -is_non_numeric_type(#t_list{type=Type,terminator=Terminator}) -> - is_non_numeric_type(Type) andalso is_non_numeric_type(Terminator); -is_non_numeric_type(#t_map{super_value=Value}) -> - is_non_numeric_type(Value); -is_non_numeric_type(nil) -> true; -is_non_numeric_type(#t_tuple{size=Size,exact=true,elements=Types}) - when map_size(Types) =:= Size -> - is_non_numeric_tuple_type(Size, Types); -is_non_numeric_type(_) -> false. - -is_non_numeric_tuple_type(0, _Types) -> - true; -is_non_numeric_tuple_type(Pos, Types) -> - is_non_numeric_type(map_get(Pos, Types)) andalso - is_non_numeric_tuple_type(Pos - 1, Types). +number_type(V, Ts) -> + number_type_1(concrete_type(V, Ts)). + +number_type_1(any) -> + #t_number{}; +number_type_1(Type) -> + N = beam_types:meet(Type, #t_number{}), + + List = case beam_types:meet(Type, #t_list{}) of + #t_cons{type=Head,terminator=Tail} -> + beam_types:join(number_type_1(Head), number_type_1(Tail)); + #t_list{type=Head,terminator=Tail} -> + beam_types:join(number_type_1(Head), number_type_1(Tail)); + nil -> + none; + none -> + none + end, + + Tup = case beam_types:meet(Type, #t_tuple{}) of + #t_tuple{size=Size,exact=true,elements=ElemTypes} + when map_size(ElemTypes) =:= Size -> + beam_types:join([number_type_1(ET) || + ET <- maps:values(ElemTypes)] ++ [none]); + #t_tuple{} -> + #t_number{}; + #t_union{} -> + #t_number{}; + none -> + none + end, + + Map = case beam_types:meet(Type, #t_map{}) of + #t_map{super_value=MapValue} -> + %% Starting from OTP 18, map keys are compared using + %% `=:=`. Therefore, we only need to use the values + %% in the map. + MapValue; + none -> + none + end, + + Fun = case beam_types:meet(Type, #t_fun{}) of + #t_fun{} -> + %% The environment could contain a number. We don't + %% keep track of the environment in #t_fun{}. + #t_number{}; + none -> + none + end, + + beam_types:join([N,List,Tup,Map,Fun]). make_literal_list(Args) -> make_literal_list(Args, []). @@ -1595,7 +1734,7 @@ is_safe_bool_op([LHS, RHS], Ts) -> beam_types:is_boolean_type(LType) andalso beam_types:is_boolean_type(RType). -eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) -> +eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts, Ds) -> Arity = length(Args), case erl_bifs:is_pure(erlang, Bif, Arity) of false -> @@ -1603,7 +1742,7 @@ eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) -> true -> case make_literal_list(Args) of none -> - eval_bif_1(I, Bif, concrete_types(Args, Ts)); + eval_bif_1(I, Bif, Ts, Ds); LitArgs -> try apply(erlang, Bif, LitArgs) of Val -> #b_literal{val=Val} @@ -1614,8 +1753,8 @@ eval_bif(#b_set{op={bif,Bif},args=Args}=I, Ts) -> end end. -eval_bif_1(I, Op, Types) -> - case Types of +eval_bif_1(#b_set{args=Args}=I, Op, Ts, Ds) -> + case concrete_types(Args, Ts) of [#t_integer{},#t_integer{elements={0,0}}] when Op =:= 'bor'; Op =:= 'bxor' -> #b_set{args=[Result,_]} = I, Result; @@ -1637,10 +1776,30 @@ eval_bif_1(I, Op, Types) -> false -> I end; + [#t_integer{},#t_integer{}] -> + reassociate(I, Ts, Ds); _ -> I end. +reassociate(#b_set{op={bif,OpB},args=[#b_var{}=V0,#b_literal{val=B0}]}=I, _Ts, Ds) + when OpB =:= '+'; OpB =:= '-' -> + case map_get(V0, Ds) of + #b_set{op={bif,OpA},args=[#b_var{}=V,#b_literal{val=A0}]} + when OpA =:= '+'; OpA =:= '-' -> + A = erlang:OpA(A0), + B = erlang:OpB(B0), + case A + B of + Combined when Combined < 0 -> + I#b_set{op={bif,'-'},args=[V,#b_literal{val=-Combined}]}; + Combined when Combined >= 0 -> + I#b_set{op={bif,'+'},args=[V,#b_literal{val=Combined}]} + end; + #b_set{} -> + I + end; +reassociate(I, _Ts, _Ds) -> I. + simplify_args(Args, Ts, Sub) -> [simplify_arg(Arg, Ts, Sub) || Arg <- Args]. @@ -1904,7 +2063,7 @@ update_types(#b_set{op=Op,dst=Dst,anno=Anno,args=Args}, Ts, Ds) -> Ts#{ Dst => T }. type({bif,Bif}, Args, _Anno, Ts, _Ds) -> - ArgTypes = normalized_types(Args, Ts), + ArgTypes = concrete_types(Args, Ts), case beam_call_types:types(erlang, Bif, ArgTypes) of {any, _, _} -> case {Bif, Args} of @@ -1917,11 +2076,25 @@ type({bif,Bif}, Args, _Anno, Ts, _Ds) -> {RetType, _, _} -> RetType end; -type(bs_create_bin, _Args, _Anno, _Ts, _Ds) -> - #t_bitstring{}; -type(bs_extract, [Ctx], _Anno, _Ts, Ds) -> - #b_set{op=bs_match,args=Args} = map_get(Ctx, Ds), - bs_match_type(Args); +type(bs_create_bin, Args, _Anno, Ts, _Ds) -> + SizeUnit = bs_size_unit(Args, Ts), + Appendable = case Args of + [#b_literal{val=private_append}|_] -> + true; + [#b_literal{val=append},_,Var|_] -> + case argument_type(Var, Ts) of + #t_bitstring{appendable=A} -> A; + #t_union{other=#t_bitstring{appendable=A}} -> A; + _ -> false + end; + _ -> + false + end, + #t_bitstring{size_unit=SizeUnit,appendable=Appendable}; +type(bs_extract, [Ctx], _Anno, Ts, Ds) -> + #b_set{op=bs_match, + args=[#b_literal{val=Type}, _OrigCtx | Args]} = map_get(Ctx, Ds), + bs_match_type(Type, Args, Ts); type(bs_start_match, [_, Src], _Anno, Ts, _Ds) -> case beam_types:meet(#t_bs_matchable{}, concrete_type(Src, Ts)) of none -> @@ -1940,23 +2113,28 @@ type(bs_match, [#b_literal{val=binary}, Ctx, _Flags, OpType = #t_bs_context{tail_unit=OpUnit}, beam_types:meet(CtxType, OpType); -type(bs_match, Args, _Anno, Ts, _Ds) -> - [_, Ctx | _] = Args, - - %% Matches advance the current position without testing the tail unit. We - %% try to retain unit information by taking the GCD of our current unit and - %% the increments we know the match will advance by. - #t_bs_context{tail_unit=CtxUnit} = concrete_type(Ctx, Ts), - OpUnit = bs_match_stride(Args, Ts), +type(bs_match, Args0, _Anno, Ts, _Ds) -> + [#b_literal{val=Type}, Ctx | Args] = Args0, %Assertion. + case bs_match_type(Type, Args, Ts) of + none -> + none; + _ -> + %% Matches advance the current position without testing the tail + %% unit. We try to retain unit information by taking the GCD of our + %% current unit and the increments we know the match will advance + %% by. + #t_bs_context{tail_unit=CtxUnit} = concrete_type(Ctx, Ts), + OpUnit = bs_match_stride(Type, Args, Ts), - #t_bs_context{tail_unit=gcd(OpUnit, CtxUnit)}; + #t_bs_context{tail_unit=gcd(OpUnit, CtxUnit)} + end; type(bs_get_tail, [Ctx], _Anno, Ts, _Ds) -> #t_bs_context{tail_unit=Unit} = concrete_type(Ctx, Ts), #t_bitstring{size_unit=Unit}; type(call, [#b_remote{mod=#b_literal{val=Mod}, name=#b_literal{val=Name}}|Args], _Anno, Ts, _Ds) when is_atom(Mod), is_atom(Name) -> - ArgTypes = normalized_types(Args, Ts), + ArgTypes = concrete_types(Args, Ts), {RetType, _, _} = beam_call_types:types(Mod, Name, ArgTypes), RetType; type(call, [#b_remote{mod=Mod,name=Name} | _Args], _Anno, Ts, _Ds) -> @@ -2012,8 +2190,10 @@ type(get_tl, [Src], _Anno, Ts, _Ds) -> SrcType = #t_cons{} = normalized_type(Src, Ts), %Assertion. {RetType, _, _} = beam_call_types:types(erlang, tl, [SrcType]), RetType; -type(get_map_element, [_, _]=Args0, _Anno, Ts, _Ds) -> - [#t_map{}=Map, Key] = normalized_types(Args0, Ts), %Assertion. +type(get_map_element, [Map0, Key0], _Anno, Ts, _Ds) -> + Map = concrete_type(Map0, Ts), + Key = concrete_type(Key0, Ts), + %% Note the reversed argument order! {RetType, _, _} = beam_call_types:types(erlang, map_get, [Key, Map]), RetType; type(get_tuple_element, [Tuple, Offset], _Anno, _Ts, _Ds) -> @@ -2026,9 +2206,12 @@ type(get_tuple_element, [Tuple, Offset], _Anno, _Ts, _Ds) -> true = Index =< Size, %Assertion. beam_types:get_tuple_element(Index, Es) end; -type(has_map_field, [_, _]=Args0, _Anno, Ts, _Ds) -> - [#t_map{}=Map, Key] = normalized_types(Args0, Ts), %Assertion. +type(has_map_field, [Map0, Key0], _Anno, Ts, _Ds) -> + Map = concrete_type(Map0, Ts), + Key = concrete_type(Key0, Ts), + %% Note the reversed argument order! {RetType, _, _} = beam_call_types:types(erlang, is_map_key, [Key, Map]), + true = none =/= RetType, %Assertion. RetType; type(is_nonempty_list, [_], _Anno, _Ts, _Ds) -> beam_types:make_boolean(); @@ -2071,12 +2254,33 @@ type(raw_raise, [Class, _, _], _Anno, Ts, _Ds) -> end; type(resume, [_, _], _Anno, _Ts, _Ds) -> none; +type(update_record, [_Hint, _Size, Src | Updates], _Anno, Ts, _Ds) -> + update_tuple_type(Updates, Src, Ts); +type(update_tuple, [Src | Updates], _Anno, Ts, _Ds) -> + update_tuple_type(Updates, Src, Ts); type(wait_timeout, [#b_literal{val=infinity}], _Anno, _Ts, _Ds) -> %% Waits forever, never reaching the 'after' block. beam_types:make_atom(false); +type(bs_init_writable, [_Size], _, _, _) -> + beam_types:make_type_from_value(<<>>); type(_, _, _, _, _) -> any. +update_tuple_type([_|_]=Updates0, Src, Ts) -> + Updates = update_tuple_type_1(Updates0, Ts), + beam_types:update_tuple(concrete_type(Src, Ts), Updates). + +update_tuple_type_1([#b_literal{val=Index}, Value | Updates], Ts) -> + [{Index, concrete_type(Value, Ts)} | update_tuple_type_1(Updates, Ts)]; +update_tuple_type_1([], _Ts) -> + []. + +update_tuple_highest_index([#b_literal{val=Index}, _Value | Updates], Acc) -> + update_tuple_highest_index(Updates, max(Index, Acc)); +update_tuple_highest_index([], Acc) -> + true = Acc >= 1, %Assertion. + Acc. + join_tuple_elements(Tuple) -> join_tuple_elements(tuple_size(Tuple), Tuple, none). @@ -2088,29 +2292,74 @@ join_tuple_elements(I, Tuple, Type0) -> join_tuple_elements(I - 1, Tuple, Type). put_map_type(Map, Ss, Ts) -> - pmt_1(Ss, Ts, normalized_type(Map, Ts)). + pmt_1(Ss, Ts, concrete_type(Map, Ts)). pmt_1([Key0, Value0 | Ss], Ts, Acc0) -> - Key = normalized_type(Key0, Ts), - Value = normalized_type(Value0, Ts), + Key = concrete_type(Key0, Ts), + Value = concrete_type(Value0, Ts), {Acc, _, _} = beam_call_types:types(maps, put, [Key, Value, Acc0]), pmt_1(Ss, Ts, Acc); pmt_1([], _Ts, Acc) -> Acc. +bs_size_unit(Args, Ts) -> + bs_size_unit(Args, Ts, 0, 256). + +bs_size_unit([#b_literal{val=Type},#b_literal{val=[U1|_]},Value,SizeTerm|Args], + Ts, FixedSize, Unit) -> + case {Type,Value,SizeTerm} of + {_,#b_literal{val=Bs},#b_literal{val=all}} when is_bitstring(Bs) -> + %% Add element of known size. + Size = bit_size(Bs) + FixedSize, + bs_size_unit(Args, Ts, Size, Unit); + {_,_,#b_literal{val=all}} -> + case concrete_type(Value, Ts) of + #t_bitstring{size_unit=U2} -> + %% Adding a bitstring. + %% TODO: Can U1 be anything but 1? + U = max(U1, U2), + bs_size_unit(Args, Ts, FixedSize, gcd(U, Unit)); + _ -> + %% Adding something which isn't a bitstring + bs_size_unit(Args, Ts, FixedSize, gcd(U1, Unit)) + end; + {utf8,_,_} -> + %% Add at least 8 bits, maybe more. + bs_size_unit(Args, Ts, 8 + FixedSize, gcd(8, Unit)); + {utf16,_,_} -> + %% Add at least 16 bits, maybe more. + bs_size_unit(Args, Ts, 16 + FixedSize, gcd(16, Unit)); + {utf32,_,_} -> + %% Compared to utf8 and utf16 this is a fixed size. + bs_size_unit(Args, Ts, 32 + FixedSize, Unit); + {_,_,_} -> + case concrete_type(SizeTerm, Ts) of + #t_integer{elements={Size1, Size1}} + when is_integer(Size1), is_integer(U1), Size1 >= 0 -> + EffectiveSize = Size1 * U1, + %% Adding a fixed size element + bs_size_unit(Args, Ts, EffectiveSize + FixedSize, Unit); + _ when is_integer(U1) -> + %% Add element with known unit. + bs_size_unit(Args, Ts, FixedSize, gcd(U1, Unit)); + _ -> + %% Add element without known size or unit. + bs_size_unit(Args, Ts, FixedSize, gcd(1, Unit)) + end + end; +bs_size_unit([], _Ts, FixedSize, Unit) -> + gcd(FixedSize, Unit). + %% We seldom know how far a match operation may advance, but we can often tell %% which increment it will advance by. -bs_match_stride([#b_literal{val=Type} | Args], Ts) -> - bs_match_stride(Type, Args, Ts). - -bs_match_stride(_, [_,_,Size,#b_literal{val=Unit}], Ts) -> +bs_match_stride(_, [_,Size,#b_literal{val=Unit}], Ts) -> case concrete_type(Size, Ts) of #t_integer{elements={Sz, Sz}} when is_integer(Sz) -> Sz * Unit; _ -> Unit end; -bs_match_stride(string, [_,#b_literal{val=String}], _) -> +bs_match_stride(string, [#b_literal{val=String}], _) -> bit_size(String); bs_match_stride(utf8, _, _) -> 8; @@ -2123,42 +2372,50 @@ bs_match_stride(_, _, _) -> -define(UNICODE_MAX, (16#10FFFF)). -bs_match_type([#b_literal{val=Type}|Args]) -> - bs_match_type(Type, Args). - -bs_match_type(binary, Args) -> - [_,_,_,#b_literal{val=U}] = Args, +bs_match_type(binary, Args, _Ts) -> + [_,_,#b_literal{val=U}] = Args, #t_bitstring{size_unit=U}; -bs_match_type(float, _) -> +bs_match_type(float, _Args, _Ts) -> #t_float{}; -bs_match_type(integer, Args) -> - case Args of - [_, - #b_literal{val=Flags}, - #b_literal{val=Size}, - #b_literal{val=Unit}] when Size * Unit < 64 -> - NumBits = Size * Unit, - Max = (1 bsl NumBits) - 1, - case member(unsigned, Flags) of - true -> - beam_types:make_integer(0, Max); - false -> - Min = -(Max + 1), - beam_types:make_integer(Min, Max) +bs_match_type(integer, Args, Ts) -> + [#b_literal{val=Flags},Size,#b_literal{val=Unit}] = Args, + case beam_types:meet(concrete_type(Size, Ts), #t_integer{}) of + #t_integer{elements=Bounds} -> + case beam_bounds:bounds('*', Bounds, {Unit, Unit}) of + {_, MaxBits} when is_integer(MaxBits), + MaxBits >= 1, + MaxBits =< 64 -> + case member(unsigned, Flags) of + true -> + Max = (1 bsl MaxBits) - 1, + beam_types:make_integer(0, Max); + false -> + Max = (1 bsl (MaxBits - 1)) - 1, + Min = -(Max + 1), + beam_types:make_integer(Min, Max) + end; + {_, 0} -> + beam_types:make_integer(0); + _ -> + case member(unsigned, Flags) of + true -> #t_integer{elements={0,'+inf'}}; + false -> #t_integer{} + end end; - [_|_] -> - #t_integer{} - end; -bs_match_type(skip, _) -> - any; -bs_match_type(string, _) -> - any; -bs_match_type(utf8, _) -> + none -> + none + end; + +bs_match_type(utf8, _Args, _Ts) -> beam_types:make_integer(0, ?UNICODE_MAX); -bs_match_type(utf16, _) -> +bs_match_type(utf16, _Args, _Ts) -> beam_types:make_integer(0, ?UNICODE_MAX); -bs_match_type(utf32, _) -> - beam_types:make_integer(0, ?UNICODE_MAX). +bs_match_type(utf32, _Args, _Ts) -> + beam_types:make_integer(0, ?UNICODE_MAX); +bs_match_type(string, _Args, _Ts) -> + %% Cannot actually be extracted, but we'll return 'any' to signal that the + %% associated `bs_match` may succeed. + any. normalized_types(Values, Ts) -> [normalized_type(Val, Ts) || Val <- Values]. @@ -2212,12 +2469,7 @@ concrete_type(#b_var{}=Var, Ts) -> %% subtraction for L will be added to FailTypes. infer_types_br(#b_var{}=V, Ts, IsTempVar, Ds) -> - #{V:=#b_set{op=Op,args=Args}} = Ds, - - {PosTypes, NegTypes} = infer_type(Op, Args, Ts, Ds), - - SuccTs0 = meet_types(PosTypes, Ts), - FailTs0 = subtract_types(NegTypes, Ts), + {SuccTs0, FailTs0} = infer_types_br_1(V, Ts, Ds), case IsTempVar of true -> @@ -2234,6 +2486,146 @@ infer_types_br(#b_var{}=V, Ts, IsTempVar, Ds) -> {SuccTs, FailTs} end. +infer_types_br_1(V, Ts, Ds) -> + #{V:=#b_set{op=Op0,args=Args}} = Ds, + + case inv_relop(Op0) of + none -> + %% Not a relational operator. + {PosTypes, NegTypes} = infer_type(Op0, Args, Ts, Ds), + SuccTs = meet_types(PosTypes, Ts), + FailTs = subtract_types(NegTypes, Ts), + {SuccTs, FailTs}; + InvOp -> + %% This is an relational operator. + {bif,Op} = Op0, + + %% Infer the types for both sides of operator succceding. + Types = concrete_types(Args, Ts), + TrueTypes0 = infer_relop(Op, Args, Types, Ds), + TrueTypes = meet_types(TrueTypes0, Ts), + + %% Infer the types for both sides of operator failing. + FalseTypes0 = infer_relop(InvOp, Args, Types, Ds), + FalseTypes = meet_types(FalseTypes0, Ts), + + {TrueTypes, FalseTypes} + end. + +infer_relop('=:=', [LHS,RHS], [LType,RType], Ds) -> + EqTypes = infer_eq_type(map_get(LHS, Ds), RType), + + %% As an example, assume that L1 is known to be 'list', and L2 is + %% known to be 'cons'. Then if 'L1 =:= L2' evaluates to 'true', it + %% can be inferred that L1 is 'cons' (the meet of 'cons' and + %% 'list'). + Type = beam_types:meet(LType, RType), + [{LHS,Type},{RHS,Type}] ++ EqTypes; +infer_relop(Op, Args, Types, _Ds) -> + infer_relop(Op, Args, Types). + +infer_relop('=/=', [LHS,RHS], [LType,RType]) -> + %% We must be careful with types inferred from '=/='. + %% + %% For example, if we have L =/= [a], we must not subtract 'cons' + %% from the type of L. In general, subtraction is only safe if + %% the type being subtracted is singled-valued, for example if it + %% is [] or the the atom 'true'. + %% + %% Note that we subtract the left-hand type from the right-hand + %% value and vice versa. We must not subtract the meet of the two + %% as it may be too specific. See beam_type_SUITE:type_subtraction/1 + %% for details. + [{V,beam_types:subtract(ThisType, OtherType)} || + {V, ThisType, OtherType} <- [{RHS, RType, LType}, {LHS, LType, RType}], + beam_types:is_singleton_type(OtherType)]; +infer_relop(Op, [Arg1,Arg2], Types0) -> + case infer_relop(Op, Types0) of + any -> + %% Both operands lacked ranges. + []; + {NewType1,NewType2} -> + [{Arg1,NewType1},{Arg2,NewType2}] + end. + +infer_relop(Op, [#t_integer{elements=R1}, + #t_integer{elements=R2}]) -> + case beam_bounds:infer_relop_types(Op, R1, R2) of + {NewR1,NewR2} -> + {#t_integer{elements=NewR1}, + #t_integer{elements=NewR2}}; + none -> + {none, none}; + any -> + any + end; +infer_relop(Op0, [Type1,Type2]) -> + Op = case Op0 of + '<' -> '=<'; + '>' -> '>='; + _ -> Op0 + end, + case {infer_get_range(Type1),infer_get_range(Type2)} of + {unknown,unknown} -> + any; + {unknown,_}=R -> + infer_relop_any(Op, R); + {_,unknown}=R -> + infer_relop_any(Op, R); + {R1,R2} -> + %% Both operands are numeric types. + case beam_bounds:infer_relop_types(Op, R1, R2) of + {NewR1,NewR2} -> + {#t_number{elements=NewR1}, + #t_number{elements=NewR2}}; + none -> + {none, none}; + any -> + any + end + end. + +infer_relop_any('=<', {unknown,any}) -> + %% Since numbers are smaller than any other terms, the LHS must be + %% a number if operator '=<' evaluates to true. + {#t_number{}, any}; +infer_relop_any('=<', {unknown,{_,Max}}) -> + {make_number({'-inf',Max}), any}; +infer_relop_any('>=', {any,unknown}) -> + {any, #t_number{}}; +infer_relop_any('>=', {{_,Max},unknown}) -> + {any, make_number({'-inf',Max})}; +infer_relop_any('>=', {unknown,{Min,_}}) when is_integer(Min) -> + %% LHS can be any term, including number, but we can figure out + %% the minimal possible number. + N = #t_number{elements={'-inf',Min}}, + {beam_types:subtract(any, N), any}; +infer_relop_any('=<', {{Min,_},unknown}) when is_integer(Min) -> + N = #t_number{elements={'-inf',Min}}, + {any, beam_types:subtract(any, N)}; +infer_relop_any(_Op, _R) -> + any. + +make_number({'-inf','+inf'}) -> + #t_number{}; +make_number({_,_}=R) -> + #t_number{elements=R}. + +inv_relop({bif,Op}) -> inv_relop_1(Op); +inv_relop(_) -> none. + +inv_relop_1('<') -> '>='; +inv_relop_1('=<') -> '>'; +inv_relop_1('>=') -> '<'; +inv_relop_1('>') -> '=<'; +inv_relop_1('=:=') -> '=/='; +inv_relop_1('=/=') -> '=:='; +inv_relop_1(_) -> none. + +infer_get_range(#t_integer{elements=R}) -> R; +infer_get_range(#t_number{elements=R}) -> R; +infer_get_range(_) -> unknown. + infer_br_value(_V, _Bool, none) -> none; infer_br_value(V, Bool, NewTs) -> @@ -2247,7 +2639,9 @@ infer_br_value(V, Bool, NewTs) -> end. infer_types_switch(V, Lit, Ts0, IsTempVar, Ds) -> - {PosTypes, _} = infer_type({bif,'=:='}, [V, Lit], Ts0, Ds), + Args = [V,Lit], + Types = concrete_types(Args, Ts0), + PosTypes = infer_relop('=:=', Args, Types, Ds), Ts = meet_types(PosTypes, Ts0), case IsTempVar of true -> ts_remove_var(V, Ts); @@ -2312,7 +2706,7 @@ infer_type({bif,is_map}, [#b_var{}=Arg], _Ts, _Ds) -> T = {Arg, #t_map{}}, {[T], [T]}; infer_type({bif,is_number}, [#b_var{}=Arg], _Ts, _Ds) -> - T = {Arg, number}, + T = {Arg, #t_number{}}, {[T], [T]}; infer_type({bif,is_pid}, [#b_var{}=Arg], _Ts, _Ds) -> T = {Arg, pid}, @@ -2326,55 +2720,29 @@ infer_type({bif,is_reference}, [#b_var{}=Arg], _Ts, _Ds) -> infer_type({bif,is_tuple}, [#b_var{}=Arg], _Ts, _Ds) -> T = {Arg, #t_tuple{}}, {[T], [T]}; -infer_type({bif,'=:='}, [#b_var{}=LHS,#b_var{}=RHS], Ts, _Ds) -> - %% As an example, assume that L1 is known to be 'list', and L2 is - %% known to be 'cons'. Then if 'L1 =:= L2' evaluates to 'true', it can - %% be inferred that L1 is 'cons' (the meet of 'cons' and 'list'). - LType = concrete_type(LHS, Ts), - RType = concrete_type(RHS, Ts), - Type = beam_types:meet(LType, RType), - - PosTypes = [{V,Type} || {V, OrigType} <- [{LHS, LType}, {RHS, RType}], - OrigType =/= Type], - - %% We must be careful with types inferred from '=:='. - %% - %% If we have seen L =:= [a], we know that L is 'cons' if the - %% comparison succeeds. However, if the comparison fails, L could - %% still be 'cons'. Therefore, we must not subtract 'cons' from the - %% previous type of L. +infer_type({bif,'and'}, [#b_var{}=LHS,#b_var{}=RHS], Ts, Ds) -> + %% When this BIF yields true, we know that both `LHS` and `RHS` are 'true' + %% and should infer accordingly, lest we break later optimizations that + %% rewrite this BIF to plain control flow. %% - %% However, it is safe to subtract a type inferred from '=:=' if - %% it is single-valued, e.g. if it is [] or the atom 'true'. - %% - %% Note that we subtract the left-hand type from the right-hand - %% value and vice versa. We must not subtract the meet of the two - %% as it may be too specific. See beam_type_SUITE:type_subtraction/1 - %% for details. - NegTypes = [T || {_, OtherType}=T <- [{RHS, LType}, {LHS, RType}], - beam_types:is_singleton_type(OtherType)], - - {PosTypes, NegTypes}; -infer_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ts, Ds) -> - Def = maps:get(Src, Ds), - LitType = concrete_type(Lit, Ts), - PosTypes = [{Src, LitType} | infer_eq_type(Def, LitType)], - - %% Subtraction is only safe if LitType is single-valued. - NegTypes = case beam_types:is_singleton_type(LitType) of - true -> PosTypes; - false -> [] - end, + %% Note that we can't do anything for the 'false' case as either (or both) + %% of the arguments could be false. + #{ LHS := #b_set{op=LHSOp,args=LHSArgs}, + RHS := #b_set{op=RHSOp,args=RHSArgs} } = Ds, - {PosTypes, NegTypes}; + LHSTypes = infer_and_type(LHSOp, LHSArgs, Ts, Ds), + RHSTypes = infer_and_type(RHSOp, RHSArgs, Ts, Ds), + + True = beam_types:make_atom(true), + {[{LHS, True}, {RHS, True}] ++ LHSTypes ++ RHSTypes, []}; infer_type(_Op, _Args, _Ts, _Ds) -> {[], []}. infer_success_type({bif,Op}, Args, Ts, _Ds) -> - ArgTypes = normalized_types(Args, Ts), + ArgTypes = concrete_types(Args, Ts), {_, PosTypes0, CanSubtract} = beam_call_types:types(erlang, Op, ArgTypes), - PosTypes = [T || {#b_var{},_}=T <- zip(Args, PosTypes0)], + PosTypes = zip(Args, PosTypes0), case CanSubtract of true -> {PosTypes, PosTypes}; @@ -2415,6 +2783,16 @@ infer_eq_type(#b_set{op=get_tuple_element, infer_eq_type(_, _) -> []. +infer_and_type(Op, Args, Ts, Ds) -> + case inv_relop(Op) of + none -> + {LHSTypes0, _} = infer_type(Op, Args, Ts, Ds), + LHSTypes0; + _InvOp -> + {bif,RelOp} = Op, + infer_relop(RelOp, Args, concrete_types(Args, Ts), Ds) + end. + join_types(Ts, Ts) -> Ts; join_types(LHS, RHS) -> @@ -2448,7 +2826,13 @@ join_types_1([V | Vs], Bigger, Smaller) -> join_types_1([], _Bigger, Smaller) -> Smaller. -meet_types([{V,T0}|Vs], Ts) -> +meet_types([{#b_literal{}=Lit, T0} | Vs], Ts) -> + T1 = concrete_type(Lit, Ts), + case beam_types:meet(T0, T1) of + none -> none; + _ -> meet_types(Vs, Ts) + end; +meet_types([{#b_var{}=V, T0} | Vs], Ts) -> T1 = concrete_type(V, Ts), case beam_types:meet(T0, T1) of none -> none; @@ -2458,12 +2842,18 @@ meet_types([{V,T0}|Vs], Ts) -> meet_types([], Ts) -> Ts. -subtract_types([{V,T0}|Vs], Ts) -> +subtract_types([{#b_literal{}=Lit, T0} | Vs], Ts) -> + T1 = concrete_type(Lit, Ts), + case beam_types:subtract(T0, T1) of + none -> none; + _ -> subtract_types(Vs, Ts) + end; +subtract_types([{#b_var{}=V, T0}|Vs], Ts) -> T1 = concrete_type(V, Ts), case beam_types:subtract(T1, T0) of none -> none; T1 -> subtract_types(Vs, Ts); - T -> subtract_types(Vs, Ts#{ V:= T }) + T -> subtract_types(Vs, Ts#{V := T}) end; subtract_types([], Ts) -> Ts. |