|
19 | 19 |
|
20 | 20 | #include "include/apple_availability.h" |
21 | 21 |
|
22 | | -#ifdef __linux__ |
| 22 | +// ─── openkal ─── BEGIN ──────────────────────────────────────────────────────── |
| 23 | +// |
| 24 | +// This file's platform surface is exactly two functions, and the chain below |
| 25 | +// answers them once per operating system: Linux by `SYS_futex`, FreeBSD by |
| 26 | +// `_umtx_op`, OpenBSD by `futex`, Windows by `WaitOnAddress`, and everything |
| 27 | +// else by not waiting at all. |
| 28 | +// |
| 29 | +// openkal answers them once, full stop. `kal_task_wait` / `kal_task_wake` ARE |
| 30 | +// this primitive — the specification calls it the suspension primitive — and |
| 31 | +// they are the same two calls on every target this library is built for, |
| 32 | +// including one with no operating system underneath. |
| 33 | +// |
| 34 | +// ⚠️ IT WAS ALREADY REACHING openkal ON ONE TARGET, THE LONG WAY ROUND. On |
| 35 | +// Linux the branch below issues `SYS_futex`, openkal-musl's port intercepts the |
| 36 | +// system call, and its `__okm_futex` calls `kal_task_wait`. Going directly |
| 37 | +// removes that indirection AND makes the other object formats work, where the |
| 38 | +// interception has no system call to intercept. |
| 39 | +// |
| 40 | +// See llvm/PATCHES.md for every region of this vendored tree that is marked |
| 41 | +// like this and why. |
| 42 | +// ⚠️ AND ONLY WHEN THE IMPLEMENTATION BENEATH PROVIDES `openkal.task`. |
| 43 | +// |
| 44 | +// openkal is composable: an implementation provides an interface in whole or |
| 45 | +// not at all, and one with a single execution context has no suspension |
| 46 | +// primitive to offer — clause 6.2 says the remedy is that its absence be |
| 47 | +// expressed by its absence, so `kal_task_wait` is simply not there. |
| 48 | +// |
| 49 | +// ⚠️ Measured 2026-08-23 on riscv64 over SBI, after this block was written |
| 50 | +// without the guard: everything compiled and the link stopped on |
| 51 | +// `undefined symbol: kal_task_wait`. That is 6.1 working, not a defect — and |
| 52 | +// the answer is not to weaken openkal but to fall through to the branch that |
| 53 | +// was already correct there. `std::atomic::wait` on a machine with one |
| 54 | +// execution context has nothing to wait FOR; upstream's baseline polls, and |
| 55 | +// polling is the accurate implementation rather than a stand-in. |
| 56 | +// |
| 57 | +// ⚠️ THIS IS THE SAME FACT openkal-musl STATES AS `OKM_HAS_TASK`, said a second |
| 58 | +// time because there is now a second consumer. A manifest that could name the |
| 59 | +// interfaces an implementation provides would let both read one declaration; |
| 60 | +// today each states it for itself. |
| 61 | +#if defined(_LIBCPP_HAS_MUSL_LIBC) && OPENKAL_HAS_TASK |
| 62 | + |
| 63 | +// No headers: openkal's are reached through the declarations below, and the two |
| 64 | +// entry points are declared where they are used. |
| 65 | + |
| 66 | +#elif defined(__linux__) |
| 67 | +// ─── openkal ─── END ────────────────────────────────────────────────────────── |
23 | 68 |
|
24 | 69 | # include <linux/futex.h> |
25 | 70 | # include <sys/syscall.h> |
@@ -66,7 +111,60 @@ _LIBCPP_BEGIN_NAMESPACE_STD |
66 | 111 |
|
67 | 112 | struct NoTimeout {}; |
68 | 113 |
|
69 | | -#ifdef __linux__ |
| 114 | +// ─── openkal ─── BEGIN ──────────────────────────────────────────────────────── |
| 115 | +#if defined(_LIBCPP_HAS_MUSL_LIBC) && OPENKAL_HAS_TASK |
| 116 | + |
| 117 | +// ⚠️ A name for it. `__UINTPTR_TYPE__` expands to several tokens |
| 118 | +// (`long unsigned int`), so it cannot be written as a function-style cast — |
| 119 | +// the compiler reports `expected '(' for function-style cast`, which does not |
| 120 | +// mention the macro. One alias, used everywhere below. |
| 121 | +using __okl_uptr = __UINTPTR_TYPE__; |
| 122 | + |
| 123 | +extern "C" { |
| 124 | +// Declared rather than included: openkal's headers are C, this file is C++ in |
| 125 | +// libc++'s own namespace, and two declarations are cheaper than arranging for a |
| 126 | +// header to be reachable from inside `_LIBCPP_BEGIN_NAMESPACE_STD`. |
| 127 | +// |
| 128 | +// ⚠️ The widths are openkal's own (`kal_u32`, `kal_u64`), spelled here as what |
| 129 | +// the specification says they are. openkal derives them from the compiler for |
| 130 | +// exactly this reason: a consumer can restate them without a header and cannot |
| 131 | +// get a different type than the implementation was built with. |
| 132 | +int kal_task_wait(const unsigned int* __word, unsigned int __expected, unsigned long long __timeout_ns); |
| 133 | +int kal_task_wake(const unsigned int* __word, __okl_uptr __count, __okl_uptr* __woken); |
| 134 | +} |
| 135 | + |
| 136 | +template <std::size_t _Size, class MaybeTimeout> |
| 137 | +static void __platform_wait_on_address(void const* __ptr, void const* __val, MaybeTimeout maybe_timeout_ns) { |
| 138 | + static_assert(_Size == 4, "Can only wait on 4 bytes value"); |
| 139 | + alignas(unsigned int) char buffer[_Size]; |
| 140 | + std::memcpy(&buffer, const_cast<const void*>(__val), _Size); |
| 141 | + // ⚠️ The same two-second default the Linux branch uses, and for the same |
| 142 | + // reason: a wait that never times out cannot notice a wake it raced with. |
| 143 | + // Zero means "no timeout" to openkal, so the default is stated rather than |
| 144 | + // passed through. |
| 145 | + unsigned long long __ns = 2'000'000'000ull; |
| 146 | + if constexpr (!is_same_v<MaybeTimeout, NoTimeout>) { |
| 147 | + __ns = static_cast<unsigned long long>(maybe_timeout_ns); |
| 148 | + } |
| 149 | + kal_task_wait(reinterpret_cast<const unsigned int*>(__ptr), |
| 150 | + *reinterpret_cast<const unsigned int*>(&buffer), |
| 151 | + __ns); |
| 152 | +} |
| 153 | + |
| 154 | +template <std::size_t _Size> |
| 155 | +static void __platform_wake_by_address(void const* __ptr, bool __notify_one) { |
| 156 | + static_assert(_Size == 4, "Can only wake up on 4 bytes value"); |
| 157 | + // ⚠️ `-1` for "all", which is openkal's spelling of what the other branches |
| 158 | + // write as `INT_MAX`. The count is a `kal_uintptr`, so the maximum is stated |
| 159 | + // as the wrap rather than as a header's constant. |
| 160 | + __okl_uptr __woken = 0; |
| 161 | + kal_task_wake(reinterpret_cast<const unsigned int*>(__ptr), |
| 162 | + __notify_one ? __okl_uptr(1) : __okl_uptr(-1), |
| 163 | + &__woken); |
| 164 | +} |
| 165 | + |
| 166 | +#elif defined(__linux__) |
| 167 | +// ─── openkal ─── END ────────────────────────────────────────────────────────── |
70 | 168 |
|
71 | 169 | template <std::size_t _Size, class MaybeTimeout> |
72 | 170 | static void __platform_wait_on_address(void const* __ptr, void const* __val, MaybeTimeout maybe_timeout_ns) { |
|
0 commit comments