File 2172-compiler-Suppress-stack-traces-for-throw-1-when-they.patch of Package erlang

From 1067ef391c492683e91f7d4d7a4a96fbd2a26b05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20H=C3=B6gberg?= <john@erlang.org>
Date: Mon, 28 Sep 2020 14:33:31 +0200
Subject: [PATCH 2/2] compiler: Suppress stack traces for throw/1 when they're
 not used

---
 lib/compiler/src/Makefile            |   2 +
 lib/compiler/src/beam_ssa.erl        |   2 +-
 lib/compiler/src/beam_ssa_throw.erl  | 495 +++++++++++++++++++++++++++
 lib/compiler/src/beam_ssa_type.erl   |  30 +-
 lib/compiler/src/compile.erl         |   5 +
 lib/compiler/src/compiler.app.src    |   1 +
 lib/compiler/test/trycatch_SUITE.erl |  73 ++++
 7 files changed, 596 insertions(+), 12 deletions(-)
 create mode 100644 lib/compiler/src/beam_ssa_throw.erl

diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile
index b1531ac985..d4eacfa154 100644
--- a/lib/compiler/src/Makefile
+++ b/lib/compiler/src/Makefile
@@ -71,6 +71,7 @@ MODULES =  \
 	beam_ssa_pre_codegen \
 	beam_ssa_recv \
 	beam_ssa_share \
+	beam_ssa_throw \
 	beam_ssa_type \
 	beam_kernel_to_ssa \
 	beam_trim \
@@ -210,6 +211,7 @@ $(EBIN)/beam_ssa_pp.beam: beam_ssa.hrl
 $(EBIN)/beam_ssa_pre_codegen.beam: beam_ssa.hrl
 $(EBIN)/beam_ssa_recv.beam: beam_ssa.hrl
 $(EBIN)/beam_ssa_share.beam: beam_ssa.hrl
+$(EBIN)/beam_ssa_throw.beam: beam_ssa.hrl
 $(EBIN)/beam_ssa_type.beam: beam_ssa.hrl beam_types.hrl
 $(EBIN)/beam_types.beam: beam_types.hrl
 $(EBIN)/beam_validator.beam: beam_types.hrl
diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl
index aa971b5f57..be251d0c78 100644
--- a/lib/compiler/src/beam_ssa.erl
+++ b/lib/compiler/src/beam_ssa.erl
@@ -50,7 +50,7 @@
               var_name/0,var_base/0,literal_value/0,
               op/0,anno/0,block_map/0,dominator_map/0,
               rename_map/0,rename_proplist/0,usage_map/0,
-              definition_map/0]).
+              definition_map/0,predecessor_map/0]).
 
 -include("beam_ssa.hrl").
 
