diff --git a/src/cmd/compile/fmt_test.go b/src/cmd/compile/fmt_test.go index 59de326a91..2052a4200e 100644 --- a/src/cmd/compile/fmt_test.go +++ b/src/cmd/compile/fmt_test.go @@ -571,9 +571,16 @@ var knownFormats = map[string]string{ "*cmd/compile/internal/ssa.Block %s": "", "*cmd/compile/internal/ssa.Block %v": "", "*cmd/compile/internal/ssa.Func %s": "", + "*cmd/compile/internal/ssa.Func %v": "", + "*cmd/compile/internal/ssa.FuncDebug %v": "", + "*cmd/compile/internal/ssa.LocalSlot %+v": "", + "*cmd/compile/internal/ssa.LocalSlot %v": "", + "*cmd/compile/internal/ssa.Register %v": "", "*cmd/compile/internal/ssa.SparseTreeNode %v": "", "*cmd/compile/internal/ssa.Value %s": "", "*cmd/compile/internal/ssa.Value %v": "", + "*cmd/compile/internal/ssa.VarLoc %+v": "", + "*cmd/compile/internal/ssa.VarLoc %v": "", "*cmd/compile/internal/ssa.sparseTreeMapEntry %v": "", "*cmd/compile/internal/types.Field %p": "", "*cmd/compile/internal/types.Field %v": "", @@ -592,6 +599,7 @@ var knownFormats = map[string]string{ "*cmd/compile/internal/types.Type %p": "", "*cmd/compile/internal/types.Type %s": "", "*cmd/compile/internal/types.Type %v": "", + "*cmd/internal/dwarf.Location %#v": "", "*cmd/internal/obj.Addr %v": "", "*cmd/internal/obj.LSym %v": "", "*cmd/internal/obj.Prog %s": "", @@ -600,17 +608,21 @@ var knownFormats = map[string]string{ "[16]byte %x": "", "[]*cmd/compile/internal/gc.Node %v": "", "[]*cmd/compile/internal/gc.Sig %#v": "", + "[]*cmd/compile/internal/ssa.Block %+v": "", "[]*cmd/compile/internal/ssa.Value %v": "", + "[][]cmd/compile/internal/ssa.SlotID %v": "", "[]byte %s": "", "[]byte %x": "", "[]cmd/compile/internal/ssa.Edge %v": "", "[]cmd/compile/internal/ssa.ID %v": "", + "[]cmd/compile/internal/ssa.VarLocList %v": "", "[]string %v": "", "bool %v": "", "byte %08b": "", "byte %c": "", "cmd/compile/internal/arm.shift %d": "", "cmd/compile/internal/gc.Class %d": "", + "cmd/compile/internal/gc.Class %v": "", "cmd/compile/internal/gc.Ctype %d": "", "cmd/compile/internal/gc.Ctype %v": "", "cmd/compile/internal/gc.Level %d": "", @@ -630,11 +642,13 @@ var knownFormats = map[string]string{ "cmd/compile/internal/ssa.Edge %v": "", "cmd/compile/internal/ssa.GCNode %v": "", "cmd/compile/internal/ssa.ID %d": "", + "cmd/compile/internal/ssa.ID %v": "", "cmd/compile/internal/ssa.LocalSlot %v": "", "cmd/compile/internal/ssa.Location %v": "", "cmd/compile/internal/ssa.Op %s": "", "cmd/compile/internal/ssa.Op %v": "", "cmd/compile/internal/ssa.ValAndOff %s": "", + "cmd/compile/internal/ssa.VarLocList %v": "", "cmd/compile/internal/ssa.rbrank %d": "", "cmd/compile/internal/ssa.regMask %d": "", "cmd/compile/internal/ssa.register %d": "", @@ -648,6 +662,7 @@ var knownFormats = map[string]string{ "cmd/compile/internal/types.EType %d": "", "cmd/compile/internal/types.EType %s": "", "cmd/compile/internal/types.EType %v": "", + "cmd/internal/dwarf.Location %#v": "", "cmd/internal/src.Pos %s": "", "cmd/internal/src.Pos %v": "", "error %v": "", diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 2b61564ad8..a1f4767c8f 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -44,6 +44,7 @@ var ( Debug_vlog bool Debug_wb int Debug_pctab string + Debug_locationlist int ) // Debug arguments. @@ -69,6 +70,7 @@ var debugtab = []struct { {"wb", "print information about write barriers", &Debug_wb}, {"export", "print export data", &Debug_export}, {"pctab", "print named pc-value table", &Debug_pctab}, + {"locationlists", "print information about DWARF location list creation", &Debug_locationlist}, } const debugHelpHeader = `usage: -d arg[,arg]* and arg is [=] @@ -192,6 +194,7 @@ func Main(archInit func(*Arch)) { flag.BoolVar(&pure_go, "complete", false, "compiling complete package (no C or assembly)") flag.StringVar(&debugstr, "d", "", "print debug information about items in `list`; try -d help") flag.BoolVar(&flagDWARF, "dwarf", true, "generate DWARF symbols") + flag.BoolVar(&Ctxt.Flag_locationlists, "dwarflocationlists", false, "add location lists to DWARF in optimized mode") objabi.Flagcount("e", "no limit on number of errors reported", &Debug['e']) objabi.Flagcount("f", "debug stack frames", &Debug['f']) objabi.Flagcount("h", "halt on error", &Debug['h']) @@ -298,6 +301,9 @@ func Main(archInit func(*Arch)) { if nBackendWorkers > 1 && !concurrentBackendAllowed() { log.Fatalf("cannot use concurrent backend compilation with provided flags; invoked as %v", os.Args) } + if Ctxt.Flag_locationlists && len(Ctxt.Arch.DWARFRegisters) == 0 { + log.Fatalf("location lists requested but register mapping not available on %v", Ctxt.Arch.Name) + } // parse -d argument if debugstr != "" { @@ -383,7 +389,7 @@ func Main(archInit func(*Arch)) { Debug['l'] = 1 - Debug['l'] } - trackScopes = flagDWARF && Debug['l'] == 0 && Debug['N'] != 0 + trackScopes = flagDWARF && ((Debug['l'] == 0 && Debug['N'] != 0) || Ctxt.Flag_locationlists) Widthptr = thearch.LinkArch.PtrSize Widthreg = thearch.LinkArch.RegSize diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index d301ae19c8..542fd43b63 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -13,6 +13,7 @@ import ( "cmd/internal/src" "cmd/internal/sys" "fmt" + "math" "math/rand" "sort" "sync" @@ -303,29 +304,77 @@ func compileFunctions() { func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope { fn := curfn.(*Node) + debugInfo := fn.Func.DebugInfo + fn.Func.DebugInfo = nil if expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect { Fatalf("unexpected fnsym: %v != %v", fnsym, expect) } - var dwarfVars []*dwarf.Var - var varScopes []ScopeID - + var automDecls []*Node + // Populate Automs for fn. for _, n := range fn.Func.Dcl { if n.Op != ONAME { // might be OTYPE or OLITERAL continue } - var name obj.AddrName - var abbrev int - offs := n.Xoffset - switch n.Class() { case PAUTO: if !n.Name.Used() { Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)") } name = obj.NAME_AUTO + case PPARAM, PPARAMOUT: + name = obj.NAME_PARAM + default: + continue + } + automDecls = append(automDecls, n) + gotype := ngotype(n).Linksym() + fnsym.Func.Autom = append(fnsym.Func.Autom, &obj.Auto{ + Asym: Ctxt.Lookup(n.Sym.Name), + Aoffset: int32(n.Xoffset), + Name: name, + Gotype: gotype, + }) + } + var dwarfVars []*dwarf.Var + var decls []*Node + if Ctxt.Flag_locationlists && Ctxt.Flag_optimize { + decls, dwarfVars = createComplexVars(fn, debugInfo) + } else { + decls, dwarfVars = createSimpleVars(automDecls) + } + + var varScopes []ScopeID + for _, decl := range decls { + var scope ScopeID + if !decl.Name.Captured() && !decl.Name.Byval() { + // n.Pos of captured variables is their first + // use in the closure but they should always + // be assigned to scope 0 instead. + // TODO(mdempsky): Verify this. + scope = findScope(fn.Func.Marks, decl.Pos) + } + varScopes = append(varScopes, scope) + } + return assembleScopes(fnsym, fn, dwarfVars, varScopes) +} + +// createSimpleVars creates a DWARF entry for every variable declared in the +// function, claiming that they are permanently on the stack. +func createSimpleVars(automDecls []*Node) ([]*Node, []*dwarf.Var) { + var vars []*dwarf.Var + var decls []*Node + for _, n := range automDecls { + if n.IsAutoTmp() { + continue + } + var abbrev int + offs := n.Xoffset + + switch n.Class() { + case PAUTO: abbrev = dwarf.DW_ABRV_AUTO if Ctxt.FixedFrameSize() == 0 { offs -= int64(Widthptr) @@ -335,48 +384,288 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope { } case PPARAM, PPARAMOUT: - name = obj.NAME_PARAM - abbrev = dwarf.DW_ABRV_PARAM offs += Ctxt.FixedFrameSize() - default: - continue + Fatalf("createSimpleVars unexpected type %v for node %v", n.Class(), n) } - gotype := ngotype(n).Linksym() - fnsym.Func.Autom = append(fnsym.Func.Autom, &obj.Auto{ - Asym: Ctxt.Lookup(n.Sym.Name), - Aoffset: int32(n.Xoffset), - Name: name, - Gotype: gotype, - }) - - if n.IsAutoTmp() { - continue - } - - typename := dwarf.InfoPrefix + gotype.Name[len("type."):] - dwarfVars = append(dwarfVars, &dwarf.Var{ + typename := dwarf.InfoPrefix + typesymname(n.Type) + decls = append(decls, n) + vars = append(vars, &dwarf.Var{ Name: n.Sym.Name, Abbrev: abbrev, StackOffset: int32(offs), Type: Ctxt.Lookup(typename), }) + } + return decls, vars +} - var scope ScopeID - if !n.Name.Captured() && !n.Name.Byval() { - // n.Pos of captured variables is their first - // use in the closure but they should always - // be assigned to scope 0 instead. - // TODO(mdempsky): Verify this. - scope = findScope(fn.Func.Marks, n.Pos) +type varPart struct { + varOffset int64 + slot ssa.SlotID + locs ssa.VarLocList +} + +func createComplexVars(fn *Node, debugInfo *ssa.FuncDebug) ([]*Node, []*dwarf.Var) { + for _, locList := range debugInfo.Variables { + for _, loc := range locList.Locations { + if loc.StartProg != nil { + loc.StartPC = loc.StartProg.Pc + } + if loc.EndProg != nil { + loc.EndPC = loc.EndProg.Pc + } + if Debug_locationlist == 0 { + loc.EndProg = nil + loc.StartProg = nil + } } - - varScopes = append(varScopes, scope) } - return assembleScopes(fnsym, fn, dwarfVars, varScopes) + // Group SSA variables by the user variable they were decomposed from. + varParts := map[*Node][]varPart{} + for slotID, locList := range debugInfo.Variables { + if len(locList.Locations) == 0 { + continue + } + slot := debugInfo.Slots[slotID] + for slot.SplitOf != nil { + slot = slot.SplitOf + } + n := slot.N.(*Node) + varParts[n] = append(varParts[n], varPart{varOffset(slot), ssa.SlotID(slotID), locList}) + } + + // Produce a DWARF variable entry for each user variable. + // Don't iterate over the map -- that's nondeterministic, and + // createComplexVar has side effects. Instead, go by slot. + var decls []*Node + var vars []*dwarf.Var + for _, slot := range debugInfo.Slots { + for slot.SplitOf != nil { + slot = slot.SplitOf + } + n := slot.N.(*Node) + parts := varParts[n] + if parts == nil { + continue + } + + // Get the order the parts need to be in to represent the memory + // of the decomposed user variable. + sort.Sort(partsByVarOffset(parts)) + + if dvar := createComplexVar(debugInfo, n, parts); dvar != nil { + decls = append(decls, n) + vars = append(vars, dvar) + } + } + return decls, vars +} + +// varOffset returns the offset of slot within the user variable it was +// decomposed from. This has nothing to do with its stack offset. +func varOffset(slot *ssa.LocalSlot) int64 { + offset := slot.Off + for ; slot.SplitOf != nil; slot = slot.SplitOf { + offset += slot.SplitOffset + } + return offset +} + +type partsByVarOffset []varPart + +func (a partsByVarOffset) Len() int { return len(a) } +func (a partsByVarOffset) Less(i, j int) bool { return a[i].varOffset < a[j].varOffset } +func (a partsByVarOffset) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// createComplexVar builds a DWARF variable entry and location list representing n. +func createComplexVar(debugInfo *ssa.FuncDebug, n *Node, parts []varPart) *dwarf.Var { + slots := debugInfo.Slots + var offs int64 // base stack offset for this kind of variable + var abbrev int + switch n.Class() { + case PAUTO: + abbrev = dwarf.DW_ABRV_AUTO_LOCLIST + if Ctxt.FixedFrameSize() == 0 { + offs -= int64(Widthptr) + } + if objabi.Framepointer_enabled(objabi.GOOS, objabi.GOARCH) { + offs -= int64(Widthptr) + } + + case PPARAM, PPARAMOUT: + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + offs += Ctxt.FixedFrameSize() + default: + return nil + } + + gotype := ngotype(n).Linksym() + typename := dwarf.InfoPrefix + gotype.Name[len("type."):] + // The stack offset is used as a sorting key, so for decomposed + // variables just give it the lowest one. It's not used otherwise. + stackOffset := debugInfo.Slots[parts[0].slot].N.(*Node).Xoffset + offs + dvar := &dwarf.Var{ + Name: n.Sym.Name, + Abbrev: abbrev, + Type: Ctxt.Lookup(typename), + StackOffset: int32(stackOffset), + } + + if Debug_locationlist != 0 { + Ctxt.Logf("Building location list for %+v. Parts:\n", n) + for _, part := range parts { + Ctxt.Logf("\t%v => %v\n", debugInfo.Slots[part.slot], part.locs) + } + } + + // Given a variable that's been decomposed into multiple parts, + // its location list may need a new entry after the beginning or + // end of every location entry for each of its parts. For example: + // + // [variable] [pc range] + // string.ptr |----|-----| |----| + // string.len |------------| |--| + // ... needs a location list like: + // string |----|-----|-| |--|-| + // + // Note that location entries may or may not line up with each other, + // and some of the result will only have one or the other part. + // + // To build the resulting list: + // - keep a "current" pointer for each part + // - find the next transition point + // - advance the current pointer for each part up to that transition point + // - build the piece for the range between that transition point and the next + // - repeat + + curLoc := make([]int, len(slots)) + + // findBoundaryAfter finds the next beginning or end of a piece after currentPC. + findBoundaryAfter := func(currentPC int64) int64 { + min := int64(math.MaxInt64) + for slot, part := range parts { + // For each part, find the first PC greater than current. Doesn't + // matter if it's a start or an end, since we're looking for any boundary. + // If it's the new winner, save it. + onePart: + for i := curLoc[slot]; i < len(part.locs.Locations); i++ { + for _, pc := range [2]int64{part.locs.Locations[i].StartPC, part.locs.Locations[i].EndPC} { + if pc > currentPC { + if pc < min { + min = pc + } + break onePart + } + } + } + } + return min + } + var start int64 + end := findBoundaryAfter(0) + for { + // Advance to the next chunk. + start = end + end = findBoundaryAfter(start) + if end == math.MaxInt64 { + break + } + + dloc := dwarf.Location{StartPC: start, EndPC: end} + if Debug_locationlist != 0 { + Ctxt.Logf("Processing range %x -> %x\n", start, end) + } + + // Advance curLoc to the last location that starts before/at start. + // After this loop, if there's a location that covers [start, end), it will be current. + // Otherwise the current piece will be too early. + for _, part := range parts { + choice := -1 + for i := curLoc[part.slot]; i < len(part.locs.Locations); i++ { + if part.locs.Locations[i].StartPC > start { + break //overshot + } + choice = i // best yet + } + if choice != -1 { + curLoc[part.slot] = choice + } + if Debug_locationlist != 0 { + Ctxt.Logf("\t %v => %v", slots[part.slot], curLoc[part.slot]) + } + } + if Debug_locationlist != 0 { + Ctxt.Logf("\n") + } + // Assemble the location list entry for this chunk. + present := 0 + for _, part := range parts { + dpiece := dwarf.Piece{ + Length: slots[part.slot].Type.Size(), + } + locIdx := curLoc[part.slot] + if locIdx >= len(part.locs.Locations) || + start >= part.locs.Locations[locIdx].EndPC || + end <= part.locs.Locations[locIdx].StartPC { + if Debug_locationlist != 0 { + Ctxt.Logf("\t%v: missing", slots[part.slot]) + } + dpiece.Missing = true + dloc.Pieces = append(dloc.Pieces, dpiece) + continue + } + present++ + loc := part.locs.Locations[locIdx] + if Debug_locationlist != 0 { + Ctxt.Logf("\t%v: %v", slots[part.slot], loc) + } + if loc.OnStack { + dpiece.OnStack = true + dpiece.StackOffset = int32(offs + slots[part.slot].Off + slots[part.slot].N.(*Node).Xoffset) + } else { + for reg := 0; reg < len(debugInfo.Registers); reg++ { + if loc.Registers&(1< totally missing\n") + } + continue + } + // Extend the previous entry if possible. + if len(dvar.LocationList) > 0 { + prev := &dvar.LocationList[len(dvar.LocationList)-1] + if prev.EndPC == dloc.StartPC && len(prev.Pieces) == len(dloc.Pieces) { + equal := true + for i := range prev.Pieces { + if prev.Pieces[i] != dloc.Pieces[i] { + equal = false + } + } + if equal { + prev.EndPC = end + if Debug_locationlist != 0 { + Ctxt.Logf("-> merged with previous, now %#v\n", prev) + } + continue + } + } + } + dvar.LocationList = append(dvar.LocationList, dloc) + if Debug_locationlist != 0 { + Ctxt.Logf("-> added: %#v\n", dloc) + } + } + return dvar } // fieldtrack adds R_USEFIELD relocations to fnsym to record any diff --git a/src/cmd/compile/internal/gc/sizeof_test.go b/src/cmd/compile/internal/gc/sizeof_test.go index 1ca0a61535..bd4453fa84 100644 --- a/src/cmd/compile/internal/gc/sizeof_test.go +++ b/src/cmd/compile/internal/gc/sizeof_test.go @@ -22,7 +22,7 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {Func{}, 124, 216}, + {Func{}, 128, 224}, {Name{}, 36, 56}, {Param{}, 28, 56}, {Node{}, 76, 128}, diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index f8aefaae5e..c769efe8cd 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -4384,6 +4384,7 @@ func genssa(f *ssa.Func, pp *Progs) { s.pp = pp var progToValue map[*obj.Prog]*ssa.Value var progToBlock map[*obj.Prog]*ssa.Block + var valueToProg []*obj.Prog var logProgs = e.log if logProgs { progToValue = make(map[*obj.Prog]*ssa.Value, f.NumValues()) @@ -4398,6 +4399,11 @@ func genssa(f *ssa.Func, pp *Progs) { s.ScratchFpMem = e.scratchFpMem + logLocationLists := Debug_locationlist != 0 + if Ctxt.Flag_locationlists { + e.curfn.Func.DebugInfo = ssa.BuildFuncDebug(f, logLocationLists) + valueToProg = make([]*obj.Prog, f.NumValues()) + } // Emit basic blocks for i, b := range f.Blocks { s.bstart[b.ID] = s.pp.next @@ -4438,12 +4444,16 @@ func genssa(f *ssa.Func, pp *Progs) { } case ssa.OpPhi: CheckLoweredPhi(v) - + case ssa.OpRegKill: + // nothing to do default: // let the backend handle it thearch.SSAGenValue(&s, v) } + if Ctxt.Flag_locationlists { + valueToProg[v.ID] = x + } if logProgs { for ; x != s.pp.next; x = x.Link { progToValue[x] = v @@ -4469,6 +4479,22 @@ func genssa(f *ssa.Func, pp *Progs) { } } + if Ctxt.Flag_locationlists { + for _, locList := range e.curfn.Func.DebugInfo.Variables { + for _, loc := range locList.Locations { + loc.StartProg = valueToProg[loc.Start.ID] + if loc.End == nil { + Fatalf("empty loc %v compiling %v", loc, f.Name) + } + loc.EndProg = valueToProg[loc.End.ID] + if !logLocationLists { + loc.Start = nil + loc.End = nil + } + } + } + } + // Resolve branches for _, br := range s.Branches { br.P.To.Val = s.bstart[br.B.ID] diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index 0fd146bca2..32ae6f2f28 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -7,6 +7,7 @@ package gc import ( + "cmd/compile/internal/ssa" "cmd/compile/internal/syntax" "cmd/compile/internal/types" "cmd/internal/obj" @@ -369,6 +370,7 @@ type Func struct { Closgen int Outerfunc *Node // outer function (for closure) FieldTrack map[*types.Sym]struct{} + DebugInfo *ssa.FuncDebug Ntype *Node // signature Top int // top context (Ecall, Eproc, etc) Closure *Node // OCLOSURE <-> ODCLFUNC diff --git a/src/cmd/compile/internal/ssa/cache.go b/src/cmd/compile/internal/ssa/cache.go index f1018da497..8434084bde 100644 --- a/src/cmd/compile/internal/ssa/cache.go +++ b/src/cmd/compile/internal/ssa/cache.go @@ -14,6 +14,11 @@ type Cache struct { blocks [200]Block locs [2000]Location + // Storage for DWARF variable locations. Lazily allocated + // since location lists are off by default. + varLocs []VarLoc + curVarLoc int + // Reusable stackAllocState. // See stackalloc.go's {new,put}StackAllocState. stackAllocState *stackAllocState @@ -38,4 +43,21 @@ func (c *Cache) Reset() { for i := range xl { xl[i] = nil } + xvl := c.varLocs[:c.curVarLoc] + for i := range xvl { + xvl[i] = VarLoc{} + } + c.curVarLoc = 0 +} + +func (c *Cache) NewVarLoc() *VarLoc { + if c.varLocs == nil { + c.varLocs = make([]VarLoc, 4000) + } + if c.curVarLoc == len(c.varLocs) { + return &VarLoc{} + } + vl := &c.varLocs[c.curVarLoc] + c.curVarLoc++ + return vl } diff --git a/src/cmd/compile/internal/ssa/debug.go b/src/cmd/compile/internal/ssa/debug.go new file mode 100644 index 0000000000..55db45b642 --- /dev/null +++ b/src/cmd/compile/internal/ssa/debug.go @@ -0,0 +1,559 @@ +// 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 ssa + +import ( + "cmd/internal/obj" + "fmt" + "strings" +) + +type SlotID int32 + +// A FuncDebug contains all the debug information for the variables in a +// function. Variables are identified by their LocalSlot, which may be the +// result of decomposing a larger variable. +type FuncDebug struct { + Slots []*LocalSlot + Variables []VarLocList + Registers []Register +} + +// append adds a location to the location list for slot. +func (f *FuncDebug) append(slot SlotID, loc *VarLoc) { + f.Variables[slot].append(loc) +} + +// lastLoc returns the last VarLoc for slot, or nil if it has none. +func (f *FuncDebug) lastLoc(slot SlotID) *VarLoc { + return f.Variables[slot].last() +} + +func (f *FuncDebug) String() string { + var vars []string + for slot, list := range f.Variables { + if len(list.Locations) == 0 { + continue + } + vars = append(vars, fmt.Sprintf("%v = %v", f.Slots[slot], list)) + } + return fmt.Sprintf("{%v}", strings.Join(vars, ", ")) +} + +// A VarLocList contains the locations for a variable, in program text order. +// It will often have gaps. +type VarLocList struct { + Locations []*VarLoc +} + +func (l *VarLocList) append(loc *VarLoc) { + l.Locations = append(l.Locations, loc) +} + +// last returns the last location in the list. +func (l *VarLocList) last() *VarLoc { + if l == nil || len(l.Locations) == 0 { + return nil + } + return l.Locations[len(l.Locations)-1] +} + +// A VarLoc describes a variable's location in a single contiguous range +// of program text. It is generated from the SSA representation, but it +// refers to the generated machine code, so the Values referenced are better +// understood as PCs than actual Values, and the ranges can cross blocks. +// The range is defined first by Values, which are then mapped to Progs +// during genssa and finally to function PCs after assembly. +// A variable can be on the stack and in any number of registers. +type VarLoc struct { + // Inclusive -- the first SSA value that the range covers. The value + // doesn't necessarily have anything to do with the variable; it just + // identifies a point in the program text. + Start *Value + // Exclusive -- the first SSA value after start that the range doesn't + // cover. A location with start == end is empty. + End *Value + // The prog/PCs corresponding to Start and End above. These are for the + // convenience of later passes, since code generation isn't done when + // BuildFuncDebug runs. + StartProg, EndProg *obj.Prog + StartPC, EndPC int64 + + // The registers this variable is available in. There can be more than + // one in various situations, e.g. it's being moved between registers. + Registers RegisterSet + // Indicates whether the variable is on the stack. The stack position is + // stored in the associated gc.Node. + OnStack bool + + // Used only during generation. Indicates whether this location lasts + // past the block's end. Without this, there would be no way to distinguish + // between a range that ended on the last Value of a block and one that + // didn't end at all. + survivedBlock bool +} + +// RegisterSet is a bitmap of registers, indexed by Register.num. +type RegisterSet uint64 + +func (v *VarLoc) String() string { + var registers []Register + if v.Start != nil { + registers = v.Start.Block.Func.Config.registers + } + loc := "" + if !v.OnStack && v.Registers == 0 { + loc = "!!!no location!!!" + } + if v.OnStack { + loc += "stack," + } + var regnames []string + for reg := 0; reg < 64; reg++ { + if v.Registers&(1< 0 { + b := work[0] + work = work[1:] + if blockLocs[b.ID] != nil { + continue // already processed + } + if !state.predecessorsDone(b, blockLocs) { + continue // not ready yet + } + + for _, edge := range b.Succs { + if blockLocs[edge.Block().ID] != nil { + continue + } + work = append(work, edge.Block()) + } + + // Build the starting state for the block from the final + // state of its predecessors. + locs := state.mergePredecessors(b, blockLocs) + if state.loggingEnabled { + state.logf("Processing %v, initial locs %v, regs %v\n", b, locs, state.registerContents) + } + // Update locs/registers with the effects of each Value. + for _, v := range b.Values { + slots := valueNames[v.ID] + + // Loads and stores inherit the names of their sources. + var source *Value + switch v.Op { + case OpStoreReg: + source = v.Args[0] + case OpLoadReg: + switch a := v.Args[0]; a.Op { + case OpArg: + source = a + case OpStoreReg: + source = a.Args[0] + default: + state.unexpected(v, "load with unexpected source op %v", a) + } + } + if source != nil { + slots = append(slots, valueNames[source.ID]...) + // As of writing, the compiler never uses a load/store as a + // source of another load/store, so there's no reason this should + // ever be consulted. Update just in case, and so that when + // valueNames is cached, we can reuse the memory. + valueNames[v.ID] = slots + } + + if len(slots) == 0 { + continue + } + + reg, _ := f.getHome(v.ID).(*Register) + state.processValue(locs, v, slots, reg) + + } + + // The block is done; end the locations for all its slots. + for _, locList := range locs.Variables { + last := locList.last() + if last == nil || last.End != nil { + continue + } + if len(b.Values) != 0 { + last.End = b.Values[len(b.Values)-1] + } else { + // This happens when a value survives into an empty block from its predecessor. + // Just carry it forward for liveness's sake. + last.End = last.Start + } + last.survivedBlock = true + } + if state.loggingEnabled { + f.Logf("Block done: locs %v, regs %v. work = %+v\n", locs, state.registerContents, work) + } + blockLocs[b.ID] = locs + } + + // Build the complete debug info by concatenating each of the blocks' + // locations together. + info := &FuncDebug{ + Variables: make([]VarLocList, len(state.slots)), + Slots: state.slots, + Registers: f.Config.registers, + } + for _, b := range f.Blocks { + // Ignore empty blocks; there will be some records for liveness + // but they're all useless. + if len(b.Values) == 0 { + continue + } + if blockLocs[b.ID] == nil { + state.unexpected(b.Values[0], "Never processed block %v\n", b) + continue + } + for slot, blockLocList := range blockLocs[b.ID].Variables { + for _, loc := range blockLocList.Locations { + if !loc.OnStack && loc.Registers == 0 { + state.unexpected(loc.Start, "Location for %v with no storage: %+v\n", state.slots[slot], loc) + continue // don't confuse downstream with our bugs + } + if loc.Start == nil || loc.End == nil { + state.unexpected(b.Values[0], "Location for %v missing start or end: %v\n", state.slots[slot], loc) + continue + } + info.append(SlotID(slot), loc) + } + } + } + if state.loggingEnabled { + f.Logf("Final result:\n") + for slot, locList := range info.Variables { + f.Logf("\t%v => %v\n", state.slots[slot], locList) + } + } + return info +} + +// isSynthetic reports whether if slot represents a compiler-inserted variable, +// e.g. an autotmp or an anonymous return value that needed a stack slot. +func isSynthetic(slot *LocalSlot) bool { + c := slot.Name()[0] + return c == '.' || c == '~' +} + +// predecessorsDone reports whether block is ready to be processed. +func (state *debugState) predecessorsDone(b *Block, blockLocs []*FuncDebug) bool { + f := b.Func + for _, edge := range b.Preds { + // Ignore back branches, e.g. the continuation of a for loop. + // This may not work for functions with mutual gotos, which are not + // reducible, in which case debug information will be missing for any + // code after that point in the control flow. + if f.sdom().isAncestorEq(b, edge.b) { + if state.loggingEnabled { + f.Logf("ignoring back branch from %v to %v\n", edge.b, b) + } + continue // back branch + } + if blockLocs[edge.b.ID] == nil { + if state.loggingEnabled { + f.Logf("%v is not ready because %v isn't done\n", b, edge.b) + } + return false + } + } + return true +} + +// mergePredecessors takes the end state of each of b's predecessors and +// intersects them to form the starting state for b. +// The registers slice (the second return value) will be reused for each call to mergePredecessors. +func (state *debugState) mergePredecessors(b *Block, blockLocs []*FuncDebug) *FuncDebug { + live := make([]VarLocList, len(state.slots)) + + // Filter out back branches. + var preds []*Block + for _, pred := range b.Preds { + if blockLocs[pred.b.ID] != nil { + preds = append(preds, pred.b) + } + } + + if len(preds) > 0 { + p := preds[0] + for slot, locList := range blockLocs[p.ID].Variables { + last := locList.last() + if last == nil || !last.survivedBlock { + continue + } + // If this block is empty, carry forward the end value for liveness. + // It'll be ignored later. + start := last.End + if len(b.Values) != 0 { + start = b.Values[0] + } + loc := state.cache.NewVarLoc() + loc.Start = start + loc.OnStack = last.OnStack + loc.Registers = last.Registers + live[slot].append(loc) + } + } + if state.loggingEnabled && len(b.Preds) > 1 { + state.logf("Starting merge with state from %v: %v\n", b.Preds[0].b, blockLocs[b.Preds[0].b.ID]) + } + for i := 1; i < len(preds); i++ { + p := preds[i] + if state.loggingEnabled { + state.logf("Merging in state from %v: %v &= %v\n", p, live, blockLocs[p.ID]) + } + + for slot, liveVar := range live { + liveLoc := liveVar.last() + if liveLoc == nil { + continue + } + + predLoc := blockLocs[p.ID].lastLoc(SlotID(slot)) + // Clear out slots missing/dead in p. + if predLoc == nil || !predLoc.survivedBlock { + live[slot].Locations = nil + continue + } + + // Unify storage locations. + liveLoc.OnStack = liveLoc.OnStack && predLoc.OnStack + liveLoc.Registers &= predLoc.Registers + } + } + + // Create final result. + locs := &FuncDebug{Variables: live, Slots: state.slots} + for reg := range state.registerContents { + state.registerContents[reg] = state.registerContents[reg][:0] + } + for slot, locList := range live { + loc := locList.last() + if loc == nil { + continue + } + for reg := 0; reg < state.numRegisters; reg++ { + if loc.Registers&(1< regDebug { fmt.Printf("freeReg %s (dump %s/%s)\n", s.registers[r].Name(), v, s.regs[r].c) } + if !resetting && s.f.Config.ctxt.Flag_locationlists && len(s.valueNames[v.ID]) != 0 { + kill := s.curBlock.NewValue0(src.NoXPos, OpRegKill, types.TypeVoid) + for int(kill.ID) >= len(s.orig) { + s.orig = append(s.orig, nil) + } + for _, name := range s.valueNames[v.ID] { + s.f.NamedValues[name] = append(s.f.NamedValues[name], kill) + } + s.f.setHome(kill, &s.registers[r]) + } s.regs[r] = regState{} s.values[v.ID].regs &^= regMask(1) << r s.used &^= regMask(1) << r @@ -599,6 +619,17 @@ func (s *regAllocState) init(f *Func) { s.values = make([]valState, f.NumValues()) s.orig = make([]*Value, f.NumValues()) s.copies = make(map[*Value]bool) + if s.f.Config.ctxt.Flag_locationlists { + s.valueNames = make([][]LocalSlot, f.NumValues()) + for slot, values := range f.NamedValues { + if isSynthetic(&slot) { + continue + } + for _, value := range values { + s.valueNames[value.ID] = append(s.valueNames[value.ID], slot) + } + } + } for _, b := range f.Blocks { for _, v := range b.Values { if !v.Type.IsMemory() && !v.Type.IsVoid() && !v.Type.IsFlags() && !v.Type.IsTuple() { @@ -692,7 +723,9 @@ func (s *regAllocState) liveAfterCurrentInstruction(v *Value) bool { // Sets the state of the registers to that encoded in regs. func (s *regAllocState) setState(regs []endReg) { - s.freeRegs(s.used) + for s.used != 0 { + s.freeOrResetReg(pickReg(s.used), true) + } for _, x := range regs { s.assignReg(x.r, x.v, x.c) } @@ -735,6 +768,9 @@ func (s *regAllocState) regalloc(f *Func) { } for _, b := range f.Blocks { + if s.f.pass.debug > regDebug { + fmt.Printf("Begin processing block %v\n", b) + } s.curBlock = b // Initialize regValLiveSet and uses fields for this block. @@ -830,9 +866,6 @@ func (s *regAllocState) regalloc(f *Func) { // This is the complicated case. We have more than one predecessor, // which means we may have Phi ops. - // Copy phi ops into new schedule. - b.Values = append(b.Values, phis...) - // Start with the final register state of the primary predecessor idx := s.primary[b.ID] if idx < 0 { @@ -910,6 +943,9 @@ func (s *regAllocState) regalloc(f *Func) { } } + // Copy phi ops into new schedule. + b.Values = append(b.Values, phis...) + // Third pass - pick registers for phis whose inputs // were not in a register. for i, v := range phis { @@ -1005,7 +1041,7 @@ func (s *regAllocState) regalloc(f *Func) { pidx := e.i for _, v := range succ.Values { if v.Op != OpPhi { - break + continue } if !s.values[v.ID].needReg { continue @@ -1565,6 +1601,9 @@ func (s *regAllocState) placeSpills() { for _, b := range f.Blocks { var m regMask for _, v := range b.Values { + if v.Op == OpRegKill { + continue + } if v.Op != OpPhi { break } @@ -1675,7 +1714,7 @@ func (s *regAllocState) placeSpills() { for _, b := range f.Blocks { nphi := 0 for _, v := range b.Values { - if v.Op != OpPhi { + if v.Op != OpRegKill && v.Op != OpPhi { break } nphi++ @@ -1800,6 +1839,9 @@ func (e *edgeState) setup(idx int, srcReg []endReg, dstReg []startReg, stacklive } // Phis need their args to end up in a specific location. for _, v := range e.b.Values { + if v.Op == OpRegKill { + continue + } if v.Op != OpPhi { break } @@ -1878,6 +1920,7 @@ func (e *edgeState) process() { if e.s.f.pass.debug > regDebug { fmt.Printf("breaking cycle with v%d in %s:%s\n", vid, loc.Name(), c) } + e.erase(r) if _, isReg := loc.(*Register); isReg { c = e.p.NewValue1(d.pos, OpCopy, c.Type, c) } else { @@ -1943,6 +1986,18 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP } } _, dstReg := loc.(*Register) + + // Pre-clobber destination. This avoids the + // following situation: + // - v is currently held in R0 and stacktmp0. + // - We want to copy stacktmp1 to stacktmp0. + // - We choose R0 as the temporary register. + // During the copy, both R0 and stacktmp0 are + // clobbered, losing both copies of v. Oops! + // Erasing the destination early means R0 will not + // be chosen as the temp register, as it will then + // be the last copy of v. + e.erase(loc) var x *Value if c == nil { if !e.s.values[vid].rematerializeable { @@ -1953,8 +2008,8 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP } else { // Rematerialize into stack slot. Need a free // register to accomplish this. - e.erase(loc) // see pre-clobber comment below r := e.findRegFor(v.Type) + e.erase(r) x = v.copyIntoNoXPos(e.p) e.set(r, vid, x, false, pos) // Make sure we spill with the size of the slot, not the @@ -1976,20 +2031,8 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP x = e.p.NewValue1(pos, OpLoadReg, c.Type, c) } else { // mem->mem. Use temp register. - - // Pre-clobber destination. This avoids the - // following situation: - // - v is currently held in R0 and stacktmp0. - // - We want to copy stacktmp1 to stacktmp0. - // - We choose R0 as the temporary register. - // During the copy, both R0 and stacktmp0 are - // clobbered, losing both copies of v. Oops! - // Erasing the destination early means R0 will not - // be chosen as the temp register, as it will then - // be the last copy of v. - e.erase(loc) - r := e.findRegFor(c.Type) + e.erase(r) t := e.p.NewValue1(pos, OpLoadReg, c.Type, c) e.set(r, vid, t, false, pos) x = e.p.NewValue1(pos, OpStoreReg, loc.(LocalSlot).Type, t) @@ -2008,7 +2051,6 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP // set changes the contents of location loc to hold the given value and its cached representative. func (e *edgeState) set(loc Location, vid ID, c *Value, final bool, pos src.XPos) { e.s.f.setHome(c, loc) - e.erase(loc) e.contents[loc] = contentRecord{vid, c, final, pos} a := e.cache[vid] if len(a) == 0 { @@ -2059,6 +2101,16 @@ func (e *edgeState) erase(loc Location) { fmt.Printf("v%d no longer available in %s:%s\n", vid, loc.Name(), c) } a[i], a = a[len(a)-1], a[:len(a)-1] + if e.s.f.Config.ctxt.Flag_locationlists { + if _, isReg := loc.(*Register); isReg && int(c.ID) < len(e.s.valueNames) && len(e.s.valueNames[c.ID]) != 0 { + kill := e.p.NewValue0(src.NoXPos, OpRegKill, types.TypeVoid) + e.s.f.setHome(kill, loc) + for _, name := range e.s.valueNames[c.ID] { + e.s.f.NamedValues[name] = append(e.s.f.NamedValues[name], kill) + } + } + } + break } } diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go index 7edc71be52..6df535153a 100644 --- a/src/cmd/compile/internal/ssa/value.go +++ b/src/cmd/compile/internal/ssa/value.go @@ -10,6 +10,7 @@ import ( "cmd/internal/src" "fmt" "math" + "strings" ) // A Value represents a value in the SSA representation of the program. @@ -98,7 +99,7 @@ func (v *Value) AuxValAndOff() ValAndOff { return ValAndOff(v.AuxInt) } -// long form print. v# = opcode [aux] args [: reg] +// long form print. v# = opcode [aux] args [: reg] (names) func (v *Value) LongString() string { s := fmt.Sprintf("v%d = %s", v.ID, v.Op) s += " <" + v.Type.String() + ">" @@ -110,6 +111,18 @@ func (v *Value) LongString() string { if int(v.ID) < len(r) && r[v.ID] != nil { s += " : " + r[v.ID].Name() } + var names []string + for name, values := range v.Block.Func.NamedValues { + for _, value := range values { + if value == v { + names = append(names, name.Name()) + break // drop duplicates. + } + } + } + if len(names) != 0 { + s += " (" + strings.Join(names, ", ") + ")" + } return s } diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index b58052beb3..2b034257a6 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -15,6 +15,9 @@ import ( // InfoPrefix is the prefix for all the symbols containing DWARF info entries. const InfoPrefix = "go.info." +// RangePrefix is the prefix for all the symbols containing DWARF location lists. +const LocPrefix = "go.loc." + // RangePrefix is the prefix for all the symbols containing DWARF range lists. const RangePrefix = "go.range." @@ -23,13 +26,31 @@ type Sym interface { Len() int64 } +// A Location represents a variable's location at a particular PC range. +// It becomes a location list entry in the DWARF. +type Location struct { + StartPC, EndPC int64 + Pieces []Piece +} + +// A Piece represents the location of a particular part of a variable. +// It becomes part of a location list entry (a DW_OP_piece) in the DWARF. +type Piece struct { + Length int64 + StackOffset int32 + RegNum int16 + Missing bool + OnStack bool // if true, RegNum is unset. +} + // A Var represents a local variable or a function parameter. type Var struct { - Name string - Abbrev int // Either DW_ABRV_AUTO or DW_ABRV_PARAM - StackOffset int32 - Scope int32 - Type Sym + Name string + Abbrev int // Either DW_ABRV_AUTO or DW_ABRV_PARAM + StackOffset int32 + LocationList []Location + Scope int32 + Type Sym } // A Scope represents a lexical scope. All variables declared within a @@ -205,7 +226,7 @@ const ( ) // Index into the abbrevs table below. -// Keep in sync with ispubname() and ispubtype() below. +// Keep in sync with ispubname() and ispubtype() in ld/dwarf.go. // ispubtype considers >= NULLTYPE public const ( DW_ABRV_NULL = iota @@ -709,31 +730,30 @@ func HasChildren(die *DWDie) bool { // PutFunc writes a DIE for a function to s. // It also writes child DIEs for each variable in vars. -func PutFunc(ctxt Context, s, ranges Sym, name string, external bool, startPC Sym, size int64, scopes []Scope) error { - Uleb128put(ctxt, s, DW_ABRV_FUNCTION) - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_string, DW_CLS_STRING, int64(len(name)), name) - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, 0, startPC) - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, size, startPC) - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_block1, DW_CLS_BLOCK, 1, []byte{DW_OP_call_frame_cfa}) +func PutFunc(ctxt Context, info, loc, ranges Sym, name string, external bool, startPC Sym, size int64, scopes []Scope) error { + Uleb128put(ctxt, info, DW_ABRV_FUNCTION) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_string, DW_CLS_STRING, int64(len(name)), name) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, 0, startPC) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, size, startPC) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_block1, DW_CLS_BLOCK, 1, []byte{DW_OP_call_frame_cfa}) var ev int64 if external { ev = 1 } - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_flag, DW_CLS_FLAG, ev, 0) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_flag, DW_CLS_FLAG, ev, 0) if len(scopes) > 0 { var encbuf [20]byte - if putscope(ctxt, s, ranges, startPC, 0, scopes, encbuf[:0]) < int32(len(scopes)) { + if putscope(ctxt, info, loc, ranges, startPC, 0, scopes, encbuf[:0]) < int32(len(scopes)) { return errors.New("multiple toplevel scopes") } } - - Uleb128put(ctxt, s, 0) + Uleb128put(ctxt, info, 0) return nil } -func putscope(ctxt Context, s, ranges Sym, startPC Sym, curscope int32, scopes []Scope, encbuf []byte) int32 { +func putscope(ctxt Context, info, loc, ranges, startPC Sym, curscope int32, scopes []Scope, encbuf []byte) int32 { for _, v := range scopes[curscope].Vars { - putvar(ctxt, s, v, encbuf) + putvar(ctxt, info, loc, v, startPC, encbuf) } this := curscope curscope++ @@ -744,12 +764,12 @@ func putscope(ctxt Context, s, ranges Sym, startPC Sym, curscope int32, scopes [ } if len(scope.Ranges) == 1 { - Uleb128put(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE) - putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].Start, startPC) - putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].End, startPC) + Uleb128put(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE) + putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].Start, startPC) + putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].End, startPC) } else { - Uleb128put(ctxt, s, DW_ABRV_LEXICAL_BLOCK_RANGES) - putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_RANGES, DW_FORM_sec_offset, DW_CLS_PTR, ranges.Len(), ranges) + Uleb128put(ctxt, info, DW_ABRV_LEXICAL_BLOCK_RANGES) + putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_RANGES, DW_FORM_sec_offset, DW_CLS_PTR, ranges.Len(), ranges) ctxt.AddAddress(ranges, nil, -1) ctxt.AddAddress(ranges, startPC, 0) @@ -761,26 +781,66 @@ func putscope(ctxt Context, s, ranges Sym, startPC Sym, curscope int32, scopes [ ctxt.AddAddress(ranges, nil, 0) } - curscope = putscope(ctxt, s, ranges, startPC, curscope, scopes, encbuf) + curscope = putscope(ctxt, info, loc, ranges, startPC, curscope, scopes, encbuf) - Uleb128put(ctxt, s, 0) + Uleb128put(ctxt, info, 0) } return curscope } -func putvar(ctxt Context, s Sym, v *Var, encbuf []byte) { +func putvar(ctxt Context, info, loc Sym, v *Var, startPC Sym, encbuf []byte) { n := v.Name - Uleb128put(ctxt, s, int64(v.Abbrev)) - putattr(ctxt, s, v.Abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) - loc := append(encbuf[:0], DW_OP_call_frame_cfa) - if v.StackOffset != 0 { - loc = append(loc, DW_OP_consts) - loc = AppendSleb128(loc, int64(v.StackOffset)) - loc = append(loc, DW_OP_plus) + Uleb128put(ctxt, info, int64(v.Abbrev)) + putattr(ctxt, info, v.Abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) + if v.Abbrev == DW_ABRV_AUTO_LOCLIST || v.Abbrev == DW_ABRV_PARAM_LOCLIST { + putattr(ctxt, info, v.Abbrev, DW_FORM_sec_offset, DW_CLS_PTR, int64(loc.Len()), loc) + addLocList(ctxt, loc, startPC, v, encbuf) + } else { + loc := append(encbuf[:0], DW_OP_call_frame_cfa) + if v.StackOffset != 0 { + loc = append(loc, DW_OP_consts) + loc = AppendSleb128(loc, int64(v.StackOffset)) + loc = append(loc, DW_OP_plus) + } + putattr(ctxt, info, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) } - putattr(ctxt, s, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) - putattr(ctxt, s, v.Abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) + putattr(ctxt, info, v.Abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) +} + +func addLocList(ctxt Context, listSym, startPC Sym, v *Var, encbuf []byte) { + // Base address entry: max ptr followed by the base address. + ctxt.AddInt(listSym, ctxt.PtrSize(), ^0) + ctxt.AddAddress(listSym, startPC, 0) + for _, entry := range v.LocationList { + ctxt.AddInt(listSym, ctxt.PtrSize(), entry.StartPC) + ctxt.AddInt(listSym, ctxt.PtrSize(), entry.EndPC) + locBuf := encbuf[:0] + for _, piece := range entry.Pieces { + if !piece.Missing { + if piece.OnStack { + locBuf = append(locBuf, DW_OP_fbreg) + locBuf = AppendSleb128(locBuf, int64(piece.StackOffset)) + } else { + if piece.RegNum < 32 { + locBuf = append(locBuf, DW_OP_reg0+byte(piece.RegNum)) + } else { + locBuf = append(locBuf, DW_OP_regx) + locBuf = AppendUleb128(locBuf, uint64(piece.RegNum)) + } + } + } + if len(entry.Pieces) > 1 { + locBuf = append(locBuf, DW_OP_piece) + locBuf = AppendUleb128(locBuf, uint64(piece.Length)) + } + } + ctxt.AddInt(listSym, 2, int64(len(locBuf))) + ctxt.AddBytes(listSym, locBuf) + } + // End list + ctxt.AddInt(listSym, ctxt.PtrSize(), 0) + ctxt.AddInt(listSym, ctxt.PtrSize(), 0) } // VarsByOffset attaches the methods of sort.Interface to []*Var, diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index d49bc8c564..68e1b70ac0 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -330,7 +330,8 @@ type FuncInfo struct { Autom []*Auto Pcln Pcln - dwarfSym *LSym + dwarfInfoSym *LSym + dwarfLocSym *LSym dwarfRangesSym *LSym GCArgs LSym @@ -476,25 +477,26 @@ type Pcdata struct { // Link holds the context for writing object code from a compiler // to be linker input or for reading that input into the linker. type Link struct { - Headtype objabi.HeadType - Arch *LinkArch - Debugasm bool - Debugvlog bool - Debugpcln string - Flag_shared bool - Flag_dynlink bool - Flag_optimize bool - Bso *bufio.Writer - Pathname string - hashmu sync.Mutex // protects hash - hash map[string]*LSym // name -> sym mapping - statichash map[string]*LSym // name -> sym mapping for static syms - PosTable src.PosTable - InlTree InlTree // global inlining tree used by gc/inl.go - Imports []string - DiagFunc func(string, ...interface{}) - DebugInfo func(fn *LSym, curfn interface{}) []dwarf.Scope // if non-nil, curfn is a *gc.Node - Errors int + Headtype objabi.HeadType + Arch *LinkArch + Debugasm bool + Debugvlog bool + Debugpcln string + Flag_shared bool + Flag_dynlink bool + Flag_optimize bool + Flag_locationlists bool + Bso *bufio.Writer + Pathname string + hashmu sync.Mutex // protects hash + hash map[string]*LSym // name -> sym mapping + statichash map[string]*LSym // name -> sym mapping for static syms + PosTable src.PosTable + InlTree InlTree // global inlining tree used by gc/inl.go + Imports []string + DiagFunc func(string, ...interface{}) + DebugInfo func(fn *LSym, curfn interface{}) []dwarf.Scope // if non-nil, curfn is a *gc.Node + Errors int Framepointer_enabled bool @@ -533,9 +535,10 @@ func (ctxt *Link) FixedFrameSize() int64 { // LinkArch is the definition of a single architecture. type LinkArch struct { *sys.Arch - Init func(*Link) - Preprocess func(*Link, *LSym, ProgAlloc) - Assemble func(*Link, *LSym, ProgAlloc) - Progedit func(*Link, *Prog, ProgAlloc) - UnaryDst map[As]bool // Instruction takes one operand, a destination. + Init func(*Link) + Preprocess func(*Link, *LSym, ProgAlloc) + Assemble func(*Link, *LSym, ProgAlloc) + Progedit func(*Link, *Prog, ProgAlloc) + UnaryDst map[As]bool // Instruction takes one operand, a destination. + DWARFRegisters map[int16]int16 } diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index e309c5f7e7..539d013037 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -465,15 +465,18 @@ func (c dwCtxt) AddSectionOffset(s dwarf.Sym, size int, t interface{}, ofs int64 } // dwarfSym returns the DWARF symbols for TEXT symbol. -func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfRangesSym *LSym) { +func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfLocSym, dwarfRangesSym *LSym) { if s.Type != objabi.STEXT { ctxt.Diag("dwarfSym of non-TEXT %v", s) } - if s.Func.dwarfSym == nil { - s.Func.dwarfSym = ctxt.LookupDerived(s, dwarf.InfoPrefix+s.Name) + if s.Func.dwarfInfoSym == nil { + s.Func.dwarfInfoSym = ctxt.LookupDerived(s, dwarf.InfoPrefix+s.Name) + if ctxt.Flag_locationlists { + s.Func.dwarfLocSym = ctxt.LookupDerived(s, dwarf.LocPrefix+s.Name) + } s.Func.dwarfRangesSym = ctxt.LookupDerived(s, dwarf.RangePrefix+s.Name) } - return s.Func.dwarfSym, s.Func.dwarfRangesSym + return s.Func.dwarfInfoSym, s.Func.dwarfLocSym, s.Func.dwarfRangesSym } func (s *LSym) Len() int64 { @@ -483,15 +486,15 @@ func (s *LSym) Len() int64 { // populateDWARF fills in the DWARF Debugging Information Entries for TEXT symbol s. // The DWARFs symbol must already have been initialized in InitTextSym. func (ctxt *Link) populateDWARF(curfn interface{}, s *LSym) { - dsym, drsym := ctxt.dwarfSym(s) - if dsym.Size != 0 { + info, loc, ranges := ctxt.dwarfSym(s) + if info.Size != 0 { ctxt.Diag("makeFuncDebugEntry double process %v", s) } var scopes []dwarf.Scope if ctxt.DebugInfo != nil { scopes = ctxt.DebugInfo(s, curfn) } - err := dwarf.PutFunc(dwCtxt{ctxt}, dsym, drsym, s.Name, !s.Static(), s, s.Size, scopes) + err := dwarf.PutFunc(dwCtxt{ctxt}, info, loc, ranges, s.Name, !s.Static(), s, s.Size, scopes) if err != nil { ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err) } diff --git a/src/cmd/internal/obj/plist.go b/src/cmd/internal/obj/plist.go index 861da88703..1bb05aedfa 100644 --- a/src/cmd/internal/obj/plist.go +++ b/src/cmd/internal/obj/plist.go @@ -136,13 +136,17 @@ func (ctxt *Link) InitTextSym(s *LSym, flag int) { ctxt.Text = append(ctxt.Text, s) // Set up DWARF entries for s. - dsym, drsym := ctxt.dwarfSym(s) - dsym.Type = objabi.SDWARFINFO - dsym.Set(AttrDuplicateOK, s.DuplicateOK()) - drsym.Type = objabi.SDWARFRANGE - drsym.Set(AttrDuplicateOK, s.DuplicateOK()) - ctxt.Data = append(ctxt.Data, dsym) - ctxt.Data = append(ctxt.Data, drsym) + info, loc, ranges := ctxt.dwarfSym(s) + info.Type = objabi.SDWARFINFO + info.Set(AttrDuplicateOK, s.DuplicateOK()) + if loc != nil { + loc.Type = objabi.SDWARFLOC + loc.Set(AttrDuplicateOK, s.DuplicateOK()) + ctxt.Data = append(ctxt.Data, loc) + } + ranges.Type = objabi.SDWARFRANGE + ranges.Set(AttrDuplicateOK, s.DuplicateOK()) + ctxt.Data = append(ctxt.Data, info, ranges) // Set up the function's gcargs and gclocals. // They will be filled in later if needed. diff --git a/src/cmd/internal/obj/x86/a.out.go b/src/cmd/internal/obj/x86/a.out.go index 04f9ef68a4..92d358ba4e 100644 --- a/src/cmd/internal/obj/x86/a.out.go +++ b/src/cmd/internal/obj/x86/a.out.go @@ -1006,3 +1006,120 @@ const ( T_64 = 1 << 6 T_GOTYPE = 1 << 7 ) + +// https://www.uclibc.org/docs/psABI-x86_64.pdf, figure 3.36 +var AMD64DWARFRegisters = map[int16]int16{ + REG_AX: 0, + REG_DX: 1, + REG_CX: 2, + REG_BX: 3, + REG_SI: 4, + REG_DI: 5, + REG_BP: 6, + REG_SP: 7, + REG_R8: 8, + REG_R9: 9, + REG_R10: 10, + REG_R11: 11, + REG_R12: 12, + REG_R13: 13, + REG_R14: 14, + REG_R15: 15, + // 16 is "Return Address RA", whatever that is. + // XMM registers. %xmmN => XN. + REG_X0: 17, + REG_X1: 18, + REG_X2: 19, + REG_X3: 20, + REG_X4: 21, + REG_X5: 22, + REG_X6: 23, + REG_X7: 24, + REG_X8: 25, + REG_X9: 26, + REG_X10: 27, + REG_X11: 28, + REG_X12: 29, + REG_X13: 30, + REG_X14: 31, + REG_X15: 32, + // ST registers. %stN => FN. + REG_F0: 33, + REG_F1: 34, + REG_F2: 35, + REG_F3: 36, + REG_F4: 37, + REG_F5: 38, + REG_F6: 39, + REG_F7: 40, + // MMX registers. %mmN => MN. + REG_M0: 41, + REG_M1: 42, + REG_M2: 43, + REG_M3: 44, + REG_M4: 45, + REG_M5: 46, + REG_M6: 47, + REG_M7: 48, + // 48 is flags, which doesn't have a name. + REG_ES: 50, + REG_CS: 51, + REG_SS: 52, + REG_DS: 53, + REG_FS: 54, + REG_GS: 55, + // 58 and 59 are {fs,gs}base, which don't have names. + REG_TR: 62, + REG_LDTR: 63, + // 64-66 are mxcsr, fcw, fsw, which don't have names. +} + +// https://www.uclibc.org/docs/psABI-i386.pdf, table 2.14 +var X86DWARFRegisters = map[int16]int16{ + REG_AX: 0, + REG_CX: 1, + REG_DX: 2, + REG_BX: 3, + REG_SP: 4, + REG_BP: 5, + REG_SI: 6, + REG_DI: 7, + // 8 is "Return Address RA", whatever that is. + // 9 is flags, which doesn't have a name. + // ST registers. %stN => FN. + REG_F0: 11, + REG_F1: 12, + REG_F2: 13, + REG_F3: 14, + REG_F4: 15, + REG_F5: 16, + REG_F6: 17, + REG_F7: 18, + // XMM registers. %xmmN => XN. + REG_X0: 21, + REG_X1: 22, + REG_X2: 23, + REG_X3: 24, + REG_X4: 25, + REG_X5: 26, + REG_X6: 27, + REG_X7: 28, + // MMX registers. %mmN => MN. + REG_M0: 29, + REG_M1: 30, + REG_M2: 31, + REG_M3: 32, + REG_M4: 33, + REG_M5: 34, + REG_M6: 35, + REG_M7: 36, + // 39 is mxcsr, which doesn't have a name. + REG_ES: 40, + REG_CS: 41, + REG_SS: 42, + REG_DS: 43, + REG_FS: 44, + REG_GS: 45, + REG_TR: 48, + REG_LDTR: 49, +} diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index d34f0aeaa6..27873e0824 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -1231,28 +1231,31 @@ var unaryDst = map[obj.As]bool{ } var Linkamd64 = obj.LinkArch{ - Arch: sys.ArchAMD64, - Init: instinit, - Preprocess: preprocess, - Assemble: span6, - Progedit: progedit, - UnaryDst: unaryDst, + Arch: sys.ArchAMD64, + Init: instinit, + Preprocess: preprocess, + Assemble: span6, + Progedit: progedit, + UnaryDst: unaryDst, + DWARFRegisters: AMD64DWARFRegisters, } var Linkamd64p32 = obj.LinkArch{ - Arch: sys.ArchAMD64P32, - Init: instinit, - Preprocess: preprocess, - Assemble: span6, - Progedit: progedit, - UnaryDst: unaryDst, + Arch: sys.ArchAMD64P32, + Init: instinit, + Preprocess: preprocess, + Assemble: span6, + Progedit: progedit, + UnaryDst: unaryDst, + DWARFRegisters: AMD64DWARFRegisters, } var Link386 = obj.LinkArch{ - Arch: sys.Arch386, - Init: instinit, - Preprocess: preprocess, - Assemble: span6, - Progedit: progedit, - UnaryDst: unaryDst, + Arch: sys.Arch386, + Init: instinit, + Preprocess: preprocess, + Assemble: span6, + Progedit: progedit, + UnaryDst: unaryDst, + DWARFRegisters: AMD64DWARFRegisters, } diff --git a/src/cmd/internal/objabi/symkind.go b/src/cmd/internal/objabi/symkind.go index b037e9e4ed..ac91824d17 100644 --- a/src/cmd/internal/objabi/symkind.go +++ b/src/cmd/internal/objabi/symkind.go @@ -57,4 +57,5 @@ const ( // Debugging data SDWARFINFO SDWARFRANGE + SDWARFLOC ) diff --git a/src/cmd/internal/objabi/symkind_string.go b/src/cmd/internal/objabi/symkind_string.go index 5123dc7097..3064c8ee05 100644 --- a/src/cmd/internal/objabi/symkind_string.go +++ b/src/cmd/internal/objabi/symkind_string.go @@ -4,9 +4,9 @@ package objabi import "fmt" -const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFINFOSDWARFRANGE" +const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFINFOSDWARFRANGESDWARFLOC" -var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 61, 72} +var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 61, 72, 81} func (i SymKind) String() string { if i >= SymKind(len(_SymKind_index)-1) { diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index bf219f7b62..1d053d23b7 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1862,6 +1862,8 @@ func (ctxt *Link) dodata() { sect = addsection(&Segdwarf, ".debug_info", 04) case SDWARFRANGE: sect = addsection(&Segdwarf, ".debug_ranges", 04) + case SDWARFLOC: + sect = addsection(&Segdwarf, ".debug_loc", 04) default: Errorf(dwarfp[i], "unknown DWARF section %v", curType) } diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 9b11fdcff6..b6fb1bb5c1 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -1579,10 +1579,35 @@ func dwarfgeneratedebugsyms(ctxt *Link) { syms = writearanges(ctxt, syms) syms = writegdbscript(ctxt, syms) syms = append(syms, infosyms...) + syms = collectlocs(ctxt, syms, funcs) syms = writeranges(ctxt, syms) dwarfp = syms } +func collectlocs(ctxt *Link, syms []*Symbol, funcs []*Symbol) []*Symbol { + empty := true + for _, fn := range funcs { + for _, reloc := range fn.R { + if reloc.Type == objabi.R_DWARFREF && strings.HasPrefix(reloc.Sym.Name, dwarf.LocPrefix) { + reloc.Sym.Attr |= AttrReachable | AttrNotInSymbolTable + syms = append(syms, reloc.Sym) + empty = false + // One location list entry per function, but many relocations to it. Don't duplicate. + break + } + } + } + // Don't emit .debug_loc if it's empty -- it makes the ARM linker mad. + if !empty { + locsym := ctxt.Syms.Lookup(".debug_loc", 0) + locsym.R = locsym.R[:0] + locsym.Type = SDWARFLOC + locsym.Attr |= AttrReachable + syms = append(syms, locsym) + } + return syms +} + /* * Elf. */ @@ -1595,6 +1620,7 @@ func dwarfaddshstrings(ctxt *Link, shstrtab *Symbol) { Addstring(shstrtab, ".debug_aranges") Addstring(shstrtab, ".debug_frame") Addstring(shstrtab, ".debug_info") + Addstring(shstrtab, ".debug_loc") Addstring(shstrtab, ".debug_line") Addstring(shstrtab, ".debug_pubnames") Addstring(shstrtab, ".debug_pubtypes") @@ -1602,6 +1628,7 @@ func dwarfaddshstrings(ctxt *Link, shstrtab *Symbol) { Addstring(shstrtab, ".debug_ranges") if Linkmode == LinkExternal { Addstring(shstrtab, elfRelType+".debug_info") + Addstring(shstrtab, elfRelType+".debug_loc") Addstring(shstrtab, elfRelType+".debug_aranges") Addstring(shstrtab, elfRelType+".debug_line") Addstring(shstrtab, elfRelType+".debug_frame") @@ -1628,6 +1655,10 @@ func dwarfaddelfsectionsyms(ctxt *Link) { putelfsectionsym(sym, sym.Sect.Elfsect.shnum) sym = ctxt.Syms.Lookup(".debug_frame", 0) putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + sym = ctxt.Syms.Lookup(".debug_loc", 0) + if sym.Sect != nil { + putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + } sym = ctxt.Syms.Lookup(".debug_ranges", 0) if sym.Sect != nil { putelfsectionsym(sym, sym.Sect.Elfsect.shnum) diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 0fc947fec2..78f8d6e70e 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -1808,7 +1808,7 @@ func elfrelocsect(ctxt *Link, sect *Section, syms []*Symbol) { continue } if r.Xsym == nil { - Errorf(sym, "missing xsym in relocation") + Errorf(sym, "missing xsym in relocation %#v %#v", r.Sym.Name, sym) continue } if r.Xsym.ElfsymForReloc() == 0 { @@ -2596,12 +2596,9 @@ elfobj: elfshreloc(sect) } for _, s := range dwarfp { - if len(s.R) > 0 || s.Type == SDWARFINFO { + if len(s.R) > 0 || s.Type == SDWARFINFO || s.Type == SDWARFLOC { elfshreloc(s.Sect) } - if s.Type == SDWARFINFO { - break - } } // add a .note.GNU-stack section to mark the stack as non-executable sh := elfshname(".note.GNU-stack") diff --git a/src/cmd/link/internal/ld/symkind.go b/src/cmd/link/internal/ld/symkind.go index c057f6cd0c..5ac04cf45a 100644 --- a/src/cmd/link/internal/ld/symkind.go +++ b/src/cmd/link/internal/ld/symkind.go @@ -105,6 +105,7 @@ const ( SDWARFSECT SDWARFINFO SDWARFRANGE + SDWARFLOC SSUB = SymKind(1 << 8) SMASK = SymKind(SSUB - 1) SHIDDEN = SymKind(1 << 9) @@ -124,6 +125,7 @@ var abiSymKindToSymKind = [...]SymKind{ STLSBSS, SDWARFINFO, SDWARFRANGE, + SDWARFLOC, } // readOnly are the symbol kinds that form read-only sections. In some diff --git a/src/cmd/link/internal/ld/symkind_string.go b/src/cmd/link/internal/ld/symkind_string.go index 2178b50c36..87da3c40bb 100644 --- a/src/cmd/link/internal/ld/symkind_string.go +++ b/src/cmd/link/internal/ld/symkind_string.go @@ -4,9 +4,9 @@ package ld import "fmt" -const _SymKind_name = "SxxxSTEXTSELFRXSECTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSMACHOPLTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASBSSSNOPTRBSSSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILESFILEPATHSCONSTSDYNIMPORTSHOSTOBJSDWARFSECTSDWARFINFOSDWARFRANGE" +const _SymKind_name = "SxxxSTEXTSELFRXSECTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSMACHOPLTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASBSSSNOPTRBSSSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILESFILEPATHSCONSTSDYNIMPORTSHOSTOBJSDWARFSECTSDWARFINFOSDWARFRANGESDWARFLOC" -var _SymKind_index = [...]uint16{0, 4, 9, 19, 24, 31, 40, 47, 54, 61, 69, 79, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 214, 220, 229, 237, 244, 254, 262, 267, 271, 280, 287, 292, 304, 316, 333, 350, 355, 364, 370, 380, 388, 398, 408, 419} +var _SymKind_index = [...]uint16{0, 4, 9, 19, 24, 31, 40, 47, 54, 61, 69, 79, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 214, 220, 229, 237, 244, 254, 262, 267, 271, 280, 287, 292, 304, 316, 333, 350, 355, 364, 370, 380, 388, 398, 408, 419, 428} func (i SymKind) String() string { if i < 0 || i >= SymKind(len(_SymKind_index)-1) {