]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile/internal/dwarfgen: refactor putvar and putAbstractVar
authorAlessandro Arzilli <alessandro.arzilli@gmail.com>
Thu, 8 Feb 2024 11:06:00 +0000 (12:06 +0100)
committerGopher Robot <gobot@golang.org>
Mon, 26 Feb 2024 20:45:07 +0000 (20:45 +0000)
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>

src/cmd/compile/internal/dwarfgen/dwarf.go
src/cmd/compile/internal/dwarfgen/dwinl.go
src/cmd/internal/dwarf/dwarf.go
src/cmd/internal/dwarf/putvarabbrevgen.go [new file with mode: 0644]
src/cmd/internal/dwarf/putvarabbrevgen_test.go [new file with mode: 0644]

index e9553d118535e9c68d7dc931db4dbeefad0dba11..733eeff4ac3c244e448397a89daec9d3c2b27313 100644 (file)
@@ -218,10 +218,10 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
                }
                typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
                decls = append(decls, n)
-               abbrev := dwarf.DW_ABRV_AUTO_LOCLIST
+               tag := dwarf.DW_TAG_variable
                isReturnValue := (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 {
                        // 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() {
                                inlIndex = posInlIndex(n.Pos()) + 1
                                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{
                        Name:          n.Sym().Name,
                        IsReturnValue: isReturnValue,
-                       Abbrev:        abbrev,
+                       Tag:           tag,
+                       WithLoclist:   true,
                        StackOffset:   int32(n.FrameOffset()),
                        Type:          base.Ctxt.Lookup(typename),
                        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 {
-       var abbrev int
+       var tag int
        var offs int64
 
        localAutoOffset := func() int64 {
@@ -367,9 +368,9 @@ func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
        switch n.Class {
        case ir.PAUTO:
                offs = localAutoOffset()
-               abbrev = dwarf.DW_ABRV_AUTO
+               tag = dwarf.DW_TAG_variable
        case ir.PPARAM, ir.PPARAMOUT:
-               abbrev = dwarf.DW_ABRV_PARAM
+               tag = dwarf.DW_TAG_formal_parameter
                if n.IsOutputParamInRegisters() {
                        offs = localAutoOffset()
                } else {
@@ -387,7 +388,7 @@ func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
                if n.InlFormal() || n.InlLocal() {
                        inlIndex = posInlIndex(n.Pos()) + 1
                        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,
                IsReturnValue: n.Class == ir.PPARAMOUT,
                IsInlFormal:   n.InlFormal(),
-               Abbrev:        abbrev,
+               Tag:           tag,
                StackOffset:   int32(offs),
                Type:          base.Ctxt.Lookup(typename),
                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)
        n := debug.Vars[varID]
 
-       var abbrev int
+       var tag int
        switch n.Class {
        case ir.PAUTO:
-               abbrev = dwarf.DW_ABRV_AUTO_LOCLIST
+               tag = dwarf.DW_TAG_variable
        case ir.PPARAM, ir.PPARAMOUT:
-               abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+               tag = dwarf.DW_TAG_formal_parameter
        default:
                return nil
        }
@@ -488,7 +489,7 @@ func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var
                if n.InlFormal() || n.InlLocal() {
                        inlIndex = posInlIndex(n.Pos()) + 1
                        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,
                IsReturnValue: n.Class == ir.PPARAMOUT,
                IsInlFormal:   n.InlFormal(),
-               Abbrev:        abbrev,
+               Tag:           tag,
+               WithLoclist:   true,
                Type:          base.Ctxt.Lookup(typename),
                // The stack offset is used as a sorting key, so for decomposed
                // variables just give it the first one. It's not used otherwise.
index 655e7c66ac24f8da8efe7d81156e73006c8ad365..bb3ef84df8639e01f41e6b4e3717a8e6e5a8a0b4 100644 (file)
@@ -358,7 +358,7 @@ func dumpInlCalls(inlcalls dwarf.InlCalls) {
 func dumpInlVars(dwvars []*dwarf.Var) {
        for i, dwv := range dwvars {
                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"
                }
                ia := 0
index d10b3731df4c4988e247c1586dd6d51c9ba063ef..06cafc8886c83eeb31bd8bfb51d51dee5d1f8f45 100644 (file)
@@ -45,7 +45,8 @@ type Sym interface {
 // A Var represents a local variable or a function parameter.
 type Var struct {
        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
        IsInlFormal   bool
        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_VARIABLE
        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_SIMPLE
        DW_ABRV_STRUCTFIELD
@@ -361,7 +352,7 @@ const (
        DW_ABRV_STRUCTTYPE
        DW_ABRV_TYPEDECL
        DW_ABRV_DICT_INDEX
-       DW_NABRV
+       DW_ABRV_PUTVAR_START
 )
 
 type dwAbbrev struct {
@@ -394,22 +385,23 @@ func expandPseudoForm(form uint8) uint8 {
 // expanding any DW_FORM pseudo-ops to real values.
 func Abbrevs() []dwAbbrev {
        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++ {
                        abbrevs[i].attr[j].form = expandPseudoForm(abbrevs[i].attr[j].form)
                }
        }
        abbrevsFinalized = true
-       return abbrevs[:]
+       return abbrevs
 }
 
 // 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
 // the 'pseudo-form' entries below to real DWARF form values.
 
-var abbrevs = [DW_NABRV]dwAbbrev{
+var abbrevs = []dwAbbrev{
        /* The mandatory DW_ABRV_NULL entry. */
        {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 */
        {
                DW_TAG_lexical_block,
@@ -901,7 +781,7 @@ var abbrevs = [DW_NABRV]dwAbbrev{
 func GetAbbrev() []byte {
        abbrevs := Abbrevs()
        var buf []byte
-       for i := 1; i < DW_NABRV; i++ {
+       for i := 1; i < len(abbrevs); i++ {
                // See section 7.5.3
                buf = AppendUleb128(buf, uint64(i))
                buf = AppendUleb128(buf, uint64(abbrevs[i].tag))
@@ -1548,39 +1428,7 @@ func putscope(ctxt Context, s *FnState, scopes []Scope, curscope int32, fnabbrev
        return curscope
 }
 
-// Given a default var abbrev code, select corresponding concrete code.
-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.
+func concreteVar(fnabbrev int, v *Var) bool {
        concrete := true
        switch fnabbrev {
        case DW_ABRV_FUNCTION, DW_ABRV_WRAPPER:
@@ -1596,64 +1444,44 @@ func determineVarAbbrev(v *Var, fnabbrev int) (int, bool, bool) {
        default:
                panic("should never happen")
        }
-
-       // 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
-       }
+       return concrete
 }
 
 // Emit DWARF attributes for a variable belonging to an 'abstract' subprogram.
 func putAbstractVar(ctxt Context, info Sym, v *Var) {
-       // Remap abbrev
-       abbrev := v.Abbrev
-       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
-       }
-
+       // The contents of this functions are used to generate putAbstractVarAbbrev automatically, see TestPutVarAbbrevGenerator.
+       abbrev := putAbstractVarAbbrev(v)
        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
-       if abbrev == DW_ABRV_PARAM_ABSTRACT {
+       if v.Tag == DW_TAG_formal_parameter {
                var isReturn int64
                if v.IsReturnValue {
                        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
-       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.
-               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
-       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
 }
 
 func putvar(ctxt Context, s *FnState, v *Var, absfn Sym, fnabbrev, inlIndex int, encbuf []byte) {
-       // Remap abbrev according to parent DIE abbrev
-       abbrev, missing, concrete := determineVarAbbrev(v, fnabbrev)
+       // The contents of this functions are used to generate putvarAbbrev automatically, see TestPutVarAbbrevGenerator.
+       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))
 
        // 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
                // after the call to 'putattr' below we make a call to register
                // 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)
        } else {
                // Var name, line for abstract and default cases
                n := v.Name
-               putattr(ctxt, s.Info, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n)
-               if abbrev == DW_ABRV_PARAM || abbrev == DW_ABRV_PARAM_LOCLIST || abbrev == DW_ABRV_PARAM_ABSTRACT {
+               putattr(ctxt, s.Info, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) // DW_AT_name
+               if v.Tag == DW_TAG_formal_parameter {
                        var isReturn int64
                        if v.IsReturnValue {
                                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)
-               if v.DictIndex > 0 && s.dictIndexToOffset != nil && s.dictIndexToOffset[v.DictIndex-1] != 0 {
+               putattr(ctxt, s.Info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(v.DeclLine), nil) // DW_AT_decl_line
+               if hasParametricType {
                        // 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 {
-                       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) {
-               putattr(ctxt, s.Info, abbrev, DW_FORM_sec_offset, DW_CLS_PTR, ctxt.Size(s.Loc), s.Loc)
+       if withLoclist {
+               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)
        } else {
                loc := encbuf[:0]
                switch {
-               case missing:
+               case v.WithLoclist:
                        break // no location
                case v.StackOffset == 0:
                        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 = 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
diff --git a/src/cmd/internal/dwarf/putvarabbrevgen.go b/src/cmd/internal/dwarf/putvarabbrevgen.go
new file mode 100644 (file)
index 0000000..418063d
--- /dev/null
@@ -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
+                       }
+               }
+       }
+}
diff --git a/src/cmd/internal/dwarf/putvarabbrevgen_test.go b/src/cmd/internal/dwarf/putvarabbrevgen_test.go
new file mode 100644 (file)
index 0000000..24500a3
--- /dev/null
@@ -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()
+}