summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Gustavsson <bjorn@erlang.org>2022-01-11 08:53:09 +0100
committerBjörn Gustavsson <bjorn@erlang.org>2022-01-14 06:46:59 +0100
commitfe244ad80c1b8621fc499f94cac758b90d063831 (patch)
treef6a31e3b59a2c3e3a25c6e284592683c437f8e32
parent70b3bf6371aaf351d4a3dddc383508133a8d92bc (diff)
downloaderlang-fe244ad80c1b8621fc499f94cac758b90d063831.tar.gz
Eliminate confusing `case_clause` exception
Consider this module: -module(bug). -export([foo/0]). foo() -> fun(a) -> ok end(b). The call to the fun will always fail, which will be noted by the compiler: 1> c(bug). bug.erl:5:5: Warning: no clause will ever match % 5| fun(a) -> ok end(b). % | ^ {ok,bug} What is unexpected is that the exception that is raised is not a `function_clause` exception: 2> bug:foo(). ** exception error: no case clause matching {b} in function bug:foo/0 (bug.erl, line 5) This confusing `case_clause` exception started to appear in OTP 24 because of 72675baaa9fd30 (#4545) that inlines funs that are immediately used. Before OTP 24, when the fun was not inlined, the exception would be: 2> bug:foo(). ** exception error: no function clause matching bug:'-foo/0-fun-0-'(b) (bug.erl, line 5) The reason that `function_clause` exceptions are rewritten to `case_clause` exceptions in inlined code is to avoid the even more confusing and misleading exception: 2> bug:foo(). ** exception error: no function clause matching bug:foo(b) (bug.erl, line 5) This is confusing because it seems that `foo/0` was called with one argument. To reduce the confusion, this commmit ensures that `function_clause` exceptions in inlined code remains `function_clause` exceptions, but with a generated name that makes it clear that the `function_clause` exception occurred in a fun: 2> bug:foo(). ** exception error: no function clause matching bug:'-foo/0-inlined-0-'(b) (bug.erl, line 5) Fixes #5513
-rw-r--r--lib/compiler/src/beam_kernel_to_ssa.erl13
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl128
-rw-r--r--lib/compiler/src/beam_validator.erl13
-rw-r--r--lib/compiler/src/v3_core.erl2
-rw-r--r--lib/compiler/src/v3_kernel.erl49
-rw-r--r--lib/compiler/test/beam_except_SUITE.erl8
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl7
-rw-r--r--lib/compiler/test/guard_SUITE.erl3
-rw-r--r--lib/compiler/test/inline_SUITE.erl2
9 files changed, 165 insertions, 60 deletions
diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl
index c2e22f4b6d..7722078206 100644
--- a/lib/compiler/src/beam_kernel_to_ssa.erl
+++ b/lib/compiler/src/beam_kernel_to_ssa.erl
@@ -25,7 +25,7 @@
-export([module/2]).
-import(lists, [all/2,append/1,flatmap/2,foldl/3,
- keysort/2,mapfoldl/3,member/2,
+ keyfind/3,keysort/2,mapfoldl/3,member/2,
reverse/1,sort/1]).
-include("v3_kernel.hrl").
@@ -672,7 +672,7 @@ call_cg(Func, As, [#k_var{name=R}|MoreRs]=Rs, Le, St0) ->
%% Ordinary function call in a function body.
Args = ssa_args([Func|As], St0),
{Ret,St1} = new_ssa_var(R, St0),
- Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=Args},
+ Call = #b_set{anno=call_anno(Le),op=call,dst=Ret,args=Args},
%% If this is a call to erlang:error(), MoreRs could be a
%% nonempty list of variables that each need a value.
@@ -685,9 +685,16 @@ call_cg(Func, As, [#k_var{name=R}|MoreRs]=Rs, Le, St0) ->
enter_cg(Func, As0, Le, St0) ->
As = ssa_args([Func|As0], St0),
{Ret,St} = new_ssa_var('@ssa_ret', St0),
- Call = #b_set{anno=line_anno(Le),op=call,dst=Ret,args=As},
+ Call = #b_set{anno=call_anno(Le),op=call,dst=Ret,args=As},
{[Call,#b_ret{arg=Ret}],St}.
+call_anno(Le) ->
+ Anno = line_anno(Le),
+ case keyfind(inlined, 1, Le) of
+ false -> Anno;
+ {inlined,NameArity} -> Anno#{inlined => NameArity}
+ end.
+
%% bif_cg(#k_bif{}, Le,State) -> {[Ainstr],State}.
%% Generate code for a guard BIF or primop.
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index 3567fb4210..f5ad81f760 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -73,14 +73,16 @@
-import(lists, [all/2,any/2,append/1,duplicate/2,
foldl/3,last/1,member/2,partition/2,
- reverse/1,reverse/2,sort/1,splitwith/2,zip/2]).
+ reverse/1,reverse/2,seq/2,sort/1,
+ splitwith/2,usort/1,zip/2]).
-spec module(beam_ssa:b_module(), [compile:option()]) ->
{'ok',beam_ssa:b_module()}.
module(#b_module{body=Fs0}=Module, Opts) ->
Ps = passes(Opts),
- Fs = functions(Fs0, Ps),
+ Fs1 = functions(Fs0, Ps),
+ Fs = fc_stubs(Fs1, Module),
{ok,Module#b_module{body=Fs}}.
functions([F|Fs], Ps) ->
@@ -835,19 +837,21 @@ match_fail_instrs_1([{L,#b_blk{is=Is0}=Blk}|Bs], Arity, Blocks0) ->
match_fail_instrs_1([], _Arity, Blocks) -> Blocks.
match_fail_instrs_blk([#b_set{op=put_tuple,dst=Dst,
- args=[#b_literal{val=Tag},Val]},
+ args=[#b_literal{val=Tag}|Values]},
#b_set{op=call,
args=[#b_remote{mod=#b_literal{val=erlang},
name=#b_literal{val=error}},
Dst]}=Call|Is],
_Arity, Acc) ->
- match_fail_instr(Call, Tag, Val, Is, Acc);
+ match_fail_instr(Call, Tag, Values, Is, Acc);
match_fail_instrs_blk([#b_set{op=call,
args=[#b_remote{mod=#b_literal{val=erlang},
name=#b_literal{val=error}},
- #b_literal{val={Tag,Val}}]}=Call|Is],
- _Arity, Acc) ->
- match_fail_instr(Call, Tag, #b_literal{val=Val}, Is, Acc);
+ #b_literal{val=Tuple}]}=Call|Is],
+ _Arity, Acc) when tuple_size(Tuple) >= 1 ->
+ [Tag|Values0] = tuple_to_list(Tuple),
+ Values = [#b_literal{val=V} || V <- Values0],
+ match_fail_instr(Call, Tag, Values, Is, Acc);
match_fail_instrs_blk([#b_set{op=call,
args=[#b_remote{mod=#b_literal{val=erlang},
name=#b_literal{val=error}},
@@ -860,34 +864,29 @@ match_fail_instrs_blk([#b_set{op=call,anno=Anno,
name=#b_literal{val=error}},
#b_literal{val=function_clause},
Stk]}=Call],
- {Arity,Location}, Acc) ->
- case match_fail_stk(Stk, Acc, [], []) of
- {[_|_]=Vars,Is} when length(Vars) =:= Arity ->
- case maps:get(location, Anno, none) of
- Location ->
- I = Call#b_set{op=match_fail,
- args=[#b_literal{val=function_clause}|Vars]},
- Is ++ [I];
- _ ->
- %% erlang:error/2 has a different location than the
- %% func_info instruction at the beginning of the function
- %% (probably because of inlining). Keep the original call.
- reverse(Acc, [Call])
- end;
- _ ->
- %% Either the stacktrace could not be picked apart (for example,
- %% if the call to erlang:error/2 was handwritten) or the number
- %% of arguments in the stacktrace was different from the arity
- %% of the host function (because it is the implementation of a
- %% fun). Keep the original call.
- reverse(Acc, [Call])
- end;
+ Arity, Acc) ->
+ match_fail_fc(Anno, Call, Stk, Arity, Acc);
match_fail_instrs_blk([I|Is], Arity, Acc) ->
match_fail_instrs_blk(Is, Arity, [I|Acc]);
match_fail_instrs_blk(_, _, _) ->
none.
-match_fail_instr(Call, Tag, Val, Is, Acc) ->
+match_fail_instr(Call, function_clause, Values, Is, Acc) ->
+ case beam_ssa:get_anno(inlined, Call, none) of
+ none ->
+ %% If there is no `inlined` annotation, it implies that
+ %% the call to erlang:error/1 was handwritten.
+ none;
+ {Name,Arity} ->
+ %% A `function_clause` in inlined code. Convert it to
+ %% a call to a stub function that will raise a proper
+ %% `function_clause` exception. (The stub function will
+ %% be created later by fc_stubs/2.)
+ Target = #b_local{name=#b_literal{val=Name},arity=Arity},
+ I = Call#b_set{args=[Target|Values]},
+ reverse(Acc, [I|Is])
+ end;
+match_fail_instr(Call, Tag, [Val], Is, Acc) ->
Op = case Tag of
badmatch -> Tag;
case_clause -> case_end;
@@ -900,19 +899,84 @@ match_fail_instr(Call, Tag, Val, Is, Acc) ->
_ ->
I = Call#b_set{op=match_fail,args=[#b_literal{val=Op},Val]},
reverse(Acc, [I|Is])
+ end;
+match_fail_instr(_, _, _, _, _) -> none.
+
+match_fail_fc(Anno, Call, Stk, {Arity,Location}, Acc) ->
+ case match_fail_stk(Stk, Acc, [], []) of
+ {[_|_]=Vars,Is} when length(Vars) =:= Arity ->
+ case maps:get(location, Anno, none) of
+ Location ->
+ I = Call#b_set{op=match_fail,
+ args=[#b_literal{val=function_clause}|Vars]},
+ Is ++ [I];
+ _ ->
+ %% erlang:error/2 has a different location than
+ %% the func_info instruction at the beginning of
+ %% the function (probably because of
+ %% inlining). Keep the original call.
+ reverse(Acc, [Call])
+ end;
+ _ ->
+ %% Either the stacktrace could not be picked apart (for
+ %% example, if the call to erlang:error/2 was handwritten)
+ %% or the number of arguments in the stacktrace was
+ %% different from the arity of the host function (because
+ %% it is the implementation of a fun). Keep the original
+ %% call.
+ reverse(Acc, [Call])
end.
match_fail_stk(#b_var{}=V, [#b_set{op=put_list,dst=V,args=[H,T]}|Is], IAcc, VAcc) ->
match_fail_stk(T, Is, IAcc, [H|VAcc]);
match_fail_stk(#b_literal{val=[H|T]}, Is, IAcc, VAcc) ->
match_fail_stk(#b_literal{val=T}, Is, IAcc, [#b_literal{val=H}|VAcc]);
-match_fail_stk(#b_literal{val=[]}, [], IAcc, VAcc) ->
- {reverse(VAcc),IAcc};
+match_fail_stk(#b_literal{val=[]}, Is, IAcc, VAcc) ->
+ {reverse(VAcc),reverse(Is, IAcc)};
match_fail_stk(T, [#b_set{op=Op}=I|Is], IAcc, VAcc)
when Op =:= bs_get_tail; Op =:= bs_set_position ->
match_fail_stk(T, Is, [I|IAcc], VAcc);
match_fail_stk(_, _, _, _) -> none.
+%% Create stubs for `function_clause` exceptions generated by
+%% inlined code.
+fc_stubs(Fs, #b_module{name=Mod}) ->
+ Stubs0 = usort(find_fc_calls(Fs, [])),
+ Stubs = [begin
+ Seq = seq(0, Arity-1),
+ Args = [#b_var{name=V} || V <- Seq],
+ XRegs = [{x,V} || V <- Seq],
+ Ret = #b_var{name='@ssa_ret'},
+ Regs = maps:from_list([{Ret,{x,0}}|zip(Args, XRegs)]),
+ Anno = #{func_info => {Mod,Name,Arity},
+ location => Location,
+ parameter_info => #{},
+ registers => Regs},
+ Fc = #b_set{op=match_fail,dst=Ret,
+ args=[#b_literal{val=function_clause}|Args]},
+ Blk = #b_blk{is=[Fc],last=#b_ret{arg=Ret}},
+ #b_function{anno=Anno,args=Args,
+ bs=#{0 => Blk},
+ cnt=1}
+ end || {{Name,Arity},Location} <- Stubs0],
+ Fs ++ Stubs.
+
+find_fc_calls([#b_function{bs=Blocks}|Fs], Acc0) ->
+ F = fun(#b_set{anno=Anno,op=call}, A) ->
+ case Anno of
+ #{inlined := FA} ->
+ [{FA,maps:get(location, Anno, [])}|A];
+ #{} ->
+ A
+ end;
+ (_, A) ->
+ A
+ end,
+ Acc = beam_ssa:fold_instrs(F, maps:keys(Blocks), Acc0, Blocks),
+ find_fc_calls(Fs, Acc);
+find_fc_calls([], Acc) -> Acc.
+
+
%%%
%%% Introduce the set_tuple_element instructions to make
%%% multiple-field record updates faster.
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 125c01c6a6..adbddc7059 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -245,7 +245,8 @@ build_function_table([{function,Name,Arity,Entry,Code0}|Fs], Acc) ->
[{label,Entry} | Is] ->
Info = #{ name => Name,
arity => Arity,
- parameter_info => find_parameter_info(Is, #{}) },
+ parameter_info => find_parameter_info(Is, #{}),
+ always_fails => always_fails(Is) },
build_function_table(Fs, Acc#{ Entry => Info });
_ ->
%% Something is seriously wrong. Ignore it for now.
@@ -262,6 +263,9 @@ find_parameter_info([{'%', _} | Is], Acc) ->
find_parameter_info(_, Acc) ->
Acc.
+always_fails([{jump,_}|_]) -> true;
+always_fails(_) -> false.
+
validate_1(Is, MFA0, Entry, Level, Ft) ->
{Offset, MFA, Header, Body} = extract_header(Is, MFA0, Entry, 1, []),
@@ -3078,6 +3082,13 @@ will_call_succeed(bs_init_writable, _Vst) ->
yes;
will_call_succeed(raw_raise, _Vst) ->
no;
+will_call_succeed({f,Lbl}, #vst{ft=Ft}) ->
+ case Ft of
+ #{Lbl := #{always_fails := true}} ->
+ no;
+ #{} ->
+ maybe
+ end;
will_call_succeed(_Call, _Vst) ->
maybe.
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 6bf87e131c..8e89c88318 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -264,7 +264,7 @@ body(Cs0, Name, Arity, St0) ->
Args = reverse(Args0), %Nicer order
{Cs1,St2} = clauses(Cs0, St1),
{Ps,St3} = new_vars(Arity, St2), %Need new variables here
- Fc = function_clause(Ps, Anno),
+ Fc = function_clause(Ps, FunAnno),
{#ifun{anno=#a{anno=FunAnno},id=[],vars=Args,clauses=Cs1,fc=Fc},St3}.
%% clause(Clause, State) -> {Cclause,State}.
diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl
index aa3fc033eb..aa8a906ca7 100644
--- a/lib/compiler/src/v3_kernel.erl
+++ b/lib/compiler/src/v3_kernel.erl
@@ -405,36 +405,51 @@ letrec_goto([{#c_var{name={Label,0}},Cfail}], Cb, Sub0,
%% erlang:error/2.
translate_match_fail(Arg, Sub, Anno, St0) ->
- Cargs = case {cerl:data_type(Arg),cerl:data_es(Arg)} of
- {tuple,[#c_literal{val=function_clause}|As]} ->
- translate_fc_args(As, Sub, St0);
- {_,_} ->
- [Arg]
- end,
- {Kargs,Ap,St} = atomic_list(Cargs, Sub, St0),
+ {Cargs,ExtraAnno,St1} =
+ case {cerl:data_type(Arg),cerl:data_es(Arg)} of
+ {tuple,[#c_literal{val=function_clause}|As]} ->
+ translate_fc_args(As, Sub, Anno, St0);
+ {_,_} ->
+ {[Arg],[],St0}
+ end,
+ {Kargs,Ap,St} = atomic_list(Cargs, Sub, St1),
Ar = length(Cargs),
- Call = #k_call{anno=Anno,
+ Call = #k_call{anno=ExtraAnno++Anno,
op=#k_remote{mod=#k_literal{val=erlang},
name=#k_literal{val=error},
arity=Ar},args=Kargs},
{Call,Ap,St}.
-translate_fc_args(As, Sub, #kern{fargs=Fargs}) ->
+translate_fc_args(As, Sub, Anno, #kern{fargs=Fargs}=St0) ->
case same_args(As, Fargs, Sub) of
true ->
%% The arguments for the `function_clause` exception are
%% the arguments for the current function in the correct
%% order.
- [#c_literal{val=function_clause},cerl:make_list(As)];
+ {[#c_literal{val=function_clause},cerl:make_list(As)],
+ [],
+ St0};
false ->
%% The arguments in the `function_clause` exception don't
- %% match the arguments for the current function because
- %% of inlining. Keeping the `function_clause`
- %% exception reason would be confusing. Rewrite it to
- %% a `case_clause` exception with the arguments in a
- %% tuple.
- [cerl:c_tuple([#c_literal{val=case_clause},
- cerl:c_tuple(As)])]
+ %% match the arguments for the current function because of
+ %% inlining.
+ Args = [cerl:c_tuple([#c_literal{val=function_clause}|As])],
+ case keyfind(function, 1, Anno) of
+ false ->
+ %% This is probably a fun that has been inlined
+ %% by sys_core_fold.
+ {Name,St1} = new_fun_name("inlined", St0),
+ {Args,
+ [{inlined,{Name,length(As)}}],
+ St1};
+ {_,{Name0,Arity}} ->
+ %% This is function that has been inlined.
+ Name1 = ["-inlined-",Name0,"/",Arity,"-"],
+ Name = list_to_atom(lists:concat(Name1)),
+ {Args,
+ [{inlined,{Name,Arity}}],
+ St0}
+ end
end.
same_args([#c_var{name=Cv}|Vs], [#k_var{name=Kv}|As], Sub) ->
diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl
index 95e5989081..2bc805be71 100644
--- a/lib/compiler/test/beam_except_SUITE.erl
+++ b/lib/compiler/test/beam_except_SUITE.erl
@@ -135,14 +135,18 @@ coverage(_) ->
{'EXIT',{function_clause,[{?MODULE,foobar,[[fail],1,2],
[{file,"fake.erl"},{line,16}]}|_]}} =
(catch foobar([fail], 1, 2)),
+
{'EXIT',{function_clause,[{?MODULE,fake_function_clause1,[{a,b},42.0],_}|_]}} =
(catch fake_function_clause1({a,b})),
{'EXIT',{function_clause,[{?MODULE,fake_function_clause2,[42|bad_tl],_}|_]}} =
(catch fake_function_clause2(42, bad_tl)),
+
{'EXIT',{function_clause,[{?MODULE,fake_function_clause3,[x,y],_}|_]}} =
(catch fake_function_clause3(42, id([x,y]))),
+ {'EXIT',{{function_clause,a,b,c}, _}} = catch fake_function_clause4(),
+
{'EXIT',{{badmatch,0.0},_}} = (catch coverage_1(id(42))),
{'EXIT',{badarith,_}} = (catch coverage_1(id(a))),
@@ -153,9 +157,13 @@ coverage_1(X) ->
true = 0 / X.
fake_function_clause1(A) -> error(function_clause, [A,42.0]).
+
fake_function_clause2(A, Tl) -> error(function_clause, [A|Tl]).
+
fake_function_clause3(_, Stk) -> error(function_clause, Stk).
+fake_function_clause4() -> error({function_clause,a,b,c}).
+
binary_construction_allocation(_Config) ->
ok = do_binary_construction_allocation("PUT"),
ok.
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index f7542be568..96ae549b28 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -1468,10 +1468,11 @@ haystack_2(Haystack) ->
fc({'EXIT',{function_clause,_}}) -> ok;
fc({'EXIT',{{case_clause,_},_}}) when ?MODULE =:= bs_match_inline_SUITE -> ok.
-fc(Name, Args, {'EXIT',{function_clause,[{?MODULE,Name,Args,_}|_]}}) -> ok;
-fc(_, Args, {'EXIT',{{case_clause,ActualArgs},_}})
+fc(Name, Args, {'EXIT',{function_clause,[{?MODULE,Name,Args,_}|_]}}) ->
+ ok;
+fc(Name, Args, {'EXIT',{function_clause,[{?MODULE,_,Args,_}|_]}})
when ?MODULE =:= bs_match_inline_SUITE ->
- Args = tuple_to_list(ActualArgs).
+ ok.
%% Cover the clause handling bs_context to binary in
%% beam_block:initialized_regs/2.
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index 6699d82262..6a42453654 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -3070,5 +3070,4 @@ check(F, Result) ->
ct:fail(check_failed)
end.
-fc({'EXIT',{function_clause,_}}) -> ok;
-fc({'EXIT',{{case_clause,_},_}}) when ?MODULE =:= guard_inline_SUITE -> ok.
+fc({'EXIT',{function_clause,_}}) -> ok.
diff --git a/lib/compiler/test/inline_SUITE.erl b/lib/compiler/test/inline_SUITE.erl
index 7482f53ed4..b2778bebcd 100644
--- a/lib/compiler/test/inline_SUITE.erl
+++ b/lib/compiler/test/inline_SUITE.erl
@@ -332,7 +332,7 @@ badarg(Reply, _A) ->
Reply.
otp_7223(Config) when is_list(Config) ->
- {'EXIT', {{case_clause,{1}},_}} = (catch otp_7223_1(1)),
+ {'EXIT', {function_clause, [{?MODULE,_,[1],_}|_]}} = (catch otp_7223_1(1)),
ok.
-compile({inline,[{otp_7223_1,1}]}).