Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:23
erlang
2172-compiler-Suppress-stack-traces-for-throw-1...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
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
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor