Commit 332719f7 authored by Ian Lance Taylor's avatar Ian Lance Taylor

runtime: don't call lockOSThread for every cgo call

For a trivial benchmark with a do-nothing cgo call:

name    old time/op  new time/op  delta
Call-4  64.5ns ± 7%  63.0ns ± 6%  -2.25%  (p=0.027 n=20+16)

Because Windows uses the cgocall mechanism to make system calls,
and passes arguments in a struct held in the m,
we need to do the lockOSThread/unlockOSThread in that code.

Because deferreturn was getting a nosplit stack overflow error,
change it to avoid calling typedmemmove.

Updates #21827.

Change-Id: I9b1d61434c44faeb29805b46b409c812c9acadc2
Reviewed-on: https://go-review.googlesource.com/64070
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAustin Clements <austin@google.com>
Reviewed-by: default avatarDavid Crawshaw <crawshaw@golang.org>
parent 9daee931
...@@ -8,9 +8,9 @@ ...@@ -8,9 +8,9 @@
// runtime.cgocall(_cgo_Cfunc_f, frame), where _cgo_Cfunc_f is a // runtime.cgocall(_cgo_Cfunc_f, frame), where _cgo_Cfunc_f is a
// gcc-compiled function written by cgo. // gcc-compiled function written by cgo.
// //
// runtime.cgocall (below) locks g to m, calls entersyscall // runtime.cgocall (below) calls entersyscall so as not to block
// so as not to block other goroutines or the garbage collector, // other goroutines or the garbage collector, and then calls
// and then calls runtime.asmcgocall(_cgo_Cfunc_f, frame). // runtime.asmcgocall(_cgo_Cfunc_f, frame).
// //
// runtime.asmcgocall (in asm_$GOARCH.s) switches to the m->g0 stack // runtime.asmcgocall (in asm_$GOARCH.s) switches to the m->g0 stack
// (assumed to be an operating system-allocated stack, so safe to run // (assumed to be an operating system-allocated stack, so safe to run
...@@ -104,13 +104,9 @@ func cgocall(fn, arg unsafe.Pointer) int32 { ...@@ -104,13 +104,9 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
racereleasemerge(unsafe.Pointer(&racecgosync)) racereleasemerge(unsafe.Pointer(&racecgosync))
} }
// Lock g to m to ensure we stay on the same stack if we do a
// cgo callback. In case of panic, unwindm calls endcgo.
lockOSThread()
mp := getg().m mp := getg().m
mp.ncgocall++ mp.ncgocall++
mp.ncgo++ mp.ncgo++
mp.incgo = true
// Reset traceback. // Reset traceback.
mp.cgoCallers[0] = 0 mp.cgoCallers[0] = 0
...@@ -130,7 +126,14 @@ func cgocall(fn, arg unsafe.Pointer) int32 { ...@@ -130,7 +126,14 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
// and then re-enter the "system call" reusing the PC and SP // and then re-enter the "system call" reusing the PC and SP
// saved by entersyscall here. // saved by entersyscall here.
entersyscall(0) entersyscall(0)
mp.incgo = true
errno := asmcgocall(fn, arg) errno := asmcgocall(fn, arg)
// Call endcgo before exitsyscall because exitsyscall may
// reschedule us on to a different M.
endcgo(mp)
exitsyscall(0) exitsyscall(0)
// From the garbage collector's perspective, time can move // From the garbage collector's perspective, time can move
...@@ -145,8 +148,8 @@ func cgocall(fn, arg unsafe.Pointer) int32 { ...@@ -145,8 +148,8 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
// GC by forcing them to stay live across this time warp. // GC by forcing them to stay live across this time warp.
KeepAlive(fn) KeepAlive(fn)
KeepAlive(arg) KeepAlive(arg)
KeepAlive(mp)
endcgo(mp)
return errno return errno
} }
...@@ -158,8 +161,6 @@ func endcgo(mp *m) { ...@@ -158,8 +161,6 @@ func endcgo(mp *m) {
if raceenabled { if raceenabled {
raceacquire(unsafe.Pointer(&racecgosync)) raceacquire(unsafe.Pointer(&racecgosync))
} }
unlockOSThread() // invalidates mp
} }
// Call from C back to Go. // Call from C back to Go.
...@@ -171,6 +172,12 @@ func cgocallbackg(ctxt uintptr) { ...@@ -171,6 +172,12 @@ func cgocallbackg(ctxt uintptr) {
exit(2) exit(2)
} }
// The call from C is on gp.m's g0 stack, so we must ensure
// that we stay on that M. We have to do this before calling
// exitsyscall, since it would otherwise be free to move us to
// a different M. The call to unlockOSThread is in unwindm.
lockOSThread()
// Save current syscall parameters, so m.syscall can be // Save current syscall parameters, so m.syscall can be
// used again if callback decide to make syscall. // used again if callback decide to make syscall.
syscall := gp.m.syscall syscall := gp.m.syscall
...@@ -186,6 +193,10 @@ func cgocallbackg(ctxt uintptr) { ...@@ -186,6 +193,10 @@ func cgocallbackg(ctxt uintptr) {
cgocallbackg1(ctxt) cgocallbackg1(ctxt)
// At this point unlockOSThread has been called.
// The following code must not change to a different m.
// This is enforced by checking incgo in the schedule function.
gp.m.incgo = true gp.m.incgo = true
// going back to cgo call // going back to cgo call
reentersyscall(savedpc, uintptr(savedsp)) reentersyscall(savedpc, uintptr(savedsp))
...@@ -321,9 +332,7 @@ func cgocallbackg1(ctxt uintptr) { ...@@ -321,9 +332,7 @@ func cgocallbackg1(ctxt uintptr) {
} }
func unwindm(restore *bool) { func unwindm(restore *bool) {
if !*restore { if *restore {
return
}
// Restore sp saved by cgocallback during // Restore sp saved by cgocallback during
// unwind of g's stack (see comment at top of file). // unwind of g's stack (see comment at top of file).
mp := acquirem() mp := acquirem()
...@@ -347,6 +356,11 @@ func unwindm(restore *bool) { ...@@ -347,6 +356,11 @@ func unwindm(restore *bool) {
} }
releasem(mp) releasem(mp)
}
// Undo the call to lockOSThread in cgocallbackg.
// We must still stay on the same m.
unlockOSThread()
} }
// called from assembly // called from assembly
......
...@@ -273,7 +273,17 @@ func freedefer(d *_defer) { ...@@ -273,7 +273,17 @@ func freedefer(d *_defer) {
unlock(&sched.deferlock) unlock(&sched.deferlock)
}) })
} }
*d = _defer{}
// These lines used to be simply `*d = _defer{}` but that
// started causing a nosplit stack overflow via typedmemmove.
d.siz = 0
d.started = false
d.sp = 0
d.pc = 0
d.fn = nil
d._panic = nil
d.link = nil
pp.deferpool[sc] = append(pp.deferpool[sc], d) pp.deferpool[sc] = append(pp.deferpool[sc], d)
} }
......
...@@ -2221,6 +2221,12 @@ func schedule() { ...@@ -2221,6 +2221,12 @@ func schedule() {
execute(_g_.m.lockedg.ptr(), false) // Never returns. execute(_g_.m.lockedg.ptr(), false) // Never returns.
} }
// We should not schedule away from a g that is executing a cgo call,
// since the cgo call is using the m's g0 stack.
if _g_.m.incgo {
throw("schedule: in cgo")
}
top: top:
if sched.gcwaiting != 0 { if sched.gcwaiting != 0 {
gcstopm() gcstopm()
......
...@@ -675,7 +675,8 @@ func extendRandom(r []byte, n int) { ...@@ -675,7 +675,8 @@ func extendRandom(r []byte, n int) {
} }
} }
// deferred subroutine calls // A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer.
type _defer struct { type _defer struct {
siz int32 siz int32
started bool started bool
......
...@@ -93,6 +93,8 @@ const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 ...@@ -93,6 +93,8 @@ const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary //go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
//go:nosplit //go:nosplit
func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) { func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
lockOSThread()
defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
if useLoadLibraryEx { if useLoadLibraryEx {
...@@ -126,6 +128,8 @@ func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) { ...@@ -126,6 +128,8 @@ func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
//go:linkname syscall_loadlibrary syscall.loadlibrary //go:linkname syscall_loadlibrary syscall.loadlibrary
//go:nosplit //go:nosplit
func syscall_loadlibrary(filename *uint16) (handle, err uintptr) { func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
lockOSThread()
defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
c.fn = getLoadLibrary() c.fn = getLoadLibrary()
c.n = 1 c.n = 1
...@@ -141,6 +145,8 @@ func syscall_loadlibrary(filename *uint16) (handle, err uintptr) { ...@@ -141,6 +145,8 @@ func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
//go:linkname syscall_getprocaddress syscall.getprocaddress //go:linkname syscall_getprocaddress syscall.getprocaddress
//go:nosplit //go:nosplit
func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) { func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) {
lockOSThread()
defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
c.fn = getGetProcAddress() c.fn = getGetProcAddress()
c.n = 2 c.n = 2
...@@ -156,6 +162,8 @@ func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uint ...@@ -156,6 +162,8 @@ func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uint
//go:linkname syscall_Syscall syscall.Syscall //go:linkname syscall_Syscall syscall.Syscall
//go:nosplit //go:nosplit
func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) { func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
lockOSThread()
defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
c.fn = fn c.fn = fn
c.n = nargs c.n = nargs
...@@ -167,6 +175,8 @@ func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) { ...@@ -167,6 +175,8 @@ func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
//go:linkname syscall_Syscall6 syscall.Syscall6 //go:linkname syscall_Syscall6 syscall.Syscall6
//go:nosplit //go:nosplit
func syscall_Syscall6(fn, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { func syscall_Syscall6(fn, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) {
lockOSThread()
defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
c.fn = fn c.fn = fn
c.n = nargs c.n = nargs
...@@ -178,6 +188,8 @@ func syscall_Syscall6(fn, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err ui ...@@ -178,6 +188,8 @@ func syscall_Syscall6(fn, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err ui
//go:linkname syscall_Syscall9 syscall.Syscall9 //go:linkname syscall_Syscall9 syscall.Syscall9
//go:nosplit //go:nosplit
func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) { func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
lockOSThread()
defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
c.fn = fn c.fn = fn
c.n = nargs c.n = nargs
...@@ -189,6 +201,8 @@ func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1 ...@@ -189,6 +201,8 @@ func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1
//go:linkname syscall_Syscall12 syscall.Syscall12 //go:linkname syscall_Syscall12 syscall.Syscall12
//go:nosplit //go:nosplit
func syscall_Syscall12(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 uintptr) (r1, r2, err uintptr) { func syscall_Syscall12(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 uintptr) (r1, r2, err uintptr) {
lockOSThread()
defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
c.fn = fn c.fn = fn
c.n = nargs c.n = nargs
...@@ -200,6 +214,8 @@ func syscall_Syscall12(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, ...@@ -200,6 +214,8 @@ func syscall_Syscall12(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
//go:linkname syscall_Syscall15 syscall.Syscall15 //go:linkname syscall_Syscall15 syscall.Syscall15
//go:nosplit //go:nosplit
func syscall_Syscall15(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { func syscall_Syscall15(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
lockOSThread()
defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
c.fn = fn c.fn = fn
c.n = nargs c.n = nargs
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment