Skip to content

Commit

Permalink
compiler: Do strength reduction of the hint for update_record
Browse files Browse the repository at this point in the history
Change the hint from `reuse` to `copy` when reusing clearly
will not work.

Extending this optimization to be module-global, and not just work at
the function level, increases the time spent on this pass by a factor
of between 4 and 5 and only triggers infrequently, and for example,
never allows for additional destructive updates. For the moment this
is considered good enough.

Co-authored-by: Frej Drejhammar <[email protected]>
  • Loading branch information
bjorng and frej committed Aug 19, 2024
1 parent 61b1ac5 commit f0c9191
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 3 deletions.
86 changes: 85 additions & 1 deletion lib/compiler/src/beam_ssa_opt.erl
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ epilogue_module_passes(Opts) ->
early_epilogue_passes(Opts) ->
Ps = [?PASS(ssa_opt_type_finish),
?PASS(ssa_opt_float),
?PASS(ssa_opt_sw)],
?PASS(ssa_opt_sw),
?PASS(ssa_opt_no_reuse)],
passes_1(Ps, Opts).

late_epilogue_passes(Opts) ->
Expand Down Expand Up @@ -3711,6 +3712,89 @@ build_bs_ensure_match(L, {_,Size,Unit}, Count0, Blocks0) ->

{Blocks,Count}.

%%%
%%% Change the `reuse` hint to `copy` when it is highly probable that
%%% reuse will not happen.
%%%

