Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,7 @@ endif
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
Expand Down
1 change: 1 addition & 0 deletions builder/musl.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ var libMusl = Library{
"malloc/mallocng/*.c",
"mman/*.c",
"math/*.c",
"misc/*.c",
"multibyte/*.c",
"signal/" + arch + "/*.s",
"signal/*.c",
Expand Down
1 change: 1 addition & 0 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ func (c *Config) CFlags(libclang bool) []string {
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
"-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"),
"-isystem", filepath.Join(root, "lib", "musl", "include"),
)
case "wasi-libc":
Expand Down
2 changes: 2 additions & 0 deletions compileopts/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"-platform_version", "macos", platformVersion, platformVersion,
)
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_darwin.c",
"src/runtime/os_darwin.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
Expand All @@ -413,6 +414,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
}
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_linux.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "windows":
Expand Down
2 changes: 1 addition & 1 deletion lib/macos-minimal-sdk
1 change: 1 addition & 0 deletions loader/goroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"internal/binary/": false,
"internal/bytealg/": false,
"internal/cm/": false,
"internal/futex/": false,
"internal/fuzz/": false,
"internal/reflectlite/": false,
"internal/gclayout": false,
Expand Down
72 changes: 72 additions & 0 deletions src/internal/futex/futex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package futex

// Cross platform futex implementation.
// Futexes are supported on all major operating systems and on WebAssembly.
//
// For more information, see: https://outerproduct.net/futex-dictionary.html

import (
"sync/atomic"
"unsafe"
)

// A futex is a way for userspace to wait with the pointer as the key, and for
// another thread to wake one or all waiting threads keyed on the same pointer.
//
// A futex does not change the underlying value, it only reads it before going
// to sleep (atomically) to prevent lost wake-ups.
type Futex struct {
atomic.Uint32
}

// Atomically check for cmp to still be equal to the futex value and if so, go
// to sleep. Return true if we were definitely awoken by a call to Wake or
// WakeAll, and false if we can't be sure of that.
func (f *Futex) Wait(cmp uint32) bool {
tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably ok to assume the layout of atomic.Uint32 here and below, but it seems to me you can avoid it by

type Futex struct {
    v uint32
}

and adding methods that forwards to atomic.(Load|And|Store...)Uint32 functions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to keep using atomic.Uint32. I don't expect atomic.Uint32 to ever change to something else, and if it does we can always update this code.


// We *could* detect a zero return value from the futex system call which
// would indicate we got awoken by a Wake or WakeAll call. However, this is
// what the manual page has to say:
//
// > Note that a wake-up can also be caused by common futex usage patterns
// > in unrelated code that happened to have previously used the futex
// > word's memory location (e.g., typical futex-based implementations of
// > Pthreads mutexes can cause this under some conditions). Therefore,
// > callers should always conservatively assume that a return value of 0
// > can mean a spurious wake-up, and use the futex word's value (i.e., the
// > user-space synchronization scheme) to decide whether to continue to
// > block or not.
//
// I'm not sure whether we do anything like pthread does, so to be on the
// safe side we say we don't know whether the wakeup was spurious or not and
// return false.
return false
}

// Like Wait, but times out after the number of nanoseconds in timeout.
func (f *Futex) WaitUntil(cmp uint32, timeout uint64) {
tinygo_futex_wait_timeout((*uint32)(unsafe.Pointer(&f.Uint32)), cmp, timeout)
}

// Wake a single waiter.
func (f *Futex) Wake() {
tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32)))
}

// Wake all waiters.
func (f *Futex) WakeAll() {
tinygo_futex_wake_all((*uint32)(unsafe.Pointer(&f.Uint32)))
}

//export tinygo_futex_wait
func tinygo_futex_wait(addr *uint32, cmp uint32)

//export tinygo_futex_wait_timeout
func tinygo_futex_wait_timeout(addr *uint32, cmp uint32, timeout uint64)

//export tinygo_futex_wake
func tinygo_futex_wake(addr *uint32)

//export tinygo_futex_wake_all
func tinygo_futex_wake_all(addr *uint32)
49 changes: 49 additions & 0 deletions src/internal/futex/futex_darwin.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//go:build none

// This file is manually included, to avoid CGo which would cause a circular
// import.

#include <stdint.h>

// This API isn't documented by Apple, but it is used by LLVM libc++ (so should
// be stable) and has been documented extensively here:
// https://outerproduct.net/futex-dictionary.html

int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_us);
int __ulock_wait2(uint32_t operation, void *addr, uint64_t value, uint64_t timeout_ns, uint64_t value2);
int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value);
Comment on lines +12 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to documentation, if it exists somewhere (semi-)stable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. There's no real documentation, but someone has figured out these calls: https://outerproduct.net/futex-dictionary.html


// Operation code.
#define UL_COMPARE_AND_WAIT 1

// Flags to the operation value.
#define ULF_WAKE_ALL 0x00000100
#define ULF_NO_ERRNO 0x01000000

void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) {
__ulock_wait(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, 0);
}

void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) {
// Make sure that an accidental use of a zero timeout is not treated as an
// infinite timeout. Return if it's zero since it wouldn't be waiting for
// any significant time anyway.
// Probably unnecessary, but guards against potential bugs.
if (timeout == 0) {
return;
}

// Note: __ulock_wait2 is available since MacOS 11.
// I think that's fine, since the version before that (MacOS 10.15) is EOL
// since 2022. Though if needed, we could certainly use __ulock_wait instead
// and deal with the smaller timeout value.
Comment on lines +36 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would put (most of) this note in the commit message. Go 1.23 also requires macOS 11 and it doesn't seem reasonable to do better in TinyGo.

__ulock_wait2(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, timeout, 0);
}

void tinygo_futex_wake(uint32_t *addr) {
__ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, 0);
}

void tinygo_futex_wake_all(uint32_t *addr) {
__ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO|ULF_WAKE_ALL, addr, 0);
}
33 changes: 33 additions & 0 deletions src/internal/futex/futex_linux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build none

// This file is manually included, to avoid CGo which would cause a circular
// import.

#include <limits.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>

#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_PRIVATE_FLAG 128
Comment on lines +12 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not #include <linux/futex.h>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this header file is not available in musl, which we use.


void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) {
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, NULL, NULL, 0);
}

void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) {
struct timespec ts = {0};
ts.tv_sec = timeout / 1000000000;
ts.tv_nsec = timeout % 1000000000;
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, &ts, NULL, 0);
}

void tinygo_futex_wake(uint32_t *addr) {
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0);
}

void tinygo_futex_wake_all(uint32_t *addr) {
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, INT_MAX, NULL, NULL, 0);
}
Loading
Loading