Skip to content

Commit faafdf5

Browse files
committed
cmd/compile: fix unsafe-points with stack maps
The compiler currently conflates whether a Value has a stack map with whether it's an unsafe point. For the most part, unsafe-points don't have stack maps, so this is mostly fine, but call instructions can be both an unsafe-point *and* have a stack map. For example, none of the instructions in a nosplit function should be preemptible, but calls must still have stack maps in case the called function grows the stack or get preempted. Currently, the compiler can't distinguish this case, so calls in nosplit functions are marked as safe-points just because they have stack maps. This is particularly problematic if a nosplit function calls another nosplit function, since this can introduce a preemption point where there should be none. We realized this was a problem for split-stack prologues a while back, and CL 207349 changed the encoding of unsafe-points to use the register map index instead of the stack map index so we could record both a stack map and an unsafe-point at the same instruction. But this was never extended into the compiler. This CL fixes this problem in the compiler. We make LivenessIndex slightly more abstract by separating unsafe-point marks from stack and register map indexes. We map this to the PCDATA encoding later when producing Progs. This isn't enough to fix the whole problem for nosplit functions, because obj still adds prologues and marks those as preemptible, but it's a step in the right direction. I checked this CL by comparing maps before and after this change in the runtime and net/http. In net/http, unsafe-points match exactly; at anything that isn't an unsafe-point, both the stack and register maps are unchanged by this CL. In the runtime, at every point that was a safe-point before this change, the stack maps agree (and mostly the runtime doesn't have register maps at all now). In both, all CALLs (except write barrier calls) have stack maps. For #36365. Change-Id: I066628938b02e78be5c81a6614295bcf7cc566c2 Reviewed-on: https://go-review.googlesource.com/c/go/+/230541 Run-TryBot: Austin Clements <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]>
1 parent a6deafa commit faafdf5

File tree

4 files changed

+96
-57
lines changed

4 files changed

+96
-57
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func newProgs(fn *Node, worker int) *Progs {
7272
pp.settext(fn)
7373
pp.nextLive = LivenessInvalid
7474
// PCDATA tables implicitly start with index -1.
75-
pp.prevLive = LivenessIndex{-1, -1}
75+
pp.prevLive = LivenessIndex{-1, -1, false}
7676
return pp
7777
}
7878

@@ -109,14 +109,19 @@ func (pp *Progs) Free() {
109109

110110
// Prog adds a Prog with instruction As to pp.
111111
func (pp *Progs) Prog(as obj.As) *obj.Prog {
112-
if pp.nextLive.stackMapIndex != pp.prevLive.stackMapIndex {
112+
if pp.nextLive.StackMapValid() && pp.nextLive.stackMapIndex != pp.prevLive.stackMapIndex {
113113
// Emit stack map index change.
114114
idx := pp.nextLive.stackMapIndex
115115
pp.prevLive.stackMapIndex = idx
116116
p := pp.Prog(obj.APCDATA)
117117
Addrconst(&p.From, objabi.PCDATA_StackMapIndex)
118118
Addrconst(&p.To, int64(idx))
119119
}
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+
}
120125
if pp.nextLive.regMapIndex != pp.prevLive.regMapIndex {
121126
// Emit register map index change.
122127
idx := pp.nextLive.regMapIndex

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

Lines changed: 81 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ type Liveness struct {
107107

108108
be []BlockEffects
109109

110-
// unsafePoints bit i is set if Value ID i is not a safe point.
110+
// allUnsafe indicates that all points in this function are
111+
// unsafe-points.
112+
allUnsafe bool
113+
// unsafePoints bit i is set if Value ID i is an unsafe-point
114+
// (preemption is not allowed). Only valid if !allUnsafe.
111115
unsafePoints bvec
112116

113117
// An array with a bit vector for each safe point in the
@@ -172,23 +176,37 @@ func (m LivenessMap) Get(v *ssa.Value) LivenessIndex {
172176
return LivenessInvalid
173177
}
174178

175-
// LivenessIndex stores the liveness map index for a safe-point.
179+
// LivenessIndex stores the liveness map information for a Value.
176180
type LivenessIndex struct {
177181
stackMapIndex int
178182
regMapIndex int
183+
184+
// isUnsafePoint indicates that this is an unsafe-point.
185+
//
186+
// Note that it's possible for a call Value to have a stack
187+
// map while also being an unsafe-point. This means it cannot
188+
// be preempted at this instruction, but that a preemption or
189+
// stack growth may happen in the called function.
190+
isUnsafePoint bool
179191
}
180192

181-
// LivenessInvalid indicates an unsafe point.
193+
// LivenessInvalid indicates an unsafe point with no stack map.
194+
var LivenessInvalid = LivenessIndex{StackMapDontCare, StackMapDontCare, true}
195+
196+
// StackMapDontCare indicates that the stack map index at a Value
197+
// doesn't matter.
182198
//
183-
// We use index -2 because PCDATA tables conventionally start at -1,
184-
// so -1 is used to mean the entry liveness map (which is actually at
185-
// index 0; sigh). TODO(austin): Maybe we should use PCDATA+1 as the
186-
// index into the liveness map so -1 uniquely refers to the entry
187-
// liveness map.
188-
var LivenessInvalid = LivenessIndex{-2, -2}
189-
190-
func (idx LivenessIndex) Valid() bool {
191-
return idx.stackMapIndex >= 0
199+
// This is a sentinel value that should never be emitted to the PCDATA
200+
// stream. We use -1000 because that's obviously never a valid stack
201+
// index (but -1 is).
202+
const StackMapDontCare = -1000
203+
204+
func (idx LivenessIndex) StackMapValid() bool {
205+
return idx.stackMapIndex != StackMapDontCare
206+
}
207+
208+
func (idx LivenessIndex) RegMapValid() bool {
209+
return idx.regMapIndex != StackMapDontCare
192210
}
193211

194212
type progeffectscache struct {
@@ -644,9 +662,18 @@ func (lv *Liveness) pointerMap(liveout bvec, vars []*Node, args, locals bvec) {
644662

645663
// markUnsafePoints finds unsafe points and computes lv.unsafePoints.
646664
func (lv *Liveness) markUnsafePoints() {
665+
// The runtime assumes the only safe-points are function
666+
// prologues (because that's how it used to be). We could and
667+
// should improve that, but for now keep consider all points
668+
// in the runtime unsafe. obj will add prologues and their
669+
// safe-points.
670+
//
671+
// go:nosplit functions are similar. Since safe points used to
672+
// be coupled with stack checks, go:nosplit often actually
673+
// means "no safe points in this function".
647674
if compiling_runtime || lv.f.NoSplit {
648-
// No complex analysis necessary. Do this on the fly
649-
// in hasStackMap.
675+
// No complex analysis necessary.
676+
lv.allUnsafe = true
650677
return
651678
}
652679

@@ -807,17 +834,13 @@ func (lv *Liveness) markUnsafePoints() {
807834
// particular, call Values can have a stack map in case the callee
808835
// grows the stack, but not themselves be a safe-point.
809836
func (lv *Liveness) hasStackMap(v *ssa.Value) bool {
810-
// The runtime was written with the assumption that
811-
// safe-points only appear at call sites (because that's how
812-
// it used to be). We could and should improve that, but for
813-
// now keep the old safe-point rules in the runtime.
814-
//
815-
// go:nosplit functions are similar. Since safe points used to
816-
// be coupled with stack checks, go:nosplit often actually
817-
// means "no safe points in this function".
837+
// The runtime only has safe-points in function prologues, so
838+
// we only need stack maps at call sites. go:nosplit functions
839+
// are similar.
818840
if compiling_runtime || lv.f.NoSplit {
819841
return v.Op.IsCall()
820842
}
843+
821844
switch v.Op {
822845
case ssa.OpInitMem, ssa.OpArg, ssa.OpSP, ssa.OpSB,
823846
ssa.OpSelect0, ssa.OpSelect1, ssa.OpGetG,
@@ -1169,7 +1192,7 @@ func (lv *Liveness) epilogue() {
11691192
// PCDATA tables cost about 100k. So for now we keep using a single index for
11701193
// both bitmap lists.
11711194
func (lv *Liveness) compact(b *ssa.Block) {
1172-
add := func(live varRegVec) LivenessIndex {
1195+
add := func(live varRegVec, isUnsafePoint bool) LivenessIndex {
11731196
// Deduplicate the stack map.
11741197
stackIndex := lv.stackMapSet.add(live.vars)
11751198
// Deduplicate the register map.
@@ -1179,17 +1202,18 @@ func (lv *Liveness) compact(b *ssa.Block) {
11791202
lv.regMapSet[live.regs] = regIndex
11801203
lv.regMaps = append(lv.regMaps, live.regs)
11811204
}
1182-
return LivenessIndex{stackIndex, regIndex}
1205+
return LivenessIndex{stackIndex, regIndex, isUnsafePoint}
11831206
}
11841207
pos := 0
11851208
if b == lv.f.Entry {
11861209
// Handle entry stack map.
1187-
add(lv.livevars[0])
1210+
add(lv.livevars[0], false)
11881211
pos++
11891212
}
11901213
for _, v := range b.Values {
11911214
if lv.hasStackMap(v) {
1192-
lv.livenessMap.set(v, add(lv.livevars[pos]))
1215+
isUnsafePoint := lv.allUnsafe || lv.unsafePoints.Get(int32(v.ID))
1216+
lv.livenessMap.set(v, add(lv.livevars[pos], isUnsafePoint))
11931217
pos++
11941218
}
11951219
}
@@ -1294,7 +1318,6 @@ func (lv *Liveness) printeffect(printed bool, name string, pos int32, x bool, re
12941318
func (lv *Liveness) printDebug() {
12951319
fmt.Printf("liveness: %s\n", lv.fn.funcname())
12961320

1297-
pcdata := 0
12981321
for i, b := range lv.f.Blocks {
12991322
if i > 0 {
13001323
fmt.Printf("\n")
@@ -1330,7 +1353,7 @@ func (lv *Liveness) printDebug() {
13301353
// program listing, with individual effects listed
13311354

13321355
if b == lv.f.Entry {
1333-
live := lv.stackMaps[pcdata]
1356+
live := lv.stackMaps[0]
13341357
fmt.Printf("(%s) function entry\n", linestr(lv.fn.Func.Nname.Pos))
13351358
fmt.Printf("\tlive=")
13361359
printed = false
@@ -1350,9 +1373,7 @@ func (lv *Liveness) printDebug() {
13501373
for _, v := range b.Values {
13511374
fmt.Printf("(%s) %v\n", linestr(v.Pos), v.LongString())
13521375

1353-
if pos := lv.livenessMap.Get(v); pos.Valid() {
1354-
pcdata = pos.stackMapIndex
1355-
}
1376+
pcdata := lv.livenessMap.Get(v)
13561377

13571378
pos, effect := lv.valueEffects(v)
13581379
regUevar, regKill := lv.regEffects(v)
@@ -1363,31 +1384,38 @@ func (lv *Liveness) printDebug() {
13631384
fmt.Printf("\n")
13641385
}
13651386

1366-
if !lv.hasStackMap(v) {
1367-
continue
1368-
}
1369-
1370-
live := lv.stackMaps[pcdata]
1371-
fmt.Printf("\tlive=")
1372-
printed = false
1373-
for j, n := range lv.vars {
1374-
if !live.Get(int32(j)) {
1375-
continue
1387+
if pcdata.StackMapValid() || pcdata.RegMapValid() {
1388+
fmt.Printf("\tlive=")
1389+
printed = false
1390+
if pcdata.StackMapValid() {
1391+
live := lv.stackMaps[pcdata.stackMapIndex]
1392+
for j, n := range lv.vars {
1393+
if !live.Get(int32(j)) {
1394+
continue
1395+
}
1396+
if printed {
1397+
fmt.Printf(",")
1398+
}
1399+
fmt.Printf("%v", n)
1400+
printed = true
1401+
}
13761402
}
1377-
if printed {
1378-
fmt.Printf(",")
1403+
if pcdata.RegMapValid() {
1404+
regLive := lv.regMaps[pcdata.regMapIndex]
1405+
if regLive != 0 {
1406+
if printed {
1407+
fmt.Printf(",")
1408+
}
1409+
fmt.Printf("%s", regLive.niceString(lv.f.Config))
1410+
printed = true
1411+
}
13791412
}
1380-
fmt.Printf("%v", n)
1381-
printed = true
1413+
fmt.Printf("\n")
13821414
}
1383-
regLive := lv.regMaps[lv.livenessMap.Get(v).regMapIndex]
1384-
if regLive != 0 {
1385-
if printed {
1386-
fmt.Printf(",")
1387-
}
1388-
fmt.Printf("%s", regLive.niceString(lv.f.Config))
1415+
1416+
if pcdata.isUnsafePoint {
1417+
fmt.Printf("\tunsafe-point\n")
13891418
}
1390-
fmt.Printf("\n")
13911419
}
13921420

13931421
// bb bitsets
@@ -1503,7 +1531,7 @@ func liveness(e *ssafn, f *ssa.Func, pp *Progs) LivenessMap {
15031531
lv.showlive(nil, lv.stackMaps[0])
15041532
for _, b := range f.Blocks {
15051533
for _, val := range b.Values {
1506-
if idx := lv.livenessMap.Get(val); idx.Valid() {
1534+
if idx := lv.livenessMap.Get(val); idx.StackMapValid() {
15071535
lv.showlive(val, lv.stackMaps[idx.stackMapIndex])
15081536
}
15091537
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6011,7 +6011,7 @@ func genssa(f *ssa.Func, pp *Progs) {
60116011
// instruction. We won't use the actual liveness map on a
60126012
// control instruction. Just mark it something that is
60136013
// preemptible.
6014-
s.pp.nextLive = LivenessIndex{-1, -1}
6014+
s.pp.nextLive = LivenessIndex{-1, -1, false}
60156015

60166016
// Emit values in block
60176017
thearch.SSAMarkMoves(&s, b)
@@ -6571,7 +6571,7 @@ func (s *SSAGenState) Call(v *ssa.Value) *obj.Prog {
65716571
// since it emits PCDATA for the stack map at the call (calls are safe points).
65726572
func (s *SSAGenState) PrepareCall(v *ssa.Value) {
65736573
idx := s.livenessMap.Get(v)
6574-
if !idx.Valid() {
6574+
if !idx.StackMapValid() {
65756575
// typedmemclr and typedmemmove are write barriers and
65766576
// deeply non-preemptible. They are unsafe points and
65776577
// hence should not have liveness maps.

src/cmd/internal/objabi/funcdata.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ const (
2828
// This value is generated by the compiler, assembler, or linker.
2929
ArgsSizeUnknown = -0x80000000
3030
)
31+
32+
// Special PCDATA values.
33+
const (
34+
// PCDATA_RegMapIndex values.
35+
PCDATA_RegMapUnsafe = -2 // Unsafe for async preemption
36+
)

0 commit comments

Comments
 (0)