ssa_opt_no_reuse({#opt_st{ssa=Linear0}=St, FuncDb}) when is_list(Linear0) ->
New = sets:new([{version,2}]),
Linear = ssa_opt_no_reuse_blks(Linear0, New),
{St#opt_st{ssa=Linear}, FuncDb}.

ssa_opt_no_reuse_blks([{L,#b_blk{is=Is0}=Blk0}|Bs], New0) ->
{Is,New} = ssa_opt_no_reuse_is(Is0, New0, []),
Blk = Blk0#b_blk{is=Is},
[{L,Blk}|ssa_opt_no_reuse_blks(Bs, New)];
ssa_opt_no_reuse_blks([], _) ->
[].

ssa_opt_no_reuse_is([#b_set{op=update_record,args=Args}=I0|Is], New, Acc) ->
[_,_,_|Updates] = Args,
case cannot_reuse(Updates, New) of
true ->
I = I0#b_set{args=[#b_literal{val=copy}|tl(Args)]},
ssa_opt_no_reuse_is(Is, New, [I|Acc]);
false ->
ssa_opt_no_reuse_is(Is, New, [I0|Acc])
end;
ssa_opt_no_reuse_is([#b_set{dst=Dst}=I|Is], New0, Acc) ->
case inhibits_reuse(I, New0) of
true ->
New = sets:add_element(Dst, New0),
ssa_opt_no_reuse_is(Is, New, [I|Acc]);
false ->
ssa_opt_no_reuse_is(Is, New0, [I|Acc])
end;
ssa_opt_no_reuse_is([], New, Acc) ->
{reverse(Acc),New}.

inhibits_reuse(#b_set{op=phi,args=Args}, New) ->
all(fun({Value,_}) ->
sets:is_element(Value, New)
end, Args);
inhibits_reuse(#b_set{op=put_map,args=[_|Args]}, New) ->
cannot_reuse(Args, New);
inhibits_reuse(#b_set{op=call,
args=[#b_remote{mod=#b_literal{val=erlang},
name=#b_literal{val=Name}}|_]},
_New) ->
case Name of
'++' -> true;
'--' -> true;
atom_to_list -> true;
atom_to_binary -> true;
list_to_tuple -> true;
make_ref -> true;
monitor -> true;
setelement -> true;
send_after -> true;
spawn -> true;
spawn_link -> true;
spawn_monitor -> true;
tuple_to_list -> true;
_ -> false
end;
inhibits_reuse(#b_set{op={bif,Arith},args=[#b_var{},#b_literal{}]}, _New)
when Arith =:= '+'; Arith =:= '-' ->
%% This is probably a counter in a record being updated. (Heuristic,
%% but with a high probability of being correct).
true;
inhibits_reuse(#b_set{op=Op}, _New) ->
case Op of
bs_create_bin -> true;
bs_get_tail -> true;
make_fun -> true;
put_list -> true;
put_tuple -> true;
_ -> false
end.

cannot_reuse([V|Values], New) ->
sets:is_element(V, New) orelse cannot_reuse(Values, New);
cannot_reuse([], _New) ->
false.

%%%
%%% Common utilities.
%%%
Expand Down
5 changes: 5 additions & 0 deletions lib/compiler/test/beam_ssa_check_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
annotation_checks/1,
appendable_checks/1,
bs_size_unit_checks/1,
no_reuse_hint_checks/1,
private_append_checks/1,
ret_annotation_checks/1,
sanity_checks/1,
Expand All @@ -47,6 +48,7 @@ groups() ->
[alias_checks,
annotation_checks,
appendable_checks,
no_reuse_hint_checks,
private_append_checks,
ret_annotation_checks,
sanity_checks,
Expand Down Expand Up @@ -99,6 +101,9 @@ appendable_checks(Config) when is_list(Config) ->
bs_size_unit_checks(Config) when is_list(Config) ->
gen_and_run_post_ssa_opt(bs_size_unit_checks, Config).

no_reuse_hint_checks(Config) when is_list(Config) ->
run_post_ssa_opt(no_reuse_hint, Config).

private_append_checks(Config) when is_list(Config) ->
run_post_ssa_opt(private_append, Config).

Expand Down
4 changes: 2 additions & 2 deletions lib/compiler/test/beam_ssa_check_SUITE_data/alias.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,7 @@ update_record0() ->

update_record0([Val|Ls], Acc=#r0{not_aliased=N}) ->
%ssa% (_, Rec) when post_ssa_opt ->
%ssa% _ = update_record(reuse, 3, Rec, 3, A, 2, NA) {unique => [Rec, NA], aliased => [A]}.
%ssa% _ = update_record(copy, 3, Rec, 3, A, 2, NA) {unique => [Rec, NA], aliased => [A]}.
R = Acc#r0{not_aliased=N+1,aliased=Val},
update_record0(Ls, R);
update_record0([], Acc) ->
Expand All @@ -1084,7 +1084,7 @@ update_record1() ->

update_record1([Val|Ls], Acc=#r1{not_aliased0=N0,not_aliased1=N1}) ->
%ssa% (_, Rec) when post_ssa_opt ->
%ssa% _ = update_record(reuse, 3, Rec, 3, NA0, 2, NA1) {unique => [Rec, NA1, NA0], source_dies => true}.
%ssa% _ = update_record(copy, 3, Rec, 3, NA0, 2, NA1) {unique => [Rec, NA1, NA0], source_dies => true}.
R = Acc#r1{not_aliased0=N0+1,not_aliased1=[Val|N1]},
update_record1(Ls, R);
update_record1([], Acc) ->
Expand Down
102 changes: 102 additions & 0 deletions lib/compiler/test/beam_ssa_check_SUITE_data/no_reuse_hint.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2024. 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 the ssa_opt_no_reuse compiler pass. The test
%% strategy is to ensure 100% coverage of the pass and to check for
%% correct functioning for each of the different categories of
%% operations inhibiting reuse: phis, known bifs, functions and
%% instructions.
%%

-module(no_reuse_hint).

-export([coverage0/0,
inhibit_by_known_bif/2,
inhibit_by_known_fun/1,
inhibit_by_known_op/2,
inhibit_by_map/4,
inhibit_by_map_key/2,
inhibit_by_map_value/2,
inhibit_by_phi/1]).

inhibit_by_known_bif({_}=X, Y) ->
%ssa% (X, Y) when post_ssa_opt ->
%ssa% Bif = bif:'+'(...),
%ssa% R = update_record(copy, 1, X, 1, Bif),
%ssa% ret(R).
setelement(1, X, Y + 1).

inhibit_by_known_fun(X) ->
case X of
{_} ->
%ssa% (X) when post_ssa_opt ->
%ssa% KnownFun = call(fun erlang:'++'/2, ...),
%ssa% R = update_record(copy, 1, X, 1, KnownFun),
%ssa% ret(R).
setelement(1, X, e:f() ++ e:f())
end.

inhibit_by_known_op({_, _}=X, Y) ->
%ssa% (X, Y) when post_ssa_opt ->
%ssa% Op = put_tuple(...),
%ssa% R = update_record(copy, 2, X, 1, Op),
%ssa% ret(R).
setelement(1, X, {Y}).

inhibit_by_map(A, B, C, {_}=D) ->
%ssa% (A, B, C, D) when post_ssa_opt ->
%ssa% Map1 = put_map(_, C, B, _),
%ssa% Map = put_map(_, Map1, A, _),
%ssa% R = update_record(copy, 1, D, 1, Map),
%ssa% ret(R).
setelement(1, D, C#{B => {e:f()}, A => e:f()}).

inhibit_by_map_key({Y0}=Z, K) ->
%ssa% (X, Y) when post_ssa_opt ->
%ssa% Key = put_tuple(...),
%ssa% Map = put_map(_, _, Key, value),
%ssa% R = update_record(copy, 1, Z, 1, Map),
%ssa% ret(R).
Y = Y0#{{K} => value},
setelement(1, Z, Y).

inhibit_by_map_value(X, {Y}=Z) ->
%ssa% (X, Y) when post_ssa_opt ->
%ssa% T = put_tuple(...),
%ssa% Map = put_map(_, _, key, T),
%ssa% R = update_record(copy, 1, Z, 1, Map),
%ssa% ret(R).
M = Y#{key => {X}},
setelement(1, Z, M).

inhibit_by_phi({_}=X) ->
%ssa% (X) when post_ssa_opt ->
%ssa% Phi = phi(...),
%ssa% R = update_record(copy, 1, X, 1, Phi),
%ssa% ret(R).
Y = case e:f() of
a -> {e:f(), 1};
b -> {e:f(), 2}
end,
setelement(1, X, Y).

%%
%% Ensure full coverage of the functions in the ssa_opt_no_reuse pass.
%%
coverage0() ->
erlang:send_after(500, self(), fun() -> ok end).

0 comments on commit f0c9191

Please sign in to comment.