mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime: avoid relying on the unwinder in deferreturn
This CL changes deferreturn so that it never needs to invoke the unwinder. Instead, in the unusual case that we recover into a frame with pending open-coded defers, we now save the extra state needed to find them in g.param. Change-Id: Ied35f6c1063fee5b6044cc37b2bccd3f90682fe6 Reviewed-on: https://go-review.googlesource.com/c/go/+/515856 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Keith Randall <khr@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
parent
2bf8b3985f
commit
9869699c44
2 changed files with 64 additions and 23 deletions
|
|
@ -635,19 +635,26 @@ func (p *_panic) start(pc uintptr, sp unsafe.Pointer) {
|
||||||
p.startPC = getcallerpc()
|
p.startPC = getcallerpc()
|
||||||
p.startSP = unsafe.Pointer(getcallersp())
|
p.startSP = unsafe.Pointer(getcallersp())
|
||||||
|
|
||||||
if !p.deferreturn {
|
if p.deferreturn {
|
||||||
p.link = gp._panic
|
p.sp = sp
|
||||||
gp._panic = (*_panic)(noescape(unsafe.Pointer(p)))
|
|
||||||
} else {
|
if s := (*savedOpenDeferState)(gp.param); s != nil {
|
||||||
// Fast path for deferreturn: if there's a pending linked defer
|
// recovery saved some state for us, so that we can resume
|
||||||
// for this frame, then we know there aren't any open-coded
|
// calling open-coded defers without unwinding the stack.
|
||||||
// defers, and we don't need to find the parent frame either.
|
|
||||||
if d := gp._defer; d != nil && d.sp == uintptr(sp) {
|
gp.param = nil
|
||||||
p.sp = sp
|
|
||||||
return
|
p.retpc = s.retpc
|
||||||
|
p.deferBitsPtr = (*byte)(add(sp, s.deferBitsOffset))
|
||||||
|
p.slotsPtr = add(sp, s.slotsOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.link = gp._panic
|
||||||
|
gp._panic = (*_panic)(noescape(unsafe.Pointer(p)))
|
||||||
|
|
||||||
// Initialize state machine, and find the first frame with a defer.
|
// Initialize state machine, and find the first frame with a defer.
|
||||||
//
|
//
|
||||||
// Note: We could use startPC and startSP here, but callers will
|
// Note: We could use startPC and startSP here, but callers will
|
||||||
|
|
@ -684,6 +691,15 @@ func (p *_panic) nextDefer() (func(), bool) {
|
||||||
for {
|
for {
|
||||||
for p.deferBitsPtr != nil {
|
for p.deferBitsPtr != nil {
|
||||||
bits := *p.deferBitsPtr
|
bits := *p.deferBitsPtr
|
||||||
|
|
||||||
|
// Check whether any open-coded defers are still pending.
|
||||||
|
//
|
||||||
|
// Note: We need to check this upfront (rather than after
|
||||||
|
// clearing the top bit) because it's possible that Goexit
|
||||||
|
// invokes a deferred call, and there were still more pending
|
||||||
|
// open-coded defers in the frame; but then the deferred call
|
||||||
|
// panic and invoked the remaining defers in the frame, before
|
||||||
|
// recovering and restarting the Goexit loop.
|
||||||
if bits == 0 {
|
if bits == 0 {
|
||||||
p.deferBitsPtr = nil
|
p.deferBitsPtr = nil
|
||||||
break
|
break
|
||||||
|
|
@ -730,9 +746,7 @@ func (p *_panic) nextFrame() (ok bool) {
|
||||||
gp := getg()
|
gp := getg()
|
||||||
systemstack(func() {
|
systemstack(func() {
|
||||||
var limit uintptr
|
var limit uintptr
|
||||||
if p.deferreturn {
|
if d := gp._defer; d != nil {
|
||||||
limit = uintptr(p.fp)
|
|
||||||
} else if d := gp._defer; d != nil {
|
|
||||||
limit = uintptr(d.sp)
|
limit = uintptr(d.sp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -749,22 +763,18 @@ func (p *_panic) nextFrame() (ok bool) {
|
||||||
// then we can simply loop until we find the next frame where
|
// then we can simply loop until we find the next frame where
|
||||||
// it's non-zero.
|
// it's non-zero.
|
||||||
|
|
||||||
if p.initOpenCodedDefers(u.frame.fn, unsafe.Pointer(u.frame.varp)) {
|
if u.frame.sp == limit {
|
||||||
break // found a frame with open-coded defers
|
break // found a frame with linked defers
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.frame.sp == limit {
|
if p.initOpenCodedDefers(u.frame.fn, unsafe.Pointer(u.frame.varp)) {
|
||||||
break // found a frame with linked defers, or deferreturn with no defers
|
break // found a frame with open-coded defers
|
||||||
}
|
}
|
||||||
|
|
||||||
u.next()
|
u.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.deferreturn {
|
p.lr = u.frame.lr
|
||||||
p.lr = 0 // prevent unwinding past this frame
|
|
||||||
} else {
|
|
||||||
p.lr = u.frame.lr
|
|
||||||
}
|
|
||||||
p.sp = unsafe.Pointer(u.frame.sp)
|
p.sp = unsafe.Pointer(u.frame.sp)
|
||||||
p.fp = unsafe.Pointer(u.frame.fp)
|
p.fp = unsafe.Pointer(u.frame.fp)
|
||||||
|
|
||||||
|
|
@ -889,6 +899,7 @@ var paniclk mutex
|
||||||
func recovery(gp *g) {
|
func recovery(gp *g) {
|
||||||
p := gp._panic
|
p := gp._panic
|
||||||
pc, sp := p.retpc, uintptr(p.sp)
|
pc, sp := p.retpc, uintptr(p.sp)
|
||||||
|
p0, saveOpenDeferState := p, p.deferBitsPtr != nil && *p.deferBitsPtr != 0
|
||||||
|
|
||||||
// Unwind the panic stack.
|
// Unwind the panic stack.
|
||||||
for ; p != nil && uintptr(p.startSP) < sp; p = p.link {
|
for ; p != nil && uintptr(p.startSP) < sp; p = p.link {
|
||||||
|
|
@ -913,6 +924,7 @@ func recovery(gp *g) {
|
||||||
// worthwhile though.
|
// worthwhile though.
|
||||||
if p.goexit {
|
if p.goexit {
|
||||||
pc, sp = p.startPC, uintptr(p.startSP)
|
pc, sp = p.startPC, uintptr(p.startSP)
|
||||||
|
saveOpenDeferState = false // goexit is unwinding the stack anyway
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -924,6 +936,24 @@ func recovery(gp *g) {
|
||||||
gp.sig = 0
|
gp.sig = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if gp.param != nil {
|
||||||
|
throw("unexpected gp.param")
|
||||||
|
}
|
||||||
|
if saveOpenDeferState {
|
||||||
|
// If we're returning to deferreturn and there are more open-coded
|
||||||
|
// defers for it to call, save enough state for it to be able to
|
||||||
|
// pick up where p0 left off.
|
||||||
|
gp.param = unsafe.Pointer(&savedOpenDeferState{
|
||||||
|
retpc: p0.retpc,
|
||||||
|
|
||||||
|
// We need to save deferBitsPtr and slotsPtr too, but those are
|
||||||
|
// stack pointers. To avoid issues around heap objects pointing
|
||||||
|
// to the stack, save them as offsets from SP.
|
||||||
|
deferBitsOffset: uintptr(unsafe.Pointer(p0.deferBitsPtr)) - uintptr(p0.sp),
|
||||||
|
slotsOffset: uintptr(p0.slotsPtr) - uintptr(p0.sp),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(mdempsky): Currently, we rely on frames containing "defer"
|
// TODO(mdempsky): Currently, we rely on frames containing "defer"
|
||||||
// to end with "CALL deferreturn; RET". This allows deferreturn to
|
// to end with "CALL deferreturn; RET". This allows deferreturn to
|
||||||
// finish running any pending defers in the frame.
|
// finish running any pending defers in the frame.
|
||||||
|
|
|
||||||
|
|
@ -433,7 +433,7 @@ type g struct {
|
||||||
// param is a generic pointer parameter field used to pass
|
// param is a generic pointer parameter field used to pass
|
||||||
// values in particular contexts where other storage for the
|
// values in particular contexts where other storage for the
|
||||||
// parameter would be difficult to find. It is currently used
|
// parameter would be difficult to find. It is currently used
|
||||||
// in three ways:
|
// in four ways:
|
||||||
// 1. When a channel operation wakes up a blocked goroutine, it sets param to
|
// 1. When a channel operation wakes up a blocked goroutine, it sets param to
|
||||||
// point to the sudog of the completed blocking operation.
|
// point to the sudog of the completed blocking operation.
|
||||||
// 2. By gcAssistAlloc1 to signal back to its caller that the goroutine completed
|
// 2. By gcAssistAlloc1 to signal back to its caller that the goroutine completed
|
||||||
|
|
@ -441,6 +441,8 @@ type g struct {
|
||||||
// stack may have moved in the meantime.
|
// stack may have moved in the meantime.
|
||||||
// 3. By debugCallWrap to pass parameters to a new goroutine because allocating a
|
// 3. By debugCallWrap to pass parameters to a new goroutine because allocating a
|
||||||
// closure in the runtime is forbidden.
|
// closure in the runtime is forbidden.
|
||||||
|
// 4. When a panic is recovered and control returns to the respective frame,
|
||||||
|
// param may point to a savedOpenDeferState.
|
||||||
param unsafe.Pointer
|
param unsafe.Pointer
|
||||||
atomicstatus atomic.Uint32
|
atomicstatus atomic.Uint32
|
||||||
stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
|
stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
|
||||||
|
|
@ -1041,6 +1043,15 @@ type _panic struct {
|
||||||
deferreturn bool
|
deferreturn bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// savedOpenDeferState tracks the extra state from _panic that's
|
||||||
|
// necessary for deferreturn to pick up where gopanic left off,
|
||||||
|
// without needing to unwind the stack.
|
||||||
|
type savedOpenDeferState struct {
|
||||||
|
retpc uintptr
|
||||||
|
deferBitsOffset uintptr
|
||||||
|
slotsOffset uintptr
|
||||||
|
}
|
||||||
|
|
||||||
// ancestorInfo records details of where a goroutine was started.
|
// ancestorInfo records details of where a goroutine was started.
|
||||||
type ancestorInfo struct {
|
type ancestorInfo struct {
|
||||||
pcs []uintptr // pcs from the stack of this goroutine
|
pcs []uintptr // pcs from the stack of this goroutine
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue