aboutsummaryrefslogblamecommitdiffstats
path: root/lib/hipe/regalloc/hipe_restore_reuse.erl
blob: 2158bd185e49fd71b7f35c55f754ec399f757e77 (plain) (tree)





































                                                                                







                                                                               






















                                                                                
                                            















































































































































































































































































                                                                                




                                                                  








































































































































































                                                                               
%% -*- erlang-indent-level: 2 -*-
%%
%% 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.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%@doc
%%	       RESTORE REUSE LIVE RANGE SPLITTING PASS
%%
%% This is a simple live range splitter that tries to avoid sequences where a
%% temporary is accessed on stack multiple times by keeping a copy of that temp
%% around in a register.
%%
%% At any point where a temporary that is expected to be spilled (see uses of
%% spills_add_list/2) is defined or used, this pass considers that temporary
%% "available".
%%
%% Limitations:
%%   * If a live range part starts with several different restores, this module
%%     will introduce a new temp number for each of them, and later be forced to
%%     generate phi blocks. It would be more efficient to introduce just a
%%     single temp number. That would also remove the need for the phi blocks.
%%   * If a live range part ends in a definition, that definition should just
%%     define the base temp rather than the substitution, since some CISC
%%     targets might be able to inline the memory access in the instruction.
-module(hipe_restore_reuse).

-export([split/4]).

%% Exports for hipe_range_split, which uses restore_reuse as one possible spill
%% "mode"
-export([analyse/3
	,renamed_in_block/2
	,split_in_block/2
	]).
-export_type([avail/0]).

-compile(inline).

%% -define(DO_ASSERT, 1).
-include("../main/hipe.hrl").

-type target_cfg()       :: any().
-type liveness()         :: any().
-type target_module()    :: module().
-type target_context()   :: any().
-type target()           :: {target_module(), target_context()}.
-type label()            :: non_neg_integer().
-type reg()              :: non_neg_integer().
-type instr()            :: any().
-type temp()             :: any().

-spec split(target_cfg(), liveness(), target_module(), target_context())
	   -> target_cfg().
split(CFG, Liveness, TargetMod, TargetContext) ->
  Target = {TargetMod, TargetContext},
  Avail = analyse(CFG, Liveness, Target),
  rewrite(CFG, Target, Avail).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-opaque avail() :: #{label() => avail_bb()}.

-record(avail_bb, {
	  %% Blocks where HasCall is true are considered to have too high
	  %% register pressure to support a register copy of a temp
	  has_call :: boolean(),
	  %% AvailOut: Temps that can be split (are available)
	  out      :: availset(),
	  %% Gen: AvailOut generated locally
	  gen      :: availset(),
	  %% WantIn: Temps that are split
	  want     :: regset(),
	  %% Self: Temps with avail-want pairs locally
	  self     :: regset(),
	  %% DefIn: Temps shadowed by later def in same live range part
	  defin    :: regset(),
	  pred     :: [label()],
	  succ     :: [label()]
	 }).
-type avail_bb() :: #avail_bb{}.

avail_get(L, Avail) -> maps:get(L, Avail).
avail_set(L, Val, Avail) -> maps:put(L, Val, Avail).
avail_has_call(L, Avail) -> (avail_get(L, Avail))#avail_bb.has_call.
avail_out(L, Avail) -> (avail_get(L, Avail))#avail_bb.out.
avail_self(L, Avail) -> (avail_get(L, Avail))#avail_bb.self.
avail_pred(L, Avail) -> (avail_get(L, Avail))#avail_bb.pred.
avail_succ(L, Avail) -> (avail_get(L, Avail))#avail_bb.succ.

avail_in(L, Avail) ->
  case avail_pred(L, Avail) of
    [] -> availset_empty(); % entry
    Pred ->
      lists:foldl(fun(P, ASet) ->
		      availset_intersect(avail_out(P, Avail), ASet)
		  end, availset_top(), Pred)
  end.

