blob: e3c1d2894839d9560302ab4bf1aace36bcf11425 [file]
// Copyright 2026 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Cross-platform futex primitives for low-level synchronization.
//
// This provides a 32-bit futex API that works across Linux, Windows, and
// Emscripten. These are the building blocks for higher-level synchronization
// primitives like iree_slim_mutex_t and iree_notification_t.
//
// The API is 32-bit only because that's the lowest common denominator:
// - Linux futex syscall: 32-bit
// - Windows WaitOnAddress: variable size, but we use 32-bit
// - Emscripten: 32-bit (JavaScript Atomics.wait)
//
// For 64-bit futex support (Linux kernel 6.7+ futex2), see
// iree/async/operations/futex.h which provides async io_uring operations
// with size flags.
//
// Note: Futex operations are disabled under ThreadSanitizer because TSan
// doesn't instrument futex syscalls. When IREE_SANITIZER_THREAD is defined,
// IREE_PLATFORM_HAS_FUTEX will be defined but IREE_RUNTIME_USE_FUTEX will not,
// causing higher-level primitives to fall back to pthread-based
// implementations.
#ifndef IREE_BASE_THREADING_FUTEX_H_
#define IREE_BASE_THREADING_FUTEX_H_
#include <stdint.h>
#include "iree/base/attributes.h"
#include "iree/base/config.h"
#include "iree/base/status.h"
#include "iree/base/target_platform.h"
#include "iree/base/time.h"
//===----------------------------------------------------------------------===//
// Platform detection
//===----------------------------------------------------------------------===//
// Allow users to fully disable all synchronization for systems that are known
// to never need it. This removes our dependency on pthreads.
#if !IREE_SYNCHRONIZATION_DISABLE_UNSAFE
#if defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_EMSCRIPTEN) || \
defined(IREE_PLATFORM_LINUX) || defined(IREE_PLATFORM_WINDOWS)
#define IREE_PLATFORM_HAS_FUTEX 1
#endif // IREE_PLATFORM_*
#if defined(IREE_PLATFORM_HAS_FUTEX) && !defined(IREE_SANITIZER_THREAD)
// TODO: If we have TSan instrumentation for futexes we can enabled them when
// compiling with TSan.
#define IREE_RUNTIME_USE_FUTEX 1
#endif // IREE_PLATFORM_HAS_FUTEX
#endif // !IREE_SYNCHRONIZATION_DISABLE_UNSAFE
//===----------------------------------------------------------------------===//
// Platform headers
//===----------------------------------------------------------------------===//
#if defined(IREE_RUNTIME_USE_FUTEX)
#if defined(IREE_PLATFORM_EMSCRIPTEN)
#include <emscripten/threading.h>
#include <errno.h>
#elif defined(IREE_PLATFORM_WINDOWS)
// Windows headers included via iree/base/target_platform.h
#elif defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_LINUX)
#include <errno.h>
#include <linux/futex.h>
#include <sys/syscall.h>
#include <unistd.h>
#if defined(IREE_ARCH_RISCV_32) && defined(__NR_futex_time64) && \
!defined(__NR_futex)
// RV32 uses 64-bit times by default (unlike other 32-bit archs).
#define __NR_futex __NR_futex_time64
#endif // IREE_ARCH_RISCV_32
// Oh Android...
#ifndef SYS_futex
#define SYS_futex __NR_futex
#endif // !SYS_futex
#ifndef FUTEX_PRIVATE_FLAG
#define FUTEX_PRIVATE_FLAG 128
#endif // !FUTEX_PRIVATE_FLAG
#endif // IREE_PLATFORM_*
#endif // IREE_RUNTIME_USE_FUTEX
#ifdef __cplusplus
extern "C" {
#endif
//===----------------------------------------------------------------------===//
// Constants
//===----------------------------------------------------------------------===//
// Sentinel value to wake all waiters.
#define IREE_ALL_WAITERS INT32_MAX
// Infinite timeout value in milliseconds for internal use.
#define IREE_INFINITE_TIMEOUT_MS UINT32_MAX
//===----------------------------------------------------------------------===//
// Futex API
//===----------------------------------------------------------------------===//
#if defined(IREE_RUNTIME_USE_FUTEX)
// Waits in the OS for the value at the specified |address| to change.
// If the contents of |address| do not match |expected_value| the wait will
// fail and return IREE_STATUS_UNAVAILABLE and should be retried.
//
// |deadline_ns| can be either IREE_TIME_INFINITE_FUTURE to wait forever or an
// absolute time to wait until prior to returning early with
// IREE_STATUS_DEADLINE_EXCEEDED.
//
// Returns:
// IREE_STATUS_OK: Woken by another thread or value changed.
// IREE_STATUS_DEADLINE_EXCEEDED: Timeout reached before wake.
// IREE_STATUS_UNAVAILABLE: Value at address != expected_value (retry needed).
static inline iree_status_code_t iree_futex_wait(void* address,
uint32_t expected_value,
iree_time_t deadline_ns);
// Wakes at most |count| threads waiting for the |address| to change.
// Use IREE_ALL_WAITERS to wake all waiters. Which waiters are woken is
// undefined and it is not guaranteed that higher priority waiters will be woken
// over lower priority waiters.
static inline void iree_futex_wake(void* address, int32_t count);
//===----------------------------------------------------------------------===//
// Shared (cross-process) futex API
//===----------------------------------------------------------------------===//
// Cross-process variants that operate on physical page addresses instead of
// virtual addresses. On Linux, this omits FUTEX_PRIVATE_FLAG, causing the
// kernel to hash by physical page rather than {mm, virtual address}. This
// allows futex operations to work across processes sharing the same physical
// page (e.g., via mmap MAP_SHARED or shm_open).
//
// On Windows, WaitOnAddress/WakeByAddress already hash by physical page, so
// these are identical to the private variants.
//
// On Emscripten, cross-process shared memory is not meaningful, so these are
// identical to the private variants.
//
// Performance: ~20ns slower per operation on Linux due to the kernel page table
// walk. Use the private variants (iree_futex_wait/wake) when cross-process
// semantics are not needed.
static inline iree_status_code_t iree_futex_wait_shared(
void* address, uint32_t expected_value, iree_time_t deadline_ns);
static inline void iree_futex_wake_shared(void* address, int32_t count);
//===----------------------------------------------------------------------===//
// Platform implementations
//===----------------------------------------------------------------------===//
#if defined(IREE_PLATFORM_EMSCRIPTEN)
static inline iree_status_code_t iree_futex_wait(void* address,
uint32_t expected_value,
iree_time_t deadline_ns) {
uint32_t timeout_ms = iree_absolute_deadline_to_timeout_ms(deadline_ns);
int rc = emscripten_futex_wait(address, expected_value, (double)timeout_ms);
switch (rc) {
default:
return IREE_STATUS_OK;
case -ETIMEDOUT:
return IREE_STATUS_DEADLINE_EXCEEDED;
case -EWOULDBLOCK:
return IREE_STATUS_UNAVAILABLE;
}
}
static inline void iree_futex_wake(void* address, int32_t count) {
emscripten_futex_wake(address, count);
}
// Emscripten has no cross-process shared memory — alias to private variants.
static inline iree_status_code_t iree_futex_wait_shared(
void* address, uint32_t expected_value, iree_time_t deadline_ns) {
return iree_futex_wait(address, expected_value, deadline_ns);
}
static inline void iree_futex_wake_shared(void* address, int32_t count) {
iree_futex_wake(address, count);
}
#elif defined(IREE_PLATFORM_WINDOWS)
#pragma comment(lib, "Synchronization.lib")
static inline iree_status_code_t iree_futex_wait(void* address,
uint32_t expected_value,
iree_time_t deadline_ns) {
uint32_t timeout_ms = iree_absolute_deadline_to_timeout_ms(deadline_ns);
if (IREE_LIKELY(WaitOnAddress(address, &expected_value,
sizeof(expected_value), timeout_ms) == TRUE)) {
return IREE_STATUS_OK;
}
if (GetLastError() == ERROR_TIMEOUT) {
return IREE_STATUS_DEADLINE_EXCEEDED;
}
return IREE_STATUS_UNAVAILABLE;
}
static inline void iree_futex_wake(void* address, int32_t count) {
if (count == INT32_MAX) {
WakeByAddressAll(address);
return;
}
for (; count > 0; --count) {
WakeByAddressSingle(address);
}
}
// WaitOnAddress/WakeByAddress already hash by physical page on Windows.
static inline iree_status_code_t iree_futex_wait_shared(
void* address, uint32_t expected_value, iree_time_t deadline_ns) {
return iree_futex_wait(address, expected_value, deadline_ns);
}
static inline void iree_futex_wake_shared(void* address, int32_t count) {
iree_futex_wake(address, count);
}
#elif defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_LINUX)
static inline iree_status_code_t iree_futex_wait(void* address,
uint32_t expected_value,
iree_time_t deadline_ns) {
uint32_t timeout_ms = iree_absolute_deadline_to_timeout_ms(deadline_ns);
struct timespec timeout = {
.tv_sec = timeout_ms / 1000,
.tv_nsec = (timeout_ms % 1000) * 1000000,
};
int rc = syscall(
SYS_futex, address, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, expected_value,
timeout_ms == IREE_INFINITE_TIMEOUT_MS ? NULL : &timeout, NULL, 0);
if (IREE_LIKELY(rc == 0) || errno == EAGAIN || errno == EINTR) {
return IREE_STATUS_OK;
} else if (errno == ETIMEDOUT) {
return IREE_STATUS_DEADLINE_EXCEEDED;
}
return IREE_STATUS_UNAVAILABLE;
}
static inline void iree_futex_wake(void* address, int32_t count) {
syscall(SYS_futex, address, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, count, NULL,
NULL, 0);
}
// Cross-process variants: omit FUTEX_PRIVATE_FLAG so the kernel hashes by
// physical page instead of {mm, virtual address}. This allows futex operations
// across processes sharing the same physical page via mmap MAP_SHARED.
static inline iree_status_code_t iree_futex_wait_shared(
void* address, uint32_t expected_value, iree_time_t deadline_ns) {
uint32_t timeout_ms = iree_absolute_deadline_to_timeout_ms(deadline_ns);
struct timespec timeout = {
.tv_sec = timeout_ms / 1000,
.tv_nsec = (timeout_ms % 1000) * 1000000,
};
int rc = syscall(SYS_futex, address, FUTEX_WAIT, expected_value,
timeout_ms == IREE_INFINITE_TIMEOUT_MS ? NULL : &timeout,
NULL, 0);
if (IREE_LIKELY(rc == 0) || errno == EAGAIN || errno == EINTR) {
return IREE_STATUS_OK;
} else if (errno == ETIMEDOUT) {
return IREE_STATUS_DEADLINE_EXCEEDED;
}
return IREE_STATUS_UNAVAILABLE;
}
static inline void iree_futex_wake_shared(void* address, int32_t count) {
syscall(SYS_futex, address, FUTEX_WAKE, count, NULL, NULL, 0);
}
#endif // IREE_PLATFORM_*
#endif // IREE_RUNTIME_USE_FUTEX
#ifdef __cplusplus
} // extern "C"
#endif
#endif // IREE_BASE_THREADING_FUTEX_H_