Skip to content

Commit 699175a

Browse files
committed
cmd/compile,link: generate PC-value tables with inlining information
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 <lazard@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com>
1 parent ed70f37 commit 699175a

File tree

17 files changed

+354
-25
lines changed

17 files changed

+354
-25
lines changed

src/cmd/compile/internal/gc/inl.go

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -843,12 +843,23 @@ func mkinlcall1(n *Node, fn *Node, isddd bool) *Node {
843843
call.Type = n.Type
844844
call.Typecheck = 1
845845

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

851-
setlno(call, n.Pos)
851+
// Rewrite the line information for the inlined AST.
852+
parent := -1
853+
callBase := Ctxt.PosTable.Pos(n.Pos).Base()
854+
if callBase != nil {
855+
parent = callBase.InliningIndex()
856+
}
857+
newIndex := Ctxt.InlTree.Add(parent, n.Pos, Linksym(fn.Sym))
858+
setpos := &setPos{
859+
bases: make(map[*src.PosBase]*src.PosBase),
860+
newInlIndex: newIndex,
861+
}
862+
setpos.node(call)
852863

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

@@ -1024,29 +1035,47 @@ func (subst *inlsubst) node(n *Node) *Node {
10241035
}
10251036
}
10261037

1027-
// Plaster over linenumbers
1028-
func setlnolist(ll Nodes, lno src.XPos) {
1038+
// setPos is a visitor to update position info with a new inlining index.
1039+
type setPos struct {
1040+
bases map[*src.PosBase]*src.PosBase
1041+
newInlIndex int
1042+
}
1043+
1044+
func (s *setPos) nodelist(ll Nodes) {
10291045
for _, n := range ll.Slice() {
1030-
setlno(n, lno)
1046+
s.node(n)
10311047
}
10321048
}
10331049

1034-
func setlno(n *Node, lno src.XPos) {
1050+
func (s *setPos) node(n *Node) {
10351051
if n == nil {
10361052
return
10371053
}
10381054

10391055
// don't clobber names, unless they're freshly synthesized
10401056
if n.Op != ONAME || !n.Pos.IsKnown() {
1041-
n.Pos = lno
1057+
n.Pos = s.updatedPos(n)
10421058
}
10431059

1044-
setlno(n.Left, lno)
1045-
setlno(n.Right, lno)
1046-
setlnolist(n.List, lno)
1047-
setlnolist(n.Rlist, lno)
1048-
setlnolist(n.Ninit, lno)
1049-
setlnolist(n.Nbody, lno)
1060+
s.node(n.Left)
1061+
s.node(n.Right)
1062+
s.nodelist(n.List)
1063+
s.nodelist(n.Rlist)
1064+
s.nodelist(n.Ninit)
1065+
s.nodelist(n.Nbody)
1066+
}
1067+
1068+
func (s *setPos) updatedPos(n *Node) src.XPos {
1069+
pos := Ctxt.PosTable.Pos(n.Pos)
1070+
oldbase := pos.Base() // can be nil
1071+
newbase := s.bases[oldbase]
1072+
if newbase == nil {
1073+
newbase = src.NewInliningBase(oldbase, s.newInlIndex)
1074+
pos.SetBase(newbase)
1075+
s.bases[oldbase] = newbase
1076+
}
1077+
pos.SetBase(newbase)
1078+
return Ctxt.PosTable.XPos(pos)
10501079
}
10511080

10521081
func (n *Node) isMethodCalledAsFunction() bool {

src/cmd/compile/internal/gc/subr.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func hcrash() {
8787
}
8888

8989
func linestr(pos src.XPos) string {
90-
return Ctxt.PosTable.Pos(pos).String()
90+
return Ctxt.OutermostPos(pos).String()
9191
}
9292

9393
// lasterror keeps track of the most recently issued error.

src/cmd/compile/internal/gc/util.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"runtime/pprof"
1111
)
1212

13+
// Line returns n's position as a string. If n has been inlined,
14+
// it uses the outermost position where n has been inlined.
1315
func (n *Node) Line() string {
1416
return linestr(n.Pos)
1517
}

src/cmd/internal/obj/funcdata.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ package obj
1212

1313
const (
1414
PCDATA_StackMapIndex = 0
15+
PCDATA_InlTreeIndex = 1
1516
FUNCDATA_ArgsPointerMaps = 0
1617
FUNCDATA_LocalsPointerMaps = 1
18+
FUNCDATA_InlTree = 2
1719

1820
// ArgsSizeUnknown is set in Func.argsize to mark all functions
1921
// whose argument size is unknown (C vararg functions, and

src/cmd/internal/obj/inl.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package obj
6+
7+
import "cmd/internal/src"
8+
9+
// InlTree s a collection of inlined calls. The Parent field of an
10+
// InlinedCall is the index of another InlinedCall in InlTree.
11+
//
12+
// The compiler maintains a global inlining tree and adds a node to it
13+
// every time a function is inlined. For example, suppose f() calls g()
14+
// and g has two calls to h(), and that f, g, and h are inlineable:
15+
//
16+
// 1 func main() {
17+
// 2 f()
18+
// 3 }
19+
// 4 func f() {
20+
// 5 g()
21+
// 6 }
22+
// 7 func g() {
23+
// 8 h()
24+
// 9 h()
25+
// 10 }
26+
//
27+
// Assuming the global tree starts empty, inlining will produce the
28+
// following tree:
29+
//
30+
// []InlinedCall{
31+
// {Parent: -1, Func: "f", Pos: <line 2>},
32+
// {Parent: 0, Func: "g", Pos: <line 5>},
33+
// {Parent: 1, Func: "h", Pos: <line 8>},
34+
// {Parent: 1, Func: "h", Pos: <line 9>},
35+
// }
36+
//
37+
// The nodes of h inlined into main will have inlining indexes 2 and 3.
38+
//
39+
// Eventually, the compiler extracts a per-function inlining tree from
40+
// the global inlining tree (see pcln.go).
41+
type InlTree struct {
42+
nodes []InlinedCall
43+
}
44+
45+
// InlinedCall is a node in an InlTree.
46+
type InlinedCall struct {
47+
Parent int // index of the parent in the InlTree or < 0 if outermost call
48+
Pos src.XPos // position of the inlined call
49+
Func *LSym // function that was inlined
50+
}
51+
52+
// Add adds a new call to the tree, returning its index.
53+
func (tree *InlTree) Add(parent int, pos src.XPos, func_ *LSym) int {
54+
r := len(tree.nodes)
55+
call := InlinedCall{
56+
Parent: parent,
57+
Pos: pos,
58+
Func: func_,
59+
}
60+
tree.nodes = append(tree.nodes, call)
61+
return r
62+
}
63+
64+
// OutermostPos returns the outermost position corresponding to xpos,
65+
// which is where xpos was ultimately inlined to. In the example for
66+
// InlTree, main() contains inlined AST nodes from h(), but the
67+
// outermost position for those nodes is line 2.
68+
func (ctxt *Link) OutermostPos(xpos src.XPos) src.Pos {
69+
pos := ctxt.PosTable.Pos(xpos)
70+
71+
outerxpos := xpos
72+
for ix := pos.Base().InliningIndex(); ix >= 0; {
73+
call := ctxt.InlTree.nodes[ix]
74+
ix = call.Parent
75+
outerxpos = call.Pos
76+
}
77+
return ctxt.PosTable.Pos(outerxpos)
78+
}

src/cmd/internal/obj/link.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,12 +397,14 @@ type Pcln struct {
397397
Pcsp Pcdata
398398
Pcfile Pcdata
399399
Pcline Pcdata
400+
Pcinline Pcdata
400401
Pcdata []Pcdata
401402
Funcdata []*LSym
402403
Funcdataoff []int64
403404
File []*LSym
404405
Lastfile *LSym
405406
Lastindex int
407+
InlTree InlTree // per-function inlining tree extracted from the global tree
406408
}
407409

408410
// A SymKind describes the kind of memory represented by a symbol.
@@ -728,6 +730,7 @@ type Link struct {
728730
Pathname string
729731
Hash map[SymVer]*LSym
730732
PosTable src.PosTable
733+
InlTree InlTree // global inlining tree used by gc/inl.go
731734
Imports []string
732735
Sym_div *LSym
733736
Sym_divu *LSym

src/cmd/internal/obj/objfile.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,16 @@
9494
// - pcsp [data block]
9595
// - pcfile [data block]
9696
// - pcline [data block]
97+
// - pcinline [data block]
9798
// - npcdata [int]
9899
// - pcdata [npcdata data blocks]
99100
// - nfuncdata [int]
100101
// - funcdata [nfuncdata symref index]
101102
// - funcdatasym [nfuncdata ints]
102103
// - nfile [int]
103104
// - file [nfile symref index]
105+
// - ninlinedcall [int]
106+
// - inlinedcall [ninlinedcall int symref int symref]
104107
//
105108
// The file layout and meaning of type integers are architecture-independent.
106109
//
@@ -156,6 +159,7 @@ func (w *objWriter) addLengths(s *LSym) {
156159
data += len(pc.Pcsp.P)
157160
data += len(pc.Pcfile.P)
158161
data += len(pc.Pcline.P)
162+
data += len(pc.Pcinline.P)
159163
for i := 0; i < len(pc.Pcdata); i++ {
160164
data += len(pc.Pcdata[i].P)
161165
}
@@ -227,6 +231,7 @@ func WriteObjFile(ctxt *Link, b *bufio.Writer) {
227231
w.wr.Write(pc.Pcsp.P)
228232
w.wr.Write(pc.Pcfile.P)
229233
w.wr.Write(pc.Pcline.P)
234+
w.wr.Write(pc.Pcinline.P)
230235
for i := 0; i < len(pc.Pcdata); i++ {
231236
w.wr.Write(pc.Pcdata[i].P)
232237
}
@@ -300,6 +305,11 @@ func (w *objWriter) writeRefs(s *LSym) {
300305
for _, f := range pc.File {
301306
w.writeRef(f, true)
302307
}
308+
for _, call := range pc.InlTree.nodes {
309+
w.writeRef(call.Func, false)
310+
f, _ := linkgetlineFromPos(w.ctxt, call.Pos)
311+
w.writeRef(f, true)
312+
}
303313
}
304314
}
305315

@@ -452,6 +462,7 @@ func (w *objWriter) writeSym(s *LSym) {
452462
w.writeInt(int64(len(pc.Pcsp.P)))
453463
w.writeInt(int64(len(pc.Pcfile.P)))
454464
w.writeInt(int64(len(pc.Pcline.P)))
465+
w.writeInt(int64(len(pc.Pcinline.P)))
455466
w.writeInt(int64(len(pc.Pcdata)))
456467
for i := 0; i < len(pc.Pcdata); i++ {
457468
w.writeInt(int64(len(pc.Pcdata[i].P)))
@@ -467,6 +478,14 @@ func (w *objWriter) writeSym(s *LSym) {
467478
for _, f := range pc.File {
468479
w.writeRefIndex(f)
469480
}
481+
w.writeInt(int64(len(pc.InlTree.nodes)))
482+
for _, call := range pc.InlTree.nodes {
483+
w.writeInt(int64(call.Parent))
484+
f, l := linkgetlineFromPos(w.ctxt, call.Pos)
485+
w.writeRefIndex(f)
486+
w.writeInt(int64(l))
487+
w.writeRefIndex(call.Func)
488+
}
470489
}
471490

472491
func (w *objWriter) writeInt(sval int64) {

src/cmd/internal/obj/pcln.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,62 @@ func pctofileline(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg
169169
return int32(i)
170170
}
171171

172+
// pcinlineState holds the state used to create a function's inlining
173+
// tree and the PC-value table that maps PCs to nodes in that tree.
174+
type pcinlineState struct {
175+
globalToLocal map[int]int
176+
localTree InlTree
177+
}
178+
179+
// addBranch adds a branch from the global inlining tree in ctxt to
180+
// the function's local inlining tree, returning the index in the local tree.
181+
func (s *pcinlineState) addBranch(ctxt *Link, globalIndex int) int {
182+
if globalIndex < 0 {
183+
return -1
184+
}
185+
186+
localIndex, ok := s.globalToLocal[globalIndex]
187+
if ok {
188+
return localIndex
189+
}
190+
191+
// Since tracebacks don't include column information, we could
192+
// use one node for multiple calls of the same function on the
193+
// same line (e.g., f(x) + f(y)). For now, we use one node for
194+
// each inlined call.
195+
call := ctxt.InlTree.nodes[globalIndex]
196+
call.Parent = s.addBranch(ctxt, call.Parent)
197+
localIndex = len(s.localTree.nodes)
198+
s.localTree.nodes = append(s.localTree.nodes, call)
199+
s.globalToLocal[globalIndex] = localIndex
200+
return localIndex
201+
}
202+
203+
// pctoinline computes the index into the local inlining tree to use at p.
204+
// If p is not the result of inlining, pctoinline returns -1. Because p.Pos
205+
// applies to p, phase == 0 (before p) takes care of the update.
206+
func (s *pcinlineState) pctoinline(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
207+
if phase == 1 {
208+
return oldval
209+
}
210+
211+
posBase := ctxt.PosTable.Pos(p.Pos).Base()
212+
if posBase == nil {
213+
return -1
214+
}
215+
216+
globalIndex := posBase.InliningIndex()
217+
if globalIndex < 0 {
218+
return -1
219+
}
220+
221+
if s.globalToLocal == nil {
222+
s.globalToLocal = make(map[int]int)
223+
}
224+
225+
return int32(s.addBranch(ctxt, globalIndex))
226+
}
227+
172228
// pctospadj computes the sp adjustment in effect.
173229
// It is oldval plus any adjustment made by p itself.
174230
// The adjustment by p takes effect only after p, so we
@@ -238,6 +294,10 @@ func linkpcln(ctxt *Link, cursym *LSym) {
238294
funcpctab(ctxt, &pcln.Pcfile, cursym, "pctofile", pctofileline, pcln)
239295
funcpctab(ctxt, &pcln.Pcline, cursym, "pctoline", pctofileline, nil)
240296

297+
pcinlineState := new(pcinlineState)
298+
funcpctab(ctxt, &pcln.Pcinline, cursym, "pctoinline", pcinlineState.pctoinline, nil)
299+
pcln.InlTree = pcinlineState.localTree
300+
241301
// tabulate which pc and func data we have.
242302
havepc := make([]uint32, (npcdata+31)/32)
243303
havefunc := make([]uint32, (nfuncdata+31)/32)

src/cmd/internal/obj/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func Getgoextlinkenabled() string {
5959
}
6060

6161
func (p *Prog) Line() string {
62-
return p.Ctxt.PosTable.Pos(p.Pos).String()
62+
return p.Ctxt.OutermostPos(p.Pos).String()
6363
}
6464

6565
var armCondCode = []string{

0 commit comments

Comments
 (0)