mirror of
https://github.com/golang/go
synced 2024-11-26 09:08:07 -07:00
runtime: get a better g0 stack bound in needm
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>
This commit is contained in:
parent
c5ccff405e
commit
443eb9757c
@ -1247,3 +1247,57 @@ func TestPreemption(t *testing.T) {
|
|||||||
t.Error(err)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
14
misc/cgo/testcarchive/testdata/libgo9/a.go
vendored
Normal file
14
misc/cgo/testcarchive/testdata/libgo9/a.go
vendored
Normal file
@ -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() }
|
24
misc/cgo/testcarchive/testdata/main9.c
vendored
Normal file
24
misc/cgo/testcarchive/testdata/main9.c
vendored
Normal file
@ -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
|
||||||
|
}
|
@ -19,6 +19,7 @@ import "unsafe"
|
|||||||
//go:linkname _cgo_yield _cgo_yield
|
//go:linkname _cgo_yield _cgo_yield
|
||||||
//go:linkname _cgo_pthread_key_created _cgo_pthread_key_created
|
//go:linkname _cgo_pthread_key_created _cgo_pthread_key_created
|
||||||
//go:linkname _cgo_bindm _cgo_bindm
|
//go:linkname _cgo_bindm _cgo_bindm
|
||||||
|
//go:linkname _cgo_getstackbound _cgo_getstackbound
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_cgo_init unsafe.Pointer
|
_cgo_init unsafe.Pointer
|
||||||
@ -30,6 +31,7 @@ var (
|
|||||||
_cgo_yield unsafe.Pointer
|
_cgo_yield unsafe.Pointer
|
||||||
_cgo_pthread_key_created unsafe.Pointer
|
_cgo_pthread_key_created unsafe.Pointer
|
||||||
_cgo_bindm unsafe.Pointer
|
_cgo_bindm unsafe.Pointer
|
||||||
|
_cgo_getstackbound unsafe.Pointer
|
||||||
)
|
)
|
||||||
|
|
||||||
// iscgo is set to true by the runtime/cgo package
|
// iscgo is set to true by the runtime/cgo package
|
||||||
|
@ -141,3 +141,12 @@ var _cgo_yield unsafe.Pointer
|
|||||||
|
|
||||||
//go:cgo_export_static _cgo_topofstack
|
//go:cgo_export_static _cgo_topofstack
|
||||||
//go:cgo_export_dynamic _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
|
||||||
|
21
src/runtime/cgo/gcc_stack_darwin.c
Normal file
21
src/runtime/cgo/gcc_stack_darwin.c
Normal file
@ -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.
|
||||||
|
}
|
32
src/runtime/cgo/gcc_stack_unix.c
Normal file
32
src/runtime/cgo/gcc_stack_unix.c
Normal file
@ -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.
|
||||||
|
}
|
7
src/runtime/cgo/gcc_stack_windows.c
Normal file
7
src/runtime/cgo/gcc_stack_windows.c
Normal file
@ -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
|
@ -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,
|
// 1. when the callback is done with the m in non-pthread platforms,
|
||||||
// 2. or when the C thread exiting on 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
|
//go:nosplit
|
||||||
func needm() {
|
func needm(signal bool) {
|
||||||
if (iscgo || GOOS == "windows") && !cgoHasExtraM {
|
if (iscgo || GOOS == "windows") && !cgoHasExtraM {
|
||||||
// Can happen if C/C++ code calls Go from a global ctor.
|
// Can happen if C/C++ code calls Go from a global ctor.
|
||||||
// Can also happen on Windows if a global ctor uses a
|
// Can also happen on Windows if a global ctor uses a
|
||||||
@ -1939,14 +1942,23 @@ func needm() {
|
|||||||
osSetupTLS(mp)
|
osSetupTLS(mp)
|
||||||
|
|
||||||
// Install g (= m->g0) and set the stack bounds
|
// 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
|
// 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,
|
// scheduling stack is, but we assume there's at least 32 kB.
|
||||||
// which is more than enough for us.
|
// If we can get a more accurate stack bound from pthread,
|
||||||
|
// use that.
|
||||||
setg(mp.g0)
|
setg(mp.g0)
|
||||||
gp := getg()
|
gp := getg()
|
||||||
gp.stack.hi = getcallersp() + 1024
|
gp.stack.hi = getcallersp() + 1024
|
||||||
gp.stack.lo = getcallersp() - 32*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
|
gp.stackguard0 = gp.stack.lo + _StackGuard
|
||||||
|
|
||||||
// Should mark we are already in Go now.
|
// Should mark we are already in Go now.
|
||||||
@ -1967,7 +1979,7 @@ func needm() {
|
|||||||
//
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func needAndBindM() {
|
func needAndBindM() {
|
||||||
needm()
|
needm(false)
|
||||||
|
|
||||||
if _cgo_pthread_key_created != nil && *(*uintptr)(_cgo_pthread_key_created) != 0 {
|
if _cgo_pthread_key_created != nil && *(*uintptr)(_cgo_pthread_key_created) != 0 {
|
||||||
cgoBindM()
|
cgoBindM()
|
||||||
|
@ -585,7 +585,7 @@ func adjustSignalStack(sig uint32, mp *m, gsigStack *gsignalStack) bool {
|
|||||||
|
|
||||||
// sp is not within gsignal stack, g0 stack, or sigaltstack. Bad.
|
// sp is not within gsignal stack, g0 stack, or sigaltstack. Bad.
|
||||||
setg(nil)
|
setg(nil)
|
||||||
needm()
|
needm(true)
|
||||||
if st.ss_flags&_SS_DISABLE != 0 {
|
if st.ss_flags&_SS_DISABLE != 0 {
|
||||||
noSignalStack(sig)
|
noSignalStack(sig)
|
||||||
} else {
|
} else {
|
||||||
@ -1068,7 +1068,7 @@ func badsignal(sig uintptr, c *sigctxt) {
|
|||||||
exit(2)
|
exit(2)
|
||||||
*(*uintptr)(unsafe.Pointer(uintptr(123))) = 2
|
*(*uintptr)(unsafe.Pointer(uintptr(123))) = 2
|
||||||
}
|
}
|
||||||
needm()
|
needm(true)
|
||||||
if !sigsend(uint32(sig)) {
|
if !sigsend(uint32(sig)) {
|
||||||
// A foreign thread received the signal sig, and the
|
// A foreign thread received the signal sig, and the
|
||||||
// Go code does not want to handle it.
|
// Go code does not want to handle it.
|
||||||
|
Loading…
Reference in New Issue
Block a user