want_in(L, Avail) -> (avail_get(L, Avail))#avail_bb.want.
want_out(L, Avail) ->
  lists:foldl(fun(S, Set) ->
		  ordsets:union(want_in(S, Avail), Set)
	      end, ordsets:new(), avail_succ(L, Avail)).

def_in(L, Avail) -> (avail_get(L, Avail))#avail_bb.defin.
def_out(L, Avail) ->
  case avail_succ(L, Avail) of
    [] -> ordsets:new(); % entry
    Succ ->
      ordsets:intersection([def_in(S, Avail) || S <- Succ])
  end.

-type regset()  :: ordsets:ordset(reg()).
-type availset() :: top | regset().
availset_empty() -> [].
availset_top() -> top.
availset_intersect(top, B) -> B;
availset_intersect(A, top) -> A;
availset_intersect(A, B) -> ordsets:intersection(A, B).
availset_union(top, _) -> top;
availset_union(_, top) -> top;
availset_union(A, B) -> ordsets:union(A, B).
ordset_intersect_availset(OS, top) -> OS;
ordset_intersect_availset(OS, AS) -> ordsets:intersection(OS, AS).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Analysis pass
%%
%% The analysis pass collects the set of temps we're interested in splitting
%% (Spills), and computes three dataflow analyses for this subset of temps.
%%
%% Avail, which is the set of temps which are available in register from a
%%   previous (potential) spill or restore without going through a HasCall
%%   block.
%% Want, which is a liveness analysis for the subset of temps used by an
%%   instruction that are also in Avail at that point. In other words, Want is
%%   the set of temps that are split (has a register copy) at a particular
%%   point.
%% Def, which are the temps that are already going to be spilled later, and so
%%   need not be spilled when they're defined.
%%
%% Lastly, it computes the set Self for each block, which is the temps that have
%% avail-want pairs in the same block, and so should be split in that block even
%% if they're not in WantIn for the block.

-spec analyse(target_cfg(), liveness(), target()) -> avail().
analyse(CFG, Liveness, Target) ->
  Avail0 = analyse_init(CFG, Liveness, Target),
  RPO = reverse_postorder(CFG, Target),
  AvailLs = [L || L <- RPO, not avail_has_call(L, Avail0)],
  Avail1 = avail_dataf(AvailLs, Avail0),
  Avail2 = analyse_filter_want(maps:keys(Avail1), Avail1),
  PO = lists:reverse(RPO),
  want_dataf(PO, Avail2).

-spec analyse_init(target_cfg(), liveness(), target()) -> avail().
analyse_init(CFG, Liveness, Target) ->
  analyse_init(labels(CFG, Target), CFG, Liveness, Target, #{}, []).

-spec analyse_init([label()], target_cfg(), liveness(), target(), spillset(),
		   [{label(), avail_bb()}])
		  -> avail().
analyse_init([], _CFG, _Liveness, Target, Spills0, Acc) ->
  %% Precoloured temps can't be spilled
  Spills = spills_filter(fun(R) -> not is_precoloured(R, Target) end, Spills0),
  analyse_init_1(Acc, Spills, []);
analyse_init([L|Ls], CFG, Liveness, Target, Spills0, Acc) ->
  {DefIn, Gen, Self, Want, HasCall0} =
    analyse_scan(hipe_bb:code(bb(CFG, L, Target)), Target,
		 ordsets:new(), ordsets:new(), ordsets:new(),
		 ordsets:new()),
  {Spills, Out, HasCall} =
    case HasCall0 of
      false -> {Spills0, availset_top(), false};
      {true, CallDefs} ->
	Spill = ordsets:subtract(liveout(Liveness, L, Target), CallDefs),
	{spills_add_list(Spill, Spills0), Gen, true}
    end,
  Pred = hipe_gen_cfg:pred(CFG, L),
  Succ = hipe_gen_cfg:succ(CFG, L),
  Val = #avail_bb{gen=Gen, want=Want, self=Self, out=Out, has_call=HasCall,
		  pred=Pred, succ=Succ, defin=DefIn},
  analyse_init(Ls, CFG, Liveness, Target, Spills, [{L, Val} | Acc]).

