Skip to content

Commit 9d812cf

Browse files
committed
cmd/compile,runtime: stack maps only at calls, remove register maps
Currently, we emit stack maps and register maps at almost every instruction. This was originally intended to support non-cooperative preemption, but was only ever used for debug call injection. Now debug call injection also uses conservative frame scanning. As a result, stack maps are only needed at call sites and register maps aren't needed at all except that we happen to also encode unsafe-point information in the register map PCDATA stream. This CL reduces stack maps to only appear at calls, and replace full register maps with just safe/unsafe-point information. This is all protected by the go115ReduceLiveness feature flag, which is defined in both runtime and cmd/compile. This CL significantly reduces binary sizes and also speeds up compiles and links: name old exe-bytes new exe-bytes delta BinGoSize 15.0MB ± 0% 14.1MB ± 0% -5.72% name old pcln-bytes new pcln-bytes delta BinGoSize 3.14MB ± 0% 2.48MB ± 0% -21.08% name old time/op new time/op delta Template 178ms ± 7% 172ms ±14% -3.59% (p=0.005 n=19+19) Unicode 71.0ms ±12% 69.8ms ±10% ~ (p=0.126 n=18+18) GoTypes 655ms ± 8% 615ms ± 8% -6.11% (p=0.000 n=19+19) Compiler 3.27s ± 6% 3.15s ± 7% -3.69% (p=0.001 n=20+20) SSA 7.10s ± 5% 6.85s ± 8% -3.53% (p=0.001 n=19+20) Flate 124ms ±15% 116ms ±22% -6.57% (p=0.024 n=18+19) GoParser 156ms ±26% 147ms ±34% ~ (p=0.070 n=19+19) Reflect 406ms ± 9% 387ms ±21% -4.69% (p=0.028 n=19+20) Tar 163ms ±15% 162ms ±27% ~ (p=0.370 n=19+19) XML 223ms ±13% 218ms ±14% ~ (p=0.157 n=20+20) LinkCompiler 503ms ±21% 484ms ±23% ~ (p=0.072 n=20+20) ExternalLinkCompiler 1.27s ± 7% 1.22s ± 8% -3.85% (p=0.005 n=20+19) LinkWithoutDebugCompiler 294ms ±17% 273ms ±11% -7.16% (p=0.001 n=19+18) (https://perf.golang.org/search?q=upload:20200428.8) The binary size improvement is even slightly better when you include the CLs leading up to this. Relative to the parent of "cmd/compile: mark PanicBounds/Extend as calls": name old exe-bytes new exe-bytes delta BinGoSize 15.0MB ± 0% 14.1MB ± 0% -6.18% name old pcln-bytes new pcln-bytes delta BinGoSize 3.22MB ± 0% 2.48MB ± 0% -22.92% (https://perf.golang.org/search?q=upload:20200428.9) For #36365. Change-Id: I69448e714f2a44430067ca97f6b78e08c0abed27 Reviewed-on: https://go-review.googlesource.com/c/go/+/230544 Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Cherry Zhang <cherryyz@google.com>
1 parent ee7d9f1 commit 9d812cf

File tree

7 files changed

+178
-74
lines changed

7 files changed

+178
-74
lines changed

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

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ func newProgs(fn *Node, worker int) *Progs {
7070

7171
pp.pos = fn.Pos
7272
pp.settext(fn)
73-
pp.nextLive = LivenessInvalid
7473
// PCDATA tables implicitly start with index -1.
7574
pp.prevLive = LivenessIndex{-1, -1, false}
75+
if go115ReduceLiveness {
76+
pp.nextLive = pp.prevLive
77+
} else {
78+
pp.nextLive = LivenessInvalid
79+
}
7680
return pp
7781
}
7882

