[dev.debug] cmd/compile: better DWARF with optimizations on

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>
diff --git a/src/cmd/compile/fmt_test.go b/src/cmd/compile/fmt_test.go
index 59de326..2052a42 100644
--- a/src/cmd/compile/fmt_test.go
+++ b/src/cmd/compile/fmt_test.go
@@ -571,9 +571,16 @@
 	"*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 @@
 	"*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 @@
 	"[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 @@
 	"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 @@
 	"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 2b61564..a1f4767 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -44,6 +44,7 @@
 	Debug_vlog         bool
 	Debug_wb           int
 	Debug_pctab        string
+	Debug_locationlist int
 )
 
 // Debug arguments.
@@ -69,6 +70,7 @@
 	{"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 @@
 	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 @@
 	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 @@
 		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 d301ae1..542fd43 100644
--- a/src/cmd/compile/internal/gc/pgen.go
+++ b/src/cmd/compile/internal/gc/pgen.go
@@ -13,6 +13,7 @@
 	"cmd/internal/src"
 	"cmd/internal/sys"
 	"fmt"
+	"math"
 	"math/rand"
 	"sort"
 	"sync"
@@ -303,29 +304,77 @@
 
 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 @@
 			}
 
 		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<<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 1ca0a61..bd4453f 100644
--- a/src/cmd/compile/internal/gc/sizeof_test.go
+++ b/src/cmd/compile/internal/gc/sizeof_test.go
@@ -22,7 +22,7 @@
 		_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 f8aefaa..c769efe 100644
--- a/src/cmd/compile/internal/gc/ssa.go
+++ b/src/cmd/compile/internal/gc/ssa.go
@@ -4384,6 +4384,7 @@
 	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 @@
 
 	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 @@
 				}
 			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 @@
 		}
 	}
 
+	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 0fd146b..32ae6f2 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 @@
 	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 f1018da..8434084 100644
--- a/src/cmd/compile/internal/ssa/cache.go
+++ b/src/cmd/compile/internal/ssa/cache.go
@@ -14,6 +14,11 @@
 	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 @@
 	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 0000000..55db45b
--- /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 d962e4a..63bb9a8 100644
--- a/src/cmd/compile/internal/ssa/gen/genericOps.go
+++ b/src/cmd/compile/internal/ssa/gen/genericOps.go
@@ -417,6 +417,7 @@
 	{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 d554907..6efe93e 100644
--- a/src/cmd/compile/internal/ssa/html.go
+++ b/src/cmd/compile/internal/ssa/html.go
@@ -11,6 +11,7 @@
 	"html"
 	"io"
 	"os"
+	"strings"
 )
 
 type HTMLWriter struct {
@@ -362,6 +363,18 @@
 	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 70afa47..dc01bd4 100644
--- a/src/cmd/compile/internal/ssa/location.go
+++ b/src/cmd/compile/internal/ssa/location.go
@@ -26,6 +26,12 @@
 	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 ae2dd5f..87a2ea0 100644
--- a/src/cmd/compile/internal/ssa/opGen.go
+++ b/src/cmd/compile/internal/ssa/opGen.go
@@ -1897,6 +1897,7 @@
 	OpVarKill
 	OpVarLive
 	OpKeepAlive
+	OpRegKill
 	OpInt64Make
 	OpInt64Hi
 	OpInt64Lo
@@ -22498,6 +22499,11 @@
 		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 e297e6b..0abaeae 100644
--- a/src/cmd/compile/internal/ssa/regalloc.go
+++ b/src/cmd/compile/internal/ssa/regalloc.go
@@ -242,6 +242,9 @@
 	// 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 @@
 
 // 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 @@
 	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 @@
 	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 @@
 
 // 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 @@
 	}
 
 	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 @@
 			// 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 @@
 				}
 			}
 
+			// 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 @@
 			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 @@
 	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 @@
 	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 @@
 	}
 	// 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 @@
 		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 @@
 		}
 	}
 	_, 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 @@
 		} 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 @@
 				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 @@
 // 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 @@
 				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 7edc71b..6df5351 100644
