cmd/compile/internal/dwarfgen: refactor putvar and putAbstractVar

Currently, changing putvar or putAbstractVar involves:

1. changing the abbrevs array to add new abbrevs or modify existing ones
2. changing the DW_ABRV_XXX const block to add the new abbrevs, this const
   block must match the changes to the abbrevs array
3. change the code at the start of putvar and putAbstractVar that selects
   the abbrev to use
4. change the body of putvar/putAbstractVar to emit the right attributes in
   the right sequence

Each change must agree with all other, this is error prone and if an mistake
is made there is no compile time or runtime check detecting it. Erroneous
code will simply produce unreadable debug sections.

This commit adds a mechanism to automatically generate code for abbrev
selection as well as the abbrev definitions based on static examination of
the body of putvar and putAbstractVar.

TestPutVarAbbrevGenerator is responsible for checking that the generated
code is kept updated and will regenerated it by passing the '-generate'
option to it.

benchstat output:

                         |    old.txt    |               new.txt                |
                         |    sec/op     |    sec/op      vs base               |
Template                    153.8m ±  6%    153.0m ±  6%       ~ (p=0.853 n=10)
Unicode                     98.98m ± 19%   100.22m ± 15%       ~ (p=0.796 n=10)
GoTypes                    1013.7m ± 13%    943.6m ±  8%       ~ (p=0.353 n=10)
Compiler                    98.48m ± 10%    97.79m ±  6%       ~ (p=0.353 n=10)
SSA                          8.921 ± 31%     6.872 ± 37%       ~ (p=0.912 n=10)
Flate                       114.3m ± 21%    128.0m ± 36%       ~ (p=0.436 n=10)
GoParser                    219.0m ± 27%    214.9m ± 26%       ~ (p=0.631 n=10)
Reflect                     447.5m ± 20%    452.6m ± 22%       ~ (p=0.684 n=10)
Tar                         166.9m ± 27%    166.2m ± 27%       ~ (p=0.529 n=10)
XML                         218.6m ± 25%    219.3m ± 24%       ~ (p=0.631 n=10)
LinkCompiler                492.7m ± 12%    523.2m ± 13%       ~ (p=0.315 n=10)
ExternalLinkCompiler         1.684 ±  3%     1.684 ±  2%       ~ (p=0.684 n=10)
LinkWithoutDebugCompiler    296.0m ±  8%    304.9m ± 12%       ~ (p=0.579 n=10)
StdCmd                       69.59 ± 15%     70.76 ± 14%       ~ (p=0.436 n=10)
geomean                     516.0m          511.5m        -0.87%

                         |   old.txt    |               new.txt               |
                         | user-sec/op  | user-sec/op   vs base               |
Template                   281.5m ± 10%   269.6m ± 13%       ~ (p=0.315 n=10)
Unicode                    107.3m ±  8%   110.2m ±  8%       ~ (p=0.165 n=10)
GoTypes                     2.414 ± 16%    2.181 ±  9%       ~ (p=0.315 n=10)
Compiler                   116.0m ± 16%   119.1m ± 11%       ~ (p=0.971 n=10)
SSA                         25.47 ± 39%    17.75 ± 52%       ~ (p=0.739 n=10)
Flate                      205.2m ± 25%   256.2m ± 43%       ~ (p=0.393 n=10)
GoParser                   456.8m ± 28%   427.0m ± 24%       ~ (p=0.912 n=10)
Reflect                    960.3m ± 22%   990.5m ± 23%       ~ (p=0.280 n=10)
Tar                        299.8m ± 27%   307.9m ± 27%       ~ (p=0.631 n=10)
XML                        425.0m ± 21%   432.8m ± 24%       ~ (p=0.353 n=10)
LinkCompiler               768.1m ± 11%   796.9m ± 14%       ~ (p=0.631 n=10)
ExternalLinkCompiler        1.713 ±  5%    1.666 ±  4%       ~ (p=0.190 n=10)
LinkWithoutDebugCompiler   313.0m ±  9%   316.7m ± 12%       ~ (p=0.481 n=10)
geomean                    588.6m         579.5m        -1.55%

          |   old.txt    |                new.txt                |
          |  text-bytes  |  text-bytes   vs base                 |
HelloSize   842.9Ki ± 0%   842.9Ki ± 0%       ~ (p=1.000 n=10) ¹
CmdGoSize   10.95Mi ± 0%   10.95Mi ± 0%       ~ (p=1.000 n=10) ¹
geomean     3.003Mi        3.003Mi       +0.00%
¹ all samples are equal

          |   old.txt    |                new.txt                |
          |  data-bytes  |  data-bytes   vs base                 |
HelloSize   15.08Ki ± 0%   15.08Ki ± 0%       ~ (p=1.000 n=10) ¹
CmdGoSize   314.7Ki ± 0%   314.7Ki ± 0%       ~ (p=1.000 n=10) ¹
geomean     68.88Ki        68.88Ki       +0.00%
¹ all samples are equal

          |   old.txt    |                new.txt                |
          |  bss-bytes   |  bss-bytes    vs base                 |