@@ -117,18 +121,32 @@ func (pp *Progs) Prog(as obj.As) *obj.Prog {
117121
Addrconst(&p.From, objabi.PCDATA_StackMapIndex)
118122
Addrconst(&p.To, int64(idx))
119123
}
120-
if pp.nextLive.isUnsafePoint {
121-
// Unsafe points are encoded as a special value in the
122-
// register map.
123-
pp.nextLive.regMapIndex = objabi.PCDATA_RegMapUnsafe
124-
}
125-
if pp.nextLive.regMapIndex != pp.prevLive.regMapIndex {
126-
// Emit register map index change.
127-
idx := pp.nextLive.regMapIndex
128-
pp.prevLive.regMapIndex = idx
129-
p := pp.Prog(obj.APCDATA)
130-
Addrconst(&p.From, objabi.PCDATA_RegMapIndex)
131-
Addrconst(&p.To, int64(idx))
124+
if !go115ReduceLiveness {
125+
if pp.nextLive.isUnsafePoint {
126+
// Unsafe points are encoded as a special value in the
127+
// register map.
128+
pp.nextLive.regMapIndex = objabi.PCDATA_RegMapUnsafe
129+
}
130+
if pp.nextLive.regMapIndex != pp.prevLive.regMapIndex {
131+
// Emit register map index change.
132+
idx := pp.nextLive.regMapIndex
133+
pp.prevLive.regMapIndex = idx
134+
p := pp.Prog(obj.APCDATA)
135+
Addrconst(&p.From, objabi.PCDATA_RegMapIndex)
136+
Addrconst(&p.To, int64(idx))
137+
}
138+
} else {
139+
if pp.nextLive.isUnsafePoint != pp.prevLive.isUnsafePoint {
140+
// Emit unsafe-point marker.
141+
pp.prevLive.isUnsafePoint = pp.nextLive.isUnsafePoint
142+
p := pp.Prog(obj.APCDATA)
143+
Addrconst(&p.From, objabi.PCDATA_UnsafePoint)
144+
if pp.nextLive.isUnsafePoint {
145+
Addrconst(&p.To, objabi.PCDATA_UnsafePointUnsafe)
146+
} else {
147+
Addrconst(&p.To, objabi.PCDATA_UnsafePointSafe)
148+
}
149+
}
132150
}
133151

134152
p := pp.next

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

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ import (
2424
"strings"
2525
)
2626

27+
// go115ReduceLiveness disables register maps and only produces stack
28+
// maps at call sites.
29+
//
30+
// In Go 1.15, we changed debug call injection to use conservative
31+
// scanning instead of precise pointer maps, so these are no longer
32+
// necessary.
33+
//
34+
// Keep in sync with runtime/preempt.go:go115ReduceLiveness.
35+
const go115ReduceLiveness = true
36+
2737
// OpVarDef is an annotation for the liveness analysis, marking a place
2838
// where a complete initialization (definition) of a variable begins.
2939
// Since the liveness analysis can see initialization of single-word
@@ -165,18 +175,27 @@ func (m *LivenessMap) set(v *ssa.Value, i LivenessIndex) {
165175
}
166176

167177
func (m LivenessMap) Get(v *ssa.Value) LivenessIndex {
168-
// All safe-points are in the map, so if v isn't in
169-
// the map, it's an unsafe-point.
178+
if !go115ReduceLiveness {
179+
// All safe-points are in the map, so if v isn't in
180+
// the map, it's an unsafe-point.
181+
if idx, ok := m.vals[v.ID]; ok {
182+
return idx
183+
}
184+
return LivenessInvalid
185+
}
186+
187+
// If v isn't in the map, then it's a "don't care" and not an
188+
// unsafe-point.
170189
if idx, ok := m.vals[v.ID]; ok {
171190
return idx
172191
}
173-
return LivenessInvalid
192+
return LivenessIndex{StackMapDontCare, StackMapDontCare, false}
174193
}
175194

176195
// LivenessIndex stores the liveness map information for a Value.
177196
type LivenessIndex struct {
178197
stackMapIndex int
179-
regMapIndex int
198+
regMapIndex int // only for !go115ReduceLiveness
180199

181200
// isUnsafePoint indicates that this is an unsafe-point.
182201
//
@@ -188,7 +207,7 @@ type LivenessIndex struct {
188207
}
189208

190209
// LivenessInvalid indicates an unsafe point with no stack map.
191-
var LivenessInvalid = LivenessIndex{StackMapDontCare, StackMapDontCare, true}
210+
var LivenessInvalid = LivenessIndex{StackMapDontCare, StackMapDontCare, true} // only for !go115ReduceLiveness
192211

193212
// StackMapDontCare indicates that the stack map index at a Value
194213
// doesn't matter.
@@ -392,6 +411,9 @@ func affectedNode(v *ssa.Value) (*Node, ssa.SymEffect) {
392411

393412
// regEffects returns the registers affected by v.
394413
func (lv *Liveness) regEffects(v *ssa.Value) (uevar, kill liveRegMask) {
414+
if go115ReduceLiveness {
415+
return 0, 0
416+
}
395417
if v.Op == ssa.OpPhi {
396418
// All phi node arguments must come from the same
397419
// register and the result must also go to that
@@ -473,7 +495,7 @@ func (lv *Liveness) regEffects(v *ssa.Value) (uevar, kill liveRegMask) {
473495
return uevar, kill
474496
}
475497

476-
type liveRegMask uint32
498+
type liveRegMask uint32 // only if !go115ReduceLiveness
477499

478500
func (m liveRegMask) niceString(config *ssa.Config) string {
479501
if m == 0 {
@@ -835,7 +857,7 @@ func (lv *Liveness) hasStackMap(v *ssa.Value) bool {
835857
// The runtime only has safe-points in function prologues, so
836858
// we only need stack maps at call sites. go:nosplit functions
837859
// are similar.
838-
if compiling_runtime || lv.f.NoSplit {
860+
if go115ReduceLiveness || compiling_runtime || lv.f.NoSplit {
839861
if !v.Op.IsCall() {
840862
return false
841863
}
@@ -1172,13 +1194,15 @@ func (lv *Liveness) epilogue() {
11721194
lv.f.Fatalf("%v %L recorded as live on entry", lv.fn.Func.Nname, n)
11731195
}
11741196
}
1175-
// Check that no registers are live at function entry.
1176-
// The context register, if any, comes from a
1177-
// LoweredGetClosurePtr operation first thing in the function,
1178-
// so it doesn't appear live at entry.
1179-
if regs := lv.regMaps[0]; regs != 0 {
1180-
lv.printDebug()
1181-
lv.f.Fatalf("%v register %s recorded as live on entry", lv.fn.Func.Nname, regs.niceString(lv.f.Config))
1197+
if !go115ReduceLiveness {
1198+
// Check that no registers are live at function entry.
1199+
// The context register, if any, comes from a
1200+
// LoweredGetClosurePtr operation first thing in the function,
1201+
// so it doesn't appear live at entry.
1202+
if regs := lv.regMaps[0]; regs != 0 {
1203+
lv.printDebug()
1204+
lv.f.Fatalf("%v register %s recorded as live on entry", lv.fn.Func.Nname, regs.niceString(lv.f.Config))
1205+
}
11821206
}
11831207
}
11841208

@@ -1199,7 +1223,7 @@ func (lv *Liveness) epilogue() {
11991223
// PCDATA tables cost about 100k. So for now we keep using a single index for
12001224
// both bitmap lists.
12011225
func (lv *Liveness) compact(b *ssa.Block) {
1202-
add := func(live varRegVec, isUnsafePoint bool) LivenessIndex {
1226+
add := func(live varRegVec, isUnsafePoint bool) LivenessIndex { // only if !go115ReduceLiveness
12031227
// Deduplicate the stack map.
12041228
stackIndex := lv.stackMapSet.add(live.vars)
12051229
// Deduplicate the register map.
@@ -1214,11 +1238,26 @@ func (lv *Liveness) compact(b *ssa.Block) {
12141238
pos := 0
12151239
if b == lv.f.Entry {
12161240
// Handle entry stack map.
1217-
add(lv.livevars[0], false)
1241+
if !go115ReduceLiveness {
1242+
add(lv.livevars[0], false)
1243+
} else {
1244+
lv.stackMapSet.add(lv.livevars[0].vars)
1245+
}
12181246
pos++
12191247
}
12201248
for _, v := range b.Values {
1221-
if lv.hasStackMap(v) {
1249+
if go115ReduceLiveness {
1250+
hasStackMap := lv.hasStackMap(v)
1251+
isUnsafePoint := lv.allUnsafe || lv.unsafePoints.Get(int32(v.ID))
1252+
idx := LivenessIndex{StackMapDontCare, 0, isUnsafePoint}
1253+
if hasStackMap {
1254+
idx.stackMapIndex = lv.stackMapSet.add(lv.livevars[pos].vars)
1255+
pos++
1256+
}
1257+
if hasStackMap || isUnsafePoint {
1258+
lv.livenessMap.set(v, idx)
1259+
}
1260+
} else if lv.hasStackMap(v) {
12221261
isUnsafePoint := lv.allUnsafe || lv.unsafePoints.Get(int32(v.ID))
12231262
lv.livenessMap.set(v, add(lv.livevars[pos], isUnsafePoint))
12241263
pos++
@@ -1407,7 +1446,7 @@ func (lv *Liveness) printDebug() {
14071446
printed = true
14081447
}
14091448
}
1410-
if pcdata.RegMapValid() {
1449+
if pcdata.RegMapValid() { // only if !go115ReduceLiveness
14111450
regLive := lv.regMaps[pcdata.regMapIndex]
14121451
if regLive != 0 {
14131452
if printed {
@@ -1491,19 +1530,21 @@ func (lv *Liveness) emit() (argsSym, liveSym, regsSym *obj.LSym) {
14911530
loff = dbvec(&liveSymTmp, loff, locals)
14921531
}
14931532

1494-
regs := bvalloc(lv.usedRegs())
1495-
roff := duint32(&regsSymTmp, 0, uint32(len(lv.regMaps))) // number of bitmaps
1496-
roff = duint32(&regsSymTmp, roff, uint32(regs.n)) // number of bits in each bitmap
1497-
if regs.n > 32 {
1498-
// Our uint32 conversion below won't work.
1499-
Fatalf("GP registers overflow uint32")
1500-
}
1533+
if !go115ReduceLiveness {
1534+
regs := bvalloc(lv.usedRegs())
1535+
roff := duint32(&regsSymTmp, 0, uint32(len(lv.regMaps))) // number of bitmaps
1536+
roff = duint32(&regsSymTmp, roff, uint32(regs.n)) // number of bits in each bitmap
1537+
if regs.n > 32 {
1538+
// Our uint32 conversion below won't work.
1539+
Fatalf("GP registers overflow uint32")
1540+
}
15011541

1502-
if regs.n > 0 {
1503-
for _, live := range lv.regMaps {
1504-
regs.Clear()
1505-
regs.b[0] = uint32(live)
1506-
roff = dbvec(&regsSymTmp, roff, regs)
1542+
if regs.n > 0 {
1543+
for _, live := range lv.regMaps {
1544+
regs.Clear()
1545+
regs.b[0] = uint32(live)
1546+
roff = dbvec(&regsSymTmp, roff, regs)
1547+
}
15071548
}
15081549
}
15091550

@@ -1518,7 +1559,11 @@ func (lv *Liveness) emit() (argsSym, liveSym, regsSym *obj.LSym) {
15181559
lsym.P = tmpSym.P
15191560
})
15201561
}
1521-
return makeSym(&argsSymTmp), makeSym(&liveSymTmp), makeSym(&regsSymTmp)
1562+
if !go115ReduceLiveness {
1563+
return makeSym(&argsSymTmp), makeSym(&liveSymTmp), makeSym(&regsSymTmp)
1564+
}
1565+
// TODO(go115ReduceLiveness): Remove regsSym result
1566+
return makeSym(&argsSymTmp), makeSym(&liveSymTmp), nil
15221567
}
15231568

15241569
// Entry pointer for liveness analysis. Solves for the liveness of
@@ -1578,11 +1623,13 @@ func liveness(e *ssafn, f *ssa.Func, pp *Progs) LivenessMap {
15781623
p.To.Name = obj.NAME_EXTERN
15791624
p.To.Sym = ls.Func.GCLocals
15801625

1581-
p = pp.Prog(obj.AFUNCDATA)
1582-
Addrconst(&p.From, objabi.FUNCDATA_RegPointerMaps)
1583-
p.To.Type = obj.TYPE_MEM
1584-
p.To.Name = obj.NAME_EXTERN
1585-
p.To.Sym = ls.Func.GCRegs
1626+
if !go115ReduceLiveness {
1627+
p = pp.Prog(obj.AFUNCDATA)
1628+
Addrconst(&p.From, objabi.FUNCDATA_RegPointerMaps)
1629+
p.To.Type = obj.TYPE_MEM
1630+
p.To.Name = obj.NAME_EXTERN
1631+
p.To.Sym = ls.Func.GCRegs
1632+
}
15861633

15871634
return lv.livenessMap
15881635
}

src/cmd/internal/obj/link.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ type FuncInfo struct {
412412

413413
GCArgs *LSym
414414
GCLocals *LSym
415-
GCRegs *LSym
415+
GCRegs *LSym // Only if !go115ReduceLiveness
416416
StackObjects *LSym
417417
OpenCodedDeferInfo *LSym
418418

src/cmd/internal/objabi/funcdata.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ package objabi
1111
// ../../../runtime/symtab.go.
1212

1313
const (
14-
PCDATA_RegMapIndex = 0
14+
PCDATA_RegMapIndex = 0 // if !go115ReduceLiveness
15+
PCDATA_UnsafePoint = 0 // if go115ReduceLiveness
1516
PCDATA_StackMapIndex = 1
1617
PCDATA_InlTreeIndex = 2
1718

1819
FUNCDATA_ArgsPointerMaps = 0
1920
FUNCDATA_LocalsPointerMaps = 1
20-
FUNCDATA_RegPointerMaps = 2
21+
FUNCDATA_RegPointerMaps = 2 // if !go115ReduceLiveness
2122
FUNCDATA_StackObjects = 3
2223
FUNCDATA_InlTree = 4
2324
FUNCDATA_OpenCodedDeferInfo = 5
@@ -32,5 +33,11 @@ const (
3233
// Special PCDATA values.
3334
const (
3435
// PCDATA_RegMapIndex values.
36+
//
37+
// Only if !go115ReduceLiveness.
3538
PCDATA_RegMapUnsafe = -2 // Unsafe for async preemption
39+
40+
// PCDATA_UnsafePoint values.
41+
PCDATA_UnsafePointSafe = -1 // Safe for async preemption
42+
PCDATA_UnsafePointUnsafe = -2 // Unsafe for async preemption
3643
)

src/runtime/debugcall.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,32 @@ func debugCallCheck(pc uintptr) string {
7676
return
7777
}
7878

79-
// Look up PC's register map.
80-
pcdata := int32(-1)
81-
if pc != f.entry {
82-
pc--
83-
pcdata = pcdatavalue(f, _PCDATA_RegMapIndex, pc, nil)
84-
}
85-
if pcdata == -1 {
86-
pcdata = 0 // in prologue
87-
}
88-
stkmap := (*stackmap)(funcdata(f, _FUNCDATA_RegPointerMaps))
89-
if pcdata == -2 || stkmap == nil {
90-
// Not at a safe point.
91-
ret = debugCallUnsafePoint
92-
return
79+
if !go115ReduceLiveness {
80+
// Look up PC's register map.
81+
pcdata := int32(-1)
82+
if pc != f.entry {
83+
pc--
84+
pcdata = pcdatavalue(f, _PCDATA_RegMapIndex, pc, nil)
85+
}
86+
if pcdata == -1 {
87+
pcdata = 0 // in prologue
88+
}
89+
stkmap := (*stackmap)(funcdata(f, _FUNCDATA_RegPointerMaps))
90+
if pcdata == -2 || stkmap == nil {
91+
// Not at a safe point.
92+
ret = debugCallUnsafePoint
93+
return
94+
}
95+
} else {
96+
// Check that this isn't an unsafe-point.
97+
if pc != f.entry {
98+
pc--
99+
}
100+
up := pcdatavalue(f, _PCDATA_UnsafePoint, pc, nil)
101+
if up != _PCDATA_UnsafePointSafe {
102+
// Not at a safe point.
103+
ret = debugCallUnsafePoint
104+
}
93105
}
94106
})
95107
return ret

0 commit comments

Comments
 (0)