Loading include/cxx/future +205 −193 Changes for include/cxx/future: 205 added lines, 193 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #pragma once /* Real gap: was missing entirely (libparitypp's auth_recursion_test.cpp Loading @@ -34,23 +35,23 @@ * facility's convention of wrapping a real primitive rather than * reinventing synchronization here. * * Deliberately scoped to a ZERO-ARGUMENT callable (no variadic Args... * forwarding) -- every real call site across this whole migration only * ever passes async() a single closure that captures what it needs, the * same way std::thread's fuller variadic-args machinery exists but * nothing here has needed promise<T>::set_exception()/exception_ptr * propagation either (this codebase's <exception> deliberately doesn't * have a working exception_ptr yet, documented there). If a real * variadic-args or exception-propagating call site shows up, extend * this then rather than guessing the right shape now. * Second real gap (mediadb): its ThreadPool hands out results through * std::packaged_task<R()>, and its backend calls * std::async(std::launch::async, fn, args...) with arguments. So async() * now takes variadic arguments (decay-copied, as the standard says), * packaged_task exists, and an exception escaping the task is stored in * the shared state (promise::set_exception) and rethrown by get() -- * <exception>'s exception_ptr is real now -- instead of terminating the * detached worker thread. * * Also deliberately does not distinguish launch::async from * Still deliberately does not distinguish launch::async from * launch::deferred at runtime -- always spawns a real thread regardless * of which policy value is passed. Real deferred semantics (lazy, * runs on the *waiting* thread, only at the first real wait()/get()) * add real complexity for a policy no real caller here has ever * requested; both enumerators are still real and nameable so * std::launch::async/std::launch::deferred both compile. */ * std::launch::async/std::launch::deferred both compile. No future_error * either: a broken promise leaves its future waiting forever. */ #include <chrono> #include <mutex> Loading @@ -59,81 +60,138 @@ #include <memory> #include <utility> #include <optional> #include <tuple> #include <type_traits> #include <exception> namespace std __attribute__((__visibility__("default"))) { enum class future_status { ready, timeout, deferred }; enum class launch { async = 1, deferred = 2 }; constexpr launch operator|(launch a, launch b) noexcept { return launch(int(a) | int(b)); } constexpr launch operator&(launch a, launch b) noexcept { return launch(int(a) & int(b)); } namespace __future_detail { /* Real gap found compiling LLVM: llvm/lib/ExecutionEngine/RuntimeDyld/ * RuntimeDyld.cpp does `std::make_shared<std::promise<ExpectedLookupResult>>()` * where ExpectedLookupResult is a real llvm::Expected<std::map<...>> -- * real llvm::Expected<T> is deliberately NOT default-constructible (it * must always hold either a value or an Error, never neither), but * `T value{}` below used to be a default member initializer, which * requires T to be default-constructible unconditionally, for every T, * not just the ones this codebase happened to instantiate promise<T> * with before. Real std::promise<T> has no such requirement either. * optional<T> (this codebase's own <optional>, same header/same * placement-new storage <memory>'s shared_ptr already relies on) is * default-constructible for ANY T and holds no T at all until actually * assigned, exactly matching promise<T>'s own real "value not set yet" * state -- same fix as this file's own <memory> sibling already applies * for shared_ptr<T>'s T=void case, just one level up here. */ template<class T> struct shared_state { struct state_base { mutex mtx; condition_variable cv; bool ready = false; exception_ptr ex; void wait() { unique_lock<mutex> lk(mtx); cv.wait(lk, [&] { return ready; }); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) { unique_lock<mutex> lk(mtx); if (cv.wait_for(lk, d, [&] { return ready; })) return future_status::ready; return future_status::timeout; } template<class Clock, class Duration> future_status wait_until(const chrono::time_point<Clock, Duration>& t) { unique_lock<mutex> lk(mtx); if (cv.wait_until(lk, t, [&] { return ready; })) return future_status::ready; return future_status::timeout; } void set_exception(exception_ptr p) { lock_guard<mutex> lk(mtx); ex = std::move(p); ready = true; cv.notify_all(); } }; template<class T> struct shared_state : state_base { optional<T> value; template<class U> void set_value(U&& v) { lock_guard<mutex> lk(mtx); value.emplace(std::forward<U>(v)); ready = true; cv.notify_all(); } }; template<> struct shared_state<void> { mutex mtx; condition_variable cv; bool ready = false; struct shared_state<void> : state_base { void set_value() { lock_guard<mutex> lk(mtx); ready = true; cv.notify_all(); } }; /* shared_future<T>::get() returns const T&, shared_future<void>::get() void */ template<class T> struct shared_get { using type = const T&; }; template<> struct shared_get<void> { using type = void; }; /* the futures' state constructor is private to everything but this */ struct access { template<class Fut, class S> static Fut make(shared_ptr<S> s) noexcept { return Fut(std::move(s)); } }; /* Run g() and store what it returns -- or what it throws -- in s. */ template<class R, class G> void fulfill(shared_state<R>& s, G&& g) { #if __cpp_exceptions try { #endif if constexpr (is_void_v<R>) { g(); s.set_value(); } else { s.set_value(g()); } #if __cpp_exceptions } catch (...) { s.set_exception(current_exception()); } #endif } } template<class T> class promise; template<class T> class shared_future; template<class T> class future { public: future() noexcept : _state(nullptr) {} future() noexcept = default; future(const future&) = delete; future(future&& o) noexcept : _state(std::move(o._state)) {} future(future&&) noexcept = default; future& operator=(const future&) = delete; future& operator=(future&& o) noexcept { _state = std::move(o._state); return *this; } future& operator=(future&&) noexcept = default; ~future() {} bool valid() const noexcept { return _state != nullptr; } T get() { wait(); shared_ptr<__future_detail::shared_state<T>> s = _state; _state = nullptr; unique_lock<mutex> lk(s->mtx); shared_ptr<__future_detail::shared_state<T>> s = std::move(_state); s->wait(); if (s->ex) rethrow_exception(s->ex); if constexpr (!is_void_v<T>) return std::move(*s->value); } void wait() const { unique_lock<mutex> lk(_state->mtx); _state->cv.wait(lk, [&] { return _state->ready; }); } void wait() const { _state->wait(); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) const { unique_lock<mutex> lk(_state->mtx); if (_state->cv.wait_for(lk, d, [&] { return _state->ready; })) return future_status::ready; return future_status::timeout; } future_status wait_for(const chrono::duration<Rep, Period>& d) const { return _state->wait_for(d); } template<class Clock, class Duration> future_status wait_until(const chrono::time_point<Clock, Duration>& t) const { return _state->wait_until(t); } /* Real gap found compiling LLVM: llvm/Support/ThreadPool.h does * `std::async(std::launch::deferred, std::move(Task)).share();` to Loading @@ -145,68 +203,16 @@ public: private: shared_ptr<__future_detail::shared_state<T>> _state; explicit future(shared_ptr<__future_detail::shared_state<T>> s) noexcept : _state(s) {} explicit future(shared_ptr<__future_detail::shared_state<T>> s) noexcept : _state(std::move(s)) {} friend class promise<T>; friend struct __future_detail::access; friend class shared_future<T>; template<class F> friend future<decltype(declval<F>()())> async(launch, F&&); }; template<> class future<void> { public: future() noexcept : _state(nullptr) {} future(const future&) = delete; future(future&& o) noexcept : _state(std::move(o._state)) {} future& operator=(const future&) = delete; future& operator=(future&& o) noexcept { _state = std::move(o._state); return *this; } ~future() {} bool valid() const noexcept { return _state != nullptr; } void get() { wait(); _state = nullptr; } void wait() const { unique_lock<mutex> lk(_state->mtx); _state->cv.wait(lk, [&] { return _state->ready; }); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) const { unique_lock<mutex> lk(_state->mtx); if (_state->cv.wait_for(lk, d, [&] { return _state->ready; })) return future_status::ready; return future_status::timeout; } shared_future<void> share() noexcept; private: shared_ptr<__future_detail::shared_state<void>> _state; explicit future(shared_ptr<__future_detail::shared_state<void>> s) noexcept : _state(s) {} friend class promise<void>; friend class shared_future<void>; template<class F> friend future<decltype(declval<F>()())> async(launch, F&&); }; /* Real gap found compiling LLVM: shared_future<T> was missing entirely. * Unlike future<T> (move-only, get() moves the value out and invalidates * the future -- a single owner, single consumption), shared_future<T> is * copyable and multiple copies (all sharing the same underlying state * via shared_ptr, exactly like future<T> already does internally) can * each independently wait()/get() the same result -- so get() returns a * const reference into the still-owned state instead of moving out of * it. Built the same way future<T>/promise<T> already are: a thin * wrapper over the same __future_detail::shared_state<T>. */ template<class T> class shared_future { public: shared_future() noexcept : _state(nullptr) {} shared_future() noexcept = default; shared_future(const shared_future&) noexcept = default; shared_future(shared_future&&) noexcept = default; shared_future(future<T>&& f) noexcept : _state(std::move(f._state)) {} Loading @@ -216,64 +222,28 @@ public: bool valid() const noexcept { return _state != nullptr; } const T& get() const { wait(); unique_lock<mutex> lk(_state->mtx); typename __future_detail::shared_get<T>::type get() const { _state->wait(); if (_state->ex) rethrow_exception(_state->ex); if constexpr (!is_void_v<T>) return *_state->value; } void wait() const { unique_lock<mutex> lk(_state->mtx); _state->cv.wait(lk, [&] { return _state->ready; }); } void wait() const { _state->wait(); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) const { unique_lock<mutex> lk(_state->mtx); if (_state->cv.wait_for(lk, d, [&] { return _state->ready; })) return future_status::ready; return future_status::timeout; } future_status wait_for(const chrono::duration<Rep, Period>& d) const { return _state->wait_for(d); } private: shared_ptr<__future_detail::shared_state<T>> _state; }; template<> class shared_future<void> { public: shared_future() noexcept : _state(nullptr) {} shared_future(const shared_future&) noexcept = default; shared_future(shared_future&&) noexcept = default; shared_future(future<void>&& f) noexcept : _state(std::move(f._state)) {} shared_future& operator=(const shared_future&) noexcept = default; shared_future& operator=(shared_future&&) noexcept = default; ~shared_future() {} bool valid() const noexcept { return _state != nullptr; } void get() const { wait(); } void wait() const { unique_lock<mutex> lk(_state->mtx); _state->cv.wait(lk, [&] { return _state->ready; }); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) const { unique_lock<mutex> lk(_state->mtx); if (_state->cv.wait_for(lk, d, [&] { return _state->ready; })) return future_status::ready; return future_status::timeout; } template<class Clock, class Duration> future_status wait_until(const chrono::time_point<Clock, Duration>& t) const { return _state->wait_until(t); } private: shared_ptr<__future_detail::shared_state<void>> _state; shared_ptr<__future_detail::shared_state<T>> _state; }; template<class T> inline shared_future<T> future<T>::share() noexcept { return shared_future<T>(std::move(*this)); } inline shared_future<void> future<void>::share() noexcept { return shared_future<void>(std::move(*this)); } template<class T> class promise { Loading @@ -285,20 +255,13 @@ public: promise& operator=(promise&&) noexcept = default; ~promise() {} future<T> get_future() { return future<T>(_state); } void swap(promise& o) noexcept { _state.swap(o._state); } void set_value(const T& v) { lock_guard<mutex> lk(_state->mtx); _state->value = v; _state->ready = true; _state->cv.notify_all(); } void set_value(T&& v) { lock_guard<mutex> lk(_state->mtx); _state->value = std::move(v); _state->ready = true; _state->cv.notify_all(); } future<T> get_future() { return __future_detail::access::make<future<T>>(_state); } void set_value(const T& v) { _state->set_value(v); } void set_value(T&& v) { _state->set_value(std::move(v)); } void set_exception(exception_ptr p) { _state->set_exception(std::move(p)); } private: shared_ptr<__future_detail::shared_state<T>> _state; Loading @@ -314,46 +277,95 @@ public: promise& operator=(promise&&) noexcept = default; ~promise() {} future<void> get_future() { return future<void>(_state); } void swap(promise& o) noexcept { _state.swap(o._state); } void set_value() { lock_guard<mutex> lk(_state->mtx); _state->ready = true; _state->cv.notify_all(); } future<void> get_future() { return __future_detail::access::make<future<void>>(_state); } void set_value() { _state->set_value(); } void set_exception(exception_ptr p) { _state->set_exception(std::move(p)); } private: shared_ptr<__future_detail::shared_state<void>> _state; }; /* Real async() -- spawns a real std::thread that runs f() and stores * its result (or, for a void-returning f, just its completion) into a * real shared state, exactly the same shared_ptr<state>+mutex+ * condition_variable pattern promise<T>/future<T> already use above * (async() is implementable purely in terms of promise+thread, which is * exactly what this does). */ template<class T> void swap(promise<T>& a, promise<T>& b) noexcept { a.swap(b); } /* packaged_task: the callable is type-erased behind a virtual call (not * std::function, which needs it copyable -- tasks may own move-only * state); calling the task fulfils the shared state once. */ template<class> class packaged_task; template<class R, class... Args> class packaged_task<R(Args...)> { struct callable_base { virtual ~callable_base() {} virtual R call(Args&&... args) = 0; }; template<class F> future<decltype(declval<F>()())> async(launch /*policy*/, F&& f) { using R = decltype(f()); auto state = make_shared<__future_detail::shared_state<R>>(); struct callable : callable_base { F f; template<class G> explicit callable(G&& g) : f(std::forward<G>(g)) {} R call(Args&&... args) override { return std::invoke(f, std::forward<Args>(args)...); } }; thread([state, fn = decay_t<F>(std::forward<F>(f))]() mutable { if constexpr (is_void_v<R>) { fn(); lock_guard<mutex> lk(state->mtx); state->ready = true; state->cv.notify_all(); } else { R r = fn(); lock_guard<mutex> lk(state->mtx); state->value = std::move(r); state->ready = true; state->cv.notify_all(); public: packaged_task() noexcept = default; template<class F, class = enable_if_t<!is_same_v<remove_cvref_t<F>, packaged_task>>> explicit packaged_task(F&& f) : _fn(new callable<decay_t<F>>(std::forward<F>(f))), _state(make_shared<__future_detail::shared_state<R>>()) {} packaged_task(const packaged_task&) = delete; packaged_task(packaged_task&&) noexcept = default; packaged_task& operator=(const packaged_task&) = delete; packaged_task& operator=(packaged_task&&) noexcept = default; ~packaged_task() {} bool valid() const noexcept { return _fn != nullptr; } void swap(packaged_task& o) noexcept { _fn.swap(o._fn); _state.swap(o._state); } future<R> get_future() { return __future_detail::access::make<future<R>>(_state); } void operator()(Args... args) { __future_detail::fulfill(*_state, [&]() -> R { return _fn->call(std::forward<Args>(args)...); }); } /* same callable, fresh shared state */ void reset() { _state = make_shared<__future_detail::shared_state<R>>(); } private: unique_ptr<callable_base> _fn; shared_ptr<__future_detail::shared_state<R>> _state; }; template<class R, class... Args> void swap(packaged_task<R(Args...)>& a, packaged_task<R(Args...)>& b) noexcept { a.swap(b); } template<class F, class... Args> future<invoke_result_t<decay_t<F>, decay_t<Args>...>> async(launch /*policy*/, F&& f, Args&&... args) { using R = invoke_result_t<decay_t<F>, decay_t<Args>...>; auto state = make_shared<__future_detail::shared_state<R>>(); thread([state, fn = decay_t<F>(std::forward<F>(f)), bound = tuple<decay_t<Args>...>(std::forward<Args>(args)...)]() mutable { __future_detail::fulfill(*state, [&]() -> R { return std::apply(std::move(fn), std::move(bound)); }); }).detach(); if constexpr (is_void_v<R>) return future<void>(state); else return future<R>(state); return __future_detail::access::make<future<R>>(std::move(state)); } template<class F, class... Args, class = enable_if_t<!is_same_v<decay_t<F>, launch>>> future<invoke_result_t<decay_t<F>, decay_t<Args>...>> async(F&& f, Args&&... args) { return std::async(launch::async | launch::deferred, std::forward<F>(f), std::forward<Args>(args)...); } } include/cxx/semaphore 0 → 100644 +104 −0 Changes for include/cxx/semaphore: 104 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* * Copyright (c) 2026, Jan Koester jan.koester@gmx.net * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #pragma once /* Real gap: was missing entirely (mediadb's preview renderer bounds its * concurrent FFmpeg renders with std::counting_semaphore and * try_acquire_for()). Wraps this codebase's own mutex/condition_variable * like <future> does, rather than a futex of its own. */ #include <chrono> #include <cstddef> #include <mutex> #include <condition_variable> namespace std __attribute__((__visibility__("default"))) { template<ptrdiff_t LeastMaxValue = __PTRDIFF_MAX__> class counting_semaphore { public: static constexpr ptrdiff_t max() noexcept { return LeastMaxValue; } explicit counting_semaphore(ptrdiff_t desired) : _count(desired) {} counting_semaphore(const counting_semaphore&) = delete; counting_semaphore& operator=(const counting_semaphore&) = delete; ~counting_semaphore() {} void release(ptrdiff_t update = 1) { { lock_guard<mutex> lk(_mtx); _count += update; } if (update == 1) _cv.notify_one(); else _cv.notify_all(); } void acquire() { unique_lock<mutex> lk(_mtx); _cv.wait(lk, [&] { return _count > 0; }); --_count; } bool try_acquire() noexcept { lock_guard<mutex> lk(_mtx); if (_count <= 0) return false; --_count; return true; } template<class Rep, class Period> bool try_acquire_for(const chrono::duration<Rep, Period>& rel_time) { unique_lock<mutex> lk(_mtx); if (!_cv.wait_for(lk, rel_time, [&] { return _count > 0; })) return false; --_count; return true; } template<class Clock, class Duration> bool try_acquire_until(const chrono::time_point<Clock, Duration>& abs_time) { unique_lock<mutex> lk(_mtx); if (!_cv.wait_until(lk, abs_time, [&] { return _count > 0; })) return false; --_count; return true; } private: mutex _mtx; condition_variable _cv; ptrdiff_t _count; }; using binary_semaphore = counting_semaphore<1>; } Loading
include/cxx/future +205 −193 Changes for include/cxx/future: 205 added lines, 193 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #pragma once /* Real gap: was missing entirely (libparitypp's auth_recursion_test.cpp Loading @@ -34,23 +35,23 @@ * facility's convention of wrapping a real primitive rather than * reinventing synchronization here. * * Deliberately scoped to a ZERO-ARGUMENT callable (no variadic Args... * forwarding) -- every real call site across this whole migration only * ever passes async() a single closure that captures what it needs, the * same way std::thread's fuller variadic-args machinery exists but * nothing here has needed promise<T>::set_exception()/exception_ptr * propagation either (this codebase's <exception> deliberately doesn't * have a working exception_ptr yet, documented there). If a real * variadic-args or exception-propagating call site shows up, extend * this then rather than guessing the right shape now. * Second real gap (mediadb): its ThreadPool hands out results through * std::packaged_task<R()>, and its backend calls * std::async(std::launch::async, fn, args...) with arguments. So async() * now takes variadic arguments (decay-copied, as the standard says), * packaged_task exists, and an exception escaping the task is stored in * the shared state (promise::set_exception) and rethrown by get() -- * <exception>'s exception_ptr is real now -- instead of terminating the * detached worker thread. * * Also deliberately does not distinguish launch::async from * Still deliberately does not distinguish launch::async from * launch::deferred at runtime -- always spawns a real thread regardless * of which policy value is passed. Real deferred semantics (lazy, * runs on the *waiting* thread, only at the first real wait()/get()) * add real complexity for a policy no real caller here has ever * requested; both enumerators are still real and nameable so * std::launch::async/std::launch::deferred both compile. */ * std::launch::async/std::launch::deferred both compile. No future_error * either: a broken promise leaves its future waiting forever. */ #include <chrono> #include <mutex> Loading @@ -59,81 +60,138 @@ #include <memory> #include <utility> #include <optional> #include <tuple> #include <type_traits> #include <exception> namespace std __attribute__((__visibility__("default"))) { enum class future_status { ready, timeout, deferred }; enum class launch { async = 1, deferred = 2 }; constexpr launch operator|(launch a, launch b) noexcept { return launch(int(a) | int(b)); } constexpr launch operator&(launch a, launch b) noexcept { return launch(int(a) & int(b)); } namespace __future_detail { /* Real gap found compiling LLVM: llvm/lib/ExecutionEngine/RuntimeDyld/ * RuntimeDyld.cpp does `std::make_shared<std::promise<ExpectedLookupResult>>()` * where ExpectedLookupResult is a real llvm::Expected<std::map<...>> -- * real llvm::Expected<T> is deliberately NOT default-constructible (it * must always hold either a value or an Error, never neither), but * `T value{}` below used to be a default member initializer, which * requires T to be default-constructible unconditionally, for every T, * not just the ones this codebase happened to instantiate promise<T> * with before. Real std::promise<T> has no such requirement either. * optional<T> (this codebase's own <optional>, same header/same * placement-new storage <memory>'s shared_ptr already relies on) is * default-constructible for ANY T and holds no T at all until actually * assigned, exactly matching promise<T>'s own real "value not set yet" * state -- same fix as this file's own <memory> sibling already applies * for shared_ptr<T>'s T=void case, just one level up here. */ template<class T> struct shared_state { struct state_base { mutex mtx; condition_variable cv; bool ready = false; exception_ptr ex; void wait() { unique_lock<mutex> lk(mtx); cv.wait(lk, [&] { return ready; }); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) { unique_lock<mutex> lk(mtx); if (cv.wait_for(lk, d, [&] { return ready; })) return future_status::ready; return future_status::timeout; } template<class Clock, class Duration> future_status wait_until(const chrono::time_point<Clock, Duration>& t) { unique_lock<mutex> lk(mtx); if (cv.wait_until(lk, t, [&] { return ready; })) return future_status::ready; return future_status::timeout; } void set_exception(exception_ptr p) { lock_guard<mutex> lk(mtx); ex = std::move(p); ready = true; cv.notify_all(); } }; template<class T> struct shared_state : state_base { optional<T> value; template<class U> void set_value(U&& v) { lock_guard<mutex> lk(mtx); value.emplace(std::forward<U>(v)); ready = true; cv.notify_all(); } }; template<> struct shared_state<void> { mutex mtx; condition_variable cv; bool ready = false; struct shared_state<void> : state_base { void set_value() { lock_guard<mutex> lk(mtx); ready = true; cv.notify_all(); } }; /* shared_future<T>::get() returns const T&, shared_future<void>::get() void */ template<class T> struct shared_get { using type = const T&; }; template<> struct shared_get<void> { using type = void; }; /* the futures' state constructor is private to everything but this */ struct access { template<class Fut, class S> static Fut make(shared_ptr<S> s) noexcept { return Fut(std::move(s)); } }; /* Run g() and store what it returns -- or what it throws -- in s. */ template<class R, class G> void fulfill(shared_state<R>& s, G&& g) { #if __cpp_exceptions try { #endif if constexpr (is_void_v<R>) { g(); s.set_value(); } else { s.set_value(g()); } #if __cpp_exceptions } catch (...) { s.set_exception(current_exception()); } #endif } } template<class T> class promise; template<class T> class shared_future; template<class T> class future { public: future() noexcept : _state(nullptr) {} future() noexcept = default; future(const future&) = delete; future(future&& o) noexcept : _state(std::move(o._state)) {} future(future&&) noexcept = default; future& operator=(const future&) = delete; future& operator=(future&& o) noexcept { _state = std::move(o._state); return *this; } future& operator=(future&&) noexcept = default; ~future() {} bool valid() const noexcept { return _state != nullptr; } T get() { wait(); shared_ptr<__future_detail::shared_state<T>> s = _state; _state = nullptr; unique_lock<mutex> lk(s->mtx); shared_ptr<__future_detail::shared_state<T>> s = std::move(_state); s->wait(); if (s->ex) rethrow_exception(s->ex); if constexpr (!is_void_v<T>) return std::move(*s->value); } void wait() const { unique_lock<mutex> lk(_state->mtx); _state->cv.wait(lk, [&] { return _state->ready; }); } void wait() const { _state->wait(); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) const { unique_lock<mutex> lk(_state->mtx); if (_state->cv.wait_for(lk, d, [&] { return _state->ready; })) return future_status::ready; return future_status::timeout; } future_status wait_for(const chrono::duration<Rep, Period>& d) const { return _state->wait_for(d); } template<class Clock, class Duration> future_status wait_until(const chrono::time_point<Clock, Duration>& t) const { return _state->wait_until(t); } /* Real gap found compiling LLVM: llvm/Support/ThreadPool.h does * `std::async(std::launch::deferred, std::move(Task)).share();` to Loading @@ -145,68 +203,16 @@ public: private: shared_ptr<__future_detail::shared_state<T>> _state; explicit future(shared_ptr<__future_detail::shared_state<T>> s) noexcept : _state(s) {} explicit future(shared_ptr<__future_detail::shared_state<T>> s) noexcept : _state(std::move(s)) {} friend class promise<T>; friend struct __future_detail::access; friend class shared_future<T>; template<class F> friend future<decltype(declval<F>()())> async(launch, F&&); }; template<> class future<void> { public: future() noexcept : _state(nullptr) {} future(const future&) = delete; future(future&& o) noexcept : _state(std::move(o._state)) {} future& operator=(const future&) = delete; future& operator=(future&& o) noexcept { _state = std::move(o._state); return *this; } ~future() {} bool valid() const noexcept { return _state != nullptr; } void get() { wait(); _state = nullptr; } void wait() const { unique_lock<mutex> lk(_state->mtx); _state->cv.wait(lk, [&] { return _state->ready; }); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) const { unique_lock<mutex> lk(_state->mtx); if (_state->cv.wait_for(lk, d, [&] { return _state->ready; })) return future_status::ready; return future_status::timeout; } shared_future<void> share() noexcept; private: shared_ptr<__future_detail::shared_state<void>> _state; explicit future(shared_ptr<__future_detail::shared_state<void>> s) noexcept : _state(s) {} friend class promise<void>; friend class shared_future<void>; template<class F> friend future<decltype(declval<F>()())> async(launch, F&&); }; /* Real gap found compiling LLVM: shared_future<T> was missing entirely. * Unlike future<T> (move-only, get() moves the value out and invalidates * the future -- a single owner, single consumption), shared_future<T> is * copyable and multiple copies (all sharing the same underlying state * via shared_ptr, exactly like future<T> already does internally) can * each independently wait()/get() the same result -- so get() returns a * const reference into the still-owned state instead of moving out of * it. Built the same way future<T>/promise<T> already are: a thin * wrapper over the same __future_detail::shared_state<T>. */ template<class T> class shared_future { public: shared_future() noexcept : _state(nullptr) {} shared_future() noexcept = default; shared_future(const shared_future&) noexcept = default; shared_future(shared_future&&) noexcept = default; shared_future(future<T>&& f) noexcept : _state(std::move(f._state)) {} Loading @@ -216,64 +222,28 @@ public: bool valid() const noexcept { return _state != nullptr; } const T& get() const { wait(); unique_lock<mutex> lk(_state->mtx); typename __future_detail::shared_get<T>::type get() const { _state->wait(); if (_state->ex) rethrow_exception(_state->ex); if constexpr (!is_void_v<T>) return *_state->value; } void wait() const { unique_lock<mutex> lk(_state->mtx); _state->cv.wait(lk, [&] { return _state->ready; }); } void wait() const { _state->wait(); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) const { unique_lock<mutex> lk(_state->mtx); if (_state->cv.wait_for(lk, d, [&] { return _state->ready; })) return future_status::ready; return future_status::timeout; } future_status wait_for(const chrono::duration<Rep, Period>& d) const { return _state->wait_for(d); } private: shared_ptr<__future_detail::shared_state<T>> _state; }; template<> class shared_future<void> { public: shared_future() noexcept : _state(nullptr) {} shared_future(const shared_future&) noexcept = default; shared_future(shared_future&&) noexcept = default; shared_future(future<void>&& f) noexcept : _state(std::move(f._state)) {} shared_future& operator=(const shared_future&) noexcept = default; shared_future& operator=(shared_future&&) noexcept = default; ~shared_future() {} bool valid() const noexcept { return _state != nullptr; } void get() const { wait(); } void wait() const { unique_lock<mutex> lk(_state->mtx); _state->cv.wait(lk, [&] { return _state->ready; }); } template<class Rep, class Period> future_status wait_for(const chrono::duration<Rep, Period>& d) const { unique_lock<mutex> lk(_state->mtx); if (_state->cv.wait_for(lk, d, [&] { return _state->ready; })) return future_status::ready; return future_status::timeout; } template<class Clock, class Duration> future_status wait_until(const chrono::time_point<Clock, Duration>& t) const { return _state->wait_until(t); } private: shared_ptr<__future_detail::shared_state<void>> _state; shared_ptr<__future_detail::shared_state<T>> _state; }; template<class T> inline shared_future<T> future<T>::share() noexcept { return shared_future<T>(std::move(*this)); } inline shared_future<void> future<void>::share() noexcept { return shared_future<void>(std::move(*this)); } template<class T> class promise { Loading @@ -285,20 +255,13 @@ public: promise& operator=(promise&&) noexcept = default; ~promise() {} future<T> get_future() { return future<T>(_state); } void swap(promise& o) noexcept { _state.swap(o._state); } void set_value(const T& v) { lock_guard<mutex> lk(_state->mtx); _state->value = v; _state->ready = true; _state->cv.notify_all(); } void set_value(T&& v) { lock_guard<mutex> lk(_state->mtx); _state->value = std::move(v); _state->ready = true; _state->cv.notify_all(); } future<T> get_future() { return __future_detail::access::make<future<T>>(_state); } void set_value(const T& v) { _state->set_value(v); } void set_value(T&& v) { _state->set_value(std::move(v)); } void set_exception(exception_ptr p) { _state->set_exception(std::move(p)); } private: shared_ptr<__future_detail::shared_state<T>> _state; Loading @@ -314,46 +277,95 @@ public: promise& operator=(promise&&) noexcept = default; ~promise() {} future<void> get_future() { return future<void>(_state); } void swap(promise& o) noexcept { _state.swap(o._state); } void set_value() { lock_guard<mutex> lk(_state->mtx); _state->ready = true; _state->cv.notify_all(); } future<void> get_future() { return __future_detail::access::make<future<void>>(_state); } void set_value() { _state->set_value(); } void set_exception(exception_ptr p) { _state->set_exception(std::move(p)); } private: shared_ptr<__future_detail::shared_state<void>> _state; }; /* Real async() -- spawns a real std::thread that runs f() and stores * its result (or, for a void-returning f, just its completion) into a * real shared state, exactly the same shared_ptr<state>+mutex+ * condition_variable pattern promise<T>/future<T> already use above * (async() is implementable purely in terms of promise+thread, which is * exactly what this does). */ template<class T> void swap(promise<T>& a, promise<T>& b) noexcept { a.swap(b); } /* packaged_task: the callable is type-erased behind a virtual call (not * std::function, which needs it copyable -- tasks may own move-only * state); calling the task fulfils the shared state once. */ template<class> class packaged_task; template<class R, class... Args> class packaged_task<R(Args...)> { struct callable_base { virtual ~callable_base() {} virtual R call(Args&&... args) = 0; }; template<class F> future<decltype(declval<F>()())> async(launch /*policy*/, F&& f) { using R = decltype(f()); auto state = make_shared<__future_detail::shared_state<R>>(); struct callable : callable_base { F f; template<class G> explicit callable(G&& g) : f(std::forward<G>(g)) {} R call(Args&&... args) override { return std::invoke(f, std::forward<Args>(args)...); } }; thread([state, fn = decay_t<F>(std::forward<F>(f))]() mutable { if constexpr (is_void_v<R>) { fn(); lock_guard<mutex> lk(state->mtx); state->ready = true; state->cv.notify_all(); } else { R r = fn(); lock_guard<mutex> lk(state->mtx); state->value = std::move(r); state->ready = true; state->cv.notify_all(); public: packaged_task() noexcept = default; template<class F, class = enable_if_t<!is_same_v<remove_cvref_t<F>, packaged_task>>> explicit packaged_task(F&& f) : _fn(new callable<decay_t<F>>(std::forward<F>(f))), _state(make_shared<__future_detail::shared_state<R>>()) {} packaged_task(const packaged_task&) = delete; packaged_task(packaged_task&&) noexcept = default; packaged_task& operator=(const packaged_task&) = delete; packaged_task& operator=(packaged_task&&) noexcept = default; ~packaged_task() {} bool valid() const noexcept { return _fn != nullptr; } void swap(packaged_task& o) noexcept { _fn.swap(o._fn); _state.swap(o._state); } future<R> get_future() { return __future_detail::access::make<future<R>>(_state); } void operator()(Args... args) { __future_detail::fulfill(*_state, [&]() -> R { return _fn->call(std::forward<Args>(args)...); }); } /* same callable, fresh shared state */ void reset() { _state = make_shared<__future_detail::shared_state<R>>(); } private: unique_ptr<callable_base> _fn; shared_ptr<__future_detail::shared_state<R>> _state; }; template<class R, class... Args> void swap(packaged_task<R(Args...)>& a, packaged_task<R(Args...)>& b) noexcept { a.swap(b); } template<class F, class... Args> future<invoke_result_t<decay_t<F>, decay_t<Args>...>> async(launch /*policy*/, F&& f, Args&&... args) { using R = invoke_result_t<decay_t<F>, decay_t<Args>...>; auto state = make_shared<__future_detail::shared_state<R>>(); thread([state, fn = decay_t<F>(std::forward<F>(f)), bound = tuple<decay_t<Args>...>(std::forward<Args>(args)...)]() mutable { __future_detail::fulfill(*state, [&]() -> R { return std::apply(std::move(fn), std::move(bound)); }); }).detach(); if constexpr (is_void_v<R>) return future<void>(state); else return future<R>(state); return __future_detail::access::make<future<R>>(std::move(state)); } template<class F, class... Args, class = enable_if_t<!is_same_v<decay_t<F>, launch>>> future<invoke_result_t<decay_t<F>, decay_t<Args>...>> async(F&& f, Args&&... args) { return std::async(launch::async | launch::deferred, std::forward<F>(f), std::forward<Args>(args)...); } }
include/cxx/semaphore 0 → 100644 +104 −0 Changes for include/cxx/semaphore: 104 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* * Copyright (c) 2026, Jan Koester jan.koester@gmx.net * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #pragma once /* Real gap: was missing entirely (mediadb's preview renderer bounds its * concurrent FFmpeg renders with std::counting_semaphore and * try_acquire_for()). Wraps this codebase's own mutex/condition_variable * like <future> does, rather than a futex of its own. */ #include <chrono> #include <cstddef> #include <mutex> #include <condition_variable> namespace std __attribute__((__visibility__("default"))) { template<ptrdiff_t LeastMaxValue = __PTRDIFF_MAX__> class counting_semaphore { public: static constexpr ptrdiff_t max() noexcept { return LeastMaxValue; } explicit counting_semaphore(ptrdiff_t desired) : _count(desired) {} counting_semaphore(const counting_semaphore&) = delete; counting_semaphore& operator=(const counting_semaphore&) = delete; ~counting_semaphore() {} void release(ptrdiff_t update = 1) { { lock_guard<mutex> lk(_mtx); _count += update; } if (update == 1) _cv.notify_one(); else _cv.notify_all(); } void acquire() { unique_lock<mutex> lk(_mtx); _cv.wait(lk, [&] { return _count > 0; }); --_count; } bool try_acquire() noexcept { lock_guard<mutex> lk(_mtx); if (_count <= 0) return false; --_count; return true; } template<class Rep, class Period> bool try_acquire_for(const chrono::duration<Rep, Period>& rel_time) { unique_lock<mutex> lk(_mtx); if (!_cv.wait_for(lk, rel_time, [&] { return _count > 0; })) return false; --_count; return true; } template<class Clock, class Duration> bool try_acquire_until(const chrono::time_point<Clock, Duration>& abs_time) { unique_lock<mutex> lk(_mtx); if (!_cv.wait_until(lk, abs_time, [&] { return _count > 0; })) return false; --_count; return true; } private: mutex _mtx; condition_variable _cv; ptrdiff_t _count; }; using binary_semaphore = counting_semaphore<1>; }