From 045f605ea15117a9264dd766998221cda87fa40a Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Fri, 21 Jul 2017 17:55:41 -0400 Subject: [PATCH 1/7] [dev.debug] cmd/compile: rename dwarf.Var.Offset to StackOffset After we track decomposition, offset could mean stack offset or offset in recomposed variable. Disambiguate. Change-Id: I4d810b8c0dcac7a4ec25ac1e52898f55477025df Reviewed-on: https://go-review.googlesource.com/50875 Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/gc/pgen.go | 8 ++++---- src/cmd/compile/internal/gc/scope.go | 2 +- src/cmd/internal/dwarf/dwarf.go | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index 66e4a10ee8..d301ae19c8 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -358,10 +358,10 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope { typename := dwarf.InfoPrefix + gotype.Name[len("type."):] dwarfVars = append(dwarfVars, &dwarf.Var{ - Name: n.Sym.Name, - Abbrev: abbrev, - Offset: int32(offs), - Type: Ctxt.Lookup(typename), + Name: n.Sym.Name, + Abbrev: abbrev, + StackOffset: int32(offs), + Type: Ctxt.Lookup(typename), }) var scope ScopeID diff --git a/src/cmd/compile/internal/gc/scope.go b/src/cmd/compile/internal/gc/scope.go index b0bc7f6908..ebdaa19994 100644 --- a/src/cmd/compile/internal/gc/scope.go +++ b/src/cmd/compile/internal/gc/scope.go @@ -168,7 +168,7 @@ func (v varsByScopeAndOffset) Less(i, j int) bool { if v.scopes[i] != v.scopes[j] { return v.scopes[i] < v.scopes[j] } - return v.vars[i].Offset < v.vars[j].Offset + return v.vars[i].StackOffset < v.vars[j].StackOffset } func (v varsByScopeAndOffset) Swap(i, j int) { diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index a617c389f9..325836119f 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -25,11 +25,11 @@ type Sym interface { // 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 - Offset int32 - Scope int32 - Type Sym + Name string + Abbrev int // Either DW_ABRV_AUTO or DW_ABRV_PARAM + StackOffset int32 + Scope int32 + Type Sym } // A Scope represents a lexical scope. All variables declared within a @@ -749,9 +749,9 @@ func putvar(ctxt Context, s Sym, v *Var, encbuf []byte) { 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.Offset != 0 { + if v.StackOffset != 0 { loc = append(loc, DW_OP_consts) - loc = AppendSleb128(loc, int64(v.Offset)) + loc = AppendSleb128(loc, int64(v.StackOffset)) loc = append(loc, DW_OP_plus) } putattr(ctxt, s, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) @@ -759,9 +759,9 @@ func putvar(ctxt Context, s Sym, v *Var, encbuf []byte) { } // VarsByOffset attaches the methods of sort.Interface to []*Var, -// sorting in increasing Offset. +// sorting in increasing StackOffset. type VarsByOffset []*Var func (s VarsByOffset) Len() int { return len(s) } -func (s VarsByOffset) Less(i, j int) bool { return s[i].Offset < s[j].Offset } +func (s VarsByOffset) Less(i, j int) bool { return s[i].StackOffset < s[j].StackOffset } func (s VarsByOffset) Swap(i, j int) { s[i], s[j] = s[j], s[i] } From c1c08a13e7ed219148f18ce8e4aaed5202eed409 Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Fri, 21 Jul 2017 18:00:22 -0400 Subject: [PATCH 2/7] [dev.debug] cmd/compile: rename some locals in genssa When we start tracking the mapping from Value to Prog, valueProgs will be confusing. Disambiguate. Change-Id: Ib3b302fedb7eb0ff1bde789d70a11656d82f0897 Reviewed-on: https://go-review.googlesource.com/50876 Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/gc/ssa.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 1497c5c2f5..7f179847fa 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -4382,14 +4382,14 @@ func genssa(f *ssa.Func, pp *Progs) { // Remember where each block starts. s.bstart = make([]*obj.Prog, f.NumBlocks()) s.pp = pp - var valueProgs map[*obj.Prog]*ssa.Value - var blockProgs map[*obj.Prog]*ssa.Block + var progToValue map[*obj.Prog]*ssa.Value + var progToBlock map[*obj.Prog]*ssa.Block var logProgs = e.log if logProgs { - valueProgs = make(map[*obj.Prog]*ssa.Value, f.NumValues()) - blockProgs = make(map[*obj.Prog]*ssa.Block, f.NumBlocks()) + progToValue = make(map[*obj.Prog]*ssa.Value, f.NumValues()) + progToBlock = make(map[*obj.Prog]*ssa.Block, f.NumBlocks()) f.Logf("genssa %s\n", f.Name) - blockProgs[s.pp.next] = f.Blocks[0] + progToBlock[s.pp.next] = f.Blocks[0] } if thearch.Use387 { @@ -4446,7 +4446,7 @@ func genssa(f *ssa.Func, pp *Progs) { if logProgs { for ; x != s.pp.next; x = x.Link { - valueProgs[x] = v + progToValue[x] = v } } } @@ -4464,7 +4464,7 @@ func genssa(f *ssa.Func, pp *Progs) { thearch.SSAGenBlock(&s, b, next) if logProgs { for ; x != s.pp.next; x = x.Link { - blockProgs[x] = b + progToBlock[x] = b } } } @@ -4477,9 +4477,9 @@ func genssa(f *ssa.Func, pp *Progs) { if logProgs { for p := pp.Text; p != nil; p = p.Link { var s string - if v, ok := valueProgs[p]; ok { + if v, ok := progToValue[p]; ok { s = v.String() - } else if b, ok := blockProgs[p]; ok { + } else if b, ok := progToBlock[p]; ok { s = b.String() } else { s = " " // most value and branch strings are 2-3 characters long @@ -4497,9 +4497,9 @@ func genssa(f *ssa.Func, pp *Progs) { buf.WriteString("
") for p := pp.Text; p != nil; p = p.Link { buf.WriteString("
") - if v, ok := valueProgs[p]; ok { + if v, ok := progToValue[p]; ok { buf.WriteString(v.HTML()) - } else if b, ok := blockProgs[p]; ok { + } else if b, ok := progToBlock[p]; ok { buf.WriteString(b.HTML()) } buf.WriteString("
") From 788aa88cd0c63b75d49a54592e2a467c6183256f Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Thu, 25 May 2017 15:34:05 -0400 Subject: [PATCH 3/7] [dev.debug] cmd/compile: keep float names in the Names list Fix an oversight in decompose that caused floats to be missing from the Names list. Change-Id: I5db9c9498e9a4421742389eb929752fdac873b38 Reviewed-on: https://go-review.googlesource.com/50877 Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/ssa/decompose.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmd/compile/internal/ssa/decompose.go b/src/cmd/compile/internal/ssa/decompose.go index 2b3f16c30c..120f84fdcf 100644 --- a/src/cmd/compile/internal/ssa/decompose.go +++ b/src/cmd/compile/internal/ssa/decompose.go @@ -98,6 +98,7 @@ func decomposeBuiltIn(f *Func) { delete(f.NamedValues, name) case t.IsFloat(): // floats are never decomposed, even ones bigger than RegSize + newNames = append(newNames, name) case t.Size() > f.Config.RegSize: f.Fatalf("undecomposed named type %v %v", name, t) default: From 2d57d94ac314fd32529b1b2a92a086cb2dce0057 Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Fri, 21 Jul 2017 18:28:06 -0400 Subject: [PATCH 4/7] [dev.debug] cmd/compile: track variable decomposition in LocalSlot When the compiler decomposes a user variable, track its origin so that it can be recomposed during DWARF generation. Change-Id: Ia71c7f8e7f4d65f0652f1c97b0dda5d9cad41936 Reviewed-on: https://go-review.googlesource.com/50878 Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/gc/ssa.go | 55 +++++++++++---------- src/cmd/compile/internal/ssa/export_test.go | 22 ++++----- src/cmd/compile/internal/ssa/location.go | 26 ++++++++-- src/cmd/compile/internal/ssa/regalloc.go | 4 +- src/cmd/compile/internal/ssa/sizeof_test.go | 1 + src/cmd/compile/internal/ssa/stackalloc.go | 2 +- 6 files changed, 66 insertions(+), 44 deletions(-) diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 7f179847fa..f8aefaae5e 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -4876,9 +4876,9 @@ func (e *ssafn) SplitString(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot) { lenType := types.Types[TINT] if n.Class() == PAUTO && !n.Addrtaken() { // Split this string up into two separate variables. - p := e.namedAuto(n.Sym.Name+".ptr", ptrType, n.Pos) - l := e.namedAuto(n.Sym.Name+".len", lenType, n.Pos) - return ssa.LocalSlot{N: p, Type: ptrType, Off: 0}, ssa.LocalSlot{N: l, Type: lenType, Off: 0} + p := e.splitSlot(&name, ".ptr", 0, ptrType) + l := e.splitSlot(&name, ".len", ptrType.Size(), lenType) + return p, l } // Return the two parts of the larger variable. return ssa.LocalSlot{N: n, Type: ptrType, Off: name.Off}, ssa.LocalSlot{N: n, Type: lenType, Off: name.Off + int64(Widthptr)} @@ -4893,9 +4893,9 @@ func (e *ssafn) SplitInterface(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot if n.Type.IsEmptyInterface() { f = ".type" } - c := e.namedAuto(n.Sym.Name+f, t, n.Pos) - d := e.namedAuto(n.Sym.Name+".data", t, n.Pos) - return ssa.LocalSlot{N: c, Type: t, Off: 0}, ssa.LocalSlot{N: d, Type: t, Off: 0} + c := e.splitSlot(&name, f, 0, t) + d := e.splitSlot(&name, ".data", t.Size(), t) + return c, d } // Return the two parts of the larger variable. return ssa.LocalSlot{N: n, Type: t, Off: name.Off}, ssa.LocalSlot{N: n, Type: t, Off: name.Off + int64(Widthptr)} @@ -4907,10 +4907,10 @@ func (e *ssafn) SplitSlice(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot, ss lenType := types.Types[TINT] if n.Class() == PAUTO && !n.Addrtaken() { // Split this slice up into three separate variables. - p := e.namedAuto(n.Sym.Name+".ptr", ptrType, n.Pos) - l := e.namedAuto(n.Sym.Name+".len", lenType, n.Pos) - c := e.namedAuto(n.Sym.Name+".cap", lenType, n.Pos) - return ssa.LocalSlot{N: p, Type: ptrType, Off: 0}, ssa.LocalSlot{N: l, Type: lenType, Off: 0}, ssa.LocalSlot{N: c, Type: lenType, Off: 0} + p := e.splitSlot(&name, ".ptr", 0, ptrType) + l := e.splitSlot(&name, ".len", ptrType.Size(), lenType) + c := e.splitSlot(&name, ".cap", ptrType.Size()+lenType.Size(), lenType) + return p, l, c } // Return the three parts of the larger variable. return ssa.LocalSlot{N: n, Type: ptrType, Off: name.Off}, @@ -4929,9 +4929,9 @@ func (e *ssafn) SplitComplex(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot) } if n.Class() == PAUTO && !n.Addrtaken() { // Split this complex up into two separate variables. - c := e.namedAuto(n.Sym.Name+".real", t, n.Pos) - d := e.namedAuto(n.Sym.Name+".imag", t, n.Pos) - return ssa.LocalSlot{N: c, Type: t, Off: 0}, ssa.LocalSlot{N: d, Type: t, Off: 0} + r := e.splitSlot(&name, ".real", 0, t) + i := e.splitSlot(&name, ".imag", t.Size(), t) + return r, i } // Return the two parts of the larger variable. return ssa.LocalSlot{N: n, Type: t, Off: name.Off}, ssa.LocalSlot{N: n, Type: t, Off: name.Off + s} @@ -4947,9 +4947,10 @@ func (e *ssafn) SplitInt64(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot) { } if n.Class() == PAUTO && !n.Addrtaken() { // Split this int64 up into two separate variables. - h := e.namedAuto(n.Sym.Name+".hi", t, n.Pos) - l := e.namedAuto(n.Sym.Name+".lo", types.Types[TUINT32], n.Pos) - return ssa.LocalSlot{N: h, Type: t, Off: 0}, ssa.LocalSlot{N: l, Type: types.Types[TUINT32], Off: 0} + if thearch.LinkArch.ByteOrder == binary.BigEndian { + return e.splitSlot(&name, ".hi", 0, t), e.splitSlot(&name, ".lo", t.Size(), types.Types[TUINT32]) + } + return e.splitSlot(&name, ".hi", t.Size(), t), e.splitSlot(&name, ".lo", 0, types.Types[TUINT32]) } // Return the two parts of the larger variable. if thearch.LinkArch.ByteOrder == binary.BigEndian { @@ -4962,12 +4963,15 @@ func (e *ssafn) SplitStruct(name ssa.LocalSlot, i int) ssa.LocalSlot { n := name.N.(*Node) st := name.Type ft := st.FieldType(i) + var offset int64 + for f := 0; f < i; f++ { + offset += st.FieldType(f).Size() + } if n.Class() == PAUTO && !n.Addrtaken() { // Note: the _ field may appear several times. But // have no fear, identically-named but distinct Autos are // ok, albeit maybe confusing for a debugger. - x := e.namedAuto(n.Sym.Name+"."+st.FieldName(i), ft, n.Pos) - return ssa.LocalSlot{N: x, Type: ft, Off: 0} + return e.splitSlot(&name, "."+st.FieldName(i), offset, ft) } return ssa.LocalSlot{N: n, Type: ft, Off: name.Off + st.FieldOff(i)} } @@ -4980,8 +4984,7 @@ func (e *ssafn) SplitArray(name ssa.LocalSlot) ssa.LocalSlot { } et := at.ElemType() if n.Class() == PAUTO && !n.Addrtaken() { - x := e.namedAuto(n.Sym.Name+"[0]", et, n.Pos) - return ssa.LocalSlot{N: x, Type: et, Off: 0} + return e.splitSlot(&name, "[0]", 0, et) } return ssa.LocalSlot{N: n, Type: et, Off: name.Off} } @@ -4990,16 +4993,14 @@ func (e *ssafn) DerefItab(it *obj.LSym, offset int64) *obj.LSym { return itabsym(it, offset) } -// namedAuto returns a new AUTO variable with the given name and type. -// These are exposed to the debugger. -func (e *ssafn) namedAuto(name string, typ *types.Type, pos src.XPos) ssa.GCNode { - t := typ - s := &types.Sym{Name: name, Pkg: localpkg} +// splitSlot returns a slot representing the data of parent starting at offset. +func (e *ssafn) splitSlot(parent *ssa.LocalSlot, suffix string, offset int64, t *types.Type) ssa.LocalSlot { + s := &types.Sym{Name: parent.N.(*Node).Sym.Name + suffix, Pkg: localpkg} n := new(Node) n.Name = new(Name) n.Op = ONAME - n.Pos = pos + n.Pos = parent.N.(*Node).Pos n.Orig = n s.Def = asTypesNode(n) @@ -5012,7 +5013,7 @@ func (e *ssafn) namedAuto(name string, typ *types.Type, pos src.XPos) ssa.GCNode n.Name.Curfn = e.curfn e.curfn.Func.Dcl = append(e.curfn.Func.Dcl, n) dowidth(t) - return n + return ssa.LocalSlot{N: n, Type: t, Off: 0, SplitOf: parent, SplitOffset: offset} } func (e *ssafn) CanSSA(t *types.Type) bool { diff --git a/src/cmd/compile/internal/ssa/export_test.go b/src/cmd/compile/internal/ssa/export_test.go index 3bb67a951b..54cd96beaa 100644 --- a/src/cmd/compile/internal/ssa/export_test.go +++ b/src/cmd/compile/internal/ssa/export_test.go @@ -82,33 +82,33 @@ func (DummyFrontend) Auto(pos src.XPos, t *types.Type) GCNode { return &DummyAuto{t: t, s: "aDummyAuto"} } func (d DummyFrontend) SplitString(s LocalSlot) (LocalSlot, LocalSlot) { - return LocalSlot{s.N, dummyTypes.BytePtr, s.Off}, LocalSlot{s.N, dummyTypes.Int, s.Off + 8} + return LocalSlot{N: s.N, Type: dummyTypes.BytePtr, Off: s.Off}, LocalSlot{N: s.N, Type: dummyTypes.Int, Off: s.Off + 8} } func (d DummyFrontend) SplitInterface(s LocalSlot) (LocalSlot, LocalSlot) { - return LocalSlot{s.N, dummyTypes.BytePtr, s.Off}, LocalSlot{s.N, dummyTypes.BytePtr, s.Off + 8} + return LocalSlot{N: s.N, Type: dummyTypes.BytePtr, Off: s.Off}, LocalSlot{N: s.N, Type: dummyTypes.BytePtr, Off: s.Off + 8} } func (d DummyFrontend) SplitSlice(s LocalSlot) (LocalSlot, LocalSlot, LocalSlot) { - return LocalSlot{s.N, s.Type.ElemType().PtrTo(), s.Off}, - LocalSlot{s.N, dummyTypes.Int, s.Off + 8}, - LocalSlot{s.N, dummyTypes.Int, s.Off + 16} + return LocalSlot{N: s.N, Type: s.Type.ElemType().PtrTo(), Off: s.Off}, + LocalSlot{N: s.N, Type: dummyTypes.Int, Off: s.Off + 8}, + LocalSlot{N: s.N, Type: dummyTypes.Int, Off: s.Off + 16} } func (d DummyFrontend) SplitComplex(s LocalSlot) (LocalSlot, LocalSlot) { if s.Type.Size() == 16 { - return LocalSlot{s.N, dummyTypes.Float64, s.Off}, LocalSlot{s.N, dummyTypes.Float64, s.Off + 8} + return LocalSlot{N: s.N, Type: dummyTypes.Float64, Off: s.Off}, LocalSlot{N: s.N, Type: dummyTypes.Float64, Off: s.Off + 8} } - return LocalSlot{s.N, dummyTypes.Float32, s.Off}, LocalSlot{s.N, dummyTypes.Float32, s.Off + 4} + return LocalSlot{N: s.N, Type: dummyTypes.Float32, Off: s.Off}, LocalSlot{N: s.N, Type: dummyTypes.Float32, Off: s.Off + 4} } func (d DummyFrontend) SplitInt64(s LocalSlot) (LocalSlot, LocalSlot) { if s.Type.IsSigned() { - return LocalSlot{s.N, dummyTypes.Int32, s.Off + 4}, LocalSlot{s.N, dummyTypes.UInt32, s.Off} + return LocalSlot{N: s.N, Type: dummyTypes.Int32, Off: s.Off + 4}, LocalSlot{N: s.N, Type: dummyTypes.UInt32, Off: s.Off} } - return LocalSlot{s.N, dummyTypes.UInt32, s.Off + 4}, LocalSlot{s.N, dummyTypes.UInt32, s.Off} + return LocalSlot{N: s.N, Type: dummyTypes.UInt32, Off: s.Off + 4}, LocalSlot{N: s.N, Type: dummyTypes.UInt32, Off: s.Off} } func (d DummyFrontend) SplitStruct(s LocalSlot, i int) LocalSlot { - return LocalSlot{s.N, s.Type.FieldType(i), s.Off + s.Type.FieldOff(i)} + return LocalSlot{N: s.N, Type: s.Type.FieldType(i), Off: s.Off + s.Type.FieldOff(i)} } func (d DummyFrontend) SplitArray(s LocalSlot) LocalSlot { - return LocalSlot{s.N, s.Type.ElemType(), s.Off} + return LocalSlot{N: s.N, Type: s.Type.ElemType(), Off: s.Off} } func (DummyFrontend) Line(_ src.XPos) string { return "unknown.go:0" diff --git a/src/cmd/compile/internal/ssa/location.go b/src/cmd/compile/internal/ssa/location.go index 41b48947aa..70afa47e9d 100644 --- a/src/cmd/compile/internal/ssa/location.go +++ b/src/cmd/compile/internal/ssa/location.go @@ -26,12 +26,32 @@ func (r *Register) Name() string { return r.name } -// A LocalSlot is a location in the stack frame. -// It is (possibly a subpiece of) a PPARAM, PPARAMOUT, or PAUTO ONAME node. +// 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 +// variable that has been decomposed into multiple stack slots. +// As an example, a string could have the following configurations: +// +// stack layout LocalSlots +// +// Optimizations are disabled. s is on the stack and represented in its entirety. +// [ ------- s string ---- ] { N: s, Type: string, Off: 0 } +// +// s was not decomposed, but the SSA operates on its parts individually, so +// there is a LocalSlot for each of its fields that points into the single stack slot. +// [ ------- s string ---- ] { N: s, Type: *uint8, Off: 0 }, {N: s, Type: int, Off: 8} +// +// s was decomposed. Each of its fields is in its own stack slot and has its own LocalSLot. +// [ ptr *uint8 ] [ len int] { N: ptr, Type: *uint8, Off: 0, SplitOf: parent, SplitOffset: 0}, +// { N: len, Type: int, Off: 0, SplitOf: parent, SplitOffset: 8} +// parent = &{N: s, Type: string} type LocalSlot struct { - N GCNode // an ONAME *gc.Node representing a variable on the stack + N GCNode // an ONAME *gc.Node representing a stack location. Type *types.Type // type of slot Off int64 // offset of slot in N + + SplitOf *LocalSlot // slot is a decomposition of SplitOf + SplitOffset int64 // .. at this offset. } func (s LocalSlot) Name() string { diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go index 137e5fc4c2..e297e6bce7 100644 --- a/src/cmd/compile/internal/ssa/regalloc.go +++ b/src/cmd/compile/internal/ssa/regalloc.go @@ -2118,8 +2118,8 @@ func (e *edgeState) findRegFor(typ *types.Type) Location { // Allocate a temp location to spill a register to. // The type of the slot is immaterial - it will not be live across // any safepoint. Just use a type big enough to hold any register. - t := LocalSlot{e.s.f.fe.Auto(c.Pos, types.Int64), types.Int64, 0} - // TODO: reuse these slots. + t := LocalSlot{N: e.s.f.fe.Auto(c.Pos, types.Int64), Type: types.Int64} + // TODO: reuse these slots. They'll need to be erased first. e.set(t, vid, x, false, c.Pos) if e.s.f.pass.debug > regDebug { fmt.Printf(" SPILL %s->%s %s\n", r.Name(), t.Name(), x.LongString()) diff --git a/src/cmd/compile/internal/ssa/sizeof_test.go b/src/cmd/compile/internal/ssa/sizeof_test.go index 9fab7b664f..f8bbed91b4 100644 --- a/src/cmd/compile/internal/ssa/sizeof_test.go +++ b/src/cmd/compile/internal/ssa/sizeof_test.go @@ -24,6 +24,7 @@ func TestSizeof(t *testing.T) { }{ {Value{}, 68, 112}, {Block{}, 152, 288}, + {LocalSlot{}, 32, 48}, {valState{}, 28, 40}, } diff --git a/src/cmd/compile/internal/ssa/stackalloc.go b/src/cmd/compile/internal/ssa/stackalloc.go index 3b44986eee..341bb7b871 100644 --- a/src/cmd/compile/internal/ssa/stackalloc.go +++ b/src/cmd/compile/internal/ssa/stackalloc.go @@ -151,7 +151,7 @@ func (s *stackAllocState) stackalloc() { if v.Op != OpArg { continue } - loc := LocalSlot{v.Aux.(GCNode), v.Type, v.AuxInt} + loc := LocalSlot{N: v.Aux.(GCNode), Type: v.Type, Off: v.AuxInt} if f.pass.debug > stackDebug { fmt.Printf("stackalloc %s to %s\n", v, loc.Name()) } From 59fe2fbfe549f3dffec940581a71b42644ee5320 Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Mon, 22 May 2017 20:17:31 -0400 Subject: [PATCH 5/7] [dev.debug] cmd/link: let the linker combine .debug_ranges, remove globals The linker is pretty good at combining a bunch of symbols into a section, so let it do .debug_ranges the normal way. Along the way, remove a bunch of globals that were only used by one function that would only be called once per invocation. Change-Id: I1a528a438b193c41e7c444e8830516b07f11affc Reviewed-on: https://go-review.googlesource.com/43890 Reviewed-by: Alessandro Arzilli Reviewed-by: Ian Lance Taylor --- src/cmd/link/internal/ld/data.go | 41 ++++++++------- src/cmd/link/internal/ld/dwarf.go | 83 +++++++++++-------------------- 2 files changed, 52 insertions(+), 72 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 452332367c..bf219f7b62 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -592,15 +592,7 @@ func relocsym(ctxt *Link, s *Symbol) { } case objabi.R_DWARFREF: - var sectName string - var vaddr int64 - switch { - case r.Sym.Sect != nil: - sectName = r.Sym.Sect.Name - vaddr = int64(r.Sym.Sect.Vaddr) - case r.Sym.Type == SDWARFRANGE: - sectName = ".debug_ranges" - default: + if r.Sym.Sect == nil { Errorf(s, "missing DWARF section for relocation target %s", r.Sym.Name) } @@ -615,8 +607,8 @@ func relocsym(ctxt *Link, s *Symbol) { r.Type = objabi.R_ADDR } - r.Xsym = ctxt.Syms.ROLookup(sectName, 0) - r.Xadd = r.Add + Symaddr(r.Sym) - vaddr + r.Xsym = ctxt.Syms.ROLookup(r.Sym.Sect.Name, 0) + r.Xadd = r.Add + Symaddr(r.Sym) - int64(r.Sym.Sect.Vaddr) o = r.Xadd rs = r.Xsym @@ -625,7 +617,7 @@ func relocsym(ctxt *Link, s *Symbol) { } break } - o = Symaddr(r.Sym) + r.Add - vaddr + o = Symaddr(r.Sym) + r.Add - int64(r.Sym.Sect.Vaddr) case objabi.R_WEAKADDROFF: if !r.Sym.Attr.Reachable() { @@ -1843,9 +1835,9 @@ func (ctxt *Link) dodata() { dwarfgeneratedebugsyms(ctxt) - var s *Symbol var i int - for i, s = range dwarfp { + for ; i < len(dwarfp); i++ { + s := dwarfp[i] if s.Type != SDWARFSECT { break } @@ -1862,13 +1854,24 @@ func (ctxt *Link) dodata() { } checkdatsize(ctxt, datsize, SDWARFSECT) - if i < len(dwarfp) { - sect = addsection(&Segdwarf, ".debug_info", 04) + for i < len(dwarfp) { + curType := dwarfp[i].Type + var sect *Section + switch curType { + case SDWARFINFO: + sect = addsection(&Segdwarf, ".debug_info", 04) + case SDWARFRANGE: + sect = addsection(&Segdwarf, ".debug_ranges", 04) + default: + Errorf(dwarfp[i], "unknown DWARF section %v", curType) + } + sect.Align = 1 datsize = Rnd(datsize, int64(sect.Align)) sect.Vaddr = uint64(datsize) - for _, s := range dwarfp[i:] { - if s.Type != SDWARFINFO { + for ; i < len(dwarfp); i++ { + s := dwarfp[i] + if s.Type != curType { break } s.Sect = sect @@ -1878,7 +1881,7 @@ func (ctxt *Link) dodata() { datsize += s.Size } sect.Length = uint64(datsize) - sect.Vaddr - checkdatsize(ctxt, datsize, SDWARFINFO) + checkdatsize(ctxt, datsize, curType) } /* number the sections */ diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index ba8ace54c8..9b11fdcff6 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -67,26 +67,15 @@ func (c dwctxt) AddSectionOffset(s dwarf.Sym, size int, t interface{}, ofs int64 r.Add = ofs } -/* - * Offsets and sizes of the debug_* sections in the cout file. - */ -var abbrevsym *Symbol -var arangessec *Symbol -var framesec *Symbol -var infosec *Symbol -var linesec *Symbol -var rangesec *Symbol - var gdbscript string var dwarfp []*Symbol -func writeabbrev(ctxt *Link, syms []*Symbol) []*Symbol { +func writeabbrev(ctxt *Link) *Symbol { s := ctxt.Syms.Lookup(".debug_abbrev", 0) s.Type = SDWARFSECT - abbrevsym = s Addbytes(s, dwarf.GetAbbrev()) - return append(syms, s) + return s } /* @@ -993,13 +982,10 @@ func getCompilationDir() string { func writelines(ctxt *Link, syms []*Symbol) ([]*Symbol, []*Symbol) { var dwarfctxt dwarf.Context = dwctxt{ctxt} - if linesec == nil { - linesec = ctxt.Syms.Lookup(".debug_line", 0) - } - linesec.Type = SDWARFSECT - linesec.R = linesec.R[:0] + ls := ctxt.Syms.Lookup(".debug_line", 0) + ls.Type = SDWARFSECT + ls.R = ls.R[:0] - ls := linesec syms = append(syms, ls) var funcs []*Symbol @@ -1019,7 +1005,7 @@ func writelines(ctxt *Link, syms []*Symbol) ([]*Symbol, []*Symbol) { dwinfo = newdie(ctxt, &dwroot, dwarf.DW_ABRV_COMPUNIT, "go", 0) newattr(dwinfo, dwarf.DW_AT_language, dwarf.DW_CLS_CONSTANT, int64(lang), 0) - newattr(dwinfo, dwarf.DW_AT_stmt_list, dwarf.DW_CLS_PTR, 0, linesec) + newattr(dwinfo, dwarf.DW_AT_stmt_list, dwarf.DW_CLS_PTR, 0, ls) newattr(dwinfo, dwarf.DW_AT_low_pc, dwarf.DW_CLS_ADDRESS, s.Value, s) // OS X linker requires compilation dir or absolute path in comp unit name to output debug info. compDir := getCompilationDir() @@ -1178,12 +1164,9 @@ func appendPCDeltaCFA(b []byte, deltapc, cfa int64) []byte { func writeframes(ctxt *Link, syms []*Symbol) []*Symbol { var dwarfctxt dwarf.Context = dwctxt{ctxt} - if framesec == nil { - framesec = ctxt.Syms.Lookup(".debug_frame", 0) - } - framesec.Type = SDWARFSECT - framesec.R = framesec.R[:0] - fs := framesec + fs := ctxt.Syms.Lookup(".debug_frame", 0) + fs.Type = SDWARFSECT + fs.R = fs.R[:0] syms = append(syms, fs) // Emit the CIE, Section 6.4.1 @@ -1280,7 +1263,7 @@ func writeframes(ctxt *Link, syms []*Symbol) []*Symbol { // ptrsize: address range Adduint32(ctxt, fs, uint32(4+2*SysArch.PtrSize+len(deltaBuf))) // length (excludes itself) if Linkmode == LinkExternal { - adddwarfref(ctxt, fs, framesec, 4) + adddwarfref(ctxt, fs, fs, 4) } else { Adduint32(ctxt, fs, 0) // CIE offset } @@ -1292,27 +1275,24 @@ func writeframes(ctxt *Link, syms []*Symbol) []*Symbol { } func writeranges(ctxt *Link, syms []*Symbol) []*Symbol { - if rangesec == nil { - rangesec = ctxt.Syms.Lookup(".debug_ranges", 0) - } - rangesec.Type = SDWARFSECT - rangesec.Attr |= AttrReachable - rangesec.R = rangesec.R[:0] - + empty := true for _, s := range ctxt.Textp { rangeSym := ctxt.Syms.Lookup(dwarf.RangePrefix+s.Name, int(s.Version)) - rangeSym.Attr |= AttrReachable - rangeSym.Type = SDWARFRANGE - rangeSym.Value = rangesec.Size - rangesec.P = append(rangesec.P, rangeSym.P...) - for _, r := range rangeSym.R { - r.Off += int32(rangesec.Size) - rangesec.R = append(rangesec.R, r) + if rangeSym.Size == 0 { + continue } - rangesec.Size += rangeSym.Size + rangeSym.Attr |= AttrReachable | AttrNotInSymbolTable + rangeSym.Type = SDWARFRANGE + syms = append(syms, rangeSym) + empty = false } - if rangesec.Size > 0 { + if !empty { // PE does not like empty sections + rangesec := ctxt.Syms.Lookup(".debug_ranges", 0) + rangesec.Type = SDWARFRANGE + rangesec.Attr |= AttrReachable + rangesec.R = rangesec.R[:0] + syms = append(syms, rangesec) } return syms @@ -1325,18 +1305,14 @@ const ( COMPUNITHEADERSIZE = 4 + 2 + 4 + 1 ) -func writeinfo(ctxt *Link, syms []*Symbol, funcs []*Symbol) []*Symbol { - if infosec == nil { - infosec = ctxt.Syms.Lookup(".debug_info", 0) - } +func writeinfo(ctxt *Link, syms []*Symbol, funcs []*Symbol, abbrevsym *Symbol) []*Symbol { + infosec := ctxt.Syms.Lookup(".debug_info", 0) infosec.R = infosec.R[:0] infosec.Type = SDWARFINFO infosec.Attr |= AttrReachable syms = append(syms, infosec) - if arangessec == nil { - arangessec = ctxt.Syms.Lookup(".dwarfaranges", 0) - } + arangessec := ctxt.Syms.Lookup(".dwarfaranges", 0) arangessec.R = arangessec.R[:0] var dwarfctxt dwarf.Context = dwctxt{ctxt} @@ -1577,10 +1553,10 @@ func dwarfgeneratedebugsyms(ctxt *Link) { genasmsym(ctxt, defdwsymb) - syms := writeabbrev(ctxt, nil) + abbrev := writeabbrev(ctxt) + syms := []*Symbol{abbrev} syms, funcs := writelines(ctxt, syms) syms = writeframes(ctxt, syms) - syms = writeranges(ctxt, syms) synthesizestringtypes(ctxt, dwtypes.Child) synthesizeslicetypes(ctxt, dwtypes.Child) @@ -1596,13 +1572,14 @@ func dwarfgeneratedebugsyms(ctxt *Link) { // Need to reorder symbols so SDWARFINFO is after all SDWARFSECT // (but we need to generate dies before writepub) - infosyms := writeinfo(ctxt, nil, funcs) + infosyms := writeinfo(ctxt, nil, funcs, abbrev) syms = writepub(ctxt, ".debug_pubnames", ispubname, syms) syms = writepub(ctxt, ".debug_pubtypes", ispubtype, syms) syms = writearanges(ctxt, syms) syms = writegdbscript(ctxt, syms) syms = append(syms, infosyms...) + syms = writeranges(ctxt, syms) dwarfp = syms } From cd702b171c90be4b410d19bd93d5ea2899eaa809 Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Mon, 24 Jul 2017 13:37:04 -0400 Subject: [PATCH 6/7] [dev.debug] cmd/internal/dwarf: add DWARF abbrevs with location lists Location lists require new DWARF abbrev entries. Add them before CL 41770 to enable binary comparison. Change-Id: If99461f6896db902f2774e0718065eb3d3522026 Reviewed-on: https://go-review.googlesource.com/50881 Reviewed-by: Josh Bleecher Snyder --- src/cmd/internal/dwarf/dwarf.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index 325836119f..b58052beb3 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -213,7 +213,9 @@ const ( DW_ABRV_FUNCTION DW_ABRV_VARIABLE DW_ABRV_AUTO + DW_ABRV_AUTO_LOCLIST DW_ABRV_PARAM + DW_ABRV_PARAM_LOCLIST DW_ABRV_LEXICAL_BLOCK_RANGES DW_ABRV_LEXICAL_BLOCK_SIMPLE DW_ABRV_STRUCTFIELD @@ -297,6 +299,17 @@ var abbrevs = [DW_NABRV]dwAbbrev{ }, }, + /* AUTO_LOCLIST */ + { + DW_TAG_variable, + DW_CHILDREN_no, + []dwAttrForm{ + {DW_AT_name, DW_FORM_string}, + {DW_AT_location, DW_FORM_sec_offset}, + {DW_AT_type, DW_FORM_ref_addr}, + }, + }, + /* PARAM */ { DW_TAG_formal_parameter, @@ -307,6 +320,18 @@ var abbrevs = [DW_NABRV]dwAbbrev{ {DW_AT_type, DW_FORM_ref_addr}, }, }, + + /* PARAM_LOCLIST */ + { + DW_TAG_formal_parameter, + DW_CHILDREN_no, + []dwAttrForm{ + {DW_AT_name, DW_FORM_string}, + {DW_AT_location, DW_FORM_sec_offset}, + {DW_AT_type, DW_FORM_ref_addr}, + }, + }, + /* LEXICAL_BLOCK_RANGES */ { DW_TAG_lexical_block, From 4c54a047c6ea88dd77416a3b878f6935165f6129 Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Fri, 21 Jul 2017 18:30:19 -0400 Subject: [PATCH 7/7] [dev.debug] cmd/compile: better DWARF with optimizations on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/cmd/compile/fmt_test.go | 15 + src/cmd/compile/internal/gc/main.go | 8 +- src/cmd/compile/internal/gc/pgen.go | 359 +++++++++-- src/cmd/compile/internal/gc/sizeof_test.go | 2 +- src/cmd/compile/internal/gc/ssa.go | 28 +- src/cmd/compile/internal/gc/syntax.go | 2 + src/cmd/compile/internal/ssa/cache.go | 22 + src/cmd/compile/internal/ssa/debug.go | 559 ++++++++++++++++++ .../compile/internal/ssa/gen/genericOps.go | 1 + src/cmd/compile/internal/ssa/html.go | 13 + src/cmd/compile/internal/ssa/location.go | 6 + src/cmd/compile/internal/ssa/opGen.go | 6 + src/cmd/compile/internal/ssa/regalloc.go | 94 ++- src/cmd/compile/internal/ssa/value.go | 15 +- src/cmd/internal/dwarf/dwarf.go | 130 ++-- src/cmd/internal/obj/link.go | 53 +- src/cmd/internal/obj/objfile.go | 17 +- src/cmd/internal/obj/plist.go | 18 +- src/cmd/internal/obj/x86/a.out.go | 117 ++++ src/cmd/internal/obj/x86/obj6.go | 39 +- src/cmd/internal/objabi/symkind.go | 1 + src/cmd/internal/objabi/symkind_string.go | 4 +- src/cmd/link/internal/ld/data.go | 2 + src/cmd/link/internal/ld/dwarf.go | 31 + src/cmd/link/internal/ld/elf.go | 7 +- src/cmd/link/internal/ld/symkind.go | 2 + src/cmd/link/internal/ld/symkind_string.go | 4 +- 27 files changed, 1394 insertions(+), 161 deletions(-) create mode 100644 src/cmd/compile/internal/ssa/debug.go diff --git a/src/cmd/compile/fmt_test.go b/src/cmd/compile/fmt_test.go index 59de326a91..2052a4200e 100644 --- a/src/cmd/compile/fmt_test.go +++ b/src/cmd/compile/fmt_test.go @@ -571,9 +571,16 @@ var knownFormats = map[string]string{ "*cmd/compile/internal/ssa.Block %s": "", "*cmd/compile/internal/ssa.Block %v": "", "*cmd/compile/internal/ssa.Func %s": "", + "*cmd/compile/internal/ssa.Func %v": "", + "*cmd/compile/internal/ssa.FuncDebug %v": "", + "*cmd/compile/internal/ssa.LocalSlot %+v": "", + "*cmd/compile/internal/ssa.LocalSlot %v": "", + "*cmd/compile/internal/ssa.Register %v": "", "*cmd/compile/internal/ssa.SparseTreeNode %v": "", "*cmd/compile/internal/ssa.Value %s": "", "*cmd/compile/internal/ssa.Value %v": "", + "*cmd/compile/internal/ssa.VarLoc %+v": "", + "*cmd/compile/internal/ssa.VarLoc %v": "", "*cmd/compile/internal/ssa.sparseTreeMapEntry %v": "", "*cmd/compile/internal/types.Field %p": "", "*cmd/compile/internal/types.Field %v": "", @@ -592,6 +599,7 @@ var knownFormats = map[string]string{ "*cmd/compile/internal/types.Type %p": "", "*cmd/compile/internal/types.Type %s": "", "*cmd/compile/internal/types.Type %v": "", + "*cmd/internal/dwarf.Location %#v": "", "*cmd/internal/obj.Addr %v": "", "*cmd/internal/obj.LSym %v": "", "*cmd/internal/obj.Prog %s": "", @@ -600,17 +608,21 @@ var knownFormats = map[string]string{ "[16]byte %x": "", "[]*cmd/compile/internal/gc.Node %v": "", "[]*cmd/compile/internal/gc.Sig %#v": "", + "[]*cmd/compile/internal/ssa.Block %+v": "", "[]*cmd/compile/internal/ssa.Value %v": "", + "[][]cmd/compile/internal/ssa.SlotID %v": "", "[]byte %s": "", "[]byte %x": "", "[]cmd/compile/internal/ssa.Edge %v": "", "[]cmd/compile/internal/ssa.ID %v": "", + "[]cmd/compile/internal/ssa.VarLocList %v": "", "[]string %v": "", "bool %v": "", "byte %08b": "", "byte %c": "", "cmd/compile/internal/arm.shift %d": "", "cmd/compile/internal/gc.Class %d": "", + "cmd/compile/internal/gc.Class %v": "", "cmd/compile/internal/gc.Ctype %d": "", "cmd/compile/internal/gc.Ctype %v": "", "cmd/compile/internal/gc.Level %d": "", @@ -630,11 +642,13 @@ var knownFormats = map[string]string{ "cmd/compile/internal/ssa.Edge %v": "", "cmd/compile/internal/ssa.GCNode %v": "", "cmd/compile/internal/ssa.ID %d": "", + "cmd/compile/internal/ssa.ID %v": "", "cmd/compile/internal/ssa.LocalSlot %v": "", "cmd/compile/internal/ssa.Location %v": "", "cmd/compile/internal/ssa.Op %s": "", "cmd/compile/internal/ssa.Op %v": "", "cmd/compile/internal/ssa.ValAndOff %s": "", + "cmd/compile/internal/ssa.VarLocList %v": "", "cmd/compile/internal/ssa.rbrank %d": "", "cmd/compile/internal/ssa.regMask %d": "", "cmd/compile/internal/ssa.register %d": "", @@ -648,6 +662,7 @@ var knownFormats = map[string]string{ "cmd/compile/internal/types.EType %d": "", "cmd/compile/internal/types.EType %s": "", "cmd/compile/internal/types.EType %v": "", + "cmd/internal/dwarf.Location %#v": "", "cmd/internal/src.Pos %s": "", "cmd/internal/src.Pos %v": "", "error %v": "", diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 2b61564ad8..a1f4767c8f 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -44,6 +44,7 @@ var ( Debug_vlog bool Debug_wb int Debug_pctab string + Debug_locationlist int ) // Debug arguments. @@ -69,6 +70,7 @@ var debugtab = []struct { {"wb", "print information about write barriers", &Debug_wb}, {"export", "print export data", &Debug_export}, {"pctab", "print named pc-value table", &Debug_pctab}, + {"locationlists", "print information about DWARF location list creation", &Debug_locationlist}, } const debugHelpHeader = `usage: -d arg[,arg]* and arg is [=] @@ -192,6 +194,7 @@ func Main(archInit func(*Arch)) { flag.BoolVar(&pure_go, "complete", false, "compiling complete package (no C or assembly)") flag.StringVar(&debugstr, "d", "", "print debug information about items in `list`; try -d help") flag.BoolVar(&flagDWARF, "dwarf", true, "generate DWARF symbols") + flag.BoolVar(&Ctxt.Flag_locationlists, "dwarflocationlists", false, "add location lists to DWARF in optimized mode") objabi.Flagcount("e", "no limit on number of errors reported", &Debug['e']) objabi.Flagcount("f", "debug stack frames", &Debug['f']) objabi.Flagcount("h", "halt on error", &Debug['h']) @@ -298,6 +301,9 @@ func Main(archInit func(*Arch)) { if nBackendWorkers > 1 && !concurrentBackendAllowed() { log.Fatalf("cannot use concurrent backend compilation with provided flags; invoked as %v", os.Args) } + if Ctxt.Flag_locationlists && len(Ctxt.Arch.DWARFRegisters) == 0 { + log.Fatalf("location lists requested but register mapping not available on %v", Ctxt.Arch.Name) + } // parse -d argument if debugstr != "" { @@ -383,7 +389,7 @@ func Main(archInit func(*Arch)) { Debug['l'] = 1 - Debug['l'] } - trackScopes = flagDWARF && Debug['l'] == 0 && Debug['N'] != 0 + trackScopes = flagDWARF && ((Debug['l'] == 0 && Debug['N'] != 0) || Ctxt.Flag_locationlists) Widthptr = thearch.LinkArch.PtrSize Widthreg = thearch.LinkArch.RegSize diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index d301ae19c8..542fd43b63 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -13,6 +13,7 @@ import ( "cmd/internal/src" "cmd/internal/sys" "fmt" + "math" "math/rand" "sort" "sync" @@ -303,29 +304,77 @@ func compileFunctions() { func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope { fn := curfn.(*Node) + debugInfo := fn.Func.DebugInfo + fn.Func.DebugInfo = nil if expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect { Fatalf("unexpected fnsym: %v != %v", fnsym, expect) } - var dwarfVars []*dwarf.Var - var varScopes []ScopeID - + var automDecls []*Node + // Populate Automs for fn. for _, n := range fn.Func.Dcl { if n.Op != ONAME { // might be OTYPE or OLITERAL continue } - var name obj.AddrName - var abbrev int - offs := n.Xoffset - switch n.Class() { case PAUTO: if !n.Name.Used() { Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)") } name = obj.NAME_AUTO + case PPARAM, PPARAMOUT: + name = obj.NAME_PARAM + default: + continue + } + automDecls = append(automDecls, n) + gotype := ngotype(n).Linksym() + fnsym.Func.Autom = append(fnsym.Func.Autom, &obj.Auto{ + Asym: Ctxt.Lookup(n.Sym.Name), + Aoffset: int32(n.Xoffset), + Name: name, + Gotype: gotype, + }) + } + var dwarfVars []*dwarf.Var + var decls []*Node + if Ctxt.Flag_locationlists && Ctxt.Flag_optimize { + decls, dwarfVars = createComplexVars(fn, debugInfo) + } else { + decls, dwarfVars = createSimpleVars(automDecls) + } + + var varScopes []ScopeID + for _, decl := range decls { + var scope ScopeID + if !decl.Name.Captured() && !decl.Name.Byval() { + // n.Pos of captured variables is their first + // use in the closure but they should always + // be assigned to scope 0 instead. + // TODO(mdempsky): Verify this. + scope = findScope(fn.Func.Marks, decl.Pos) + } + varScopes = append(varScopes, scope) + } + return assembleScopes(fnsym, fn, dwarfVars, varScopes) +} + +// createSimpleVars creates a DWARF entry for every variable declared in the +// function, claiming that they are permanently on the stack. +func createSimpleVars(automDecls []*Node) ([]*Node, []*dwarf.Var) { + var vars []*dwarf.Var + var decls []*Node + for _, n := range automDecls { + if n.IsAutoTmp() { + continue + } + var abbrev int + offs := n.Xoffset + + switch n.Class() { + case PAUTO: abbrev = dwarf.DW_ABRV_AUTO if Ctxt.FixedFrameSize() == 0 { offs -= int64(Widthptr) @@ -335,48 +384,288 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope { } case PPARAM, PPARAMOUT: - name = obj.NAME_PARAM - abbrev = dwarf.DW_ABRV_PARAM offs += Ctxt.FixedFrameSize() - default: - continue + Fatalf("createSimpleVars unexpected type %v for node %v", n.Class(), n) } - gotype := ngotype(n).Linksym() - fnsym.Func.Autom = append(fnsym.Func.Autom, &obj.Auto{ - Asym: Ctxt.Lookup(n.Sym.Name), - Aoffset: int32(n.Xoffset), - Name: name, - Gotype: gotype, - }) - - if n.IsAutoTmp() { - continue - } - - typename := dwarf.InfoPrefix + gotype.Name[len("type."):] - dwarfVars = append(dwarfVars, &dwarf.Var{ + typename := dwarf.InfoPrefix + typesymname(n.Type) + decls = append(decls, n) + vars = append(vars, &dwarf.Var{ Name: n.Sym.Name, Abbrev: abbrev, StackOffset: int32(offs), Type: Ctxt.Lookup(typename), }) + } + return decls, vars +} - var scope ScopeID - if !n.Name.Captured() && !n.Name.Byval() { - // n.Pos of captured variables is their first - // use in the closure but they should always - // be assigned to scope 0 instead. - // TODO(mdempsky): Verify this. - scope = findScope(fn.Func.Marks, n.Pos) +type varPart struct { + varOffset int64 + slot ssa.SlotID + locs ssa.VarLocList +} + +func createComplexVars(fn *Node, debugInfo *ssa.FuncDebug) ([]*Node, []*dwarf.Var) { + for _, locList := range debugInfo.Variables { + for _, loc := range locList.Locations { + if loc.StartProg != nil { + loc.StartPC = loc.StartProg.Pc + } + if loc.EndProg != nil { + loc.EndPC = loc.EndProg.Pc + } + if Debug_locationlist == 0 { + loc.EndProg = nil + loc.StartProg = nil + } } - - varScopes = append(varScopes, scope) } - return assembleScopes(fnsym, fn, dwarfVars, varScopes) + // Group SSA variables by the user variable they were decomposed from. + varParts := map[*Node][]varPart{} + for slotID, locList := range debugInfo.Variables { + if len(locList.Locations) == 0 { + continue + } + slot := debugInfo.Slots[slotID] + for slot.SplitOf != nil { + slot = slot.SplitOf + } + n := slot.N.(*Node) + varParts[n] = append(varParts[n], varPart{varOffset(slot), ssa.SlotID(slotID), locList}) + } + + // Produce a DWARF variable entry for each user variable. + // Don't iterate over the map -- that's nondeterministic, and + // createComplexVar has side effects. Instead, go by slot. + var decls []*Node + var vars []*dwarf.Var + for _, slot := range debugInfo.Slots { + for slot.SplitOf != nil { + slot = slot.SplitOf + } + n := slot.N.(*Node) + parts := varParts[n] + if parts == nil { + continue + } + + // Get the order the parts need to be in to represent the memory + // of the decomposed user variable. + sort.Sort(partsByVarOffset(parts)) + + if dvar := createComplexVar(debugInfo, n, parts); dvar != nil { + decls = append(decls, n) + vars = append(vars, dvar) + } + } + return decls, vars +} + +// varOffset returns the offset of slot within the user variable it was +// decomposed from. This has nothing to do with its stack offset. +func varOffset(slot *ssa.LocalSlot) int64 { + offset := slot.Off + for ; slot.SplitOf != nil; slot = slot.SplitOf { + offset += slot.SplitOffset + } + return offset +} + +type partsByVarOffset []varPart + +func (a partsByVarOffset) Len() int { return len(a) } +func (a partsByVarOffset) Less(i, j int) bool { return a[i].varOffset < a[j].varOffset } +func (a partsByVarOffset) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// createComplexVar builds a DWARF variable entry and location list representing n. +func createComplexVar(debugInfo *ssa.FuncDebug, n *Node, parts []varPart) *dwarf.Var { + slots := debugInfo.Slots + var offs int64 // base stack offset for this kind of variable + var abbrev int + switch n.Class() { + case PAUTO: + abbrev = dwarf.DW_ABRV_AUTO_LOCLIST + if Ctxt.FixedFrameSize() == 0 { + offs -= int64(Widthptr) + } + if objabi.Framepointer_enabled(objabi.GOOS, objabi.GOARCH) { + offs -= int64(Widthptr) + } + + case PPARAM, PPARAMOUT: + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + offs += Ctxt.FixedFrameSize() + default: + return nil + } + + gotype := ngotype(n).Linksym() + typename := dwarf.InfoPrefix + gotype.Name[len("type."):] + // The stack offset is used as a sorting key, so for decomposed + // variables just give it the lowest one. It's not used otherwise. + stackOffset := debugInfo.Slots[parts[0].slot].N.(*Node).Xoffset + offs + dvar := &dwarf.Var{ + Name: n.Sym.Name, + Abbrev: abbrev, + Type: Ctxt.Lookup(typename), + StackOffset: int32(stackOffset), + } + + if Debug_locationlist != 0 { + Ctxt.Logf("Building location list for %+v. Parts:\n", n) + for _, part := range parts { + Ctxt.Logf("\t%v => %v\n", debugInfo.Slots[part.slot], part.locs) + } + } + + // Given a variable that's been decomposed into multiple parts, + // its location list may need a new entry after the beginning or + // end of every location entry for each of its parts. For example: + // + // [variable] [pc range] + // string.ptr |----|-----| |----| + // string.len |------------| |--| + // ... needs a location list like: + // string |----|-----|-| |--|-| + // + // Note that location entries may or may not line up with each other, + // and some of the result will only have one or the other part. + // + // To build the resulting list: + // - keep a "current" pointer for each part + // - find the next transition point + // - advance the current pointer for each part up to that transition point + // - build the piece for the range between that transition point and the next + // - repeat + + curLoc := make([]int, len(slots)) + + // findBoundaryAfter finds the next beginning or end of a piece after currentPC. + findBoundaryAfter := func(currentPC int64) int64 { + min := int64(math.MaxInt64) + for slot, part := range parts { + // For each part, find the first PC greater than current. Doesn't + // matter if it's a start or an end, since we're looking for any boundary. + // If it's the new winner, save it. + onePart: + for i := curLoc[slot]; i < len(part.locs.Locations); i++ { + for _, pc := range [2]int64{part.locs.Locations[i].StartPC, part.locs.Locations[i].EndPC} { + if pc > currentPC { + if pc < min { + min = pc + } + break onePart + } + } + } + } + return min + } + var start int64 + end := findBoundaryAfter(0) + for { + // Advance to the next chunk. + start = end + end = findBoundaryAfter(start) + if end == math.MaxInt64 { + break + } + + dloc := dwarf.Location{StartPC: start, EndPC: end} + if Debug_locationlist != 0 { + Ctxt.Logf("Processing range %x -> %x\n", start, end) + } + + // Advance curLoc to the last location that starts before/at start. + // After this loop, if there's a location that covers [start, end), it will be current. + // Otherwise the current piece will be too early. + for _, part := range parts { + choice := -1 + for i := curLoc[part.slot]; i < len(part.locs.Locations); i++ { + if part.locs.Locations[i].StartPC > start { + break //overshot + } + choice = i // best yet + } + if choice != -1 { + curLoc[part.slot] = choice + } + if Debug_locationlist != 0 { + Ctxt.Logf("\t %v => %v", slots[part.slot], curLoc[part.slot]) + } + } + if Debug_locationlist != 0 { + Ctxt.Logf("\n") + } + // Assemble the location list entry for this chunk. + present := 0 + for _, part := range parts { + dpiece := dwarf.Piece{ + Length: slots[part.slot].Type.Size(), + } + locIdx := curLoc[part.slot] + if locIdx >= len(part.locs.Locations) || + start >= part.locs.Locations[locIdx].EndPC || + end <= part.locs.Locations[locIdx].StartPC { + if Debug_locationlist != 0 { + Ctxt.Logf("\t%v: missing", slots[part.slot]) + } + dpiece.Missing = true + dloc.Pieces = append(dloc.Pieces, dpiece) + continue + } + present++ + loc := part.locs.Locations[locIdx] + if Debug_locationlist != 0 { + Ctxt.Logf("\t%v: %v", slots[part.slot], loc) + } + if loc.OnStack { + dpiece.OnStack = true + dpiece.StackOffset = int32(offs + slots[part.slot].Off + slots[part.slot].N.(*Node).Xoffset) + } else { + for reg := 0; reg < len(debugInfo.Registers); reg++ { + if loc.Registers&(1< totally missing\n") + } + continue + } + // Extend the previous entry if possible. + if len(dvar.LocationList) > 0 { + prev := &dvar.LocationList[len(dvar.LocationList)-1] + if prev.EndPC == dloc.StartPC && len(prev.Pieces) == len(dloc.Pieces) { + equal := true + for i := range prev.Pieces { + if prev.Pieces[i] != dloc.Pieces[i] { + equal = false + } + } + if equal { + prev.EndPC = end + if Debug_locationlist != 0 { + Ctxt.Logf("-> merged with previous, now %#v\n", prev) + } + continue + } + } + } + dvar.LocationList = append(dvar.LocationList, dloc) + if Debug_locationlist != 0 { + Ctxt.Logf("-> added: %#v\n", dloc) + } + } + return dvar } // fieldtrack adds R_USEFIELD relocations to fnsym to record any diff --git a/src/cmd/compile/internal/gc/sizeof_test.go b/src/cmd/compile/internal/gc/sizeof_test.go index 1ca0a61535..bd4453fa84 100644 --- a/src/cmd/compile/internal/gc/sizeof_test.go +++ b/src/cmd/compile/internal/gc/sizeof_test.go @@ -22,7 +22,7 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {Func{}, 124, 216}, + {Func{}, 128, 224}, {Name{}, 36, 56}, {Param{}, 28, 56}, {Node{}, 76, 128}, diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index f8aefaae5e..c769efe8cd 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -4384,6 +4384,7 @@ func genssa(f *ssa.Func, pp *Progs) { s.pp = pp var progToValue map[*obj.Prog]*ssa.Value var progToBlock map[*obj.Prog]*ssa.Block + var valueToProg []*obj.Prog var logProgs = e.log if logProgs { progToValue = make(map[*obj.Prog]*ssa.Value, f.NumValues()) @@ -4398,6 +4399,11 @@ func genssa(f *ssa.Func, pp *Progs) { s.ScratchFpMem = e.scratchFpMem + logLocationLists := Debug_locationlist != 0 + if Ctxt.Flag_locationlists { + e.curfn.Func.DebugInfo = ssa.BuildFuncDebug(f, logLocationLists) + valueToProg = make([]*obj.Prog, f.NumValues()) + } // Emit basic blocks for i, b := range f.Blocks { s.bstart[b.ID] = s.pp.next @@ -4438,12 +4444,16 @@ func genssa(f *ssa.Func, pp *Progs) { } case ssa.OpPhi: CheckLoweredPhi(v) - + case ssa.OpRegKill: + // nothing to do default: // let the backend handle it thearch.SSAGenValue(&s, v) } + if Ctxt.Flag_locationlists { + valueToProg[v.ID] = x + } if logProgs { for ; x != s.pp.next; x = x.Link { progToValue[x] = v @@ -4469,6 +4479,22 @@ func genssa(f *ssa.Func, pp *Progs) { } } + if Ctxt.Flag_locationlists { + for _, locList := range e.curfn.Func.DebugInfo.Variables { + for _, loc := range locList.Locations { + loc.StartProg = valueToProg[loc.Start.ID] + if loc.End == nil { + Fatalf("empty loc %v compiling %v", loc, f.Name) + } + loc.EndProg = valueToProg[loc.End.ID] + if !logLocationLists { + loc.Start = nil + loc.End = nil + } + } + } + } + // Resolve branches for _, br := range s.Branches { br.P.To.Val = s.bstart[br.B.ID] diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index 0fd146bca2..32ae6f2f28 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -7,6 +7,7 @@ package gc import ( + "cmd/compile/internal/ssa" "cmd/compile/internal/syntax" "cmd/compile/internal/types" "cmd/internal/obj" @@ -369,6 +370,7 @@ type Func struct { Closgen int Outerfunc *Node // outer function (for closure) FieldTrack map[*types.Sym]struct{} + DebugInfo *ssa.FuncDebug Ntype *Node // signature Top int // top context (Ecall, Eproc, etc) Closure *Node // OCLOSURE <-> ODCLFUNC diff --git a/src/cmd/compile/internal/ssa/cache.go b/src/cmd/compile/internal/ssa/cache.go index f1018da497..8434084bde 100644 --- a/src/cmd/compile/internal/ssa/cache.go +++ b/src/cmd/compile/internal/ssa/cache.go @@ -14,6 +14,11 @@ type Cache struct { blocks [200]Block locs [2000]Location + // Storage for DWARF variable locations. Lazily allocated + // since location lists are off by default. + varLocs []VarLoc + curVarLoc int + // Reusable stackAllocState. // See stackalloc.go's {new,put}StackAllocState. stackAllocState *stackAllocState @@ -38,4 +43,21 @@ func (c *Cache) Reset() { for i := range xl { xl[i] = nil } + xvl := c.varLocs[:c.curVarLoc] + for i := range xvl { + xvl[i] = VarLoc{} + } + c.curVarLoc = 0 +} + +func (c *Cache) NewVarLoc() *VarLoc { + if c.varLocs == nil { + c.varLocs = make([]VarLoc, 4000) + } + if c.curVarLoc == len(c.varLocs) { + return &VarLoc{} + } + vl := &c.varLocs[c.curVarLoc] + c.curVarLoc++ + return vl } diff --git a/src/cmd/compile/internal/ssa/debug.go b/src/cmd/compile/internal/ssa/debug.go new file mode 100644 index 0000000000..55db45b642 --- /dev/null +++ b/src/cmd/compile/internal/ssa/debug.go @@ -0,0 +1,559 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package ssa + +import ( + "cmd/internal/obj" + "fmt" + "strings" +) + +type SlotID int32 + +// A FuncDebug contains all the debug information for the variables in a +// function. Variables are identified by their LocalSlot, which may be the +// result of decomposing a larger variable. +type FuncDebug struct { + Slots []*LocalSlot + Variables []VarLocList + Registers []Register +} + +// append adds a location to the location list for slot. +func (f *FuncDebug) append(slot SlotID, loc *VarLoc) { + f.Variables[slot].append(loc) +} + +// lastLoc returns the last VarLoc for slot, or nil if it has none. +func (f *FuncDebug) lastLoc(slot SlotID) *VarLoc { + return f.Variables[slot].last() +} + +func (f *FuncDebug) String() string { + var vars []string + for slot, list := range f.Variables { + if len(list.Locations) == 0 { + continue + } + vars = append(vars, fmt.Sprintf("%v = %v", f.Slots[slot], list)) + } + return fmt.Sprintf("{%v}", strings.Join(vars, ", ")) +} + +// A VarLocList contains the locations for a variable, in program text order. +// It will often have gaps. +type VarLocList struct { + Locations []*VarLoc +} + +func (l *VarLocList) append(loc *VarLoc) { + l.Locations = append(l.Locations, loc) +} + +// last returns the last location in the list. +func (l *VarLocList) last() *VarLoc { + if l == nil || len(l.Locations) == 0 { + return nil + } + return l.Locations[len(l.Locations)-1] +} + +// A VarLoc describes a variable's location in a single contiguous range +// of program text. It is generated from the SSA representation, but it +// refers to the generated machine code, so the Values referenced are better +// understood as PCs than actual Values, and the ranges can cross blocks. +// The range is defined first by Values, which are then mapped to Progs +// during genssa and finally to function PCs after assembly. +// A variable can be on the stack and in any number of registers. +type VarLoc struct { + // Inclusive -- the first SSA value that the range covers. The value + // doesn't necessarily have anything to do with the variable; it just + // identifies a point in the program text. + Start *Value + // Exclusive -- the first SSA value after start that the range doesn't + // cover. A location with start == end is empty. + End *Value + // The prog/PCs corresponding to Start and End above. These are for the + // convenience of later passes, since code generation isn't done when + // BuildFuncDebug runs. + StartProg, EndProg *obj.Prog + StartPC, EndPC int64 + + // The registers this variable is available in. There can be more than + // one in various situations, e.g. it's being moved between registers. + Registers RegisterSet + // Indicates whether the variable is on the stack. The stack position is + // stored in the associated gc.Node. + OnStack bool + + // Used only during generation. Indicates whether this location lasts + // past the block's end. Without this, there would be no way to distinguish + // between a range that ended on the last Value of a block and one that + // didn't end at all. + survivedBlock bool +} + +// RegisterSet is a bitmap of registers, indexed by Register.num. +type RegisterSet uint64 + +func (v *VarLoc) String() string { + var registers []Register + if v.Start != nil { + registers = v.Start.Block.Func.Config.registers + } + loc := "" + if !v.OnStack && v.Registers == 0 { + loc = "!!!no location!!!" + } + if v.OnStack { + loc += "stack," + } + var regnames []string + for reg := 0; reg < 64; reg++ { + if v.Registers&(1< 0 { + b := work[0] + work = work[1:] + if blockLocs[b.ID] != nil { + continue // already processed + } + if !state.predecessorsDone(b, blockLocs) { + continue // not ready yet + } + + for _, edge := range b.Succs { + if blockLocs[edge.Block().ID] != nil { + continue + } + work = append(work, edge.Block()) + } + + // Build the starting state for the block from the final + // state of its predecessors. + locs := state.mergePredecessors(b, blockLocs) + if state.loggingEnabled { + state.logf("Processing %v, initial locs %v, regs %v\n", b, locs, state.registerContents) + } + // Update locs/registers with the effects of each Value. + for _, v := range b.Values { + slots := valueNames[v.ID] + + // Loads and stores inherit the names of their sources. + var source *Value + switch v.Op { + case OpStoreReg: + source = v.Args[0] + case OpLoadReg: + switch a := v.Args[0]; a.Op { + case OpArg: + source = a + case OpStoreReg: + source = a.Args[0] + default: + state.unexpected(v, "load with unexpected source op %v", a) + } + } + if source != nil { + slots = append(slots, valueNames[source.ID]...) + // As of writing, the compiler never uses a load/store as a + // source of another load/store, so there's no reason this should + // ever be consulted. Update just in case, and so that when + // valueNames is cached, we can reuse the memory. + valueNames[v.ID] = slots + } + + if len(slots) == 0 { + continue + } + + reg, _ := f.getHome(v.ID).(*Register) + state.processValue(locs, v, slots, reg) + + } + + // The block is done; end the locations for all its slots. + for _, locList := range locs.Variables { + last := locList.last() + if last == nil || last.End != nil { + continue + } + if len(b.Values) != 0 { + last.End = b.Values[len(b.Values)-1] + } else { + // This happens when a value survives into an empty block from its predecessor. + // Just carry it forward for liveness's sake. + last.End = last.Start + } + last.survivedBlock = true + } + if state.loggingEnabled { + f.Logf("Block done: locs %v, regs %v. work = %+v\n", locs, state.registerContents, work) + } + blockLocs[b.ID] = locs + } + + // Build the complete debug info by concatenating each of the blocks' + // locations together. + info := &FuncDebug{ + Variables: make([]VarLocList, len(state.slots)), + Slots: state.slots, + Registers: f.Config.registers, + } + for _, b := range f.Blocks { + // Ignore empty blocks; there will be some records for liveness + // but they're all useless. + if len(b.Values) == 0 { + continue + } + if blockLocs[b.ID] == nil { + state.unexpected(b.Values[0], "Never processed block %v\n", b) + continue + } + for slot, blockLocList := range blockLocs[b.ID].Variables { + for _, loc := range blockLocList.Locations { + if !loc.OnStack && loc.Registers == 0 { + state.unexpected(loc.Start, "Location for %v with no storage: %+v\n", state.slots[slot], loc) + continue // don't confuse downstream with our bugs + } + if loc.Start == nil || loc.End == nil { + state.unexpected(b.Values[0], "Location for %v missing start or end: %v\n", state.slots[slot], loc) + continue + } + info.append(SlotID(slot), loc) + } + } + } + if state.loggingEnabled { + f.Logf("Final result:\n") + for slot, locList := range info.Variables { + f.Logf("\t%v => %v\n", state.slots[slot], locList) + } + } + return info +} + +// isSynthetic reports whether if slot represents a compiler-inserted variable, +// e.g. an autotmp or an anonymous return value that needed a stack slot. +func isSynthetic(slot *LocalSlot) bool { + c := slot.Name()[0] + return c == '.' || c == '~' +} + +// predecessorsDone reports whether block is ready to be processed. +func (state *debugState) predecessorsDone(b *Block, blockLocs []*FuncDebug) bool { + f := b.Func + for _, edge := range b.Preds { + // Ignore back branches, e.g. the continuation of a for loop. + // This may not work for functions with mutual gotos, which are not + // reducible, in which case debug information will be missing for any + // code after that point in the control flow. + if f.sdom().isAncestorEq(b, edge.b) { + if state.loggingEnabled { + f.Logf("ignoring back branch from %v to %v\n", edge.b, b) + } + continue // back branch + } + if blockLocs[edge.b.ID] == nil { + if state.loggingEnabled { + f.Logf("%v is not ready because %v isn't done\n", b, edge.b) + } + return false + } + } + return true +} + +// mergePredecessors takes the end state of each of b's predecessors and +// intersects them to form the starting state for b. +// The registers slice (the second return value) will be reused for each call to mergePredecessors. +func (state *debugState) mergePredecessors(b *Block, blockLocs []*FuncDebug) *FuncDebug { + live := make([]VarLocList, len(state.slots)) + + // Filter out back branches. + var preds []*Block + for _, pred := range b.Preds { + if blockLocs[pred.b.ID] != nil { + preds = append(preds, pred.b) + } + } + + if len(preds) > 0 { + p := preds[0] + for slot, locList := range blockLocs[p.ID].Variables { + last := locList.last() + if last == nil || !last.survivedBlock { + continue + } + // If this block is empty, carry forward the end value for liveness. + // It'll be ignored later. + start := last.End + if len(b.Values) != 0 { + start = b.Values[0] + } + loc := state.cache.NewVarLoc() + loc.Start = start + loc.OnStack = last.OnStack + loc.Registers = last.Registers + live[slot].append(loc) + } + } + if state.loggingEnabled && len(b.Preds) > 1 { + state.logf("Starting merge with state from %v: %v\n", b.Preds[0].b, blockLocs[b.Preds[0].b.ID]) + } + for i := 1; i < len(preds); i++ { + p := preds[i] + if state.loggingEnabled { + state.logf("Merging in state from %v: %v &= %v\n", p, live, blockLocs[p.ID]) + } + + for slot, liveVar := range live { + liveLoc := liveVar.last() + if liveLoc == nil { + continue + } + + predLoc := blockLocs[p.ID].lastLoc(SlotID(slot)) + // Clear out slots missing/dead in p. + if predLoc == nil || !predLoc.survivedBlock { + live[slot].Locations = nil + continue + } + + // Unify storage locations. + liveLoc.OnStack = liveLoc.OnStack && predLoc.OnStack + liveLoc.Registers &= predLoc.Registers + } + } + + // Create final result. + locs := &FuncDebug{Variables: live, Slots: state.slots} + for reg := range state.registerContents { + state.registerContents[reg] = state.registerContents[reg][:0] + } + for slot, locList := range live { + loc := locList.last() + if loc == nil { + continue + } + for reg := 0; reg < state.numRegisters; reg++ { + if loc.Registers&(1< regDebug { fmt.Printf("freeReg %s (dump %s/%s)\n", s.registers[r].Name(), v, s.regs[r].c) } + if !resetting && s.f.Config.ctxt.Flag_locationlists && len(s.valueNames[v.ID]) != 0 { + kill := s.curBlock.NewValue0(src.NoXPos, OpRegKill, types.TypeVoid) + for int(kill.ID) >= len(s.orig) { + s.orig = append(s.orig, nil) + } + for _, name := range s.valueNames[v.ID] { + s.f.NamedValues[name] = append(s.f.NamedValues[name], kill) + } + s.f.setHome(kill, &s.registers[r]) + } s.regs[r] = regState{} s.values[v.ID].regs &^= regMask(1) << r s.used &^= regMask(1) << r @@ -599,6 +619,17 @@ func (s *regAllocState) init(f *Func) { s.values = make([]valState, f.NumValues()) s.orig = make([]*Value, f.NumValues()) s.copies = make(map[*Value]bool) + if s.f.Config.ctxt.Flag_locationlists { + s.valueNames = make([][]LocalSlot, f.NumValues()) + for slot, values := range f.NamedValues { + if isSynthetic(&slot) { + continue + } + for _, value := range values { + s.valueNames[value.ID] = append(s.valueNames[value.ID], slot) + } + } + } for _, b := range f.Blocks { for _, v := range b.Values { if !v.Type.IsMemory() && !v.Type.IsVoid() && !v.Type.IsFlags() && !v.Type.IsTuple() { @@ -692,7 +723,9 @@ func (s *regAllocState) liveAfterCurrentInstruction(v *Value) bool { // Sets the state of the registers to that encoded in regs. func (s *regAllocState) setState(regs []endReg) { - s.freeRegs(s.used) + for s.used != 0 { + s.freeOrResetReg(pickReg(s.used), true) + } for _, x := range regs { s.assignReg(x.r, x.v, x.c) } @@ -735,6 +768,9 @@ func (s *regAllocState) regalloc(f *Func) { } for _, b := range f.Blocks { + if s.f.pass.debug > regDebug { + fmt.Printf("Begin processing block %v\n", b) + } s.curBlock = b // Initialize regValLiveSet and uses fields for this block. @@ -830,9 +866,6 @@ func (s *regAllocState) regalloc(f *Func) { // This is the complicated case. We have more than one predecessor, // which means we may have Phi ops. - // Copy phi ops into new schedule. - b.Values = append(b.Values, phis...) - // Start with the final register state of the primary predecessor idx := s.primary[b.ID] if idx < 0 { @@ -910,6 +943,9 @@ func (s *regAllocState) regalloc(f *Func) { } } + // Copy phi ops into new schedule. + b.Values = append(b.Values, phis...) + // Third pass - pick registers for phis whose inputs // were not in a register. for i, v := range phis { @@ -1005,7 +1041,7 @@ func (s *regAllocState) regalloc(f *Func) { pidx := e.i for _, v := range succ.Values { if v.Op != OpPhi { - break + continue } if !s.values[v.ID].needReg { continue @@ -1565,6 +1601,9 @@ func (s *regAllocState) placeSpills() { for _, b := range f.Blocks { var m regMask for _, v := range b.Values { + if v.Op == OpRegKill { + continue + } if v.Op != OpPhi { break } @@ -1675,7 +1714,7 @@ func (s *regAllocState) placeSpills() { for _, b := range f.Blocks { nphi := 0 for _, v := range b.Values { - if v.Op != OpPhi { + if v.Op != OpRegKill && v.Op != OpPhi { break } nphi++ @@ -1800,6 +1839,9 @@ func (e *edgeState) setup(idx int, srcReg []endReg, dstReg []startReg, stacklive } // Phis need their args to end up in a specific location. for _, v := range e.b.Values { + if v.Op == OpRegKill { + continue + } if v.Op != OpPhi { break } @@ -1878,6 +1920,7 @@ func (e *edgeState) process() { if e.s.f.pass.debug > regDebug { fmt.Printf("breaking cycle with v%d in %s:%s\n", vid, loc.Name(), c) } + e.erase(r) if _, isReg := loc.(*Register); isReg { c = e.p.NewValue1(d.pos, OpCopy, c.Type, c) } else { @@ -1943,6 +1986,18 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP } } _, dstReg := loc.(*Register) + + // Pre-clobber destination. This avoids the + // following situation: + // - v is currently held in R0 and stacktmp0. + // - We want to copy stacktmp1 to stacktmp0. + // - We choose R0 as the temporary register. + // During the copy, both R0 and stacktmp0 are + // clobbered, losing both copies of v. Oops! + // Erasing the destination early means R0 will not + // be chosen as the temp register, as it will then + // be the last copy of v. + e.erase(loc) var x *Value if c == nil { if !e.s.values[vid].rematerializeable { @@ -1953,8 +2008,8 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP } else { // Rematerialize into stack slot. Need a free // register to accomplish this. - e.erase(loc) // see pre-clobber comment below r := e.findRegFor(v.Type) + e.erase(r) x = v.copyIntoNoXPos(e.p) e.set(r, vid, x, false, pos) // Make sure we spill with the size of the slot, not the @@ -1976,20 +2031,8 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP x = e.p.NewValue1(pos, OpLoadReg, c.Type, c) } else { // mem->mem. Use temp register. - - // Pre-clobber destination. This avoids the - // following situation: - // - v is currently held in R0 and stacktmp0. - // - We want to copy stacktmp1 to stacktmp0. - // - We choose R0 as the temporary register. - // During the copy, both R0 and stacktmp0 are - // clobbered, losing both copies of v. Oops! - // Erasing the destination early means R0 will not - // be chosen as the temp register, as it will then - // be the last copy of v. - e.erase(loc) - r := e.findRegFor(c.Type) + e.erase(r) t := e.p.NewValue1(pos, OpLoadReg, c.Type, c) e.set(r, vid, t, false, pos) x = e.p.NewValue1(pos, OpStoreReg, loc.(LocalSlot).Type, t) @@ -2008,7 +2051,6 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP // set changes the contents of location loc to hold the given value and its cached representative. func (e *edgeState) set(loc Location, vid ID, c *Value, final bool, pos src.XPos) { e.s.f.setHome(c, loc) - e.erase(loc) e.contents[loc] = contentRecord{vid, c, final, pos} a := e.cache[vid] if len(a) == 0 { @@ -2059,6 +2101,16 @@ func (e *edgeState) erase(loc Location) { fmt.Printf("v%d no longer available in %s:%s\n", vid, loc.Name(), c) } a[i], a = a[len(a)-1], a[:len(a)-1] + if e.s.f.Config.ctxt.Flag_locationlists { + if _, isReg := loc.(*Register); isReg && int(c.ID) < len(e.s.valueNames) && len(e.s.valueNames[c.ID]) != 0 { + kill := e.p.NewValue0(src.NoXPos, OpRegKill, types.TypeVoid) + e.s.f.setHome(kill, loc) + for _, name := range e.s.valueNames[c.ID] { + e.s.f.NamedValues[name] = append(e.s.f.NamedValues[name], kill) + } + } + } + break } } diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go index 7edc71be52..6df535153a 100644 --- a/src/cmd/compile/internal/ssa/value.go +++ b/src/cmd/compile/internal/ssa/value.go @@ -10,6 +10,7 @@ import ( "cmd/internal/src" "fmt" "math" + "strings" ) // A Value represents a value in the SSA representation of the program. @@ -98,7 +99,7 @@ func (v *Value) AuxValAndOff() ValAndOff { return ValAndOff(v.AuxInt) } -// long form print. v# = opcode [aux] args [: reg] +// long form print. v# = opcode [aux] args [: reg] (names) func (v *Value) LongString() string { s := fmt.Sprintf("v%d = %s", v.ID, v.Op) s += " <" + v.Type.String() + ">" @@ -110,6 +111,18 @@ func (v *Value) LongString() string { if int(v.ID) < len(r) && r[v.ID] != nil { s += " : " + r[v.ID].Name() } + var names []string + for name, values := range v.Block.Func.NamedValues { + for _, value := range values { + if value == v { + names = append(names, name.Name()) + break // drop duplicates. + } + } + } + if len(names) != 0 { + s += " (" + strings.Join(names, ", ") + ")" + } return s } diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index b58052beb3..2b034257a6 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -15,6 +15,9 @@ import ( // InfoPrefix is the prefix for all the symbols containing DWARF info entries. const InfoPrefix = "go.info." +// RangePrefix is the prefix for all the symbols containing DWARF location lists. +const LocPrefix = "go.loc." + // RangePrefix is the prefix for all the symbols containing DWARF range lists. const RangePrefix = "go.range." @@ -23,13 +26,31 @@ type Sym interface { Len() int64 } +// A Location represents a variable's location at a particular PC range. +// It becomes a location list entry in the DWARF. +type Location struct { + StartPC, EndPC int64 + Pieces []Piece +} + +// A Piece represents the location of a particular part of a variable. +// It becomes part of a location list entry (a DW_OP_piece) in the DWARF. +type Piece struct { + Length int64 + StackOffset int32 + RegNum int16 + Missing bool + OnStack bool // if true, RegNum is unset. +} + // A Var represents a local variable or a function parameter. type Var struct { - Name string - Abbrev int // Either DW_ABRV_AUTO or DW_ABRV_PARAM - StackOffset int32 - Scope int32 - Type Sym + Name string + Abbrev int // Either DW_ABRV_AUTO or DW_ABRV_PARAM + StackOffset int32 + LocationList []Location + Scope int32 + Type Sym } // A Scope represents a lexical scope. All variables declared within a @@ -205,7 +226,7 @@ const ( ) // Index into the abbrevs table below. -// Keep in sync with ispubname() and ispubtype() below. +// Keep in sync with ispubname() and ispubtype() in ld/dwarf.go. // ispubtype considers >= NULLTYPE public const ( DW_ABRV_NULL = iota @@ -709,31 +730,30 @@ func HasChildren(die *DWDie) bool { // PutFunc writes a DIE for a function to s. // It also writes child DIEs for each variable in vars. -func PutFunc(ctxt Context, s, ranges Sym, name string, external bool, startPC Sym, size int64, scopes []Scope) error { - Uleb128put(ctxt, s, DW_ABRV_FUNCTION) - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_string, DW_CLS_STRING, int64(len(name)), name) - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, 0, startPC) - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, size, startPC) - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_block1, DW_CLS_BLOCK, 1, []byte{DW_OP_call_frame_cfa}) +func PutFunc(ctxt Context, info, loc, ranges Sym, name string, external bool, startPC Sym, size int64, scopes []Scope) error { + Uleb128put(ctxt, info, DW_ABRV_FUNCTION) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_string, DW_CLS_STRING, int64(len(name)), name) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, 0, startPC) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, size, startPC) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_block1, DW_CLS_BLOCK, 1, []byte{DW_OP_call_frame_cfa}) var ev int64 if external { ev = 1 } - putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_flag, DW_CLS_FLAG, ev, 0) + putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_flag, DW_CLS_FLAG, ev, 0) if len(scopes) > 0 { var encbuf [20]byte - if putscope(ctxt, s, ranges, startPC, 0, scopes, encbuf[:0]) < int32(len(scopes)) { + if putscope(ctxt, info, loc, ranges, startPC, 0, scopes, encbuf[:0]) < int32(len(scopes)) { return errors.New("multiple toplevel scopes") } } - - Uleb128put(ctxt, s, 0) + Uleb128put(ctxt, info, 0) return nil } -func putscope(ctxt Context, s, ranges Sym, startPC Sym, curscope int32, scopes []Scope, encbuf []byte) int32 { +func putscope(ctxt Context, info, loc, ranges, startPC Sym, curscope int32, scopes []Scope, encbuf []byte) int32 { for _, v := range scopes[curscope].Vars { - putvar(ctxt, s, v, encbuf) + putvar(ctxt, info, loc, v, startPC, encbuf) } this := curscope curscope++ @@ -744,12 +764,12 @@ func putscope(ctxt Context, s, ranges Sym, startPC Sym, curscope int32, scopes [ } if len(scope.Ranges) == 1 { - Uleb128put(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE) - putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].Start, startPC) - putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].End, startPC) + Uleb128put(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE) + putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].Start, startPC) + putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].End, startPC) } else { - Uleb128put(ctxt, s, DW_ABRV_LEXICAL_BLOCK_RANGES) - putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_RANGES, DW_FORM_sec_offset, DW_CLS_PTR, ranges.Len(), ranges) + Uleb128put(ctxt, info, DW_ABRV_LEXICAL_BLOCK_RANGES) + putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_RANGES, DW_FORM_sec_offset, DW_CLS_PTR, ranges.Len(), ranges) ctxt.AddAddress(ranges, nil, -1) ctxt.AddAddress(ranges, startPC, 0) @@ -761,26 +781,66 @@ func putscope(ctxt Context, s, ranges Sym, startPC Sym, curscope int32, scopes [ ctxt.AddAddress(ranges, nil, 0) } - curscope = putscope(ctxt, s, ranges, startPC, curscope, scopes, encbuf) + curscope = putscope(ctxt, info, loc, ranges, startPC, curscope, scopes, encbuf) - Uleb128put(ctxt, s, 0) + Uleb128put(ctxt, info, 0) } return curscope } -func putvar(ctxt Context, s Sym, v *Var, encbuf []byte) { +func putvar(ctxt Context, info, loc Sym, v *Var, startPC Sym, encbuf []byte) { n := v.Name - Uleb128put(ctxt, s, int64(v.Abbrev)) - putattr(ctxt, s, v.Abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) - loc := append(encbuf[:0], DW_OP_call_frame_cfa) - if v.StackOffset != 0 { - loc = append(loc, DW_OP_consts) - loc = AppendSleb128(loc, int64(v.StackOffset)) - loc = append(loc, DW_OP_plus) + Uleb128put(ctxt, info, int64(v.Abbrev)) + putattr(ctxt, info, v.Abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) + if v.Abbrev == DW_ABRV_AUTO_LOCLIST || v.Abbrev == DW_ABRV_PARAM_LOCLIST { + putattr(ctxt, info, v.Abbrev, DW_FORM_sec_offset, DW_CLS_PTR, int64(loc.Len()), loc) + addLocList(ctxt, loc, startPC, v, encbuf) + } else { + loc := append(encbuf[:0], DW_OP_call_frame_cfa) + if v.StackOffset != 0 { + loc = append(loc, DW_OP_consts) + loc = AppendSleb128(loc, int64(v.StackOffset)) + loc = append(loc, DW_OP_plus) + } + putattr(ctxt, info, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) } - putattr(ctxt, s, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) - putattr(ctxt, s, v.Abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) + putattr(ctxt, info, v.Abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) +} + +func addLocList(ctxt Context, listSym, startPC Sym, v *Var, encbuf []byte) { + // Base address entry: max ptr followed by the base address. + ctxt.AddInt(listSym, ctxt.PtrSize(), ^0) + ctxt.AddAddress(listSym, startPC, 0) + for _, entry := range v.LocationList { + ctxt.AddInt(listSym, ctxt.PtrSize(), entry.StartPC) + ctxt.AddInt(listSym, ctxt.PtrSize(), entry.EndPC) + locBuf := encbuf[:0] + for _, piece := range entry.Pieces { + if !piece.Missing { + if piece.OnStack { + locBuf = append(locBuf, DW_OP_fbreg) + locBuf = AppendSleb128(locBuf, int64(piece.StackOffset)) + } else { + if piece.RegNum < 32 { + locBuf = append(locBuf, DW_OP_reg0+byte(piece.RegNum)) + } else { + locBuf = append(locBuf, DW_OP_regx) + locBuf = AppendUleb128(locBuf, uint64(piece.RegNum)) + } + } + } + if len(entry.Pieces) > 1 { + locBuf = append(locBuf, DW_OP_piece) + locBuf = AppendUleb128(locBuf, uint64(piece.Length)) + } + } + ctxt.AddInt(listSym, 2, int64(len(locBuf))) + ctxt.AddBytes(listSym, locBuf) + } + // End list + ctxt.AddInt(listSym, ctxt.PtrSize(), 0) + ctxt.AddInt(listSym, ctxt.PtrSize(), 0) } // VarsByOffset attaches the methods of sort.Interface to []*Var, diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index d49bc8c564..68e1b70ac0 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -330,7 +330,8 @@ type FuncInfo struct { Autom []*Auto Pcln Pcln - dwarfSym *LSym + dwarfInfoSym *LSym + dwarfLocSym *LSym dwarfRangesSym *LSym GCArgs LSym @@ -476,25 +477,26 @@ type Pcdata struct { // Link holds the context for writing object code from a compiler // to be linker input or for reading that input into the linker. type Link struct { - Headtype objabi.HeadType - Arch *LinkArch - Debugasm bool - Debugvlog bool - Debugpcln string - Flag_shared bool - Flag_dynlink bool - Flag_optimize bool - Bso *bufio.Writer - Pathname string - hashmu sync.Mutex // protects hash - hash map[string]*LSym // name -> sym mapping - statichash map[string]*LSym // name -> sym mapping for static syms - PosTable src.PosTable - InlTree InlTree // global inlining tree used by gc/inl.go - Imports []string - DiagFunc func(string, ...interface{}) - DebugInfo func(fn *LSym, curfn interface{}) []dwarf.Scope // if non-nil, curfn is a *gc.Node - Errors int + Headtype objabi.HeadType + Arch *LinkArch + Debugasm bool + Debugvlog bool + Debugpcln string + Flag_shared bool + Flag_dynlink bool + Flag_optimize bool + Flag_locationlists bool + Bso *bufio.Writer + Pathname string + hashmu sync.Mutex // protects hash + hash map[string]*LSym // name -> sym mapping + statichash map[string]*LSym // name -> sym mapping for static syms + PosTable src.PosTable + InlTree InlTree // global inlining tree used by gc/inl.go + Imports []string + DiagFunc func(string, ...interface{}) + DebugInfo func(fn *LSym, curfn interface{}) []dwarf.Scope // if non-nil, curfn is a *gc.Node + Errors int Framepointer_enabled bool @@ -533,9 +535,10 @@ func (ctxt *Link) FixedFrameSize() int64 { // LinkArch is the definition of a single architecture. type LinkArch struct { *sys.Arch - Init func(*Link) - Preprocess func(*Link, *LSym, ProgAlloc) - Assemble func(*Link, *LSym, ProgAlloc) - Progedit func(*Link, *Prog, ProgAlloc) - UnaryDst map[As]bool // Instruction takes one operand, a destination. + Init func(*Link) + Preprocess func(*Link, *LSym, ProgAlloc) + Assemble func(*Link, *LSym, ProgAlloc) + Progedit func(*Link, *Prog, ProgAlloc) + UnaryDst map[As]bool // Instruction takes one operand, a destination. + DWARFRegisters map[int16]int16 } diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index e309c5f7e7..539d013037 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -465,15 +465,18 @@ func (c dwCtxt) AddSectionOffset(s dwarf.Sym, size int, t interface{}, ofs int64 } // dwarfSym returns the DWARF symbols for TEXT symbol. -func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfRangesSym *LSym) { +func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfLocSym, dwarfRangesSym *LSym) { if s.Type != objabi.STEXT { ctxt.Diag("dwarfSym of non-TEXT %v", s) } - if s.Func.dwarfSym == nil { - s.Func.dwarfSym = ctxt.LookupDerived(s, dwarf.InfoPrefix+s.Name) + if s.Func.dwarfInfoSym == nil { + s.Func.dwarfInfoSym = ctxt.LookupDerived(s, dwarf.InfoPrefix+s.Name) + if ctxt.Flag_locationlists { + s.Func.dwarfLocSym = ctxt.LookupDerived(s, dwarf.LocPrefix+s.Name) + } s.Func.dwarfRangesSym = ctxt.LookupDerived(s, dwarf.RangePrefix+s.Name) } - return s.Func.dwarfSym, s.Func.dwarfRangesSym + return s.Func.dwarfInfoSym, s.Func.dwarfLocSym, s.Func.dwarfRangesSym } func (s *LSym) Len() int64 { @@ -483,15 +486,15 @@ func (s *LSym) Len() int64 { // populateDWARF fills in the DWARF Debugging Information Entries for TEXT symbol s. // The DWARFs symbol must already have been initialized in InitTextSym. func (ctxt *Link) populateDWARF(curfn interface{}, s *LSym) { - dsym, drsym := ctxt.dwarfSym(s) - if dsym.Size != 0 { + info, loc, ranges := ctxt.dwarfSym(s) + if info.Size != 0 { ctxt.Diag("makeFuncDebugEntry double process %v", s) } var scopes []dwarf.Scope if ctxt.DebugInfo != nil { scopes = ctxt.DebugInfo(s, curfn) } - err := dwarf.PutFunc(dwCtxt{ctxt}, dsym, drsym, s.Name, !s.Static(), s, s.Size, scopes) + err := dwarf.PutFunc(dwCtxt{ctxt}, info, loc, ranges, s.Name, !s.Static(), s, s.Size, scopes) if err != nil { ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err) } diff --git a/src/cmd/internal/obj/plist.go b/src/cmd/internal/obj/plist.go index 861da88703..1bb05aedfa 100644 --- a/src/cmd/internal/obj/plist.go +++ b/src/cmd/internal/obj/plist.go @@ -136,13 +136,17 @@ func (ctxt *Link) InitTextSym(s *LSym, flag int) { ctxt.Text = append(ctxt.Text, s) // Set up DWARF entries for s. - dsym, drsym := ctxt.dwarfSym(s) - dsym.Type = objabi.SDWARFINFO - dsym.Set(AttrDuplicateOK, s.DuplicateOK()) - drsym.Type = objabi.SDWARFRANGE - drsym.Set(AttrDuplicateOK, s.DuplicateOK()) - ctxt.Data = append(ctxt.Data, dsym) - ctxt.Data = append(ctxt.Data, drsym) + info, loc, ranges := ctxt.dwarfSym(s) + info.Type = objabi.SDWARFINFO + info.Set(AttrDuplicateOK, s.DuplicateOK()) + if loc != nil { + loc.Type = objabi.SDWARFLOC + loc.Set(AttrDuplicateOK, s.DuplicateOK()) + ctxt.Data = append(ctxt.Data, loc) + } + ranges.Type = objabi.SDWARFRANGE + ranges.Set(AttrDuplicateOK, s.DuplicateOK()) + ctxt.Data = append(ctxt.Data, info, ranges) // Set up the function's gcargs and gclocals. // They will be filled in later if needed. diff --git a/src/cmd/internal/obj/x86/a.out.go b/src/cmd/internal/obj/x86/a.out.go index 04f9ef68a4..92d358ba4e 100644 --- a/src/cmd/internal/obj/x86/a.out.go +++ b/src/cmd/internal/obj/x86/a.out.go @@ -1006,3 +1006,120 @@ const ( T_64 = 1 << 6 T_GOTYPE = 1 << 7 ) + +// https://www.uclibc.org/docs/psABI-x86_64.pdf, figure 3.36 +var AMD64DWARFRegisters = map[int16]int16{ + REG_AX: 0, + REG_DX: 1, + REG_CX: 2, + REG_BX: 3, + REG_SI: 4, + REG_DI: 5, + REG_BP: 6, + REG_SP: 7, + REG_R8: 8, + REG_R9: 9, + REG_R10: 10, + REG_R11: 11, + REG_R12: 12, + REG_R13: 13, + REG_R14: 14, + REG_R15: 15, + // 16 is "Return Address RA", whatever that is. + // XMM registers. %xmmN => XN. + REG_X0: 17, + REG_X1: 18, + REG_X2: 19, + REG_X3: 20, + REG_X4: 21, + REG_X5: 22, + REG_X6: 23, + REG_X7: 24, + REG_X8: 25, + REG_X9: 26, + REG_X10: 27, + REG_X11: 28, + REG_X12: 29, + REG_X13: 30, + REG_X14: 31, + REG_X15: 32, + // ST registers. %stN => FN. + REG_F0: 33, + REG_F1: 34, + REG_F2: 35, + REG_F3: 36, + REG_F4: 37, + REG_F5: 38, + REG_F6: 39, + REG_F7: 40, + // MMX registers. %mmN => MN. + REG_M0: 41, + REG_M1: 42, + REG_M2: 43, + REG_M3: 44, + REG_M4: 45, + REG_M5: 46, + REG_M6: 47, + REG_M7: 48, + // 48 is flags, which doesn't have a name. + REG_ES: 50, + REG_CS: 51, + REG_SS: 52, + REG_DS: 53, + REG_FS: 54, + REG_GS: 55, + // 58 and 59 are {fs,gs}base, which don't have names. + REG_TR: 62, + REG_LDTR: 63, + // 64-66 are mxcsr, fcw, fsw, which don't have names. +} + +// https://www.uclibc.org/docs/psABI-i386.pdf, table 2.14 +var X86DWARFRegisters = map[int16]int16{ + REG_AX: 0, + REG_CX: 1, + REG_DX: 2, + REG_BX: 3, + REG_SP: 4, + REG_BP: 5, + REG_SI: 6, + REG_DI: 7, + // 8 is "Return Address RA", whatever that is. + // 9 is flags, which doesn't have a name. + // ST registers. %stN => FN. + REG_F0: 11, + REG_F1: 12, + REG_F2: 13, + REG_F3: 14, + REG_F4: 15, + REG_F5: 16, + REG_F6: 17, + REG_F7: 18, + // XMM registers. %xmmN => XN. + REG_X0: 21, + REG_X1: 22, + REG_X2: 23, + REG_X3: 24, + REG_X4: 25, + REG_X5: 26, + REG_X6: 27, + REG_X7: 28, + // MMX registers. %mmN => MN. + REG_M0: 29, + REG_M1: 30, + REG_M2: 31, + REG_M3: 32, + REG_M4: 33, + REG_M5: 34, + REG_M6: 35, + REG_M7: 36, + // 39 is mxcsr, which doesn't have a name. + REG_ES: 40, + REG_CS: 41, + REG_SS: 42, + REG_DS: 43, + REG_FS: 44, + REG_GS: 45, + REG_TR: 48, + REG_LDTR: 49, +} diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index d34f0aeaa6..27873e0824 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -1231,28 +1231,31 @@ var unaryDst = map[obj.As]bool{ } var Linkamd64 = obj.LinkArch{ - Arch: sys.ArchAMD64, - Init: instinit, - Preprocess: preprocess, - Assemble: span6, - Progedit: progedit, - UnaryDst: unaryDst, + Arch: sys.ArchAMD64, + Init: instinit, + Preprocess: preprocess, + Assemble: span6, + Progedit: progedit, + UnaryDst: unaryDst, + DWARFRegisters: AMD64DWARFRegisters, } var Linkamd64p32 = obj.LinkArch{ - Arch: sys.ArchAMD64P32, - Init: instinit, - Preprocess: preprocess, - Assemble: span6, - Progedit: progedit, - UnaryDst: unaryDst, + Arch: sys.ArchAMD64P32, + Init: instinit, + Preprocess: preprocess, + Assemble: span6, + Progedit: progedit, + UnaryDst: unaryDst, + DWARFRegisters: AMD64DWARFRegisters, } var Link386 = obj.LinkArch{ - Arch: sys.Arch386, - Init: instinit, - Preprocess: preprocess, - Assemble: span6, - Progedit: progedit, - UnaryDst: unaryDst, + Arch: sys.Arch386, + Init: instinit, + Preprocess: preprocess, + Assemble: span6, + Progedit: progedit, + UnaryDst: unaryDst, + DWARFRegisters: AMD64DWARFRegisters, } diff --git a/src/cmd/internal/objabi/symkind.go b/src/cmd/internal/objabi/symkind.go index b037e9e4ed..ac91824d17 100644 --- a/src/cmd/internal/objabi/symkind.go +++ b/src/cmd/internal/objabi/symkind.go @@ -57,4 +57,5 @@ const ( // Debugging data SDWARFINFO SDWARFRANGE + SDWARFLOC ) diff --git a/src/cmd/internal/objabi/symkind_string.go b/src/cmd/internal/objabi/symkind_string.go index 5123dc7097..3064c8ee05 100644 --- a/src/cmd/internal/objabi/symkind_string.go +++ b/src/cmd/internal/objabi/symkind_string.go @@ -4,9 +4,9 @@ package objabi import "fmt" -const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFINFOSDWARFRANGE" +const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFINFOSDWARFRANGESDWARFLOC" -var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 61, 72} +var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 61, 72, 81} func (i SymKind) String() string { if i >= SymKind(len(_SymKind_index)-1) { diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index bf219f7b62..1d053d23b7 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1862,6 +1862,8 @@ func (ctxt *Link) dodata() { sect = addsection(&Segdwarf, ".debug_info", 04) case SDWARFRANGE: sect = addsection(&Segdwarf, ".debug_ranges", 04) + case SDWARFLOC: + sect = addsection(&Segdwarf, ".debug_loc", 04) default: Errorf(dwarfp[i], "unknown DWARF section %v", curType) } diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 9b11fdcff6..b6fb1bb5c1 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -1579,10 +1579,35 @@ func dwarfgeneratedebugsyms(ctxt *Link) { syms = writearanges(ctxt, syms) syms = writegdbscript(ctxt, syms) syms = append(syms, infosyms...) + syms = collectlocs(ctxt, syms, funcs) syms = writeranges(ctxt, syms) dwarfp = syms } +func collectlocs(ctxt *Link, syms []*Symbol, funcs []*Symbol) []*Symbol { + empty := true + for _, fn := range funcs { + for _, reloc := range fn.R { + if reloc.Type == objabi.R_DWARFREF && strings.HasPrefix(reloc.Sym.Name, dwarf.LocPrefix) { + reloc.Sym.Attr |= AttrReachable | AttrNotInSymbolTable + syms = append(syms, reloc.Sym) + empty = false + // One location list entry per function, but many relocations to it. Don't duplicate. + break + } + } + } + // Don't emit .debug_loc if it's empty -- it makes the ARM linker mad. + if !empty { + locsym := ctxt.Syms.Lookup(".debug_loc", 0) + locsym.R = locsym.R[:0] + locsym.Type = SDWARFLOC + locsym.Attr |= AttrReachable + syms = append(syms, locsym) + } + return syms +} + /* * Elf. */ @@ -1595,6 +1620,7 @@ func dwarfaddshstrings(ctxt *Link, shstrtab *Symbol) { Addstring(shstrtab, ".debug_aranges") Addstring(shstrtab, ".debug_frame") Addstring(shstrtab, ".debug_info") + Addstring(shstrtab, ".debug_loc") Addstring(shstrtab, ".debug_line") Addstring(shstrtab, ".debug_pubnames") Addstring(shstrtab, ".debug_pubtypes") @@ -1602,6 +1628,7 @@ func dwarfaddshstrings(ctxt *Link, shstrtab *Symbol) { Addstring(shstrtab, ".debug_ranges") if Linkmode == LinkExternal { Addstring(shstrtab, elfRelType+".debug_info") + Addstring(shstrtab, elfRelType+".debug_loc") Addstring(shstrtab, elfRelType+".debug_aranges") Addstring(shstrtab, elfRelType+".debug_line") Addstring(shstrtab, elfRelType+".debug_frame") @@ -1628,6 +1655,10 @@ func dwarfaddelfsectionsyms(ctxt *Link) { putelfsectionsym(sym, sym.Sect.Elfsect.shnum) sym = ctxt.Syms.Lookup(".debug_frame", 0) putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + sym = ctxt.Syms.Lookup(".debug_loc", 0) + if sym.Sect != nil { + putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + } sym = ctxt.Syms.Lookup(".debug_ranges", 0) if sym.Sect != nil { putelfsectionsym(sym, sym.Sect.Elfsect.shnum) diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 0fc947fec2..78f8d6e70e 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -1808,7 +1808,7 @@ func elfrelocsect(ctxt *Link, sect *Section, syms []*Symbol) { continue } if r.Xsym == nil { - Errorf(sym, "missing xsym in relocation") + Errorf(sym, "missing xsym in relocation %#v %#v", r.Sym.Name, sym) continue } if r.Xsym.ElfsymForReloc() == 0 { @@ -2596,12 +2596,9 @@ elfobj: elfshreloc(sect) } for _, s := range dwarfp { - if len(s.R) > 0 || s.Type == SDWARFINFO { + if len(s.R) > 0 || s.Type == SDWARFINFO || s.Type == SDWARFLOC { elfshreloc(s.Sect) } - if s.Type == SDWARFINFO { - break - } } // add a .note.GNU-stack section to mark the stack as non-executable sh := elfshname(".note.GNU-stack") diff --git a/src/cmd/link/internal/ld/symkind.go b/src/cmd/link/internal/ld/symkind.go index c057f6cd0c..5ac04cf45a 100644 --- a/src/cmd/link/internal/ld/symkind.go +++ b/src/cmd/link/internal/ld/symkind.go @@ -105,6 +105,7 @@ const ( SDWARFSECT SDWARFINFO SDWARFRANGE + SDWARFLOC SSUB = SymKind(1 << 8) SMASK = SymKind(SSUB - 1) SHIDDEN = SymKind(1 << 9) @@ -124,6 +125,7 @@ var abiSymKindToSymKind = [...]SymKind{ STLSBSS, SDWARFINFO, SDWARFRANGE, + SDWARFLOC, } // readOnly are the symbol kinds that form read-only sections. In some diff --git a/src/cmd/link/internal/ld/symkind_string.go b/src/cmd/link/internal/ld/symkind_string.go index 2178b50c36..87da3c40bb 100644 --- a/src/cmd/link/internal/ld/symkind_string.go +++ b/src/cmd/link/internal/ld/symkind_string.go @@ -4,9 +4,9 @@ package ld import "fmt" -const _SymKind_name = "SxxxSTEXTSELFRXSECTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSMACHOPLTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASBSSSNOPTRBSSSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILESFILEPATHSCONSTSDYNIMPORTSHOSTOBJSDWARFSECTSDWARFINFOSDWARFRANGE" +const _SymKind_name = "SxxxSTEXTSELFRXSECTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSMACHOPLTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASBSSSNOPTRBSSSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILESFILEPATHSCONSTSDYNIMPORTSHOSTOBJSDWARFSECTSDWARFINFOSDWARFRANGESDWARFLOC" -var _SymKind_index = [...]uint16{0, 4, 9, 19, 24, 31, 40, 47, 54, 61, 69, 79, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 214, 220, 229, 237, 244, 254, 262, 267, 271, 280, 287, 292, 304, 316, 333, 350, 355, 364, 370, 380, 388, 398, 408, 419} +var _SymKind_index = [...]uint16{0, 4, 9, 19, 24, 31, 40, 47, 54, 61, 69, 79, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 214, 220, 229, 237, 244, 254, 262, 267, 271, 280, 287, 292, 304, 316, 333, 350, 355, 364, 370, 380, 388, 398, 408, 419, 428} func (i SymKind) String() string { if i < 0 || i >= SymKind(len(_SymKind_index)-1) {