diff options
author | John Högberg <[email protected]> | 2019-07-10 09:05:13 +0200 |
---|---|---|
committer | John Högberg <[email protected]> | 2019-08-06 10:01:54 +0200 |
commit | dee48ffd9832d59e8f66882ca240bd028a745f59 (patch) | |
tree | 4bad77e7e988a9ef236544c2590626e339f60bf9 /lib/compiler | |
parent | 9b2011e8e435460f79185682a36d82e94f32321d (diff) | |
download | otp-dee48ffd9832d59e8f66882ca240bd028a745f59.tar.gz otp-dee48ffd9832d59e8f66882ca240bd028a745f59.tar.bz2 otp-dee48ffd9832d59e8f66882ca240bd028a745f59.zip |
compiler: Make 'succeeded' optimization more general
Diffstat (limited to 'lib/compiler')
-rw-r--r-- | lib/compiler/src/beam_call_types.erl | 56 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_type.erl | 40 | ||||
-rw-r--r-- | lib/compiler/src/beam_validator.erl | 123 |
3 files changed, 161 insertions, 58 deletions
diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl index e76ad79365..4cda2f2610 100644 --- a/lib/compiler/src/beam_call_types.erl +++ b/lib/compiler/src/beam_call_types.erl @@ -24,7 +24,61 @@ -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, 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_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index e2c4b96c02..364a87f67e 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -901,32 +901,20 @@ type(succeeded, [#b_var{}=Src], _Anno, Ts, _Ds) 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); diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index aa3cf59288..0fc3593265 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. @@ -487,14 +499,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}}) -> @@ -556,6 +572,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), @@ -717,18 +734,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); @@ -1100,7 +1108,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), @@ -1118,7 +1159,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). @@ -1222,8 +1263,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 - {RetType, _, _} when RetType =/= none -> - %% Type is never 'none' because it has been handled earlier. + {none, _, _} -> + kill_state(Vst0); + {RetType, _, _} -> St = St0#st{f=init_fregs()}, Vst = prune_x_regs(0, Vst0#vst{current=St}), create_term(RetType, call, [], {x,0}, Vst) @@ -2404,6 +2446,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). |