src/internal/shared_tls_index.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_INTERNAL_SHARED_TLS_INDEX_HPP | ||
| 2 | #define DETOURMODKIT_INTERNAL_SHARED_TLS_INDEX_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file internal/shared_tls_index.hpp | ||
| 6 | * @brief A Win32 TLS index that counted owners share and the last owner returns. | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <windows.h> | ||
| 10 | |||
| 11 | #include <atomic> | ||
| 12 | #include <cstdint> | ||
| 13 | |||
| 14 | namespace DetourModKit::detail | ||
| 15 | { | ||
| 16 | /** | ||
| 17 | * @brief One Win32 TLS index that counted owners share. | ||
| 18 | * @details An owner is an object whose lifetime spans every write through the index on any thread. The one | ||
| 19 | * exception is a restore of null that runs before other code on its thread (pop_emit_frame). Each acquire | ||
| 20 | * reserves an index when none is published. The last release unpublishes the index before TlsFree. A | ||
| 21 | * later acquire reserves a fresh index. The owner count and index share one word, so a reservation cannot | ||
| 22 | * interleave with a return. | ||
| 23 | * | ||
| 24 | * A reader with a stale index reads its own slot, which TlsFree zeroes, so a read needs no owner. | ||
| 25 | * Constant initialization and a trivial destructor permit use after static destruction ([B-47]). | ||
| 26 | */ | ||
| 27 | class SharedTlsIndex | ||
| 28 | { | ||
| 29 | public: | ||
| 30 | constexpr SharedTlsIndex() noexcept = default; | ||
| 31 | SharedTlsIndex(const SharedTlsIndex &) = delete; | ||
| 32 | SharedTlsIndex &operator=(const SharedTlsIndex &) = delete; | ||
| 33 | |||
| 34 | /** | ||
| 35 | * @brief Registers one owner, and reserves an index when none is published. | ||
| 36 | * @param error Receives GetLastError() from a failed reservation. Can be null. | ||
| 37 | * @return The published index, or TLS_OUT_OF_INDEXES when the process has none. The owner stays registered in | ||
| 38 | * both cases, so each call needs one @ref release. | ||
| 39 | */ | ||
| 40 | 18021 | [[nodiscard]] DWORD acquire(DWORD *error = nullptr) noexcept | |
| 41 | { | ||
| 42 | 18021 | DWORD spare = TLS_OUT_OF_INDEXES; | |
| 43 | 36041 | std::uint64_t state = m_state.load(std::memory_order_acquire); | |
| 44 | for (;;) | ||
| 45 | { | ||
| 46 | 18026 | DWORD index = index_of(state); | |
| 47 | 18023 | if (index == TLS_OUT_OF_INDEXES) | |
| 48 | { | ||
| 49 | 5785 | if (spare == TLS_OUT_OF_INDEXES) | |
| 50 | { | ||
| 51 | 5785 | spare = ::TlsAlloc(); | |
| 52 | 5788 | if (spare == TLS_OUT_OF_INDEXES && error != nullptr) | |
| 53 | { | ||
| 54 | 1 | *error = ::GetLastError(); | |
| 55 | } | ||
| 56 | } | ||
| 57 | 5788 | index = spare; | |
| 58 | } | ||
| 59 | 36053 | if (m_state.compare_exchange_weak( | |
| 60 | state, | ||
| 61 | 18026 | pack(owners_of(state) + 1, index), | |
| 62 | std::memory_order_acq_rel, | ||
| 63 | std::memory_order_acquire | ||
| 64 | )) | ||
| 65 | { | ||
| 66 | // A spare that lost to a published index was never visible to another thread. | ||
| 67 | 18021 | if (spare != TLS_OUT_OF_INDEXES && spare != index) | |
| 68 | { | ||
| 69 | 3 | (void)::TlsFree(spare); | |
| 70 | } | ||
| 71 | 18021 | return index; | |
| 72 | } | ||
| 73 | 6 | } | |
| 74 | } | ||
| 75 | |||
| 76 | /// Deregisters one owner. The last owner unpublishes the index, then frees it. | ||
| 77 | 17785 | void release() noexcept | |
| 78 | { | ||
| 79 | 35568 | std::uint64_t state = m_state.load(std::memory_order_acquire); | |
| 80 | for (;;) | ||
| 81 | { | ||
| 82 | 17880 | const std::uint32_t owners = owners_of(state); | |
| 83 | 17892 | if (owners == 0) | |
| 84 | { | ||
| 85 | ✗ | return; | |
| 86 | } | ||
| 87 | 17892 | const std::uint64_t next = owners == 1 ? UNOWNED : pack(owners - 1, index_of(state)); | |
| 88 | 35784 | if (m_state.compare_exchange_weak(state, next, std::memory_order_acq_rel, std::memory_order_acquire)) | |
| 89 | { | ||
| 90 | 17800 | if (owners == 1 && index_of(state) != TLS_OUT_OF_INDEXES) | |
| 91 | { | ||
| 92 | 5564 | (void)::TlsFree(index_of(state)); | |
| 93 | } | ||
| 94 | 17801 | return; | |
| 95 | } | ||
| 96 | 97 | } | |
| 97 | } | ||
| 98 | |||
| 99 | /// Returns the published index, or TLS_OUT_OF_INDEXES. | ||
| 100 | 65504206 | [[nodiscard]] DWORD index() const noexcept { return index_of(m_state.load(std::memory_order_acquire)); } | |
| 101 | |||
| 102 | /// Returns the registered owner count. | ||
| 103 | 2 | [[nodiscard]] std::uint32_t owners() const noexcept | |
| 104 | { | ||
| 105 | 4 | return owners_of(m_state.load(std::memory_order_acquire)); | |
| 106 | } | ||
| 107 | |||
| 108 | private: | ||
| 109 | 30358 | [[nodiscard]] static constexpr std::uint64_t pack(std::uint32_t owners, DWORD index) noexcept | |
| 110 | { | ||
| 111 | 30358 | return (static_cast<std::uint64_t>(owners) << 32) | index; | |
| 112 | } | ||
| 113 | |||
| 114 | 32779272 | [[nodiscard]] static constexpr DWORD index_of(std::uint64_t state) noexcept | |
| 115 | { | ||
| 116 | 32779272 | return static_cast<DWORD>(state & 0xFFFFFFFFULL); | |
| 117 | } | ||
| 118 | |||
| 119 | 35926 | [[nodiscard]] static constexpr std::uint32_t owners_of(std::uint64_t state) noexcept | |
| 120 | { | ||
| 121 | 35926 | return static_cast<std::uint32_t>(state >> 32); | |
| 122 | } | ||
| 123 | |||
| 124 | /// No owner and no published index: the high word is zero and the low word is TLS_OUT_OF_INDEXES. | ||
| 125 | static constexpr std::uint64_t UNOWNED = TLS_OUT_OF_INDEXES; | ||
| 126 | static_assert(std::atomic<std::uint64_t>::is_always_lock_free); | ||
| 127 | |||
| 128 | std::atomic<std::uint64_t> m_state{UNOWNED}; | ||
| 129 | }; | ||
| 130 | } // namespace DetourModKit::detail | ||
| 131 | |||
| 132 | #endif // DETOURMODKIT_INTERNAL_SHARED_TLS_INDEX_HPP | ||
| 133 |