--- a/src/cmd/compile/internal/ssa/value.go
+++ b/src/cmd/compile/internal/ssa/value.go
@@ -10,6 +10,7 @@
 	"cmd/internal/src"
 	"fmt"
 	"math"
+	"strings"
 )
 
 // A Value represents a value in the SSA representation of the program.
@@ -98,7 +99,7 @@
 	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 @@
 	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 b58052b..2b03425 100644
--- a/src/cmd/internal/dwarf/dwarf.go
+++ b/src/cmd/internal/dwarf/dwarf.go
@@ -15,6 +15,9 @@
 // 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 @@
 	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 @@
 )
 
 // 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 @@
 
 // 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 @@
 		}
 
 		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 @@
 			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 d49bc8c..68e1b70 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -330,7 +330,8 @@
 	Autom  []*Auto
 	Pcln   Pcln
 
-	dwarfSym       *LSym
+	dwarfInfoSym   *LSym
+	dwarfLocSym    *LSym
 	dwarfRangesSym *LSym
 
 	GCArgs   LSym
@@ -476,25 +477,26 @@
 // 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 @@
 // 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 e309c5f..539d013 100644
--- a/src/cmd/internal/obj/objfile.go
+++ b/src/cmd/internal/obj/objfile.go
@@ -465,15 +465,18 @@
 }
 
 // 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 @@
 // 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 861da88..1bb05ae 100644
--- a/src/cmd/internal/obj/plist.go
+++ b/src/cmd/internal/obj/plist.go
@@ -136,13 +136,17 @@
 	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 04f9ef6..92d358b 100644
--- a/src/cmd/internal/obj/x86/a.out.go
+++ b/src/cmd/internal/obj/x86/a.out.go
@@ -1006,3 +1006,120 @@
 	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 d34f0ae..27873e0 100644
--- a/src/cmd/internal/obj/x86/obj6.go
+++ b/src/cmd/internal/obj/x86/obj6.go
@@ -1231,28 +1231,31 @@
 }
 
 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 b037e9e..ac91824 100644
--- a/src/cmd/internal/objabi/symkind.go
+++ b/src/cmd/internal/objabi/symkind.go
@@ -57,4 +57,5 @@
 	// Debugging data
 	SDWARFINFO
 	SDWARFRANGE
+	SDWARFLOC
 )
diff --git a/src/cmd/internal/objabi/symkind_string.go b/src/cmd/internal/objabi/symkind_string.go
index 5123dc7..3064c8e 100644
--- a/src/cmd/internal/objabi/symkind_string.go
+++ b/src/cmd/internal/objabi/symkind_string.go
@@ -4,9 +4,9 @@
 
 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 bf219f7..1d053d2 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -1862,6 +1862,8 @@
 			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 9b11fdc..b6fb1bb 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -1579,10 +1579,35 @@
 	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 @@
 	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 @@
 	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 @@
 	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 0fc947f..78f8d6e 100644
--- a/src/cmd/link/internal/ld/elf.go
+++ b/src/cmd/link/internal/ld/elf.go
@@ -1808,7 +1808,7 @@
 				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 @@
 			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 c057f6c..5ac04cf 100644
--- a/src/cmd/link/internal/ld/symkind.go
+++ b/src/cmd/link/internal/ld/symkind.go
@@ -105,6 +105,7 @@
 	SDWARFSECT
 	SDWARFINFO
 	SDWARFRANGE
+	SDWARFLOC
 	SSUB       = SymKind(1 << 8)
 	SMASK      = SymKind(SSUB - 1)
 	SHIDDEN    = SymKind(1 << 9)
@@ -124,6 +125,7 @@
 	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 2178b50..87da3c4 100644
--- a/src/cmd/link/internal/ld/symkind_string.go
+++ b/src/cmd/link/internal/ld/symkind_string.go
@@ -4,9 +4,9 @@
 
 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) {