From 08029be9fc12023f036bc695efd688fc4616f0c7 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Thu, 8 Feb 2024 12:06:00 +0100 Subject: [PATCH] cmd/compile/internal/dwarfgen: refactor putvar and putAbstractVar MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Than McIntosh Auto-Submit: Michael Pratt LUCI-TryBot-Result: Go LUCI --- src/cmd/compile/internal/dwarfgen/dwarf.go | 30 +- src/cmd/compile/internal/dwarfgen/dwinl.go | 2 +- src/cmd/internal/dwarf/dwarf.go | 244 ++------------ src/cmd/internal/dwarf/putvarabbrevgen.go | 139 ++++++++ .../internal/dwarf/putvarabbrevgen_test.go | 316 ++++++++++++++++++ 5 files changed, 508 insertions(+), 223 deletions(-) create mode 100644 src/cmd/internal/dwarf/putvarabbrevgen.go create mode 100644 src/cmd/internal/dwarf/putvarabbrevgen_test.go diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go index e9553d1185..733eeff4ac 100644 --- a/src/cmd/compile/internal/dwarfgen/dwarf.go +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -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. diff --git a/src/cmd/compile/internal/dwarfgen/dwinl.go b/src/cmd/compile/internal/dwarfgen/dwinl.go index 655e7c66ac..bb3ef84df8 100644 --- a/src/cmd/compile/internal/dwarfgen/dwinl.go +++ b/src/cmd/compile/internal/dwarfgen/dwinl.go @@ -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 diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index d10b3731df..06cafc8886 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -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 index 0000000000..418063d211 --- /dev/null +++ b/src/cmd/internal/dwarf/putvarabbrevgen.go @@ -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 index 0000000000..24500a3388 --- /dev/null +++ b/src/cmd/internal/dwarf/putvarabbrevgen_test.go @@ -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() +} -- 2.48.1