File 2743-v3_core-Raise-clearer-exceptions-when-generators-are.patch of Package erlang

From a6ecaf8b937449b6f6de67efaee6b2460fffc8ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Fri, 6 Nov 2020 07:36:50 +0100
Subject: [PATCH 3/5] v3_core: Raise clearer exceptions when generators are bad

Raise a `{bad_generator,Generator}` exception when a generator is bad
instead of some ad-hoc exception. To implement this in a clean way,
the calculation of size of binary comprehension will be moved out of
`v3_core` and into a new SSA-based pass.
---
 lib/compiler/src/Makefile               |   1 +
 lib/compiler/src/beam_kernel_to_ssa.erl |  22 +-
 lib/compiler/src/beam_ssa_bc_size.erl   | 593 ++++++++++++++++++++++++
 lib/compiler/src/beam_ssa_opt.erl       |   7 +-
 lib/compiler/src/beam_ssa_type.erl      |  22 +-
 lib/compiler/src/beam_types.erl         |   6 +
 lib/compiler/src/cerl_inline.erl        |   5 +-
 lib/compiler/src/compile.erl            |   1 +
 lib/compiler/src/compiler.app.src       |   1 +
 lib/compiler/src/v3_core.erl            | 280 ++---------
 lib/compiler/test/beam_except_SUITE.erl |   2 +-
 lib/compiler/test/bs_bincomp_SUITE.erl  | 101 +++-
 lib/compiler/test/lc_SUITE.erl          |  36 +-
 lib/compiler/test/misc_SUITE.erl        |  12 +
 14 files changed, 787 insertions(+), 302 deletions(-)
 create mode 100644 lib/compiler/src/beam_ssa_bc_size.erl

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,17 +771,17 @@ 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(Op, As, [#k_var{name=Dst0}], St0) when is_atom(Op) ->
+internal_cg(Anno, Op, As, [#k_var{name=Dst0}], St0) when is_atom(Op) ->
     %% This behaves like a function call.
     {Dst,St} = new_ssa_var(Dst0, St0),
     Args = ssa_args(As, 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(Op, As, [], St0) when is_atom(Op) ->
+internal_cg(Anno, Op, As, [], St0) when is_atom(Op) ->
     %% This behaves like a function call.
     {Dst,St} = new_ssa_var('@ssa_ignored', St0),
     Args = ssa_args(As, St),
-    Set = #b_set{op=Op,dst=Dst,args=Args},
+    Set = #b_set{anno=Anno,op=Op,dst=Dst,args=Args},
     {[Set],St}.
 
 bif_cg(Bif, As0, [#k_var{name=Dst0}], Le, St0) ->
@@ -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}],
@@ -470,6 +474,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;
            (_) -> false
         end,
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,249 +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) ->
-    %% Single generator.
-    bc_gen_size_1(Q, EVs, #c_literal{val=1}, [], St);
-bc_gen_size(_, _, _) ->
-    %% There are multiple generators (or there are filters).
-    %% To avoid introducing unbound variables in the size
-    %% calculation when one generator references a
-    %% variable bound by a previous generator, we will
-    %% not do any size calculation. This issue will be
-    %% handled in a cleaner way in OTP 24.
-    throw(impossible).
-
-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,47 @@ mixed(Config) when is_list(Config) ->
 
     <<1>> = cs_default(<< <<X>> || L <- [[1]], X <- L >>),
 
+    %% 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}],
-- 
2.26.2

openSUSE Build Service is sponsored by