From d9470c07acc8d9b5f18676d852a0ff1707788e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 30 Oct 2018 06:25:40 +0100 Subject: beam_except: Generalize translation to func_info instructions The `beam_except` pass replaces some calls to `erlang:error/1` or `erlang:error/2` with specialized instructions in order to reduce the size of the BEAM code. In functions that do binary matching, `beam_except` would fail to translate the instructions that generate a `function_clause` exception. Here is an example: bsum(<>, Acc) -> bsum(T, Acc+H); bsum(<<>>, Acc) -> Acc. The BEAM code that generates the `function_clause` exception looks like this: {label,4}. {test_heap,2,3}. {put_list,{x,1},nil,{x,1}}. %% The following two instructions prevents the translation. {bs_set_position,{x,2},{x,0}}. {bs_get_tail,{x,2},{x,0},3}. {test_heap,2,2}. {put_list,{x,0},{x,1},{x,1}}. {move,{atom,function_clause},{x,0}}. {line,...}. {call_ext,2,{extfunc,erlang,error,2}}. Make the translation of `function_clause` exceptions smarter, so that the following code will be produced: {label,4}. {bs_set_position,{x,2},{x,0}}. {bs_get_tail,{x,2},{x,0},3}. {jump,{f,1}}. %Jump to `func_info` instruction. (This issue was noticed when looking at the code generated by https://github.com/tomas-abrahamsson/gpb.) --- lib/compiler/src/beam_except.erl | 96 +++++++++++++++++++++------------ lib/compiler/test/beam_except_SUITE.erl | 8 +++ 2 files changed, 71 insertions(+), 33 deletions(-) diff --git a/lib/compiler/src/beam_except.erl b/lib/compiler/src/beam_except.erl index 98831d87a7..49bfb5606f 100644 --- a/lib/compiler/src/beam_except.erl +++ b/lib/compiler/src/beam_except.erl @@ -31,7 +31,7 @@ %%% erlang:error(function_clause, Args) => jump FuncInfoLabel %%% --import(lists, [reverse/1,seq/2]). +-import(lists, [reverse/1,seq/2,splitwith/2]). -spec module(beam_utils:module_code(), [compile:option()]) -> {'ok',beam_utils:module_code()}. @@ -74,13 +74,13 @@ translate([I|Is], St, Acc) -> translate([], _, Acc) -> reverse(Acc). -translate_1(Ar, I, Is, St, [{line,_}=Line|Acc1]=Acc0) -> - case dig_out(Ar, Acc1) of +translate_1(Ar, I, Is, #st{arity=Arity}=St, [{line,_}=Line|Acc1]=Acc0) -> + case dig_out(Ar, Arity, Acc1) of no -> translate(Is, St, [I|Acc0]); - {yes,{function_clause,Arity},Acc2} -> + {yes,function_clause,Acc2} -> case {Line,St} of - {{line,Loc},#st{lbl=Fi,loc=Loc,arity=Arity}} -> + {{line,Loc},#st{lbl=Fi,loc=Loc}} -> Instr = {jump,{f,Fi}}, translate(Is, St, [Instr|Acc2]); {_,_} -> @@ -92,9 +92,13 @@ translate_1(Ar, I, Is, St, [{line,_}=Line|Acc1]=Acc0) -> translate(Is, St, [Instr,Line|Acc2]) end. -dig_out(Ar, [{kill,_}|Is]) -> - dig_out(Ar, Is); -dig_out(1, [{block,Bl0}|Is]) -> +dig_out(1, _Arity, Is) -> + dig_out(Is); +dig_out(2, Arity, Is) -> + dig_out_fc(Arity, Is); +dig_out(_, _, _) -> no. + +dig_out([{block,Bl0}|Is]) -> case dig_out_block(reverse(Bl0)) of no -> no; {yes,What,[]} -> @@ -102,12 +106,7 @@ dig_out(1, [{block,Bl0}|Is]) -> {yes,What,Bl} -> {yes,What,[{block,Bl}|Is]} end; -dig_out(2, [{block,Bl}|Is]) -> - case dig_out_block_fc(Bl) of - no -> no; - {yes,What} -> {yes,What,Is} - end; -dig_out(_, _) -> no. +dig_out(_) -> no. dig_out_block([{set,[{x,0}],[{atom,if_clause}],move}]) -> {yes,if_end,[]}; @@ -141,33 +140,64 @@ fix_block_1([{set,[],[],{alloc,Live,{F1,F2,Needed0,F3}}}|Is], Words) -> fix_block_1([I|Is], Words) -> [I|fix_block_1(Is, Words)]. -dig_out_block_fc([{set,[],[],{alloc,Live,_}}|Bl]) -> - Regs = maps:from_list([{{x,X},{arg,X}} || X <- seq(0, Live-1)]), - dig_out_fc(Bl, Regs); -dig_out_block_fc(_) -> no. -dig_out_fc([{set,[Dst],[Hd,Tl],put_list}|Is], Regs0) -> +dig_out_fc(Arity, Is0) -> + Regs0 = maps:from_list([{{x,X},{arg,X}} || X <- seq(0, Arity-1)]), + {Is,Acc0} = splitwith(fun({label,_}) -> false; + ({test,_,_,_}) -> false; + (_) -> true + end, Is0), + {Regs,Acc} = dig_out_fc_1(reverse(Is), Regs0, Acc0), + case is_fc(Arity, Regs) of + true -> + {yes,function_clause,Acc}; + false -> + no + end. + +dig_out_fc_1([{block,Bl}|Is], Regs0, Acc) -> + Regs = dig_out_fc_block(Bl, Regs0), + dig_out_fc_1(Is, Regs, Acc); +dig_out_fc_1([{bs_set_position,_,_}=I|Is], Regs, Acc) -> + dig_out_fc_1(Is, Regs, [I|Acc]); +dig_out_fc_1([{bs_get_tail,_,_,Live}=I|Is], Regs0, Acc) -> + Regs = prune_xregs(Live, Regs0), + dig_out_fc_1(Is, Regs, [I|Acc]); +dig_out_fc_1([_|_], _Regs, _Acc) -> + {#{},[]}; +dig_out_fc_1([], Regs, Acc) -> + {Regs,Acc}. + +dig_out_fc_block([{set,[],[],{alloc,Live,_}}|Is], Regs0) -> + Regs = prune_xregs(Live, Regs0), + dig_out_fc_block(Is, Regs); +dig_out_fc_block([{set,[Dst],[Hd,Tl],put_list}|Is], Regs0) -> Regs = Regs0#{Dst=>{cons,get_reg(Hd, Regs0),get_reg(Tl, Regs0)}}, - dig_out_fc(Is, Regs); -dig_out_fc([{set,[Dst],[Src],move}|Is], Regs0) -> + dig_out_fc_block(Is, Regs); +dig_out_fc_block([{set,[Dst],[Src],move}|Is], Regs0) -> Regs = Regs0#{Dst=>get_reg(Src, Regs0)}, - dig_out_fc(Is, Regs); -dig_out_fc([{set,_,_,_}|_], _Regs) -> - %% Unknown instruction. It is not a function_clause error. - no; -dig_out_fc([], Regs) -> + dig_out_fc_block(Is, Regs); +dig_out_fc_block([{set,_,_,_}|_], _Regs) -> + %% Unknown instruction. Fail. + #{}; +dig_out_fc_block([], Regs) -> Regs. + +prune_xregs(Live, Regs) -> + maps:filter(fun({x,X}, _) -> X < Live end, Regs). + +is_fc(Arity, Regs) -> case Regs of #{{x,0}:={atom,function_clause},{x,1}:=Args} -> - dig_out_fc_1(Args, 0); + is_fc_1(Args, 0) =:= Arity; #{} -> - no + false end. -dig_out_fc_1({cons,{arg,I},T}, I) -> - dig_out_fc_1(T, I+1); -dig_out_fc_1(nil, I) -> - {yes,{function_clause,I}}; -dig_out_fc_1(_, _) -> no. +is_fc_1({cons,{arg,I},T}, I) -> + is_fc_1(T, I+1); +is_fc_1(nil, I) -> + I; +is_fc_1(_, _) -> -1. get_reg(R, Regs) -> case Regs of diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl index 2b4a780899..da61931136 100644 --- a/lib/compiler/test/beam_except_SUITE.erl +++ b/lib/compiler/test/beam_except_SUITE.erl @@ -83,6 +83,11 @@ coverage(_) -> (catch bar(x)), {'EXIT',{{case_clause,{1}},[{?MODULE,bar,1,[File,{line,9}]}|_]}} = (catch bar(0)), + + Self = self(), + {'EXIT',{{strange,Self},[{?MODULE,foo,[any],[File,{line,14}]}|_]}} = + (catch foo(any)), + ok. -file("fake.erl", 1). @@ -96,3 +101,6 @@ bar(X) -> %Line 8 case {X+1} of %Line 9 1 -> ok %Line 10 end. %Line 11 +%% Cover collection code for function_clause exceptions. +foo(A) -> %Line 13 + error({strange,self()}, [A]). %Line 14 -- cgit v1.2.3