diff options
author | John Högberg <[email protected]> | 2019-08-07 08:12:58 +0200 |
---|---|---|
committer | John Högberg <[email protected]> | 2019-08-07 08:12:58 +0200 |
commit | 18ab59fe935799f6495ed8273a9c577c655d30ab (patch) | |
tree | b7d02a7e18786f7ef645f6d72a826caa1e8e034e /lib | |
parent | 86b0385622e25502222ae171a14f24aba998f2f9 (diff) | |
parent | 2ea04617d18bc3c4f80fc3b28af47379a3ec26fc (diff) | |
download | otp-18ab59fe935799f6495ed8273a9c577c655d30ab.tar.gz otp-18ab59fe935799f6495ed8273a9c577c655d30ab.tar.bz2 otp-18ab59fe935799f6495ed8273a9c577c655d30ab.zip |
Merge branch 'john/compiler/explicit-call-exceptions'
* john/compiler/explicit-call-exceptions:
compiler: Simplify set_tuple_element optimization
compiler: Make 'succeeded' optimization more general
compiler: Simplify call type optimization
compiler: All calls may throw, so they all need success checks
erts_debug: Turn off unsafe optimizations in test case
Diffstat (limited to 'lib')
-rw-r--r-- | lib/compiler/src/beam_call_types.erl | 63 | ||||
-rw-r--r-- | lib/compiler/src/beam_kernel_to_ssa.erl | 9 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_pre_codegen.erl | 80 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_type.erl | 392 | ||||
-rw-r--r-- | lib/compiler/src/beam_validator.erl | 148 | ||||
-rw-r--r-- | lib/kernel/src/erts_debug.erl | 9 |
6 files changed, 347 insertions, 354 deletions
diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl index e76ad79365..904d82a62d 100644 --- a/lib/compiler/src/beam_call_types.erl +++ b/lib/compiler/src/beam_call_types.erl @@ -24,7 +24,68 @@ -import(lists, [duplicate/2,foldl/3]). --export([types/3]). +-export([will_succeed/3, types/3]). + +%% +%% Returns whether a call will succeed or not. +%% +%% Note that it only answers 'yes' for functions in the 'erlang' module as +%% calls to other modules may fail due to not being loaded, even if we consider +%% the module to be known. +%% + +-spec will_succeed(Mod, Func, ArgTypes) -> Result when + Mod :: atom(), + Func :: atom(), + ArgTypes :: [normal_type()], + Result :: yes | no | maybe. + +will_succeed(erlang, BoolOp, [LHS, RHS]) when BoolOp =:= 'and'; + BoolOp =:= 'or' -> + case {succeeds_if_type(LHS, beam_types:make_boolean()), + succeeds_if_type(RHS, beam_types:make_boolean())} of + {yes, yes} -> yes; + {no, _} -> no; + {_, no} -> no; + {_, _} -> maybe + end; +will_succeed(erlang, bit_size, [Arg]) -> + succeeds_if_type(Arg, #t_bitstring{}); +will_succeed(erlang, byte_size, [Arg]) -> + succeeds_if_type(Arg, #t_bitstring{}); +will_succeed(erlang, map_size, [Arg]) -> + succeeds_if_type(Arg, #t_map{}); +will_succeed(erlang, 'not', [Arg]) -> + succeeds_if_type(Arg, beam_types:make_boolean()); +will_succeed(erlang, setelement, [#t_integer{elements={Min,Max}}, + #t_tuple{exact=Exact,size=Size}, _]) -> + case Min >= 1 andalso Max =< Size of + true -> yes; + false when Exact -> no; + false -> maybe + end; +will_succeed(erlang, size, [Arg]) -> + succeeds_if_type(Arg, #t_bitstring{}); +will_succeed(erlang, tuple_size, [Arg]) -> + succeeds_if_type(Arg, #t_tuple{}); +will_succeed(Mod, Func, Args) -> + Arity = length(Args), + case erl_bifs:is_safe(Mod, Func, Arity) of + true -> + yes; + false -> + case erl_bifs:is_exit_bif(Mod, Func, Arity) of + true -> no; + false -> maybe + end + end. + +succeeds_if_type(ArgType, Required) -> + case beam_types:meet(ArgType, Required) of + ArgType -> yes; + none -> no; + _ -> maybe + end. %% %% Returns the inferred return and argument types for known functions, and diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl index 474cb1a851..2406a634e6 100644 --- a/lib/compiler/src/beam_kernel_to_ssa.erl +++ b/lib/compiler/src/beam_kernel_to_ssa.erl @@ -680,13 +680,8 @@ call_cg(Func0, As, [#k_var{name=R}|MoreRs]=Rs, Le, St0) -> set_ssa_var(Dummy, #b_literal{val=unused}, S) end, St1, MoreRs), - case FailCtx of - {no_catch, _} -> - {[Call],St2}; - {in_catch, _} -> - {TestIs,St} = make_succeeded(Ret, FailCtx, St2), - {[Call|TestIs],St} - end + {TestIs,St} = make_succeeded(Ret, FailCtx, St2), + {[Call|TestIs],St} end. enter_cg(Func0, As0, Le, St0) -> diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index 89053c7b9f..9847b87b18 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -1062,9 +1062,8 @@ use_set_tuple_element(#st{ssa=Blocks0}=St) -> Blocks = use_ste_1(RPO, Uses, Blocks0), St#st{ssa=Blocks}. -use_ste_1([L|Ls], Uses, Blocks0) -> - {Blk0,Blocks} = use_ste_across(L, Uses, Blocks0), - #b_blk{is=Is0} = Blk0, +use_ste_1([L|Ls], Uses, Blocks) -> + #b_blk{is=Is0} = Blk0 = map_get(L, Blocks), case use_ste_is(Is0, Uses) of Is0 -> use_ste_1(Ls, Uses, Blocks); @@ -1127,69 +1126,6 @@ extract_ste(#b_set{op=call,dst=Dst, end; extract_ste(#b_set{}) -> none. -%%% Optimize accross blocks within a try/catch block. - -use_ste_across(L, Uses, Blocks) -> - case map_get(L, Blocks) of - #b_blk{last=#b_br{bool=#b_var{}}}=Blk -> - try - use_ste_across_1(L, Blk, Uses, Blocks) - catch - throw:not_possible -> - {Blk,Blocks} - end; - #b_blk{}=Blk -> - {Blk,Blocks} - end. - -use_ste_across_1(L, Blk0, Uses, Blocks0) -> - #b_blk{is=IsThis,last=#b_br{bool=Bool,succ=Next}} = Blk0, - case reverse(IsThis) of - [#b_set{op=succeeded,dst=Bool,args=[Result]}=Succ0, - #b_set{op=call,args=[#b_remote{}|_],dst=Result}=Call1|Prefix] -> - case is_single_use(Bool, Uses) andalso - is_n_uses(2, Result, Uses) of - true -> ok; - false -> throw(not_possible) - end, - Call2 = use_ste_across_next(Next, Uses, Blocks0), - Is = [Call1,Call2], - case use_ste_is(Is, decrement_uses(Result, Uses)) of - [#b_set{}=Call,#b_set{op=set_tuple_element}=Ste] -> - Blocks1 = use_ste_fix_next(Ste, Next, Blocks0), - Succ = Succ0#b_set{args=[Call#b_set.dst]}, - Blk = Blk0#b_blk{is=reverse(Prefix, [Call,Succ])}, - Blocks = Blocks1#{L:=Blk}, - {Blk,Blocks}; - _ -> - throw(not_possible) - end; - _ -> - throw(not_possible) - end. - -use_ste_across_next(Next, Uses, Blocks) -> - case map_get(Next, Blocks) of - #b_blk{is=[#b_set{op=call,dst=Result,args=[#b_remote{}|_]}=Call, - #b_set{op=succeeded,dst=Bool,args=[Result]}], - last=#b_br{bool=Bool}} -> - case is_single_use(Bool, Uses) andalso - is_n_uses(2, Result, Uses) of - true -> ok; - false -> throw(not_possible) - end, - Call; - #b_blk{} -> - throw(not_possible) - end. - -use_ste_fix_next(Ste, Next, Blocks) -> - Blk0 = map_get(Next, Blocks), - #b_blk{is=[#b_set{op=call},#b_set{op=succeeded}],last=Br0} = Blk0, - Br = beam_ssa:normalize(Br0#b_br{bool=#b_literal{val=true}}), - Blk = Blk0#b_blk{is=[Ste],last=Br}, - Blocks#{Next:=Blk}. - %% Count how many times each variable is used. count_uses(Blocks) -> @@ -1199,7 +1135,7 @@ count_uses_blk([#b_blk{is=Is,last=Last}|Bs], CountMap0) -> F = fun(I, CountMap) -> foldl(fun(Var, Acc) -> case Acc of - #{Var:=3} -> Acc; + #{Var:=2} -> Acc; #{Var:=C} -> Acc#{Var:=C+1}; #{} -> Acc#{Var=>1} end @@ -1209,16 +1145,6 @@ count_uses_blk([#b_blk{is=Is,last=Last}|Bs], CountMap0) -> count_uses_blk(Bs, CountMap); count_uses_blk([], CountMap) -> CountMap. -decrement_uses(V, Uses) -> - #{V:=C} = Uses, - Uses#{V:=C-1}. - -is_n_uses(N, V, Uses) -> - case Uses of - #{V:=N} -> true; - #{} -> false - end. - is_single_use(V, Uses) -> case Uses of #{V:=1} -> true; diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 0912ecb2c2..364a87f67e 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -25,7 +25,7 @@ -include("beam_types.hrl"). -import(lists, [all/2,any/2,droplast/1,duplicate/2,foldl/3,last/1,member/2, - keyfind/3,reverse/1,reverse/2,sort/1,split/2,zip/2]). + keyfind/3,reverse/1,sort/1,split/2,zip/2]). -define(UNICODE_MAX, (16#10FFFF)). @@ -171,27 +171,17 @@ opt([], D, Acc) -> opt_1(L, #b_blk{is=Is0,last=Last0}=Blk0, Bs, Ts0, #d{ds=Ds0,sub=Sub0,func_db=Fdb0}=D0, Acc) -> - case opt_is(Is0, Ts0, Ds0, Fdb0, D0, Sub0, []) of - {Is,Ts,Ds,Fdb,Sub} -> - D1 = D0#d{ds=Ds,sub=Sub,func_db=Fdb}, - Last1 = simplify_terminator(Last0, Sub, Ts, Ds), - Last2 = opt_terminator(Last1, Ts, Ds), - {Last,D} = update_successors(Last2, Ts, D1), - Blk = Blk0#b_blk{is=Is,last=Last}, - opt(Bs, D, [{L,Blk}|Acc]); - {no_return,Ret,Is,Ds,Fdb,Sub} -> - %% This call will never reach the successor block. - %% Rewrite the terminator to a 'ret', and remove - %% all type information for this label. That can - %% potentially narrow the type of the phi node - %% in the former successor. - Ls = maps:remove(L, D0#d.ls), - RetType = beam_types:join([none|D0#d.ret_type]), - D = D0#d{ds=Ds,ls=Ls,sub=Sub, - func_db=Fdb,ret_type=[RetType]}, - Blk = Blk0#b_blk{is=Is,last=Ret}, - opt(Bs, D, [{L,Blk}|Acc]) - end. + {Is,Ts,Ds,Fdb,Sub} = opt_is(Is0, Ts0, Ds0, Fdb0, D0, Sub0, []), + + D1 = D0#d{ds=Ds,sub=Sub,func_db=Fdb}, + + Last1 = simplify_terminator(Last0, Sub, Ts, Ds), + Last2 = opt_terminator(Last1, Ts, Ds), + + {Last, D} = update_successors(Last2, Ts, D1), + + Blk = Blk0#b_blk{is=Is,last=Last}, + opt(Bs, D, [{L,Blk}|Acc]). simplify_terminator(#b_br{bool=Bool}=Br, Sub, Ts, _Ds) -> Br#b_br{bool=simplify_arg(Bool, Sub, Ts)}; @@ -224,166 +214,74 @@ opt_is([#b_set{op=phi,dst=Dst,args=Args0}=I0|Is], Ds = Ds0#{Dst=>I}, opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I|Acc]) end; -opt_is([#b_set{op=call,args=Args0,dst=Dst}=I0|Is], - Ts0, Ds0, Fdb0, D, Sub0, Acc) -> - Args = simplify_args(Args0, Sub0, Ts0), +opt_is([#b_set{op=call,args=Args0}=I0|Is], + Ts, Ds, Fdb0, D, Sub, Acc) -> + Args = simplify_args(Args0, Sub, Ts), I1 = beam_ssa:normalize(I0#b_set{args=Args}), - {Ts1,Ds,Fdb,I2} = opt_call(I1, D, Ts0, Ds0, Fdb0), - case {map_get(Dst, Ts1),Is} of - {Type,[#b_set{op=succeeded}]} when Type =/= none -> - %% This call instruction is inside a try/catch - %% block. Don't attempt to simplify it. - opt_is(Is, Ts1, Ds, Fdb, D, Sub0, [I2|Acc]); - {none,[#b_set{op=succeeded}]} -> - %% This call instruction is inside a try/catch - %% block, but we know it will never return and - %% later optimizations may try to exploit that. - %% - %% For example, if we have an expression that - %% either returns this call or a tuple, we know - %% that the expression always returns a tuple - %% and can turn a later element/3 into - %% get_tuple_element. - %% - %% This is sound but difficult to validate in a - %% meaningful way as try/catch currently forces - %% us to maintain the illusion that the success - %% block is reachable even when its not, so we - %% disable the optimization to keep things - %% simple. - Ts = Ts1#{ Dst := any }, - opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I2|Acc]); - {none,_} -> - %% This call never returns. The rest of the - %% instructions will not be executed. - Ret = #b_ret{arg=Dst}, - {no_return,Ret,reverse(Acc, [I2]),Ds,Fdb,Sub0}; - {_,_} -> - case simplify_call(I2) of - #b_set{}=I -> - opt_is(Is, Ts1, Ds, Fdb, D, Sub0, [I|Acc]); - #b_literal{}=Lit -> - Sub = Sub0#{Dst=>Lit}, - Ts = maps:remove(Dst, Ts1), - opt_is(Is, Ts, Ds0, Fdb, D, Sub, Acc); - #b_var{}=Var -> - Ts = maps:remove(Dst, Ts1), - Sub = Sub0#{Dst=>Var}, - opt_is(Is, Ts, Ds0, Fdb, D, Sub, Acc) - end - end; + {I, Fdb} = opt_call(I1, Ts, Fdb0, D), + opt_simplify(I, Is, Ts, Ds, Fdb, D, Sub, Acc); opt_is([#b_set{op=make_fun,args=Args0}=I0|Is], Ts0, Ds0, Fdb0, D, Sub0, Acc) -> Args = simplify_args(Args0, Sub0, Ts0), I1 = beam_ssa:normalize(I0#b_set{args=Args}), {Ts,Ds,Fdb,I} = opt_make_fun(I1, D, Ts0, Ds0, Fdb0), opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I|Acc]); -opt_is([#b_set{op=succeeded,args=[Arg],dst=Dst}=I], +opt_is([#b_set{op=succeeded,args=[Arg],dst=Dst,anno=Anno}=I], Ts0, Ds0, Fdb, D, Sub0, Acc) -> - case Ds0 of - #{ Arg := #b_set{op=call} } -> - %% The success check of a call is part of exception handling and - %% must not be optimized away. We still have to update its type - %% though. - Ts = update_types(I, Ts0, Ds0), - Ds = Ds0#{Dst=>I}, - - opt_is([], Ts, Ds, Fdb, D, Sub0, [I|Acc]); - #{} -> - Args = simplify_args([Arg], Sub0, Ts0), - Type = type(succeeded, Args, Ts0, Ds0), - case beam_types:get_singleton_value(Type) of - {ok, Lit} -> - Sub = Sub0#{Dst=>#b_literal{val=Lit}}, - opt_is([], Ts0, Ds0, Fdb, D, Sub, Acc); - error -> - Ts = Ts0#{Dst=>Type}, - Ds = Ds0#{Dst=>I}, - opt_is([], Ts, Ds, Fdb, D, Sub0, [I|Acc]) - end + Type = case Ds0 of + #{ Arg := #b_set{op=call} } -> + %% Calls can always throw exceptions and their return types + %% are what they return on success, so we must avoid + %% simplifying arguments in case `Arg` would become a + %% literal, which would trick 'succeeded' into thinking it + %% can't fail. + type(succeeded, [Arg], Anno, Ts0, Ds0); + #{} -> + Args = simplify_args([Arg], Sub0, Ts0), + type(succeeded, Args, Anno, Ts0, Ds0) + end, + case beam_types:get_singleton_value(Type) of + {ok, Lit} -> + Sub = Sub0#{ Dst => #b_literal{val=Lit} }, + opt_is([], Ts0, Ds0, Fdb, D, Sub, Acc); + error -> + Ts = Ts0#{ Dst => Type }, + Ds = Ds0#{ Dst => I }, + opt_is([], Ts, Ds, Fdb, D, Sub0, [I | Acc]) end; -opt_is([#b_set{args=Args0,dst=Dst}=I0|Is], - Ts0, Ds0, Fdb, D, Sub0, Acc) -> - Args = simplify_args(Args0, Sub0, Ts0), - I1 = beam_ssa:normalize(I0#b_set{args=Args}), - case simplify(I1, Ts0) of +opt_is([#b_set{args=Args0}=I0|Is], + Ts, Ds, Fdb, D, Sub, Acc) -> + Args = simplify_args(Args0, Sub, Ts), + I = beam_ssa:normalize(I0#b_set{args=Args}), + opt_simplify(I, Is, Ts, Ds, Fdb, D, Sub, Acc); +opt_is([], Ts, Ds, Fdb, _D, Sub, Acc) -> + {reverse(Acc), Ts, Ds, Fdb, Sub}. + +opt_simplify(#b_set{dst=Dst}=I0, Is, Ts0, Ds0, Fdb, D, Sub0, Acc) -> + case simplify(I0, Ts0) of #b_set{}=I2 -> I = beam_ssa:normalize(I2), Ts = update_types(I, Ts0, Ds0), - Ds = Ds0#{Dst=>I}, + Ds = Ds0#{ Dst => I }, opt_is(Is, Ts, Ds, Fdb, D, Sub0, [I|Acc]); #b_literal{}=Lit -> - Sub = Sub0#{Dst=>Lit}, + Sub = Sub0#{ Dst => Lit }, opt_is(Is, Ts0, Ds0, Fdb, D, Sub, Acc); #b_var{}=Var -> case Is of [#b_set{op=succeeded,dst=SuccDst,args=[Dst]}] -> - %% We must remove this 'succeeded' instruction. - Sub = Sub0#{Dst=>Var,SuccDst=>#b_literal{val=true}}, + %% We must remove this 'succeeded' instruction since the + %% variable it checks is gone. + Sub = Sub0#{ Dst => Var, SuccDst => #b_literal{val=true} }, opt_is([], Ts0, Ds0, Fdb, D, Sub, Acc); _ -> - Sub = Sub0#{Dst=>Var}, + Sub = Sub0#{ Dst => Var}, opt_is(Is, Ts0, Ds0, Fdb, D, Sub, Acc) end - end; -opt_is([], Ts, Ds, Fdb, _D, Sub, Acc) -> - {reverse(Acc), Ts, Ds, Fdb, Sub}. - -simplify_call(#b_set{op=call,args=[#b_remote{}=Rem|Args]}=I) -> - case Rem of - #b_remote{mod=#b_literal{val=Mod}, - name=#b_literal{val=Name}} -> - case erl_bifs:is_pure(Mod, Name, length(Args)) of - true -> - simplify_remote_call(Mod, Name, Args, I); - false -> - I - end; - #b_remote{} -> - I - end; -simplify_call(I) -> I. - -%% Simplify a remote call to a pure BIF. -simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _I) -> - Tl; -simplify_remote_call(erlang, setelement, - [#b_literal{val=Pos}, - #b_literal{val=Tuple}, - #b_var{}=Value], 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, Args0, I) -> - case make_literal_list(Args0) of - none -> - I; - Args -> - %% The arguments are literals. Try to evaluate the BIF. - try apply(Mod, Name, Args) of - Val -> - case cerl:is_literal_term(Val) of - true -> - #b_literal{val=Val}; - false -> - %% The value can't be expressed as a literal - %% (e.g. a pid). - I - end - catch - _:_ -> - %% Failed. Don't bother trying to optimize - %% the call. - I - end end. -opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, D, Ts0, Ds0, Fdb0) -> - {Ts, Ds, I} = opt_local_call(I0, Ts0, Ds0, Fdb0), +opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, Ts, Fdb0, D) -> + I = opt_local_call_return(I0, Callee, Fdb0), case Fdb0 of #{ Callee := #func_info{exported=false,arg_types=ArgTypes0}=Info } -> %% Match contexts are treated as bitstrings when optimizing @@ -400,36 +298,22 @@ opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, D, Ts0, Ds0, Fdb0) -> ArgTypes = update_arg_types(Types, ArgTypes0, CallId), Fdb = Fdb0#{ Callee => Info#func_info{arg_types=ArgTypes} }, - {Ts, Ds, Fdb, I}; + {I, Fdb}; #{} -> %% We can't narrow the argument types of exported functions as they %% can receive anything as part of an external call. - {Ts, Ds, Fdb0, I} + {I, Fdb0} end; -opt_call(#b_set{dst=Dst,args=[#b_var{}=Fun|Args]}=I, _D, Ts0, Ds0, Fdb) -> - Type = #t_fun{arity=length(Args)}, - Ts = Ts0#{ Fun => Type, Dst => any }, - Ds = Ds0#{ Dst => I }, - {Ts, Ds, Fdb, I}; -opt_call(#b_set{dst=Dst}=I, _D, Ts0, Ds0, Fdb) -> - %% #b_remote{} and literal funs - Ts = update_types(I, Ts0, Ds0), - Ds = Ds0#{ Dst => I }, - {Ts, Ds, Fdb, I}. +opt_call(I, _Ts, Fdb, _D) -> + {I, Fdb}. -opt_local_call(#b_set{dst=Dst,args=[Id|_]}=I0, Ts0, Ds0, Fdb) -> - Type = case Fdb of - #{ Id := #func_info{ret_type=[T]} } -> T; - #{} -> any - end, - I = case Type of - any -> I0; - none -> I0; - _ -> beam_ssa:add_anno(result_type, Type, I0) - end, - Ts = Ts0#{ Dst => Type }, - Ds = Ds0#{ Dst => I }, - {Ts, Ds, I}. +opt_local_call_return(I, Callee, Fdb) -> + case Fdb of + #{ Callee := #func_info{ret_type=[Type]} } when Type =/= any -> + beam_ssa:add_anno(result_type, Type, I); + #{} -> + I + end. %% While we have no way to know which arguments a fun will be called with, we %% do know its free variables and can update their types as if this were a @@ -631,8 +515,59 @@ simplify(#b_set{op=wait_timeout,args=[#b_literal{val=0}]}, _Ts) -> #b_literal{val=true}; simplify(#b_set{op=wait_timeout,args=[#b_literal{val=infinity}]}=I, _Ts) -> I#b_set{op=wait,args=[]}; +simplify(#b_set{op=call,args=[#b_remote{}=Rem|Args]}=I, _Ts) -> + case Rem of + #b_remote{mod=#b_literal{val=Mod}, + name=#b_literal{val=Name}} -> + case erl_bifs:is_pure(Mod, Name, length(Args)) of + true -> + simplify_remote_call(Mod, Name, Args, I); + false -> + I + end; + #b_remote{} -> + I + end; simplify(I, _Ts) -> I. +%% Simplify a remote call to a pure BIF. +simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _I) -> + Tl; +simplify_remote_call(erlang, setelement, + [#b_literal{val=Pos}, + #b_literal{val=Tuple}, + #b_var{}=Value], 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, Args0, I) -> + case make_literal_list(Args0) of + none -> + I; + Args -> + %% The arguments are literals. Try to evaluate the BIF. + try apply(Mod, Name, Args) of + Val -> + case cerl:is_literal_term(Val) of + true -> + #b_literal{val=Val}; + false -> + %% The value can't be expressed as a literal + %% (e.g. a pid). + I + end + catch + _:_ -> + %% Failed. Don't bother trying to optimize + %% the call. + 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) -> @@ -908,82 +843,78 @@ update_successor(S, Ts0, #d{ls=Ls}=D) -> D#d{ls=Ls#{S=>Ts0}} end. -update_types(#b_set{op=Op,dst=Dst,args=Args}, Ts, Ds) -> - T = type(Op, Args, Ts, Ds), +update_types(#b_set{op=Op,dst=Dst,anno=Anno,args=Args}, Ts, Ds) -> + T = type(Op, Args, Anno, Ts, Ds), Ts#{Dst=>T}. -type(phi, Args, Ts, _Ds) -> +type(phi, Args, _Anno, Ts, _Ds) -> Types = [raw_type(A, Ts) || {A,_} <- Args], beam_types:join(Types); -type({bif,Bif}, Args, Ts, _Ds) -> +type({bif,Bif}, Args, _Anno, Ts, _Ds) -> ArgTypes = normalized_types(Args, Ts), {RetType, _, _} = beam_call_types:types(erlang, Bif, ArgTypes), RetType; -type(bs_init, _Args, _Ts, _Ds) -> +type(bs_init, _Args, _Anno, _Ts, _Ds) -> #t_bitstring{}; -type(bs_extract, [Ctx], _Ts, Ds) -> +type(bs_extract, [Ctx], _Anno, _Ts, Ds) -> #b_set{op=bs_match,args=Args} = map_get(Ctx, Ds), bs_match_type(Args); -type(bs_match, _Args, _Ts, _Ds) -> +type(bs_match, _Args, _Anno, _Ts, _Ds) -> #t_bs_context{}; -type(bs_get_tail, _Args, _Ts, _Ds) -> +type(bs_get_tail, _Args, _Anno, _Ts, _Ds) -> #t_bitstring{}; +type(call, [#b_local{} | _Args], Anno, _Ts, _Ds) -> + case Anno of + #{ result_type := Type } -> Type; + #{} -> any + end; type(call, [#b_remote{mod=#b_literal{val=Mod}, - name=#b_literal{val=Name}}|Args], Ts, _Ds) -> + name=#b_literal{val=Name}}|Args], _Anno, Ts, _Ds) -> ArgTypes = normalized_types(Args, Ts), {RetType, _, _} = beam_call_types:types(Mod, Name, ArgTypes), RetType; -type(get_tuple_element, [Tuple, Offset], Ts, _Ds) -> +type(get_tuple_element, [Tuple, Offset], _Anno, Ts, _Ds) -> #t_tuple{size=Size,elements=Es} = normalized_type(Tuple, Ts), #b_literal{val=N} = Offset, true = Size > N, %Assertion. beam_types:get_element_type(N + 1, Es); -type(is_nonempty_list, [_], _Ts, _Ds) -> +type(is_nonempty_list, [_], _Anno, _Ts, _Ds) -> beam_types:make_boolean(); -type(is_tagged_tuple, [_,#b_literal{},#b_literal{}], _Ts, _Ds) -> +type(is_tagged_tuple, [_,#b_literal{},#b_literal{}], _Anno, _Ts, _Ds) -> beam_types:make_boolean(); -type(make_fun, [#b_local{arity=TotalArity}|Env], _Ts, _Ds) -> +type(make_fun, [#b_local{arity=TotalArity}|Env], _Anno, _Ts, _Ds) -> #t_fun{arity=TotalArity-length(Env)}; -type(put_map, _Args, _Ts, _Ds) -> +type(put_map, _Args, _Anno, _Ts, _Ds) -> #t_map{}; -type(put_list, _Args, _Ts, _Ds) -> +type(put_list, _Args, _Anno, _Ts, _Ds) -> cons; -type(put_tuple, Args, Ts, _Ds) -> +type(put_tuple, Args, _Anno, Ts, _Ds) -> {Es, _} = foldl(fun(Arg, {Es0, Index}) -> Type = raw_type(Arg, Ts), Es = beam_types:set_element_type(Index, Type, Es0), {Es, Index + 1} end, {#{}, 1}, Args), #t_tuple{exact=true,size=length(Args),elements=Es}; -type(succeeded, [#b_var{}=Src], Ts, Ds) -> +type(succeeded, [#b_var{}=Src], _Anno, Ts, _Ds) + when map_get(Src, Ts) =:= none -> + beam_types:make_atom(false); +type(succeeded, [#b_var{}=Src], _Anno, Ts, Ds) -> case maps:get(Src, Ds) of #b_set{op={bif,Bif},args=BifArgs} -> - Types = normalized_types(BifArgs, Ts), - case {Bif,Types} of - {BoolOp,[T1,T2]} when BoolOp =:= 'and'; BoolOp =:= 'or' -> - BothBool = beam_types:is_boolean_type(T1) andalso - beam_types:is_boolean_type(T2), - case BothBool of - true -> beam_types:make_atom(true); - false -> beam_types:make_boolean() - end; - {byte_size,[#t_bitstring{}]} -> - beam_types:make_atom(true); - {bit_size,[#t_bitstring{}]} -> - beam_types:make_atom(true); - {map_size,[#t_map{}]} -> - beam_types:make_atom(true); - {'not',[Type]} -> - case beam_types:is_boolean_type(Type) of - true -> beam_types:make_atom(true); - false -> beam_types:make_boolean() - end; - {size,[#t_bitstring{}]} -> - beam_types:make_atom(true); - {tuple_size,[#t_tuple{}]} -> - beam_types:make_atom(true); - {_,_} -> - beam_types:make_boolean() + ArgTypes = normalized_types(BifArgs, Ts), + case beam_call_types:will_succeed(erlang, Bif, ArgTypes) of + yes -> beam_types:make_atom(true); + no -> beam_types:make_atom(false); + maybe -> beam_types:make_boolean() + end; + #b_set{op=call,args=[#b_remote{mod=#b_literal{val=Mod}, + name=#b_literal{val=Func}} | + CallArgs]} -> + ArgTypes = normalized_types(CallArgs, Ts), + case beam_call_types:will_succeed(Mod, Func, ArgTypes) of + yes -> beam_types:make_atom(true); + no -> beam_types:make_atom(false); + maybe -> beam_types:make_boolean() end; #b_set{op=get_hd} -> beam_types:make_atom(true); @@ -991,14 +922,16 @@ type(succeeded, [#b_var{}=Src], Ts, Ds) -> beam_types:make_atom(true); #b_set{op=get_tuple_element} -> beam_types:make_atom(true); + #b_set{op=put_tuple} -> + beam_types:make_atom(true); #b_set{op=wait} -> beam_types:make_atom(false); #b_set{} -> beam_types:make_boolean() end; -type(succeeded, [#b_literal{}], _Ts, _Ds) -> +type(succeeded, [#b_literal{}], _Anno, _Ts, _Ds) -> beam_types:make_atom(true); -type(_, _, _, _) -> any. +type(_, _, _, _, _) -> any. %% will_succeed(TestOperation, Type) -> yes|no|maybe. %% Test whether TestOperation applied to an argument of type Type @@ -1423,6 +1356,9 @@ infer_success_type({bif,Op}, Args, Ts, _Ds) -> true -> {PosTypes, PosTypes}; false -> {PosTypes, []} end; +infer_success_type(call, [#b_var{}=Fun|Args], _Ts, _Ds) -> + T = {Fun, #t_fun{arity=length(Args)}}, + {[T], []}; infer_success_type(bs_start_match, [#b_var{}=Bin], _Ts, _Ds) -> T = {Bin,#t_bitstring{}}, {[T], [T]}; diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index fd265fe174..eb8d075c3e 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -395,16 +395,28 @@ valfun_1({init,Reg}, Vst) -> create_tag(initialized, init, [], Reg, Vst); valfun_1({test_heap,Heap,Live}, Vst) -> test_heap(Heap, Live, Vst); -valfun_1({bif,Op,{f,_},Ss,Dst}=I, Vst) -> - case erl_bifs:is_safe(erlang, Op, length(Ss)) of - true -> - %% It can't fail, so we finish handling it here (not updating - %% catch state). - {RetType, _, _} = bif_types(Op, Ss, Vst), - extract_term(RetType, {bif,Op}, Ss, Dst, Vst); - false -> - %% Since the BIF can fail, make sure that any catch state - %% is updated. +valfun_1({bif,Op,{f,0},Ss,Dst}=I, Vst) -> + case will_bif_succeed(Op, Ss, Vst) of + yes -> + %% This BIF cannot fail, handle it here without updating catch + %% state. + validate_bif(Op, cannot_fail, Ss, Dst, Vst); + no -> + %% The stack will be scanned, so Y registers must be initialized. + verify_y_init(Vst), + kill_state(Vst); + maybe -> + %% The BIF can fail, make sure that any catch state is updated. + valfun_2(I, Vst) + end; +valfun_1({gc_bif,Op,{f,0},Live,Ss,Dst}=I, Vst) -> + case will_bif_succeed(Op, Ss, Vst) of + yes -> + validate_gc_bif(Op, cannot_fail, Ss, Dst, Live, Vst); + no -> + verify_y_init(Vst), + kill_state(Vst); + maybe -> valfun_2(I, Vst) end; %% Put instructions. @@ -451,6 +463,19 @@ valfun_1({put,Src}, Vst0) -> St = St0#st{puts_left={PutsLeft-1,{Dst,Sz,Es}}}, Vst#vst{current=St} end; +%% This instruction never fails, though it may be invalid in some contexts; see +%% val_dsetel/2 +valfun_1({set_tuple_element,Src,Tuple,N}, Vst) -> + I = N + 1, + assert_term(Src, Vst), + assert_type(#t_tuple{size=I}, Tuple, Vst), + %% Manually update the tuple type; we can't rely on the ordinary update + %% helpers as we must support overwriting (rather than just widening or + %% narrowing) known elements, and we can't use extract_term either since + %% the source tuple may be aliased. + #t_tuple{elements=Es0}=Type = normalize(get_term_type(Tuple, Vst)), + Es = beam_types:set_element_type(I, get_term_type(Src, Vst), Es0), + override_type(Type#t_tuple{elements=Es}, Tuple, Vst); %% Instructions for optimization of selective receives. valfun_1({recv_mark,{f,Fail}}, Vst) when is_integer(Fail) -> Vst; @@ -461,6 +486,9 @@ valfun_1(remove_message, Vst) -> %% The message term is no longer fragile. It can be used %% without restrictions. remove_fragility(Vst); +valfun_1({'%', {type_info, _Reg, none}}, Vst) -> + %% Unreachable code, typically after a call that never returns. + kill_state(Vst); valfun_1({'%', {type_info, Reg, #t_bs_context{}=Type}}, Vst) -> %% This is a gross hack, but we'll be rid of it once we have proper union %% types. @@ -484,14 +512,18 @@ valfun_1({line,_}, Vst) -> Vst; %% Exception generating calls valfun_1({call_ext,Live,Func}=I, Vst) -> - case call_types(Func, Live, Vst) of - {none, _, _} -> + case will_call_succeed(Func, Vst) of + yes -> + %% This call cannot fail, handle it here without updating catch + %% state. + call(Func, Live, Vst); + no -> + %% The stack will be scanned, so Y registers must be initialized. verify_live(Live, Vst), - %% The stack will be scanned, so Y registers - %% must be initialized. verify_y_init(Vst), kill_state(Vst); - _ -> + maybe -> + %% The call can fail, make sure that any catch state is updated. valfun_2(I, Vst) end; valfun_1(_I, #vst{current=#st{ct=undecided}}) -> @@ -553,6 +585,7 @@ valfun_1({try_case,Reg}, #vst{current=#st{ct=[Fail|_]}}=Vst0) -> Type -> error({wrong_tag_type,Type}) end; +%% Simple getters that can't fail. valfun_1({get_list,Src,D1,D2}, Vst0) -> assert_not_literal(Src), assert_type(cons, Src, Vst0), @@ -714,18 +747,9 @@ valfun_4(raw_raise=I, Vst) -> call(I, 3, Vst); valfun_4({bif,Op,{f,Fail},Ss,Dst}, Vst) -> validate_src(Ss, Vst), - validate_bif(bif, Op, Fail, Ss, Dst, Vst, Vst); -valfun_4({gc_bif,Op,{f,Fail},Live,Ss,Dst}, #vst{current=St0}=Vst0) -> - validate_src(Ss, Vst0), - verify_live(Live, Vst0), - verify_y_init(Vst0), - - %% Heap allocations and X registers are killed regardless of whether we - %% fail or not, as we may fail after GC. - St = kill_heap_allocation(St0), - Vst = prune_x_regs(Live, Vst0#vst{current=St}), - - validate_bif(gc_bif, Op, Fail, Ss, Dst, Vst0, Vst); + validate_bif(Op, Fail, Ss, Dst, Vst); +valfun_4({gc_bif,Op,{f,Fail},Live,Ss,Dst}, Vst) -> + validate_gc_bif(Op, Fail, Ss, Dst, Live, Vst); valfun_4(return, #vst{current=#st{numy=none}}=Vst) -> assert_durable_term({x,0}, Vst), kill_state(Vst); @@ -754,17 +778,6 @@ valfun_4(timeout, Vst) -> prune_x_regs(0, Vst); valfun_4(send, Vst) -> call(send, 2, Vst); -valfun_4({set_tuple_element,Src,Tuple,N}, Vst) -> - I = N + 1, - assert_term(Src, Vst), - assert_type(#t_tuple{size=I}, Tuple, Vst), - %% Manually update the tuple type; we can't rely on the ordinary update - %% helpers as we must support overwriting (rather than just widening or - %% narrowing) known elements, and we can't use extract_term either since - %% the source tuple may be aliased. - #t_tuple{elements=Es0}=Type = normalize(get_term_type(Tuple, Vst)), - Es = beam_types:set_element_type(I, get_term_type(Src, Vst), Es0), - override_type(Type#t_tuple{elements=Es}, Tuple, Vst); %% Match instructions. valfun_4({select_val,Src,{f,Fail},{list,Choices}}, Vst) -> assert_term(Src, Vst), @@ -1111,7 +1124,40 @@ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) -> %% gc_bifs as X registers are pruned prior to calling this function, which may %% have clobbered the sources. %% -validate_bif(Kind, Op, Fail, Ss, Dst, OrigVst, Vst) -> + +validate_bif(Op, Fail, Ss, Dst, Vst) -> + validate_src(Ss, Vst), + validate_bif_1(bif, Op, Fail, Ss, Dst, Vst, Vst). + +validate_gc_bif(Op, Fail, Ss, Dst, Live, #vst{current=St0}=Vst0) -> + validate_src(Ss, Vst0), + verify_live(Live, Vst0), + verify_y_init(Vst0), + + %% Heap allocations and X registers are killed regardless of whether we + %% fail or not, as we may fail after GC. + St = kill_heap_allocation(St0), + Vst = prune_x_regs(Live, Vst0#vst{current=St}), + validate_src(Ss, Vst), + + validate_bif_1(gc_bif, Op, Fail, Ss, Dst, Vst, Vst). + +validate_bif_1(Kind, Op, cannot_fail, Ss, Dst, OrigVst, Vst0) -> + %% This BIF explicitly cannot fail; it will not jump to a guard nor throw + %% an exception. Validation will fail if it returns 'none' or has a type + %% conflict on one of its arguments. + + {Type, ArgTypes, _CanSubtract} = bif_types(Op, Ss, Vst0), + ZippedArgs = zip(Ss, ArgTypes), + + Vst = foldl(fun({A, T}, V) -> + update_type(fun meet/2, T, A, V) + end, Vst0, ZippedArgs), + + true = Type =/= none, %Assertion. + + extract_term(Type, {Kind, Op}, Ss, Dst, Vst, OrigVst); +validate_bif_1(Kind, Op, Fail, Ss, Dst, OrigVst, Vst) -> {Type, ArgTypes, CanSubtract} = bif_types(Op, Ss, Vst), ZippedArgs = zip(Ss, ArgTypes), @@ -1129,7 +1175,7 @@ validate_bif(Kind, Op, Fail, Ss, Dst, OrigVst, Vst) -> SuccVst = foldl(fun({A, T}, V) -> update_type(fun meet/2, T, A, V) end, SuccVst0, ZippedArgs), - extract_term(Type, {Kind,Op}, Ss, Dst, SuccVst, OrigVst) + extract_term(Type, {Kind, Op}, Ss, Dst, SuccVst, OrigVst) end, branch(Fail, Vst, FailFun, SuccFun). @@ -1233,8 +1279,9 @@ call(Name, Live, #vst{current=St0}=Vst0) -> verify_call_args(Name, Live, Vst0), verify_y_init(Vst0), case call_types(Name, Live, Vst0) of + {none, _, _} -> + kill_state(Vst0); {RetType, _, _} -> - %% Type is never 'none' because it has been handled earlier. St = St0#st{f=init_fregs()}, Vst = prune_x_regs(0, Vst0#vst{current=St}), create_term(RetType, call, [], {x,0}, Vst) @@ -2411,6 +2458,25 @@ call_types({extfunc,M,F,A}, A, Vst) -> call_types(_, A, Vst) -> {any, get_call_args(A, Vst), false}. +will_bif_succeed(fadd, [_,_], _Vst) -> + maybe; +will_bif_succeed(fdiv, [_,_], _Vst) -> + maybe; +will_bif_succeed(fmul, [_,_], _Vst) -> + maybe; +will_bif_succeed(fnegate, [_], _Vst) -> + maybe; +will_bif_succeed(fsub, [_,_], _Vst) -> + maybe; +will_bif_succeed(Op, Ss, Vst) -> + Args = [normalize(get_term_type(Arg, Vst)) || Arg <- Ss], + beam_call_types:will_succeed(erlang, Op, Args). + +will_call_succeed({extfunc,M,F,A}, Vst) -> + beam_call_types:will_succeed(M, F, get_call_args(A, Vst)); +will_call_succeed(_Call, _Vst) -> + maybe. + get_call_args(Arity, Vst) -> get_call_args_1(0, Arity, Vst). diff --git a/lib/kernel/src/erts_debug.erl b/lib/kernel/src/erts_debug.erl index 42261d371d..7b9067d079 100644 --- a/lib/kernel/src/erts_debug.erl +++ b/lib/kernel/src/erts_debug.erl @@ -40,6 +40,15 @@ lc_graph/0, lc_graph_to_dot/2, lc_graph_merge/2, alloc_blocks_size/1]). +%% Reroutes calls to the given MFA to error_handler:breakpoint/3 +%% +%% Note that this is potentially unsafe as compiled code may assume that the +%% targeted function returns a specific type, triggering undefined behavior if +%% this function were to return something else. +%% +%% For reference, the debugger avoids the issue by purging the affected module +%% and interpreting all functions in the module, ensuring that no assumptions +%% are made with regard to return or argument types. -spec breakpoint(MFA, Flag) -> non_neg_integer() when MFA :: {Module :: module(), Function :: atom(), |