File 0527-v3_core-Don-t-use-wrapper-functions-for-small-after-.patch of Package erlang
From fe34ef3dda90f189c6d75be9d661f361ccbcc533 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20H=C3=B6gberg?= <john@erlang.org>
Date: Thu, 17 Sep 2020 18:10:15 +0200
Subject: [PATCH] v3_core: Don't use wrapper functions for small after blocks
This commit fixes a slowdown in both dialyzer and the compiler by
inlining small (try) after blocks, only emitting wrapper functions
when they exceed a certain size.
---
lib/compiler/src/v3_core.erl | 134 +++++++++++++++++++++------
lib/compiler/test/trycatch_SUITE.erl | 114 ++++++++++++++++++-----
2 files changed, 199 insertions(+), 49 deletions(-)
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 8037bb6efc..74a45b7279 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -649,25 +649,7 @@ expr({'try',L,Es0,Cs0,Ecs,[]}, St0) ->
Ceps,St5};
expr({'try',L,Es0,[],[],As0}, St0) ->
%% 'try ... after ... end'
- {Es1,St1} = exprs(Es0, St0),
- {As1,St2} = exprs(As0, St1),
- {Name,St3} = new_fun_name("after", St2),
- {V,St4} = new_var(St3), % (must not exist in As1)
- LA = lineno_anno(L, St4),
- Lanno = #a{anno=LA},
- Fc = function_clause([], LA, {Name,0}),
- Fun = #ifun{anno=Lanno,id=[],vars=[],
- clauses=[#iclause{anno=Lanno,pats=[],
- guard=[#c_literal{val=true}],
- body=As1}],
- fc=Fc},
- App = #iapply{anno=#a{anno=[compiler_generated|LA]},
- op=#c_var{anno=LA,name={Name,0}},args=[]},
- {Evs,Hs,St5} = try_after([App], St4),
- Try = #itry{anno=Lanno,args=Es1,vars=[V],body=[App,V],evars=Evs,handler=Hs},
- Letrec = #iletrec{anno=Lanno,defs=[{{Name,0},Fun}],
- body=[Try]},
- {Letrec,[],St5};
+ try_after(L, Es0, As0, St0);
expr({'try',L,Es,Cs,Ecs,As}, St0) ->
%% 'try ... [of ...] [catch ...] after ... end'
expr({'try',L,[{'try',L,Es,Cs,Ecs,[]}],[],[],As}, St0);
@@ -935,18 +917,74 @@ try_exception(Ecs0, St0) ->
Hs = [#icase{anno=#a{anno=LA},args=[c_tuple(Evs)],clauses=Ecs2,fc=Ec}],
{Evs,Ceps++Hs,St2}.
-try_after(As, St0) ->
+try_after(Line, Es0, As0, St0) ->
+ %% 'try ... after ... end'
+ As1 = ta_sanitize_as(As0, Line),
+
+ {Es, St1} = exprs(Es0, St0),
+ {As, St2} = exprs(As1, St1),
+ {V, St3} = new_var(St2), % (must not exist in As1)
+ LineAnno = lineno_anno(Line, St3),
+
+ case is_iexprs_small(As, 20) of
+ true -> try_after_small(LineAnno, Es, As, V, St3);
+ false -> try_after_large(LineAnno, Es, As, V, St3)
+ end.
+
+%% 'after' blocks don't have a result, so we match the last expression with '_'
+%% to suppress false "unmatched return" warnings in tools that look at core
+%% Erlang, such as `dialyzer`.
+ta_sanitize_as([Expr], Line) ->
+ [{match, Line, {var,[],'_'}, Expr}];
+ta_sanitize_as([Expr | Exprs], Line) ->
+ [Expr | ta_sanitize_as(Exprs, Line)].
+
+try_after_large(LA, Es, As, V, St0) ->
+ %% Large 'after' block; break it out into a wrapper function to reduce
+ %% code size.
+ Lanno = #a{anno=LA},
+ {Name, St1} = new_fun_name("after", St0),
+ Fc = function_clause([], LA, {Name,0}),
+ Fun = #ifun{anno=Lanno,id=[],vars=[],
+ clauses=[#iclause{anno=Lanno,pats=[],
+ guard=[#c_literal{val=true}],
+ body=As}],
+ fc=Fc},
+ App = #iapply{anno=#a{anno=[compiler_generated|LA]},
+ op=#c_var{anno=LA,name={Name,0}},
+ args=[]},
+ {Evs, Hs, St} = after_block([App], St1),
+ Try = #itry{anno=Lanno,
+ args=Es,
+ vars=[V],
+ body=[App,V],
+ evars=Evs,
+ handler=Hs},
+ Letrec = #iletrec{anno=Lanno,defs=[{{Name,0},Fun}],
+ body=[Try]},
+ {Letrec, [], St}.
+
+try_after_small(LA, Es, As, V, St0) ->
+ %% Small 'after' block; inline it.
+ Lanno = #a{anno=LA},
+ {Evs, Hs, St1} = after_block(As, St0),
+ Try = #itry{anno=Lanno,args=Es,vars=[V],
+ body=(As ++ [V]),
+ evars=Evs,handler=Hs},
+ {Try, [], St1}.
+
+after_block(As, St0) ->
%% See above.
- {Evs,St1} = new_vars(3, St0), % Tag, Value, Info
+ {Evs, St1} = new_vars(3, St0), % Tag, Value, Info
[_,Value,Info] = Evs,
- B = As ++ [#iprimop{anno=#a{}, % Must have an #a{}
- name=#c_literal{val=raise},
- args=[Info,Value]}],
+ B = As ++ [#iprimop{anno=#a{}, % Must have an #a{}
+ name=#c_literal{val=raise},
+ args=[Info,Value]}],
Ec = #iclause{anno=#a{anno=[compiler_generated]},
- pats=[c_tuple(Evs)],guard=[#c_literal{val=true}],
- body=B},
+ pats=[c_tuple(Evs)],guard=[#c_literal{val=true}],
+ body=B},
Hs = [#icase{anno=#a{},args=[c_tuple(Evs)],clauses=[],fc=Ec}],
- {Evs,Hs,St1}.
+ {Evs, Hs, St1}.
try_build_stacktrace([#iclause{pats=Ps0,body=B0}=C0|Cs], RawStk) ->
[#c_tuple{es=[Class,Exc,Stk]}=Tup] = Ps0,
@@ -967,6 +1005,48 @@ try_build_stacktrace([#iclause{pats=Ps0,body=B0}=C0|Cs], RawStk) ->
end;
try_build_stacktrace([], _) -> [].
+%% is_iexprs_small([Exprs], Threshold) -> boolean().
+%% Determines whether a list of expressions is "smaller" than the given
+%% threshold. This is largely analogous to cerl_trees:size/1 but operates on
+%% our internal #iexprs{} and bails out as soon as the threshold is exceeded.
+is_iexprs_small(Exprs, Threshold) ->
+ 0 < is_iexprs_small_1(Exprs, Threshold).
+
+is_iexprs_small_1(_, 0) ->
+ 0;
+is_iexprs_small_1([], Threshold) ->
+ Threshold;
+is_iexprs_small_1([Expr | Exprs], Threshold0) ->
+ Threshold = is_iexprs_small_2(Expr, Threshold0 - 1),
+ is_iexprs_small_1(Exprs, Threshold).
+
+is_iexprs_small_2(#iclause{guard=Guards,body=Body}, Threshold0) ->
+ Threshold = is_iexprs_small_1(Guards, Threshold0),
+ is_iexprs_small_1(Body, Threshold);
+is_iexprs_small_2(#itry{body=Body,handler=Handler}, Threshold0) ->
+ Threshold = is_iexprs_small_1(Body, Threshold0),
+ is_iexprs_small_1(Handler, Threshold);
+is_iexprs_small_2(#imatch{guard=Guards}, Threshold) ->
+ is_iexprs_small_1(Guards, Threshold);
+is_iexprs_small_2(#icase{clauses=Clauses}, Threshold) ->
+ is_iexprs_small_1(Clauses, Threshold);
+is_iexprs_small_2(#ifun{clauses=Clauses}, Threshold) ->
+ is_iexprs_small_1(Clauses, Threshold);
+is_iexprs_small_2(#ireceive1{clauses=Clauses}, Threshold) ->
+ is_iexprs_small_1(Clauses, Threshold);
+is_iexprs_small_2(#ireceive2{clauses=Clauses}, Threshold) ->
+ is_iexprs_small_1(Clauses, Threshold);
+is_iexprs_small_2(#icatch{body=Body}, Threshold) ->
+ is_iexprs_small_1(Body, Threshold);
+is_iexprs_small_2(#iletrec{body=Body}, Threshold) ->
+ is_iexprs_small_1(Body, Threshold);
+is_iexprs_small_2(#iprotect{body=Body}, Threshold) ->
+ is_iexprs_small_1(Body, Threshold);
+is_iexprs_small_2(#iset{arg=Arg}, Threshold) ->
+ is_iexprs_small_2(Arg, Threshold);
+is_iexprs_small_2(_, Threshold) ->
+ Threshold.
+
%% expr_bin([ArgExpr], St) -> {[Arg],[PreExpr],St}.
%% Flatten the arguments of a bin. Do this straight left to right!
%% Note that ibinary needs to have its annotation wrapped in a #a{}
diff --git a/lib/compiler/test/trycatch_SUITE.erl b/lib/compiler/test/trycatch_SUITE.erl
index 630f96349b..9b4a7754b3 100644
--- a/lib/compiler/test/trycatch_SUITE.erl
+++ b/lib/compiler/test/trycatch_SUITE.erl
@@ -205,55 +205,125 @@ try_of_1(X) ->
{caught,{Class,Reason}}
end.
-
-
try_after(Conf) when is_list(Conf) ->
+ try_after_1(fun try_after_basic/2),
+ try_after_1(fun try_after_catch/2),
+ try_after_1(fun try_after_complex/2),
+ try_after_1(fun try_after_fun/2),
+ try_after_1(fun try_after_letrec/2),
+ try_after_1(fun try_after_protect/2),
+ try_after_1(fun try_after_receive/2),
+ try_after_1(fun try_after_receive_timeout/2),
+ try_after_1(fun try_after_try/2),
+ ok.
+
+try_after_1(TestFun) ->
{{ok,[some,value],undefined},finalized} =
- try_after_1({value,{ok,[some,value]}},finalized),
+ TestFun({value,{ok,[some,value]}},finalized),
{{error,badarith,undefined},finalized} =
- try_after_1({'div',{1,0}},finalized),
+ TestFun({'div',{1,0}},finalized),
{{error,badarith,undefined},finalized} =
- try_after_1({'add',{1,a}},finalized),
+ TestFun({'add',{1,a}},finalized),
{{error,badarg,undefined},finalized} =
- try_after_1({'abs',a},finalized),
+ TestFun({'abs',a},finalized),
{{error,[the,{reason}],undefined},finalized} =
- try_after_1({error,[the,{reason}]},finalized),
+ TestFun({error,[the,{reason}]},finalized),
{{throw,{thrown,[reason]},undefined},finalized} =
- try_after_1({throw,{thrown,[reason]}},finalized),
+ TestFun({throw,{thrown,[reason]}},finalized),
{{exit,{exited,{reason}},undefined},finalized} =
- try_after_1({exit,{exited,{reason}}},finalized),
+ TestFun({exit,{exited,{reason}}},finalized),
{{error,function_clause,undefined},finalized} =
- try_after_1(function_clause,finalized),
+ TestFun(function_clause,finalized),
ok =
- try try_after_1({'add',{1,1}}, finalized)
+ try
+ TestFun({'add',{1,1}}, finalized)
catch
error:{try_clause,2} -> ok
- end,
+ end,
finalized = erase(try_after),
ok =
- try try foo({exit,[reaso,{n}]})
- after put(try_after, finalized)
+ try
+ try
+ foo({exit,[reaso,{n}]})
+ after
+ put(try_after, finalized)
end
catch
exit:[reaso,{n}] -> ok
end,
ok.
-try_after_1(X, Y) ->
+-define(TRY_AFTER_TESTCASE(Block),
erase(try_after),
Try =
try foo(X) of
- {ok,Value} -> {ok,Value,get(try_after)}
+ {ok,Value} -> {ok,Value,get(try_after)}
catch
- Reason -> {throw,Reason,get(try_after)};
- error:Reason -> {error,Reason,get(try_after)};
- exit:Reason -> {exit,Reason,get(try_after)}
+ Reason -> {throw,Reason,get(try_after)};
+ error:Reason -> {error,Reason,get(try_after)};
+ exit:Reason -> {exit,Reason,get(try_after)}
after
- put(try_after, Y)
+ Block,
+ put(try_after, Y)
end,
- {Try,erase(try_after)}.
-
+ {Try,erase(try_after)}).
+
+try_after_basic(X, Y) ->
+ ?TRY_AFTER_TESTCASE(ok).
+
+try_after_catch(X, Y) ->
+ ?TRY_AFTER_TESTCASE((catch put(try_after, Y))).
+
+try_after_complex(X, Y) ->
+ %% Large 'after' block, going above the threshold for wrapper functions.
+ ?TRY_AFTER_TESTCASE(case get(try_after) of
+ unreachable_0 -> dummy:unreachable_0();
+ unreachable_1 -> dummy:unreachable_1();
+ unreachable_2 -> dummy:unreachable_2();
+ unreachable_3 -> dummy:unreachable_3();
+ unreachable_4 -> dummy:unreachable_4();
+ unreachable_5 -> dummy:unreachable_5();
+ unreachable_6 -> dummy:unreachable_6();
+ unreachable_7 -> dummy:unreachable_7();
+ unreachable_8 -> dummy:unreachable_8();
+ unreachable_9 -> dummy:unreachable_9();
+ _ -> put(try_after, Y)
+ end).
+
+try_after_fun(X, Y) ->
+ ?TRY_AFTER_TESTCASE((fun() -> ok end)()).
+
+try_after_letrec(X, Y) ->
+ List = lists:duplicate(100, ok),
+ ?TRY_AFTER_TESTCASE([L || L <- List]).
+
+try_after_protect(X, Y) ->
+ ?TRY_AFTER_TESTCASE(case get(try_after) of
+ N when element(52, N) < 32 -> ok;
+ _ -> ok
+ end).
+
+try_after_receive(X, Y) ->
+ Ref = make_ref(),
+ self() ! Ref,
+ ?TRY_AFTER_TESTCASE(receive
+ Ref -> Ref
+ end).
+try_after_receive_timeout(X, Y) ->
+ Ref = make_ref(),
+ self() ! Ref,
+ ?TRY_AFTER_TESTCASE(receive
+ Ref -> Ref
+ after 1000 -> ok
+ end).
+
+try_after_try(X, Y) ->
+ ?TRY_AFTER_TESTCASE(try
+ put(try_after, Y)
+ catch
+ _ -> ok
+ end).
-ifdef(begone).
--
2.26.2