diff --git a/lib/compiler/src/beam_ssa_throw.erl b/lib/compiler/src/beam_ssa_throw.erl
new file mode 100644
index 0000000000..3e0ce49056
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_throw.erl
@@ -0,0 +1,495 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2020-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%
+%%
+
+%%%
+%%% This pass skips stack trace generation on erlang:throw/1 when they're
+%%% guaranteed not to be inspected.
+%%%
+%%% It does so by finding all exception handlers in the module, determining
+%%% which functions they cover, and then simulating the result of every throw/1
+%%% in the module; if a particular throw/1 never reaches an instruction that
+%%% creates a stack trace, then it's safe to rewrite it.
+%%%
+
+-module(beam_ssa_throw).
+
+-export([module/2]).
+
+-import(lists, [foldl/3]).
+
+-include("beam_ssa.hrl").
+-include("beam_types.hrl").
+
+-type exception_var() :: none | #b_var{}.
+-type exception_vars() :: {Class :: exception_var(),
+                           Reason :: exception_var(),
+                           Stacktrace :: exception_var()}.
+-type tentative() :: {tentative,
+                      Start :: beam_ssa:label(),
+                      Vars :: exception_vars(),
+                      Blocks :: beam_ssa:block_map()}.
+
+-type handler() :: tentative() | unsuitable | suitable.
+-type handler_id() :: {#b_local{}, beam_ssa:label()}.
+
+-type suitability() :: {tentative, beam_ssa:label()} |
+                       unsuitable |
+                       suitable.
+
+%% Per-module scan state
+-record(gst, {tlh_roots :: gb_trees:tree(#b_local{}, gb_sets:set(handler())),
+              tlh_edges=#{} :: #{ #b_local{} => gb_sets:set(#b_local{}) },
+              throws=cerl_sets:new() :: cerl_sets:set(#b_local{})}).
+
+%% Per-function scan state
+-record(lst, {suitability=#{} :: #{ #b_var{} => suitability() },
+              handlers=#{} :: #{ handler_id() => handler() },
+              blocks :: beam_ssa:block_map(),
+              predecessors :: beam_ssa:predecessor_map()}).
+
+-spec module(beam_ssa:b_module(), [compile:option()]) ->
+          {'ok',beam_ssa:b_module()}.
+
+module(#b_module{body=Fs0}=Module, _Opts) ->
+    case scan(Module) of
+        {Throws, TLHs} ->
+            Fs = opt(Fs0, Throws, TLHs),
+            {ok, Module#b_module{body=Fs}};
+        no_throws ->
+            {ok, Module}
+    end.
+
+%% Builds a map with the top-level exception handlers (TLHs) for each function,
+%% as well as the set of functions that call throw/1.
+scan(#b_module{body=Fs0}=Module) ->
+    scan_1(Fs0, init_gst(Module)).
+
+init_gst(#b_module{exports=Exports}) ->
+    %% We can't optimize any throw that leaves the module, so we'll add an
+    %% unsuitable top-level handler to all exported functions.
+    Unsuitable = gb_sets:singleton(unsuitable),
+    Roots = foldl(fun({Name, Arity}, Acc) ->
+                          Id = #b_local{name=#b_literal{val=Name},arity=Arity},
+                          gb_trees:insert(Id, Unsuitable, Acc)
+                  end, gb_trees:empty(), Exports),
+    #gst{tlh_roots=Roots}.
+
+scan_1([#b_function{bs=Blocks}=F | Fs], Gst0) ->
+    Id = get_func_id(F),
+
+    Preds = beam_ssa:predecessors(Blocks),
+    Linear = beam_ssa:linearize(Blocks),
+
+    Lst = #lst{blocks=Blocks,predecessors=Preds},
+    {_, Gst} = scan_bs(Linear, Id, Lst, Gst0),
+
+    scan_1(Fs, Gst);
+scan_1([], Gst) ->
+    #gst{tlh_roots=Roots,
+         tlh_edges=Edges,
+         throws=Throws} = Gst,
+
+    case cerl_sets:size(Throws) of
+        0 ->
+            no_throws;
+        _ ->
+            TLHs = propagate_tlhs(gb_trees:to_list(Roots), Edges, #{}),
+            {Throws, TLHs}
+    end.
+
+%% Propagates all top-level handlers to the functions they cover. See
+%% inherit_tlh/3 and add_tlh/3.
+propagate_tlhs([{Id, HandlersA} | Roots], Edges, Acc0) ->
+    HandlersB = maps:get(Id, Acc0, gb_sets:empty()),
+    case gb_sets:is_subset(HandlersA, HandlersB) of
+        true ->
+            propagate_tlhs(Roots, Edges, Acc0);
+        false ->
+            Merged = gb_sets:union(HandlersA, HandlersB),
+            Callees = pt_callees(Id, Merged, Edges),
+
+            Acc = propagate_tlhs(Callees, Edges, Acc0#{ Id => Merged }),
+            propagate_tlhs(Roots, Edges, Acc)
+    end;
+propagate_tlhs([], _Edges, Acc) ->
+    Acc.
+
+pt_callees(Id, Merged, Edges) ->
+    case Edges of
+        #{ Id := Callees } ->
+            gb_sets:fold(fun(Callee, Acc) ->
+                                 [{Callee, Merged} | Acc]
+                         end, [], Callees);
+        #{} ->
+            []
+    end.
+
+%% We scan the blocks in post-order so we can finish analyzing handlers before
+%% reaching their affected calls. This lets us avoid unnecessary indirection
+%% when adding or inspecting top-level handlers.
+scan_bs([{?EXCEPTION_BLOCK, _} | Bs], Id, Lst, Gst) ->
+    scan_bs(Bs, Id, Lst, Gst);
+scan_bs([{Lbl, #b_blk{last=Last,is=Is}} | Bs], Id, Lst0, Gst0) ->
+    {Lst, Gst} = scan_bs(Bs, Id, Lst0, Gst0),
+    si_is(Is, Id, Lbl, Last, Lst, Gst);
+scan_bs([], _Id, Lst, Gst) ->
+    {Lst, Gst}.
+
+si_is([#b_set{op=landingpad,args=[_Kind, _Tag]} | Is],
+      Id, Lbl, Last, Lst, Gst) ->
+    %% Class:Reason:Stacktrace
+    Vars = {none, none, none},
+    si_handler_start(Is, Id, Lbl, Last, Vars, Lst, Gst);
+si_is([#b_set{op=resume,args=[Stacktrace, _Reason]} | Is],
+      Id, Lbl, Last, Lst, Gst) ->
+    si_handler_end(Is, Id, Lbl, Last, Stacktrace, Lst, Gst);
+si_is([#b_set{op=raw_raise,args=[_,_,Stacktrace]} | Is],
+      Id, Lbl, Last, Lst, Gst) ->
+    si_handler_end(Is, Id, Lbl, Last, Stacktrace, Lst, Gst);
+si_is([#b_set{op=build_stacktrace,args=[Stacktrace]} | Is],
+      Id, Lbl, Last, Lst, Gst) ->
+    si_handler_end(Is, Id, Lbl, Last, Stacktrace, Lst, Gst);
+si_is([#b_set{op=call,
+              dst=Dst,
+              args=[#b_remote{mod=#b_literal{val=erlang},
+                              name=#b_literal{val=throw},
+                              arity=1}, _Term]}],
+      Id, _Lbl, #b_ret{arg=Dst}, Lst, Gst) ->
+    %% Tail throw, handled by caller. We'll need to visit this function again.
+    #gst{throws=Throws0} = Gst,
+    Throws = cerl_sets:add_element(Id, Throws0),
+    {Lst, Gst#gst{throws=Throws}};
+si_is([#b_set{op=call,dst=Dst,args=[#b_local{}=Callee | _]}],
+      Id, _Lbl, #b_ret{arg=Dst}, Lst, Gst) ->
+    %% Tail call, inherit our caller's handler.
+    {Lst, inherit_tlh(Id, Callee, Gst)};
+si_is([#b_set{op=call,dst=Dst,args=[#b_local{}=Callee | _]},
+       #b_set{op={succeeded,body},args=[Dst]}],
+      Id, _Lbl, #b_br{fail=?EXCEPTION_BLOCK}, Lst, Gst) ->
+    %% No local handler, inherit our caller's top-level handlers.
+    {Lst, inherit_tlh(Id, Callee, Gst)};
+si_is([#b_set{op=call,dst=Dst,args=[#b_local{}=Callee | _]},
+       #b_set{op={succeeded,body},args=[Dst]}],
+      Id, _Lbl, #b_br{fail=Fail}, Lst, Gst) ->
+    %% Local handler, register it as a top-level handler with our callee.
+    HandlerId = {Id, Fail},
+    {Lst, add_tlh(HandlerId, Callee, Lst, Gst)};
+si_is([#b_set{} | Is], Id, Lbl, Last, Lst, Gst) ->
+    si_is(Is, Id, Lbl, Last, Lst, Gst);
+si_is([], _Id, _Lbl, _Last, Lst, Gst) ->
+    {Lst, Gst}.
+
+%% Marks the end of a handler. Note that we encounter this _before_ the start
+%% since we iterate the blocks in post-order.
+si_handler_end(Is, Id, EndLbl, Last, Stacktrace, Lst0, Gst) ->
+    #lst{suitability=Suitability0} = Lst0,
+
+    Marker = case Suitability0 of
+                 #{ Stacktrace := {tentative, _} } ->
+                     %% Multiple uses make partitioning more difficult, so
+                     %% we'll disqualify this handler to save us the hassle.
+                     unsuitable;
+                 #{} ->
+                     %% This handler may or may not be suitable depending on
+                     %% what's thrown, so we keep it for later analysis.
+                     {tentative, EndLbl}
+             end,
+
+    Suitability = Suitability0#{ Stacktrace => Marker },
+
+    Lst = Lst0#lst{suitability=Suitability},
+    si_is(Is, Id, EndLbl, Last, Lst, Gst).
+
+si_handler_start([#b_set{op=extract,
+                         dst=Dst,
+                         args=[_, #b_literal{val=Idx}]} | Is],
+                 Id, StartLbl, Last, Vars0, Lst, Gst) ->
+    none = element(1 + Idx, Vars0),             %Assertion.
+    Vars = setelement(1 + Idx, Vars0, Dst),
+    si_handler_start(Is, Id, StartLbl, Last, Vars, Lst, Gst);
+si_handler_start(Is, Id, StartLbl, Last, Vars, Lst0, Gst) ->
+    HandlerId = {Id, StartLbl},
+
+    #lst{blocks=Blocks,
+         predecessors=Preds,
+         suitability=Suitability,
+         handlers=Handlers0} = Lst0,
+
+    {_, _, Stacktrace} = Vars,
+
+    Handlers = case Suitability of
+                   #{ Stacktrace := {tentative, EndLbl} } ->
+                       %% This handler's suitability depends on what we throw,
+                       %% so we'll extract the blocks between handler start and
+                       %% the first use of the stack trace for later analysis.
+                       Path = beam_ssa:between(StartLbl, EndLbl, Preds, Blocks),
+                       Partition = maps:with(Path, Blocks),
+
+                       Handler = {tentative, StartLbl, Vars, Partition},
+                       Handlers0#{ HandlerId => Handler };
+                   #{} when Stacktrace =/= none ->
+                       %% Stack trace is used in an unsupported manner.
+                       Handlers0#{ HandlerId => unsuitable };
+                   #{} when Stacktrace =:= none ->
+                       %% Stack trace is never used, so it's always safe to
+                       %% skip trace generation.
+                       Handlers0#{ HandlerId => suitable }
+               end,
+
+    Lst = Lst0#lst{handlers=Handlers},
+
+    si_is(Is, Id, StartLbl, Last, Lst, Gst).
+
+%% Makes the callee inherit its callers top-level handlers
+inherit_tlh(Caller, Callee, #gst{tlh_edges=Edges0}=Gst) ->
+    Callees = case Edges0 of
+                  #{ Caller := Callees0 } ->
+                      gb_sets:add_element(Callee, Callees0);
+                  #{} ->
+                      gb_sets:singleton(Callee)
+              end,
+    Edges = Edges0#{ Caller => Callees },
+    Gst#gst{tlh_edges=Edges}.
+
+%% Registers the given handler as a top-level handler for Callee
+add_tlh(Id, Callee, Lst, Gst) ->
+    #lst{handlers=Handlers} = Lst,
+    #gst{tlh_roots=Roots0} = Gst,
+
+    #{ Id := Handler } = Handlers,
+
+    TLHs0 = case gb_trees:lookup(Callee, Roots0) of
+                none -> gb_sets:singleton(Handler);
+                {value, V} -> V
+            end,
+
+    TLHs = case gb_sets:is_element(unsuitable, TLHs0) of
+               true ->
+                   %% One bad apple spoils the bunch.
+                   TLHs0;
+               false ->
+                   gb_sets:add_element(Handler, TLHs0)
+           end,
+
+    Roots = gb_trees:enter(Callee, TLHs, Roots0),
+    Gst#gst{tlh_roots=Roots}.
+
+%% Goes through all functions that call throw/1, checking whether the stack
+%% trace is unused in all of its top-level exception handlers (TLHs), and
+%% rewriting such calls to `raise(throw, Term, [])`.
+opt([#b_function{bs=Blocks0}=F | Fs], Throws, TLHs) ->
+    Id = get_func_id(F),
+
+    Blocks = case {cerl_sets:is_element(Id, Throws), TLHs} of
+                 {true, #{ Id := Handlers } } ->
+                     opt_function(Handlers, Blocks0);
+                 {_, _} ->
+                     %% We're either unreachable or never throw.
+                     Blocks0
+             end,
+
+    [F#b_function{bs=Blocks} | opt(Fs, Throws, TLHs)];
+opt([], _Throws, _TLHs) ->
+    [].
+
+get_func_id(#b_function{anno=Anno}) ->
+    {_,Name,Arity} = maps:get(func_info, Anno),
+    #b_local{name=#b_literal{val=Name},arity=Arity}.
+
+opt_function(Handlers, Blocks) ->
+    case gb_sets:is_member(unsuitable, Handlers) of
+        true ->
+            Blocks;
+        false ->
+            Linear0 = beam_ssa:linearize(Blocks),
+            Linear = opt_bs(Linear0, gb_sets:to_list(Handlers)),
+            maps:from_list(Linear)
+    end.
+
+opt_bs([{Lbl, #b_blk{last=Last,is=Is0}=Blk} | Bs], Hs) ->
+    Is = opt_is(Is0, Last, Hs),
+    [{Lbl, Blk#b_blk{is=Is}} | opt_bs(Bs, Hs)];
+opt_bs([], _Hs) ->
+    [].
+
+opt_is([#b_set{op=call,
+               dst=Dst,
+               args=[#b_remote{mod=#b_literal{val=erlang},
+                               name=#b_literal{val=throw},
+                               arity=1}, _Term]}=I0],
+       #b_ret{arg=Dst}, Hs) ->
+    ThrownType = beam_ssa:get_anno(thrown_type, I0, any),
+    I = opt_throw(Hs, ThrownType, I0),
+    [I];
+opt_is([I | Is], Last, Hs) ->
+    [I | opt_is(Is, Last, Hs)];
+opt_is([], _Last, _Hs) ->
+    [].
+
+opt_throw([suitable | Hs], ThrownType, I) ->
+    opt_throw(Hs, ThrownType, I);
+opt_throw([{tentative, Start, Vars, Blocks} | Hs], ThrownType, I) ->
+    case opt_is_suitable(Start, Blocks, Vars, ThrownType) of
+        true -> opt_throw(Hs, ThrownType, I);
+        false -> I
+    end;
+opt_throw([], _ThrownType, #b_set{args=[_, Reason]}=I) ->
+    %% All handlers caught us and ignored our stack trace. Rewrite ourselves to
+    %% erlang:raise/3 with an empty stack trace.
+    MFA = #b_remote{mod=#b_literal{val=erlang},
+                    name=#b_literal{val=raise},
+                    arity=3},
+    Stacktrace = #b_literal{val=[]},
+    I#b_set{args=[MFA, #b_literal{val=throw}, Reason, Stacktrace]}.
+
+%% Checks whether we avoid building a stack trace when entering the handler
+%% with the given throw type ("reason").
+%%
+%% The analysis is similar to that of the type pass, and I'd liked to have used
+%% that more directly but it would require much more work for little benefit;
+%% the watered-down version below is good enough to handle the most common
+%% cases.
+opt_is_suitable(Start, Blocks, Vars, ThrownType) ->
+    Ts = ois_init_ts(Vars, ThrownType),
+    ois_1([Start], Blocks, Ts).
+
+ois_1([Lbl | Lbls], Blocks, Ts0) ->
+    case Blocks of
+        #{ Lbl := #b_blk{last=Last,is=Is} } ->
+            case ois_is(Is, Ts0) of
+                {ok, Ts} ->
+                    Next = ois_successors(Last, Ts),
+                    ois_1(Next ++ Lbls, Blocks, Ts);
+                error ->
+                    false
+            end;
+        #{} ->
+            ois_1(Lbls, Blocks, Ts0)
+    end;
+ois_1([], _Blocks, _Ts) ->
+    true.
+
+ois_successors(#b_switch{fail=Fail,list=List}, _Ts) ->
+    Lbls = [Lbl || {_, Lbl} <- List],
+    [Fail | Lbls];
+ois_successors(#b_br{bool=Bool,succ=Succ,fail=Fail}, Ts) ->
+    case beam_types:get_singleton_value(ois_get_type(Bool, Ts)) of
+        {ok, true} -> [Succ];
+        {ok, false} -> [Fail];
+        error -> [Succ, Fail]
+    end.
+
+ois_init_ts({Class, Reason, Stacktrace}, ThrownType) ->
+    Ts = #{ Class => beam_types:make_atom(throw),
+            Reason => ThrownType,
+            Stacktrace => any },
+    maps:remove(none, Ts).
+
+ois_is([#b_set{op=build_stacktrace} | _], _Ts) ->
+    error;
+ois_is([#b_set{op=raw_raise} | _], _Ts) ->
+    error;
+ois_is([#b_set{op=resume} | _], _Ts) ->
+    error;
+ois_is([#b_set{op=get_hd,dst=Dst,args=[Src]} | Is], Ts0) ->
+    SrcType = ois_get_type(Src, Ts0),
+    {Type, _, _} = beam_call_types:types(erlang, hd, [SrcType]),
+    Ts = Ts0#{ Dst => Type },
+    ois_is(Is, Ts);
+ois_is([#b_set{op=get_tl,dst=Dst,args=[Src]} | Is], Ts0) ->
+    SrcType = ois_get_type(Src, Ts0),
+    {Type, _, _} = beam_call_types:types(erlang, tl, [SrcType]),
+    Ts = Ts0#{ Dst => Type },
+    ois_is(Is, Ts);
+ois_is([#b_set{op=get_tuple_element,dst=Dst,args=[Src, Offset]} | Is], Ts0) ->
+    Type = case Ts0 of
+               #{ Src := #t_tuple{size=Size,elements=Es} } ->
+                   #b_literal{val=N} = Offset,
+                   true = Size > N,                    %Assertion.
+                   beam_types:get_tuple_element(N + 1, Es);
+               #{} ->
+                   any
+           end,
+    Ts = Ts0#{ Dst => Type },
+    ois_is(Is, Ts);
+ois_is([#b_set{op={bif,is_atom},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_atom{}, Is, Ts);
+ois_is([#b_set{op={bif,is_bitstring},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_bitstring{}, Is, Ts);
+ois_is([#b_set{op={bif,is_binary},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_bitstring{size_unit=8}, Is, Ts);
+ois_is([#b_set{op={bif,is_float},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_float{}, Is, Ts);
+ois_is([#b_set{op={bif,is_integer},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_integer{}, Is, Ts);
+ois_is([#b_set{op={bif,is_list},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_list{}, Is, Ts);
+ois_is([#b_set{op={bif,is_map},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_map{}, Is, Ts);
+ois_is([#b_set{op={bif,is_number},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, number, Is, Ts);
+ois_is([#b_set{op={bif,is_tuple},dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_tuple{}, Is, Ts);
+ois_is([#b_set{op=is_nonempty_list,dst=Dst,args=[Src]} | Is], Ts) ->
+    ois_type_test(Src, Dst, #t_cons{}, Is, Ts);
+ois_is([#b_set{op=is_tagged_tuple,dst=Dst,args=[Src, Size, Tag]} | Is], Ts) ->
+    Es = beam_types:set_tuple_element(1, ois_get_type(Tag, Ts), #{}),
+    #b_literal{val=N} = Size,
+
+    Type = #t_tuple{exact=true,size=N,elements=Es},
+
+    ois_type_test(Src, Dst, Type, Is, Ts);
+ois_is([#b_set{op={bif,tuple_size},dst=Dst,args=[Src]} | Is], Ts0) ->
+    SrcType = ois_get_type(Src, Ts0),
+    {Type, _, _} = beam_call_types:types(erlang, tuple_size, [SrcType]),
+    Ts = Ts0#{ Dst => Type },
+    ois_is(Is, Ts);
+ois_is([#b_set{op={bif,'=:='},
+               dst=Dst,
+               args=[LHS,#b_literal{val=RHS}]} | Is], Ts0) ->
+    Type = case beam_types:get_singleton_value(ois_get_type(LHS, Ts0)) of
+               {ok, RHS} -> beam_types:make_atom(true);
+               {ok, _Other} -> beam_types:make_atom(false);
+               error -> beam_types:make_boolean()
+           end,
+    Ts = Ts0#{ Dst => Type },
+    ois_is(Is, Ts);
+ois_is([#b_set{} | Is], Ts) ->
+    ois_is(Is, Ts);
+ois_is([], Ts) ->
+    {ok, Ts}.
+
+ois_type_test(Src, Dst, RequiredType, Is, Ts0) ->
+    GivenType = ois_get_type(Src, Ts0),
+    Type = case beam_types:meet(GivenType, RequiredType) of
+               GivenType -> beam_types:make_atom(true);
+               none -> beam_types:make_atom(false);
+               _Other -> beam_types:make_boolean()
+           end,
+    Ts = Ts0#{ Dst => Type },
+    ois_is(Is, Ts).
+
+ois_get_type(#b_literal{val=Value}, _Ts) ->
+    beam_types:make_type_from_value(Value);
+ois_get_type(#b_var{}=Arg, Ts) ->
+    case Ts of
+        #{ Arg := Type } -> Type;
+        #{} -> any
+    end.
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index 1e4c9ba5a0..232a5daf16 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -931,16 +931,11 @@ simplify(#b_set{op=put_tuple,args=Args}=I, _Ts) ->
     end;
 simplify(#b_set{op=wait_timeout,args=[#b_literal{val=0}]}, _Ts) ->
     #b_literal{val=true};
-simplify(#b_set{op=call,args=[#b_remote{}=Rem|Args]}=I, _Ts) ->
+simplify(#b_set{op=call,args=[#b_remote{}=Rem|Args]}=I, Ts) ->
     case Rem of
         #b_remote{mod=#b_literal{val=Mod},
                   name=#b_literal{val=Name}} ->
-            case erl_bifs:is_pure(Mod, Name, length(Args)) of
-                true ->
-                    simplify_remote_call(Mod, Name, Args, I);
-                false ->
-                    I
-            end;
+            simplify_remote_call(Mod, Name, Args, Ts, I);
         #b_remote{} ->
             I
     end;
@@ -1154,13 +1149,17 @@ phi_all_same_1([], _Arg) ->
 phi_all_same_1(_Phis, _Arg) ->
     false.
 
-%% Simplify a remote call to a pure BIF.
-simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _I) ->
+simplify_remote_call(erlang, throw, [Term], Ts, I) ->
+    %% Annotate `throw` instructions with the type of their thrown term,
+    %% helping `beam_ssa_throw` optimize non-local returns.
+    Type = normalized_type(Term, Ts),
+    beam_ssa:add_anno(thrown_type, Type, I);
+simplify_remote_call(erlang, '++', [#b_literal{val=[]},Tl], _Ts, _I) ->
     Tl;
 simplify_remote_call(erlang, setelement,
                      [#b_literal{val=Pos},
                       #b_literal{val=Tuple},
-                      #b_var{}=Value], I)
+                      #b_var{}=Value], _Ts, I)
   when is_integer(Pos), 1 =< Pos, Pos =< tuple_size(Tuple) ->
     %% Position is a literal integer and the shape of the
     %% tuple is known.
@@ -1168,7 +1167,16 @@ simplify_remote_call(erlang, setelement,
     {Bef,[_|Aft]} = split(Pos - 1, Els0),
     Els = Bef ++ [Value|Aft],
     I#b_set{op=put_tuple,args=Els};
-simplify_remote_call(Mod, Name, Args0, I) ->
+simplify_remote_call(Mod, Name, Args, _Ts, I) ->
+    case erl_bifs:is_pure(Mod, Name, length(Args)) of
+        true ->
+            simplify_pure_call(Mod, Name, Args, I);
+        false ->
+            I
+    end.
+
+%% Simplify a remote call to a pure BIF.
+simplify_pure_call(Mod, Name, Args0, I) ->
     case make_literal_list(Args0) of
         none ->
             I;
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 8a3e1c6cd3..e08b444a13 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -874,6 +874,10 @@ kernel_passes() ->
        {iff,dssaopt,{listing,"ssaopt"}},
        {unless,no_ssa_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
 
+       {unless,no_throw_opt,{pass,beam_ssa_throw}},
+       {iff,dthrow,{listing,"throw"}},
+       {unless,no_throw_opt,{iff,ssalint,{pass,beam_ssa_lint}}},
+
        {unless,no_recv_opt,{pass,beam_ssa_recv}},
        {iff,drecv,{listing,"recv"}},
        {unless,no_recv_opt,{iff,ssalint,{pass,beam_ssa_lint}}}]},
@@ -2173,6 +2177,7 @@ pre_load() ->
 	 beam_ssa_pre_codegen,
 	 beam_ssa_recv,
 	 beam_ssa_share,
+	 beam_ssa_throw,
 	 beam_ssa_type,
 	 beam_trim,
 	 beam_types,
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index 92e9fa74e5..9ab9553283 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -47,6 +47,7 @@
              beam_ssa_pre_codegen,
              beam_ssa_recv,
              beam_ssa_share,
+             beam_ssa_throw,
              beam_ssa_type,
 	     beam_trim,
              beam_types,
diff --git a/lib/compiler/test/trycatch_SUITE.erl b/lib/compiler/test/trycatch_SUITE.erl
index 9b4a7754b3..8695946409 100644
--- a/lib/compiler/test/trycatch_SUITE.erl
+++ b/lib/compiler/test/trycatch_SUITE.erl
@@ -1526,6 +1526,7 @@ expr_export_5() ->
 
 coverage(_Config) ->
     {'EXIT',{{badfun,true},[_|_]}} = (catch coverage_1()),
+    ok = coverage_ssa_throw(),
     ok.
 
 %% Cover some code in beam_trim.
@@ -1543,5 +1544,77 @@ coverage_1() ->
             true
     end.
 
+%% Cover some code in beam_ssa_throw.
+coverage_ssa_throw() ->
+    cst_trivial(),
+    cst_raw(),
+    cst_stacktrace(),
+    cst_types(),
+
+    ok.
+
+cst_trivial() ->
+    %% never inspects stacktrace
+    try
+        cst_trivial_1()
+    catch
+        _C:_R:_S ->
+            ok
+    end.
+
+cst_trivial_1() -> throw(id(gurka)).
+
+cst_types() ->
+    %% type tests
+    try
+        cst_types_1()
+    catch
+        throw:Val when is_atom(Val);
+                       is_bitstring(Val);
+                       is_binary(Val);
+                       is_float(Val);
+                       is_integer(Val);
+                       is_list(Val);
+                       is_map(Val);
+                       is_number(Val);
+                       is_tuple(Val) ->
+            ok;
+        throw:[_|_]=Cons when hd(Cons) =/= gurka;
+                              tl(Cons) =/= gaffel ->
+            %% is_nonempty_list, get_hd, get_tl
+            ok;
+        throw:Tuple when tuple_size(Tuple) < 5 ->
+            %% tuple_size
+            ok
+    end.
+
+cst_types_1() -> throw(id(gurka)).
+
+cst_stacktrace() ->
+    %% build_stacktrace
+    try
+        cst_stacktrace_1()
+    catch
+        throw:gurka ->
+            ok;
+        _C:_R:Stack ->
+            id(Stack),
+            ok
+    end.
+
+cst_stacktrace_1() -> throw(id(gurka)).
+
+cst_raw() ->
+    %% raw_raise
+    try
+        cst_raw_1()
+    catch
+        throw:gurka ->
+            ok;
+        _C:_R:Stack ->
+            erlang:raise(error, dummy, Stack)
+    end.
+
+cst_raw_1() -> throw(id(gurka)).
 
 id(I) -> I.
-- 
2.26.2

openSUSE Build Service is sponsored by