diff options
-rw-r--r-- | lib/compiler/src/Makefile | 1 | ||||
-rw-r--r-- | lib/compiler/src/beam_kernel_to_ssa.erl | 22 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_bc_size.erl | 593 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_opt.erl | 7 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_type.erl | 22 | ||||
-rw-r--r-- | lib/compiler/src/beam_types.erl | 6 | ||||
-rw-r--r-- | lib/compiler/src/cerl_inline.erl | 5 | ||||
-rw-r--r-- | lib/compiler/src/compile.erl | 1 | ||||
-rw-r--r-- | lib/compiler/src/compiler.app.src | 1 | ||||
-rw-r--r-- | lib/compiler/src/v3_core.erl | 280 | ||||
-rw-r--r-- | lib/compiler/test/beam_except_SUITE.erl | 2 | ||||
-rw-r--r-- | lib/compiler/test/bs_bincomp_SUITE.erl | 101 | ||||
-rw-r--r-- | lib/compiler/test/lc_SUITE.erl | 36 | ||||
-rw-r--r-- | lib/compiler/test/misc_SUITE.erl | 12 |
14 files changed, 787 insertions, 302 deletions
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile index 712f795601..f4600c35f8 100644 --- a/lib/compiler/src/Makefile +++ b/lib/compiler/src/Makefile @@ -60,6 +60,7 @@ MODULES = \ beam_opcodes \ beam_peep \ beam_ssa \ + beam_ssa_bc_size \ beam_ssa_bool \ beam_ssa_bsm \ beam_ssa_codegen \ diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl index 43834cb145..b4ba15a7d3 100644 --- a/lib/compiler/src/beam_kernel_to_ssa.erl +++ b/lib/compiler/src/beam_kernel_to_ssa.erl @@ -697,8 +697,8 @@ enter_cg(Func, As0, Le, St0) -> %% bif_cg(#k_bif{}, Le,State) -> {[Ainstr],State}. %% Generate code for a guard BIF or primop. -bif_cg(#k_bif{op=#k_internal{name=Name},args=As,ret=Rs}, _Le, St) -> - internal_cg(Name, As, Rs, St); +bif_cg(#k_bif{anno=A,op=#k_internal{name=Name},args=As,ret=Rs}, _Le, St) -> + internal_cg(line_anno(A), Name, As, Rs, St); bif_cg(#k_bif{op=#k_remote{mod=#k_literal{val=erlang},name=#k_literal{val=Name}}, args=As,ret=Rs}, Le, St) -> bif_cg(Name, As, Rs, Le, St). @@ -706,7 +706,7 @@ bif_cg(#k_bif{op=#k_remote{mod=#k_literal{val=erlang},name=#k_literal{val=Name}} %% internal_cg(Bif, [Arg], [Ret], Le, State) -> %% {[Ainstr],State}. -internal_cg(raise, As, [#k_var{name=Dst0}], St0) -> +internal_cg(_Anno, raise, As, [#k_var{name=Dst0}], St0) -> Args = ssa_args(As, St0), {Dst,St} = new_ssa_var(Dst0, St0), Resume = #b_set{op=resume,dst=Dst,args=Args}, @@ -724,13 +724,13 @@ internal_cg(raise, As, [#k_var{name=Dst0}], St0) -> Is = [Resume,make_uncond_branch(Fail),#cg_unreachable{}], {Is,St} end; -internal_cg(recv_peek_message, [], [#k_var{name=Succeeded0}, - #k_var{name=Dst0}], St0) -> +internal_cg(_Anno, recv_peek_message, [], [#k_var{name=Succeeded0}, + #k_var{name=Dst0}], St0) -> {Dst,St1} = new_ssa_var(Dst0, St0), St = new_succeeded_value(Succeeded0, Dst, St1), Set = #b_set{op=peek_message,dst=Dst,args=[]}, {[Set],St}; -internal_cg(recv_wait_timeout, As, [#k_var{name=Succeeded0}], St0) -> +internal_cg(_Anno, recv_wait_timeout, As, [#k_var{name=Succeeded0}], St0) -> case ssa_args(As, St0) of [#b_literal{val=0}] -> %% If beam_ssa_opt is run (which is default), the @@ -771,19 +771,19 @@ internal_cg(recv_wait_timeout, As, [#k_var{name=Succeeded0}], St0) -> Set = #b_set{op=wait_timeout,dst=Wait,args=Args}, {[Set|Succ],St} end; -internal_cg(Op0, As, [#k_var{name=Dst0}], St0) when is_atom(Op0) -> +internal_cg(Anno, Op0, As, [#k_var{name=Dst0}], St0) when is_atom(Op0) -> %% This behaves like a function call. {Dst,St} = new_ssa_var(Dst0, St0), Args = ssa_args(As, St), Op = fix_op(Op0, St), - Set = #b_set{op=Op,dst=Dst,args=Args}, + Set = #b_set{anno=Anno,op=Op,dst=Dst,args=Args}, {[Set],St}; -internal_cg(Op0, As, [], St0) when is_atom(Op0) -> +internal_cg(Anno, Op0, As, [], St0) when is_atom(Op0) -> %% This behaves like a function call. {Dst,St} = new_ssa_var('@ssa_ignored', St0), Args = ssa_args(As, St), Op = fix_op(Op0, St), - Set = #b_set{op=Op,dst=Dst,args=Args}, + Set = #b_set{anno=Anno,op=Op,dst=Dst,args=Args}, {[Set],St}. fix_op(make_fun, #cg{no_make_fun3=true}) -> old_make_fun; @@ -1153,6 +1153,8 @@ cg_size_bif({Name,Src}, FailCtx, St0) -> cg_size_add(#b_literal{val=0}, Val, #b_literal{val=1}, _FailCtx, St) -> {Val,[],St}; +cg_size_add(#b_literal{val=A}, #b_literal{val=B}, #b_literal{val=U}, _FailCtx, St) -> + {#b_literal{val=A+B*U},[],St}; cg_size_add(A, B, Unit, FailCtx, St0) -> {Dst,St1} = new_ssa_var('@ssa_sum', St0), {TestIs,St} = make_succeeded(Dst, FailCtx, St1), diff --git a/lib/compiler/src/beam_ssa_bc_size.erl b/lib/compiler/src/beam_ssa_bc_size.erl new file mode 100644 index 0000000000..2c3be55bc6 --- /dev/null +++ b/lib/compiler/src/beam_ssa_bc_size.erl @@ -0,0 +1,593 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% Try to calculate the exact size of bitstring produced by a binary +%% comprehension such as: +%% +%% << <<X:16>> || <<X:4>> <= Bs >> +%% +%% For this example, the size of the resulting binary (rounded up to +%% the nearest number of bytes) will be: +%% +%% ((bit_size(Bs) div 4) * 16 + 7) div 8 +%% +%% If the exact size can't be determined, such as for this comprehension: +%% +%% << <<X:16>> || <<X:4>> <= Bs, X =/= 15 >> +%% +%% the default size of 256 bytes will be used as starting size of +%% the writable binary. +%% + +-module(beam_ssa_bc_size). +-export([opt/1]). + +-import(lists, [any/2,reverse/1,sort/1]). + +-include("beam_ssa_opt.hrl"). + +-spec opt(st_map()) -> st_map(). + +opt(StMap) -> + opt(maps:keys(StMap), StMap). + +opt([Id|Ids], StMap0) -> + StMap = opt_function(Id, StMap0), + opt(Ids, StMap); +opt([], StMap) -> StMap. + +opt_function(Id, StMap) -> + #opt_st{ssa=Linear0,cnt=Count0} = OptSt0 = map_get(Id, StMap), + try opt_blks(Linear0, StMap, unchanged, Count0, []) of + {Linear,Count} -> + OptSt = OptSt0#opt_st{ssa=Linear,cnt=Count}, + StMap#{Id := OptSt}; + none -> + StMap + catch + Class:Error:Stack -> + #b_local{name=#b_literal{val=Name},arity=Arity} = Id, + io:fwrite("Function: ~w/~w\n", [Name,Arity]), + erlang:raise(Class, Error, Stack) + end. + +opt_blks([{L,#b_blk{is=Is}=Blk}|Blks], StMap, AnyChange, Count0, Acc0) -> + case Is of + [#b_set{op=bs_init_writable,dst=Dst}] -> + Bs = #{st_map => StMap, Dst => {writable,#b_literal{val=0}}}, + try opt_writable(Bs, L, Blk, Blks, Count0, Acc0) of + {Acc,Count} -> + opt_blks(Blks, StMap, changed, Count, Acc) + catch + throw:not_possible -> + opt_blks(Blks, StMap, AnyChange, Count0, [{L,Blk}|Acc0]) + end; + _ -> + opt_blks(Blks, StMap, AnyChange, Count0, [{L,Blk}|Acc0]) + end; +opt_blks([], _StMap, changed, Count, Acc) -> + {reverse(Acc),Count}; +opt_blks([], _StMap, unchanged, _Count, _Acc) -> + none. + +opt_writable(Bs0, L, Blk, Blks, Count0, Acc0) -> + case {Blk,Blks} of + {#b_blk{last=#b_br{succ=Next,fail=Next}}, + [{Next,#b_blk{is=[#b_set{op=call,args=[_|Args],dst=Dst}=Call|_]}}|_]} -> + ArgTypes = maps:from_list([{Arg,{arg,Arg}} || Arg <- Args]), + Bs = maps:merge(ArgTypes, Bs0), + Result = map_get(Dst, call_size_func(Call, Bs)), + {Expr,Annos} = make_expr_tree(Result), + cg_size_calc(Expr, L, Blk, Annos, Count0, Acc0); + {_,_} -> + throw(not_possible) + end. + +%%% +%%% Traverse the SSA code of the binary comprehension functions to +%%% figure out the exact size for the writable binary. This algorithm +%%% is similar to how types are determined by beam_ssa_type, but here +%%% we only care about how many bits are matched of from the generators +%%% and how many bits are appended to the writable binary. +%%% + +call_size_func(#b_set{anno=Anno,op=call,args=[Name|Args],dst=Dst}, Bs) -> + StMap = map_get(st_map, Bs), + case StMap of + #{Name := #opt_st{ssa=Linear,args=Params}} -> + NewBs0 = setup_call_bs(Params, Args, Bs, #{}), + case any(fun({writable,_}) -> true; + (_) -> false + end, maps:values(NewBs0)) of + false -> + %% Since the writable binary is not passed to the called function, + %% it can't have any effect on the size of the writable binary + %% and there is no need to analyze it. + Bs#{Dst => any}; + true -> + NewBs = NewBs0#{Name => self, st_map => StMap}, + Map0 = #{0 => NewBs}, + Result = calc_size(Linear, Map0), + Bs#{Dst => Result} + end; + #{} -> + case Name of + #b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=error}, + arity=1} -> + capture_anno(Anno, Dst, Args, Bs#{Dst => exception}); + _ -> + Bs#{Dst => any} + end + end. + +capture_anno(Anno, Dst, [ErrorTerm], Bs) -> + case get_value(ErrorTerm, Bs) of + {tuple,Elements} -> + Ts = [get_value(E, Bs) || E <- Elements], + capture_anno_1(Anno, Dst, Ts, Bs); + _ -> + Bs + end. + +capture_anno_1(Anno, Dst, [{nil_or_bad,Generator}|_], Bs) -> + Bs#{Dst => {generator_anno,{Generator,Anno}}}; +capture_anno_1(Anno, Dst, [{arg,Generator}|_], Bs) -> + Bs#{Dst => {generator_anno,{Generator,Anno}}}; +capture_anno_1(Anno, Dst, [_|T], Bs) -> + capture_anno_1(Anno, Dst, T, Bs); +capture_anno_1(_, _, [], Bs) -> + Bs. + +setup_call_bs([V|Vs], [A0|As], OldBs, NewBs) -> + A = case get_value(A0, OldBs) of + #b_literal{}=Lit -> {arg,Lit}; + {writable,#b_literal{val=0}}=Wr -> Wr; + {arg,_}=Arg -> Arg; + _ -> any + end, + setup_call_bs(Vs, As, OldBs, NewBs#{V => A}); +setup_call_bs([], [], #{}, NewBs) -> NewBs. + +calc_size([{L,#b_blk{is=Is,last=Last}}|Blks], Map0) -> + case maps:take(L, Map0) of + {Bs0,Map1} -> + Bs1 = calc_size_is(Is, Bs0), + Map2 = update_successors(Last, Bs1, Map1), + case get_ret(Last, Bs1) of + none -> + calc_size(Blks, Map2); + Ret -> + %% Save information about the function returned. + Map = Map2#{L => Ret}, + calc_size(Blks, Map) + end; + error -> + %% This block is unreachable. + calc_size(Blks, Map0) + end; +calc_size([], Map) -> + case sort(maps:values(Map)) of + [{call,_}=Call,{generator_anno,GenAnno}] -> + {Call,GenAnno}; + _ -> + throw(not_possible) + end. + +get_ret(#b_ret{arg=Arg}, Bs) -> + case get_value(Arg, Bs) of + exception -> + none; + {writable,#b_literal{val=0}} -> + none; + {generator_anno,_}=GenAnno -> + GenAnno; + Ret -> + Ret + end; +get_ret(_, _) -> none. + +update_successors(#b_br{bool=Bool,succ=Succ,fail=Fail}, Bs0, Map0) -> + case get_value(Bool, Bs0) of + #b_literal{val=true} -> + update_successor(Succ, Bs0, Map0); + {succeeded,Var} -> + Map = update_successor(Succ, Bs0, Map0), + update_successor(Fail, maps:remove(Var, Bs0), Map); + {'if',Var,TrueType,FalseType} -> + Bs = maps:remove(Bool, Bs0), + Map = update_successor(Succ, Bs#{Var => TrueType}, Map0), + update_successor(Fail, Bs#{Var => FalseType}, Map); + any -> + Map = update_successor(Succ, Bs0#{Bool := #b_literal{val=true}}, Map0), + update_successor(Fail, Bs0#{Bool := #b_literal{val=false}}, Map) + end; +update_successors(#b_switch{}, _Bs, _Map) -> + %% A switch implies a filter, which means that we cannot calculate the + %% exact size. + throw(not_possible); +update_successors(#b_ret{}, _Bs, Map) -> Map. + +update_successor(?EXCEPTION_BLOCK, _Bs, Map) -> + Map; +update_successor(L, Bs, Map) -> + case Map of + #{L := OldBs} -> + Map#{L := join_bs(OldBs, Bs)}; + #{} -> + Map#{L => Bs} + end. + +calc_size_is([I|Is], Bs0) -> + Bs = calc_size_instr(I, Bs0), + calc_size_is(Is, Bs); +calc_size_is([], Bs) -> Bs. + +calc_size_instr(#b_set{op=bs_add,args=[A,B,U],dst=Dst}, Bs) -> + %% We must make sure that the value of bs_add only depends on literals + %% and arguments passed from the function that created the writable + %% binary. + case {get_value(A, Bs),get_arg_value(B, Bs)} of + {#b_literal{}=Lit,Val} -> + Bs#{Dst => {expr,{{bif,'+'},[Lit,{{bif,'*'},[Val,U]}]}}}; + {{expr,Expr},Val} -> + Bs#{Dst => {expr,{{bif,'+'},[Expr,{{bif,'*'},[Val,U]}]}}}; + {_,_} -> + %% The value depends on a variable of which we know nothing. + Bs#{Dst => any} + end; +calc_size_instr(#b_set{op=bs_init,args=[#b_literal{val=private_append}, + Writable,Size,Unit], + dst=Dst}, Bs) -> + case get_value(Size, Bs) of + {arg,SizeOrigin} -> + Expr = {{bif,'*'},[SizeOrigin,Unit]}, + update_writable(Dst, Writable, Expr, Bs); + #b_literal{} -> + Expr = {{bif,'*'},[Size,Unit]}, + update_writable(Dst, Writable, Expr, Bs); + {expr,Expr} -> + update_writable(Dst, Writable, Expr, Bs); + _ -> + Bs#{Dst => any} + end; +calc_size_instr(#b_set{op=bs_match,args=[_Type,Ctx,_Flags, + Size,Unit],dst=Dst}, Bs) -> + case get_arg_value(Size, Bs) of + none -> + Bs#{Dst => any}; + Val -> + update_match(Dst, Ctx, {{safe,{bif,'*'}},[Val,Unit]}, Bs) + end; +calc_size_instr(#b_set{op=bs_start_match,args=[#b_literal{val=new},Arg],dst=Dst}, Bs) -> + case get_arg_value(Arg, Bs) of + none -> + Bs#{Dst => any}; + Val -> + Bs#{Dst => {match,{{bif,bit_size},[Val]},#b_literal{val=0}}} + end; +calc_size_instr(#b_set{op=call,args=[Name|Args],dst=Dst}=I, Bs) -> + if + is_map_key(Name, Bs) -> + Result0 = [get_value(A, Bs) || A <- Args], + Result = [Val || Val <- Result0, Val =/= any], + Bs#{Dst => {call,Result}}; + true -> + call_size_func(I, Bs) + end; +calc_size_instr(#b_set{op=get_tl,args=[Ctx],dst=Dst}, Bs) -> + update_match(Dst, Ctx, #b_literal{val=1}, Bs); +calc_size_instr(#b_set{op=is_nonempty_list,args=[Arg],dst=Dst}, Bs) -> + case get_arg_value(Arg, Bs) of + none -> + Bs#{Dst => any}; + Val -> + NumElements = {{bif,length},[Val]}, + Match = {match,NumElements,#b_literal{val=0}}, + NoMatch = {nil_or_bad,Val}, + Bs#{Dst => {'if',Arg,Match,NoMatch}} + end; +calc_size_instr(#b_set{op=put_tuple,args=Args,dst=Dst}, Bs) -> + Bs#{Dst => {tuple,Args}}; +calc_size_instr(#b_set{op={succeeded,_},args=[Arg],dst=Dst}, Bs) -> + Bs#{Dst => {succeeded,Arg}}; +calc_size_instr(#b_set{dst=Dst}, Bs) -> + Bs#{Dst => any}. + +update_writable(Dst, Writable, Expr, Bs) -> + case get_value(Writable, Bs) of + {writable,#b_literal{val=0}} -> + Bs#{Dst => {writable,Expr}}; + _ -> + Bs#{Dst => any} + end. + +update_match(Dst, Ctx, Increment, Bs) -> + case get_value(Ctx, Bs) of + {match,NumElements,Offset0} -> + Offset = {{bif,'+'},[Offset0,Increment]}, + Bs#{Dst => {match,NumElements,Offset}}; + _ -> + Bs#{Dst => any} + end. + +get_arg_value(#b_literal{}=Lit, _Bs) -> + Lit; +get_arg_value(Name, Bs) -> + case Bs of + #{Name := {arg,Val}} -> Val; + #{} -> none + end. + +get_value(Name, Bs) -> + case Bs of + #{Name := Value} -> Value; + #{} -> Name + end. + +join_bs(LHS, RHS) -> + if + map_size(LHS) < map_size(RHS) -> + join_bs_1(maps:keys(LHS), RHS, LHS); + true -> + join_bs_1(maps:keys(RHS), LHS, RHS) + end. + +%% Joins two maps of bindings, keeping the variables that are common to both maps. +join_bs_1([V | Vs], Bigger, Smaller) -> + case {Bigger, Smaller} of + {#{V := Same},#{V := Same}} -> + join_bs_1(Vs, Bigger, Smaller); + {#{V := _LHS},#{V := _RHS}} -> + join_bs_1(Vs, Bigger, Smaller#{V := any}); + {#{}, #{V := _}} -> + join_bs_1(Vs, Bigger, maps:remove(V, Smaller)) + end; +join_bs_1([], _Bigger, Smaller) -> Smaller. + +%%% +%%% Turn the result of the traversal of the SSA code into an expression tree. +%%% + +make_expr_tree({{call,Alloc0},GenAnno}) -> + {Alloc1,Annos} = make_expr_tree_list(Alloc0, none, none, [GenAnno]), + Alloc2 = opt_expr(Alloc1), + Alloc = round_up_to_byte_size(Alloc2), + {Alloc,maps:from_list(Annos)}; +make_expr_tree(_) -> + throw(not_possible). + +make_expr_tree_list([{{call,List},GenAnno}|T], Match, none, Annos0) -> + {BuildSize,Annos} = make_expr_tree_list(List, none, none, [GenAnno|Annos0]), + make_expr_tree_list(T, Match, BuildSize, Annos); +make_expr_tree_list([{match,NumItems,N}|T], none, BuildSize, Annos) -> + make_expr_tree_list(T, {NumItems,N}, BuildSize, Annos); +make_expr_tree_list([{writable,BuildSize}|T], Match, none, Annos) -> + make_expr_tree_list(T, Match, BuildSize, Annos); +make_expr_tree_list([_|T], Match, BuildSize, Annos) -> + make_expr_tree_list(T, Match, BuildSize, Annos); +make_expr_tree_list([], Match, BuildSize, Annos) + when Match =/= none, BuildSize =/= none -> + {NumItems,N} = Match, + Expr = {{bif,'*'},[{{safe,{bif,'div'}},[NumItems,N]},BuildSize]}, + {Expr,Annos}; +make_expr_tree_list([], _, _, Annos) -> + {none,Annos}. + +round_up_to_byte_size(Alloc0) -> + Alloc = case divisible_by_eight(Alloc0) of + true -> Alloc0; + false -> {{bif,'+'},[Alloc0,#b_literal{val=7}]} + end, + opt_expr({{bif,'div'},[Alloc,#b_literal{val=8}]}). + +divisible_by_eight({{bif,'*'},[Expr1,Expr2]}) -> + divisible_by_eight(Expr1) orelse divisible_by_eight(Expr2); +divisible_by_eight(#b_literal{val=Val}) when Val rem 8 =:= 0 -> + true; +divisible_by_eight(_) -> false. + +%%% +%%% Optimize an expression tree. +%%% + +opt_expr({Op,Args0}) -> + Args = opt_expr_args(Args0), + case literal_expr_args(Args, []) of + none -> + opt_expr_1(Op, Args); + LitArgs -> + Bif = case Op of + {safe,{bif,Bif0}} -> Bif0; + {bif,Bif0} -> Bif0 + end, + try apply(erlang, Bif, LitArgs) of + Result -> + #b_literal{val=Result} + catch + error:_ -> + opt_expr_1(Op, Args) + end + end; +opt_expr(none) -> none. + +opt_expr_1({safe,{bif,'div'}}=Op, Args) -> + case Args of + [Int,#b_literal{val=1}] -> + Int; + [_Int,#b_literal{val=N}] when N > 1 -> + opt_expr_1({bif,'div'}, Args); + [_,_] -> + {Op,Args} + end; +opt_expr_1({bif,'div'}=Op, [Numerator,#b_literal{val=Denominator}]=Args) -> + try + opt_expr_div(Numerator, Denominator) + catch + throw:not_possible -> + case Denominator band (Denominator - 1) of + 0 -> + %% The denominator is a power of two. + Shift = round(math:log2(Denominator)), + {{bif,'bsr'},[Numerator,#b_literal{val=Shift}]}; + _ -> + {Op,Args} + end + end; +opt_expr_1({bif,'*'}, [{{safe,_},_},#b_literal{val=0}=Zero]) -> + Zero; +opt_expr_1({bif,'*'}, [Factor,#b_literal{val=1}]) -> + Factor; +opt_expr_1(Op, Args) -> + {Op,Args}. + +opt_expr_div({{bif,'*'},[A,B]}, Denominator) -> + case B of + #b_literal{val=Factor} when Factor rem Denominator =:= 0 -> + {{bif,'*'},[A,#b_literal{val=Factor div Denominator}]}; + _ -> + {{bif,'*'},[A,opt_expr_div(B, Denominator)]} + end; +opt_expr_div(_, _) -> + throw(not_possible). + +opt_expr_args([A0|As]) -> + A = case A0 of + #b_literal{} -> A0; + #b_var{} -> A0; + _ -> opt_expr(A0) + end, + [A|opt_expr_args(As)]; +opt_expr_args([]) -> []. + +literal_expr_args([#b_literal{val=Val}|As], Acc) -> + literal_expr_args(As, [Val|Acc]); +literal_expr_args([_|_], _) -> + none; +literal_expr_args([], Acc) -> + reverse(Acc). + +%%% +%%% Given an expression tree, generate SSA code to calculate the number +%%% bytes to allocate for the writable binary. +%%% + +cg_size_calc(Expr, L, #b_blk{is=Is0}=Blk0, Annos, Count0, Acc0) -> + [InitWr] = Is0, + FailBlk0 = [], + {Acc1,Alloc,NextBlk,FailBlk,Count} = cg_size_calc_1(L, Expr, Annos, FailBlk0, Count0, Acc0), + Is = [InitWr#b_set{args=[Alloc]}], + Blk = Blk0#b_blk{is=Is}, + Acc = [{NextBlk,Blk}|FailBlk++Acc1], + {Acc,Count}. + +cg_size_calc_1(L, #b_literal{}=Alloc, _Annos, FailBlk, Count, Acc) -> + {Acc,Alloc,L,FailBlk,Count}; +cg_size_calc_1(L0, {Op0,Args0}, Annos, FailBlk0, Count0, Acc0) -> + {Args,Acc1,L,FailBlk1,Count1} = cg_atomic_args(Args0, L0, Annos, FailBlk0, Count0, Acc0, []), + {BadGenL,FailBlk,Count2} = cg_bad_generator(Args, Annos, FailBlk1, Count1), + {Dst,Count3} = new_var('@ssa_tmp', Count2), + case Op0 of + {safe,Op} -> + {OpDst,Count4} = new_var('@ssa_size', Count3), + {[OpSuccL,OpFailL,PhiL,NextL],Count5} = new_blocks(4, Count4), + I = #b_set{op=Op,args=Args,dst=OpDst}, + {Blk,Count} = cg_succeeded(I, OpSuccL, OpFailL, Count5), + JumpBlk = #b_blk{is=[],last=cg_br(PhiL)}, + PhiIs = [#b_set{op=phi, + args=[{OpDst,OpSuccL},{#b_literal{val=0},OpFailL}], + dst=Dst}], + PhiBlk = #b_blk{is=PhiIs,last=cg_br(NextL)}, + Acc = [{PhiL,PhiBlk},{OpSuccL,JumpBlk}, + {OpFailL,JumpBlk},{L,Blk}|Acc1], + {Acc,Dst,NextL,FailBlk,Count}; + _ -> + {NextBlkL,Count4} = new_block(Count3), + I = #b_set{op=Op0,args=Args,dst=Dst}, + {SuccBlk,Count} = cg_succeeded(I, NextBlkL, BadGenL, Count4), + Acc = [{L,SuccBlk}|Acc1], + {Acc,Dst,NextBlkL,FailBlk,Count} + end. + +cg_bad_generator([Arg|_], Annos, FailBlk, Count) -> + case Annos of + #{Arg := Anno} -> + cg_bad_generator_1(Anno, Arg, FailBlk, Count); + #{} -> + case FailBlk of + [{L,_}|_] -> + {L,FailBlk,Count}; + [] -> + cg_bad_generator_1(#{}, Arg, FailBlk, Count) + end + end. + +cg_bad_generator_1(Anno, Arg, FailBlk, Count0) -> + {L,Count1} = new_block(Count0), + {TupleDst,Count2} = new_var('@ssa_tuple', Count1), + {Ret,Count3} = new_var('@ssa_ret', Count2), + MFA = #b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=error}, + arity=1}, + TupleI = #b_set{op=put_tuple, + args=[#b_literal{val=bad_generator},Arg], + dst=TupleDst}, + CallI = #b_set{anno=Anno,op=call,args=[MFA,TupleDst],dst=Ret}, + Is = [TupleI,CallI], + Blk = #b_blk{is=Is,last=#b_ret{arg=Ret}}, + {L,[{L,Blk}|FailBlk],Count3}. + +cg_succeeded(#b_set{dst=OpDst}=I, Succ, Fail, Count0) -> + {Bool,Count} = new_var('@ssa_bool', Count0), + SuccI = #b_set{op={succeeded,guard},args=[OpDst],dst=Bool}, + Blk = #b_blk{is=[I,SuccI],last=#b_br{bool=Bool,succ=Succ,fail=Fail}}, + {Blk,Count}. + +cg_br(Target) -> + #b_br{bool=#b_literal{val=true},succ=Target,fail=Target}. + +cg_atomic_args([A|As], L, Annos, FailBlk0, Count0, BlkAcc0, Acc) -> + case A of + #b_literal{} -> + cg_atomic_args(As, L, Annos, FailBlk0, Count0, BlkAcc0, [A|Acc]); + #b_var{} -> + cg_atomic_args(As, L, Annos, FailBlk0, Count0, BlkAcc0, [A|Acc]); + none -> + throw(not_possible); + _ -> + {BlkAcc,Var,NextBlk,FailBlk,Count} = + cg_size_calc_1(L, A, Annos, FailBlk0, Count0, BlkAcc0), + cg_atomic_args(As, NextBlk, Annos, FailBlk, Count, BlkAcc, [Var|Acc]) + end; +cg_atomic_args([], NextBlk, _Annos, FailBlk, Count, BlkAcc, Acc) -> + {reverse(Acc),BlkAcc,NextBlk,FailBlk,Count}. + +new_var(Base, Count) -> + {#b_var{name={Base,Count}},Count+1}. + +new_blocks(N, Count) -> + new_blocks(N, Count, []). + +new_blocks(0, Count, Acc) -> + {Acc,Count}; +new_blocks(N, Count, Acc) -> + new_blocks(N - 1, Count + 1, [Count|Acc]). + +new_block(Count) -> + {Count,Count+1}. diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 3cd25ff273..325c6da682 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -263,7 +263,11 @@ prologue_passes(Opts) -> passes_1(Ps, Opts). module_passes(Opts) -> - Ps0 = [{ssa_opt_type_start, + Ps0 = [{ssa_opt_bc_size, + fun({StMap, FuncDb}) -> + {beam_ssa_bc_size:opt(StMap), FuncDb} + end}, + {ssa_opt_type_start, fun({StMap, FuncDb}) -> beam_ssa_type:opt_start(StMap, FuncDb) end}], @@ -471,6 +475,7 @@ ssa_opt_merge_blocks({#opt_st{ssa=Blocks0}=St, FuncDb}) -> ssa_opt_split_blocks({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) -> P = fun(#b_set{op={bif,element}}) -> true; (#b_set{op=call}) -> true; + (#b_set{op=bs_init_writable}) -> true; (#b_set{op=make_fun}) -> true; (#b_set{op=old_make_fun}) -> true; (_) -> false diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 8d7e6ad877..105ee12422 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -1323,15 +1323,27 @@ eval_type_test_bif(I, is_tuple, [Type]) -> eval_type_test_bif_1(I, Type, #t_tuple{}); eval_type_test_bif(I, Op, Types) -> case Types of - [#t_integer{},#t_integer{elements={0,0}}] - when Op =:= '+'; Op =:= '-'; Op =:= 'bor'; Op =:= 'bxor' -> + [#t_integer{},#t_integer{elements={0,0}}] when Op =:= 'bor'; Op =:= 'bxor' -> #b_set{args=[Result,_]} = I, Result; [#t_integer{},#t_integer{elements={0,0}}] when Op =:= '*'; Op =:= 'band' -> #b_literal{val=0}; - [#t_integer{},#t_integer{elements={1,1}}] when Op =:= '*'; Op =:= 'div' -> - #b_set{args=[Result,_]} = I, - Result; + [T,#t_integer{elements={0,0}}] when Op =:= '+'; Op =:= '-' -> + case beam_types:is_numerical_type(T) of + true -> + #b_set{args=[Result,_]} = I, + Result; + false -> + I + end; + [T,#t_integer{elements={1,1}}] when Op =:= '*'; Op =:= 'div' -> + case beam_types:is_numerical_type(T) of + true -> + #b_set{args=[Result,_]} = I, + Result; + false -> + I + end; [#t_integer{elements={LMin,LMax}},#t_integer{elements={RMin,RMax}}] -> case is_inequality_op(Op) of true -> diff --git a/lib/compiler/src/beam_types.erl b/lib/compiler/src/beam_types.erl index 5577fe79d8..48e720edd7 100644 --- a/lib/compiler/src/beam_types.erl +++ b/lib/compiler/src/beam_types.erl @@ -28,6 +28,7 @@ -export([meet/1, meet/2, join/1, join/2, subtract/2]). -export([is_boolean_type/1, + is_numerical_type/1, get_bs_matchable_unit/1, is_bs_matchable_type/1, get_singleton_value/1, @@ -464,6 +465,11 @@ is_boolean_type(#t_union{}=T) -> is_boolean_type(_) -> false. +-spec is_numerical_type(type()) -> boolean(). +is_numerical_type(#t_integer{}) -> true; +is_numerical_type(number) -> true; +is_numerical_type(_) -> false. + -spec set_tuple_element(Index, Type, Elements) -> Elements when Index :: pos_integer(), Type :: type(), diff --git a/lib/compiler/src/cerl_inline.erl b/lib/compiler/src/cerl_inline.erl index 8a2ea77b99..3f3d162302 100644 --- a/lib/compiler/src/cerl_inline.erl +++ b/lib/compiler/src/cerl_inline.erl @@ -903,9 +903,8 @@ i_fun(E, Ctxt, Ren, Env, S) -> %% side of each definition. i_letrec(E, Ctxt, Ren, Env, S) -> - %% We must turn off inlining if this `letrec' is specially - %% implemented. - NoInline = member(letrec_goto, get_ann(E)), + %% Test whether we must turn off inlining for this `letrec`. + NoInline = member(no_inline, get_ann(E)), %% Note that we pass an empty list for the auto-referenced %% (exported) functions here. diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 525db89cd5..9818005d33 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -2008,6 +2008,7 @@ pre_load() -> beam_opcodes, beam_peep, beam_ssa, + beam_ssa_bc_size, beam_ssa_bool, beam_ssa_bsm, beam_ssa_codegen, diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src index 5b18d91f6b..00db23ee20 100644 --- a/lib/compiler/src/compiler.app.src +++ b/lib/compiler/src/compiler.app.src @@ -36,6 +36,7 @@ beam_opcodes, beam_peep, beam_ssa, + beam_ssa_bc_size, beam_ssa_bool, beam_ssa_bsm, beam_ssa_codegen, diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 860976218a..a14ad988ae 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -82,7 +82,7 @@ -export([module/2,format_error/1]). -import(lists, [reverse/1,reverse/2,map/2,member/2,foldl/3,foldr/3,mapfoldl/3, - splitwith/2,keyfind/3,sort/1,foreach/2,droplast/1,last/1, + splitwith/2,keyfind/3,sort/1,droplast/1,last/1, duplicate/2]). -import(ordsets, [add_element/2,del_element/2,is_element/2, union/1,union/2,intersection/2,subtract/2]). @@ -1388,7 +1388,7 @@ lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno, F = #c_var{anno=LA,name={Name,1}}, Nc = #iapply{anno=GAnno,op=F,args=[Tail]}, {[FcVar,Var],St2} = new_vars(2, St1), - Fc = function_clause([FcVar], GA), + Fc = bad_generator([FcVar], FcVar, Arg), TailClause = #iclause{anno=LAnno,pats=[TailPat],guard=[],body=[Mc]}, Cs0 = case {AccPat,AccGuard} of {SkipPat,[]} -> @@ -1430,13 +1430,20 @@ lc_tq(Line, E0, [], Mc0, St0) -> bc_tq(Line, Exp, Qs0, St0) -> {BinVar,St1} = new_var(St0), - {Sz,SzPre,St2} = bc_initial_size(Exp, Qs0, St1), - {Qs,St3} = preprocess_quals(Line, Qs0, St2), - {E,BcPre,St} = bc_tq1(Line, Exp, Qs, BinVar, St3), - Pre = SzPre ++ - [#iset{var=BinVar, - arg=#iprimop{name=#c_literal{val=bs_init_writable}, - args=[Sz]}}] ++ BcPre, + {Qs1,St2} = preprocess_quals(Line, Qs0, St1), + {PrePre,Qs} = case Qs1 of + [#igen{arg={IgenPre,Arg}}=Igen|Igens] -> + {IgenPre,[Igen#igen{arg={[],Arg}}|Igens]}; + _ -> + {[],Qs1} + end, + {E,BcPre,St} = bc_tq1(Line, Exp, Qs, BinVar, St2), + InitialSize = #c_literal{val=256}, + Pre = PrePre ++ + [#iset{var=BinVar, + arg=#iprimop{anno=#a{anno=lineno_anno(Line, St)}, + name=#c_literal{val=bs_init_writable}, + args=[InitialSize]}}] ++ BcPre, {E,Pre,St}. bc_tq1(Line, E, [#igen{anno=GAnno, @@ -1451,7 +1458,7 @@ bc_tq1(Line, E, [#igen{anno=GAnno, {IgnoreVar,St4} = new_var(LA, St3), F = #c_var{anno=LA,name={Name,2}}, Nc = #iapply{anno=GAnno,op=F,args=[Tail,AccVar]}, - Fc = function_clause(FcVars, LA), + Fc = bad_generator(FcVars, hd(FcVars), Arg), TailClause = #iclause{anno=LAnno,pats=[TailPat,IgnoreVar],guard=[], body=[AccVar]}, Cs0 = case {AccPat,AccGuard} of @@ -1478,7 +1485,11 @@ bc_tq1(Line, E, [#igen{anno=GAnno, St5} end, Fun = #ifun{anno=LAnno,id=[],vars=Vars,clauses=Cs,fc=Fc}, - {#iletrec{anno=LAnno#a{anno=[list_comprehension|LA]},defs=[{{Name,2},Fun}], + + %% Inlining would disable the size calculation optimization for + %% bs_init_writable. + {#iletrec{anno=LAnno#a{anno=[list_comprehension,no_inline|LA]}, + defs=[{{Name,2},Fun}], body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg,Mc]}]}, [],St}; bc_tq1(Line, E, [#ifilter{}=Filter|Qs], Mc, St) -> @@ -1707,240 +1718,6 @@ list_gen_pattern(P0, Line, St) -> nomatch -> {nomatch,add_warning(Line, nomatch, St)} end. -%%% -%%% Generate code to calculate the initial size for -%%% the result binary in a binary comprehension. -%%% - -bc_initial_size(E0, Q, St0) -> - try - E = bin_bin_element(E0), - {ElemSzExpr,ElemSzPre,EVs,St1} = bc_elem_size(E, St0), - {V,St2} = new_var(St1), - {GenSzExpr,GenSzPre,St3} = bc_gen_size(Q, EVs, St2), - case ElemSzExpr of - #c_literal{val=ElemSz} when ElemSz rem 8 =:= 0 -> - NumBytesExpr = #c_literal{val=ElemSz div 8}, - BytesExpr = [#iset{var=V, - arg=bc_mul(GenSzExpr, NumBytesExpr)}], - {V,ElemSzPre++GenSzPre++BytesExpr,St3}; - _ -> - {[BitsV,PlusSevenV],St} = new_vars(2, St3), - BitsExpr = #iset{var=BitsV,arg=bc_mul(GenSzExpr, ElemSzExpr)}, - PlusSevenExpr = #iset{var=PlusSevenV, - arg=bc_add(BitsV, #c_literal{val=7})}, - Expr = #iset{var=V, - arg=bc_bsr(PlusSevenV, #c_literal{val=3})}, - {V,ElemSzPre++GenSzPre++ - [BitsExpr,PlusSevenExpr,Expr],St} - end - catch - throw:impossible -> - {#c_literal{val=256},[],St0}; - throw:nomatch -> - {#c_literal{val=1},[],St0} - end. - -bc_elem_size({bin,_,El}, St0) -> - case bc_elem_size_1(El, ordsets:new(), 0, []) of - {Bits,[]} -> - {#c_literal{val=Bits},[],[],St0}; - {Bits,Vars0} -> - [{U,V0}|Pairs] = sort(Vars0), - F = bc_elem_size_combine(Pairs, U, [V0], []), - Vs = [V || {_,#c_var{name=V}} <- Vars0], - {E,Pre,St} = bc_mul_pairs(F, #c_literal{val=Bits}, [], St0), - {E,Pre,Vs,St} - end; -bc_elem_size(_, _) -> - throw(impossible). - -bc_elem_size_1([{bin_element,_,{string,_,String},{integer,_,N},_}=El|Es], - DefVars, Bits, SizeVars) -> - U = get_unit(El), - bc_elem_size_1(Es, DefVars, Bits+U*N*length(String), SizeVars); -bc_elem_size_1([{bin_element,_,Expr,{integer,_,N},_}=El|Es], - DefVars0, Bits, SizeVars) -> - U = get_unit(El), - DefVars = bc_elem_size_def_var(Expr, DefVars0), - bc_elem_size_1(Es, DefVars, Bits+U*N, SizeVars); -bc_elem_size_1([{bin_element,_,Expr,{var,_,Src},_}=El|Es], - DefVars0, Bits, SizeVars) -> - case ordsets:is_element(Src, DefVars0) of - false -> - U = get_unit(El), - DefVars = bc_elem_size_def_var(Expr, DefVars0), - bc_elem_size_1(Es, DefVars, Bits, [{U,#c_var{name=Src}}|SizeVars]); - true -> - throw(impossible) - end; -bc_elem_size_1([_|_], _, _, _) -> - throw(impossible); -bc_elem_size_1([], _DefVars, Bits, SizeVars) -> - {Bits,SizeVars}. - -bc_elem_size_def_var({var,_,Var}, DefVars) -> - ordsets:add_element(Var, DefVars); -bc_elem_size_def_var(_Expr, DefVars) -> - DefVars. - -bc_elem_size_combine([{U,V}|T], U, UVars, Acc) -> - bc_elem_size_combine(T, U, [V|UVars], Acc); -bc_elem_size_combine([{U,V}|T], OldU, UVars, Acc) -> - bc_elem_size_combine(T, U, [V], [{OldU,UVars}|Acc]); -bc_elem_size_combine([], U, Uvars, Acc) -> - [{U,Uvars}|Acc]. - -bc_mul_pairs([{U,L0}|T], E0, Pre, St0) -> - {AddExpr,AddPre,St1} = bc_add_list(L0, St0), - {[V1,V2],St} = new_vars(2, St1), - Set1 = #iset{var=V1,arg=bc_mul(AddExpr, #c_literal{val=U})}, - Set2 = #iset{var=V2,arg=bc_add(V1, E0)}, - bc_mul_pairs(T, V2, [Set2,Set1|reverse(AddPre, Pre)], St); -bc_mul_pairs([], E, Pre, St) -> - {E,reverse(Pre),St}. - -bc_add_list([V], St) -> - {V,[],St}; -bc_add_list([H|T], St) -> - bc_add_list_1(T, [], H, St). - -bc_add_list_1([H|T], Pre, E, St0) -> - {Var,St} = new_var(St0), - Set = #iset{var=Var,arg=bc_add(H, E)}, - bc_add_list_1(T, [Set|Pre], Var, St); -bc_add_list_1([], Pre, E, St) -> - {E,reverse(Pre),St}. - -bc_gen_size(Q, EVs, St) -> - bc_gen_size_1(Q, EVs, #c_literal{val=1}, [], St). - -bc_gen_size_1([{generate,L,El,Gen}|Qs], EVs, E0, Pre0, St0) -> - bc_verify_non_filtering(El, EVs), - case Gen of - {var,_,ListVar} -> - Lanno = lineno_anno(L, St0), - {LenVar,St1} = new_var(St0), - Set = #iset{var=LenVar, - arg=#icall{anno=#a{anno=Lanno}, - module=#c_literal{val=erlang}, - name=#c_literal{val=length}, - args=[#c_var{name=ListVar}]}}, - {E,Pre,St} = bc_gen_size_mul(E0, LenVar, [Set|Pre0], St1), - bc_gen_size_1(Qs, EVs, E, Pre, St); - _ -> - %% The only expressions we handle is literal lists. - Len = bc_list_length(Gen, 0), - {E,Pre,St} = bc_gen_size_mul(E0, #c_literal{val=Len}, Pre0, St0), - bc_gen_size_1(Qs, EVs, E, Pre, St) - end; -bc_gen_size_1([{b_generate,_,El0,Gen0}|Qs], EVs, E0, Pre0, St0) -> - El = bin_bin_element(El0), - Gen = bin_bin_element(Gen0), - bc_verify_non_filtering(El, EVs), - {MatchSzExpr,Pre1,_,St1} = bc_elem_size(El, St0), - Pre2 = reverse(Pre1, Pre0), - {ResVar,St2} = new_var(St1), - {BitSizeExpr,Pre3,St3} = bc_gen_bit_size(Gen, Pre2, St2), - Div = #iset{var=ResVar,arg=bc_div(BitSizeExpr, - MatchSzExpr)}, - Pre4 = [Div|Pre3], - {E,Pre,St} = bc_gen_size_mul(E0, ResVar, Pre4, St3), - bc_gen_size_1(Qs, EVs, E, Pre, St); -bc_gen_size_1([], _, E, Pre, St) -> - {E,reverse(Pre),St}; -bc_gen_size_1(_, _, _, _, _) -> - throw(impossible). - -bin_bin_element({bin,L,El}) -> - {bin,L,[bin_element(E) || E <- El]}; -bin_bin_element(Other) -> Other. - -bc_gen_bit_size({var,L,V}, Pre0, St0) -> - Lanno = lineno_anno(L, St0), - {SzVar,St} = new_var(St0), - Pre = [#iset{var=SzVar, - arg=#icall{anno=#a{anno=Lanno}, - module=#c_literal{val=erlang}, - name=#c_literal{val=bit_size}, - args=[#c_var{name=V}]}}|Pre0], - {SzVar,Pre,St}; -bc_gen_bit_size({bin,_,_}=Bin, Pre, St) -> - {#c_literal{val=bc_bin_size(Bin)},Pre,St}; -bc_gen_bit_size(_, _, _) -> - throw(impossible). - -bc_verify_non_filtering({bin,_,Els}, EVs) -> - foreach(fun({bin_element,_,{var,_,V},_,_}) -> - case member(V, EVs) of - true -> throw(impossible); - false -> ok - end; - (_) -> throw(impossible) - end, Els); -bc_verify_non_filtering({var,_,V}, EVs) -> - case member(V, EVs) of - true -> throw(impossible); - false -> ok - end; -bc_verify_non_filtering(_, _) -> - throw(impossible). - -bc_list_length({string,_,Str}, Len) -> - Len + length(Str); -bc_list_length({cons,_,_,T}, Len) -> - bc_list_length(T, Len+1); -bc_list_length({nil,_}, Len) -> - Len; -bc_list_length(_, _) -> - throw(impossible). - -bc_bin_size({bin,_,Els}) -> - bc_bin_size_1(Els, 0). - -bc_bin_size_1([{bin_element,_,{string,_,String},{integer,_,Sz},_}=El|Els], N) -> - U = get_unit(El), - bc_bin_size_1(Els, N+U*Sz*length(String)); -bc_bin_size_1([{bin_element,_,_,{integer,_,Sz},_}=El|Els], N) -> - U = get_unit(El), - bc_bin_size_1(Els, N+U*Sz); -bc_bin_size_1([], N) -> N; -bc_bin_size_1(_, _) -> throw(impossible). - -bc_gen_size_mul(#c_literal{val=1}, E, Pre, St) -> - {E,Pre,St}; -bc_gen_size_mul(E1, E2, Pre, St0) -> - {V,St} = new_var(St0), - {V,[#iset{var=V,arg=bc_mul(E1, E2)}|Pre],St}. - -bc_mul(E1, #c_literal{val=1}) -> - E1; -bc_mul(E1, E2) -> - #icall{module=#c_literal{val=erlang}, - name=#c_literal{val='*'}, - args=[E1,E2]}. - -bc_div(E1, E2) -> - #icall{module=#c_literal{val=erlang}, - name=#c_literal{val='div'}, - args=[E1,E2]}. - -bc_add(E1, #c_literal{val=0}) -> - E1; -bc_add(E1, E2) -> - #icall{module=#c_literal{val=erlang}, - name=#c_literal{val='+'}, - args=[E1,E2]}. - -bc_bsr(E1, E2) -> - #icall{module=#c_literal{val=erlang}, - name=#c_literal{val='bsr'}, - args=[E1,E2]}. - -get_unit({bin_element,_,_,_,Flags}) -> - {unit,U} = keyfind(unit, 1, Flags), - U. - %% is_guard_test(Expression) -> true | false. %% Test if a general expression is a guard test. %% @@ -2020,7 +1797,7 @@ force_safe(Ce, St0) -> case is_safe(Ce) of true -> {Ce,[],St0}; false -> - {V,St1} = new_var(St0), + {V,St1} = new_var(get_lineno_anno(Ce), St0), {V,[#iset{var=V,arg=Ce}],St1} end. @@ -2284,6 +2061,11 @@ new_vars_1(N, Anno, St0, Vs) when N > 0 -> new_vars_1(N-1, Anno, St1, [V|Vs]); new_vars_1(0, _, St, Vs) -> {Vs,St}. +bad_generator(Ps, Generator, Arg) -> + Anno = get_anno(Arg), + Tuple = ann_c_tuple(Anno, [#c_literal{val=bad_generator},Generator]), + fail_clause(Ps, Anno, Tuple). + function_clause(Ps, LineAnno) -> fail_clause(Ps, LineAnno, ann_c_tuple(LineAnno, [#c_literal{val=function_clause}|Ps])). @@ -3157,7 +2939,7 @@ lexpr(#c_receive{clauses=[],timeout=Timeout0,action=Action}, St0) -> Fun = #c_fun{vars=[],body=TimeoutLet}, - Letrec = #c_letrec{anno=[letrec_goto], + Letrec = #c_letrec{anno=[letrec_goto,no_inline], defs=[{LoopFun,Fun}], body=ApplyLoop}, @@ -3223,7 +3005,7 @@ lexpr(#c_receive{anno=RecvAnno,clauses=Cs0,timeout=Timeout0,action=Action}, St0) body=PeekCase}, Fun = #c_fun{vars=[],body=PeekLet}, - Letrec = #c_letrec{anno=[letrec_goto], + Letrec = #c_letrec{anno=[letrec_goto,no_inline], defs=[{LoopFun,Fun}], body=ApplyLoop}, @@ -3294,7 +3076,7 @@ split_letify([], [], Body, [_|_]=VsAcc, [_|_]=ArgAcc) -> split_case_letrec(#c_fun{anno=FunAnno0}=Fun0, Body, #core{gcount=C}=St0) -> FunAnno = [compiler_generated|FunAnno0], Fun = Fun0#c_fun{anno=FunAnno}, - Anno = [letrec_goto], + Anno = [letrec_goto,no_inline], DefFunName = goto_func(C), Letrec = #c_letrec{anno=Anno,defs=[{#c_var{name=DefFunName},Fun}],body=Body}, St = St0#core{gcount=C+1}, diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl index 1a9406b5ea..2540a0a911 100644 --- a/lib/compiler/test/beam_except_SUITE.erl +++ b/lib/compiler/test/beam_except_SUITE.erl @@ -52,7 +52,7 @@ end_per_group(_GroupName, Config) -> multiple_allocs(_Config) -> {'EXIT',{{badmatch,#{true:=[p]}},_}} = (catch could(pda, 0.0, {false,true}, {p})), - {'EXIT',{function_clause,_}} = (catch place(lee)), + {'EXIT',{{bad_generator,0},_}} = (catch place(lee)), {'EXIT',{{badmatch,wanted},_}} = (catch conditions()), ok. diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl index ddd804f133..b9d3d3eb4a 100644 --- a/lib/compiler/test/bs_bincomp_SUITE.erl +++ b/lib/compiler/test/bs_bincomp_SUITE.erl @@ -27,7 +27,7 @@ byte_aligned/1,bit_aligned/1,extended_byte_aligned/1, extended_bit_aligned/1,mixed/1,filters/1,trim_coverage/1, nomatch/1,sizes/1,general_expressions/1,matched_out_size/1, - no_generator/1]). + no_generator/1,zero_pattern/1]). -include_lib("common_test/include/ct.hrl"). @@ -37,7 +37,7 @@ all() -> [byte_aligned, bit_aligned, extended_byte_aligned, extended_bit_aligned, mixed, filters, trim_coverage, nomatch, sizes, general_expressions, matched_out_size, - no_generator]. + no_generator, zero_pattern]. groups() -> []. @@ -69,8 +69,7 @@ bit_aligned(Config) when is_list(Config) -> cs_init(), <<$a:7,$b:7,$c:7,$d:7,$e:7,$f:7,$g:7>> = cs(<< <<(X+32):7>> || <<X>> <= <<"ABCDEFG">> >>), - <<"ABCDEFG">> = - cs(<< <<(X-32)>> || <<X:7>> <= <<$a:7,$b:7,$c:7,$d:7,$e:7,$f:7,$g:7>> >>), + <<"ABCDEFG">> = cs(<< <<(X-32)>> || <<X:7>> <= id(<<$a:7,$b:7,$c:7,$d:7,$e:7,$f:7,$g:7>>) >>), <<1:31/little,2:31/little,3:31/little,4:31/little>> = cs(<< <<X:31/little>> || <<X:31>> <= <<1:31,2:31,3:31,4:31>> >>), <<1:31/little,2:31/little,3:31/little,4:31/little>> = @@ -131,11 +130,48 @@ mixed(Config) when is_list(Config) -> %% OTP-16899: Nested binary comprehensions would fail to load. <<0,1,0,2,0,3,99>> = mixed_nested([1,2,3]), + + %% The compiler would crash in v3_kernel. + <<42:32,75:32,253:32,(42 bsl 8 bor 75):32>> = + cs_default(mixed_size(id([8,16]), <<42,75,253>>)), + + silly_lc_bc(5), + + gen_data(0), + gen_data(256), + gen_data(512), + cs_end(). mixed_nested(L) -> << << << << E:16 >> || E <- L >> || true >>/binary, 99:(id(8))>>. +mixed_size(List, Bin) -> + << <<X:32>> || Size <- List, <<X:Size>> <= Bin >>. + +silly_lc_bc(N) when N > 0 -> + Bin = iolist_to_binary(silly_lc(N)), + Bin = cs(silly_bc(N)), + Size = byte_size(Bin), + Size = 5 bsl N, + silly_lc_bc(N - 1); +silly_lc_bc(_) -> ok. + +silly_bc(0) -> + <<0, 1, 2, 3, 4>>; +silly_bc(N) -> + << <<X, X>> || <<X>> <= silly_bc(N - 1) >>. + +silly_lc(0) -> + [0, 1, 2, 3, 4]; +silly_lc(N) -> + [[X, X] || X <- silly_lc(N - 1)]. + +gen_data(Size) -> + Data = cs(<< <<C>> || C <- lists:seq(0, Size-1) >>), + Data = << <<C>> || _ <- lists:seq(1, Size div 256), + C <- lists:seq(0, 255) >>. + filters(Config) when is_list(Config) -> cs_init(), <<"BDF">> = @@ -162,6 +198,7 @@ filters(Config) when is_list(Config) -> [] -> false; [_|_] -> true end >>), + <<77,42>> = cs_default(<< <<B>> || <<B>> <- [<<77>>,<<1,2>>,<<42>>] >>), %% Filtering by a non-matching pattern. <<"abd">> = cs_default(<< <<X:8>> || @@ -217,8 +254,16 @@ nomatch(Config) when is_list(Config) -> NaN = <<(-1):32>>, <<>> = << <<"a">> || <<_:32/float>> <= NaN >>, + <<1:32,2:32,3:32>> = nomatch_1(<<1,2,3>>, 8), + <<>> = nomatch_1(<<1,2,3>>, bad), + + <<>> = << <<>> || <<_:8>> <= <<>> >>, + ok. +nomatch_1(Bin, Size) -> + << <<X:32>> || <<X:Size>> <= Bin >>. + sizes(Config) when is_list(Config) -> cs_init(), Fun0 = fun(List) -> @@ -311,7 +356,7 @@ sizes(Config) when is_list(Config) -> <<17:9,19:9>> = Fun12(<<17:6,19:6>>, 9, 6), Fun13 = fun(Sz) -> - cs_default(<< <<C:8>> || <<C:4>> <= <<1:4,2:4,3:4,0:Sz>> >>) + cs(<< <<C:8>> || <<C:4>> <= <<1:4,2:4,3:4,0:Sz>> >>) end, <<1,2,3>> = Fun13(0), <<1,2,3,0>> = Fun13(4), @@ -323,6 +368,12 @@ sizes(Config) when is_list(Config) -> <<0:3>> = cs_default(<< <<0:S>> || S <- [0,1,2] >>), <<0:3>> = cs_default(<< <<0:S>> || <<S>> <= <<0,1,2>> >>), + Fun14 = fun(L, B) -> + cs_default(<< <<X:32>> || Size <- L, <<X:Size>> <= B >>) + end, + <<$a:32,$b:32,$c:32,($a bsl 8 bor $b):32>> = Fun14([8,16], <<"abc">>), + <<$a:32,$b:32,$c:32>> = Fun14([8,bad], <<"abc">>), + {'EXIT',_} = (catch << <<C:4>> || <<C:8>> <= {1,2,3} >>), cs_end(), @@ -334,8 +385,8 @@ sizes(Config) when is_list(Config) -> general_expressions(_) -> cs_init(), - <<1,2,3>> = cs_default(<< begin <<1,2,3>> end || _ <- [1] >>), - <<"abc">> = cs_default(<< begin <<"abc">> end || _ <- [1] >>), + <<1,2,3>> = cs(<< begin <<1,2,3>> end || _ <- [1] >>), + <<"abc">> = cs(<< begin <<"abc">> end || _ <- [1] >>), <<1,2,3>> = cs_default(<< begin I = <<(I0+1)>>, id(I) @@ -411,6 +462,27 @@ no_generator(_Config) -> ok. +zero_pattern(Config) -> + case is_atom(Config) of + true -> + %% Patterns that match zero bits loops forever, so we must + %% be careful not to execute them. + _ = << <<>> || <<>> <= <<>> >>, + _ = << <<42>> || <<>> <= <<42>> >>, + _ = << + <<>> || + <<>> <= << >>, + <<3:back>> <= << >> + >>, + _ = << + <<>> || + <<>> <= <<>>, + <<>> <- << <<>> || area >> + >>; + false -> + ok + end. + cs_init() -> erts_debug:set_internal_state(available_internal_state, true), ok. @@ -421,9 +493,18 @@ cs_end() -> %% Verify that the allocated size is exact (rounded up to the nearest byte). cs(Bin) -> - ByteSize = byte_size(Bin), - {refc_binary,ByteSize,{binary,ByteSize},_} = - erts_debug:get_internal_state({binary_info,Bin}), + case ?MODULE of + bs_bincomp_no_opt_SUITE -> + ok; + bs_bincomp_no_ssa_opt_SUITE -> + ok; + bs_bincomp_post_opt_SUITE -> + ok; + _ -> + ByteSize = byte_size(Bin), + {refc_binary,ByteSize,{binary,ByteSize},_} = + erts_debug:get_internal_state({binary_info,Bin}) + end, Bin. %% Verify that the allocated size of the binary is the default size. diff --git a/lib/compiler/test/lc_SUITE.erl b/lib/compiler/test/lc_SUITE.erl index c80b7cc59e..47579145ef 100644 --- a/lib/compiler/test/lc_SUITE.erl +++ b/lib/compiler/test/lc_SUITE.erl @@ -106,32 +106,30 @@ basic(Config) when is_list(Config) -> {'EXIT',_} = (catch [X || X <- L1, list_to_atom(X) == dum]), [] = [X || X <- L1, X+1 < 2], {'EXIT',_} = (catch [X || X <- L1, odd(X)]), - fc([x], catch [E || E <- id(x)]), + {'EXIT',{{bad_generator,x},_}} = (catch [E || E <- id(x)]), %% Make sure that line numbers point out the generator. case ?MODULE of lc_inline_SUITE -> ok; _ -> - {'EXIT',{function_clause, + {'EXIT',{{bad_generator,a}, [{?MODULE,_,_, [{file,"bad_lc.erl"},{line,4}]}|_]}} = - (catch bad_generator(a)), - {'EXIT',{function_clause, + (catch id(bad_generator(a))), + + {'EXIT',{{bad_generator,a}, [{?MODULE,_,_, - [{file,"bad_lc.erl"},{line,4}]}|_]}} = - (catch bad_generator([a|b])), - {'EXIT',{badarg, - [{erlang,length,_,_}, - {?MODULE,bad_generator_bc,1, [{file,"bad_lc.erl"},{line,7}]}|_]}} = - (catch bad_generator_bc(a)), - {'EXIT',{badarg, - [{erlang,length,_,_}, - {?MODULE,bad_generator_bc,1, - [{file,"bad_lc.erl"},{line,7}]}|_]}} = - (catch bad_generator_bc([a|b])) + (catch id(bad_generator_bc(a))), + + %% List comprehensions with improper lists. + {'EXIT',{{bad_generator,d}, + [{?MODULE,_,_, + [{file,"bad_lc.erl"},{line,4}]}|_]}} = + (catch bad_generator(id([a,b,c|d]))) end, + ok. tuple_list() -> @@ -267,14 +265,6 @@ do_effect(Lc, L) -> id(I) -> I. -fc(Args, {'EXIT',{function_clause,[{?MODULE,_,Args,_}|_]}}) -> ok; -fc(Args, {'EXIT',{function_clause,[{?MODULE,_,Arity,_}|_]}}) - when length(Args) =:= Arity -> - true = test_server:is_native(?MODULE); -fc(Args, {'EXIT',{{case_clause,ActualArgs},_}}) - when ?MODULE =:= lc_inline_SUITE -> - Args = tuple_to_list(ActualArgs). - -file("bad_lc.erl", 1). bad_generator(List) -> %Line 2 [I || %Line 3 diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl index bc07099915..4e577311cc 100644 --- a/lib/compiler/test/misc_SUITE.erl +++ b/lib/compiler/test/misc_SUITE.erl @@ -210,6 +210,9 @@ silly_coverage(Config) when is_list(Config) -> BadSSABlocks,0}]}, expect_error(fun() -> beam_ssa_opt:module(BadSSAOpt, []) end), + %% beam_ssa_bc_size + cover_beam_ssa_bc_size(1), + %% beam_ssa_lint, beam_ssa_pp {error,[{_,Errors}]} = beam_ssa_lint:module(bad_ssa_lint_input(), []), _ = [io:put_chars(Mod:format_error(Reason)) || @@ -282,6 +285,15 @@ silly_coverage(Config) when is_list(Config) -> ok. +cover_beam_ssa_bc_size(20) -> + ok; +cover_beam_ssa_bc_size(N) -> + BcSizeKey = {b_local,{b_literal,name},1}, + %% Try different sizes for the opt_st record. + OptSt = erlang:make_tuple(N, whatever, [{1,opt_st}]), + expect_error(fun() -> beam_ssa_bc_size:opt(#{BcSizeKey => OptSt}) end), + cover_beam_ssa_bc_size(N + 1). + bad_ssa_lint_input() -> {b_module,#{},t, [{a,1},{b,1},{c,1},{module_info,0},{module_info,1}], |