From 4fe3f3b8be4825726fab6d4ea7515adf5b5df1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 6 Oct 2017 08:52:32 +0200 Subject: Slightly speed up try/catch The try_end and try_case instructions are implemented the same way (try_case is translated to try_end by the loader). We can do better than that. We know that try_case will only be executed when an exception has been caught. Therefore, we know that x(0) is the non-value and that x(1) through x(3) need to be shifted down to x(0) through x(2). There is no need to test x(0) before shifting down. try_end does not need the register shifting code at all. --- erts/emulator/beam/instrs.tab | 18 ++++++++++-------- erts/emulator/beam/ops.tab | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/erts/emulator/beam/instrs.tab b/erts/emulator/beam/instrs.tab index 07cc4bd527..20d356a81d 100644 --- a/erts/emulator/beam/instrs.tab +++ b/erts/emulator/beam/instrs.tab @@ -857,8 +857,7 @@ catch(Y, Fail) { } catch_end(Y) { - c_p->catches--; - make_blank($Y); + $try_end($Y); if (is_non_value(r(0))) { c_p->fvalue = NIL; if (x(1) == am_throw) { @@ -888,12 +887,15 @@ catch_end(Y) { try_end(Y) { c_p->catches--; make_blank($Y); - if (is_non_value(r(0))) { - c_p->fvalue = NIL; - r(0) = x(1); - x(1) = x(2); - x(2) = x(3); - } +} + +try_case(Y) { + $try_end($Y); + ASSERT(is_non_value(r(0))); + c_p->fvalue = NIL; + r(0) = x(1); + x(1) = x(2); + x(2) = x(3); } try_case_end(Src) { diff --git a/erts/emulator/beam/ops.tab b/erts/emulator/beam/ops.tab index 4a915c7762..7a2c39b3a8 100644 --- a/erts/emulator/beam/ops.tab +++ b/erts/emulator/beam/ops.tab @@ -188,7 +188,8 @@ catch_end y # Try/catch. try Y F => catch Y F -try_case Y => try_end Y + +try_case y try_end y %cold -- cgit v1.2.3 From 34c8b06de52ac81e2938e93ed7a219d7611c16f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 6 Oct 2017 09:06:24 +0200 Subject: Optimize try/catch that ignores the return value If a try/catch is used to ignore the potential exception caused by some expression with a side effect such as: try side_effect() catch _Class:_Reason -> ok end, . . . the generated code will be wasteful: try YReg TryLabel %% call side_effect() here try_end Yreg jump GoodLabel TryLabel: try_case YReg %% try_case has set up registers as follows: %% x(0) -> error | exit | throw %% x(1) -> reason %% x(2) -> raw stack trace data GoodLabel: %% This code does not use any x registers. There will be two code paths that both end up at GoodLabel, and the try_case instruction will set up registers that are never used. We can do better by replacing try_case with try_end to obtain this code: try YReg TryLabel %% call side_effect() here try_end Yreg jump GoodLabel TryLabel: try_end YReg GoodLabel: The jump optimizer (beam_jump) can further optimize this code like this: try YReg TryLabel %% call side_effect() here TryLabel: try_end YReg --- lib/compiler/src/beam_utils.erl | 10 ++++++++-- lib/hipe/icode/hipe_beam_to_icode.erl | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl index a4c65397df..00f396c246 100644 --- a/lib/compiler/src/beam_utils.erl +++ b/lib/compiler/src/beam_utils.erl @@ -825,8 +825,14 @@ live_opt([{select,_,Src,Fail,List}=I|Is], Regs0, D, Acc) -> Regs1 = x_live([Src], Regs0), Regs = live_join_labels([Fail|List], D, Regs1), live_opt(Is, Regs, D, [I|Acc]); -live_opt([{try_case,_}=I|Is], _, D, Acc) -> - live_opt(Is, live_call(1), D, [I|Acc]); +live_opt([{try_case,Y}=I|Is], Regs0, D, Acc) -> + Regs = live_call(1), + case Regs0 of + 0 -> + live_opt(Is, Regs, D, [{try_end,Y}|Acc]); + _ -> + live_opt(Is, live_call(1), D, [I|Acc]) + end; live_opt([{loop_rec,_Fail,_Dst}=I|Is], _, D, Acc) -> live_opt(Is, 0, D, [I|Acc]); live_opt([timeout=I|Is], _, D, Acc) -> diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl index 2abecf7f18..167f5c67bb 100644 --- a/lib/hipe/icode/hipe_beam_to_icode.erl +++ b/lib/hipe/icode/hipe_beam_to_icode.erl @@ -2296,6 +2296,12 @@ split_code([First|Code], Label, Instr) -> split_code([Instr|Code], Label, Instr, Prev, As) when Prev =:= Label -> split_code_final(Code, As); % drop both label and instruction +split_code([{icode_end_try}|_]=Code, Label, {try_case,_}, Prev, As) + when Prev =:= Label -> + %% The try_case has been replaced with try_end as an optimization. + %% Keep this instruction, since it might be the only try_end instruction + %% for this try/catch block. + split_code_final(Code, As); % drop label split_code([Other|_Code], Label, Instr, Prev, _As) when Prev =:= Label -> ?EXIT({missing_instr_after_label, Label, Instr, [Other, Prev | _As]}); split_code([Other|Code], Label, Instr, Prev, As) -> -- cgit v1.2.3 From 5fa3ac7e10f45ad433ac6a44f32f9b5e96b56fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 6 Oct 2017 09:22:00 +0200 Subject: Do the receive optimization in the presence of try/catch Improve the receive optimization to be able to handle code such as this: Ref = make_ref(), try side_effect() catch _:_ -> ok end, receive %% Clauses that all match Ref. . . . end --- lib/compiler/src/beam_receive.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/compiler/src/beam_receive.erl b/lib/compiler/src/beam_receive.erl index 1403e1e05e..468460eedf 100644 --- a/lib/compiler/src/beam_receive.erl +++ b/lib/compiler/src/beam_receive.erl @@ -207,6 +207,8 @@ opt_update_regs({label,Lbl}, R, L) -> %% A catch label for a previously seen catch instruction is OK. {R,L} end; +opt_update_regs({'try',_,{f,Lbl}}, R, L) -> + {R,gb_sets:add(Lbl, L)}; opt_update_regs({try_end,_}, R, L) -> {R,L}; opt_update_regs({line,_}, R, L) -> -- cgit v1.2.3 From 119713b4d9ad6649aa5f1d0bf492e2f35a0ced01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 6 Oct 2017 09:25:53 +0200 Subject: Optimize a catch whose return value is ignored Rewrite a catch expression like this: catch side_effect(), ... to: try side_effect() catch _:_ -> ok end, ... A try/catch is more efficient since no stack trace will be built when an exception occurs. --- lib/compiler/src/sys_core_fold.erl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index f3f315935a..df880ff784 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -418,6 +418,15 @@ expr(#c_call{module=M0,name=N0}=Call0, Ctxt, Sub) -> expr(#c_primop{args=As0}=Prim, _, Sub) -> As1 = expr_list(As0, value, Sub), Prim#c_primop{args=As1}; +expr(#c_catch{anno=Anno,body=B}, effect, Sub) -> + %% When the return value of the 'catch' is ignored, we can replace it + %% with a try/catch to avoid building a stack trace when an exception + %% occurs. + Var = #c_var{name='catch_value'}, + Evs = [#c_var{name='Class'},#c_var{name='Reason'},#c_var{name='Stk'}], + Try = #c_try{anno=Anno,arg=B,vars=[Var],body=Var, + evars=Evs,handler=void()}, + expr(Try, effect, Sub); expr(#c_catch{body=B0}=Catch, _, Sub) -> %% We can remove catch if the value is simple B1 = body(B0, value, Sub), -- cgit v1.2.3