detached a32290b

fakecgo: C functions require nosplit (#295)

* fakecgo: C functions require nosplit

fakecgo runs on systemstack so we shouldn't ever split the stack.

Fixes #287
commit
a32290b73c0334609816e2b780b0afa13bcb0345
author
TotallyGamerJet · · 2025-01-06 14:47 UTC
committer
GitHub · · 2025-01-06 14:47 UTC
parent
ca651c3
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
5 files · +33 -5
M .github/workflows/test.yml +1 -1
@@ -96,7 +96,7 @@ jobs:
9696 # Compile without optimization to check potential stack overflow.
9797 # The option '-gcflags=all=-N -l' is often used at Visual Studio Code.
9898 # See also https://go.googlesource.com/vscode-go/+/HEAD/docs/debugging.md#launch.
99- # TODO: CGO_ENABLED=0 might cause SEGV. Fix this issue.
99+ env CGO_ENABLED=0 go test "-gcflags=all=-N -l" -v ./...
100100 env CGO_ENABLED=1 go test "-gcflags=all=-N -l" -v ./...
101101
102102 - name: go test (Linux arm64)
@@ -96,7 +96,7 @@ jobs:
96 # Compile without optimization to check potential stack overflow.96 # Compile without optimization to check potential stack overflow.
97 # The option '-gcflags=all=-N -l' is often used at Visual Studio Code.97 # The option '-gcflags=all=-N -l' is often used at Visual Studio Code.
98 # See also https://go.googlesource.com/vscode-go/+/HEAD/docs/debugging.md#launch.98 # See also https://go.googlesource.com/vscode-go/+/HEAD/docs/debugging.md#launch.
99- # TODO: CGO_ENABLED=0 might cause SEGV. Fix this issue.99+ env CGO_ENABLED=0 go test "-gcflags=all=-N -l" -v ./...
100 env CGO_ENABLED=1 go test "-gcflags=all=-N -l" -v ./...100 env CGO_ENABLED=1 go test "-gcflags=all=-N -l" -v ./...
101 101
102 - name: go test (Linux arm64)102 - name: go test (Linux arm64)
M internal/fakecgo/gen.go +1 -0
@@ -36,6 +36,7 @@ func setg_trampoline(setg uintptr, G uintptr)
3636 func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr
3737
3838 {{ range . -}}
39+//go:nosplit
3940 func {{.Name}}(
4041 {{- range .Args -}}
4142 {{- if .Name -}}
@@ -36,6 +36,7 @@ func setg_trampoline(setg uintptr, G uintptr)
36 func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr36 func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr
37 37
38 {{ range . -}}38 {{ range . -}}
39+//go:nosplit
39 func {{.Name}}(40 func {{.Name}}(
40 {{- range .Args -}}41 {{- range .Args -}}
41 {{- if .Name -}}42 {{- if .Name -}}
M internal/fakecgo/go_libinit.go +6 -3
@@ -50,9 +50,11 @@ func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe
5050 var err int
5151
5252 for tries = 0; tries < 20; tries++ {
53- err = int(pthread_create(thread, attr, pfn, unsafe.Pointer(arg)))
53+ // inlined this call because it ran out of stack when inlining was disabled
54+ err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0))
5455 if err == 0 {
55- pthread_detach(*thread)
56+ // inlined this call because it ran out of stack when inlining was disabled
57+ call5(pthread_detachABI0, uintptr(*thread), 0, 0, 0, 0)
5658 return 0
5759 }
5860 if err != int(syscall.EAGAIN) {
@@ -60,7 +62,8 @@ func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe
6062 }
6163 ts.Sec = 0
6264 ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds.
63- nanosleep(&ts, nil)
65+ // inlined this call because it ran out of stack when inlining was disabled
66+ call5(nanosleepABI0, uintptr(unsafe.Pointer(&ts)), 0, 0, 0, 0)
6467 }
6568 return int(syscall.EAGAIN)
6669 }
@@ -50,9 +50,11 @@ func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe
50 var err int50 var err int
51 51
52 for tries = 0; tries < 20; tries++ {52 for tries = 0; tries < 20; tries++ {
53- err = int(pthread_create(thread, attr, pfn, unsafe.Pointer(arg)))53+ // inlined this call because it ran out of stack when inlining was disabled
54+ err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0))
54 if err == 0 {55 if err == 0 {
55- pthread_detach(*thread)56+ // inlined this call because it ran out of stack when inlining was disabled
57+ call5(pthread_detachABI0, uintptr(*thread), 0, 0, 0, 0)
56 return 058 return 0
57 }59 }
58 if err != int(syscall.EAGAIN) {60 if err != int(syscall.EAGAIN) {
@@ -60,7 +62,8 @@ func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe
60 }62 }
61 ts.Sec = 063 ts.Sec = 0
62 ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds.64 ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds.
63- nanosleep(&ts, nil)65+ // inlined this call because it ran out of stack when inlining was disabled
66+ call5(nanosleepABI0, uintptr(unsafe.Pointer(&ts)), 0, 0, 0, 0)
64 }67 }
65 return int(syscall.EAGAIN)68 return int(syscall.EAGAIN)
66 }69 }
M internal/fakecgo/libcgo.go +5 -1
@@ -6,7 +6,11 @@
66 package fakecgo
77
88 type (
9- size_t uintptr
9+ size_t uintptr
10+ // Sources:
11+ // Darwin (32 bytes) - https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/_types.h#L74
12+ // FreeBSD (32 bytes) - https://github.com/DoctorWkt/xv6-freebsd/blob/d2a294c2a984baed27676068b15ed9a29b06ab6f/include/signal.h#L98C9-L98C21
13+ // Linux (128 bytes) - https://github.com/torvalds/linux/blob/ab75170520d4964f3acf8bb1f91d34cbc650688e/arch/x86/include/asm/signal.h#L25
1014 sigset_t [128]byte
1115 pthread_attr_t [64]byte
1216 pthread_t int
@@ -6,7 +6,11 @@
6 package fakecgo6 package fakecgo
7 7
8 type (8 type (
9- size_t uintptr9+ size_t uintptr
10+ // Sources:
11+ // Darwin (32 bytes) - https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/_types.h#L74
12+ // FreeBSD (32 bytes) - https://github.com/DoctorWkt/xv6-freebsd/blob/d2a294c2a984baed27676068b15ed9a29b06ab6f/include/signal.h#L98C9-L98C21
13+ // Linux (128 bytes) - https://github.com/torvalds/linux/blob/ab75170520d4964f3acf8bb1f91d34cbc650688e/arch/x86/include/asm/signal.h#L25
10 sigset_t [128]byte14 sigset_t [128]byte
11 pthread_attr_t [64]byte15 pthread_attr_t [64]byte
12 pthread_t int16 pthread_t int
M internal/fakecgo/symbols.go +20 -0
@@ -18,84 +18,104 @@ func setg_trampoline(setg uintptr, G uintptr)
1818 // call5 takes fn the C function and 5 arguments and calls the function with those arguments
1919 func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr
2020
21+//go:nosplit
2122 func malloc(size uintptr) unsafe.Pointer {
2223 ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0)
2324 // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer
2425 return *(*unsafe.Pointer)(unsafe.Pointer(&ret))
2526 }
2627
28+//go:nosplit
2729 func free(ptr unsafe.Pointer) {
2830 call5(freeABI0, uintptr(ptr), 0, 0, 0, 0)
2931 }
3032
33+//go:nosplit
3134 func setenv(name *byte, value *byte, overwrite int32) int32 {
3235 return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0))
3336 }
3437
38+//go:nosplit
3539 func unsetenv(name *byte) int32 {
3640 return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0))
3741 }
3842
43+//go:nosplit
3944 func sigfillset(set *sigset_t) int32 {
4045 return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0))
4146 }
4247
48+//go:nosplit
4349 func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 {
4450 return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0))
4551 }
4652
53+//go:nosplit
4754 func abort() {
4855 call5(abortABI0, 0, 0, 0, 0, 0)
4956 }
5057
58+//go:nosplit
5159 func pthread_attr_init(attr *pthread_attr_t) int32 {
5260 return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))
5361 }
5462
63+//go:nosplit
5564 func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 {
5665 return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0))
5766 }
5867
68+//go:nosplit
5969 func pthread_detach(thread pthread_t) int32 {
6070 return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0))
6171 }
6272
73+//go:nosplit
6374 func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 {
6475 return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0))
6576 }
6677
78+//go:nosplit
6779 func pthread_self() pthread_t {
6880 return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0))
6981 }
7082
83+//go:nosplit
7184 func pthread_get_stacksize_np(thread pthread_t) size_t {
7285 return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0))
7386 }
7487
88+//go:nosplit
7589 func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 {
7690 return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0))
7791 }
7892
93+//go:nosplit
7994 func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 {
8095 return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0))
8196 }
8297
98+//go:nosplit
8399 func pthread_attr_destroy(attr *pthread_attr_t) int32 {
84100 return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))
85101 }
86102
103+//go:nosplit
87104 func pthread_mutex_lock(mutex *pthread_mutex_t) int32 {
88105 return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0))
89106 }
90107
108+//go:nosplit
91109 func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 {
92110 return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0))
93111 }
94112
113+//go:nosplit
95114 func pthread_cond_broadcast(cond *pthread_cond_t) int32 {
96115 return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0))
97116 }
98117
118+//go:nosplit
99119 func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 {
100120 return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0))
101121 }
@@ -18,84 +18,104 @@ func setg_trampoline(setg uintptr, G uintptr)
18 // call5 takes fn the C function and 5 arguments and calls the function with those arguments18 // call5 takes fn the C function and 5 arguments and calls the function with those arguments
19 func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr19 func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr
20 20
21+//go:nosplit
21 func malloc(size uintptr) unsafe.Pointer {22 func malloc(size uintptr) unsafe.Pointer {
22 ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0)23 ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0)
23 // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer24 // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer
24 return *(*unsafe.Pointer)(unsafe.Pointer(&ret))25 return *(*unsafe.Pointer)(unsafe.Pointer(&ret))
25 }26 }
26 27
28+//go:nosplit
27 func free(ptr unsafe.Pointer) {29 func free(ptr unsafe.Pointer) {
28 call5(freeABI0, uintptr(ptr), 0, 0, 0, 0)30 call5(freeABI0, uintptr(ptr), 0, 0, 0, 0)
29 }31 }
30 32
33+//go:nosplit
31 func setenv(name *byte, value *byte, overwrite int32) int32 {34 func setenv(name *byte, value *byte, overwrite int32) int32 {
32 return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0))35 return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0))
33 }36 }
34 37
38+//go:nosplit
35 func unsetenv(name *byte) int32 {39 func unsetenv(name *byte) int32 {
36 return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0))40 return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0))
37 }41 }
38 42
43+//go:nosplit
39 func sigfillset(set *sigset_t) int32 {44 func sigfillset(set *sigset_t) int32 {
40 return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0))45 return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0))
41 }46 }
42 47
48+//go:nosplit
43 func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 {49 func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 {
44 return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0))50 return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0))
45 }51 }
46 52
53+//go:nosplit
47 func abort() {54 func abort() {
48 call5(abortABI0, 0, 0, 0, 0, 0)55 call5(abortABI0, 0, 0, 0, 0, 0)
49 }56 }
50 57
58+//go:nosplit
51 func pthread_attr_init(attr *pthread_attr_t) int32 {59 func pthread_attr_init(attr *pthread_attr_t) int32 {
52 return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))60 return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))
53 }61 }
54 62
63+//go:nosplit
55 func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 {64 func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 {
56 return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0))65 return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0))
57 }66 }
58 67
68+//go:nosplit
59 func pthread_detach(thread pthread_t) int32 {69 func pthread_detach(thread pthread_t) int32 {
60 return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0))70 return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0))
61 }71 }
62 72
73+//go:nosplit
63 func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 {74 func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 {
64 return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0))75 return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0))
65 }76 }
66 77
78+//go:nosplit
67 func pthread_self() pthread_t {79 func pthread_self() pthread_t {
68 return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0))80 return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0))
69 }81 }
70 82
83+//go:nosplit
71 func pthread_get_stacksize_np(thread pthread_t) size_t {84 func pthread_get_stacksize_np(thread pthread_t) size_t {
72 return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0))85 return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0))
73 }86 }
74 87
88+//go:nosplit
75 func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 {89 func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 {
76 return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0))90 return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0))
77 }91 }
78 92
93+//go:nosplit
79 func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 {94 func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 {
80 return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0))95 return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0))
81 }96 }
82 97
98+//go:nosplit
83 func pthread_attr_destroy(attr *pthread_attr_t) int32 {99 func pthread_attr_destroy(attr *pthread_attr_t) int32 {
84 return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))100 return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))
85 }101 }
86 102
103+//go:nosplit
87 func pthread_mutex_lock(mutex *pthread_mutex_t) int32 {104 func pthread_mutex_lock(mutex *pthread_mutex_t) int32 {
88 return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0))105 return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0))
89 }106 }
90 107
108+//go:nosplit
91 func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 {109 func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 {
92 return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0))110 return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0))
93 }111 }
94 112
113+//go:nosplit
95 func pthread_cond_broadcast(cond *pthread_cond_t) int32 {114 func pthread_cond_broadcast(cond *pthread_cond_t) int32 {
96 return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0))115 return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0))
97 }116 }
98 117
118+//go:nosplit
99 func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 {119 func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 {
100 return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0))120 return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0))
101 }121 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close