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/compiler/src/beam_validator.erl | |
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/compiler/src/beam_validator.erl')
-rw-r--r-- | lib/compiler/src/beam_validator.erl | 148 |
1 files changed, 107 insertions, 41 deletions
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). |