Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions lib/compiler/src/beam_call_types.erl
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,14 @@ will_succeed(erlang, setelement, [Pos, Tuple0, _Value]=Args) ->
{_, none} ->
no;
{#t_integer{elements={Min,Max}}=Pos, Tuple} ->
MaxTupleSize = max_tuple_size(Tuple),
TupleSizes = tuple_sizes(Tuple),
MaxTupleSize = lists:max(TupleSizes),
MinTupleSize = lists:min(TupleSizes),
if
MaxTupleSize < Min ->
%% Index is always out of range.
no;
Tuple0 =:= Tuple, Max =< MaxTupleSize ->
Tuple0 =:= Tuple, Max =< MinTupleSize ->
%% We always have a tuple, and the index is always in
%% range.
yes;
Expand Down Expand Up @@ -209,14 +211,14 @@ will_succeed(Mod, Func, Args) ->
end
end.

max_tuple_size(#t_union{tuple_set=[_|_]=Set}=Union) ->
tuple_sizes(#t_union{tuple_set=[_|_]=Set}=Union) ->
Union = meet(Union, #t_tuple{}), %Assertion.
Arities = [Arity || {{Arity, _Tag}, _Record} <:- Set],
lists:max(Arities);
max_tuple_size(#t_tuple{exact=true,size=Size}) ->
Size;
max_tuple_size(#t_tuple{exact=false}) ->
?MAX_TUPLE_SIZE.
Arities;
tuple_sizes(#t_tuple{exact=true,size=Size}) ->
[Size];
tuple_sizes(#t_tuple{exact=false,size=Size}) ->
[Size,?MAX_TUPLE_SIZE].

%% While we can't infer success for functions outside the 'erlang'
%% module, it's safe to infer failure when we know they return `none` or
Expand Down
2 changes: 1 addition & 1 deletion lib/compiler/src/beam_core_to_ssa.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2799,7 +2799,7 @@ cg(#cg_goto{label=Label,args=As0}, St) ->
Break = #cg_break{args=As,phi=Label},
{[Break],St};
cg(#cg_opaque{val=Check}, St) ->
{ssa_check_when,_,_,_,_} = Check, %Assertion.
{ssa_check_when,_,_,_,_,_} = Check, %Assertion.
{[],St#cg{checks=[Check|St#cg.checks]}}.

%% match_cg(Match, Fail, State) -> {[Ainstr],State}.
Expand Down
4 changes: 3 additions & 1 deletion lib/compiler/src/beam_doc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,9 @@ extract_documentation(AST, State) ->
State1 = foldl(fun extract_documentation0/2, State, AST),
State2 = purge_types_not_used_from_exported_functions(State1),
State3 = purge_unreachable_types(State2),
warnings(AST, State3).
Ws = warnings(AST, State3),
digraph:delete(State3#docs.type_dependency),
Ws.

%%
%% purges types that are not used in exported functions.
Expand Down
12 changes: 6 additions & 6 deletions lib/compiler/src/beam_ssa_bsm.erl
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,19 @@ analyze_module(#b_module{body=Fs}) ->
end, #{}, Fs).

has_bsm_ops(#b_function{bs=Blocks}) ->
hbo_blocks(maps:to_list(Blocks)).
hbo_blocks(maps:to_list(Blocks), false).

hbo_blocks([{_,#b_blk{is=Is}} | Blocks]) ->
hbo_blocks([{_,#b_blk{is=Is}} | Blocks], Acc) ->
case hbo_is(Is) of
no -> hbo_blocks(Blocks);
yes -> true;
no -> hbo_blocks(Blocks, Acc);
yes -> hbo_blocks(Blocks, true);
nif_start ->
%% Disable optimizations for declared -nifs()
%% to avoid leaking match contexts as NIF arguments.
false
end;
hbo_blocks([]) ->
false.
hbo_blocks([], Acc) ->
Acc.

hbo_is([#b_set{op=bs_start_match} | _]) -> yes;
hbo_is([#b_set{op=nif_start} | _]) -> nif_start;
Expand Down
18 changes: 9 additions & 9 deletions lib/compiler/src/beam_ssa_check.erl
Original file line number Diff line number Diff line change
Expand Up @@ -56,34 +56,34 @@ functions(_Tag, []) ->
function(Tag, F) ->
run_checks(beam_ssa:get_anno(ssa_checks, F, []), F, Tag).

run_checks([{ssa_check_when,WantedResult,{atom,_,Tag},Args,Exprs}|Checks],
run_checks([{ssa_check_when,WantedResult,{atom,_,Tag},Args,CheckAnnos,Exprs}|Checks],
F, Tag) ->
check_function(Args, Exprs, WantedResult, F) ++ run_checks(Checks, F, Tag);
check_function(Args, CheckAnnos, Exprs, WantedResult, F) ++ run_checks(Checks, F, Tag);
run_checks([_|Checks], F, Tag) ->
run_checks(Checks, F, Tag);
run_checks([], _, _) ->
[].

check_function(CheckArgs, Exprs, {atom,Loc,pass}, #b_function{args=_Args}=F) ->
run_check(CheckArgs, Exprs, Loc, F);
check_function(CheckArgs, Exprs, {atom,Loc,Key}, #b_function{args=_Args}=F)
check_function(CheckArgs, CheckAnnos, Exprs, {atom,Loc,pass}, #b_function{args=_Args}=F) ->
run_check(CheckArgs, CheckAnnos, Exprs, Loc, F);
check_function(CheckArgs, CheckAnnos, Exprs, {atom,Loc,Key}, #b_function{args=_Args}=F)
when Key =:= fail ; Key =:= xfail ->
case run_check(CheckArgs, Exprs, Loc, F) of
case run_check(CheckArgs, CheckAnnos, Exprs, Loc, F) of
[] ->
%% This succeeded but should have failed
{File,_} = beam_ssa:get_anno(location, F),
[{File,[{Loc,?MODULE,xfail_passed}]}];
_ ->
[]
end;
check_function(_, _, {atom,Loc,Result}, F) ->
check_function(_, _, _, {atom,Loc,Result}, F) ->
{File,_} = beam_ssa:get_anno(location, F),
[{File,[{Loc,?MODULE,{unknown_result_kind,Result}}]}].

run_check(CheckArgs, Exprs, Loc, #b_function{args=FunArgs}=F) ->
run_check(CheckArgs, CheckAnnos, Exprs, Loc, #b_function{args=FunArgs}=F) ->
_ = check_annos(CheckAnnos, F#b_function.anno, #{}),
init_and_run_check(CheckArgs, FunArgs, #{}, Loc, Exprs, F).


%% Create a mapping from each argument in the check pattern to the
%% actual arguments of the SSA function.
init_and_run_check([{var,Loc,'_'}|CheckArgs],
Expand Down
12 changes: 10 additions & 2 deletions lib/compiler/src/beam_ssa_destructive_update.erl
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,8 @@ aggregate_ret_patches([{self,heap_tuple},TE={tuple_element,_,_,_}|Rest]) ->
%% heap, the {self,heap_tuple} can be dropped. Due to the sort in
%% patch_ret/3, self will always occur before tuple_element.
aggregate_ret_patches([TE|Rest]);
aggregate_ret_patches([{hd,E0,D0},{hd,E1,D1}|Rest]) ->
aggregate_ret_patches([{hd, merge_patches(E0, E1), max(D0,D1)}|Rest]);
aggregate_ret_patches([R={hd,_,_}]) ->
R.

Expand Down Expand Up @@ -968,9 +970,11 @@ merge_patches({tuple_element,I,E0,D0}, {tuple_element,I,E1,D1}) ->
merge_patches({tuple_element,IA,EA,_}, {tuple_element,IB,EB,_}) ->
{tuple_elements, [{IA,EA}, {IB,EB}]};
merge_patches({tuple_element,IA,EA,_}, {tuple_elements,Es}) ->
{tuple_elements,[{IA,EA}|Es]};
{tuple_elements,merge_te(IA, EA, Es)};
merge_patches({tuple_elements,Es}, {tuple_element,IA,EA,_}) ->
{tuple_elements,[{IA,EA}|Es]};
{tuple_elements,merge_te(IA, EA, Es)};
merge_patches({hd,E0,D0},{hd,E1,D1}) ->
{hd, merge_patches(E0,E1), max(D0,D1)};
merge_patches(Patch, {self,heap_tuple}) ->
%% If we find anything more specific than a heap_tuple, the more
%% specific patch subsumes the heap_tuple
Expand All @@ -980,6 +984,10 @@ merge_patches({self,heap_tuple}, Other) ->
%% force the term onto the heap, we can ignore the new patch.
Other.

merge_te(I, E, [{I,E0}|Es]) -> [{I, merge_patches(E0, E)}|Es];
merge_te(I, E, [Other|Es]) -> [Other|merge_te(I, E, Es)];
merge_te(I, E, []) -> [{I, E}].

patch_phi(I0=#b_set{op=phi,args=Args0}, Patches, Cnt0) ->
?DP("Patching Phi:~n args: ~p~n patches: ~p~n", [Args0, Patches]),
L2P = foldl(fun merge_phi_patch/2, #{}, Patches),
Expand Down
36 changes: 21 additions & 15 deletions lib/compiler/src/beam_ssa_throw.erl
Original file line number Diff line number Diff line change
Expand Up @@ -384,22 +384,28 @@ opt_throw([], _ThrownType, #b_set{args=[_, Reason]}=I) ->
%% 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)
ois_1([Start], Blocks, Ts, sets:new()).

ois_1([Lbl | Lbls], Blocks, Ts0, Seen0) ->
case sets:is_element(Lbl, Seen0) of
true ->
ois_1(Lbls, Blocks, Ts0, Seen0);
false ->
Seen1 = sets:add_element(Lbl, Seen0),
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, Seen1);
error ->
false
end;
#{} ->
ois_1(Lbls, Blocks, Ts0, Seen1)
end
end;
ois_1([], _Blocks, _Ts) ->
ois_1([], _Blocks, _Ts, _Seen) ->
true.

ois_successors(#b_switch{fail=Fail,list=List}, _Ts) ->
Expand Down
4 changes: 2 additions & 2 deletions lib/compiler/src/v3_core.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1024,8 +1024,8 @@ expr({Op,Loc,Index}, St0) when Op =:= executable_line;
{#iprimop{anno=#a{anno=lineno_anno(Loc, St0)},
name=#c_literal{val=Op},
args=[#c_literal{val=Index}]},[],St0};
expr({ssa_check_when,L,WantedResult,Args,Tag,Clauses}, St) ->
{#c_opaque{anno=full_anno(L, St),val={ssa_check_when,WantedResult,Tag,Args,Clauses}}, [], St}.
expr({ssa_check_when,L,WantedResult,Args,AnnoCheck,Tag,Clauses}, St) ->
{#c_opaque{anno=full_anno(L, St),val={ssa_check_when,WantedResult,Tag,Args,AnnoCheck,Clauses}}, [], St}.

blockify(L0, {sequential_match,_L1,First,Then}, E) ->
[{single_match,L0,First,E}|blockify(L0, Then, E)];
Expand Down
10 changes: 8 additions & 2 deletions lib/compiler/test/beam_ssa_check_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
sanity_checks/1,
tuple_inplace_checks/1,
non_throwing_bifs/1,
phis_checks/1]).
phis_checks/1,
nifs_checks/1]).

suite() -> [{ct_hooks,[ts_install_cth]}].

Expand All @@ -60,7 +61,8 @@ groups() ->
sanity_checks,
tuple_inplace_checks,
non_throwing_bifs,
phis_checks]},
phis_checks,
nifs_checks]},
{post_ssa_opt_dynamic,test_lib:parallel(),
[bs_size_unit_checks]}].

Expand Down Expand Up @@ -138,6 +140,10 @@ phis_checks(Config) when is_list(Config) ->
run_pre_ssa_opt(phis, Config),
run_post_ssa_opt(phis, Config).

nifs_checks(Config) when is_list(Config) ->
run_post_ssa_opt(nifs, Config),
run_post_ssa_opt(nifs_many_blocks, Config).

dynamic_workdir(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
filename:join(PrivDir, "dynamic").
Expand Down
65 changes: 65 additions & 0 deletions lib/compiler/test/beam_ssa_check_SUITE_data/nifs_many_blocks.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2026. 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 module tests that beam_ssa_bsm:opt/2 correctly handles modules
%% containing nifs.
%%
-module(nifs_many_blocks).

-export([load_nif/0, a_nif/2]).

-nifs([a_nif/2]).

load_nif() ->
ok = erlang:load_nif("dummy", 0).

a_nif(1, <<Tag, Rest/binary>>) ->
%ssa% (_, _) { parameter_info => #{} } when post_ssa_opt ->
%ssa% ret(_).
case Tag of
$a -> {ok, a, a_nif(1, Rest)};
$b -> {ok, b};
$c -> {ok, c};
$d -> {ok, d};
$e -> {ok, e};
$f -> {ok, f};
$g -> {ok, g};
$h -> {ok, h};
$i -> {ok, i};
$j -> {ok, j};
$k -> {ok, k};
$l -> {ok, l};
$m -> {ok, m};
$n -> {ok, n};
$o -> {ok, o};
$p -> {ok, p};
$q -> {ok, q};
$r -> {ok, r};
$s -> {ok, s};
$t -> {ok, t};
$u -> {ok, u};
$v -> {ok, v};
$w -> {ok, w};
$x -> {ok, x};
$y -> {ok, y}
end;
a_nif(2, Bin) ->
<<Tag, _Rest/binary>> = Bin,
Tag.
14 changes: 12 additions & 2 deletions lib/compiler/test/beam_type_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
cover_maps_functions/1,min_max_mixed_types/1,
not_equal/1,infer_relops/1,binary_unit/1,premature_concretization/1,
funs/1,will_succeed/1,float_confusion/1,
cover_convert_ext/1]).
cover_convert_ext/1, catch_setelement/1]).

%% Force id/1 to return 'any'.
-export([id/1]).
Expand Down Expand Up @@ -84,7 +84,8 @@ groups() ->
funs,
will_succeed,
float_confusion,
cover_convert_ext
cover_convert_ext,
catch_setelement
]}].

init_per_suite(Config) ->
Expand Down Expand Up @@ -1661,6 +1662,15 @@ cover_convert_ext(_Config) ->
ok.


%% This used to cause an exception instead of returning an error.
catch_setelement(_Config) ->
{'EXIT', _} = catch_setelement_2(id(true), id(foo)),
ok.

catch_setelement_2(B, V) ->
T = if B -> {1}; true -> {1,2} end,
catch setelement(2, T, V).

%%%
%%% Common utilities.
%%%
Expand Down
Loading
Loading