File 1931-compiler-Speed-up-kill-set-calculation-in-alias-anal.patch of Package erlang
From 1b1c37e335b4cc13ac34034be4fcc78e19909954 Mon Sep 17 00:00:00 2001
From: Frej Drejhammar <frej.drejhammar@gmail.com>
Date: Tue, 11 Jul 2023 09:56:01 +0200
Subject: [PATCH 1/6] compiler: Speed up kill set calculation in alias analysis
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Switch from the standard fixpoint algorithm for calculating liveness
and from that deriving kill sets to a one-pass algorithm which
calculates the kill sets directly.
The algorithm traverses the basic blocks of the CFG post-order. It
traverses the instructions within a basic block in reverse order,
starting with the terminator. When starting the traversal of a basic
block, the set of variables that are live is initialized to the
variables that are live-in to the block's successors. When a def for a
variable is found, it is pruned from the live set. When a use which is
not in the live-set is found, it is a kill. The killed variable is
added to the kill set for the current instruction and added to the
live set.
As the only back-edges occurring in BEAM are for receives and
constructing terms are not allowed within the receive loop, back-edges
can be safely ignored as they won't change the alias status of any
variable.
Acknowledgments to José Valim for suggesting that kill sets could be
calculated directly.
---
lib/compiler/src/beam_ssa_alias.erl | 254 ++++++++++++++--------------
1 file changed, 123 insertions(+), 131 deletions(-)
diff --git a/lib/compiler/src/beam_ssa_alias.erl b/lib/compiler/src/beam_ssa_alias.erl
index 203c1b94e1..9bcc3839a2 100644
--- a/lib/compiler/src/beam_ssa_alias.erl
+++ b/lib/compiler/src/beam_ssa_alias.erl
@@ -87,7 +87,9 @@
%% A code location refering to either the #b_set{} defining a variable
%% or the terminator of a block.
--type kill_loc() :: #b_var{} | {terminator, beam_ssa:label()}.
+-type kill_loc() :: #b_var{}
+ | {terminator, beam_ssa:label()}
+ | {live_outs, beam_ssa:label()}.
%% Map a code location to the set of variables which die at that
%% location.
@@ -99,12 +101,6 @@
-type lbl2ss() :: #{ beam_ssa:label() => sharing_state() }.
-%% Record holding the liveness information for a code location.
--record(liveness_st, {
- in = sets:new([{version,2}]) :: sets:set(#b_var{}),
- out = sets:new([{version,2}]) :: sets:set(#b_var{})
- }).
-
%% The sharing state for a variable.
-record(vas, {
status :: 'unique' | 'aliased' | 'as_parent',
@@ -136,9 +136,7 @@ opt(StMap0, FuncDb0) ->
%% Ignore functions which are not in the function db (never
%% called).
Funs = [ F || F <- maps:keys(StMap0), is_map_key(F, FuncDb0)],
- Liveness = liveness(Funs, StMap0),
- KillsMap = killsets(Liveness, StMap0),
-
+ KillsMap = killsets(Funs, StMap0),
aa(Funs, KillsMap, StMap0, FuncDb0)
end.
@@ -155,135 +153,133 @@ any_huge_function(StMap) ->
end, maps:values(StMap)).
%%%
-%%% Calculate liveness for each function using the standard iterative
-%%% fixpoint method.
+%%% Calculate the set of variables killed at each instruction. The
+%%% algorithm traverses the basic blocks of the CFG post-order. It
+%%% traverses the instructions within a basic block in reverse order,
+%%% starting with the terminator. When starting the traversal of a
+%%% basic block, the set of variables that are live is initialized to
+%%% the variables that are live in to the block's successors. When a
+%%% def for a variable is found, it is pruned from the live set. When
+%%% a use which is not in the live-set is found, it is a kill. The
+%%% killed variable is added to the kill set for the current
+%%% instruction and added to the live set.
+%%%
+%%% As the only back-edges occuring in BEAM are for receives and
+%%% constructing terms are not allowed within the receive loop,
+%%% back-edges can be safely ignored as they won't change the alias
+%%% status of any variable.
%%%
--spec liveness([func_id()], st_map()) ->
- [{func_id(), #{func_id() => {beam_ssa:label(), #liveness_st{}}}}].
-
-liveness([F|Funs], StMap) ->
- Liveness = liveness_fun(F, StMap),
- [{F,Liveness}|liveness(Funs, StMap)];
-liveness([], _StMap) ->
- [].
-
-liveness_fun(F, StMap0) ->
- #opt_st{ssa=SSA} = map_get(F, StMap0),
- State0 = #{Lbl => #liveness_st{} || {Lbl,_} <- SSA},
- UseDefCache = liveness_make_cache(SSA),
- liveness_blks_fixp(reverse(SSA), State0, false, UseDefCache).
-
-liveness_blks_fixp(_SSA, State0, State0, _UseDefCache) ->
- State0;
-liveness_blks_fixp(SSA, State0, _Old, UseDefCache) ->
- State = liveness_blks(SSA, State0, UseDefCache),
- liveness_blks_fixp(SSA, State, State0, UseDefCache).
-
-liveness_blks([{Lbl,Blk}|Blocks], State0, UseDefCache) ->
- OutOld = get_live_out(Lbl, State0),
- #{Lbl:={Defs,Uses}} = UseDefCache,
- In = sets:union(Uses, sets:subtract(OutOld, Defs)),
- Out = successor_live_ins(Blk, State0),
- liveness_blks(Blocks, set_block_liveness(Lbl, In, Out, State0),
- UseDefCache);
-liveness_blks([], State0, _UseDefCache) ->
- State0.
-
-get_live_in(Lbl, State) ->
- #liveness_st{in=In} = map_get(Lbl, State),
- In.
-
-get_live_out(Lbl, State) ->
- #liveness_st{out=Out} = map_get(Lbl, State),
- Out.
-
-set_block_liveness(Lbl, In, Out, State) ->
- L = map_get(Lbl, State),
- State#{Lbl => L#liveness_st{in=In,out=Out}}.
-
-successor_live_ins(Blk, State) ->
- foldl(fun(Lbl, Acc) ->
- sets:union(Acc, get_live_in(Lbl, State))
- end, sets:new([{version,2}]), beam_ssa:successors(Blk)).
+killsets(Funs, StMap) ->
+ OptStates = [{F,map_get(F, StMap)} || F <- Funs],
+ #{ F=>killsets_fun(reverse(SSA)) || {F,#opt_st{ssa=SSA}} <- OptStates }.
-blk_defs(#b_blk{is=Is}) ->
- foldl(fun(#b_set{dst=Dst}, Acc) ->
- sets:add_element(Dst, Acc)
- end, sets:new([{version,2}]), Is).
+killsets_fun(Blocks) ->
+ %% Pre-calculate the live-ins due to Phi-instructions.
+ PhiLiveIns = killsets_phi_live_ins(Blocks),
+ killsets_blks(Blocks, #{}, #{}, PhiLiveIns).
-blk_effective_uses(#b_blk{is=Is,last=Last}) ->
- %% We can't use beam_ssa:used/1 on the whole block as it considers
- %% a use after a def a use and that will derail the liveness
- %% calculation.
- blk_effective_uses([Last|reverse(Is)], sets:new([{version,2}])).
+killsets_blks([{Lbl,Blk}|Blocks], LiveIns0, Kills0, PhiLiveIns) ->
+ {LiveIns,Kills} = killsets_blk(Lbl, Blk, LiveIns0, Kills0, PhiLiveIns),
+ killsets_blks(Blocks, LiveIns, Kills, PhiLiveIns);
+killsets_blks([], _LiveIns0, Kills, _PhiLiveIns) ->
+ Kills.
-blk_effective_uses([I|Is], Uses0) ->
- Uses = case I of
- #b_set{dst=Dst} ->
- %% The uses after the def do not count
- sets:del_element(Dst, Uses0);
- _ -> % A terminator, no defs
- Uses0
- end,
- LocalUses = sets:from_list(beam_ssa:used(I), [{version,2}]),
- blk_effective_uses(Is, sets:union(Uses, LocalUses));
-blk_effective_uses([], Uses) ->
- Uses.
+killsets_blk(Lbl, #b_blk{is=Is0,last=L}=Blk, LiveIns0, Kills0, PhiLiveIns) ->
+ Successors = beam_ssa:successors(Blk),
+ Live1 = killsets_blk_live_outs(Successors, Lbl, LiveIns0, PhiLiveIns),
+ Kills1 = Kills0#{{live_outs,Lbl}=>Live1},
+ Is = [L|reverse(Is0)],
+ {Live,Kills} = killsets_is(Is, Live1, Kills1, Lbl),
+ LiveIns = LiveIns0#{Lbl=>Live},
+ {LiveIns, Kills}.
-liveness_make_cache(SSA) ->
- liveness_make_cache(SSA, #{}).
+killsets_is([#b_set{op=phi,dst=Dst}|Is], Live, Kills, Lbl) ->
+ %% The Phi uses are logically located in the predecessors.
+ killsets_is(Is, sets:del_element(Dst, Live), Kills, Lbl);
+killsets_is([I|Is], Live0, Kills0, Lbl) ->
+ Uses = beam_ssa:used(I),
+ {Live,LastUses} =
+ foldl(fun(Use, {LiveAcc,LastAcc}=Acc) ->
+ case sets:is_element(Use, LiveAcc) of
+ true ->
+ Acc;
+ false ->
+ {sets:add_element(Use, LiveAcc),
+ sets:add_element(Use, LastAcc)}
+ end
+ end, {Live0,sets:new([{version,2}])}, Uses),
+ case I of
+ #b_set{dst=Dst} ->
+ killsets_is(Is, sets:del_element(Dst, Live),
+ killsets_add_kills(Dst, LastUses, Kills0), Lbl);
+ _ ->
+ killsets_is(Is, Live,
+ killsets_add_kills({terminator,Lbl}, LastUses, Kills0),
+ Lbl)
+ end;
+killsets_is([], Live, Kills, _) ->
+ {Live,Kills}.
-liveness_make_cache([{Lbl,Blk}|Blocks], Cache0) ->
- Defs = blk_defs(Blk),
- Uses = blk_effective_uses(Blk),
- Cache = Cache0#{Lbl=>{Defs,Uses}},
- liveness_make_cache(Blocks, Cache);
-liveness_make_cache([], Cache) ->
- Cache.
+killsets_add_kills(Dst, LastUses, Kills) ->
+ Kills#{Dst=>LastUses}.
%%%
-%%% Calculate the killset for all functions in the liveness
-%%% information.
+%%% Pre-calculate the live-ins due to Phi-instructions in order to
+%%% avoid having to repeatedly scan the first instruction(s) of a
+%%% basic block in order to find them when calculating live-in sets.
%%%
--spec killsets([{func_id(),
- #{func_id() => {beam_ssa:label(), #liveness_st{}}}}],
- st_map()) -> kills_map().
+killsets_phi_live_ins(Blocks) ->
+ killsets_phi_live_ins(Blocks, #{}).
-killsets(Liveness, StMap) ->
- #{F => kills_fun(F, StMap, Live) || {F, Live} <- Liveness}.
+killsets_phi_live_ins([{Lbl,#b_blk{is=Is}}|Blocks], PhiLiveIns0) ->
+ killsets_phi_live_ins(Blocks,
+ killsets_phi_uses_in_block(Lbl, Is, PhiLiveIns0));
+killsets_phi_live_ins([], PhiLiveIns) ->
+ PhiLiveIns.
-%%%
-%%% Calculate the killset for a function. The killset allows us to
-%%% look up the variables that die at a code location.
-%%%
-kills_fun(Fun, StMap, Liveness) ->
- #opt_st{ssa=SSA} = map_get(Fun, StMap),
- kills_fun1(SSA, #{}, Liveness).
+killsets_phi_uses_in_block(Lbl, [#b_set{op=phi,args=Args}|Is], PhiLiveIns0) ->
+ PhiLiveIns = foldl(fun({#b_var{}=Var,From}, Acc) ->
+ Key = {From,Lbl},
+ Old = case Acc of
+ #{Key:=O} -> O;
+ #{} -> sets:new([{version,2}])
+ end,
+ Acc#{Key=>sets:add_element(Var, Old)};
+ ({#b_literal{},_},Acc) ->
+ Acc
+ end, PhiLiveIns0, Args),
+ killsets_phi_uses_in_block(Lbl, Is, PhiLiveIns);
+killsets_phi_uses_in_block(_Lbl, _, PhiLiveIns) ->
+ %% No more phis.
+ PhiLiveIns.
-kills_fun1([{Lbl,Blk}|Blocks], KillsMap0, Liveness) ->
- KillsMap = kills_block(Lbl, Blk, map_get(Lbl, Liveness), KillsMap0),
- kills_fun1(Blocks, KillsMap, Liveness);
-kills_fun1([], KillsMap, _) ->
- KillsMap.
+%% Create a set of variables which are live out from this block.
+killsets_blk_live_outs(Successors, ThisBlock, LiveIns, PhiLiveIns) ->
+ killsets_blk_live_outs(Successors, ThisBlock, LiveIns,
+ PhiLiveIns, sets:new([{version,2}])).
-kills_block(Lbl, #b_blk{is=Is,last=Last}, #liveness_st{out=Out}, KillsMap0) ->
- kills_is([Last|reverse(Is)], Out, KillsMap0, Lbl).
+killsets_blk_live_outs([Successor|Successors],
+ ThisBlock, LiveIns, PhiLiveIns, Acc0) ->
+ Acc = case LiveIns of
+ #{Successor:=LI} ->
+ Tmp = sets:union(Acc0, LI),
+ case PhiLiveIns of
+ #{{ThisBlock,Successor}:=PhiUses} ->
+ sets:union(Tmp, PhiUses);
+ #{} ->
+ Tmp
+ end;
+ #{} ->
+ %% This is a back edge, we can ignore it as it only occurs
+ %% in combination with a receive.
+ Acc0
+ end,
+ killsets_blk_live_outs(Successors, ThisBlock, LiveIns, PhiLiveIns, Acc);
+killsets_blk_live_outs([], _, _, _, Acc) ->
+ Acc.
-kills_is([I|Is], Live0, KillsMap0, Blk) ->
- {Live, Key} = case I of
- #b_set{dst=Dst} ->
- {sets:del_element(Dst, Live0), Dst};
- _ ->
- {Live0, {terminator, Blk}}
- end,
- Uses = sets:from_list(beam_ssa:used(I), [{version,2}]),
- RemainingUses = sets:union(Live0, Uses),
- Killed = sets:subtract(RemainingUses, Live0),
- KillsMap = KillsMap0#{Key => Killed},
- kills_is(Is, sets:union(Live, Killed), KillsMap, Blk);
-kills_is([], _, KillsMap, _) ->
- KillsMap.
+%%%
%%%
%%% Perform an alias analysis of the given functions, alias
--
2.35.3