aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeschi Kreinick <heschi@google.com>2017-07-21 18:30:19 -0400
committerHeschi Kreinick <heschi@google.com>2017-07-27 20:19:44 +0000
commit4c54a047c6ea88dd77416a3b878f6935165f6129 (patch)
tree8b680905cd4b42a16861b80b5f9f32c203315d51
parentcd702b171c90be4b410d19bd93d5ea2899eaa809 (diff)
downloadgo-dev.debug.tar.gz
go-dev.debug.zip
[dev.debug] cmd/compile: better DWARF with optimizations ondev.debug
Debuggers use DWARF information to find local variables on the stack and in registers. Prior to this CL, the DWARF information for functions claimed that all variables were on the stack at all times. That's incorrect when optimizations are enabled, and results in debuggers showing data that is out of date or complete gibberish. After this CL, the compiler is capable of representing variable locations more accurately, and attempts to do so. Due to limitations of the SSA backend, it's not possible to be completely correct. There are a number of problems in the current design. One of the easier to understand is that variable names currently must be attached to an SSA value, but not all assignments in the source code actually result in machine code. For example: type myint int var a int b := myint(int) and b := (*uint64)(unsafe.Pointer(a)) don't generate machine code because the underlying representation is the same, so the correct value of b will not be set when the user would expect. Generating the more precise debug information is behind a flag, dwarflocationlists. Because of the issues described above, setting the flag may not make the debugging experience much better, and may actually make it worse in cases where the variable actually is on the stack and the more complicated analysis doesn't realize it. A number of changes are included: - Add a new pseudo-instruction, RegKill, which indicates that the value in the register has been clobbered. - Adjust regalloc to emit RegKills in the right places. Significantly, this means that phis are mixed with StoreReg and RegKills after regalloc. - Track variable decomposition in ssa.LocalSlots. - After the SSA backend is done, analyze the result and build location lists for each LocalSlot. - After assembly is done, update the location lists with the assembled PC offsets, recompose variables, and build DWARF location lists. Emit the list as a new linker symbol, one per function. - In the linker, aggregate the location lists into a .debug_loc section. TODO: - currently disabled for non-X86/AMD64 because there are no data tables. go build -toolexec 'toolstash -cmp' -a std succeeds. With -dwarflocationlists false: before: f02812195637909ff675782c0b46836a8ff01976 after: 06f61e8112a42ac34fb80e0c818b3cdb84a5e7ec benchstat -geomean /tmp/220352263 /tmp/621364410 completed 15 of 15, estimated time remaining 0s (eta 3:52PM) name old time/op new time/op delta Template 199ms ± 3% 198ms ± 2% ~ (p=0.400 n=15+14) Unicode 96.6ms ± 5% 96.4ms ± 5% ~ (p=0.838 n=15+15) GoTypes 653ms ± 2% 647ms ± 2% ~ (p=0.102 n=15+14) Flate 133ms ± 6% 129ms ± 3% -2.62% (p=0.041 n=15+15) GoParser 164ms ± 5% 159ms ± 3% -3.05% (p=0.000 n=15+15) Reflect 428ms ± 4% 422ms ± 3% ~ (p=0.156 n=15+13) Tar 123ms ±10% 124ms ± 8% ~ (p=0.461 n=15+15) XML 228ms ± 3% 224ms ± 3% -1.57% (p=0.045 n=15+15) [Geo mean] 206ms 377ms +82.86% name old user-time/op new user-time/op delta Template 292ms ±10% 301ms ±12% ~ (p=0.189 n=15+15) Unicode 166ms ±37% 158ms ±14% ~ (p=0.418 n=15+14) GoTypes 962ms ± 6% 963ms ± 7% ~ (p=0.976 n=15+15) Flate 207ms ±19% 200ms ±14% ~ (p=0.345 n=14+15) GoParser 246ms ±22% 240ms ±15% ~ (p=0.587 n=15+15) Reflect 611ms ±13% 587ms ±14% ~ (p=0.085 n=15+13) Tar 211ms ±12% 217ms ±14% ~ (p=0.355 n=14+15) XML 335ms ±15% 320ms ±18% ~ (p=0.169 n=15+15) [Geo mean] 317ms 583ms +83.72% name old alloc/op new alloc/op delta Template 40.2MB ± 0% 40.2MB ± 0% -0.15% (p=0.000 n=14+15) Unicode 29.2MB ± 0% 29.3MB ± 0% ~ (p=0.624 n=15+15) GoTypes 114MB ± 0% 114MB ± 0% -0.15% (p=0.000 n=15+14) Flate 25.7MB ± 0% 25.6MB ± 0% -0.18% (p=0.000 n=13+15) GoParser 32.2MB ± 0% 32.2MB ± 0% -0.14% (p=0.003 n=15+15) Reflect 77.8MB ± 0% 77.9MB ± 0% ~ (p=0.061 n=15+15) Tar 27.1MB ± 0% 27.0MB ± 0% -0.11% (p=0.029 n=15+15) XML 42.7MB ± 0% 42.5MB ± 0% -0.29% (p=0.000 n=15+15) [Geo mean] 42.1MB 75.0MB +78.05% name old allocs/op new allocs/op delta Template 402k ± 1% 398k ± 0% -0.91% (p=0.000 n=15+15) Unicode 344k ± 1% 344k ± 0% ~ (p=0.715 n=15+14) GoTypes 1.18M ± 0% 1.17M ± 0% -0.91% (p=0.000 n=15+14) Flate 243k ± 0% 240k ± 1% -1.05% (p=0.000 n=13+15) GoParser 327k ± 1% 324k ± 1% -0.96% (p=0.000 n=15+15) Reflect 984k ± 1% 982k ± 0% ~ (p=0.050 n=15+15) Tar 261k ± 1% 259k ± 1% -0.77% (p=0.000 n=15+15) XML 411k ± 0% 404k ± 1% -1.55% (p=0.000 n=15+15) [Geo mean] 439k 755k +72.01% name old text-bytes new text-bytes delta HelloSize 694kB ± 0% 694kB ± 0% -0.00% (p=0.000 n=15+15) name old data-bytes new data-bytes delta HelloSize 5.55kB ± 0% 5.55kB ± 0% ~ (all equal) name old bss-bytes new bss-bytes delta HelloSize 133kB ± 0% 133kB ± 0% ~ (all equal) name old exe-bytes new exe-bytes delta HelloSize 1.04MB ± 0% 1.04MB ± 0% ~ (all equal) Change-Id: I991fc553ef175db46bb23b2128317bbd48de70d8 Reviewed-on: https://go-review.googlesource.com/41770 Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
-rw-r--r--src/cmd/compile/fmt_test.go15
-rw-r--r--src/cmd/compile/internal/gc/main.go8
-rw-r--r--src/cmd/compile/internal/gc/pgen.go353
-rw-r--r--src/cmd/compile/internal/gc/sizeof_test.go2
-rw-r--r--src/cmd/compile/internal/gc/ssa.go28
-rw-r--r--src/cmd/compile/internal/gc/syntax.go2
-rw-r--r--src/cmd/compile/internal/ssa/cache.go22
-rw-r--r--src/cmd/compile/internal/ssa/debug.go559
-rw-r--r--src/cmd/compile/internal/ssa/gen/genericOps.go1
-rw-r--r--src/cmd/compile/internal/ssa/html.go13
-rw-r--r--src/cmd/compile/internal/ssa/location.go6
-rw-r--r--src/cmd/compile/internal/ssa/opGen.go6
-rw-r--r--src/cmd/compile/internal/ssa/regalloc.go94
-rw-r--r--src/cmd/compile/internal/ssa/value.go15
-rw-r--r--src/cmd/internal/dwarf/dwarf.go130
-rw-r--r--src/cmd/internal/obj/link.go53
-rw-r--r--src/cmd/internal/obj/objfile.go17
-rw-r--r--src/cmd/internal/obj/plist.go18
-rw-r--r--src/cmd/internal/obj/x86/a.out.go117
-rw-r--r--src/cmd/internal/obj/x86/obj6.go39
-rw-r--r--src/cmd/internal/objabi/symkind.go1
-rw-r--r--src/cmd/internal/objabi/symkind_string.go4
-rw-r--r--src/cmd/link/internal/ld/data.go2
-rw-r--r--src/cmd/link/internal/ld/dwarf.go31
-rw-r--r--src/cmd/link/internal/ld/elf.go7
-rw-r--r--src/cmd/link/internal/ld/symkind.go2
-rw-r--r--src/cmd/link/internal/ld/symkind_string.go4
27 files changed, 1391 insertions, 158 deletions
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 <key>[=<value>]
@@ -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,47 +304,31 @@ 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
-
- abbrev = dwarf.DW_ABRV_AUTO
- if Ctxt.FixedFrameSize() == 0 {
- offs -= int64(Widthptr)
- }
- if objabi.Framepointer_enabled(objabi.GOOS, objabi.GOARCH) {
- offs -= int64(Widthptr)
- }
-
case PPARAM, PPARAMOUT:
name = obj.NAME_PARAM
-
- abbrev = dwarf.DW_ABRV_PARAM
- offs += Ctxt.FixedFrameSize()
-
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),
@@ -351,32 +336,336 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope {
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)
+ }
+ if objabi.Framepointer_enabled(objabi.GOOS, objabi.GOARCH) {
+ offs -= int64(Widthptr)
+ }
+
+ case PPARAM, PPARAMOUT:
+ abbrev = dwarf.DW_ABRV_PARAM
+ offs += Ctxt.FixedFrameSize()
+ default:
+ Fatalf("createSimpleVars unexpected type %v for node %v", n.Class(), n)
+ }
- 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)
+ // 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})
}
- return assembleScopes(fnsym, fn, dwarfVars, varScopes)
+ // 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<<uint8(reg)) != 0 {
+ dpiece.RegNum = Ctxt.Arch.DWARFRegisters[debugInfo.Registers[reg].ObjNum()]
+ }
+ }
+ }
+ dloc.Pieces = append(dloc.Pieces, dpiece)
+ }
+ if present == 0 {
+ if Debug_locationlist != 0 {
+ Ctxt.Logf(" -> 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<<uint8(reg)) == 0 {
+ continue
+ }
+ if registers != nil {
+ regnames = append(regnames, registers[reg].Name())
+ } else {
+ regnames = append(regnames, fmt.Sprintf("reg%v", reg))
+ }
+ }
+ loc += strings.Join(regnames, ",")
+ pos := func(v *Value, p *obj.Prog, pc int64) string {
+ if v == nil {
+ return "?"
+ }
+ if p == nil {
+ return fmt.Sprintf("v%v", v.ID)
+ }
+ return fmt.Sprintf("v%v/%x", v.ID, pc)
+ }
+ surv := ""
+ if v.survivedBlock {
+ surv = "+"
+ }
+ return fmt.Sprintf("%v-%v%s@%s", pos(v.Start, v.StartProg, v.StartPC), pos(v.End, v.EndProg, v.EndPC), surv, loc)
+}
+
+// unexpected is used to indicate an inconsistency or bug in the debug info
+// generation process. These are not fixable by users. At time of writing,
+// changing this to a Fprintf(os.Stderr) and running make.bash generates
+// thousands of warnings.
+func (s *debugState) unexpected(v *Value, msg string, args ...interface{}) {
+ s.f.Logf("unexpected at "+fmt.Sprint(v.ID)+":"+msg, args...)
+}
+
+func (s *debugState) logf(msg string, args ...interface{}) {
+ s.f.Logf(msg, args...)
+}
+
+type debugState struct {
+ loggingEnabled bool
+ slots []*LocalSlot
+ f *Func
+ cache *Cache
+ numRegisters int
+
+ // working storage for BuildFuncDebug, reused between blocks.
+ registerContents [][]SlotID
+}
+
+// BuildFuncDebug returns debug information for f.
+// f must be fully processed, so that each Value is where it will be when
+// machine code is emitted.
+func BuildFuncDebug(f *Func, loggingEnabled bool) *FuncDebug {
+ if f.RegAlloc == nil {
+ f.Fatalf("BuildFuncDebug on func %v that has not been fully processed", f)
+ }
+ state := &debugState{
+ loggingEnabled: loggingEnabled,
+ slots: make([]*LocalSlot, len(f.Names)),
+ cache: f.Cache,
+ f: f,
+ numRegisters: len(f.Config.registers),
+ registerContents: make([][]SlotID, len(f.Config.registers)),
+ }
+ // TODO: consider storing this in Cache and reusing across functions.
+ valueNames := make([][]SlotID, f.NumValues())
+
+ for i, slot := range f.Names {
+ slot := slot
+ state.slots[i] = &slot
+
+ if isSynthetic(&slot) {
+ continue
+ }
+ for _, value := range f.NamedValues[slot] {
+ valueNames[value.ID] = append(valueNames[value.ID], SlotID(i))
+ }
+ }
+
+ if state.loggingEnabled {
+ var names []string
+ for i, name := range f.Names {
+ names = append(names, fmt.Sprintf("%v = %v", i, name))
+ }
+ state.logf("Name table: %v\n", strings.Join(names, ", "))
+ }
+
+ // Build up block states, starting with the first block, then
+ // processing blocks once their predecessors have been processed.
+
+ // TODO: use a reverse post-order traversal instead of the work queue.
+
+ // Location list entries for each block.
+ blockLocs := make([]*FuncDebug, f.NumBlocks())
+
+ // Work queue of blocks to visit. Some of them may already be processed.
+ work := []*Block{f.Entry}
+
+ for len(work) > 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<<uint8(reg)) != 0 {
+ state.registerContents[reg] = append(state.registerContents[reg], SlotID(slot))
+ }
+ }
+ }
+ return locs
+}
+
+// processValue updates locs and state.registerContents to reflect v, a value with
+// the names in vSlots and homed in vReg.
+func (state *debugState) processValue(locs *FuncDebug, v *Value, vSlots []SlotID, vReg *Register) {
+ switch {
+ case v.Op == OpRegKill:
+ if state.loggingEnabled {
+ existingSlots := make([]bool, len(state.slots))
+ for _, slot := range state.registerContents[vReg.num] {
+ existingSlots[slot] = true
+ }
+ for _, slot := range vSlots {
+ if existingSlots[slot] {
+ existingSlots[slot] = false
+ } else {
+ state.unexpected(v, "regkill of unassociated name %v\n", state.slots[slot])
+ }
+ }
+ for slot, live := range existingSlots {
+ if live {
+ state.unexpected(v, "leftover register name: %v\n", state.slots[slot])
+ }
+ }
+ }
+ state.registerContents[vReg.num] = nil
+
+ for _, slot := range vSlots {
+ last := locs.lastLoc(slot)
+ if last == nil {
+ state.unexpected(v, "regkill of already dead %v, %+v\n", vReg, state.slots[slot])
+ continue
+ }
+ if state.loggingEnabled {
+ state.logf("at %v: %v regkilled out of %v\n", v.ID, state.slots[slot], vReg.Name())
+ }
+ if last.End != nil {
+ state.unexpected(v, "regkill of dead slot, died at %v\n", last.End)
+ }
+ last.End = v
+
+ regs := last.Registers &^ (1 << uint8(vReg.num))
+ if !last.OnStack && regs == 0 {
+ continue
+ }
+ loc := state.cache.NewVarLoc()
+ loc.Start = v
+ loc.OnStack = last.OnStack
+ loc.Registers = regs
+ locs.append(slot, loc)
+ }
+ case v.Op == OpArg:
+ for _, slot := range vSlots {
+ if state.loggingEnabled {
+ state.logf("at %v: %v now on stack from arg\n", v.ID, state.slots[slot])
+ }
+ loc := state.cache.NewVarLoc()
+ loc.Start = v
+ loc.OnStack = true
+ locs.append(slot, loc)
+ }
+
+ case v.Op == OpStoreReg:
+ for _, slot := range vSlots {
+ if state.loggingEnabled {
+ state.logf("at %v: %v spilled to stack\n", v.ID, state.slots[slot])
+ }
+ last := locs.lastLoc(slot)
+ if last == nil {
+ state.unexpected(v, "spill of unnamed register %v\n", vReg)
+ break
+ }
+ last.End = v
+ loc := state.cache.NewVarLoc()
+ loc.Start = v
+ loc.OnStack = true
+ loc.Registers = last.Registers
+ locs.append(slot, loc)
+ }
+
+ case vReg != nil:
+ if state.loggingEnabled {
+ newSlots := make([]bool, len(state.slots))
+ for _, slot := range vSlots {
+ newSlots[slot] = true
+ }
+
+ for _, slot := range state.registerContents[vReg.num] {
+ if !newSlots[slot] {
+ state.unexpected(v, "%v clobbered\n", state.slots[slot])
+ }
+ }
+ }
+
+ for _, slot := range vSlots {
+ if state.loggingEnabled {
+ state.logf("at %v: %v now in %v\n", v.ID, state.slots[slot], vReg.Name())
+ }
+ last := locs.lastLoc(slot)
+ if last != nil && last.End == nil {
+ last.End = v
+ }
+ state.registerContents[vReg.num] = append(state.registerContents[vReg.num], slot)
+ loc := state.cache.NewVarLoc()
+ loc.Start = v
+ if last != nil {
+ loc.OnStack = last.OnStack
+ loc.Registers = last.Registers
+ }
+ loc.Registers |= 1 << uint8(vReg.num)
+ locs.append(slot, loc)
+ }
+ default:
+ state.unexpected(v, "named value with no reg\n")
+ }
+
+}
diff --git a/src/cmd/compile/internal/ssa/gen/genericOps.go b/src/cmd/compile/internal/ssa/gen/genericOps.go
index d962e4a193..63bb9a8b61 100644
--- a/src/cmd/compile/internal/ssa/gen/genericOps.go
+++ b/src/cmd/compile/internal/ssa/gen/genericOps.go
@@ -417,6 +417,7 @@ var genericOps = []opData{
{name: "VarKill", argLength: 1, aux: "Sym", symEffect: "None"}, // aux is a *gc.Node of a variable that is known to be dead. arg0=mem, returns mem
{name: "VarLive", argLength: 1, aux: "Sym", symEffect: "None"}, // aux is a *gc.Node of a variable that must be kept live. arg0=mem, returns mem
{name: "KeepAlive", argLength: 2, typ: "Mem"}, // arg[0] is a value that must be kept alive until this mark. arg[1]=mem, returns mem
+ {name: "RegKill"}, // regalloc has determined that the value in this register is dead
// Ops for breaking 64-bit operations on 32-bit architectures
{name: "Int64Make", argLength: 2, typ: "UInt64"}, // arg0=hi, arg1=lo
diff --git a/src/cmd/compile/internal/ssa/html.go b/src/cmd/compile/internal/ssa/html.go
index d554907beb..6efe93e74f 100644
--- a/src/cmd/compile/internal/ssa/html.go
+++ b/src/cmd/compile/internal/ssa/html.go
@@ -11,6 +11,7 @@ import (
"html"
"io"
"os"
+ "strings"
)
type HTMLWriter struct {
@@ -362,6 +363,18 @@ func (v *Value) LongHTML() string {
if int(v.ID) < len(r) && r[v.ID] != nil {
s += " : " + html.EscapeString(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, ", ") + ")"
+ }
s += "</span>"
return s
}
diff --git a/src/cmd/compile/internal/ssa/location.go b/src/cmd/compile/internal/ssa/location.go
index 70afa47e9d..dc01bd4235 100644
--- a/src/cmd/compile/internal/ssa/location.go
+++ b/src/cmd/compile/internal/ssa/location.go
@@ -26,6 +26,12 @@ func (r *Register) Name() string {
return r.name
}
+// ObjNum returns the register number from cmd/internal/obj/$ARCH that
+// corresponds to this register.
+func (r *Register) ObjNum() int16 {
+ return r.objNum
+}
+
// A LocalSlot is a location in the stack frame, which identifies and stores
// part or all of a PPARAM, PPARAMOUT, or PAUTO ONAME node.
// It can represent a whole variable, part of a larger stack slot, or part of a
diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go
index ae2dd5f550..87a2ea0656 100644
--- a/src/cmd/compile/internal/ssa/opGen.go
+++ b/src/cmd/compile/internal/ssa/opGen.go
@@ -1897,6 +1897,7 @@ const (
OpVarKill
OpVarLive
OpKeepAlive
+ OpRegKill
OpInt64Make
OpInt64Hi
OpInt64Lo
@@ -22498,6 +22499,11 @@ var opcodeTable = [...]opInfo{
generic: true,
},
{
+ name: "RegKill",
+ argLen: 0,
+ generic: true,
+ },
+ {
name: "Int64Make",
argLen: 2,
generic: true,
diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go
index e297e6bce7..0abaeaeeb5 100644
--- a/src/cmd/compile/internal/ssa/regalloc.go
+++ b/src/cmd/compile/internal/ssa/regalloc.go
@@ -242,6 +242,9 @@ type regAllocState struct {
// current state of each (preregalloc) Value
values []valState
+ // names associated with each Value
+ valueNames [][]LocalSlot
+
// ID of SP, SB values
sp, sb ID
@@ -300,6 +303,13 @@ type startReg struct {
// freeReg frees up register r. Any current user of r is kicked out.
func (s *regAllocState) freeReg(r register) {
+ s.freeOrResetReg(r, false)
+}
+
+// freeOrResetReg frees up register r. Any current user of r is kicked out.
+// resetting indicates that the operation is only for bookkeeping,
+// e.g. when clearing out state upon entry to a new block.
+func (s *regAllocState) freeOrResetReg(r register, resetting bool) {
v := s.regs[r].v
if v == nil {
s.f.Fatalf("tried to free an already free register %d\n", r)
@@ -309,6 +319,16 @@ func (s *regAllocState) freeReg(r register) {
if s.f.pass.debug > 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 <type> [aux] args [: reg]
+// long form print. v# = opcode <type> [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, 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)
}
- 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)
+ // 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) {