summaryrefslogtreecommitdiffstats
path: root/src/cmd/compile/internal/dwarfgen
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/dwarfgen')
-rw-r--r--src/cmd/compile/internal/dwarfgen/dwarf.go585
-rw-r--r--src/cmd/compile/internal/dwarfgen/dwinl.go454
-rw-r--r--src/cmd/compile/internal/dwarfgen/marker.go94
-rw-r--r--src/cmd/compile/internal/dwarfgen/scope.go136
-rw-r--r--src/cmd/compile/internal/dwarfgen/scope_test.go539
5 files changed, 1808 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go
new file mode 100644
index 0000000..8ae03d7
--- /dev/null
+++ b/src/cmd/compile/internal/dwarfgen/dwarf.go
@@ -0,0 +1,585 @@
+// Copyright 2011 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 dwarfgen
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "internal/buildcfg"
+ "sort"
+
+ "cmd/compile/internal/base"
+ "cmd/compile/internal/ir"
+ "cmd/compile/internal/reflectdata"
+ "cmd/compile/internal/ssa"
+ "cmd/compile/internal/ssagen"
+ "cmd/compile/internal/types"
+ "cmd/internal/dwarf"
+ "cmd/internal/obj"
+ "cmd/internal/objabi"
+ "cmd/internal/src"
+)
+
+func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) {
+ fn := curfn.(*ir.Func)
+
+ if fn.Nname != nil {
+ expect := fn.Linksym()
+ if fnsym.ABI() == obj.ABI0 {
+ expect = fn.LinksymABI(obj.ABI0)
+ }
+ if fnsym != expect {
+ base.Fatalf("unexpected fnsym: %v != %v", fnsym, expect)
+ }
+ }
+
+ // Back when there were two different *Funcs for a function, this code
+ // was not consistent about whether a particular *Node being processed
+ // was an ODCLFUNC or ONAME node. Partly this is because inlined function
+ // bodies have no ODCLFUNC node, which was it's own inconsistency.
+ // In any event, the handling of the two different nodes for DWARF purposes
+ // was subtly different, likely in unintended ways. CL 272253 merged the
+ // two nodes' Func fields, so that code sees the same *Func whether it is
+ // holding the ODCLFUNC or the ONAME. This resulted in changes in the
+ // DWARF output. To preserve the existing DWARF output and leave an
+ // intentional change for a future CL, this code does the following when
+ // fn.Op == ONAME:
+ //
+ // 1. Disallow use of createComplexVars in createDwarfVars.
+ // It was not possible to reach that code for an ONAME before,
+ // because the DebugInfo was set only on the ODCLFUNC Func.
+ // Calling into it in the ONAME case causes an index out of bounds panic.
+ //
+ // 2. Do not populate apdecls. fn.Func.Dcl was in the ODCLFUNC Func,
+ // not the ONAME Func. Populating apdecls for the ONAME case results
+ // in selected being populated after createSimpleVars is called in
+ // createDwarfVars, and then that causes the loop to skip all the entries
+ // in dcl, meaning that the RecordAutoType calls don't happen.
+ //
+ // These two adjustments keep toolstash -cmp working for now.
+ // Deciding the right answer is, as they say, future work.
+ //
+ // We can tell the difference between the old ODCLFUNC and ONAME
+ // cases by looking at the infosym.Name. If it's empty, DebugInfo is
+ // being called from (*obj.Link).populateDWARF, which used to use
+ // the ODCLFUNC. If it's non-empty (the name will end in $abstract),
+ // DebugInfo is being called from (*obj.Link).DwarfAbstractFunc,
+ // which used to use the ONAME form.
+ isODCLFUNC := infosym.Name == ""
+
+ var apdecls []*ir.Name
+ // Populate decls for fn.
+ if isODCLFUNC {
+ for _, n := range fn.Dcl {
+ if n.Op() != ir.ONAME { // might be OTYPE or OLITERAL
+ continue
+ }
+ switch n.Class {
+ case ir.PAUTO:
+ if !n.Used() {
+ // Text == nil -> generating abstract function
+ if fnsym.Func().Text != nil {
+ base.Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)")
+ }
+ continue
+ }
+ case ir.PPARAM, ir.PPARAMOUT:
+ default:
+ continue
+ }
+ apdecls = append(apdecls, n)
+ if n.Type().Kind() == types.TSSA {
+ // Can happen for TypeInt128 types. This only happens for
+ // spill locations, so not a huge deal.
+ continue
+ }
+ fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type()))
+ }
+ }
+
+ decls, dwarfVars := createDwarfVars(fnsym, isODCLFUNC, fn, apdecls)
+
+ // For each type referenced by the functions auto vars but not
+ // already referenced by a dwarf var, attach an R_USETYPE relocation to
+ // the function symbol to insure that the type included in DWARF
+ // processing during linking.
+ typesyms := []*obj.LSym{}
+ for t, _ := range fnsym.Func().Autot {
+ typesyms = append(typesyms, t)
+ }
+ sort.Sort(obj.BySymName(typesyms))
+ for _, sym := range typesyms {
+ r := obj.Addrel(infosym)
+ r.Sym = sym
+ r.Type = objabi.R_USETYPE
+ }
+ fnsym.Func().Autot = nil
+
+ var varScopes []ir.ScopeID
+ for _, decl := range decls {
+ pos := declPos(decl)
+ varScopes = append(varScopes, findScope(fn.Marks, pos))
+ }
+
+ scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes)
+ var inlcalls dwarf.InlCalls
+ if base.Flag.GenDwarfInl > 0 {
+ inlcalls = assembleInlines(fnsym, dwarfVars)
+ }
+ return scopes, inlcalls
+}
+
+func declPos(decl *ir.Name) src.XPos {
+ return decl.Canonical().Pos()
+}
+
+// createDwarfVars process fn, returning a list of DWARF variables and the
+// Nodes they represent.
+func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var) {
+ // Collect a raw list of DWARF vars.
+ var vars []*dwarf.Var
+ var decls []*ir.Name
+ var selected ir.NameSet
+
+ if base.Ctxt.Flag_locationlists && base.Ctxt.Flag_optimize && fn.DebugInfo != nil && complexOK {
+ decls, vars, selected = createComplexVars(fnsym, fn)
+ } else if fn.ABI == obj.ABIInternal && base.Flag.N != 0 && complexOK {
+ decls, vars, selected = createABIVars(fnsym, fn, apDecls)
+ } else {
+ decls, vars, selected = createSimpleVars(fnsym, apDecls)
+ }
+
+ dcl := apDecls
+ if fnsym.WasInlined() {
+ dcl = preInliningDcls(fnsym)
+ } else {
+ // The backend's stackframe pass prunes away entries from the
+ // fn's Dcl list, including PARAMOUT nodes that correspond to
+ // output params passed in registers. Add back in these
+ // entries here so that we can process them properly during
+ // DWARF-gen. See issue 48573 for more details.
+ debugInfo := fn.DebugInfo.(*ssa.FuncDebug)
+ for _, n := range debugInfo.RegOutputParams {
+ if n.Class != ir.PPARAMOUT || !n.IsOutputParamInRegisters() {
+ panic("invalid ir.Name on debugInfo.RegOutputParams list")
+ }
+ dcl = append(dcl, n)
+ }
+ }
+
+ // If optimization is enabled, the list above will typically be
+ // missing some of the original pre-optimization variables in the
+ // function (they may have been promoted to registers, folded into
+ // constants, dead-coded away, etc). Input arguments not eligible
+ // for SSA optimization are also missing. Here we add back in entries
+ // for selected missing vars. Note that the recipe below creates a
+ // conservative location. The idea here is that we want to
+ // communicate to the user that "yes, there is a variable named X
+ // in this function, but no, I don't have enough information to
+ // reliably report its contents."
+ // For non-SSA-able arguments, however, the correct information
+ // is known -- they have a single home on the stack.
+ for _, n := range dcl {
+ if selected.Has(n) {
+ continue
+ }
+ c := n.Sym().Name[0]
+ if c == '.' || n.Type().IsUntyped() {
+ continue
+ }
+ if n.Class == ir.PPARAM && !ssagen.TypeOK(n.Type()) {
+ // SSA-able args get location lists, and may move in and
+ // out of registers, so those are handled elsewhere.
+ // Autos and named output params seem to get handled
+ // with VARDEF, which creates location lists.
+ // Args not of SSA-able type are treated here; they
+ // are homed on the stack in a single place for the
+ // entire call.
+ vars = append(vars, createSimpleVar(fnsym, n))
+ decls = append(decls, n)
+ continue
+ }
+ typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
+ decls = append(decls, n)
+ abbrev := dwarf.DW_ABRV_AUTO_LOCLIST
+ isReturnValue := (n.Class == ir.PPARAMOUT)
+ if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT {
+ abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+ }
+ if n.Esc() == ir.EscHeap {
+ // The variable in question has been promoted to the heap.
+ // Its address is in n.Heapaddr.
+ // TODO(thanm): generate a better location expression
+ }
+ inlIndex := 0
+ if base.Flag.GenDwarfInl > 1 {
+ if n.InlFormal() || n.InlLocal() {
+ inlIndex = posInlIndex(n.Pos()) + 1
+ if n.InlFormal() {
+ abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+ }
+ }
+ }
+ declpos := base.Ctxt.InnermostPos(n.Pos())
+ vars = append(vars, &dwarf.Var{
+ Name: n.Sym().Name,
+ IsReturnValue: isReturnValue,
+ Abbrev: abbrev,
+ StackOffset: int32(n.FrameOffset()),
+ Type: base.Ctxt.Lookup(typename),
+ DeclFile: declpos.RelFilename(),
+ DeclLine: declpos.RelLine(),
+ DeclCol: declpos.RelCol(),
+ InlIndex: int32(inlIndex),
+ ChildIndex: -1,
+ DictIndex: n.DictIndex,
+ })
+ // Record go type of to insure that it gets emitted by the linker.
+ fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type()))
+ }
+
+ // Sort decls and vars.
+ sortDeclsAndVars(fn, decls, vars)
+
+ return decls, vars
+}
+
+// sortDeclsAndVars sorts the decl and dwarf var lists according to
+// parameter declaration order, so as to insure that when a subprogram
+// DIE is emitted, its parameter children appear in declaration order.
+// Prior to the advent of the register ABI, sorting by frame offset
+// would achieve this; with the register we now need to go back to the
+// original function signature.
+func sortDeclsAndVars(fn *ir.Func, decls []*ir.Name, vars []*dwarf.Var) {
+ paramOrder := make(map[*ir.Name]int)
+ idx := 1
+ for _, selfn := range types.RecvsParamsResults {
+ fsl := selfn(fn.Type()).FieldSlice()
+ for _, f := range fsl {
+ if n, ok := f.Nname.(*ir.Name); ok {
+ paramOrder[n] = idx
+ idx++
+ }
+ }
+ }
+ sort.Stable(varsAndDecls{decls, vars, paramOrder})
+}
+
+type varsAndDecls struct {
+ decls []*ir.Name
+ vars []*dwarf.Var
+ paramOrder map[*ir.Name]int
+}
+
+func (v varsAndDecls) Len() int {
+ return len(v.decls)
+}
+
+func (v varsAndDecls) Less(i, j int) bool {
+ nameLT := func(ni, nj *ir.Name) bool {
+ oi, foundi := v.paramOrder[ni]
+ oj, foundj := v.paramOrder[nj]
+ if foundi {
+ if foundj {
+ return oi < oj
+ } else {
+ return true
+ }
+ }
+ return false
+ }
+ return nameLT(v.decls[i], v.decls[j])
+}
+
+func (v varsAndDecls) Swap(i, j int) {
+ v.vars[i], v.vars[j] = v.vars[j], v.vars[i]
+ v.decls[i], v.decls[j] = v.decls[j], v.decls[i]
+}
+
+// Given a function that was inlined at some point during the
+// compilation, return a sorted list of nodes corresponding to the
+// autos/locals in that function prior to inlining. If this is a
+// function that is not local to the package being compiled, then the
+// names of the variables may have been "versioned" to avoid conflicts
+// with local vars; disregard this versioning when sorting.
+func preInliningDcls(fnsym *obj.LSym) []*ir.Name {
+ fn := base.Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*ir.Func)
+ var rdcl []*ir.Name
+ for _, n := range fn.Inl.Dcl {
+ c := n.Sym().Name[0]
+ // Avoid reporting "_" parameters, since if there are more than
+ // one, it can result in a collision later on, as in #23179.
+ if unversion(n.Sym().Name) == "_" || c == '.' || n.Type().IsUntyped() {
+ continue
+ }
+ rdcl = append(rdcl, n)
+ }
+ return rdcl
+}
+
+// createSimpleVars creates a DWARF entry for every variable declared in the
+// function, claiming that they are permanently on the stack.
+func createSimpleVars(fnsym *obj.LSym, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) {
+ var vars []*dwarf.Var
+ var decls []*ir.Name
+ var selected ir.NameSet
+ for _, n := range apDecls {
+ if ir.IsAutoTmp(n) {
+ continue
+ }
+
+ decls = append(decls, n)
+ vars = append(vars, createSimpleVar(fnsym, n))
+ selected.Add(n)
+ }
+ return decls, vars, selected
+}
+
+func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
+ var abbrev int
+ var offs int64
+
+ localAutoOffset := func() int64 {
+ offs = n.FrameOffset()
+ if base.Ctxt.FixedFrameSize() == 0 {
+ offs -= int64(types.PtrSize)
+ }
+ if buildcfg.FramePointerEnabled {
+ offs -= int64(types.PtrSize)
+ }
+ return offs
+ }
+
+ switch n.Class {
+ case ir.PAUTO:
+ offs = localAutoOffset()
+ abbrev = dwarf.DW_ABRV_AUTO
+ case ir.PPARAM, ir.PPARAMOUT:
+ abbrev = dwarf.DW_ABRV_PARAM
+ if n.IsOutputParamInRegisters() {
+ offs = localAutoOffset()
+ } else {
+ offs = n.FrameOffset() + base.Ctxt.FixedFrameSize()
+ }
+
+ default:
+ base.Fatalf("createSimpleVar unexpected class %v for node %v", n.Class, n)
+ }
+
+ typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
+ delete(fnsym.Func().Autot, reflectdata.TypeLinksym(n.Type()))
+ inlIndex := 0
+ if base.Flag.GenDwarfInl > 1 {
+ if n.InlFormal() || n.InlLocal() {
+ inlIndex = posInlIndex(n.Pos()) + 1
+ if n.InlFormal() {
+ abbrev = dwarf.DW_ABRV_PARAM
+ }
+ }
+ }
+ declpos := base.Ctxt.InnermostPos(declPos(n))
+ return &dwarf.Var{
+ Name: n.Sym().Name,
+ IsReturnValue: n.Class == ir.PPARAMOUT,
+ IsInlFormal: n.InlFormal(),
+ Abbrev: abbrev,
+ StackOffset: int32(offs),
+ Type: base.Ctxt.Lookup(typename),
+ DeclFile: declpos.RelFilename(),
+ DeclLine: declpos.RelLine(),
+ DeclCol: declpos.RelCol(),
+ InlIndex: int32(inlIndex),
+ ChildIndex: -1,
+ DictIndex: n.DictIndex,
+ }
+}
+
+// createABIVars creates DWARF variables for functions in which the
+// register ABI is enabled but optimization is turned off. It uses a
+// hybrid approach in which register-resident input params are
+// captured with location lists, and all other vars use the "simple"
+// strategy.
+func createABIVars(fnsym *obj.LSym, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) {
+
+ // Invoke createComplexVars to generate dwarf vars for input parameters
+ // that are register-allocated according to the ABI rules.
+ decls, vars, selected := createComplexVars(fnsym, fn)
+
+ // Now fill in the remainder of the variables: input parameters
+ // that are not register-resident, output parameters, and local
+ // variables.
+ for _, n := range apDecls {
+ if ir.IsAutoTmp(n) {
+ continue
+ }
+ if _, ok := selected[n]; ok {
+ // already handled
+ continue
+ }
+
+ decls = append(decls, n)
+ vars = append(vars, createSimpleVar(fnsym, n))
+ selected.Add(n)
+ }
+
+ return decls, vars, selected
+}
+
+// createComplexVars creates recomposed DWARF vars with location lists,
+// suitable for describing optimized code.
+func createComplexVars(fnsym *obj.LSym, fn *ir.Func) ([]*ir.Name, []*dwarf.Var, ir.NameSet) {
+ debugInfo := fn.DebugInfo.(*ssa.FuncDebug)
+
+ // Produce a DWARF variable entry for each user variable.
+ var decls []*ir.Name
+ var vars []*dwarf.Var
+ var ssaVars ir.NameSet
+
+ for varID, dvar := range debugInfo.Vars {
+ n := dvar
+ ssaVars.Add(n)
+ for _, slot := range debugInfo.VarSlots[varID] {
+ ssaVars.Add(debugInfo.Slots[slot].N)
+ }
+
+ if dvar := createComplexVar(fnsym, fn, ssa.VarID(varID)); dvar != nil {
+ decls = append(decls, n)
+ vars = append(vars, dvar)
+ }
+ }
+
+ return decls, vars, ssaVars
+}
+
+// createComplexVar builds a single DWARF variable entry and location list.
+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
+ switch n.Class {
+ case ir.PAUTO:
+ abbrev = dwarf.DW_ABRV_AUTO_LOCLIST
+ case ir.PPARAM, ir.PPARAMOUT:
+ abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+ default:
+ return nil
+ }
+
+ gotype := reflectdata.TypeLinksym(n.Type())
+ delete(fnsym.Func().Autot, gotype)
+ typename := dwarf.InfoPrefix + gotype.Name[len("type."):]
+ inlIndex := 0
+ if base.Flag.GenDwarfInl > 1 {
+ if n.InlFormal() || n.InlLocal() {
+ inlIndex = posInlIndex(n.Pos()) + 1
+ if n.InlFormal() {
+ abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+ }
+ }
+ }
+ declpos := base.Ctxt.InnermostPos(n.Pos())
+ dvar := &dwarf.Var{
+ Name: n.Sym().Name,
+ IsReturnValue: n.Class == ir.PPARAMOUT,
+ IsInlFormal: n.InlFormal(),
+ Abbrev: abbrev,
+ 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.
+ // This won't work well if the first slot hasn't been assigned a stack
+ // location, but it's not obvious how to do better.
+ StackOffset: ssagen.StackOffset(debug.Slots[debug.VarSlots[varID][0]]),
+ DeclFile: declpos.RelFilename(),
+ DeclLine: declpos.RelLine(),
+ DeclCol: declpos.RelCol(),
+ InlIndex: int32(inlIndex),
+ ChildIndex: -1,
+ DictIndex: n.DictIndex,
+ }
+ list := debug.LocationLists[varID]
+ if len(list) != 0 {
+ dvar.PutLocationList = func(listSym, startPC dwarf.Sym) {
+ debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym))
+ }
+ }
+ return dvar
+}
+
+// RecordFlags records the specified command-line flags to be placed
+// in the DWARF info.
+func RecordFlags(flags ...string) {
+ if base.Ctxt.Pkgpath == "" {
+ // We can't record the flags if we don't know what the
+ // package name is.
+ return
+ }
+
+ type BoolFlag interface {
+ IsBoolFlag() bool
+ }
+ type CountFlag interface {
+ IsCountFlag() bool
+ }
+ var cmd bytes.Buffer
+ for _, name := range flags {
+ f := flag.Lookup(name)
+ if f == nil {
+ continue
+ }
+ getter := f.Value.(flag.Getter)
+ if getter.String() == f.DefValue {
+ // Flag has default value, so omit it.
+ continue
+ }
+ if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() {
+ val, ok := getter.Get().(bool)
+ if ok && val {
+ fmt.Fprintf(&cmd, " -%s", f.Name)
+ continue
+ }
+ }
+ if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() {
+ val, ok := getter.Get().(int)
+ if ok && val == 1 {
+ fmt.Fprintf(&cmd, " -%s", f.Name)
+ continue
+ }
+ }
+ fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get())
+ }
+
+ // Adds flag to producer string singalling whether regabi is turned on or
+ // off.
+ // Once regabi is turned on across the board and the relative GOEXPERIMENT
+ // knobs no longer exist this code should be removed.
+ if buildcfg.Experiment.RegabiArgs {
+ cmd.Write([]byte(" regabi"))
+ }
+
+ if cmd.Len() == 0 {
+ return
+ }
+ s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + base.Ctxt.Pkgpath)
+ s.Type = objabi.SDWARFCUINFO
+ // Sometimes (for example when building tests) we can link
+ // together two package main archives. So allow dups.
+ s.Set(obj.AttrDuplicateOK, true)
+ base.Ctxt.Data = append(base.Ctxt.Data, s)
+ s.P = cmd.Bytes()[1:]
+}
+
+// RecordPackageName records the name of the package being
+// compiled, so that the linker can save it in the compile unit's DIE.
+func RecordPackageName() {
+ s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + base.Ctxt.Pkgpath)
+ s.Type = objabi.SDWARFCUINFO
+ // Sometimes (for example when building tests) we can link
+ // together two package main archives. So allow dups.
+ s.Set(obj.AttrDuplicateOK, true)
+ base.Ctxt.Data = append(base.Ctxt.Data, s)
+ s.P = []byte(types.LocalPkg.Name)
+}
diff --git a/src/cmd/compile/internal/dwarfgen/dwinl.go b/src/cmd/compile/internal/dwarfgen/dwinl.go
new file mode 100644
index 0000000..c785e06
--- /dev/null
+++ b/src/cmd/compile/internal/dwarfgen/dwinl.go
@@ -0,0 +1,454 @@
+// Copyright 2017 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 dwarfgen
+
+import (
+ "fmt"
+ "strings"
+
+ "cmd/compile/internal/base"
+ "cmd/compile/internal/ir"
+ "cmd/internal/dwarf"
+ "cmd/internal/obj"
+ "cmd/internal/src"
+)
+
+// To identify variables by original source position.
+type varPos struct {
+ DeclName string
+ DeclFile string
+ DeclLine uint
+ DeclCol uint
+}
+
+// This is the main entry point for collection of raw material to
+// drive generation of DWARF "inlined subroutine" DIEs. See proposal
+// 22080 for more details and background info.
+func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls {
+ var inlcalls dwarf.InlCalls
+
+ if base.Debug.DwarfInl != 0 {
+ base.Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name)
+ }
+
+ // This maps inline index (from Ctxt.InlTree) to index in inlcalls.Calls
+ imap := make(map[int]int)
+
+ // Walk progs to build up the InlCalls data structure
+ var prevpos src.XPos
+ for p := fnsym.Func().Text; p != nil; p = p.Link {
+ if p.Pos == prevpos {
+ continue
+ }
+ ii := posInlIndex(p.Pos)
+ if ii >= 0 {
+ insertInlCall(&inlcalls, ii, imap)
+ }
+ prevpos = p.Pos
+ }
+
+ // This is used to partition DWARF vars by inline index. Vars not
+ // produced by the inliner will wind up in the vmap[0] entry.
+ vmap := make(map[int32][]*dwarf.Var)
+
+ // Now walk the dwarf vars and partition them based on whether they
+ // were produced by the inliner (dwv.InlIndex > 0) or were original
+ // vars/params from the function (dwv.InlIndex == 0).
+ for _, dwv := range dwVars {
+
+ vmap[dwv.InlIndex] = append(vmap[dwv.InlIndex], dwv)
+
+ // Zero index => var was not produced by an inline
+ if dwv.InlIndex == 0 {
+ continue
+ }
+
+ // Look up index in our map, then tack the var in question
+ // onto the vars list for the correct inlined call.
+ ii := int(dwv.InlIndex) - 1
+ idx, ok := imap[ii]
+ if !ok {
+ // We can occasionally encounter a var produced by the
+ // inliner for which there is no remaining prog; add a new
+ // entry to the call list in this scenario.
+ idx = insertInlCall(&inlcalls, ii, imap)
+ }
+ inlcalls.Calls[idx].InlVars =
+ append(inlcalls.Calls[idx].InlVars, dwv)
+ }
+
+ // Post process the map above to assign child indices to vars.
+ //
+ // A given variable is treated differently depending on whether it
+ // is part of the top-level function (ii == 0) or if it was
+ // produced as a result of an inline (ii != 0).
+ //
+ // If a variable was not produced by an inline and its containing
+ // function was not inlined, then we just assign an ordering of
+ // based on variable name.
+ //
+ // If a variable was not produced by an inline and its containing
+ // function was inlined, then we need to assign a child index
+ // based on the order of vars in the abstract function (in
+ // addition, those vars that don't appear in the abstract
+ // function, such as "~r1", are flagged as such).
+ //
+ // If a variable was produced by an inline, then we locate it in
+ // the pre-inlining decls for the target function and assign child
+ // index accordingly.
+ for ii, sl := range vmap {
+ var m map[varPos]int
+ if ii == 0 {
+ if !fnsym.WasInlined() {
+ for j, v := range sl {
+ v.ChildIndex = int32(j)
+ }
+ continue
+ }
+ m = makePreinlineDclMap(fnsym)
+ } else {
+ ifnlsym := base.Ctxt.InlTree.InlinedFunction(int(ii - 1))
+ m = makePreinlineDclMap(ifnlsym)
+ }
+
+ // Here we assign child indices to variables based on
+ // pre-inlined decls, and set the "IsInAbstract" flag
+ // appropriately. In addition: parameter and local variable
+ // names are given "middle dot" version numbers as part of the
+ // writing them out to export data (see issue 4326). If DWARF
+ // inlined routine generation is turned on, we want to undo
+ // this versioning, since DWARF variables in question will be
+ // parented by the inlined routine and not the top-level
+ // caller.
+ synthCount := len(m)
+ for _, v := range sl {
+ canonName := unversion(v.Name)
+ vp := varPos{
+ DeclName: canonName,
+ DeclFile: v.DeclFile,
+ DeclLine: v.DeclLine,
+ DeclCol: v.DeclCol,
+ }
+ synthesized := strings.HasPrefix(v.Name, "~r") || canonName == "_" || strings.HasPrefix(v.Name, "~b")
+ if idx, found := m[vp]; found {
+ v.ChildIndex = int32(idx)
+ v.IsInAbstract = !synthesized
+ v.Name = canonName
+ } else {
+ // Variable can't be found in the pre-inline dcl list.
+ // In the top-level case (ii=0) this can happen
+ // because a composite variable was split into pieces,
+ // and we're looking at a piece. We can also see
+ // return temps (~r%d) that were created during
+ // lowering, or unnamed params ("_").
+ v.ChildIndex = int32(synthCount)
+ synthCount++
+ }
+ }
+ }
+
+ // Make a second pass through the progs to compute PC ranges for
+ // the various inlined calls.
+ start := int64(-1)
+ curii := -1
+ var prevp *obj.Prog
+ for p := fnsym.Func().Text; p != nil; prevp, p = p, p.Link {
+ if prevp != nil && p.Pos == prevp.Pos {
+ continue
+ }
+ ii := posInlIndex(p.Pos)
+ if ii == curii {
+ continue
+ }
+ // Close out the current range
+ if start != -1 {
+ addRange(inlcalls.Calls, start, p.Pc, curii, imap)
+ }
+ // Begin new range
+ start = p.Pc
+ curii = ii
+ }
+ if start != -1 {
+ addRange(inlcalls.Calls, start, fnsym.Size, curii, imap)
+ }
+
+ // Issue 33188: if II foo is a child of II bar, then ensure that
+ // bar's ranges include the ranges of foo (the loop above will produce
+ // disjoint ranges).
+ for k, c := range inlcalls.Calls {
+ if c.Root {
+ unifyCallRanges(inlcalls, k)
+ }
+ }
+
+ // Debugging
+ if base.Debug.DwarfInl != 0 {
+ dumpInlCalls(inlcalls)
+ dumpInlVars(dwVars)
+ }
+
+ // Perform a consistency check on inlined routine PC ranges
+ // produced by unifyCallRanges above. In particular, complain in
+ // cases where you have A -> B -> C (e.g. C is inlined into B, and
+ // B is inlined into A) and the ranges for B are not enclosed
+ // within the ranges for A, or C within B.
+ for k, c := range inlcalls.Calls {
+ if c.Root {
+ checkInlCall(fnsym.Name, inlcalls, fnsym.Size, k, -1)
+ }
+ }
+
+ return inlcalls
+}
+
+// Secondary hook for DWARF inlined subroutine generation. This is called
+// late in the compilation when it is determined that we need an
+// abstract function DIE for an inlined routine imported from a
+// previously compiled package.
+func AbstractFunc(fn *obj.LSym) {
+ ifn := base.Ctxt.DwFixups.GetPrecursorFunc(fn)
+ if ifn == nil {
+ base.Ctxt.Diag("failed to locate precursor fn for %v", fn)
+ return
+ }
+ _ = ifn.(*ir.Func)
+ if base.Debug.DwarfInl != 0 {
+ base.Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name)
+ }
+ base.Ctxt.DwarfAbstractFunc(ifn, fn, base.Ctxt.Pkgpath)
+}
+
+// Undo any versioning performed when a name was written
+// out as part of export data.
+func unversion(name string) string {
+ if i := strings.Index(name, "ยท"); i > 0 {
+ name = name[:i]
+ }
+ return name
+}
+
+// Given a function that was inlined as part of the compilation, dig
+// up the pre-inlining DCL list for the function and create a map that
+// supports lookup of pre-inline dcl index, based on variable
+// position/name. NB: the recipe for computing variable pos/file/line
+// needs to be kept in sync with the similar code in gc.createSimpleVars
+// and related functions.
+func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int {
+ dcl := preInliningDcls(fnsym)
+ m := make(map[varPos]int)
+ for i, n := range dcl {
+ pos := base.Ctxt.InnermostPos(n.Pos())
+ vp := varPos{
+ DeclName: unversion(n.Sym().Name),
+ DeclFile: pos.RelFilename(),
+ DeclLine: pos.RelLine(),
+ DeclCol: pos.RelCol(),
+ }
+ if _, found := m[vp]; found {
+ // We can see collisions (variables with the same name/file/line/col) in obfuscated or machine-generated code -- see issue 44378 for an example. Skip duplicates in such cases, since it is unlikely that a human will be debugging such code.
+ continue
+ }
+ m[vp] = i
+ }
+ return m
+}
+
+func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int {
+ callIdx, found := imap[inlIdx]
+ if found {
+ return callIdx
+ }
+
+ // Haven't seen this inline yet. Visit parent of inline if there
+ // is one. We do this first so that parents appear before their
+ // children in the resulting table.
+ parCallIdx := -1
+ parInlIdx := base.Ctxt.InlTree.Parent(inlIdx)
+ if parInlIdx >= 0 {
+ parCallIdx = insertInlCall(dwcalls, parInlIdx, imap)
+ }
+
+ // Create new entry for this inline
+ inlinedFn := base.Ctxt.InlTree.InlinedFunction(inlIdx)
+ callXPos := base.Ctxt.InlTree.CallPos(inlIdx)
+ absFnSym := base.Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn)
+ pb := base.Ctxt.PosTable.Pos(callXPos).Base()
+ callFileSym := base.Ctxt.Lookup(pb.SymFilename())
+ ic := dwarf.InlCall{
+ InlIndex: inlIdx,
+ CallFile: callFileSym,
+ CallLine: uint32(callXPos.Line()),
+ AbsFunSym: absFnSym,
+ Root: parCallIdx == -1,
+ }
+ dwcalls.Calls = append(dwcalls.Calls, ic)
+ callIdx = len(dwcalls.Calls) - 1
+ imap[inlIdx] = callIdx
+
+ if parCallIdx != -1 {
+ // Add this inline to parent's child list
+ dwcalls.Calls[parCallIdx].Children = append(dwcalls.Calls[parCallIdx].Children, callIdx)
+ }
+
+ return callIdx
+}
+
+// Given a src.XPos, return its associated inlining index if it
+// corresponds to something created as a result of an inline, or -1 if
+// there is no inline info. Note that the index returned will refer to
+// the deepest call in the inlined stack, e.g. if you have "A calls B
+// calls C calls D" and all three callees are inlined (B, C, and D),
+// the index for a node from the inlined body of D will refer to the
+// call to D from C. Whew.
+func posInlIndex(xpos src.XPos) int {
+ pos := base.Ctxt.PosTable.Pos(xpos)
+ if b := pos.Base(); b != nil {
+ ii := b.InliningIndex()
+ if ii >= 0 {
+ return ii
+ }
+ }
+ return -1
+}
+
+func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) {
+ if start == -1 {
+ panic("bad range start")
+ }
+ if end == -1 {
+ panic("bad range end")
+ }
+ if ii == -1 {
+ return
+ }
+ if start == end {
+ return
+ }
+ // Append range to correct inlined call
+ callIdx, found := imap[ii]
+ if !found {
+ base.Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start)
+ }
+ call := &calls[callIdx]
+ call.Ranges = append(call.Ranges, dwarf.Range{Start: start, End: end})
+}
+
+func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) {
+ for i := 0; i < ilevel; i++ {
+ base.Ctxt.Logf(" ")
+ }
+ ic := inlcalls.Calls[idx]
+ callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex)
+ base.Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name)
+ for _, f := range ic.InlVars {
+ base.Ctxt.Logf(" %v", f.Name)
+ }
+ base.Ctxt.Logf(" ) C: (")
+ for _, k := range ic.Children {
+ base.Ctxt.Logf(" %v", k)
+ }
+ base.Ctxt.Logf(" ) R:")
+ for _, r := range ic.Ranges {
+ base.Ctxt.Logf(" [%d,%d)", r.Start, r.End)
+ }
+ base.Ctxt.Logf("\n")
+ for _, k := range ic.Children {
+ dumpInlCall(inlcalls, k, ilevel+1)
+ }
+
+}
+
+func dumpInlCalls(inlcalls dwarf.InlCalls) {
+ for k, c := range inlcalls.Calls {
+ if c.Root {
+ dumpInlCall(inlcalls, k, 0)
+ }
+ }
+}
+
+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 {
+ typ = "param"
+ }
+ ia := 0
+ if dwv.IsInAbstract {
+ ia = 1
+ }
+ base.Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ)
+ }
+}
+
+func rangesContains(par []dwarf.Range, rng dwarf.Range) (bool, string) {
+ for _, r := range par {
+ if rng.Start >= r.Start && rng.End <= r.End {
+ return true, ""
+ }
+ }
+ msg := fmt.Sprintf("range [%d,%d) not contained in {", rng.Start, rng.End)
+ for _, r := range par {
+ msg += fmt.Sprintf(" [%d,%d)", r.Start, r.End)
+ }
+ msg += " }"
+ return false, msg
+}
+
+func rangesContainsAll(parent, child []dwarf.Range) (bool, string) {
+ for _, r := range child {
+ c, m := rangesContains(parent, r)
+ if !c {
+ return false, m
+ }
+ }
+ return true, ""
+}
+
+// checkInlCall verifies that the PC ranges for inline info 'idx' are
+// enclosed/contained within the ranges of its parent inline (or if
+// this is a root/toplevel inline, checks that the ranges fall within
+// the extent of the top level function). A panic is issued if a
+// malformed range is found.
+func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, parentIdx int) {
+
+ // Callee
+ ic := inlCalls.Calls[idx]
+ callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name
+ calleeRanges := ic.Ranges
+
+ // Caller
+ caller := funcName
+ parentRanges := []dwarf.Range{dwarf.Range{Start: int64(0), End: funcSize}}
+ if parentIdx != -1 {
+ pic := inlCalls.Calls[parentIdx]
+ caller = base.Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name
+ parentRanges = pic.Ranges
+ }
+
+ // Callee ranges contained in caller ranges?
+ c, m := rangesContainsAll(parentRanges, calleeRanges)
+ if !c {
+ base.Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m)
+ }
+
+ // Now visit kids
+ for _, k := range ic.Children {
+ checkInlCall(funcName, inlCalls, funcSize, k, idx)
+ }
+}
+
+// unifyCallRanges ensures that the ranges for a given inline
+// transitively include all of the ranges for its child inlines.
+func unifyCallRanges(inlcalls dwarf.InlCalls, idx int) {
+ ic := &inlcalls.Calls[idx]
+ for _, childIdx := range ic.Children {
+ // First make sure child ranges are unified.
+ unifyCallRanges(inlcalls, childIdx)
+
+ // Then merge child ranges into ranges for this inline.
+ cic := inlcalls.Calls[childIdx]
+ ic.Ranges = dwarf.MergeRanges(ic.Ranges, cic.Ranges)
+ }
+}
diff --git a/src/cmd/compile/internal/dwarfgen/marker.go b/src/cmd/compile/internal/dwarfgen/marker.go
new file mode 100644
index 0000000..ec6ce45
--- /dev/null
+++ b/src/cmd/compile/internal/dwarfgen/marker.go
@@ -0,0 +1,94 @@
+// Copyright 2021 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 dwarfgen
+
+import (
+ "cmd/compile/internal/base"
+ "cmd/compile/internal/ir"
+ "cmd/internal/src"
+)
+
+// A ScopeMarker tracks scope nesting and boundaries for later use
+// during DWARF generation.
+type ScopeMarker struct {
+ parents []ir.ScopeID
+ marks []ir.Mark
+}
+
+// checkPos validates the given position and returns the current scope.
+func (m *ScopeMarker) checkPos(pos src.XPos) ir.ScopeID {
+ if !pos.IsKnown() {
+ base.Fatalf("unknown scope position")
+ }
+
+ if len(m.marks) == 0 {
+ return 0
+ }
+
+ last := &m.marks[len(m.marks)-1]
+ if xposBefore(pos, last.Pos) {
+ base.FatalfAt(pos, "non-monotonic scope positions\n\t%v: previous scope position", base.FmtPos(last.Pos))
+ }
+ return last.Scope
+}
+
+// Push records a transition to a new child scope of the current scope.
+func (m *ScopeMarker) Push(pos src.XPos) {
+ current := m.checkPos(pos)
+
+ m.parents = append(m.parents, current)
+ child := ir.ScopeID(len(m.parents))
+
+ m.marks = append(m.marks, ir.Mark{Pos: pos, Scope: child})
+}
+
+// Pop records a transition back to the current scope's parent.
+func (m *ScopeMarker) Pop(pos src.XPos) {
+ current := m.checkPos(pos)
+
+ parent := m.parents[current-1]
+
+ m.marks = append(m.marks, ir.Mark{Pos: pos, Scope: parent})
+}
+
+// Unpush removes the current scope, which must be empty.
+func (m *ScopeMarker) Unpush() {
+ i := len(m.marks) - 1
+ current := m.marks[i].Scope
+
+ if current != ir.ScopeID(len(m.parents)) {
+ base.FatalfAt(m.marks[i].Pos, "current scope is not empty")
+ }
+
+ m.parents = m.parents[:current-1]
+ m.marks = m.marks[:i]
+}
+
+// WriteTo writes the recorded scope marks to the given function,
+// and resets the marker for reuse.
+func (m *ScopeMarker) WriteTo(fn *ir.Func) {
+ m.compactMarks()
+
+ fn.Parents = make([]ir.ScopeID, len(m.parents))
+ copy(fn.Parents, m.parents)
+ m.parents = m.parents[:0]
+
+ fn.Marks = make([]ir.Mark, len(m.marks))
+ copy(fn.Marks, m.marks)
+ m.marks = m.marks[:0]
+}
+
+func (m *ScopeMarker) compactMarks() {
+ n := 0
+ for _, next := range m.marks {
+ if n > 0 && next.Pos == m.marks[n-1].Pos {
+ m.marks[n-1].Scope = next.Scope
+ continue
+ }
+ m.marks[n] = next
+ n++
+ }
+ m.marks = m.marks[:n]
+}
diff --git a/src/cmd/compile/internal/dwarfgen/scope.go b/src/cmd/compile/internal/dwarfgen/scope.go
new file mode 100644
index 0000000..b4ae69e
--- /dev/null
+++ b/src/cmd/compile/internal/dwarfgen/scope.go
@@ -0,0 +1,136 @@
+// Copyright 2017 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 dwarfgen
+
+import (
+ "sort"
+
+ "cmd/compile/internal/base"
+ "cmd/compile/internal/ir"
+ "cmd/internal/dwarf"
+ "cmd/internal/obj"
+ "cmd/internal/src"
+)
+
+// See golang.org/issue/20390.
+func xposBefore(p, q src.XPos) bool {
+ return base.Ctxt.PosTable.Pos(p).Before(base.Ctxt.PosTable.Pos(q))
+}
+
+func findScope(marks []ir.Mark, pos src.XPos) ir.ScopeID {
+ i := sort.Search(len(marks), func(i int) bool {
+ return xposBefore(pos, marks[i].Pos)
+ })
+ if i == 0 {
+ return 0
+ }
+ return marks[i-1].Scope
+}
+
+func assembleScopes(fnsym *obj.LSym, fn *ir.Func, dwarfVars []*dwarf.Var, varScopes []ir.ScopeID) []dwarf.Scope {
+ // Initialize the DWARF scope tree based on lexical scopes.
+ dwarfScopes := make([]dwarf.Scope, 1+len(fn.Parents))
+ for i, parent := range fn.Parents {
+ dwarfScopes[i+1].Parent = int32(parent)
+ }
+
+ scopeVariables(dwarfVars, varScopes, dwarfScopes, fnsym.ABI() != obj.ABI0)
+ if fnsym.Func().Text != nil {
+ scopePCs(fnsym, fn.Marks, dwarfScopes)
+ }
+ return compactScopes(dwarfScopes)
+}
+
+// scopeVariables assigns DWARF variable records to their scopes.
+func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ir.ScopeID, dwarfScopes []dwarf.Scope, regabi bool) {
+ if regabi {
+ sort.Stable(varsByScope{dwarfVars, varScopes})
+ } else {
+ sort.Stable(varsByScopeAndOffset{dwarfVars, varScopes})
+ }
+
+ i0 := 0
+ for i := range dwarfVars {
+ if varScopes[i] == varScopes[i0] {
+ continue
+ }
+ dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:i]
+ i0 = i
+ }
+ if i0 < len(dwarfVars) {
+ dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:]
+ }
+}
+
+// scopePCs assigns PC ranges to their scopes.
+func scopePCs(fnsym *obj.LSym, marks []ir.Mark, dwarfScopes []dwarf.Scope) {
+ // If there aren't any child scopes (in particular, when scope
+ // tracking is disabled), we can skip a whole lot of work.
+ if len(marks) == 0 {
+ return
+ }
+ p0 := fnsym.Func().Text
+ scope := findScope(marks, p0.Pos)
+ for p := p0; p != nil; p = p.Link {
+ if p.Pos == p0.Pos {
+ continue
+ }
+ dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: p.Pc})
+ p0 = p
+ scope = findScope(marks, p0.Pos)
+ }
+ if p0.Pc < fnsym.Size {
+ dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: fnsym.Size})
+ }
+}
+
+func compactScopes(dwarfScopes []dwarf.Scope) []dwarf.Scope {
+ // Reverse pass to propagate PC ranges to parent scopes.
+ for i := len(dwarfScopes) - 1; i > 0; i-- {
+ s := &dwarfScopes[i]
+ dwarfScopes[s.Parent].UnifyRanges(s)
+ }
+
+ return dwarfScopes
+}
+
+type varsByScopeAndOffset struct {
+ vars []*dwarf.Var
+ scopes []ir.ScopeID
+}
+
+func (v varsByScopeAndOffset) Len() int {
+ return len(v.vars)
+}
+
+func (v varsByScopeAndOffset) Less(i, j int) bool {
+ if v.scopes[i] != v.scopes[j] {
+ return v.scopes[i] < v.scopes[j]
+ }
+ return v.vars[i].StackOffset < v.vars[j].StackOffset
+}
+
+func (v varsByScopeAndOffset) Swap(i, j int) {
+ v.vars[i], v.vars[j] = v.vars[j], v.vars[i]
+ v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i]
+}
+
+type varsByScope struct {
+ vars []*dwarf.Var
+ scopes []ir.ScopeID
+}
+
+func (v varsByScope) Len() int {
+ return len(v.vars)
+}
+
+func (v varsByScope) Less(i, j int) bool {
+ return v.scopes[i] < v.scopes[j]
+}
+
+func (v varsByScope) Swap(i, j int) {
+ v.vars[i], v.vars[j] = v.vars[j], v.vars[i]
+ v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i]
+}
diff --git a/src/cmd/compile/internal/dwarfgen/scope_test.go b/src/cmd/compile/internal/dwarfgen/scope_test.go
new file mode 100644
index 0000000..3df4c34
--- /dev/null
+++ b/src/cmd/compile/internal/dwarfgen/scope_test.go
@@ -0,0 +1,539 @@
+// Copyright 2016 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 dwarfgen
+
+import (
+ "debug/dwarf"
+ "fmt"
+ "internal/testenv"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "testing"
+
+ "cmd/internal/objfile"
+)
+
+type testline struct {
+ // line is one line of go source
+ line string
+
+ // scopes is a list of scope IDs of all the lexical scopes that this line
+ // of code belongs to.
+ // Scope IDs are assigned by traversing the tree of lexical blocks of a
+ // function in pre-order
+ // Scope IDs are function specific, i.e. scope 0 is always the root scope
+ // of the function that this line belongs to. Empty scopes are not assigned
+ // an ID (because they are not saved in debug_info).
+ // Scope 0 is always omitted from this list since all lines always belong
+ // to it.
+ scopes []int
+
+ // vars is the list of variables that belong in scopes[len(scopes)-1].
+ // Local variables are prefixed with "var ", formal parameters with "arg ".
+ // Must be ordered alphabetically.
+ // Set to nil to skip the check.
+ vars []string
+
+ // decl is the list of variables declared at this line.
+ decl []string
+
+ // declBefore is the list of variables declared at or before this line.
+ declBefore []string
+}
+
+var testfile = []testline{
+ {line: "package main"},
+ {line: "func f1(x int) { }"},
+ {line: "func f2(x int) { }"},
+ {line: "func f3(x int) { }"},
+ {line: "func f4(x int) { }"},
+ {line: "func f5(x int) { }"},
+ {line: "func f6(x int) { }"},
+ {line: "func fi(x interface{}) { if a, ok := x.(error); ok { a.Error() } }"},
+ {line: "func gret1() int { return 2 }"},
+ {line: "func gretbool() bool { return true }"},
+ {line: "func gret3() (int, int, int) { return 0, 1, 2 }"},
+ {line: "var v = []int{ 0, 1, 2 }"},
+ {line: "var ch = make(chan int)"},
+ {line: "var floatch = make(chan float64)"},
+ {line: "var iface interface{}"},
+ {line: "func TestNestedFor() {", vars: []string{"var a int"}},
+ {line: " a := 0", decl: []string{"a"}},
+ {line: " f1(a)"},
+ {line: " for i := 0; i < 5; i++ {", scopes: []int{1}, vars: []string{"var i int"}, decl: []string{"i"}},
+ {line: " f2(i)", scopes: []int{1}},
+ {line: " for i := 0; i < 5; i++ {", scopes: []int{1, 2}, vars: []string{"var i int"}, decl: []string{"i"}},
+ {line: " f3(i)", scopes: []int{1, 2}},
+ {line: " }"},
+ {line: " f4(i)", scopes: []int{1}},
+ {line: " }"},
+ {line: " f5(a)"},
+ {line: "}"},
+ {line: "func TestOas2() {", vars: []string{}},
+ {line: " if a, b, c := gret3(); a != 1 {", scopes: []int{1}, vars: []string{"var a int", "var b int", "var c int"}},
+ {line: " f1(a)", scopes: []int{1}},
+ {line: " f1(b)", scopes: []int{1}},
+ {line: " f1(c)", scopes: []int{1}},
+ {line: " }"},
+ {line: " for i, x := range v {", scopes: []int{2}, vars: []string{"var i int", "var x int"}},
+ {line: " f1(i)", scopes: []int{2}},
+ {line: " f1(x)", scopes: []int{2}},
+ {line: " }"},
+ {line: " if a, ok := <- ch; ok {", scopes: []int{3}, vars: []string{"var a int", "var ok bool"}},
+ {line: " f1(a)", scopes: []int{3}},
+ {line: " }"},
+ {line: " if a, ok := iface.(int); ok {", scopes: []int{4}, vars: []string{"var a int", "var ok bool"}},
+ {line: " f1(a)", scopes: []int{4}},
+ {line: " }"},
+ {line: "}"},
+ {line: "func TestIfElse() {"},
+ {line: " if x := gret1(); x != 0 {", scopes: []int{1}, vars: []string{"var x int"}},
+ {line: " a := 0", scopes: []int{1, 2}, vars: []string{"var a int"}},
+ {line: " f1(a); f1(x)", scopes: []int{1, 2}},
+ {line: " } else {"},
+ {line: " b := 1", scopes: []int{1, 3}, vars: []string{"var b int"}},
+ {line: " f1(b); f1(x+1)", scopes: []int{1, 3}},
+ {line: " }"},
+ {line: "}"},
+ {line: "func TestSwitch() {", vars: []string{}},
+ {line: " switch x := gret1(); x {", scopes: []int{1}, vars: []string{"var x int"}},
+ {line: " case 0:", scopes: []int{1, 2}},
+ {line: " i := x + 5", scopes: []int{1, 2}, vars: []string{"var i int"}},
+ {line: " f1(x); f1(i)", scopes: []int{1, 2}},
+ {line: " case 1:", scopes: []int{1, 3}},
+ {line: " j := x + 10", scopes: []int{1, 3}, vars: []string{"var j int"}},
+ {line: " f1(x); f1(j)", scopes: []int{1, 3}},
+ {line: " case 2:", scopes: []int{1, 4}},
+ {line: " k := x + 2", scopes: []int{1, 4}, vars: []string{"var k int"}},
+ {line: " f1(x); f1(k)", scopes: []int{1, 4}},
+ {line: " }"},
+ {line: "}"},
+ {line: "func TestTypeSwitch() {", vars: []string{}},
+ {line: " switch x := iface.(type) {"},
+ {line: " case int:", scopes: []int{1}},
+ {line: " f1(x)", scopes: []int{1}, vars: []string{"var x int"}},
+ {line: " case uint8:", scopes: []int{2}},
+ {line: " f1(int(x))", scopes: []int{2}, vars: []string{"var x uint8"}},
+ {line: " case float64:", scopes: []int{3}},
+ {line: " f1(int(x)+1)", scopes: []int{3}, vars: []string{"var x float64"}},
+ {line: " }"},
+ {line: "}"},
+ {line: "func TestSelectScope() {"},
+ {line: " select {"},
+ {line: " case i := <- ch:", scopes: []int{1}},
+ {line: " f1(i)", scopes: []int{1}, vars: []string{"var i int"}},
+ {line: " case f := <- floatch:", scopes: []int{2}},
+ {line: " f1(int(f))", scopes: []int{2}, vars: []string{"var f float64"}},
+ {line: " }"},
+ {line: "}"},
+ {line: "func TestBlock() {", vars: []string{"var a int"}},
+ {line: " a := 1"},
+ {line: " {"},
+ {line: " b := 2", scopes: []int{1}, vars: []string{"var b int"}},
+ {line: " f1(b)", scopes: []int{1}},
+ {line: " f1(a)", scopes: []int{1}},
+ {line: " }"},
+ {line: "}"},
+ {line: "func TestDiscontiguousRanges() {", vars: []string{"var a int"}},
+ {line: " a := 0"},
+ {line: " f1(a)"},
+ {line: " {"},
+ {line: " b := 0", scopes: []int{1}, vars: []string{"var b int"}},
+ {line: " f2(b)", scopes: []int{1}},
+ {line: " if gretbool() {", scopes: []int{1}},
+ {line: " c := 0", scopes: []int{1, 2}, vars: []string{"var c int"}},
+ {line: " f3(c)", scopes: []int{1, 2}},
+ {line: " } else {"},
+ {line: " c := 1.1", scopes: []int{1, 3}, vars: []string{"var c float64"}},
+ {line: " f4(int(c))", scopes: []int{1, 3}},
+ {line: " }"},
+ {line: " f5(b)", scopes: []int{1}},
+ {line: " }"},
+ {line: " f6(a)"},
+ {line: "}"},
+ {line: "func TestClosureScope() {", vars: []string{"var a int", "var b int", "var f func(int)"}},
+ {line: " a := 1; b := 1"},
+ {line: " f := func(c int) {", scopes: []int{0}, vars: []string{"arg c int", "var &b *int", "var a int", "var d int"}, declBefore: []string{"&b", "a"}},
+ {line: " d := 3"},
+ {line: " f1(c); f1(d)"},
+ {line: " if e := 3; e != 0 {", scopes: []int{1}, vars: []string{"var e int"}},
+ {line: " f1(e)", scopes: []int{1}},
+ {line: " f1(a)", scopes: []int{1}},
+ {line: " b = 2", scopes: []int{1}},
+ {line: " }"},
+ {line: " }"},
+ {line: " f(3); f1(b)"},
+ {line: "}"},
+ {line: "func TestEscape() {"},
+ {line: " a := 1", vars: []string{"var a int"}},
+ {line: " {"},
+ {line: " b := 2", scopes: []int{1}, vars: []string{"var &b *int", "var p *int"}},
+ {line: " p := &b", scopes: []int{1}},
+ {line: " f1(a)", scopes: []int{1}},
+ {line: " fi(p)", scopes: []int{1}},
+ {line: " }"},
+ {line: "}"},
+ {line: "var fglob func() int"},
+ {line: "func TestCaptureVar(flag bool) {"},
+ {line: " a := 1", vars: []string{"arg flag bool", "var a int"}}, // TODO(register args) restore "arg ~r1 func() int",
+ {line: " if flag {"},
+ {line: " b := 2", scopes: []int{1}, vars: []string{"var b int", "var f func() int"}},
+ {line: " f := func() int {", scopes: []int{1, 0}},
+ {line: " return b + 1"},
+ {line: " }"},
+ {line: " fglob = f", scopes: []int{1}},
+ {line: " }"},
+ {line: " f1(a)"},
+ {line: "}"},
+ {line: "func main() {"},
+ {line: " TestNestedFor()"},
+ {line: " TestOas2()"},
+ {line: " TestIfElse()"},
+ {line: " TestSwitch()"},
+ {line: " TestTypeSwitch()"},
+ {line: " TestSelectScope()"},
+ {line: " TestBlock()"},
+ {line: " TestDiscontiguousRanges()"},
+ {line: " TestClosureScope()"},
+ {line: " TestEscape()"},
+ {line: " TestCaptureVar(true)"},
+ {line: "}"},
+}
+
+const detailOutput = false
+
+// Compiles testfile checks that the description of lexical blocks emitted
+// by the linker in debug_info, for each function in the main package,
+// corresponds to what we expect it to be.
+func TestScopeRanges(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ t.Parallel()
+
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping on plan9; no DWARF symbol table in executables")
+ }
+
+ dir, err := ioutil.TempDir("", "TestScopeRanges")
+ if err != nil {
+ t.Fatalf("could not create directory: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ src, f := gobuild(t, dir, false, testfile)
+ defer f.Close()
+
+ // the compiler uses forward slashes for paths even on windows
+ src = strings.Replace(src, "\\", "/", -1)
+
+ pcln, err := f.PCLineTable()
+ if err != nil {
+ t.Fatal(err)
+ }
+ dwarfData, err := f.DWARF()
+ if err != nil {
+ t.Fatal(err)
+ }
+ dwarfReader := dwarfData.Reader()
+
+ lines := make(map[line][]*lexblock)
+
+ for {
+ entry, err := dwarfReader.Next()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if entry == nil {
+ break
+ }
+
+ if entry.Tag != dwarf.TagSubprogram {
+ continue
+ }
+
+ name, ok := entry.Val(dwarf.AttrName).(string)
+ if !ok || !strings.HasPrefix(name, "main.Test") {
+ continue
+ }
+
+ var scope lexblock
+ ctxt := scopexplainContext{
+ dwarfData: dwarfData,
+ dwarfReader: dwarfReader,
+ scopegen: 1,
+ }
+
+ readScope(&ctxt, &scope, entry)
+
+ scope.markLines(pcln, lines)
+ }
+
+ anyerror := false
+ for i := range testfile {
+ tgt := testfile[i].scopes
+ out := lines[line{src, i + 1}]
+
+ if detailOutput {
+ t.Logf("%s // %v", testfile[i].line, out)
+ }
+
+ scopesok := checkScopes(tgt, out)
+ if !scopesok {
+ t.Logf("mismatch at line %d %q: expected: %v got: %v\n", i, testfile[i].line, tgt, scopesToString(out))
+ }
+
+ varsok := true
+ if testfile[i].vars != nil {
+ if len(out) > 0 {
+ varsok = checkVars(testfile[i].vars, out[len(out)-1].vars)
+ if !varsok {
+ t.Logf("variable mismatch at line %d %q for scope %d: expected: %v got: %v\n", i+1, testfile[i].line, out[len(out)-1].id, testfile[i].vars, out[len(out)-1].vars)
+ }
+ for j := range testfile[i].decl {
+ if line := declLineForVar(out[len(out)-1].vars, testfile[i].decl[j]); line != i+1 {
+ t.Errorf("wrong declaration line for variable %s, expected %d got: %d", testfile[i].decl[j], i+1, line)
+ }
+ }
+
+ for j := range testfile[i].declBefore {
+ if line := declLineForVar(out[len(out)-1].vars, testfile[i].declBefore[j]); line > i+1 {
+ t.Errorf("wrong declaration line for variable %s, expected %d (or less) got: %d", testfile[i].declBefore[j], i+1, line)
+ }
+ }
+ }
+ }
+
+ anyerror = anyerror || !scopesok || !varsok
+ }
+
+ if anyerror {
+ t.Fatalf("mismatched output")
+ }
+}
+
+func scopesToString(v []*lexblock) string {
+ r := make([]string, len(v))
+ for i, s := range v {
+ r[i] = strconv.Itoa(s.id)
+ }
+ return "[ " + strings.Join(r, ", ") + " ]"
+}
+
+func checkScopes(tgt []int, out []*lexblock) bool {
+ if len(out) > 0 {
+ // omit scope 0
+ out = out[1:]
+ }
+ if len(tgt) != len(out) {
+ return false
+ }
+ for i := range tgt {
+ if tgt[i] != out[i].id {
+ return false
+ }
+ }
+ return true
+}
+
+func checkVars(tgt []string, out []variable) bool {
+ if len(tgt) != len(out) {
+ return false
+ }
+ for i := range tgt {
+ if tgt[i] != out[i].expr {
+ return false
+ }
+ }
+ return true
+}
+
+func declLineForVar(scope []variable, name string) int {
+ for i := range scope {
+ if scope[i].name() == name {
+ return scope[i].declLine
+ }
+ }
+ return -1
+}
+
+type lexblock struct {
+ id int
+ ranges [][2]uint64
+ vars []variable
+ scopes []lexblock
+}
+
+type variable struct {
+ expr string
+ declLine int
+}
+
+func (v *variable) name() string {
+ return strings.Split(v.expr, " ")[1]
+}
+
+type line struct {
+ file string
+ lineno int
+}
+
+type scopexplainContext struct {
+ dwarfData *dwarf.Data
+ dwarfReader *dwarf.Reader
+ scopegen int
+}
+
+// readScope reads the DW_TAG_lexical_block or the DW_TAG_subprogram in
+// entry and writes a description in scope.
+// Nested DW_TAG_lexical_block entries are read recursively.
+func readScope(ctxt *scopexplainContext, scope *lexblock, entry *dwarf.Entry) {
+ var err error
+ scope.ranges, err = ctxt.dwarfData.Ranges(entry)
+ if err != nil {
+ panic(err)
+ }
+ for {
+ e, err := ctxt.dwarfReader.Next()
+ if err != nil {
+ panic(err)
+ }
+ switch e.Tag {
+ case 0:
+ sort.Slice(scope.vars, func(i, j int) bool {
+ return scope.vars[i].expr < scope.vars[j].expr
+ })
+ return
+ case dwarf.TagFormalParameter:
+ typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset))
+ if err != nil {
+ panic(err)
+ }
+ scope.vars = append(scope.vars, entryToVar(e, "arg", typ))
+ case dwarf.TagVariable:
+ typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset))
+ if err != nil {
+ panic(err)
+ }
+ scope.vars = append(scope.vars, entryToVar(e, "var", typ))
+ case dwarf.TagLexDwarfBlock:
+ scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen})
+ ctxt.scopegen++
+ readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e)
+ }
+ }
+}
+
+func entryToVar(e *dwarf.Entry, kind string, typ dwarf.Type) variable {
+ return variable{
+ fmt.Sprintf("%s %s %s", kind, e.Val(dwarf.AttrName).(string), typ.String()),
+ int(e.Val(dwarf.AttrDeclLine).(int64)),
+ }
+}
+
+// markLines marks all lines that belong to this scope with this scope
+// Recursively calls markLines for all children scopes.
+func (scope *lexblock) markLines(pcln objfile.Liner, lines map[line][]*lexblock) {
+ for _, r := range scope.ranges {
+ for pc := r[0]; pc < r[1]; pc++ {
+ file, lineno, _ := pcln.PCToLine(pc)
+ l := line{file, lineno}
+ if len(lines[l]) == 0 || lines[l][len(lines[l])-1] != scope {
+ lines[l] = append(lines[l], scope)
+ }
+ }
+ }
+
+ for i := range scope.scopes {
+ scope.scopes[i].markLines(pcln, lines)
+ }
+}
+
+func gobuild(t *testing.T, dir string, optimized bool, testfile []testline) (string, *objfile.File) {
+ src := filepath.Join(dir, "test.go")
+ dst := filepath.Join(dir, "out.o")
+
+ f, err := os.Create(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := range testfile {
+ f.Write([]byte(testfile[i].line))
+ f.Write([]byte{'\n'})
+ }
+ f.Close()
+
+ args := []string{"build"}
+ if !optimized {
+ args = append(args, "-gcflags=-N -l")
+ }
+ args = append(args, "-o", dst, src)
+
+ cmd := exec.Command(testenv.GoToolPath(t), args...)
+ if b, err := cmd.CombinedOutput(); err != nil {
+ t.Logf("build: %s\n", string(b))
+ t.Fatal(err)
+ }
+
+ pkg, err := objfile.Open(dst)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return src, pkg
+}
+
+// TestEmptyDwarfRanges tests that no list entry in debug_ranges has start == end.
+// See issue #23928.
+func TestEmptyDwarfRanges(t *testing.T) {
+ testenv.MustHaveGoRun(t)
+ t.Parallel()
+
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping on plan9; no DWARF symbol table in executables")
+ }
+
+ dir, err := ioutil.TempDir("", "TestEmptyDwarfRanges")
+ if err != nil {
+ t.Fatalf("could not create directory: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ _, f := gobuild(t, dir, true, []testline{{line: "package main"}, {line: "func main(){ println(\"hello\") }"}})
+ defer f.Close()
+
+ dwarfData, err := f.DWARF()
+ if err != nil {
+ t.Fatal(err)
+ }
+ dwarfReader := dwarfData.Reader()
+
+ for {
+ entry, err := dwarfReader.Next()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if entry == nil {
+ break
+ }
+
+ ranges, err := dwarfData.Ranges(entry)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ranges == nil {
+ continue
+ }
+
+ for _, rng := range ranges {
+ if rng[0] == rng[1] {
+ t.Errorf("range entry with start == end: %v", rng)
+ }
+ }
+ }
+}