Skip to content

Commit

Permalink
internal/task: implement atomic primitives for preemptive scheduling
Browse files Browse the repository at this point in the history
  • Loading branch information
aykevl committed Nov 1, 2024
1 parent 2c036dc commit a8060d4
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 0 deletions.
1 change: 1 addition & 0 deletions compileopts/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
}
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/task/futex_linux.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "windows":
Expand Down
2 changes: 2 additions & 0 deletions src/internal/task/atomic-cooperative.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !scheduler.threads

package task

// Atomics implementation for cooperative systems. The atomic types here aren't
Expand Down
14 changes: 14 additions & 0 deletions src/internal/task/atomic-preemptive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build scheduler.threads

package task

// Atomics implementation for non-cooperative systems (multithreaded, etc).
// These atomic types use real atomic instructions.

import "sync/atomic"

type (
Uintptr = atomic.Uintptr
Uint32 = atomic.Uint32
Uint64 = atomic.Uint64
)
2 changes: 2 additions & 0 deletions src/internal/task/futex-cooperative.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !scheduler.threads

package task

// A futex is a way for userspace to wait with the pointer as the key, and for
Expand Down
12 changes: 12 additions & 0 deletions src/internal/task/futex-preemptive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build scheduler.threads

package task

// 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 to prevent
// lost wake-ups.
type Futex struct {
Uint32
}
20 changes: 20 additions & 0 deletions src/internal/task/futex_linux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build none

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

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

#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_PRIVATE 128

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

void tinygo_futex_wake(uint32_t *addr, uint32_t num) {
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE, num, NULL, NULL, 0);
}
49 changes: 49 additions & 0 deletions src/internal/task/futex_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//go:build scheduler.threads

package task

import (
"unsafe"
)

// 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)

// 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
}

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

// Wake all waiters.
func (f *Futex) WakeAll() {
const maxInt32 = 0x7fff_ffff
tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32)), maxInt32)
}

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

//export tinygo_futex_wake
func tinygo_futex_wake(addr *uint32, num uint32)
2 changes: 2 additions & 0 deletions src/internal/task/pmutex-cooperative.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !scheduler.threads

package task

// PMutex is a real mutex on systems that can be either preemptive or threaded,
Expand Down
37 changes: 37 additions & 0 deletions src/internal/task/pmutex-preemptive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build scheduler.threads

package task

// PMutex is a real mutex on systems that can be either preemptive or threaded,
// and a dummy lock on other (purely cooperative) systems.
//
// It is mainly useful for short operations that need a lock when threading may
// be involved, but which do not need a lock with a purely cooperative
// scheduler.
type PMutex struct {
futex Futex
}

func (m *PMutex) Lock() {
// Fast path: try to take an uncontended lock.
if m.futex.CompareAndSwap(0, 1) {
// We obtained the mutex.
return
}

// Try to lock the mutex. If it changed from 0 to 2, we took a contended
// lock.
for m.futex.Swap(2) != 0 {
// Wait until we get resumed in Unlock.
m.futex.Wait(2)
}
}

func (m *PMutex) Unlock() {
if old := m.futex.Swap(0); old == 2 {
// Mutex was a contended lock, so we need to wake the next waiter.
m.futex.Wake()
}
// Note: this implementation doesn't check for an unlock of an unlocked
// mutex to keep it as lightweight as possible.
}

0 comments on commit a8060d4

Please sign in to comment.