Skip to content

Commit

Permalink
cmd/compile,link: generate PC-value tables with inlining information
Browse files Browse the repository at this point in the history
In order to generate accurate tracebacks, the runtime needs to know the
inlined call stack for a given PC. This creates two tables per function
for this purpose. The first table is the inlining tree (stored in the
function's funcdata), which has a node containing the file, line, and
function name for every inlined call. The second table is a PC-value
table that maps each PC to a node in the inlining tree (or -1 if the PC
is not the result of inlining).

To give the appearance that inlining hasn't happened, the runtime also
needs the original source position information of inlined AST nodes.
Previously the compiler plastered over the line numbers of inlined AST
nodes with the line number of the call. This meant that the PC-line
table mapped each PC to line number of the outermost call in its inlined
call stack, with no way to access the innermost line number.

Now the compiler retains line numbers of inlined AST nodes and writes
the innermost source position information to the PC-line and PC-file
tables. Some tools and tests expect to see outermost line numbers, so we
provide the OutermostLine function for displaying line info.

To keep track of the inlined call stack for an AST node, we extend the
src.PosBase type with an index into a global inlining tree. Every time
the compiler inlines a call, it creates a node in the global inlining
tree for the call, and writes its index to the PosBase of every inlined
AST node. The parent of this node is the inlining tree index of the
call. -1 signifies no parent.

For each function, the compiler creates a local inlining tree and a
PC-value table mapping each PC to an index in the local tree.  These are
written to an object file, which is read by the linker.  The linker
re-encodes these tables compactly by deduplicating function names and
file names.

This change increases the size of binaries by 4-5%. For example, this is
how the go1 benchmark binary is impacted by this change:

section             old bytes   new bytes   delta
.text               3.49M ± 0%  3.49M ± 0%   +0.06%
.rodata             1.12M ± 0%  1.21M ± 0%   +8.21%
.gopclntab          1.50M ± 0%  1.68M ± 0%  +11.89%
.debug_line          338k ± 0%   435k ± 0%  +28.78%
Total               9.21M ± 0%  9.58M ± 0%   +4.01%

Updates #19348.

Change-Id: Ic4f180c3b516018138236b0c35e0218270d957d3
Reviewed-on: https://go-review.googlesource.com/37231
Run-TryBot: David Lazar <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Austin Clements <[email protected]>
  • Loading branch information
davidlazar committed Mar 3, 2017
1 parent ed70f37 commit 699175a
Show file tree
Hide file tree
Showing 17 changed files with 354 additions and 25 deletions.
55 changes: 42 additions & 13 deletions src/cmd/compile/internal/gc/inl.go
Original file line number Diff line number Diff line change
Expand Up @@ -843,12 +843,23 @@ func mkinlcall1(n *Node, fn *Node, isddd bool) *Node {
call.Type = n.Type
call.Typecheck = 1

// Hide the args from setlno -- the parameters to the inlined
// Hide the args from setPos -- the parameters to the inlined
// call already have good line numbers that should be preserved.
args := as.Rlist
as.Rlist.Set(nil)

setlno(call, n.Pos)
// Rewrite the line information for the inlined AST.
parent := -1
callBase := Ctxt.PosTable.Pos(n.Pos).Base()
if callBase != nil {
parent = callBase.InliningIndex()
}
newIndex := Ctxt.InlTree.Add(parent, n.Pos, Linksym(fn.Sym))
setpos := &setPos{
bases: make(map[*src.PosBase]*src.PosBase),
newInlIndex: newIndex,
}
setpos.node(call)

as.Rlist.Set(args.Slice())

Expand Down Expand Up @@ -1024,29 +1035,47 @@ func (subst *inlsubst) node(n *Node) *Node {
}
}

// Plaster over linenumbers
func setlnolist(ll Nodes, lno src.XPos) {
// setPos is a visitor to update position info with a new inlining index.
type setPos struct {
bases map[*src.PosBase]*src.PosBase
newInlIndex int
}

func (s *setPos) nodelist(ll Nodes) {
for _, n := range ll.Slice() {
setlno(n, lno)
s.node(n)
}
}

func setlno(n *Node, lno src.XPos) {
func (s *setPos) node(n *Node) {
if n == nil {
return
}

// don't clobber names, unless they're freshly synthesized
if n.Op != ONAME || !n.Pos.IsKnown() {
n.Pos = lno
n.Pos = s.updatedPos(n)
}

setlno(n.Left, lno)
setlno(n.Right, lno)
setlnolist(n.List, lno)
setlnolist(n.Rlist, lno)
setlnolist(n.Ninit, lno)
setlnolist(n.Nbody, lno)
s.node(n.Left)
s.node(n.Right)
s.nodelist(n.List)
s.nodelist(n.Rlist)
s.nodelist(n.Ninit)
s.nodelist(n.Nbody)
}

func (s *setPos) updatedPos(n *Node) src.XPos {
pos := Ctxt.PosTable.Pos(n.Pos)
oldbase := pos.Base() // can be nil
newbase := s.bases[oldbase]
if newbase == nil {
newbase = src.NewInliningBase(oldbase, s.newInlIndex)
pos.SetBase(newbase)
s.bases[oldbase] = newbase
}
pos.SetBase(newbase)
return Ctxt.PosTable.XPos(pos)
}

func (n *Node) isMethodCalledAsFunction() bool {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/gc/subr.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func hcrash() {
}

func linestr(pos src.XPos) string {
return Ctxt.PosTable.Pos(pos).String()
return Ctxt.OutermostPos(pos).String()
}

// lasterror keeps track of the most recently issued error.
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/compile/internal/gc/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"runtime/pprof"
)

// Line returns n's position as a string. If n has been inlined,
// it uses the outermost position where n has been inlined.
func (n *Node) Line() string {
return linestr(n.Pos)
}
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/internal/obj/funcdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ package obj

const (
PCDATA_StackMapIndex = 0
PCDATA_InlTreeIndex = 1
FUNCDATA_ArgsPointerMaps = 0
FUNCDATA_LocalsPointerMaps = 1
FUNCDATA_InlTree = 2

// ArgsSizeUnknown is set in Func.argsize to mark all functions
// whose argument size is unknown (C vararg functions, and
Expand Down
78 changes: 78 additions & 0 deletions src/cmd/internal/obj/inl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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 obj

import "cmd/internal/src"

// InlTree s a collection of inlined calls. The Parent field of an
// InlinedCall is the index of another InlinedCall in InlTree.
//
// The compiler maintains a global inlining tree and adds a node to it
// every time a function is inlined. For example, suppose f() calls g()
// and g has two calls to h(), and that f, g, and h are inlineable:
//
// 1 func main() {
// 2 f()
// 3 }
// 4 func f() {
// 5 g()
// 6 }
// 7 func g() {
// 8 h()
// 9 h()
// 10 }
//
// Assuming the global tree starts empty, inlining will produce the
// following tree:
//
// []InlinedCall{
// {Parent: -1, Func: "f", Pos: <line 2>},
// {Parent: 0, Func: "g", Pos: <line 5>},
// {Parent: 1, Func: "h", Pos: <line 8>},
// {Parent: 1, Func: "h", Pos: <line 9>},
// }
//
// The nodes of h inlined into main will have inlining indexes 2 and 3.
//
// Eventually, the compiler extracts a per-function inlining tree from
// the global inlining tree (see pcln.go).
type InlTree struct {
nodes []InlinedCall
}

// InlinedCall is a node in an InlTree.
type InlinedCall struct {
Parent int // index of the parent in the InlTree or < 0 if outermost call
Pos src.XPos // position of the inlined call
Func *LSym // function that was inlined
}

// Add adds a new call to the tree, returning its index.
func (tree *InlTree) Add(parent int, pos src.XPos, func_ *LSym) int {
r := len(tree.nodes)
call := InlinedCall{
Parent: parent,
Pos: pos,
Func: func_,
}
tree.nodes = append(tree.nodes, call)
return r
}

// OutermostPos returns the outermost position corresponding to xpos,
// which is where xpos was ultimately inlined to. In the example for
// InlTree, main() contains inlined AST nodes from h(), but the
// outermost position for those nodes is line 2.
func (ctxt *Link) OutermostPos(xpos src.XPos) src.Pos {
pos := ctxt.PosTable.Pos(xpos)

outerxpos := xpos
for ix := pos.Base().InliningIndex(); ix >= 0; {
call := ctxt.InlTree.nodes[ix]
ix = call.Parent
outerxpos = call.Pos
}
return ctxt.PosTable.Pos(outerxpos)
}
3 changes: 3 additions & 0 deletions src/cmd/internal/obj/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,12 +397,14 @@ type Pcln struct {
Pcsp Pcdata
Pcfile Pcdata
Pcline Pcdata
Pcinline Pcdata
Pcdata []Pcdata
Funcdata []*LSym
Funcdataoff []int64
File []*LSym
Lastfile *LSym
Lastindex int
InlTree InlTree // per-function inlining tree extracted from the global tree
}

// A SymKind describes the kind of memory represented by a symbol.
Expand Down Expand Up @@ -728,6 +730,7 @@ type Link struct {
Pathname string
Hash map[SymVer]*LSym
PosTable src.PosTable
InlTree InlTree // global inlining tree used by gc/inl.go
Imports []string
Sym_div *LSym
Sym_divu *LSym
Expand Down
19 changes: 19 additions & 0 deletions src/cmd/internal/obj/objfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,16 @@
// - pcsp [data block]
// - pcfile [data block]
// - pcline [data block]
// - pcinline [data block]
// - npcdata [int]
// - pcdata [npcdata data blocks]
// - nfuncdata [int]
// - funcdata [nfuncdata symref index]
// - funcdatasym [nfuncdata ints]
// - nfile [int]
// - file [nfile symref index]
// - ninlinedcall [int]
// - inlinedcall [ninlinedcall int symref int symref]
//
// The file layout and meaning of type integers are architecture-independent.
//
Expand Down Expand Up @@ -156,6 +159,7 @@ func (w *objWriter) addLengths(s *LSym) {
data += len(pc.Pcsp.P)
data += len(pc.Pcfile.P)
data += len(pc.Pcline.P)
data += len(pc.Pcinline.P)
for i := 0; i < len(pc.Pcdata); i++ {
data += len(pc.Pcdata[i].P)
}
Expand Down Expand Up @@ -227,6 +231,7 @@ func WriteObjFile(ctxt *Link, b *bufio.Writer) {
w.wr.Write(pc.Pcsp.P)
w.wr.Write(pc.Pcfile.P)
w.wr.Write(pc.Pcline.P)
w.wr.Write(pc.Pcinline.P)
for i := 0; i < len(pc.Pcdata); i++ {
w.wr.Write(pc.Pcdata[i].P)
}
Expand Down Expand Up @@ -300,6 +305,11 @@ func (w *objWriter) writeRefs(s *LSym) {
for _, f := range pc.File {
w.writeRef(f, true)
}
for _, call := range pc.InlTree.nodes {
w.writeRef(call.Func, false)
f, _ := linkgetlineFromPos(w.ctxt, call.Pos)
w.writeRef(f, true)
}
}
}

Expand Down Expand Up @@ -452,6 +462,7 @@ func (w *objWriter) writeSym(s *LSym) {
w.writeInt(int64(len(pc.Pcsp.P)))
w.writeInt(int64(len(pc.Pcfile.P)))
w.writeInt(int64(len(pc.Pcline.P)))
w.writeInt(int64(len(pc.Pcinline.P)))
w.writeInt(int64(len(pc.Pcdata)))
for i := 0; i < len(pc.Pcdata); i++ {
w.writeInt(int64(len(pc.Pcdata[i].P)))
Expand All @@ -467,6 +478,14 @@ func (w *objWriter) writeSym(s *LSym) {
for _, f := range pc.File {
w.writeRefIndex(f)
}
w.writeInt(int64(len(pc.InlTree.nodes)))
for _, call := range pc.InlTree.nodes {
w.writeInt(int64(call.Parent))
f, l := linkgetlineFromPos(w.ctxt, call.Pos)
w.writeRefIndex(f)
w.writeInt(int64(l))
w.writeRefIndex(call.Func)
}
}

func (w *objWriter) writeInt(sval int64) {
Expand Down
60 changes: 60 additions & 0 deletions src/cmd/internal/obj/pcln.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,62 @@ func pctofileline(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg
return int32(i)
}

// pcinlineState holds the state used to create a function's inlining
// tree and the PC-value table that maps PCs to nodes in that tree.
type pcinlineState struct {
globalToLocal map[int]int
localTree InlTree
}

// addBranch adds a branch from the global inlining tree in ctxt to
// the function's local inlining tree, returning the index in the local tree.
func (s *pcinlineState) addBranch(ctxt *Link, globalIndex int) int {
if globalIndex < 0 {
return -1
}

localIndex, ok := s.globalToLocal[globalIndex]
if ok {
return localIndex
}

// Since tracebacks don't include column information, we could
// use one node for multiple calls of the same function on the
// same line (e.g., f(x) + f(y)). For now, we use one node for
// each inlined call.
call := ctxt.InlTree.nodes[globalIndex]
call.Parent = s.addBranch(ctxt, call.Parent)
localIndex = len(s.localTree.nodes)
s.localTree.nodes = append(s.localTree.nodes, call)
s.globalToLocal[globalIndex] = localIndex
return localIndex
}

// pctoinline computes the index into the local inlining tree to use at p.
// If p is not the result of inlining, pctoinline returns -1. Because p.Pos
// applies to p, phase == 0 (before p) takes care of the update.
func (s *pcinlineState) pctoinline(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
if phase == 1 {
return oldval
}

posBase := ctxt.PosTable.Pos(p.Pos).Base()
if posBase == nil {
return -1
}

globalIndex := posBase.InliningIndex()
if globalIndex < 0 {
return -1
}

if s.globalToLocal == nil {
s.globalToLocal = make(map[int]int)
}

return int32(s.addBranch(ctxt, globalIndex))
}

// pctospadj computes the sp adjustment in effect.
// It is oldval plus any adjustment made by p itself.
// The adjustment by p takes effect only after p, so we
Expand Down Expand Up @@ -238,6 +294,10 @@ func linkpcln(ctxt *Link, cursym *LSym) {
funcpctab(ctxt, &pcln.Pcfile, cursym, "pctofile", pctofileline, pcln)
funcpctab(ctxt, &pcln.Pcline, cursym, "pctoline", pctofileline, nil)

pcinlineState := new(pcinlineState)
funcpctab(ctxt, &pcln.Pcinline, cursym, "pctoinline", pcinlineState.pctoinline, nil)
pcln.InlTree = pcinlineState.localTree

// tabulate which pc and func data we have.
havepc := make([]uint32, (npcdata+31)/32)
havefunc := make([]uint32, (nfuncdata+31)/32)
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/internal/obj/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func Getgoextlinkenabled() string {
}

func (p *Prog) Line() string {
return p.Ctxt.PosTable.Pos(p.Pos).String()
return p.Ctxt.OutermostPos(p.Pos).String()
}

var armCondCode = []string{
Expand Down
Loading

0 comments on commit 699175a

Please sign in to comment.