Skip to content

Commit 6081a9f

Browse files
committed
cmd/compile: index line number tables by source file to improve sparsity
This reduces allocations and also resolves some lurking inliner/inlinee line-number match problems. However, it does add about 1.5% to compile time. This fixes compiler OOMs seen compiling some large protobuf- derived inputs. For compiling the compiler itself, compilebench -pkg cmd/compile/internal/ssa -memprofile withcl.prof the numberlines-related memory consumption is reduced from 129MB to 29MB (about a 5% overall reduction in allocation). Additionally modified after going over changes with Austin to remove unused code (nobody called size()) and correct the cache-clearing code. I've attempted to speed this up by not using maps, and have not succeeded. I'd rather get correct code in now, speed it up later if I can. Updates #27739. Fixes #29279. Change-Id: I098005de4e45196a5f5b10c0886a49f88e9f8fd5 Reviewed-on: https://go-review.googlesource.com/c/go/+/154617 Run-TryBot: David Chase <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent a9e107c commit 6081a9f

File tree

9 files changed

+175
-44
lines changed

9 files changed

+175
-44
lines changed

src/cmd/compile/internal/ssa/biasedsparsemap.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,23 @@ func newBiasedSparseMap(first, last int) *biasedSparseMap {
2929

3030
// cap returns one more than the largest key valid for s
3131
func (s *biasedSparseMap) cap() int {
32-
if s.s == nil {
32+
if s == nil || s.s == nil {
3333
return 0
3434
}
3535
return s.s.cap() + int(s.first)
3636
}
3737

3838
// size returns the number of entries stored in s
3939
func (s *biasedSparseMap) size() int {
40-
if s.s == nil {
40+
if s == nil || s.s == nil {
4141
return 0
4242
}
4343
return s.s.size()
4444
}
4545

4646
// contains reports whether x is a key in s
4747
func (s *biasedSparseMap) contains(x uint) bool {
48-
if s.s == nil {
48+
if s == nil || s.s == nil {
4949
return false
5050
}
5151
if int(x) < s.first {
@@ -60,7 +60,7 @@ func (s *biasedSparseMap) contains(x uint) bool {
6060
// get returns the value s maps for key x, or -1 if
6161
// x is not mapped or is out of range for s.
6262
func (s *biasedSparseMap) get(x uint) int32 {
63-
if s.s == nil {
63+
if s == nil || s.s == nil {
6464
return -1
6565
}
6666
if int(x) < s.first {

src/cmd/compile/internal/ssa/deadcode.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ func deadcode(f *Func) {
258258
if !live[v.ID] {
259259
v.resetArgs()
260260
if v.Pos.IsStmt() == src.PosIsStmt && reachable[b.ID] {
261-
pendingLines.set(v.Pos.Line(), int32(i)) // TODO could be more than one pos for a line
261+
pendingLines.set(v.Pos, int32(i)) // TODO could be more than one pos for a line
262262
}
263263
}
264264
}
@@ -267,20 +267,19 @@ func deadcode(f *Func) {
267267
// Find new homes for lost lines -- require earliest in data flow with same line that is also in same block
268268
for i := len(order) - 1; i >= 0; i-- {
269269
w := order[i]
270-
if j := pendingLines.get(w.Pos.Line()); j > -1 && f.Blocks[j] == w.Block {
270+
if j := pendingLines.get(w.Pos); j > -1 && f.Blocks[j] == w.Block {
271271
w.Pos = w.Pos.WithIsStmt()
272-
pendingLines.remove(w.Pos.Line())
272+
pendingLines.remove(w.Pos)
273273
}
274274
}
275275

276276
// Any boundary that failed to match a live value can move to a block end
277-
for i := 0; i < pendingLines.size(); i++ {
278-
l, bi := pendingLines.getEntry(i)
277+
pendingLines.foreachEntry(func(j int32, l uint, bi int32) {
279278
b := f.Blocks[bi]
280-
if b.Pos.Line() == l {
279+
if b.Pos.Line() == l && b.Pos.FileIndex() == j {
281280
b.Pos = b.Pos.WithIsStmt()
282281
}
283-
}
282+
})
284283

285284
// Remove dead values from blocks' value list. Return dead
286285
// values to the allocator.

src/cmd/compile/internal/ssa/func.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ type Func struct {
6565
freeValues *Value // free Values linked by argstorage[0]. All other fields except ID are 0/nil.
6666
freeBlocks *Block // free Blocks linked by succstorage[0].b. All other fields except ID are 0/nil.
6767

68-
cachedPostorder []*Block // cached postorder traversal
69-
cachedIdom []*Block // cached immediate dominators
70-
cachedSdom SparseTree // cached dominator tree
71-
cachedLoopnest *loopnest // cached loop nest information
72-
cachedLineStarts *biasedSparseMap // cached map/set of line numbers to integers
68+
cachedPostorder []*Block // cached postorder traversal
69+
cachedIdom []*Block // cached immediate dominators
70+
cachedSdom SparseTree // cached dominator tree
71+
cachedLoopnest *loopnest // cached loop nest information
72+
cachedLineStarts *xposmap // cached map/set of xpos to integers
7373

7474
auxmap auxmap // map from aux values to opaque ids used by CSE
7575
constants map[int64][]*Value // constants cache, keyed by constant value; users must check value's Op and Type

src/cmd/compile/internal/ssa/func_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (c *Conf) Fun(entry string, blocs ...bloc) fun {
152152
// But not both.
153153
f.Cache = new(Cache)
154154
f.pass = &emptyPass
155-
f.cachedLineStarts = newBiasedSparseMap(0, 100)
155+
f.cachedLineStarts = newXposmap(map[int]lineRange{0: {0, 100}, 1: {0, 100}, 2: {0, 100}, 3: {0, 100}, 4: {0, 100}})
156156

157157
blocks := make(map[string]*Block)
158158
values := make(map[string]*Value)

src/cmd/compile/internal/ssa/nilcheck.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func nilcheckelim(f *Func) {
124124
ptr := v.Args[0]
125125
if nonNilValues[ptr.ID] {
126126
if v.Pos.IsStmt() == src.PosIsStmt { // Boolean true is a terrible statement boundary.
127-
pendingLines.add(v.Pos.Line())
127+
pendingLines.add(v.Pos)
128128
v.Pos = v.Pos.WithNotStmt()
129129
}
130130
// This is a redundant explicit nil check.
@@ -141,7 +141,7 @@ func nilcheckelim(f *Func) {
141141
f.Warnl(v.Pos, "removed nil check")
142142
}
143143
if v.Pos.IsStmt() == src.PosIsStmt { // About to lose a statement boundary
144-
pendingLines.add(v.Pos.Line())
144+
pendingLines.add(v.Pos)
145145
}
146146
v.reset(OpUnknown)
147147
f.freeValue(v)
@@ -154,15 +154,15 @@ func nilcheckelim(f *Func) {
154154
work = append(work, bp{op: ClearPtr, ptr: ptr})
155155
fallthrough // a non-eliminated nil check might be a good place for a statement boundary.
156156
default:
157-
if pendingLines.contains(v.Pos.Line()) && v.Pos.IsStmt() != src.PosNotStmt {
157+
if pendingLines.contains(v.Pos) && v.Pos.IsStmt() != src.PosNotStmt {
158158
v.Pos = v.Pos.WithIsStmt()
159-
pendingLines.remove(v.Pos.Line())
159+
pendingLines.remove(v.Pos)
160160
}
161161
}
162162
}
163-
if pendingLines.contains(b.Pos.Line()) {
163+
if pendingLines.contains(b.Pos) {
164164
b.Pos = b.Pos.WithIsStmt()
165-
pendingLines.remove(b.Pos.Line())
165+
pendingLines.remove(b.Pos)
166166
}
167167
for j := i; j < len(b.Values); j++ {
168168
b.Values[j] = nil
@@ -212,7 +212,7 @@ func nilcheckelim2(f *Func) {
212212
f.Warnl(v.Pos, "removed nil check")
213213
}
214214
if v.Pos.IsStmt() == src.PosIsStmt {
215-
pendingLines.add(v.Pos.Line())
215+
pendingLines.add(v.Pos)
216216
}
217217
v.reset(OpUnknown)
218218
firstToRemove = i
@@ -273,16 +273,16 @@ func nilcheckelim2(f *Func) {
273273
for j := i; j < len(b.Values); j++ {
274274
v := b.Values[j]
275275
if v.Op != OpUnknown {
276-
if v.Pos.IsStmt() != src.PosNotStmt && pendingLines.contains(v.Pos.Line()) {
276+
if v.Pos.IsStmt() != src.PosNotStmt && pendingLines.contains(v.Pos) {
277277
v.Pos = v.Pos.WithIsStmt()
278-
pendingLines.remove(v.Pos.Line())
278+
pendingLines.remove(v.Pos)
279279
}
280280
b.Values[i] = v
281281
i++
282282
}
283283
}
284284

285-
if pendingLines.contains(b.Pos.Line()) {
285+
if pendingLines.contains(b.Pos) {
286286
b.Pos = b.Pos.WithIsStmt()
287287
}
288288

src/cmd/compile/internal/ssa/numberlines.go

+21-13
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package ssa
77
import (
88
"cmd/internal/obj"
99
"cmd/internal/src"
10-
"math"
1110
)
1211

1312
func isPoorStatementOp(op Op) bool {
@@ -51,7 +50,7 @@ func nextGoodStatementIndex(v *Value, i int, b *Block) int {
5150
if b.Values[j].Pos.IsStmt() == src.PosNotStmt { // ignore non-statements
5251
continue
5352
}
54-
if b.Values[j].Pos.Line() == v.Pos.Line() {
53+
if b.Values[j].Pos.Line() == v.Pos.Line() && v.Pos.SameFile(b.Values[j].Pos) {
5554
return j
5655
}
5756
return i
@@ -86,14 +85,22 @@ func (b *Block) FirstPossibleStmtValue() *Value {
8685
func numberLines(f *Func) {
8786
po := f.Postorder()
8887
endlines := make(map[ID]src.XPos)
89-
last := uint(0) // uint follows type of XPos.Line()
90-
first := uint(math.MaxInt32) // unsigned, but large valid int when cast
91-
note := func(line uint) {
92-
if line < first {
93-
first = line
88+
ranges := make(map[int]lineRange)
89+
note := func(p src.XPos) {
90+
line := uint32(p.Line())
91+
i := int(p.FileIndex())
92+
lp, found := ranges[i]
93+
change := false
94+
if line < lp.first || !found {
95+
lp.first = line
96+
change = true
9497
}
95-
if line > last {
96-
last = line
98+
if line > lp.last {
99+
lp.last = line
100+
change = true
101+
}
102+
if change {
103+
ranges[i] = lp
97104
}
98105
}
99106

@@ -104,12 +111,12 @@ func numberLines(f *Func) {
104111
firstPos := src.NoXPos
105112
firstPosIndex := -1
106113
if b.Pos.IsStmt() != src.PosNotStmt {
107-
note(b.Pos.Line())
114+
note(b.Pos)
108115
}
109116
for i := 0; i < len(b.Values); i++ {
110117
v := b.Values[i]
111118
if v.Pos.IsStmt() != src.PosNotStmt {
112-
note(v.Pos.Line())
119+
note(v.Pos)
113120
// skip ahead to better instruction for this line if possible
114121
i = nextGoodStatementIndex(v, i, b)
115122
v = b.Values[i]
@@ -161,7 +168,7 @@ func numberLines(f *Func) {
161168
if v.Pos.IsStmt() == src.PosNotStmt {
162169
continue
163170
}
164-
note(v.Pos.Line())
171+
note(v.Pos)
165172
// skip ahead if possible
166173
i = nextGoodStatementIndex(v, i, b)
167174
v = b.Values[i]
@@ -178,5 +185,6 @@ func numberLines(f *Func) {
178185
}
179186
endlines[b.ID] = firstPos
180187
}
181-
f.cachedLineStarts = newBiasedSparseMap(int(first), int(last))
188+
// cachedLineStarts is an empty sparse map for values that are included within ranges.
189+
f.cachedLineStarts = newXposmap(ranges)
182190
}

src/cmd/compile/internal/ssa/rewrite.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) {
6464
// TODO: it's possible (in FOR loops, in particular) for statement boundaries for the same
6565
// line to appear in more than one block, but only one block is stored, so if both end
6666
// up here, then one will be lost.
67-
pendingLines.set(a.Pos.Line(), int32(a.Block.ID))
67+
pendingLines.set(a.Pos, int32(a.Block.ID))
6868
}
6969
a.Pos = a.Pos.WithNotStmt()
7070
}
@@ -97,7 +97,7 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) {
9797
for _, b := range f.Blocks {
9898
j := 0
9999
for i, v := range b.Values {
100-
vl := v.Pos.Line()
100+
vl := v.Pos
101101
if v.Op == OpInvalid {
102102
if v.Pos.IsStmt() == src.PosIsStmt {
103103
pendingLines.set(vl, int32(b.ID))
@@ -114,9 +114,9 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) {
114114
}
115115
j++
116116
}
117-
if pendingLines.get(b.Pos.Line()) == int32(b.ID) {
117+
if pendingLines.get(b.Pos) == int32(b.ID) {
118118
b.Pos = b.Pos.WithIsStmt()
119-
pendingLines.remove(b.Pos.Line())
119+
pendingLines.remove(b.Pos)
120120
}
121121
if j != len(b.Values) {
122122
tail := b.Values[j:]
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2019 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 ssa
6+
7+
import (
8+
"cmd/internal/src"
9+
"fmt"
10+
)
11+
12+
type lineRange struct {
13+
first, last uint32
14+
}
15+
16+
// An xposmap is a map from fileindex and line of src.XPos to int32,
17+
// implemented sparsely to save space (column and statement status are ignored).
18+
// The sparse skeleton is constructed once, and then reused by ssa phases
19+
// that (re)move values with statements attached.
20+
type xposmap struct {
21+
// A map from file index to maps from line range to integers (block numbers)
22+
maps map[int32]*biasedSparseMap
23+
// The next two fields provide a single-item cache for common case of repeated lines from same file.
24+
lastIndex int32 // -1 means no entry in cache
25+
lastMap *biasedSparseMap // map found at maps[lastIndex]
26+
}
27+
28+
// newXposmap constructs an xposmap valid for inputs which have a file index in the keys of x,
29+
// and line numbers in the range x[file index].
30+
// The resulting xposmap will panic if a caller attempts to set or add an XPos not in that range.
31+
func newXposmap(x map[int]lineRange) *xposmap {
32+
maps := make(map[int32]*biasedSparseMap)
33+
for i, p := range x {
34+
maps[int32(i)] = newBiasedSparseMap(int(p.first), int(p.last))
35+
}
36+
return &xposmap{maps: maps, lastIndex: -1} // zero for the rest is okay
37+
}
38+
39+
// clear removes data from the map but leaves the sparse skeleton.
40+
func (m *xposmap) clear() {
41+
for _, l := range m.maps {
42+
if l != nil {
43+
l.clear()
44+
}
45+
}
46+
m.lastIndex = -1
47+
m.lastMap = nil
48+
}
49+
50+
// mapFor returns the line range map for a given file index.
51+
func (m *xposmap) mapFor(index int32) *biasedSparseMap {
52+
if index == m.lastIndex {
53+
return m.lastMap
54+
}
55+
mf := m.maps[index]
56+
m.lastIndex = index
57+
m.lastMap = mf
58+
return mf
59+
}
60+
61+
// set inserts p->v into the map.
62+
// If p does not fall within the set of fileindex->lineRange used to construct m, this will panic.
63+
func (m *xposmap) set(p src.XPos, v int32) {
64+
s := m.mapFor(p.FileIndex())
65+
if s == nil {
66+
panic(fmt.Sprintf("xposmap.set(%d), file index not found in map\n", p.FileIndex()))
67+
}
68+
s.set(p.Line(), v)
69+
}
70+
71+
// get returns the int32 associated with the file index and line of p.
72+
func (m *xposmap) get(p src.XPos) int32 {
73+
s := m.mapFor(p.FileIndex())
74+
if s == nil {
75+
return -1
76+
}
77+
return s.get(p.Line())
78+
}
79+
80+
// add adds p to m, treating m as a set instead of as a map.
81+
// If p does not fall within the set of fileindex->lineRange used to construct m, this will panic.
82+
// Use clear() in between set/map interpretations of m.
83+
func (m *xposmap) add(p src.XPos) {
84+
m.set(p, 0)
85+
}
86+
87+
// contains returns whether the file index and line of p are in m,
88+
// treating m as a set instead of as a map.
89+
func (m *xposmap) contains(p src.XPos) bool {
90+
s := m.mapFor(p.FileIndex())
91+
if s == nil {
92+
return false
93+
}
94+
return s.contains(p.Line())
95+
}
96+
97+
// remove removes the file index and line for p from m,
98+
// whether m is currently treated as a map or set.
99+
func (m *xposmap) remove(p src.XPos) {
100+
s := m.mapFor(p.FileIndex())
101+
if s == nil {
102+
return
103+
}
104+
s.remove(p.Line())
105+
}
106+
107+
// foreachEntry applies f to each (fileindex, line, value) triple in m.
108+
func (m *xposmap) foreachEntry(f func(j int32, l uint, v int32)) {
109+
for j, mm := range m.maps {
110+
s := mm.size()
111+
for i := 0; i < s; i++ {
112+
l, v := mm.getEntry(i)
113+
f(j, l, v)
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)