52.17% Lines (12/23)
60.00% Functions (3/5)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2026 Michael Vandeberg | 2 | // Copyright (c) 2026 Michael Vandeberg | |||||
| 3 | // | 3 | // | |||||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||||
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 6 | // | 6 | // | |||||
| 7 | // Official repository: https://github.com/cppalliance/corosio | 7 | // Official repository: https://github.com/cppalliance/corosio | |||||
| 8 | // | 8 | // | |||||
| 9 | 9 | |||||||
| 10 | #ifndef BOOST_COROSIO_DETAIL_CONTINUATION_OP_HPP | 10 | #ifndef BOOST_COROSIO_DETAIL_CONTINUATION_OP_HPP | |||||
| 11 | #define BOOST_COROSIO_DETAIL_CONTINUATION_OP_HPP | 11 | #define BOOST_COROSIO_DETAIL_CONTINUATION_OP_HPP | |||||
| 12 | 12 | |||||||
| 13 | #include <boost/corosio/detail/scheduler_op.hpp> | 13 | #include <boost/corosio/detail/scheduler_op.hpp> | |||||
| 14 | #include <boost/capy/continuation.hpp> | 14 | #include <boost/capy/continuation.hpp> | |||||
| 15 | 15 | |||||||
| 16 | #include <atomic> | 16 | #include <atomic> | |||||
| 17 | #include <cstdint> | 17 | #include <cstdint> | |||||
| 18 | #include <cstring> | 18 | #include <cstring> | |||||
| 19 | 19 | |||||||
| 20 | namespace boost::corosio::detail { | 20 | namespace boost::corosio::detail { | |||||
| 21 | 21 | |||||||
| 22 | /* Scheduler operation that resumes a capy::continuation. | 22 | /* Scheduler operation that resumes a capy::continuation. | |||||
| 23 | 23 | |||||||
| 24 | Embeds a continuation alongside a scheduler_op so the | 24 | Embeds a continuation alongside a scheduler_op so the | |||||
| 25 | scheduler can queue it in the same FIFO as I/O completions | 25 | scheduler can queue it in the same FIFO as I/O completions | |||||
| 26 | without a heap allocation. The continuation lives in the | 26 | without a heap allocation. The continuation lives in the | |||||
| 27 | caller's coroutine frame (awaitable or op struct); this | 27 | caller's coroutine frame (awaitable or op struct); this | |||||
| 28 | wrapper gives it a scheduler_op identity. | 28 | wrapper gives it a scheduler_op identity. | |||||
| 29 | 29 | |||||||
| 30 | io_context::executor_type::post(continuation&) uses | 30 | io_context::executor_type::post(continuation&) uses | |||||
| 31 | try_from_continuation() to recover the enclosing | 31 | try_from_continuation() to recover the enclosing | |||||
| 32 | continuation_op via a magic tag. The tag is read through | 32 | continuation_op via a magic tag. The tag is read through | |||||
| 33 | memcpy (not through a continuation_op*) so that UBSan | 33 | memcpy (not through a continuation_op*) so that UBSan | |||||
| 34 | does not flag the speculative pointer arithmetic when the | 34 | does not flag the speculative pointer arithmetic when the | |||||
| 35 | continuation is not actually inside a continuation_op. | 35 | continuation is not actually inside a continuation_op. | |||||
| 36 | */ | 36 | */ | |||||
| 37 | struct continuation_op final : scheduler_op | 37 | struct continuation_op final : scheduler_op | |||||
| 38 | { | 38 | { | |||||
| 39 | static constexpr std::uint32_t magic_ = 0xC0710Au; | 39 | static constexpr std::uint32_t magic_ = 0xC0710Au; | |||||
| 40 | 40 | |||||||
| 41 | std::uint32_t tag_ = magic_; | 41 | std::uint32_t tag_ = magic_; | |||||
| 42 | capy::continuation cont; | 42 | capy::continuation cont; | |||||
| 43 | 43 | |||||||
| HITCBC | 44 | 65120 | continuation_op() noexcept : scheduler_op(&do_complete) {} | 44 | 77228 | continuation_op() noexcept : scheduler_op(&do_complete) {} | ||
| 45 | 45 | |||||||
| 46 | // Reactor backends (epoll, select, kqueue) dispatch through | 46 | // Reactor backends (epoll, select, kqueue) dispatch through | |||||
| 47 | // virtual operator()(). IOCP dispatches through func_ which | 47 | // virtual operator()(). IOCP dispatches through func_ which | |||||
| 48 | // routes to do_complete below. | 48 | // routes to do_complete below. | |||||
| HITCBC | 49 | 7000 | void operator()() override | 49 | 8212 | void operator()() override | ||
| 50 | { | 50 | { | |||||
| 51 | std::atomic_thread_fence(std::memory_order_acquire); | 51 | std::atomic_thread_fence(std::memory_order_acquire); | |||||
| HITCBC | 52 | 7000 | cont.h.resume(); | 52 | 8212 | cont.h.resume(); | ||
| HITCBC | 53 | 7000 | } | 53 | 8212 | } | ||
| 54 | 54 | |||||||
| MISUBC | 55 | ✗ | void destroy() override | 55 | ✗ | void destroy() override | ||
| 56 | { | 56 | { | |||||
| MISUBC | 57 | ✗ | if (cont.h) | 57 | ✗ | if (cont.h) | ||
| MISUBC | 58 | ✗ | cont.h.destroy(); | 58 | ✗ | cont.h.destroy(); | ||
| MISUBC | 59 | ✗ | } | 59 | ✗ | } | ||
| 60 | 60 | |||||||
| 61 | private: | 61 | private: | |||||
| 62 | // IOCP completion entry point. owner == nullptr means destroy. | 62 | // IOCP completion entry point. owner == nullptr means destroy. | |||||
| MISUBC | 63 | ✗ | static void do_complete( | 63 | ✗ | static void do_complete( | ||
| 64 | void* owner, | 64 | void* owner, | |||||
| 65 | scheduler_op* base, | 65 | scheduler_op* base, | |||||
| 66 | std::uint32_t, | 66 | std::uint32_t, | |||||
| 67 | std::uint32_t) | 67 | std::uint32_t) | |||||
| 68 | { | 68 | { | |||||
| MISUBC | 69 | ✗ | auto* self = static_cast<continuation_op*>(base); | 69 | ✗ | auto* self = static_cast<continuation_op*>(base); | ||
| MISUBC | 70 | ✗ | if (!owner) | 70 | ✗ | if (!owner) | ||
| 71 | { | 71 | { | |||||
| MISUBC | 72 | ✗ | if (self->cont.h) | 72 | ✗ | if (self->cont.h) | ||
| MISUBC | 73 | ✗ | self->cont.h.destroy(); | 73 | ✗ | self->cont.h.destroy(); | ||
| MISUBC | 74 | ✗ | return; | 74 | ✗ | return; | ||
| 75 | } | 75 | } | |||||
| 76 | std::atomic_thread_fence(std::memory_order_acquire); | 76 | std::atomic_thread_fence(std::memory_order_acquire); | |||||
| MISUBC | 77 | ✗ | self->cont.h.resume(); | 77 | ✗ | self->cont.h.resume(); | ||
| 78 | } | 78 | } | |||||
| 79 | 79 | |||||||
| 80 | public: | 80 | public: | |||||
| 81 | 81 | |||||||
| 82 | // Recover the enclosing continuation_op from its cont member. | 82 | // Recover the enclosing continuation_op from its cont member. | |||||
| 83 | // Returns nullptr if the continuation is not tagged (bare | 83 | // Returns nullptr if the continuation is not tagged (bare | |||||
| 84 | // capy::continuation from capy internals like run_async). | 84 | // capy::continuation from capy internals like run_async). | |||||
| HITCBC | 85 | 7763 | static continuation_op* try_from_continuation( | 85 | 8975 | static continuation_op* try_from_continuation( | ||
| 86 | capy::continuation& c) noexcept | 86 | capy::continuation& c) noexcept | |||||
| 87 | { | 87 | { | |||||
| 88 | // offsetof on non-standard-layout is conditionally-supported; | 88 | // offsetof on non-standard-layout is conditionally-supported; | |||||
| 89 | // suppress the warning — all targeted compilers handle this | 89 | // suppress the warning — all targeted compilers handle this | |||||
| 90 | // correctly and the self-relative arithmetic is move-safe. | 90 | // correctly and the self-relative arithmetic is move-safe. | |||||
| 91 | #if defined(__GNUC__) || defined(__clang__) | 91 | #if defined(__GNUC__) || defined(__clang__) | |||||
| 92 | #pragma GCC diagnostic push | 92 | #pragma GCC diagnostic push | |||||
| 93 | #pragma GCC diagnostic ignored "-Winvalid-offsetof" | 93 | #pragma GCC diagnostic ignored "-Winvalid-offsetof" | |||||
| 94 | #endif | 94 | #endif | |||||
| HITCBC | 95 | 7763 | constexpr auto cont_off = offsetof(continuation_op, cont); | 95 | 8975 | constexpr auto cont_off = offsetof(continuation_op, cont); | ||
| HITCBC | 96 | 7763 | constexpr auto tag_off = offsetof(continuation_op, tag_); | 96 | 8975 | constexpr auto tag_off = offsetof(continuation_op, tag_); | ||
| 97 | #if defined(__GNUC__) || defined(__clang__) | 97 | #if defined(__GNUC__) || defined(__clang__) | |||||
| 98 | #pragma GCC diagnostic pop | 98 | #pragma GCC diagnostic pop | |||||
| 99 | #endif | 99 | #endif | |||||
| 100 | // Read the tag through memcpy from a char*, not through a | 100 | // Read the tag through memcpy from a char*, not through a | |||||
| 101 | // continuation_op*. This avoids UBSan's vptr check when | 101 | // continuation_op*. This avoids UBSan's vptr check when | |||||
| 102 | // the continuation is not actually inside a continuation_op. | 102 | // the continuation is not actually inside a continuation_op. | |||||
| HITCBC | 103 | 7763 | auto* base = reinterpret_cast<char*>(&c) - cont_off; | 103 | 8975 | auto* base = reinterpret_cast<char*>(&c) - cont_off; | ||
| 104 | std::uint32_t tag; | 104 | std::uint32_t tag; | |||||
| HITCBC | 105 | 7763 | std::memcpy(&tag, base + tag_off, sizeof(tag)); | 105 | 8975 | std::memcpy(&tag, base + tag_off, sizeof(tag)); | ||
| HITCBC | 106 | 7763 | if (tag != magic_) | 106 | 8975 | if (tag != magic_) | ||
| HITCBC | 107 | 763 | return nullptr; | 107 | 763 | return nullptr; | ||
| HITCBC | 108 | 7000 | return reinterpret_cast<continuation_op*>(base); | 108 | 8212 | return reinterpret_cast<continuation_op*>(base); | ||
| 109 | } | 109 | } | |||||
| 110 | }; | 110 | }; | |||||
| 111 | 111 | |||||||
| 112 | } // namespace boost::corosio::detail | 112 | } // namespace boost::corosio::detail | |||||
| 113 | 113 | |||||||
| 114 | #endif | 114 | #endif | |||||