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

openSUSE Build Service is sponsored by