mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/ld: fix large stack split for preempt check
If the stack frame size is larger than the known-unmapped region at the
bottom of the address space, then the stack split prologue cannot use the usual
condition:
SP - size >= stackguard
because SP - size may wrap around to a very large number.
Instead, if the stack frame is large, the prologue tests:
SP - stackguard >= size
(This ends up being a few instructions more expensive, so we don't do it always.)
Preemption requests register by setting stackguard to a very large value, so
that the first test (SP - size >= stackguard) cannot possibly succeed.
Unfortunately, that same very large value causes a wraparound in the
second test (SP - stackguard >= size), making it succeed incorrectly.
To avoid *that* wraparound, we have to amend the test:
stackguard != StackPreempt && SP - stackguard >= size
This test is only used for functions with large frames, which essentially
always split the stack, so the cost of the few instructions is noise.
This CL and CL 11085043 together fix the known issues with preemption,
at the beginning of a function, so we will be able to try turning it on again.
R=ken2
CC=golang-dev
https://golang.org/cl/11205043
This commit is contained in:
parent
56cd47b295
commit
031c107cad
6 changed files with 149 additions and 15 deletions
|
|
@ -219,6 +219,76 @@ func stackGrowthRecursive(i int) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPreemptSplitBig(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in -short mode")
|
||||
}
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
|
||||
stop := make(chan int)
|
||||
go big(stop)
|
||||
for i := 0; i < 3; i++ {
|
||||
time.Sleep(1 * time.Microsecond) // let big start running
|
||||
runtime.GC()
|
||||
}
|
||||
close(stop)
|
||||
}
|
||||
|
||||
func big(stop chan int) int {
|
||||
n := 0
|
||||
for {
|
||||
// delay so that gc is sure to have asked for a preemption
|
||||
for i := int64(0); i < 1e9; i++ {
|
||||
n++
|
||||
}
|
||||
|
||||
// call bigframe, which used to miss the preemption in its prologue.
|
||||
bigframe(stop)
|
||||
|
||||
// check if we've been asked to stop.
|
||||
select {
|
||||
case <-stop:
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func bigframe(stop chan int) int {
|
||||
// not splitting the stack will overflow.
|
||||
// small will notice that it needs a stack split and will
|
||||
// catch the overflow.
|
||||
var x [8192]byte
|
||||
return small(stop, &x)
|
||||
}
|
||||
|
||||
func small(stop chan int, x *[8192]byte) int {
|
||||
for i := range x {
|
||||
x[i] = byte(i)
|
||||
}
|
||||
sum := 0
|
||||
for i := range x {
|
||||
sum += int(x[i])
|
||||
}
|
||||
|
||||
// keep small from being a leaf function, which might
|
||||
// make it not do any stack check at all.
|
||||
nonleaf(stop)
|
||||
|
||||
return sum
|
||||
}
|
||||
|
||||
func nonleaf(stop chan int) bool {
|
||||
// do something that won't be inlined:
|
||||
select {
|
||||
case <-stop:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func poll() {
|
||||
}
|
||||
|
||||
func TestSchedLocalQueue(t *testing.T) {
|
||||
runtime.TestSchedLocalQueue1()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue