]> Cypherpunks.ru repositories - gostls13.git/commitdiff
runtime: get a better g0 stack bound in needm
authorCherry Mui <cherryyz@google.com>
Tue, 28 Mar 2023 18:48:59 +0000 (14:48 -0400)
committerCherry Mui <cherryyz@google.com>
Thu, 30 Mar 2023 23:23:55 +0000 (23:23 +0000)
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.

This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.

For #59294.

Change-Id: Ie52a8f931e0648d8753e4c1dbe45468b8748b527
Reviewed-on: https://go-review.googlesource.com/c/go/+/479915
Run-TryBot: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
misc/cgo/testcarchive/carchive_test.go
misc/cgo/testcarchive/testdata/libgo9/a.go [new file with mode: 0644]
misc/cgo/testcarchive/testdata/main9.c [new file with mode: 0644]
src/runtime/cgo.go
src/runtime/cgo/callbacks.go
src/runtime/cgo/gcc_stack_darwin.c [new file with mode: 0644]
src/runtime/cgo/gcc_stack_unix.c [new file with mode: 0644]
src/runtime/cgo/gcc_stack_windows.c [new file with mode: 0644]
src/runtime/proc.go
src/runtime/signal_unix.go

index 8a39c24a6dcc1134fea38447d92e95c35137cf24..599626801812c5497cf1e6be73aad10b96c6f054 100644 (file)
@@ -1247,3 +1247,57 @@ func TestPreemption(t *testing.T) {
                t.Error(err)
        }
 }
+
+// Issue 59294. Test calling Go function from C after using some
+// stack space.
+func TestDeepStack(t *testing.T) {
+       t.Parallel()
+
+       if !testWork {
+               defer func() {
+                       os.Remove("testp9" + exeSuffix)
+                       os.Remove("libgo9.a")
+                       os.Remove("libgo9.h")
+               }()
+       }
+
+       cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo9.a", "./libgo9")
+       out, err := cmd.CombinedOutput()
+       t.Logf("%v\n%s", cmd.Args, out)
+       if err != nil {
+               t.Fatal(err)
+       }
+       checkLineComments(t, "libgo9.h")
+       checkArchive(t, "libgo9.a")
+
+       // build with -O0 so the C compiler won't optimize out the large stack frame
+       ccArgs := append(cc, "-O0", "-o", "testp9"+exeSuffix, "main9.c", "libgo9.a")
+       out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
+       t.Logf("%v\n%s", ccArgs, out)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       argv := cmdToRun("./testp9")
+       cmd = exec.Command(argv[0], argv[1:]...)
+       sb := new(strings.Builder)
+       cmd.Stdout = sb
+       cmd.Stderr = sb
+       if err := cmd.Start(); err != nil {
+               t.Fatal(err)
+       }
+
+       timer := time.AfterFunc(time.Minute,
+               func() {
+                       t.Error("test program timed out")
+                       cmd.Process.Kill()
+               },
+       )
+       defer timer.Stop()
+
+       err = cmd.Wait()
+       t.Logf("%v\n%s", cmd.Args, sb)
+       if err != nil {
+               t.Error(err)
+       }
+}
diff --git a/misc/cgo/testcarchive/testdata/libgo9/a.go b/misc/cgo/testcarchive/testdata/libgo9/a.go
new file mode 100644 (file)
index 0000000..acb08d9
--- /dev/null
@@ -0,0 +1,14 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "runtime"
+
+import "C"
+
+func main() {}
+
+//export GoF
+func GoF() { runtime.GC() }
diff --git a/misc/cgo/testcarchive/testdata/main9.c b/misc/cgo/testcarchive/testdata/main9.c
new file mode 100644 (file)
index 0000000..95ad4de
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "libgo9.h"
+
+void use(int *x) { (*x)++; }
+
+void callGoFWithDeepStack() {
+       int x[10000];
+
+       use(&x[0]);
+       use(&x[9999]);
+
+       GoF();
+
+       use(&x[0]);
+       use(&x[9999]);
+}
+
+int main() {
+       GoF();                  // call GoF without using much stack
+       callGoFWithDeepStack(); // call GoF with a deep stack
+}
index 6a3eeb58221162c4b8062f6a42c43f7616bbf544..395303552c9bd1e3bc8f3cc11a1db75e963e2401 100644 (file)
@@ -19,6 +19,7 @@ import "unsafe"
 //go:linkname _cgo_yield _cgo_yield
 //go:linkname _cgo_pthread_key_created _cgo_pthread_key_created
 //go:linkname _cgo_bindm _cgo_bindm
+//go:linkname _cgo_getstackbound _cgo_getstackbound
 
 var (
        _cgo_init                     unsafe.Pointer
@@ -30,6 +31,7 @@ var (
        _cgo_yield                    unsafe.Pointer
        _cgo_pthread_key_created      unsafe.Pointer
        _cgo_bindm                    unsafe.Pointer
+       _cgo_getstackbound            unsafe.Pointer
 )
 
 // iscgo is set to true by the runtime/cgo package
index 792dd7d0860d3f8c6a1572f509a438e194183b76..3c246a88b6ca9163ca07e46696502f85de3b0f2b 100644 (file)
@@ -141,3 +141,12 @@ var _cgo_yield unsafe.Pointer
 
 //go:cgo_export_static _cgo_topofstack
 //go:cgo_export_dynamic _cgo_topofstack
+
+// x_cgo_getstackbound gets the thread's C stack size and
+// set the G's stack bound based on the stack size.
+
+//go:cgo_import_static x_cgo_getstackbound
+//go:linkname x_cgo_getstackbound x_cgo_getstackbound
+//go:linkname _cgo_getstackbound _cgo_getstackbound
+var x_cgo_getstackbound byte
+var _cgo_getstackbound = &x_cgo_getstackbound
diff --git a/src/runtime/cgo/gcc_stack_darwin.c b/src/runtime/cgo/gcc_stack_darwin.c
new file mode 100644 (file)
index 0000000..2cc9b76
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include <pthread.h>
+#include "libcgo.h"
+
+void
+x_cgo_getstackbound(G *g)
+{
+       void* addr;
+       size_t size;
+       pthread_t p;
+
+       p = pthread_self();
+       addr = pthread_get_stackaddr_np(p); // high address (!)
+       size = pthread_get_stacksize_np(p);
+       g->stacklo = (uintptr)addr - size;
+       // NOTE: don't change g->stackhi. We are called from asmcgocall
+       // which saves the stack depth based on g->stackhi.
+}
diff --git a/src/runtime/cgo/gcc_stack_unix.c b/src/runtime/cgo/gcc_stack_unix.c
new file mode 100644 (file)
index 0000000..3826322
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix && !darwin
+
+#ifndef _GNU_SOURCE // pthread_getattr_np
+#define _GNU_SOURCE
+#endif
+
+#include <pthread.h>
+#include "libcgo.h"
+
+void
+x_cgo_getstackbound(G *g)
+{
+       pthread_attr_t attr;
+       void *addr;
+       size_t size;
+
+       pthread_attr_init(&attr);
+#if defined(__GLIBC__) || defined(__sun)
+       pthread_getattr_np(pthread_self(), &attr);  // GNU extension
+       pthread_attr_getstack(&attr, &addr, &size); // low address
+#else
+       pthread_attr_getstacksize(&attr, &size);
+       addr = __builtin_frame_address(0) + 4096 - size;
+#endif
+       g->stacklo = (uintptr)addr;
+       // NOTE: don't change g->stackhi. We are called from asmcgocall
+       // which saves the stack depth based on g->stackhi.
+}
diff --git a/src/runtime/cgo/gcc_stack_windows.c b/src/runtime/cgo/gcc_stack_windows.c
new file mode 100644 (file)
index 0000000..9fcb59c
--- /dev/null
@@ -0,0 +1,7 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "libcgo.h"
+
+void x_cgo_getstackbound(G *g) {} // no-op for now
index fd7760a571fd2f23057a05d1e104d79c03576428..4152aa485244d74e4638272dad6a84410c0b1649 100644 (file)
@@ -1889,8 +1889,11 @@ func allocm(pp *p, fn func(), id int64) *m {
 // 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
@@ -1939,14 +1942,23 @@ 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.
@@ -1967,7 +1979,7 @@ func needm() {
 //
 //go:nosplit
 func needAndBindM() {
-       needm()
+       needm(false)
 
        if _cgo_pthread_key_created != nil && *(*uintptr)(_cgo_pthread_key_created) != 0 {
                cgoBindM()
index d1719b22ffe8e13e01d4125daefd85d8dd8e941f..c7edbcd239af3ba78f087380d23d48403f835fcd 100644 (file)
@@ -585,7 +585,7 @@ func adjustSignalStack(sig uint32, mp *m, gsigStack *gsignalStack) bool {
 
        // sp is not within gsignal stack, g0 stack, or sigaltstack. Bad.
        setg(nil)
-       needm()
+       needm(true)
        if st.ss_flags&_SS_DISABLE != 0 {
                noSignalStack(sig)
        } else {
@@ -1068,7 +1068,7 @@ func badsignal(sig uintptr, c *sigctxt) {
                exit(2)
                *(*uintptr)(unsafe.Pointer(uintptr(123))) = 2
        }
-       needm()
+       needm(true)
        if !sigsend(uint32(sig)) {
                // A foreign thread received the signal sig, and the
                // Go code does not want to handle it.