aboutsummaryrefslogtreecommitdiffstats
path: root/lib/compiler/src/beam_validator.erl
diff options
context:
space:
mode:
authorJohn Högberg <[email protected]>2019-08-07 08:12:58 +0200
committerJohn Högberg <[email protected]>2019-08-07 08:12:58 +0200
commit18ab59fe935799f6495ed8273a9c577c655d30ab (patch)
treeb7d02a7e18786f7ef645f6d72a826caa1e8e034e /lib/compiler/src/beam_validator.erl
parent86b0385622e25502222ae171a14f24aba998f2f9 (diff)
parent2ea04617d18bc3c4f80fc3b28af47379a3ec26fc (diff)
downloadotp-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.erl148
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).