Skip to content

Commit 6e1fbef

Browse files
committed
runtime: implement race-free signals using futexes
1 parent ca23845 commit 6e1fbef

File tree

9 files changed

+186
-177
lines changed

9 files changed

+186
-177
lines changed

GNUmakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,7 @@ endif
934934
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
935935
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
936936
@cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src
937+
@cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src
937938
@cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src
938939
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
939940
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src

builder/musl.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ var libMusl = Library{
127127
"malloc/mallocng/*.c",
128128
"mman/*.c",
129129
"math/*.c",
130+
"misc/*.c",
130131
"multibyte/*.c",
131132
"signal/" + arch + "/*.s",
132133
"signal/*.c",

compileopts/target.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
413413
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
414414
}
415415
spec.ExtraFiles = append(spec.ExtraFiles,
416+
"src/internal/futex/futex_linux.c",
416417
"src/runtime/runtime_unix.c",
417418
"src/runtime/signal.c")
418419
case "windows":

loader/goroot.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
243243
"internal/binary/": false,
244244
"internal/bytealg/": false,
245245
"internal/cm/": false,
246+
"internal/futex/": false,
246247
"internal/fuzz/": false,
247248
"internal/reflectlite/": false,
248249
"internal/gclayout": false,

src/internal/futex/futex.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package futex
2+
3+
import "sync/atomic"
4+
5+
// A futex is a way for userspace to wait with the pointer as the key, and for
6+
// another thread to wake one or all waiting threads keyed on the same pointer.
7+
//
8+
// A futex does not change the underlying value, it only reads it before going
9+
// to sleep (atomically) to prevent lost wake-ups.
10+
type Futex struct {
11+
atomic.Uint32
12+
}

src/internal/futex/futex_linux.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//go:build none
2+
3+
// This file is manually included, to avoid CGo which would cause a circular
4+
// import.
5+
6+
#include <stdint.h>
7+
#include <sys/syscall.h>
8+
#include <time.h>
9+
#include <unistd.h>
10+
11+
#define FUTEX_WAIT 0
12+
#define FUTEX_WAKE 1
13+
#define FUTEX_PRIVATE_FLAG 128
14+
15+
void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) {
16+
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, NULL, NULL, 0);
17+
}
18+
19+
void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) {
20+
struct timespec ts = {0};
21+
ts.tv_sec = timeout / 1000000000;
22+
ts.tv_nsec = timeout % 1000000000;
23+
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, &ts, NULL, 0);
24+
}
25+
26+
void tinygo_futex_wake(uint32_t *addr, uint32_t num) {
27+
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, num, NULL, NULL, 0);
28+
}

src/internal/futex/futex_linux.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package futex
2+
3+
import (
4+
"unsafe"
5+
)
6+
7+
// Atomically check for cmp to still be equal to the futex value and if so, go
8+
// to sleep. Return true if we were definitely awoken by a call to Wake or
9+
// WakeAll, and false if we can't be sure of that.
10+
func (f *Futex) Wait(cmp uint32) bool {
11+
tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp)
12+
13+
// We *could* detect a zero return value from the futex system call which
14+
// would indicate we got awoken by a Wake or WakeAll call. However, this is
15+
// what the manual page has to say:
16+
//
17+
// > Note that a wake-up can also be caused by common futex usage patterns
18+
// > in unrelated code that happened to have previously used the futex
19+
// > word's memory location (e.g., typical futex-based implementations of
20+
// > Pthreads mutexes can cause this under some conditions). Therefore,
21+
// > callers should always conservatively assume that a return value of 0
22+
// > can mean a spurious wake-up, and use the futex word's value (i.e., the
23+
// > user-space synchronization scheme) to decide whether to continue to
24+
// > block or not.
25+
//
26+
// I'm not sure whether we do anything like pthread does, so to be on the
27+
// safe side we say we don't know whether the wakeup was spurious or not and
28+
// return false.
29+
return false
30+
}
31+
32+
// Like Wait, but times out after the number of nanoseconds in timeout.
33+
func (f *Futex) WaitUntil(cmp uint32, timeout uint64) {
34+
tinygo_futex_wait_timeout((*uint32)(unsafe.Pointer(&f.Uint32)), cmp, timeout)
35+
}
36+
37+
// Wake a single waiter.
38+
func (f *Futex) Wake() {
39+
tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32)), 1)
40+
}
41+
42+
// Wake all waiters.
43+
func (f *Futex) WakeAll() {
44+
const maxInt32 = 0x7fff_ffff
45+
tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32)), maxInt32)
46+
}
47+
48+
//export tinygo_futex_wait
49+
func tinygo_futex_wait(addr *uint32, cmp uint32)
50+
51+
//export tinygo_futex_wait_timeout
52+
func tinygo_futex_wait_timeout(addr *uint32, cmp uint32, timeout uint64)
53+
54+
//export tinygo_futex_wake
55+
func tinygo_futex_wake(addr *uint32, num uint32)

0 commit comments

Comments
 (0)