-spec analyse_init_1([{label(), avail_bb()}], spillset(),
		     [{label(), avail_bb()}])
		    -> avail().
analyse_init_1([], _Spills, Acc) -> maps:from_list(Acc);
analyse_init_1([{L, Val0}|Vs], Spills, Acc) ->
  #avail_bb{out=Out,gen=Gen,want=Want,self=Self} = Val0,
  Val = Val0#avail_bb{
	  out  = spills_filter_availset(Out,  Spills),
	  gen  = spills_filter_availset(Gen,  Spills),
	  want = spills_filter_availset(Want, Spills),
	  self = spills_filter_availset(Self, Spills)},
  analyse_init_1(Vs, Spills, [{L, Val} | Acc]).

-type spillset() :: #{reg() => []}.
-spec spills_add_list([reg()], spillset()) -> spillset().
spills_add_list([], Spills) -> Spills;
spills_add_list([R|Rs], Spills) -> spills_add_list(Rs, Spills#{R => []}).

-spec spills_filter_availset(availset(), spillset()) -> availset().
spills_filter_availset([E|Es], Spills) ->
  case Spills of
    #{E := _} -> [E|spills_filter_availset(Es, Spills)];
    #{} ->          spills_filter_availset(Es, Spills)
  end;
spills_filter_availset([], _) -> [];
spills_filter_availset(top, _) -> top.

spills_filter(Fun, Spills) -> maps:filter(fun(K, _) -> Fun(K) end, Spills).

-spec analyse_scan([instr()], target(), Defset, Gen, Self, Want)
		  -> {Defset, Gen, Self, Want, HasCall} when
    HasCall :: false | {true, regset()},
    Defset  :: regset(),
    Gen     :: availset(),
    Self    :: regset(),
    Want    :: regset().
analyse_scan([], _Target, Defs, Gen, Self, Want) ->
  {Defs, Gen, Self, Want, false};
analyse_scan([I|Is], Target, Defs0, Gen0, Self0, Want0) ->
  {DefL, UseL} = reg_def_use(I, Target),
  Use = ordsets:from_list(UseL),
  Def = ordsets:from_list(DefL),
  Self = ordsets:union(ordsets:intersection(Use, Gen0), Self0),
  Want = ordsets:union(ordsets:subtract(Use, Defs0), Want0),
  Defs = ordsets:union(Def, Defs0),
  case defines_all_alloc(I, Target) of
    true ->
      [] = Is, %assertion
      {Defs, ordsets:new(), Self, Want, {true, Def}};
    false ->
      Gen = ordsets:union(ordsets:union(Def, Use), Gen0),
      analyse_scan(Is, Target, Defs, Gen, Self, Want)
  end.

-spec avail_dataf([label()], avail()) -> avail().
avail_dataf(RPO, Avail0) ->
  case avail_dataf_once(RPO, Avail0, 0) of
    {Avail, 0} -> Avail;
    {Avail, _Changed} ->
      avail_dataf(RPO, Avail)
  end.

-spec avail_dataf_once([label()], avail(), non_neg_integer())
		      -> {avail(), non_neg_integer()}.
avail_dataf_once([], Avail, Changed) -> {Avail, Changed};
avail_dataf_once([L|Ls], Avail0, Changed0) ->
  ABB = #avail_bb{out=OldOut, gen=Gen} = avail_get(L, Avail0),
  In = avail_in(L, Avail0),
  {Changed, Avail} =
    case availset_union(In, Gen) of
      OldOut -> {Changed0, Avail0};
      Out -> {Changed0+1, avail_set(L, ABB#avail_bb{out=Out}, Avail0)}
    end,
  avail_dataf_once(Ls, Avail, Changed).

-spec analyse_filter_want([label()], avail()) -> avail().
analyse_filter_want([], Avail) -> Avail;
analyse_filter_want([L|Ls], Avail0) ->
  ABB = #avail_bb{want=Want0, defin=DefIn0} = avail_get(L, Avail0),
  In = avail_in(L, Avail0),
  Want = ordset_intersect_availset(Want0, In),
  DefIn = ordset_intersect_availset(DefIn0, In),
  Avail = avail_set(L, ABB#avail_bb{want=Want, defin=DefIn}, Avail0),
  analyse_filter_want(Ls, Avail).

-spec want_dataf([label()], avail()) -> avail().
want_dataf(PO, Avail0) ->
  case want_dataf_once(PO, Avail0, 0) of
    {Avail, 0} -> Avail;
    {Avail, _Changed} ->
      want_dataf(PO, Avail)
  end.

-spec want_dataf_once([label()], avail(), non_neg_integer())
		     -> {avail(), non_neg_integer()}.
want_dataf_once([], Avail, Changed) -> {Avail, Changed};
want_dataf_once([L|Ls], Avail0, Changed0) ->
  ABB0 = #avail_bb{want=OldIn,defin=OldDef} = avail_get(L, Avail0),
  AvailIn = avail_in(L, Avail0),
  Out = want_out(L, Avail0),
  DefOut = def_out(L, Avail0),
  {Changed, Avail} =
    case {ordsets:union(ordset_intersect_availset(Out,    AvailIn), OldIn),
	  ordsets:union(ordset_intersect_availset(DefOut, AvailIn), OldDef)}
    of
      {OldIn, OldDef} -> {Changed0, Avail0};
      {In, DefIn} ->
	ABB = ABB0#avail_bb{want=In,defin=DefIn},
	{Changed0+1, avail_set(L, ABB, Avail0)}
    end,
  want_dataf_once(Ls, Avail, Changed).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Rewrite pass
-type subst_dict() :: orddict:orddict(reg(), reg()).
-type input()      :: #{label() => subst_dict()}.

-spec rewrite(target_cfg(), target(), avail()) -> target_cfg().
rewrite(CFG, Target, Avail) ->
  RPO = reverse_postorder(CFG, Target),
  rewrite(RPO, Target, Avail, #{}, CFG).

-spec rewrite([label()], target(), avail(), input(), target_cfg())
	     -> target_cfg().
rewrite([], _Target, _Avail, _Input, CFG) -> CFG;
rewrite([L|Ls], Target, Avail, Input0, CFG0) ->
  SplitHere = split_in_block(L, Avail),
  {Input1, LInput} =
    case Input0 of
      #{L := LInput0} -> {Input0, LInput0};
      #{} -> {Input0#{L => []}, []} % entry block
    end,
  ?ASSERT([] =:= [X || X <- SplitHere, orddict:is_key(X, LInput)]),
  ?ASSERT(want_in(L, Avail) =:= orddict:fetch_keys(LInput)),
  {CFG1, LOutput} =
    case {SplitHere, LInput} of
      {[], []} -> % optimisation (rewrite will do nothing, so skip it)
	{CFG0, LInput};
      _ ->
	Code0 = hipe_bb:code(BB=bb(CFG0, L, Target)),
	DefOut = def_out(L, Avail),
	{Code, LOutput0, _DefIn} =
	  rewrite_instrs(Code0, Target, LInput, DefOut, SplitHere),
	{update_bb(CFG0, L, hipe_bb:code_update(BB, Code), Target), LOutput0}
    end,
  {Input, CFG} = rewrite_succs(avail_succ(L, Avail), Target, L, LOutput, Avail,
			       Input1, CFG1),
  rewrite(Ls, Target, Avail, Input, CFG).

-spec renamed_in_block(label(), avail()) -> ordsets:ordset(reg()).
renamed_in_block(L, Avail) ->
  ordsets:union([avail_self(L, Avail), want_in(L, Avail),
		 want_out(L, Avail)]).

-spec split_in_block(label(), avail()) -> ordsets:ordset(reg()).
split_in_block(L, Avail) ->
  ordsets:subtract(ordsets:union(avail_self(L, Avail), want_out(L, Avail)),
		   want_in(L, Avail)).

-spec rewrite_instrs([instr()], target(), subst_dict(), regset(), [reg()])
		    ->  {[instr()], subst_dict(), regset()}.
rewrite_instrs([], _Target, Output, DefOut, []) ->
  {[], Output, DefOut};
rewrite_instrs([I|Is], Target, Input0, BBDefOut, SplitHere0) ->
  {TDef, TUse} = def_use(I, Target),
  {Def, Use} = {reg_names(TDef, Target), reg_names(TUse, Target)},
  %% Restores are generated in forward order by picking temps from SplitHere as
  %% they're used or defined. After the last instruction, all temps have been
  %% picked.
  {ISplits, SplitHere} =
    lists:partition(fun(R) ->
			lists:member(R, Def) orelse lists:member(R, Use)
		    end, SplitHere0),
  {Input, Restores} =
    case ISplits of
      [] -> {Input0, []};
      _ ->
	make_splits(ISplits, Target, TDef, TUse, Input0, [])
    end,
  %% Here's the recursive call
  {Acc0, Output, DefOut} =
    rewrite_instrs(Is, Target, Input, BBDefOut, SplitHere),
  %% From here we're processing instructions in reverse order, because to avoid
  %% redundant spills we need to walk the 'def' dataflow, which is in reverse.
  SubstFun = fun(Temp) ->
		 case orddict:find(reg_nr(Temp, Target), Input) of
		   {ok, NewTemp} -> NewTemp;
		   error -> Temp
		 end
	     end,
  Acc1 = insert_spills(TDef, Target, Input, DefOut, Acc0),
  Acc = Restores ++ [subst_temps(SubstFun, I, Target) | Acc1],
  DefIn = ordsets:union(DefOut, ordsets:from_list(Def)),
  {Acc, Output, DefIn}.

-spec make_splits([reg()], target(), [temp()], [temp()], subst_dict(),
		  [instr()])
		 -> {subst_dict(), [instr()]}.
make_splits([], _Target, _TDef, _TUse, Input, Acc) ->
  {Input, Acc};
make_splits([S|Ss], Target, TDef, TUse, Input0, Acc0) ->
  SubstReg = new_reg_nr(Target),
  {Acc, Subst} =
    case find_reg_temp(S, TUse, Target) of
      error ->
	{ok, Temp} = find_reg_temp(S, TDef, Target),
	{Acc0, update_reg_nr(SubstReg, Temp, Target)};
      {ok, Temp} ->
	Subst0 = update_reg_nr(SubstReg, Temp, Target),
	Acc1 = [mk_move(Temp, Subst0, Target) | Acc0],
	{Acc1, Subst0}
    end,
  Input = orddict:store(S, Subst, Input0),
  make_splits(Ss, Target, TDef, TUse, Input, Acc).

-spec find_reg_temp(reg(), [temp()], target()) -> error | {ok, temp()}.
find_reg_temp(_Reg, [], _Target) -> error;
find_reg_temp(Reg, [T|Ts], Target) ->
  case reg_nr(T, Target) of
    Reg -> {ok, T};
    _ -> find_reg_temp(Reg, Ts, Target)
  end.

-spec insert_spills([temp()], target(), subst_dict(), regset(), [instr()])
		   -> [instr()].
insert_spills([], _Target, _Input, _DefOut, Acc) -> Acc;
insert_spills([T|Ts], Target, Input, DefOut, Acc0) ->
  R = reg_nr(T, Target),
  Acc =
    case orddict:find(R, Input) of
      error -> Acc0;
      {ok, Subst} ->
	case lists:member(R, DefOut) of
	  true -> Acc0;
	  false -> [mk_move(Subst, T, Target) | Acc0]
	end
    end,
  insert_spills(Ts, Target, Input, DefOut, Acc).

-spec rewrite_succs([label()], target(), label(), subst_dict(), avail(),
		    input(), target_cfg()) -> {input(), target_cfg()}.
rewrite_succs([], _Target, _P, _POutput, _Avail, Input, CFG) -> {Input, CFG};
rewrite_succs([L|Ls], Target, P, POutput, Avail, Input0, CFG0) ->
  NewLInput = orddict_with_ordset(want_in(L, Avail), POutput),
  {Input, CFG} =
    case Input0 of
      #{L := LInput} ->
	CFG2 =
	  case required_phi_moves(LInput, NewLInput) of
	    [] -> CFG0;
	    ReqMovs ->
	      PhiLb = new_label(Target),
	      Code = [mk_move(S,D,Target) || {S,D} <- ReqMovs]
		++ [mk_goto(L, Target)],
	      PhiBB = hipe_bb:mk_bb(Code),
	      CFG1 = update_bb(CFG0, PhiLb, PhiBB, Target),
	      bb_redirect_jmp(L, PhiLb, P, CFG1, Target)
	  end,
	{Input0, CFG2};
      #{} ->
	{Input0#{L => NewLInput}, CFG0}
    end,
  rewrite_succs(Ls, Target, P, POutput, Avail, Input, CFG).

-spec bb_redirect_jmp(label(), label(), label(), target_cfg(), target())
		     -> target_cfg().
bb_redirect_jmp(From, To, Lb, CFG, Target) ->
  BB0 = bb(CFG, Lb, Target),
  Last = redirect_jmp(hipe_bb:last(BB0), From, To, Target),
  BB = hipe_bb:code_update(BB0, hipe_bb:butlast(BB0) ++ [Last]),
  update_bb(CFG, Lb, BB, Target).

-spec required_phi_moves(subst_dict(), subst_dict()) -> [{reg(), reg()}].
required_phi_moves([], []) -> [];
required_phi_moves([P|Is], [P|Os]) -> required_phi_moves(Is, Os);
required_phi_moves([{K, In}|Is], [{K, Out}|Os]) ->
  [{Out, In}|required_phi_moves(Is, Os)].

%% @doc Returns a new orddict with the keys in Set and their associated values.
-spec orddict_with_ordset(ordsets:ordset(K), orddict:orddict(K, V))
			 -> orddict:orddict(K, V).
orddict_with_ordset([S|Ss], [{K, _}|_]=Dict) when S < K ->
  orddict_with_ordset(Ss, Dict);
orddict_with_ordset([S|_]=Set, [{K, _}|Ds]) when S > K ->
  orddict_with_ordset(Set, Ds);
orddict_with_ordset([_S|Ss], [{_K, _}=P|Ds]) -> % _S == _K
  [P|orddict_with_ordset(Ss, Ds)];
orddict_with_ordset([], _) -> [];
orddict_with_ordset(_, []) -> [].

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Target module interface functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(TGT_IFACE_0(N), N(         {M,C}) -> M:N(         C)).
-define(TGT_IFACE_1(N), N(A1,      {M,C}) -> M:N(A1,      C)).
-define(TGT_IFACE_2(N), N(A1,A2,   {M,C}) -> M:N(A1,A2,   C)).
-define(TGT_IFACE_3(N), N(A1,A2,A3,{M,C}) -> M:N(A1,A2,A3,C)).

?TGT_IFACE_2(bb).
?TGT_IFACE_1(def_use).
?TGT_IFACE_1(defines_all_alloc).
?TGT_IFACE_1(is_precoloured).
?TGT_IFACE_1(labels).
?TGT_IFACE_1(mk_goto).
?TGT_IFACE_2(mk_move).
?TGT_IFACE_0(new_label).
?TGT_IFACE_0(new_reg_nr).
?TGT_IFACE_3(redirect_jmp).
?TGT_IFACE_1(reg_nr).
?TGT_IFACE_1(reverse_postorder).
?TGT_IFACE_2(subst_temps).
?TGT_IFACE_3(update_bb).
?TGT_IFACE_2(update_reg_nr).

liveout(Liveness, L, Target={TgtMod,TgtCtx}) ->
  ordsets:from_list(reg_names(TgtMod:liveout(Liveness, L, TgtCtx), Target)).

reg_names(Regs, {TgtMod,TgtCtx}) ->
  [TgtMod:reg_nr(X,TgtCtx) || X <- Regs].

reg_def_use(I, Target) ->
  {TDef, TUse} = def_use(I, Target),
  {reg_names(TDef, Target), reg_names(TUse, Target)}.