Commit eae76dd8 authored by jan.koester's avatar jan.koester
Browse files

test

parent 972fa24f
Loading
Loading
Loading
Loading
+205 −193
Changes for include/cxx/future: 205 added lines, 193 removed lines.
Original line number Diff line number Diff line
@@ -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
@@ -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>
@@ -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
@@ -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)) {}
@@ -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 {
@@ -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;
@@ -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>;

}