%% -*- erlang-indent-level: 2 -*- %% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2016. 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% %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%@doc %% PREPASS FOR ITERATED REGISTER ALLOCATORS %% %% Implements a trivial partial but optimal fast register allocator to be used %% as the first pass of the register allocation loop. %% %% The idea is to drastically reduce the number of temporaries, so as to speed %% up the real register allocators. %% %% * Spills trivially unallocatable temps %% This relies on the fact that calls intentionally clobber all registers. %% Since this is the case, any temp that is alive over a call can't possibly %% be allocated to anything but a spill slot. %% %% * Partitions the program at points where no pseudos that were not spiled are %% live, and then do register allocation on these partitions independently. %% These program points are commonly, but not exclusively, the call %% instructions. %% %% TODO %% * This module seems very successful at finding every single spill; register %% allocation performance should be improved if we short-circuit the first %% hipe_regalloc_loop iteration, skipping directly to rewrite without ever %% calling RegAllocMod. -module(hipe_regalloc_prepass). -export([regalloc/6]). -ifndef(DEBUG). -compile(inline). -endif. %%-define(DO_ASSERT, 1). -include("../main/hipe.hrl"). %%% TUNABLES %% Partitions with fewer than ?TUNE_TOO_FEW_BBS basic block halves are merged %% together before register allocation. -define(TUNE_TOO_FEW_BBS, 256). %% Ignore the ra_partitioned option (and do whole function RA instead) when %% there are fewer than ?TUNE_MIN_SPLIT_PSEUDOS non-spilled used pseudos. -define(TUNE_MIN_SPLIT_PSEUDOS, 1024). %% We present a "pseudo-target" to the register allocator we wrap. %% Note: all arities are +1 as we're currently using the parameterised module %% facility to store context data. -export([analyze/2, all_precoloured/1, allocatable/1, args/2, bb/3, def_use/2, defines/2, is_fixed/2, % used by hipe_graph_coloring_regalloc is_global/2, is_move/2, is_precoloured/2, labels/2, livein/3, liveout/3, non_alloc/2, number_of_temporaries/2, physical_name/2, postorder/2, reg_nr/2, uses/2, var_range/2, reverse_postorder/2]). %% Eww, parameterised module. Can we fix it without having to touch all the %% register allocators? -record(?MODULE, {target :: module() ,sub :: sub_map() % Translates temp numbers found in CFG and understood by % Target to temp numbers passed to RegAllocMod. ,inv :: inv_map() % Translates temp numbers passed to RegAllocMod % to temp numbers found in CFG and understood by % Target ,max_phys :: temp() % Exclusive upper bound on physical registers }). -record(cfg, {cfg :: target_cfg() ,bbs :: transformed_bbs() ,max_reg :: temp() % Exclusive upper bound on temp numbers ,rpostorder :: undefined % Only precomputed with partitioned cfg | [label()] }). -type bb() :: hipe_bb:bb(). % containing instr() -type liveset() :: ordsets:ordset(temp()). -record(transformed_bb, {bb :: bb() ,livein :: liveset() ,liveout :: liveset() }). -type transformed_bb() :: #transformed_bb{}. -type transformed_bbs() :: #{label() => transformed_bb()}. -record(instr, {defuse :: {[temp()], [temp()]} ,is_move :: boolean() }). -type instr() :: #instr{}. -type target_cfg() :: any(). -type target_instr() :: any(). -type target_temp() :: any(). -type target_reg() :: non_neg_integer(). -type target_liveness() :: any(). -type target_liveset() :: ordsets:ordset(target_reg()). -type spillno() :: non_neg_integer(). -type temp() :: non_neg_integer(). -type label() :: non_neg_integer(). -spec regalloc(module(), target_cfg(), spillno(), spillno(), module(), proplists:proplist()) -> {hipe_map(), spillno(), target_liveness()}. regalloc(RegAllocMod, CFG, SpillIndex0, SpillLimit, Target, Options) -> Liveness = Target:analyze(CFG), {ScanBBs, Seen, SpillMap, SpillIndex1} = scan_cfg(CFG, Liveness, SpillIndex0, Target), AllPrecoloured = Target:all_precoloured(), {PartColoring, SpillIndex} = case proplists:get_bool(ra_partitioned, Options) of true when map_size(Seen) - map_size(SpillMap) - length(AllPrecoloured) > ?TUNE_MIN_SPLIT_PSEUDOS -> regalloc_partitioned(SpillMap, SpillIndex1, SpillLimit, ScanBBs, CFG, Target, RegAllocMod, Options); _ -> regalloc_whole(Seen, SpillMap, SpillIndex1, SpillLimit, ScanBBs, CFG, Target, RegAllocMod, Options) end, SpillColors = [{T, {spill, S}} || {T, S} <- maps:to_list(SpillMap)], Coloring = SpillColors ++ PartColoring, ?ASSERT(begin MaxPhys = lists:max(AllPrecoloured) + 1, Unused = unused(live_pseudos(Seen, SpillMap, MaxPhys), SpillMap, CFG, Target), unused_unused(Unused, CFG, Target) end), ?ASSERT(check_coloring(Coloring, CFG, Target)), % Sanity-check ?ASSERT(just_as_good_as(RegAllocMod, CFG, SpillIndex0, SpillLimit, Target, Options, Coloring, Unused)), {Coloring, SpillIndex, Liveness}. regalloc_whole(Seen, SpillMap, SpillIndex0, SpillLimit, ScanBBs, CFG, Target, RegAllocMod, Options) -> AllPrecoloured = Target:all_precoloured(), MaxPhys = lists:max(AllPrecoloured) + 1, LivePseudos = live_pseudos(Seen, SpillMap, MaxPhys), {SubMap, InvMap, MaxPhys, MaxR, SubSpillLimit} = number_and_map(AllPrecoloured, LivePseudos, SpillLimit), BBs = transform_whole_cfg(ScanBBs, SubMap), SubMod = #cfg{cfg=CFG, bbs=BBs, max_reg=MaxR}, SubTarget = #?MODULE{target=Target, max_phys=MaxPhys, inv=InvMap, sub=SubMap}, {SubColoring, SpillIndex, _} = RegAllocMod:regalloc(SubMod, SpillIndex0, SubSpillLimit, SubTarget, Options), ?ASSERT(check_coloring(SubColoring, SubMod, SubTarget)), {translate_coloring(SubColoring, InvMap), SpillIndex}. regalloc_partitioned(SpillMap, SpillIndex0, SpillLimit, ScanBBs, CFG, Target, RegAllocMod, Options) -> AllPrecoloured = Target:all_precoloured(), MaxPhys = lists:max(AllPrecoloured) + 1, DSets0 = initial_dsets(CFG, Target), PartBBList = part_cfg(ScanBBs, SpillMap, MaxPhys), DSets1 = join_whole_blocks(PartBBList, DSets0), {PartBBsRLList, DSets2} = merge_small_parts(DSets1), {PartBBs, DSets3} = merge_pointless_splits(PartBBList, ScanBBs, DSets2), SeenMap = collect_seenmap(PartBBsRLList, PartBBs), {RPostMap, _DSets4} = part_order(Target:reverse_postorder(CFG), DSets3), {Allocations, SpillIndex} = lists:mapfoldl( fun({Root, Elems}, SpillIndex1) -> #{Root := Seen} = SeenMap, #{Root := RPost} = RPostMap, LivePseudos = live_pseudos(Seen, SpillMap, MaxPhys), {SubMap, InvMap, MaxPhys, MaxR, SubSpillLimit} = number_and_map(AllPrecoloured, LivePseudos, SpillLimit), BBs = transform_cfg(Elems, PartBBs, SubMap), SubMod = #cfg{cfg=CFG, bbs=BBs, max_reg=MaxR, rpostorder=RPost}, SubTarget = #?MODULE{target=Target, max_phys=MaxPhys, inv=InvMap, sub=SubMap}, {SubColoring, SpillIndex2, _} = RegAllocMod:regalloc(SubMod, SpillIndex1, SubSpillLimit, SubTarget, Options), ?ASSERT(check_coloring(SubColoring, SubMod, SubTarget)), {translate_coloring(SubColoring, InvMap), SpillIndex2} end, SpillIndex0, PartBBsRLList), {combine_allocations(Allocations, MaxPhys), SpillIndex}. -spec number_and_map([target_reg()], target_liveset(), target_reg()) -> {sub_map(), inv_map(), temp(), temp(), temp()}. number_and_map(Phys, Pseud, SpillLimit) -> MaxPhys = lists:max(Phys) + 1, ?ASSERT(Pseud =:= [] orelse lists:min(Pseud) >= MaxPhys), NrPseuds = length(Pseud), MaxR = MaxPhys+NrPseuds, PseudNrs = lists:zip(Pseud, lists:seq(MaxPhys, MaxR-1)), MapList = lists:zip(Phys, Phys) % Physicals are identity-mapped ++ PseudNrs, ?ASSERT(MapList =:= lists:ukeysort(1, MapList)), SubMap = {s,maps:from_list(MapList)}, InvMap = {i,maps:from_list([{Fake, Real} || {Real, Fake} <- MapList])}, SubSpillLimit = translate_spill_limit(MapList, SpillLimit), {SubMap, InvMap, MaxPhys, MaxR, SubSpillLimit}. -spec translate_spill_limit([{target_reg(), temp()}], target_reg()) -> temp(). translate_spill_limit([{Real,Fake}], SpillLimit) when Real < SpillLimit -> Fake + 1; translate_spill_limit([{Real,_}|Ps], SpillLimit) when Real < SpillLimit -> translate_spill_limit(Ps, SpillLimit); translate_spill_limit([{Real,Fake}|_], SpillLimit) when Real >= SpillLimit -> Fake. -spec live_pseudos(seen(), spill_map(), target_reg()) -> target_liveset(). live_pseudos(Seen, SpillMap, MaxPhys) -> %% When SpillMap is much larger than Seen (which is typical in the partitioned %% case), it is much more efficient doing it like this than making an ordset %% of the spills and subtracting. ordsets:from_list( lists:filter(fun(R) -> R >= MaxPhys andalso not maps:is_key(R, SpillMap) end, maps:keys(Seen))). -spec translate_coloring(hipe_map(), inv_map()) -> hipe_map(). translate_coloring(SubColoring, InvMap) -> lists:map(fun({T, P}) -> {imap_get(T, InvMap), P} end, SubColoring). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% First pass %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Spill trivially unallocatable temps, create internal target-independent %% program representation, and collect a set of all used temps. -record(spill_state, {map :: spill_map() ,ix :: spillno() }). -type spill_state() :: #spill_state{}. -type spill_map() :: #{target_reg() => spillno()}. -spec scan_cfg(target_cfg(), target_liveness(), spillno(), module()) -> {scan_bbs() ,seen() ,spill_map() ,spillno() }. scan_cfg(CFG, Liveness, SpillIndex0, Target) -> State0 = #spill_state{map=#{}, ix=SpillIndex0}, {BBs, Seen, #spill_state{map=Spill, ix=SpillIndex}} = scan_bbs(Target:labels(CFG), CFG, Liveness, #{}, State0, #{}, Target), {BBs, Seen, Spill, SpillIndex}. -type seen() :: #{target_reg() => []}. % set -type scan_bb() :: {[instr()], target_liveset(), target_liveset()}. -type scan_bbs() :: #{label() => scan_bb()}. -spec scan_bbs([label()], target_cfg(), target_liveness(), seen(), spill_state(), scan_bbs(), module()) -> {scan_bbs(), seen(), spill_state()}. scan_bbs([], _CFG, _Liveness, Seen, State, BBs, _Target) -> {BBs, Seen, State}; scan_bbs([L|Ls], CFG, Liveness, Seen0, State0, BBs, Target) -> Liveout = t_liveout(Liveness, L, Target), {Code, Livein, Seen, State} = scan_bb(lists:reverse(hipe_bb:code(Target:bb(CFG, L))), Liveout, Seen0, State0, [], Target), BB = {Code, Livein, Liveout}, scan_bbs(Ls, CFG, Liveness, Seen, State, BBs#{L=>BB}, Target). -spec scan_bb([target_instr()], target_liveset(), seen(), spill_state(), [instr()], module()) -> {[instr()] ,target_liveset() ,seen() ,spill_state() }. scan_bb([], Live, Seen, State, IAcc, _Target) -> {IAcc, Live, Seen, State}; scan_bb([I|Is], Live0, Seen0, State0, IAcc0, Target) -> {TDef, TUse} = Target:def_use(I), ?ASSERT(TDef =:= Target:defines(I)), ?ASSERT(TUse =:= Target:uses(I)), Def = ordsets:from_list(reg_names(TDef, Target)), Use = ordsets:from_list(reg_names(TUse, Target)), Live = ordsets:union(Use, ToSpill = ordsets:subtract(Live0, Def)), Seen = add_seen(Def, add_seen(Use, Seen0)), NewI = #instr{defuse={Def, Use}, is_move=Target:is_move(I)}, IAcc = [NewI|IAcc0], State = case Target:defines_all_alloc(I) of false -> State0; true -> spill_all(ToSpill, Target, State0) end, %% We can drop "no-ops" here; where (if anywhere) is it worth it? scan_bb(Is, Live, Seen, State, IAcc, Target). -spec t_liveout(target_liveness(), label(), module()) -> target_liveset(). t_liveout(Liveness, L, Target) -> %% FIXME: unnecessary sort; liveout is sorted, reg_names(...) should be sorted %% or consist of a few sorted subsequences (per type) ordsets:from_list(reg_names(Target:liveout(Liveness, L), Target)). -spec reg_names([target_temp()], module()) -> [target_reg()]. reg_names(Regs, Target) -> [Target:reg_nr(X) || X <- Regs]. -spec add_seen([target_reg()], seen()) -> seen(). add_seen([], Seen) -> Seen; add_seen([R|Rs], Seen) -> add_seen(Rs, Seen#{R=>[]}). -spec spill_all([target_reg()], module(), spill_state()) -> spill_state(). spill_all([], _Target, State) -> State; spill_all([R|Rs], Target, State=#spill_state{map=Map, ix=Ix}) -> case Target:is_precoloured(R) or maps:is_key(R, Map) of true -> spill_all(Rs, Target, State); false -> spill_all(Rs, Target, State#spill_state{map=Map#{R=>Ix}, ix=Ix+1}) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Second pass (without split) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Rewrite CFG to the new temp names. -spec transform_whole_cfg(scan_bbs(), sub_map()) -> transformed_bbs(). transform_whole_cfg(BBs0, SubMap) -> maps:map(fun(_, BB) -> transform_whole_bb(BB, SubMap) end, BBs0). -spec transform_whole_bb(scan_bb(), sub_map()) -> transformed_bb(). transform_whole_bb({Code, Livein, Liveout}, SubMap) -> #transformed_bb{ bb=hipe_bb:mk_bb([I#instr{defuse={smap_get_all_partial(Def, SubMap), smap_get_all_partial(Use, SubMap)}} || I = #instr{defuse={Def,Use}} <- Code]) %% Assume mapping preserves monotonicity ,livein=smap_get_all_partial(Livein, SubMap) ,liveout=smap_get_all_partial(Liveout, SubMap) }. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Second pass (with split) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Discover program partitioning %% Regretfully, this needs to be a separate pass, as having the global live set %% is crucial to get a useful partitioning. %% Single-block parts are merged if there are multiple in a single block, as it %% is judged to not be beneficial to make them too small. -type part_bb_part() :: {[instr()], target_liveset(), target_liveset()}. -type part_bb() :: {single, part_bb_part()} | {split, part_bb_part(), part_bb_part()}. -type part_bb_list() :: [{label(), part_bb()}]. -type part_bbs() :: #{label() => part_bb()}. -type part_bb_sofar() :: single | {split, [instr()], target_liveset()}. % , target_liveset() -spec part_cfg(scan_bbs(), spill_map(), target_reg()) -> part_bb_list(). part_cfg(ScanBBs, SpillMap, MaxPhys) -> Liveset = mk_part_liveset(SpillMap, MaxPhys), lists:map(fun(BB) -> part_bb(BB, Liveset) end, maps:to_list(ScanBBs)). -spec part_bb({label(), scan_bb()}, part_liveset()) -> {label(), part_bb()}. part_bb({L, BB0={Code0, Livein, Liveout}}, Liveset) -> {Sofar, NewCode} = part_bb_1(lists:reverse(Code0), Liveset, Liveout, []), BB = case Sofar of single -> ?ASSERT(Code0 =:= NewCode), {single, BB0}; {split, ExitCode, ExitLivein = EntryLiveout} -> {split, {NewCode, Livein, EntryLiveout}, {ExitCode, ExitLivein, Liveout}} end, {L, BB}. -spec part_bb_1([instr()], part_liveset(), target_liveset(), [instr()]) -> {part_bb_sofar(), [instr()]}. part_bb_1([], _Liveset, _Livein, IAcc) -> {single, IAcc}; part_bb_1([I=#instr{defuse={Def,Use}}|Is], Liveset, Live0, IAcc0) -> Live = ordsets:union(Use, ordsets:subtract(Live0, Def)), IAcc = [I|IAcc0], case part_none_live(Live, Liveset) of false -> part_bb_1(Is, Liveset, Live, IAcc); %% One split point will suffice true -> {{split, IAcc, Live}, lists:reverse(Is)} end. -spec part_none_live(target_liveset(), part_liveset()) -> boolean(). part_none_live(Live, Liveset) -> not lists:any(fun(R) -> part_liveset_is_live(R, Liveset) end, Live). -type part_liveset() :: {spill_map(), target_reg()}. -spec mk_part_liveset(spill_map(), target_reg()) -> part_liveset(). mk_part_liveset(SpillMap, MaxPhys) -> {SpillMap, MaxPhys}. -spec part_liveset_is_live(target_reg(), part_liveset()) -> boolean(). part_liveset_is_live(R, {SpillMap, MaxPhys}) when is_integer(R) -> R >= MaxPhys andalso not maps:is_key(R, SpillMap). %% @doc Merges split blocks where entry and exit belong to the same DSet. %% Does not change DSets -spec merge_pointless_splits(part_bb_list(), scan_bbs(), bb_dsets()) -> {part_bbs(), bb_dsets()}. merge_pointless_splits(PartBBList0, ScanBBs, DSets0) -> {PartBBList, DSets} = merge_pointless_splits_1(PartBBList0, ScanBBs, DSets0, []), {maps:from_list(PartBBList), DSets}. -spec merge_pointless_splits_1( part_bb_list(), scan_bbs(), bb_dsets(), part_bb_list()) -> {part_bb_list(), bb_dsets()}. merge_pointless_splits_1([], _ScanBBs, DSets, Acc) -> {Acc, DSets}; merge_pointless_splits_1([P={_,{single,_}}|Ps], ScanBBs, DSets, Acc) -> merge_pointless_splits_1(Ps, ScanBBs, DSets, [P|Acc]); merge_pointless_splits_1([P0={L,{split,_,_}}|Ps], ScanBBs, DSets0, Acc) -> {EntryRoot, DSets1} = dsets_find({entry,L}, DSets0), {ExitRoot, DSets} = dsets_find({exit,L}, DSets1), case EntryRoot =:= ExitRoot of false -> merge_pointless_splits_1(Ps, ScanBBs, DSets, [P0|Acc]); true -> %% Reuse the code list from ScanBBs rather than concatenating the split %% parts #{L := BB} = ScanBBs, ?ASSERT(begin {L,{split,{_EntryCode,_,_},{_ExitCode,_,_}}}=P0, % [_| {_Code,_,_}=BB, _Code =:= (_EntryCode ++ _ExitCode) end), merge_pointless_splits_1(Ps, ScanBBs, DSets, [{L,{single, BB}}|Acc]) end. -spec merge_small_parts(bb_dsets()) -> {bb_dsets_rllist(), bb_dsets()}. merge_small_parts(DSets0) -> {RLList, DSets1} = dsets_to_rllist(DSets0), RLLList = [{R, length(Elems), Elems} || {R, Elems} <- RLList], merge_small_parts_1(RLLList, DSets1, []). -spec merge_small_parts_1( [{bb_dset_key(), non_neg_integer(), [bb_dset_key()]}], bb_dsets(), bb_dsets_rllist() ) -> {bb_dsets_rllist(), bb_dsets()}. merge_small_parts_1([], DSets, Acc) -> {Acc, DSets}; merge_small_parts_1([{R, _, Es}], DSets, Acc) -> {[{R, Es}|Acc], DSets}; merge_small_parts_1([{R, L, Es}|Ps], DSets, Acc) when L >= ?TUNE_TOO_FEW_BBS -> merge_small_parts_1(Ps, DSets, [{R,Es}|Acc]); merge_small_parts_1([Fst,{R, L, Es}|Ps], DSets, Acc) when L >= ?TUNE_TOO_FEW_BBS -> merge_small_parts_1([Fst|Ps], DSets, [{R,Es}|Acc]); merge_small_parts_1([{R1,L1,Es1},{R2,L2,Es2}|Ps], DSets0, Acc) -> ?ASSERT(L1 < ?TUNE_TOO_FEW_BBS andalso L2 < ?TUNE_TOO_FEW_BBS), DSets1 = dsets_union(R1, R2, DSets0), {R, DSets} = dsets_find(R1, DSets1), merge_small_parts_1([{R,L2+L1,Es2++Es1}|Ps], DSets, Acc). %% @doc Partition an ordering over BBs into subsequences for the dsets that %% contain them. %% Does not change dsets. -spec part_order([label()], bb_dsets()) -> {#{bb_dset_key() => [label()]}, bb_dsets()}. part_order(Lbs, DSets) -> part_order(Lbs, DSets, #{}). part_order([], DSets, Acc) -> {Acc, DSets}; part_order([L|Ls], DSets0, Acc0) -> {EntryRoot, DSets1} = dsets_find({entry,L}, DSets0), {ExitRoot, DSets2} = dsets_find({exit,L}, DSets1), Acc1 = map_append(EntryRoot, L, Acc0), %% Only include the label once if both entry and exit is in same partition Acc2 = case EntryRoot =:= ExitRoot of true -> Acc1; false -> map_append(ExitRoot, L, Acc1) end, part_order(Ls, DSets2, Acc2). map_append(Key, Elem, Map) -> case Map of #{Key := List} -> Map#{Key := [Elem|List]}; #{} -> Map#{Key => [Elem]} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Interference graph partitioning %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% We partition the program %% The algorithm considers two kinds of components; those that are local to a %% basic block, and those that are not. The key is that any basic block belongs %% to at most two non-local components; one from the beginning to the first %% split point, and one from the end to the last split point. -type bb_dset_key() :: {entry | exit, label()}. -type bb_dsets() :: dsets(bb_dset_key()). -type bb_dsets_rllist() :: [{bb_dset_key(), [bb_dset_key()]}]. -spec initial_dsets(target_cfg(), module()) -> bb_dsets(). initial_dsets(CFG, Target) -> Labels = Target:labels(CFG), DSets0 = dsets_new(lists:append([[{entry,L},{exit,L}] || L <- Labels])), Edges = lists:append([[{L, S} || S <- hipe_gen_cfg:succ(CFG, L)] || L <- Labels]), lists:foldl(fun({X, Y}, DS) -> dsets_union({exit,X}, {entry,Y}, DS) end, DSets0, Edges). -spec join_whole_blocks(part_bb_list(), bb_dsets()) -> bb_dsets(). join_whole_blocks(PartBBList, DSets0) -> lists:foldl(fun({L, {single, _}}, DS) -> dsets_union({entry,L}, {exit,L}, DS); ({_, {split, _, _}}, DS) -> DS end, DSets0, PartBBList). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% The disjoint set forests data structure, for elements of arbitrary types. %% Note that the find operation mutates the set. %% %% We could do this more efficiently if we restricted the elements to integers, %% and used the (mutable) hipe arrays. For arbitrary terms ETS could be used, %% for a persistent interface (which isn't that nice when even accessors return %% modified copies), the array module could be used. -type dsets(X) :: #{X => {node, X} | {root, non_neg_integer()}}. -spec dsets_new([E]) -> dsets(E). dsets_new(Elems) -> maps:from_list([{E,{root,0}} || E <- Elems]). -spec dsets_find(E, dsets(E)) -> {E, dsets(E)}. dsets_find(E, DS0) -> case DS0 of #{E := {root,_}} -> {E, DS0}; #{E := {node,N}} -> case dsets_find(N, DS0) of {N, _}=T -> T; {R, DS1} -> {R, DS1#{E := {node,R}}} end ;_ -> error(badarg, [E, DS0]) end. -spec dsets_union(E, E, dsets(E)) -> dsets(E). dsets_union(X, Y, DS0) -> {XRoot, DS1} = dsets_find(X, DS0), case dsets_find(Y, DS1) of {XRoot, DS2} -> DS2; {YRoot, DS2} -> #{XRoot := {root,XRR}, YRoot := {root,YRR}} = DS2, if XRR < YRR -> DS2#{XRoot := {node,YRoot}}; XRR > YRR -> DS2#{YRoot := {node,XRoot}}; true -> DS2#{YRoot := {node,XRoot}, XRoot := {root,XRR+1}} end end. -spec dsets_to_rllist(dsets(E)) -> {[{Root::E, Elems::[E]}], dsets(E)}. dsets_to_rllist(DS0) -> {Lists, DS} = dsets_to_rllist(maps:keys(DS0), #{}, DS0), {maps:to_list(Lists), DS}. dsets_to_rllist([], Acc, DS) -> {Acc, DS}; dsets_to_rllist([E|Es], Acc, DS0) -> {ERoot, DS} = dsets_find(E, DS0), dsets_to_rllist(Es, map_append(ERoot, E, Acc), DS). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Third pass %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Collect all referenced temps in each partition. %% Note: The temps could be collected during the partition pass for each %% half-bb, and then combined here. Would that be beneficial? collect_seenmap(PartBBsRLList, PartBBs) -> collect_seenmap(PartBBsRLList, #{}, PartBBs). collect_seenmap([], Acc, _PartBBs) -> Acc; collect_seenmap([{R,Elems}|Ps], Acc, PartBBs) -> Seen = collect_seen_part(Elems, #{}, PartBBs), collect_seenmap(Ps, Acc#{R => Seen}, PartBBs). collect_seen_part([], Acc, _PartBBs) -> Acc; collect_seen_part([{Half,L}|Es], Acc0, PartBBs) -> BB = maps:get(L, PartBBs), Code = case {Half, BB} of {entry, {single, {C,_,_}}} -> C; {entry, {split, {C,_,_}, _}} -> C; {exit, {split, _, {C,_,_}}} -> C; {exit, {single, _}} -> [] % Ignore; was collected by its entry half end, Acc = collect_seen_code(Code, Acc0), collect_seen_part(Es, Acc, PartBBs). collect_seen_code([], Acc) -> Acc; collect_seen_code([#instr{defuse={Def,Use}}|Is], Acc) -> collect_seen_code(Is, add_seen(Def, add_seen(Use, Acc))). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Fourth pass %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Rewrite CFG to the new temp names. -spec transform_cfg([bb_dset_key()], part_bbs(), sub_map()) -> transformed_bbs(). transform_cfg(Elems, PartBBs, SubMap) -> transform_cfg(Elems, PartBBs, SubMap, #{}). transform_cfg([], _PartBBs, _SubMap, Acc) -> Acc; transform_cfg([{Half,L}|Es], PartBBs, SubMap, Acc0) -> #{L := PBB} = PartBBs, Acc = case {Half, PBB} of {entry, {single,BB}} -> Acc0#{L=>transform_bb(BB, SubMap)}; {entry, {split,BB,_}} -> Acc0#{L=>transform_bb(BB, SubMap)}; {exit, {split,_,BB}} -> Acc0#{L=>transform_bb(BB, SubMap)}; {exit, {single, _}} -> Acc0 % Was included by the entry half end, transform_cfg(Es, PartBBs, SubMap, Acc). -spec transform_bb(part_bb_part(), sub_map()) -> transformed_bb(). transform_bb(BB, SubMap) -> %% For now, part_bb_part() and split_bb() share representation transform_whole_bb(BB, SubMap). %% combine_allocations([A|As], MaxPhys) -> {Phys, Pseuds} = lists:partition(fun({R,_}) -> R < MaxPhys end, A), combine_allocations_1(As, MaxPhys, Phys, Pseuds). combine_allocations_1([], _MaxPhys, Phys, Pseuds) -> Phys ++ Pseuds; combine_allocations_1([A|As], MaxPhys, Phys, Acc) -> {Phys, Pseuds} = lists:partition(fun({R,_}) -> R < MaxPhys end, A), combine_allocations_1(As, MaxPhys, Phys, Pseuds ++ Acc). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Temp map ADT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -type sub_map() :: {s,#{target_reg() => temp()}}. -type inv_map() :: {i,#{temp() => target_reg()}}. -spec smap_get(target_reg(), sub_map()) -> temp(). smap_get(Temp, {s,Map}) when is_integer(Temp) -> maps:get(Temp, Map). -spec imap_get(temp(), inv_map()) -> target_reg(). imap_get(Temp, {i,Map}) when is_integer(Temp) -> maps:get(Temp, Map). -spec smap_get_all_partial([target_reg()], sub_map()) -> [temp()]. smap_get_all_partial([], _) -> []; smap_get_all_partial([T|Ts], SMap={s,Map}) when is_integer(T) -> case Map of #{T := R} -> [R|smap_get_all_partial(Ts, SMap)]; #{} -> smap_get_all_partial(Ts, SMap) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Validation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -ifdef(DO_ASSERT). %%%%%%%%%%%%%%%%%%%% %% Check that the coloring is correct (if the IG is correct): %% %% Define these as 'ok' or 'report(X,Y)' depending on how much output you want. -define(report0(X,Y), ?IF_DEBUG_LEVEL(0,?msg(X, Y),ok)). -define(report(X,Y), ?IF_DEBUG_LEVEL(1,?msg(X, Y),ok)). -define(report2(X,Y), ?IF_DEBUG_LEVEL(2,?msg(X, Y),ok)). -define(report3(X,Y), ?IF_DEBUG_LEVEL(3,?msg(X, Y),ok)). check_coloring(Coloring, CFG, Target) -> ?report0("checking coloring ~p~n",[Coloring]), IG = hipe_ig:build(CFG, Target:analyze(CFG), Target), check_cols(hipe_vectors:list(hipe_ig:adj_list(IG)), init_coloring(Coloring, Target)). init_coloring(Xs, Target) -> hipe_temp_map:cols2tuple(Xs, Target). check_color_of(X, Cols) -> case hipe_temp_map:find(X, Cols) of unknown -> uncolored; C -> C end. check_cols([], _Cols) -> ?report("coloring valid~n",[]), true; check_cols([{X,Neighbours}|Xs], Cols) -> Cs = [{N, check_color_of(N, Cols)} || N <- Neighbours], C = check_color_of(X, Cols), case valid_coloring(X, C, Cs) of yes -> check_cols(Xs, Cols); {no,Invalids} -> ?msg("node ~p has same color (~p) as ~p~n", [X,C,Invalids]), check_cols(Xs, Cols) andalso false end. valid_coloring(_X, _C, []) -> yes; valid_coloring(X, C, [{Y,C}|Ys]) -> case valid_coloring(X, C, Ys) of yes -> {no, [Y]}; {no,Zs} -> {no, [Y|Zs]} end; valid_coloring(X, C, [_|Ys]) -> valid_coloring(X, C, Ys). unused_unused(Unused, CFG, Target) -> IG = hipe_ig:build(CFG, Target:analyze(CFG), Target), lists:all(fun(R) -> case hipe_ig:get_node_degree(R, IG) of 0 -> true; Deg -> ?msg("Temp ~w is in unused but has degree ~w~n", [R, Deg]), false end end, Unused). %%%%%%%%%%%%%%%%%%%% %% Check that no register allocation opportunities were missed due to ?MODULE %% just_as_good_as(RegAllocMod, CFG, SpillIndex0, SpillLimit, Target, Options, Coloring, Unused) -> {CheckColoring, _, _} = RegAllocMod:regalloc(CFG, SpillIndex0, SpillLimit, Target, Options), Now = lists:sort([{R,Kind} || {R,{Kind,_}} <- Coloring, not ordsets:is_element(R, Unused)]), Check = lists:sort([{R,Kind} || {R,{Kind,_}} <- CheckColoring, not ordsets:is_element(R, Unused)]), CheckMap = maps:from_list(Check), case lists:all(fun({R, spill}) -> maps:get(R, CheckMap) =:= spill; ({_,reg}) -> true end, Now) of true -> true; false -> {NowRegs, _} = _NowCount = count(Now), {CheckRegs, _} = _CheckCount = count(Check), io:fwrite(standard_error, "Colorings differ (~w, ~w)!~n" "Unused: ~w~n" "Now:~w~nCorrect:~w~n", [Target, RegAllocMod, Unused, Now -- Check, Check -- Now]), NowRegs >= CheckRegs end. count(C) -> {length([[] || {_, reg} <- C]), length([[] || {_, spill} <- C])}. unused(LivePseudos, SpillMap, CFG, Target) -> {TMin, TMax} = Target:var_range(CFG), SpillOSet = ordsets:from_list(maps:keys(SpillMap)), PhysOSet = ordsets:from_list(Target:all_precoloured()), Used = ordsets:union(LivePseudos, ordsets:union(PhysOSet, SpillOSet)), ordsets:subtract(lists:seq(TMin, TMax), Used). -endif. % DO_ASSERT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Pseudo-target interface %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% analyze(Cfg, _ModRec) -> Cfg. bb(Cfg=#cfg{bbs=BBs}, Ix, _ModRec) -> case BBs of #{Ix := #transformed_bb{bb=BB}} -> BB; _ -> error(badarg, [Cfg, Ix]) end. args(Arity, #?MODULE{target=Target, sub=SubM}) -> smap_get(Target:args(Arity), SubM). labels(#cfg{bbs=BBs}, _ModRec) -> maps:keys(BBs). livein(#cfg{bbs=BBs}, Lb, _SubMod) -> #{Lb := #transformed_bb{livein=Livein}} = BBs, Livein. liveout(#cfg{bbs=BBs}, Lb, _SubMod) -> #{Lb := #transformed_bb{liveout=Liveout}} = BBs, Liveout. uses(I, MR) -> element(2, def_use(I, MR)). defines(I, MR) -> element(1, def_use(I, MR)). def_use(#instr{defuse=DefUse}, _ModRec) -> DefUse. is_move(#instr{is_move=IM}, _ModRec) -> IM. is_fixed(Reg, #?MODULE{target=Target,inv=InvM}) -> Target:is_fixed(imap_get(Reg, InvM)). % XXX: Is this hot? is_global(Reg, #?MODULE{target=Target,max_phys=MaxPhys}) when Reg < MaxPhys -> Target:is_global(Reg). % assume id-map is_precoloured(Reg, #?MODULE{max_phys=MaxPhys}) -> Reg < MaxPhys. reg_nr(Reg, _ModRec) -> Reg. % After mapping (naturally) non_alloc(#cfg{cfg=CFG}, #?MODULE{target=Target,sub=SubM}) -> smap_get_all_partial(reg_names(Target:non_alloc(CFG), Target), SubM). number_of_temporaries(#cfg{max_reg=MaxR}, _ModRec) -> MaxR. allocatable(#?MODULE{target=Target}) -> Target:allocatable(). % assume id-map physical_name(Reg, _ModRec) -> Reg. all_precoloured(#?MODULE{target=Target}) -> Target:all_precoloured(). % dito var_range(#cfg{cfg=_CFG, max_reg=MaxReg}, #?MODULE{target=_Target}) -> ?ASSERT(begin {TgtMin, _} = _Target:var_range(_CFG), TgtMin =:= 0 end), {0, MaxReg-1}. postorder(#cfg{cfg=CFG,rpostorder=undefined}, #?MODULE{target=Target}) -> Target:postorder(CFG); postorder(#cfg{rpostorder=Labels}, _ModRec) when is_list(Labels) -> lists:reverse(Labels). reverse_postorder(#cfg{cfg=CFG,rpostorder=undefined}, #?MODULE{target=Target}) -> Target:reverse_postorder(CFG); reverse_postorder(#cfg{rpostorder=Labels}, _ModRec) when is_list(Labels) -> Labels.