File 1711-Improve-BEAM-debug-info-for-some-function-calls.patch of Package erlang
From 56f5f338133503a105a11b489d9369ed184f9976 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Mon, 2 Mar 2026 16:23:56 +0100
Subject: [PATCH] Improve BEAM debug info for some function calls
Consider the following example:
go(X) ->
try
Y = foo:bar(),
Z = Y:go(),
hey:ho(Y, X, Z)
catch _ -> ok
end.
In the BEAM debug info, the location of `Y` directly after `foo:bar/0`
call, would be only in `y0`; actually, the value is present in both
`x0` and `y0`.
---
lib/compiler/src/beam_ssa_codegen.erl | 126 ++++++++++++++------------
1 file changed, 70 insertions(+), 56 deletions(-)
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index 5ddf87c0f5..7baacd66b8 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -38,6 +38,7 @@
splitwith/2,takewhile/2]).
-record(cg, {lcount=1 :: beam_label(), %Label counter
+ vcount=1 :: pos_integer(), %Variable counter
functable=#{} :: #{fa() => beam_label()},
labels=#{} :: #{ssa_label() => 0|beam_label()},
used_labels=gb_sets:empty() :: gb_sets:set(ssa_label()),
@@ -67,7 +68,7 @@ module(#b_module{anno=Anno,name=Mod,exports=Es,attributes=Attrs,body=Fs}, Opts)
-record(cg_set, {anno=#{} :: anno(),
dst :: b_var(),
op :: beam_ssa:op() | 'nop',
- args :: [beam_ssa:argument() | xreg()]}).
+ args :: [beam_ssa:argument()]}).
-record(cg_alloc, {anno=#{} :: anno(),
stack=none :: 'none' | pos_integer(),
@@ -118,7 +119,8 @@ functions(Forms, AtomMod, DebugInfo) ->
mapfoldl(fun (F, St) -> function(F, AtomMod, St) end,
#cg{lcount=1,debug_info=DebugInfo}, Forms).
-function(#b_function{anno=Anno,bs=Blocks,args=Args}, AtomMod, St0) ->
+function(#b_function{anno=Anno,bs=Blocks,args=Args,cnt=Count},
+ AtomMod, St0) ->
#{func_info := {_,Name,Arity}} = Anno,
NoBsMatch = not maps:get(bs_ensure_opt, Anno, false),
try
@@ -130,7 +132,9 @@ function(#b_function{anno=Anno,bs=Blocks,args=Args}, AtomMod, St0) ->
{Entry,St3} = local_func_label(Name, Arity, St2),
{Ult,St4} = new_label(St3), %Ultimate failure
Labels = (St4#cg.labels)#{0=>Entry,?EXCEPTION_BLOCK=>0},
- St5 = St4#cg{labels=Labels,used_labels=gb_sets:singleton(Entry),
+ St5 = St4#cg{vcount=Count,
+ labels=Labels,
+ used_labels=gb_sets:singleton(Entry),
ultimate_fail=Ult},
{Body,St} = cg_fun(Blocks, Args, NoBsMatch, St5#cg{fc_label=Fi}),
Asm0 = [{label,Fi},line(Anno),
@@ -201,13 +205,13 @@ cg_fun(Blocks, Args, NoBsMatch, St0) ->
Linear0 = linearize(Blocks),
St1 = collect_catch_labels(Linear0, St0),
Linear1 = need_heap(Linear0),
- Linear2 = prefer_xregs(Linear1, St1),
- Linear3 = liveness(Linear2, St1),
- Linear4 = defined(Linear3, St1),
- Linear5 = opt_allocate(Linear4, St1),
+ {Linear2,St2} = prefer_xregs(Linear1, St1),
+ Linear3 = liveness(Linear2, St2),
+ Linear4 = defined(Linear3, St2),
+ Linear5 = opt_allocate(Linear4, St2),
Linear6 = fix_wait_timeout(Linear5),
- Linear = add_debug_info(Linear6, Args, St1),
- {Asm,St} = cg_linear(Linear, St1),
+ Linear = add_debug_info(Linear6, Args, St2),
+ {Asm,St} = cg_linear(Linear, St2),
case NoBsMatch of
true -> {Asm,St};
false -> {bs_translate(Asm),St}
@@ -482,17 +486,18 @@ classify_heap_need(wait_timeout) -> gc.
%%%
prefer_xregs(Linear, St) ->
- prefer_xregs(Linear, St, #{0=>#{}}).
+ prefer_xregs(Linear, St, #{0 => #{}}, []).
-prefer_xregs([{L,#cg_blk{is=Is0,last=Last0}=Blk0}|Bs], St, Map0) ->
+prefer_xregs([{L,#cg_blk{is=Is0,last=Last0}=Blk0}|Bs], St0, Map0, Acc) ->
Copies0 = maps:get(L, Map0),
- {Is,Copies} = prefer_xregs_is(Is0, St, Copies0, []),
+ {Is,Copies,St} = prefer_xregs_is(Is0, St0, Copies0, []),
Last = prefer_xregs_terminator(Last0, Copies, St),
Blk = Blk0#cg_blk{is=Is,last=Last},
Successors = successors(Last),
Map = prefer_xregs_successors(Successors, Copies, Map0),
- [{L,Blk}|prefer_xregs(Bs, St, Map)];
-prefer_xregs([], _St, _Map) -> [].
+ prefer_xregs(Bs, St, Map, [{L,Blk}|Acc]);
+prefer_xregs([], St, _Map, Acc) ->
+ {reverse(Acc),St}.
prefer_xregs_successors([L|Ls], Copies0, Map0) ->
case Map0 of
@@ -521,9 +526,25 @@ prefer_xregs_is([#cg_set{op=copy,dst=Dst,args=[Src]}=I|Is], St, Copies0, Acc) ->
[_,_] -> Copies1#{Dst=>Src}
end,
prefer_xregs_is(Is, St, Copies, [I|Acc]);
-prefer_xregs_is([#cg_set{op=call,dst=Dst}=I0|Is], St, Copies, Acc) ->
- I = prefer_xregs_call(I0, Copies, St),
- prefer_xregs_is(Is, St, #{Dst=>{x,0}}, [I|Acc]);
+prefer_xregs_is([#cg_set{op=call,dst=Dst}=I0|Is], St0, Copies, Acc) ->
+ I1 = prefer_xregs_call(I0, Copies, St0),
+
+ case St0#cg.regs of
+ #{Dst := {x,0}} ->
+ prefer_xregs_is(Is, St0, #{}, [I1|Acc]);
+ #{} ->
+ %% The return value will be immediately copied to another
+ %% register (almost always a Y register). Create a new
+ %% variable and register it as a copy.
+ #cg{vcount=Count,regs=Regs0} = St0,
+ Copy = #b_var{name=Count},
+ Regs = Regs0#{Copy => {x,0}},
+ St = St0#cg{vcount=Count+1,regs=Regs},
+ Anno0 = I1#cg_set.anno,
+ Anno = Anno0#{return_register => Copy},
+ I = I1#cg_set{anno=Anno},
+ prefer_xregs_is(Is, St, #{Dst => Copy}, [I|Acc])
+ end;
prefer_xregs_is([#cg_set{op=Op}=I|Is], St, Copies0, Acc)
when Op =:= bs_ensured_get;
Op =:= bs_ensured_skip;
@@ -539,8 +560,8 @@ prefer_xregs_is([#cg_set{args=Args0}=I0|Is], St, Copies0, Acc) ->
I = I0#cg_set{args=Args},
Copies = prefer_xregs_prune(I, Copies0, St),
prefer_xregs_is(Is, St, Copies, [I|Acc]);
-prefer_xregs_is([], _St, Copies, Acc) ->
- {reverse(Acc),Copies}.
+prefer_xregs_is([], St, Copies, Acc) ->
+ {reverse(Acc),Copies,St}.
prefer_xregs_terminator(#cg_br{bool=Arg0}=I, Copies, St) ->
Arg = do_prefer_xreg(Arg0, Copies, St),
@@ -645,22 +666,14 @@ liveness_terminator(#cg_ret{arg=Arg}, Live) ->
liveness_terminator_1(#b_var{}=V, Live) ->
ordsets:add_element(V, Live);
liveness_terminator_1(#b_literal{}, Live) ->
- Live;
-liveness_terminator_1(Reg, Live) ->
- _ = verify_beam_register(Reg),
- ordsets:add_element(Reg, Live).
+ Live.
liveness_args([#b_var{}=V|As], Live) ->
liveness_args(As, ordsets:add_element(V, Live));
liveness_args([#b_remote{mod=Mod,name=Name}|As], Live) ->
liveness_args([Mod,Name|As], Live);
-liveness_args([A|As], Live) ->
- case is_beam_register(A) of
- true ->
- liveness_args(As, ordsets:add_element(A, Live));
- false ->
- liveness_args(As, Live)
- end;
+liveness_args([_|As], Live) ->
+ liveness_args(As, Live);
liveness_args([], Live) -> Live.
liveness_anno(#cg_set{op=Op}=I, Live, Regs) ->
@@ -699,7 +712,7 @@ is_yreg(R, Regs) ->
end.
num_live(Live, Regs) ->
- Rs = ordsets:from_list([get_register(V, Regs) || V <- Live]),
+ Rs = ordsets:from_list([map_get(V, Regs) || V <- Live]),
num_live_1(Rs, 0).
num_live_1([{x,X}|T], X) ->
@@ -1058,6 +1071,15 @@ add_debug_info_is([#cg_set{anno=Anno,op=copy,dst=#b_var{name=Dst},
VarMap0#{Dst => [Src]}
end,
add_debug_info_is(Is, Regs, FrameSize, VarMap, [I|Acc]);
+add_debug_info_is([#cg_set{anno=Anno,op=call,dst=Dst}=I|Is],
+ Regs, FrameSize, VarMap0, Acc) ->
+ VarMap = case Anno of
+ #{return_register := Src} ->
+ VarMap0#{Src#b_var.name => [Dst#b_var.name]};
+ #{} ->
+ VarMap0
+ end,
+ add_debug_info_is(Is, Regs, FrameSize, VarMap, [I|Acc]);
add_debug_info_is([#cg_set{anno=Anno0,op=debug_line,args=[Index]}=I0|Is],
Regs, FrameSize, VarMap, Acc) ->
#{def_regs := DefRegs,
@@ -1071,8 +1093,7 @@ add_debug_info_is([#cg_set{anno=Anno0,op=debug_line,args=[Index]}=I0|Is],
Literals = [{hd(Vars),[{literal,Val}]} ||
{Vars,Val} <:- Literals1, Vars =/= []],
RegVarMap = [{map_get(V, Regs),get_original_names(V, AliasMap)} ||
- V <- DefRegs,
- not is_beam_register(V)],
+ V <- DefRegs],
S0 = sofs:family(RegVarMap, [{reg,[variable]}]),
S1 = sofs:family_to_relation(S0),
S2 = sofs:converse(S1),
@@ -1209,14 +1230,20 @@ def_regs_is([#cg_set{anno=Anno,dst=Dst}=I|Is], Regs, Def0, Acc) ->
case Anno of
#{clobbers := true} ->
Def3 = trim_xregs(Def2, 0, Regs),
- Def = case Regs of
- #{Dst := {Tag,_}=R} when Tag =:= x; Tag =:= y ->
- Def4 = kill_reg(Def3, R, Regs),
- ordsets:add_element(Dst, Def4);
- #{} ->
- Def3
- end,
- def_regs_is(Is, Regs, Def, [I|Acc]);
+ Def5 = case Regs of
+ #{Dst := {Tag,_}=R} when Tag =:= x; Tag =:= y ->
+ Def4 = kill_reg(Def3, R, Regs),
+ ordsets:add_element(Dst, Def4);
+ #{} ->
+ Def3
+ end,
+ case Anno of
+ #{return_register := Result} ->
+ Def = ordsets:add_element(Result, Def5),
+ def_regs_is(Is, Regs, Def, [I|Acc]);
+ #{} ->
+ def_regs_is(Is, Regs, Def5, [I|Acc])
+ end;
#{} ->
case Regs of
#{Dst := {Tag,_}=R} when Tag =:= x; Tag =:= y ->
@@ -1243,7 +1270,7 @@ trim_xregs([], _, _) -> [].
kill_reg([V|Vs], R, Regs) ->
case Regs of
- #{V := R} -> Vs;
+ #{V := R} -> kill_reg(Vs, R, Regs);
#{} -> [V|kill_reg(Vs, R, Regs)]
end;
kill_reg([], _, _) -> [].
@@ -2719,17 +2746,6 @@ bs_translate_instr(_) -> none.
%%% General utility functions.
%%%
-verify_beam_register({x,_}=Reg) -> Reg.
-
-is_beam_register({x,_}) -> true;
-is_beam_register(_) -> false.
-
-get_register(V, Regs) ->
- case is_beam_register(V) of
- true -> V;
- false -> maps:get(V, Regs)
- end.
-
typed_args(As, Anno, St) ->
typed_args_1(As, Anno, St, 0).
@@ -2756,9 +2772,7 @@ beam_arg(#b_literal{val=Val}, _) ->
is_integer(Val) -> {integer,Val};
Val =:= [] -> nil;
true -> {literal,Val}
- end;
-beam_arg(Reg, _) ->
- verify_beam_register(Reg).
+ end.
new_block_label(L, St0) ->
{_Lbl,St} = label_for_block(L, St0),
--
2.51.0