HelloSize   396.8Ki ± 0%   396.8Ki ± 0%       ~ (p=1.000 n=10) ¹
CmdGoSize   428.8Ki ± 0%   428.8Ki ± 0%       ~ (p=1.000 n=10) ¹
geomean     412.5Ki        412.5Ki       +0.00%
¹ all samples are equal

          |   old.txt    |               new.txt               |
          |  exe-bytes   |  exe-bytes    vs base               |
HelloSize   1.310Mi ± 0%   1.310Mi ± 0%  -0.01% (p=0.000 n=10)
CmdGoSize   16.37Mi ± 0%   16.37Mi ± 0%  -0.00% (p=0.000 n=10)
geomean     4.631Mi        4.631Mi       -0.00%

Change-Id: I7edf37b5a47fd9aceef931ddf2c701e66a7b38b2
Reviewed-on: https://go-review.googlesource.com/c/go/+/563815
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Alessandro Arzilli 2024-02-08 12:06:00 +01:00 committed by Gopher Robot
parent 5bce5362da
commit 08029be9fc
5 changed files with 508 additions and 223 deletions

View File

@ -218,10 +218,10 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
} }
typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
decls = append(decls, n) decls = append(decls, n)
abbrev := dwarf.DW_ABRV_AUTO_LOCLIST tag := dwarf.DW_TAG_variable
isReturnValue := (n.Class == ir.PPARAMOUT) isReturnValue := (n.Class == ir.PPARAMOUT)
if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT { if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT {
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST tag = dwarf.DW_TAG_formal_parameter
} }
if n.Esc() == ir.EscHeap { if n.Esc() == ir.EscHeap {
// The variable in question has been promoted to the heap. // The variable in question has been promoted to the heap.
@ -233,7 +233,7 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
if n.InlFormal() || n.InlLocal() { if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos()) + 1 inlIndex = posInlIndex(n.Pos()) + 1
if n.InlFormal() { if n.InlFormal() {
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST tag = dwarf.DW_TAG_formal_parameter
} }
} }
} }
@ -241,7 +241,8 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
vars = append(vars, &dwarf.Var{ vars = append(vars, &dwarf.Var{
Name: n.Sym().Name, Name: n.Sym().Name,
IsReturnValue: isReturnValue, IsReturnValue: isReturnValue,
Abbrev: abbrev, Tag: tag,
WithLoclist: true,
StackOffset: int32(n.FrameOffset()), StackOffset: int32(n.FrameOffset()),
Type: base.Ctxt.Lookup(typename), Type: base.Ctxt.Lookup(typename),
DeclFile: declpos.RelFilename(), DeclFile: declpos.RelFilename(),
@ -350,7 +351,7 @@ func createSimpleVars(fnsym *obj.LSym, apDecls []*ir.Name) ([]*ir.Name, []*dwarf
} }
func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var { func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
var abbrev int var tag int
var offs int64 var offs int64
localAutoOffset := func() int64 { localAutoOffset := func() int64 {
@ -367,9 +368,9 @@ func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
switch n.Class { switch n.Class {
case ir.PAUTO: case ir.PAUTO:
offs = localAutoOffset() offs = localAutoOffset()
abbrev = dwarf.DW_ABRV_AUTO tag = dwarf.DW_TAG_variable
case ir.PPARAM, ir.PPARAMOUT: case ir.PPARAM, ir.PPARAMOUT:
abbrev = dwarf.DW_ABRV_PARAM tag = dwarf.DW_TAG_formal_parameter
if n.IsOutputParamInRegisters() { if n.IsOutputParamInRegisters() {
offs = localAutoOffset() offs = localAutoOffset()
} else { } else {
@ -387,7 +388,7 @@ func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
if n.InlFormal() || n.InlLocal() { if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos()) + 1 inlIndex = posInlIndex(n.Pos()) + 1
if n.InlFormal() { if n.InlFormal() {
abbrev = dwarf.DW_ABRV_PARAM tag = dwarf.DW_TAG_formal_parameter
} }
} }
} }
@ -396,7 +397,7 @@ func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
Name: n.Sym().Name, Name: n.Sym().Name,
IsReturnValue: n.Class == ir.PPARAMOUT, IsReturnValue: n.Class == ir.PPARAMOUT,
IsInlFormal: n.InlFormal(), IsInlFormal: n.InlFormal(),
Abbrev: abbrev, Tag: tag,
StackOffset: int32(offs), StackOffset: int32(offs),
Type: base.Ctxt.Lookup(typename), Type: base.Ctxt.Lookup(typename),
DeclFile: declpos.RelFilename(), DeclFile: declpos.RelFilename(),
@ -470,12 +471,12 @@ func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var
debug := fn.DebugInfo.(*ssa.FuncDebug) debug := fn.DebugInfo.(*ssa.FuncDebug)
n := debug.Vars[varID] n := debug.Vars[varID]
var abbrev int var tag int
switch n.Class { switch n.Class {
case ir.PAUTO: case ir.PAUTO:
abbrev = dwarf.DW_ABRV_AUTO_LOCLIST tag = dwarf.DW_TAG_variable
case ir.PPARAM, ir.PPARAMOUT: case ir.PPARAM, ir.PPARAMOUT:
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST tag = dwarf.DW_TAG_formal_parameter
default: default:
return nil return nil
} }
@ -488,7 +489,7 @@ func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var
if n.InlFormal() || n.InlLocal() { if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos()) + 1 inlIndex = posInlIndex(n.Pos()) + 1
if n.InlFormal() { if n.InlFormal() {
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST tag = dwarf.DW_TAG_formal_parameter
} }
} }
} }
@ -497,7 +498,8 @@ func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var
Name: n.Sym().Name, Name: n.Sym().Name,
IsReturnValue: n.Class == ir.PPARAMOUT, IsReturnValue: n.Class == ir.PPARAMOUT,
IsInlFormal: n.InlFormal(), IsInlFormal: n.InlFormal(),
Abbrev: abbrev, Tag: tag,
WithLoclist: true,
Type: base.Ctxt.Lookup(typename), Type: base.Ctxt.Lookup(typename),
// The stack offset is used as a sorting key, so for decomposed // The stack offset is used as a sorting key, so for decomposed
// variables just give it the first one. It's not used otherwise. // variables just give it the first one. It's not used otherwise.

View File

@ -358,7 +358,7 @@ func dumpInlCalls(inlcalls dwarf.InlCalls) {
func dumpInlVars(dwvars []*dwarf.Var) { func dumpInlVars(dwvars []*dwarf.Var) {
for i, dwv := range dwvars { for i, dwv := range dwvars {
typ := "local" typ := "local"
if dwv.Abbrev == dwarf.DW_ABRV_PARAM_LOCLIST || dwv.Abbrev == dwarf.DW_ABRV_PARAM { if dwv.Tag == dwarf.DW_TAG_formal_parameter {
typ = "param" typ = "param"
} }
ia := 0 ia := 0

View File

@ -45,7 +45,8 @@ type Sym interface {
// A Var represents a local variable or a function parameter. // A Var represents a local variable or a function parameter.
type Var struct { type Var struct {
Name string Name string
Abbrev int // Either DW_ABRV_AUTO[_LOCLIST] or DW_ABRV_PARAM[_LOCLIST] Tag int // Either DW_TAG_variable or DW_TAG_formal_parameter
WithLoclist bool
IsReturnValue bool IsReturnValue bool
IsInlFormal bool IsInlFormal bool
DictIndex uint16 // index of the dictionary entry describing the type of this variable DictIndex uint16 // index of the dictionary entry describing the type of this variable
@ -331,16 +332,6 @@ const (
DW_ABRV_INLINED_SUBROUTINE_RANGES DW_ABRV_INLINED_SUBROUTINE_RANGES
DW_ABRV_VARIABLE DW_ABRV_VARIABLE
DW_ABRV_INT_CONSTANT DW_ABRV_INT_CONSTANT
DW_ABRV_AUTO
DW_ABRV_AUTO_LOCLIST
DW_ABRV_AUTO_ABSTRACT
DW_ABRV_AUTO_CONCRETE
DW_ABRV_AUTO_CONCRETE_LOCLIST
DW_ABRV_PARAM
DW_ABRV_PARAM_LOCLIST
DW_ABRV_PARAM_ABSTRACT
DW_ABRV_PARAM_CONCRETE
DW_ABRV_PARAM_CONCRETE_LOCLIST
DW_ABRV_LEXICAL_BLOCK_RANGES DW_ABRV_LEXICAL_BLOCK_RANGES
DW_ABRV_LEXICAL_BLOCK_SIMPLE DW_ABRV_LEXICAL_BLOCK_SIMPLE
DW_ABRV_STRUCTFIELD DW_ABRV_STRUCTFIELD
@ -361,7 +352,7 @@ const (
DW_ABRV_STRUCTTYPE DW_ABRV_STRUCTTYPE
DW_ABRV_TYPEDECL DW_ABRV_TYPEDECL
DW_ABRV_DICT_INDEX DW_ABRV_DICT_INDEX
DW_NABRV DW_ABRV_PUTVAR_START
) )
type dwAbbrev struct { type dwAbbrev struct {
@ -394,22 +385,23 @@ func expandPseudoForm(form uint8) uint8 {
// expanding any DW_FORM pseudo-ops to real values. // expanding any DW_FORM pseudo-ops to real values.
func Abbrevs() []dwAbbrev { func Abbrevs() []dwAbbrev {
if abbrevsFinalized { if abbrevsFinalized {
return abbrevs[:] return abbrevs
} }
for i := 1; i < DW_NABRV; i++ { abbrevs = append(abbrevs, putvarAbbrevs...)
for i := 1; i < len(abbrevs); i++ {
for j := 0; j < len(abbrevs[i].attr); j++ { for j := 0; j < len(abbrevs[i].attr); j++ {
abbrevs[i].attr[j].form = expandPseudoForm(abbrevs[i].attr[j].form) abbrevs[i].attr[j].form = expandPseudoForm(abbrevs[i].attr[j].form)
} }
} }
abbrevsFinalized = true abbrevsFinalized = true
return abbrevs[:] return abbrevs
} }
// abbrevs is a raw table of abbrev entries; it needs to be post-processed // abbrevs is a raw table of abbrev entries; it needs to be post-processed
// by the Abbrevs() function above prior to being consumed, to expand // by the Abbrevs() function above prior to being consumed, to expand
// the 'pseudo-form' entries below to real DWARF form values. // the 'pseudo-form' entries below to real DWARF form values.
var abbrevs = [DW_NABRV]dwAbbrev{ var abbrevs = []dwAbbrev{
/* The mandatory DW_ABRV_NULL entry. */ /* The mandatory DW_ABRV_NULL entry. */
{0, 0, []dwAttrForm{}}, {0, 0, []dwAttrForm{}},
@ -555,118 +547,6 @@ var abbrevs = [DW_NABRV]dwAbbrev{
}, },
}, },
/* AUTO */
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
/* AUTO_LOCLIST */
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
/* AUTO_ABSTRACT */
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
},
},
/* AUTO_CONCRETE */
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
/* AUTO_CONCRETE_LOCLIST */
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
/* PARAM */
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
/* PARAM_LOCLIST */
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
/* PARAM_ABSTRACT */
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_type, DW_FORM_ref_addr},
},
},
/* PARAM_CONCRETE */
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
/* PARAM_CONCRETE_LOCLIST */
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
/* LEXICAL_BLOCK_RANGES */ /* LEXICAL_BLOCK_RANGES */
{ {
DW_TAG_lexical_block, DW_TAG_lexical_block,
@ -901,7 +781,7 @@ var abbrevs = [DW_NABRV]dwAbbrev{
func GetAbbrev() []byte { func GetAbbrev() []byte {
abbrevs := Abbrevs() abbrevs := Abbrevs()
var buf []byte var buf []byte
for i := 1; i < DW_NABRV; i++ { for i := 1; i < len(abbrevs); i++ {
// See section 7.5.3 // See section 7.5.3
buf = AppendUleb128(buf, uint64(i)) buf = AppendUleb128(buf, uint64(i))
buf = AppendUleb128(buf, uint64(abbrevs[i].tag)) buf = AppendUleb128(buf, uint64(abbrevs[i].tag))
@ -1548,39 +1428,7 @@ func putscope(ctxt Context, s *FnState, scopes []Scope, curscope int32, fnabbrev
return curscope return curscope
} }
// Given a default var abbrev code, select corresponding concrete code. func concreteVar(fnabbrev int, v *Var) bool {
func concreteVarAbbrev(varAbbrev int) int {
switch varAbbrev {
case DW_ABRV_AUTO:
return DW_ABRV_AUTO_CONCRETE
case DW_ABRV_PARAM:
return DW_ABRV_PARAM_CONCRETE
case DW_ABRV_AUTO_LOCLIST:
return DW_ABRV_AUTO_CONCRETE_LOCLIST
case DW_ABRV_PARAM_LOCLIST:
return DW_ABRV_PARAM_CONCRETE_LOCLIST
default:
panic("should never happen")
}
}
// Pick the correct abbrev code for variable or parameter DIE.
func determineVarAbbrev(v *Var, fnabbrev int) (int, bool, bool) {
abbrev := v.Abbrev
// If the variable was entirely optimized out, don't emit a location list;
// convert to an inline abbreviation and emit an empty location.
missing := false
switch {
case abbrev == DW_ABRV_AUTO_LOCLIST && v.PutLocationList == nil:
missing = true
abbrev = DW_ABRV_AUTO
case abbrev == DW_ABRV_PARAM_LOCLIST && v.PutLocationList == nil:
missing = true
abbrev = DW_ABRV_PARAM
}
// Determine whether to use a concrete variable or regular variable DIE.
concrete := true concrete := true
switch fnabbrev { switch fnabbrev {
case DW_ABRV_FUNCTION, DW_ABRV_WRAPPER: case DW_ABRV_FUNCTION, DW_ABRV_WRAPPER:
@ -1596,64 +1444,44 @@ func determineVarAbbrev(v *Var, fnabbrev int) (int, bool, bool) {
default: default:
panic("should never happen") panic("should never happen")
} }
return concrete
// Select proper abbrev based on concrete/non-concrete
if concrete {
abbrev = concreteVarAbbrev(abbrev)
}
return abbrev, missing, concrete
}
func abbrevUsesLoclist(abbrev int) bool {
switch abbrev {
case DW_ABRV_AUTO_LOCLIST, DW_ABRV_AUTO_CONCRETE_LOCLIST,
DW_ABRV_PARAM_LOCLIST, DW_ABRV_PARAM_CONCRETE_LOCLIST:
return true
default:
return false
}
} }
// Emit DWARF attributes for a variable belonging to an 'abstract' subprogram. // Emit DWARF attributes for a variable belonging to an 'abstract' subprogram.
func putAbstractVar(ctxt Context, info Sym, v *Var) { func putAbstractVar(ctxt Context, info Sym, v *Var) {
// Remap abbrev // The contents of this functions are used to generate putAbstractVarAbbrev automatically, see TestPutVarAbbrevGenerator.
abbrev := v.Abbrev abbrev := putAbstractVarAbbrev(v)
switch abbrev {
case DW_ABRV_AUTO, DW_ABRV_AUTO_LOCLIST:
abbrev = DW_ABRV_AUTO_ABSTRACT
case DW_ABRV_PARAM, DW_ABRV_PARAM_LOCLIST:
abbrev = DW_ABRV_PARAM_ABSTRACT
}
Uleb128put(ctxt, info, int64(abbrev)) Uleb128put(ctxt, info, int64(abbrev))
putattr(ctxt, info, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(v.Name)), v.Name) putattr(ctxt, info, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(v.Name)), v.Name) // DW_AT_name
// Isreturn attribute if this is a param // Isreturn attribute if this is a param
if abbrev == DW_ABRV_PARAM_ABSTRACT { if v.Tag == DW_TAG_formal_parameter {
var isReturn int64 var isReturn int64
if v.IsReturnValue { if v.IsReturnValue {
isReturn = 1 isReturn = 1
} }
putattr(ctxt, info, abbrev, DW_FORM_flag, DW_CLS_FLAG, isReturn, nil) putattr(ctxt, info, abbrev, DW_FORM_flag, DW_CLS_FLAG, isReturn, nil) // DW_AT_variable_parameter
} }
// Line // Line
if abbrev != DW_ABRV_PARAM_ABSTRACT { if v.Tag == DW_TAG_variable {
// See issue 23374 for more on why decl line is skipped for abs params. // See issue 23374 for more on why decl line is skipped for abs params.
putattr(ctxt, info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(v.DeclLine), nil) putattr(ctxt, info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(v.DeclLine), nil) // DW_AT_decl_line
} }
// Type // Type
putattr(ctxt, info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) putattr(ctxt, info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) // DW_AT_type
// Var has no children => no terminator // Var has no children => no terminator
} }
func putvar(ctxt Context, s *FnState, v *Var, absfn Sym, fnabbrev, inlIndex int, encbuf []byte) { func putvar(ctxt Context, s *FnState, v *Var, absfn Sym, fnabbrev, inlIndex int, encbuf []byte) {
// Remap abbrev according to parent DIE abbrev // The contents of this functions are used to generate putvarAbbrev automatically, see TestPutVarAbbrevGenerator.
abbrev, missing, concrete := determineVarAbbrev(v, fnabbrev) concrete := concreteVar(fnabbrev, v)
hasParametricType := !concrete && (v.DictIndex > 0 && s.dictIndexToOffset != nil && s.dictIndexToOffset[v.DictIndex-1] != 0)
withLoclist := v.WithLoclist && v.PutLocationList != nil
abbrev := putvarAbbrev(v, concrete, withLoclist)
Uleb128put(ctxt, s.Info, int64(abbrev)) Uleb128put(ctxt, s.Info, int64(abbrev))
// Abstract origin for concrete / inlined case // Abstract origin for concrete / inlined case
@ -1662,35 +1490,35 @@ func putvar(ctxt Context, s *FnState, v *Var, absfn Sym, fnabbrev, inlIndex int,
// function subprogram DIE. The child DIE has no LSym, so instead // function subprogram DIE. The child DIE has no LSym, so instead
// after the call to 'putattr' below we make a call to register // after the call to 'putattr' below we make a call to register
// the child DIE reference. // the child DIE reference.
putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, absfn) putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, absfn) // DW_AT_abstract_origin
ctxt.RecordDclReference(s.Info, absfn, int(v.ChildIndex), inlIndex) ctxt.RecordDclReference(s.Info, absfn, int(v.ChildIndex), inlIndex)
} else { } else {
// Var name, line for abstract and default cases // Var name, line for abstract and default cases
n := v.Name n := v.Name
putattr(ctxt, s.Info, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) putattr(ctxt, s.Info, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) // DW_AT_name
if abbrev == DW_ABRV_PARAM || abbrev == DW_ABRV_PARAM_LOCLIST || abbrev == DW_ABRV_PARAM_ABSTRACT { if v.Tag == DW_TAG_formal_parameter {
var isReturn int64 var isReturn int64
if v.IsReturnValue { if v.IsReturnValue {
isReturn = 1 isReturn = 1
} }
putattr(ctxt, s.Info, abbrev, DW_FORM_flag, DW_CLS_FLAG, isReturn, nil) putattr(ctxt, s.Info, abbrev, DW_FORM_flag, DW_CLS_FLAG, isReturn, nil) // DW_AT_variable_parameter
} }
putattr(ctxt, s.Info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(v.DeclLine), nil) putattr(ctxt, s.Info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(v.DeclLine), nil) // DW_AT_decl_line
if v.DictIndex > 0 && s.dictIndexToOffset != nil && s.dictIndexToOffset[v.DictIndex-1] != 0 { if hasParametricType {
// If the type of this variable is parametric use the entry emitted by putparamtypes // If the type of this variable is parametric use the entry emitted by putparamtypes
putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, s.dictIndexToOffset[v.DictIndex-1], s.Info) putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, s.dictIndexToOffset[v.DictIndex-1], s.Info) // DW_AT_type
} else { } else {
putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) // DW_AT_type
} }
} }
if abbrevUsesLoclist(abbrev) { if withLoclist {
putattr(ctxt, s.Info, abbrev, DW_FORM_sec_offset, DW_CLS_PTR, ctxt.Size(s.Loc), s.Loc) putattr(ctxt, s.Info, abbrev, DW_FORM_sec_offset, DW_CLS_PTR, ctxt.Size(s.Loc), s.Loc) // DW_AT_location
v.PutLocationList(s.Loc, s.StartPC) v.PutLocationList(s.Loc, s.StartPC)
} else { } else {
loc := encbuf[:0] loc := encbuf[:0]
switch { switch {
case missing: case v.WithLoclist:
break // no location break // no location
case v.StackOffset == 0: case v.StackOffset == 0:
loc = append(loc, DW_OP_call_frame_cfa) loc = append(loc, DW_OP_call_frame_cfa)
@ -1698,7 +1526,7 @@ func putvar(ctxt Context, s *FnState, v *Var, absfn Sym, fnabbrev, inlIndex int,
loc = append(loc, DW_OP_fbreg) loc = append(loc, DW_OP_fbreg)
loc = AppendSleb128(loc, int64(v.StackOffset)) loc = AppendSleb128(loc, int64(v.StackOffset))
} }
putattr(ctxt, s.Info, abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) putattr(ctxt, s.Info, abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) // DW_AT_location
} }
// Var has no children => no terminator // Var has no children => no terminator

View File

@ -0,0 +1,139 @@
// Code generated by TestPutVarAbbrevGenerator. DO NOT EDIT.
// Regenerate using go test -run TestPutVarAbbrevGenerator -generate instead.
package dwarf
var putvarAbbrevs = []dwAbbrev{
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
},
},
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_type, DW_FORM_ref_addr},
},
},
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
}
func putAbstractVarAbbrev(v *Var) int {
if v.Tag == DW_TAG_variable {
return DW_ABRV_PUTVAR_START + 0
} else {
return DW_ABRV_PUTVAR_START + 1
}
}
func putvarAbbrev(v *Var, concrete, withLoclist bool) int {
if v.Tag == DW_TAG_variable {
if concrete {
if withLoclist {
return DW_ABRV_PUTVAR_START + 2
} else {
return DW_ABRV_PUTVAR_START + 3
}
} else {
if withLoclist {
return DW_ABRV_PUTVAR_START + 4
} else {
return DW_ABRV_PUTVAR_START + 5
}
}
} else {
if concrete {
if withLoclist {
return DW_ABRV_PUTVAR_START + 6
} else {
return DW_ABRV_PUTVAR_START + 7
}
} else {
if withLoclist {
return DW_ABRV_PUTVAR_START + 8
} else {
return DW_ABRV_PUTVAR_START + 9
}
}
}
}

View File

@ -0,0 +1,316 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dwarf
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/printer"
"go/token"
"os"
"strconv"
"strings"
"testing"
)
const pvagenfile = "./putvarabbrevgen.go"
var pvaDoGenerate bool
func TestMain(m *testing.M) {
flag.BoolVar(&pvaDoGenerate, "generate", false, "regenerates "+pvagenfile)
flag.Parse()
os.Exit(m.Run())
}
// TestPutVarAbbrevGenerator checks that putvarabbrevgen.go is kept in sync
// with the contents of functions putvar and putAbstractVar. If test flag -generate
// is specified the file is regenerated instead.
//
// The block of code in putvar and putAbstractVar that picks the correct
// abbrev is also generated automatically by this function by looking at all
// the possible paths in their CFG and the order in which putattr is called.
//
// There are some restrictions on how putattr can be used in putvar and
// putAbstractVar:
//
// 1. it shouldn't appear inside a for or switch statements
// 2. it can appear within any number of nested if/else statements but the
// conditionals must not change after putvarAbbrev/putAbstractVarAbbrev
// are called
// 3. the form argument of putattr must be a compile time constant
// 4. each putattr call must be followed by a comment containing the name of
// the attribute it is setting
//
// TestPutVarAbbrevGenerator will fail if (1) or (4) are not respected and
// the generated code will not compile if (3) is violated. Violating (2)
// will result in code silently wrong code (which will usually be detected
// by one of the tests that parse debug_info).
func TestPutVarAbbrevGenerator(t *testing.T) {
spvagenfile := pvagenerate(t)
if pvaDoGenerate {
err := os.WriteFile(pvagenfile, []byte(spvagenfile), 0660)
if err != nil {
t.Fatal(err)
}
return
}
slurp := func(name string) string {
out, err := os.ReadFile(name)
if err != nil {
t.Fatal(err)
}
return string(out)
}
if spvagenfile != slurp(pvagenfile) {
t.Error(pvagenfile + " is out of date")
}
}
func pvagenerate(t *testing.T) string {
var fset token.FileSet
f, err := parser.ParseFile(&fset, "./dwarf.go", nil, parser.ParseComments)
if err != nil {
t.Fatal(err)
}
cm := ast.NewCommentMap(&fset, f, f.Comments)
abbrevs := make(map[string]int)
funcs := map[string]ast.Stmt{}
for _, decl := range f.Decls {
decl, ok := decl.(*ast.FuncDecl)
if !ok || decl.Body == nil {
continue
}
if decl.Name.Name == "putvar" || decl.Name.Name == "putAbstractVar" {
// construct the simplified CFG
pvagraph, _ := pvacfgbody(t, &fset, cm, decl.Body.List)
funcs[decl.Name.Name+"Abbrev"] = pvacfgvisit(pvagraph, abbrevs)
}
}
abbrevslice := make([]string, len(abbrevs))
for abbrev, n := range abbrevs {
abbrevslice[n] = abbrev
}
buf := new(bytes.Buffer)
fmt.Fprint(buf, `// Code generated by TestPutVarAbbrevGenerator. DO NOT EDIT.
// Regenerate using go test -run TestPutVarAbbrevGenerator -generate instead.
package dwarf
var putvarAbbrevs = []dwAbbrev{
`)
for _, abbrev := range abbrevslice {
fmt.Fprint(buf, abbrev+",\n")
}
fmt.Fprint(buf, "\n}\n\n")
fmt.Fprint(buf, "func putAbstractVarAbbrev(v *Var) int {\n")
format.Node(buf, &token.FileSet{}, funcs["putAbstractVarAbbrev"])
fmt.Fprint(buf, "}\n\n")
fmt.Fprint(buf, "func putvarAbbrev(v *Var, concrete, withLoclist bool) int {\n")
format.Node(buf, &token.FileSet{}, funcs["putvarAbbrev"])
fmt.Fprint(buf, "}\n")
out, err := format.Source(buf.Bytes())
if err != nil {
t.Log(string(buf.Bytes()))
t.Fatal(err)
}
return string(out)
}
type pvacfgnode struct {
attr, form string
cond ast.Expr
then, els *pvacfgnode
}
// pvacfgbody generates a simplified CFG for a slice of statements,
// containing only calls to putattr and the if statements affecting them.
func pvacfgbody(t *testing.T, fset *token.FileSet, cm ast.CommentMap, body []ast.Stmt) (start, end *pvacfgnode) {
add := func(n *pvacfgnode) {
if start == nil || end == nil {
start = n
end = n
} else {
end.then = n
end = n
}
}
for _, stmt := range body {
switch stmt := stmt.(type) {
case *ast.ExprStmt:
if x, _ := stmt.X.(*ast.CallExpr); x != nil {
funstr := exprToString(x.Fun)
if funstr == "putattr" {
form, _ := x.Args[3].(*ast.Ident)
if form == nil {
t.Fatalf("%s invalid use of putattr", fset.Position(x.Pos()))
}
cmt := findLineComment(cm, stmt)
if cmt == nil {
t.Fatalf("%s invalid use of putattr (no comment containing the attribute name)", fset.Position(x.Pos()))
}
add(&pvacfgnode{attr: strings.TrimSpace(cmt.Text[2:]), form: form.Name})
}
}
case *ast.IfStmt:
ifStart, ifEnd := pvacfgif(t, fset, cm, stmt)
if ifStart != nil {
add(ifStart)
end = ifEnd
}
default:
// check that nothing under this contains a putattr call
ast.Inspect(stmt, func(n ast.Node) bool {
if call, _ := n.(*ast.CallExpr); call != nil {
if exprToString(call.Fun) == "putattr" {
t.Fatalf("%s use of putattr in unsupported block", fset.Position(call.Pos()))
}
}
return true
})
}
}
return start, end
}
func pvacfgif(t *testing.T, fset *token.FileSet, cm ast.CommentMap, ifstmt *ast.IfStmt) (start, end *pvacfgnode) {
thenStart, thenEnd := pvacfgbody(t, fset, cm, ifstmt.Body.List)
var elseStart, elseEnd *pvacfgnode
if ifstmt.Else != nil {
switch els := ifstmt.Else.(type) {
case *ast.IfStmt:
elseStart, elseEnd = pvacfgif(t, fset, cm, els)
case *ast.BlockStmt:
elseStart, elseEnd = pvacfgbody(t, fset, cm, els.List)
default:
t.Fatalf("%s: unexpected statement %T", fset.Position(els.Pos()), els)
}
}
if thenStart != nil && elseStart != nil && thenStart == thenEnd && elseStart == elseEnd && thenStart.form == elseStart.form && thenStart.attr == elseStart.attr {
return thenStart, thenEnd
}
if thenStart != nil || elseStart != nil {
start = &pvacfgnode{cond: ifstmt.Cond}
end = &pvacfgnode{}
if thenStart != nil {
start.then = thenStart
thenEnd.then = end
} else {
start.then = end
}
if elseStart != nil {
start.els = elseStart
elseEnd.then = end
} else {
start.els = end
}
}
return start, end
}
func exprToString(t ast.Expr) string {
var buf bytes.Buffer
printer.Fprint(&buf, token.NewFileSet(), t)
return buf.String()
}
// findLineComment finds the line comment for statement stmt.
func findLineComment(cm ast.CommentMap, stmt *ast.ExprStmt) *ast.Comment {
var r *ast.Comment
for _, cmtg := range cm[stmt] {
for _, cmt := range cmtg.List {
if cmt.Slash > stmt.Pos() {
if r != nil {
return nil
}
r = cmt
}
}
}
return r
}
// pvacfgvisit visits the CFG depth first, populates abbrevs with all
// possible dwAbbrev definitions and returns a tree of if/else statements
// that picks the correct abbrev.
func pvacfgvisit(pvacfg *pvacfgnode, abbrevs map[string]int) ast.Stmt {
r := &ast.IfStmt{Cond: &ast.BinaryExpr{
Op: token.EQL,
X: &ast.SelectorExpr{X: &ast.Ident{Name: "v"}, Sel: &ast.Ident{Name: "Tag"}},
Y: &ast.Ident{Name: "DW_TAG_variable"}}}
r.Body = &ast.BlockStmt{List: []ast.Stmt{
pvacfgvisitnode(pvacfg, "DW_TAG_variable", []*pvacfgnode{}, abbrevs),
}}
r.Else = &ast.BlockStmt{List: []ast.Stmt{
pvacfgvisitnode(pvacfg, "DW_TAG_formal_parameter", []*pvacfgnode{}, abbrevs),
}}
return r
}
func pvacfgvisitnode(pvacfg *pvacfgnode, tag string, path []*pvacfgnode, abbrevs map[string]int) ast.Stmt {
if pvacfg == nil {
abbrev := toabbrev(tag, path)
if _, ok := abbrevs[abbrev]; !ok {
abbrevs[abbrev] = len(abbrevs)
}
return &ast.ReturnStmt{
Results: []ast.Expr{&ast.BinaryExpr{
Op: token.ADD,
X: &ast.Ident{Name: "DW_ABRV_PUTVAR_START"},
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(abbrevs[abbrev])}}}}
}
if pvacfg.attr != "" {
return pvacfgvisitnode(pvacfg.then, tag, append(path, pvacfg), abbrevs)
} else if pvacfg.cond != nil {
if bx, _ := pvacfg.cond.(*ast.BinaryExpr); bx != nil && bx.Op == token.EQL && exprToString(bx.X) == "v.Tag" {
// this condition is "v.Tag == Xxx", check the value of 'tag'
y := exprToString(bx.Y)
if y == tag {
return pvacfgvisitnode(pvacfg.then, tag, path, abbrevs)
} else {
return pvacfgvisitnode(pvacfg.els, tag, path, abbrevs)
}
} else {
r := &ast.IfStmt{Cond: pvacfg.cond}
r.Body = &ast.BlockStmt{List: []ast.Stmt{pvacfgvisitnode(pvacfg.then, tag, path, abbrevs)}}
r.Else = &ast.BlockStmt{List: []ast.Stmt{pvacfgvisitnode(pvacfg.els, tag, path, abbrevs)}}
return r
}
} else {
return pvacfgvisitnode(pvacfg.then, tag, path, abbrevs)
}
}
func toabbrev(tag string, path []*pvacfgnode) string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "{\n%s,\nDW_CHILDREN_no,\n[]dwAttrForm{\n", tag)
for _, node := range path {
if node.cond == nil {
fmt.Fprintf(buf, "{%s, %s},\n", node.attr, node.form)
}
}
fmt.Fprint(buf, "},\n}")
return buf.String()
}