Threads
Threads provide both a security boundary—isolation from effects—and parallelism.
All programs start with a single thread; the "main thread" which is given initial capabilities by the system, like a GUI window, access to a console for logging etc. The main thread may then spawn any number of additional threads, which can be limited to just some capabilities (handles and rights)
Simple example of spawning a second thread:
void bg_thread(u64 arg) {
log("Hello from bg_thread");
}
void main() {
PBThread thread = PBOptOk(PBThreadStart(bg_thread, 0, NULL));
log("thread #%d started", thread.handle);
}
PBThread
typedef struct {
PBSysHandle handle;
} PBThread;
PBThreadOpt
typedef PBOpt(PBThread) PBThreadOpt;
PBThreadConfig
typedef struct PBThreadConfig {
PBThread parentThread; // defaults to PBThread_SELF if zero
u64 flags;
/*
rights for the thread itself.
Usually you want PBSysRight_SAME_RIGHTS. 0 means "no rights."
*/
PBSysRights rights;
void* nullable stack;
u32 stackSize;
u32 transferHandlesCount;
const PBSysHandle* nullable transferHandles;
} PBThreadConfig;
PBThreadConfig describes the configuration of a new thread
PBThreadFn
typedef void (*PBThreadFn)(u64);
PBThreadFn is the thread entry point function
PBProcessStart
void PBProcessStart(void* mainFn,
u64 flags);
PBProcessStart starts the main function. flags are bits of PBSysThread_
PBThreadStart
PBThreadOpt PBThreadStart(PBThreadFn arg,
u64 arg,
const PBThreadConfig* config);
PBThreadStart starts a new thread
PBThreadExit
PBSysErr PBThreadExit(PBThread thread,
int status);
PBExit terminates the thread. If this is called on the main thread, the process is terminated as an effect.
PBExit
PBSysErr PBExit(int status);
PBExit terminates the calling thread. If this is called on the main thread, the process is terminated as an effect.
PBExitProcess
PBSysErr PBExitProcess(int status);
PBExitProcess terminates the current process.
Fails with PBSysErr_ACCESS_DENIED if the calling thread's PBSysHandle_SELF_THREAD does not have PBSysRight_MANAGE_PROCESS.
PBCallCtors
PBCallCtors invokes wasm constructor list for builds without libc startup.
PB_C_START_ATTR
PB_C_START_ATTR defines attributes for the _start function, the program's entry point.
This is only needed when no libc (crt, really) is used.
PB_START_WITHOUT_GUI
PB_START_WITHOUT_GUI defines a _start function which sets PBSysThread_NOGUI
PBSetupInitialHandles
void PBSetupInitialHandles();
PBSetupInitialHandles sets up internal knowledge of what handles a thread is given at start
PBMutex
typedef struct PBMutex {
_Atomic(u32) state;
} PBMutex;
PBMutex is a reader/writer mutual exclusion lock.
The lock can be held by up to 2^31-1 readers or by a single writer. The high bit is used as a "writer pending/active" flag, so the zero-initialized value for a PBMutex is a valid, unlocked mutex.
PBMutex is not re-entrant. In particular, a thread must not try to take a read lock recursively while a writer is waiting, since the pending writer bit blocks new readers until the writer runs.
Memory model: The Nth call to PBMutexUnlock "happens before" the Mth call to PBMutexLock for any N < M. For any call to PBMutexRLock, there exists an N such that the Nth call to PBMutexUnlock "happens before" that call to PBMutexRLock, and the corresponding call to PBMutexRUnlock "happens before" the N+1th call to PBMutexUnlock.
In other words: There's no need to use atomic instructions for shared data guarded by a PBMutex.
PBMutexInit
void PBMutexInit(PBMutex* mu);
PBMutexInit initializes mu to an unlocked state with zero readers and no writer.
PBMutexRLock
void PBMutexRLock(PBMutex* mu);
PBMutexRLock acquires a shared read lock.
PBMutexRUnlock
void PBMutexRUnlock(PBMutex* mu);
PBMutexRUnlock releases a shared read lock.
PBMutexLock
void PBMutexLock(PBMutex* mu);
PBMutexLock acquires an exclusive write lock.
PBMutexUnlock
void PBMutexUnlock(PBMutex* mu);
PBMutexUnlock releases an exclusive write lock.
PBMutexIsLocked
bool PBMutexIsLocked(const PBMutex* mu);
PBMutexIsLocked returns true if the mutex is write locked at the time of the call
PBMutexIsRLocked
bool PBMutexIsRLocked(const PBMutex* mu);
PBMutexIsLocked returns true if the mutex is read locked at the time of the call
PBMutexLockInScope
macro void PBMutexLockInScope(PBMutex* mu)
PBMutexLockInScope locks a mutex for the duration of the current scope.
I.e.
void example(PBMutex* mu1, PBMutex* mu2) {
PBMutexLockInScope(mu1);
// mu1 locked until function returns
{
PBMutexLockInScope(mu2);
// mu2 locked until this scope ends
} // mu2 unlocked
} // mu1 unlocked
PBMutexRLockInScope
macro void PBMutexRLockInScope(PBMutex* mu)
#include <playbit/semaphore.h>
PBSemaphore
typedef struct PBSemaphore {
_Atomic(u32) state;
} PBSemaphore;
PBSemaphore is a counting semaphore.
The zero-initialized value for a PBSemaphore is valid and has count 0.
PBSemaphoreWait blocks until the count is nonzero, then consumes one permit. PBSemaphoreSignal adds count permits and releases up to count waiters.
Example:
static PBSemaphore workReady = { 0 };
void worker(void) {
PBSemaphoreWait(&workReady);
process_work();
}
void queue_work(void) {
add_work();
PBSemaphoreSignal(&workReady, 1);
}
PBSemaphoreInit
void PBSemaphoreInit(PBSemaphore* sem,
u32 initCount);
PBSemaphoreInit initializes sem with initCount permits.
Note: While U32_MAX is allowed for initCount, any later signal would overflow (and panic.)
sem: Semaphore to initializeinitCount: Initial permit count
PBSemaphoreWait
void PBSemaphoreWait(PBSemaphore* sem);
PBSemaphoreWait blocks until one permit is available, then consumes it.
The successful wait has acquire semantics and synchronizes with a prior PBSemaphoreSignal that made the consumed permit available.
sem: Semaphore to wait on
PBSemaphoreSignal
void PBSemaphoreSignal(PBSemaphore* sem,
u32 count);
PBSemaphoreSignal adds count permits and releases up to count waiters.
The signal has release semantics. Passing count 0 is a no-op.
This function fails fast if adding count would overflow the 32-bit permit count.
sem: Semaphore to signalcount: Number of permits to add; maximum number of waiting threads to unblock.
PBSemaphoreValue
u32 PBSemaphoreValue(const PBSemaphore* sem);
PBSemaphoreValue returns a best-effort count snapshot for diagnostics and tests.
The returned value is not a synchronization primitive and may be stale immediately.
sem: Semaphore to inspect- Returns: Observed permit count at the time of the atomic load.