]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/runtime/proc.go
runtime/cgo: store M for C-created thread in pthread key
[gostls13.git] / src / runtime / proc.go
index 0c6595f92f2cfb4f5738dbeead563bd9c40a8df4..4152aa485244d74e4638272dad6a84410c0b1649 100644 (file)
@@ -210,6 +210,10 @@ func main() {
 
        main_init_done = make(chan bool)
        if iscgo {
+               if _cgo_pthread_key_created == nil {
+                       throw("_cgo_pthread_key_created missing")
+               }
+
                if _cgo_thread_start == nil {
                        throw("_cgo_thread_start missing")
                }
@@ -224,6 +228,13 @@ func main() {
                if _cgo_notify_runtime_init_done == nil {
                        throw("_cgo_notify_runtime_init_done missing")
                }
+
+               // Set the x_crosscall2_ptr C function pointer variable point to crosscall2.
+               if set_crosscall2 == nil {
+                       throw("set_crosscall2 missing")
+               }
+               set_crosscall2()
+
                // Start the template thread in case we enter Go from
                // a C-created thread and need to create a new thread.
                startTemplateThread()
@@ -1874,11 +1885,15 @@ func allocm(pp *p, fn func(), id int64) *m {
 // pressed into service as the scheduling stack and current
 // goroutine for the duration of the cgo callback.
 //
-// When the callback is done with the m, it calls dropm to
-// put the m back on the list.
+// It calls dropm to put the m back on the list,
+// 1. when the callback is done with the m in non-pthread platforms,
+// 2. or when the C thread exiting on pthread platforms.
+//
+// The signal argument indicates whether we're called from a signal
+// handler.
 //
 //go:nosplit
-func needm() {
+func needm(signal bool) {
        if (iscgo || GOOS == "windows") && !cgoHasExtraM {
                // Can happen if C/C++ code calls Go from a global ctor.
                // Can also happen on Windows if a global ctor uses a
@@ -1927,16 +1942,30 @@ func needm() {
        osSetupTLS(mp)
 
        // Install g (= m->g0) and set the stack bounds
-       // to match the current stack. We don't actually know
+       // to match the current stack. If we don't actually know
        // how big the stack is, like we don't know how big any
-       // scheduling stack is, but we assume there's at least 32 kB,
-       // which is more than enough for us.
+       // scheduling stack is, but we assume there's at least 32 kB.
+       // If we can get a more accurate stack bound from pthread,
+       // use that.
        setg(mp.g0)
        gp := getg()
        gp.stack.hi = getcallersp() + 1024
        gp.stack.lo = getcallersp() - 32*1024
+       if !signal && _cgo_getstackbound != nil {
+               // Don't adjust if called from the signal handler.
+               // We are on the signal stack, not the pthread stack.
+               // (We could get the stack bounds from sigaltstack, but
+               // we're getting out of the signal handler very soon
+               // anyway. Not worth it.)
+               asmcgocall(_cgo_getstackbound, unsafe.Pointer(gp))
+       }
        gp.stackguard0 = gp.stack.lo + _StackGuard
 
+       // Should mark we are already in Go now.
+       // Otherwise, we may call needm again when we get a signal, before cgocallbackg1,
+       // which means the extram list may be empty, that will cause a deadlock.
+       mp.isExtraInC = false
+
        // Initialize this thread to use the m.
        asminit()
        minit()
@@ -1946,6 +1975,17 @@ func needm() {
        sched.ngsys.Add(-1)
 }
 
+// Acquire an extra m and bind it to the C thread when a pthread key has been created.
+//
+//go:nosplit
+func needAndBindM() {
+       needm(false)
+
+       if _cgo_pthread_key_created != nil && *(*uintptr)(_cgo_pthread_key_created) != 0 {
+               cgoBindM()
+       }
+}
+
 // newextram allocates m's and puts them on the extra list.
 // It is called with a working local m, so that it can do things
 // like call schedlock and allocate.
@@ -1990,6 +2030,8 @@ func oneNewExtraM() {
        gp.m = mp
        mp.curg = gp
        mp.isextra = true
+       // mark we are in C by default.
+       mp.isExtraInC = true
        mp.lockedInt++
        mp.lockedg.set(gp)
        gp.lockedm.set(mp)
@@ -2022,9 +2064,11 @@ func oneNewExtraM() {
        unlockextra(mp)
 }
 
+// dropm puts the current m back onto the extra list.
+//
+// 1. On systems without pthreads, like Windows
 // dropm is called when a cgo callback has called needm but is now
 // done with the callback and returning back into the non-Go thread.
-// It puts the current m back onto the extra list.
 //
 // The main expense here is the call to signalstack to release the
 // m's signal stack, and then the call to needm on the next callback
@@ -2036,15 +2080,18 @@ func oneNewExtraM() {
 // call. These should typically not be scheduling operations, just a few
 // atomics, so the cost should be small.
 //
-// TODO(rsc): An alternative would be to allocate a dummy pthread per-thread
-// variable using pthread_key_create. Unlike the pthread keys we already use
-// on OS X, this dummy key would never be read by Go code. It would exist
-// only so that we could register at thread-exit-time destructor.
-// That destructor would put the m back onto the extra list.
-// This is purely a performance optimization. The current version,
-// in which dropm happens on each cgo call, is still correct too.
-// We may have to keep the current version on systems with cgo
-// but without pthreads, like Windows.
+// 2. On systems with pthreads
+// dropm is called while a non-Go thread is exiting.
+// We allocate a pthread per-thread variable using pthread_key_create,
+// to register a thread-exit-time destructor.
+// And store the g into a thread-specific value associated with the pthread key,
+// when first return back to C.
+// So that the destructor would invoke dropm while the non-Go thread is exiting.
+// This is much faster since it avoids expensive signal-related syscalls.
+//
+// NOTE: this always runs without a P, so, nowritebarrierrec required.
+//
+//go:nowritebarrierrec
 func dropm() {
        // Clear m and g, and return m to the extra list.
        // After the call to setg we can only call nosplit functions
@@ -2076,6 +2123,39 @@ func dropm() {
        msigrestore(sigmask)
 }
 
+// bindm store the g0 of the current m into a thread-specific value.
+//
+// We allocate a pthread per-thread variable using pthread_key_create,
+// to register a thread-exit-time destructor.
+// We are here setting the thread-specific value of the pthread key, to enable the destructor.
+// So that the pthread_key_destructor would dropm while the C thread is exiting.
+//
+// And the saved g will be used in pthread_key_destructor,
+// since the g stored in the TLS by Go might be cleared in some platforms,
+// before the destructor invoked, so, we restore g by the stored g, before dropm.
+//
+// We store g0 instead of m, to make the assembly code simpler,
+// since we need to restore g0 in runtime.cgocallback.
+//
+// On systems without pthreads, like Windows, bindm shouldn't be used.
+//
+// NOTE: this always runs without a P, so, nowritebarrierrec required.
+//
+//go:nosplit
+//go:nowritebarrierrec
+func cgoBindM() {
+       if GOOS == "windows" || GOOS == "plan9" {
+               fatal("bindm in unexpected GOOS")
+       }
+       g := getg()
+       if g.m.g0 != g {
+               fatal("the current g is not g0")
+       }
+       if _cgo_bindm != nil {
+               asmcgocall(_cgo_bindm, unsafe.Pointer(g))
+       }
+}
+
 // A helper function for EnsureDropM.
 func getm() uintptr {
        return uintptr(unsafe.Pointer(getg().m))