Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
seqlock.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <atomic>
7#include <array>
8#include <cstddef>
9#include <cstdint>
10#include <cstring>
11#include <type_traits>
12
13namespace pulp::runtime {
14
15#ifdef _MSC_VER
16#pragma warning(push)
17#pragma warning(disable : 4324) // intentional cache-line alignment for lock-free snapshots
18#endif
19
41template <typename T>
42class SeqLock {
43 static_assert(std::is_trivially_copyable_v<T>,
44 "SeqLock<T> requires T to be trivially copyable");
45
46public:
48 const T initial{};
49 store_payload(initial);
50 }
51
53 explicit SeqLock(const T& initial) {
54 store_payload(initial);
55 }
56
59 void write(const T& value) {
60 // The opening increment needs acquire semantics as well as release:
61 // on weakly ordered CPUs that prevents the upcoming data copy from
62 // being observed before readers can see the odd "writer active" flag.
63 seq_.fetch_add(1, std::memory_order_acq_rel); // odd = writing
64 store_payload(value);
65 seq_.fetch_add(1, std::memory_order_release); // even = complete
66 }
67
71 T read() const {
72 T result;
73 for (;;) {
74 unsigned seq0 = seq_.load(std::memory_order_acquire);
75 if (seq0 & 1) continue; // writer in progress, spin
76 load_payload(result);
77 std::atomic_thread_fence(std::memory_order_acquire);
78 unsigned seq1 = seq_.load(std::memory_order_acquire);
79 if (seq0 == seq1) return result;
80 }
81 }
82
83private:
84 using PayloadWord = std::uintptr_t;
85 static constexpr std::size_t kWordSize = sizeof(PayloadWord);
86 static constexpr std::size_t kWordCount = sizeof(T) / kWordSize;
87 static constexpr std::size_t kTailBytes = sizeof(T) % kWordSize;
88
89 static_assert(std::atomic<PayloadWord>::is_always_lock_free,
90 "SeqLock payload word atomics must be lock-free");
91 static_assert(std::atomic<std::uint8_t>::is_always_lock_free,
92 "SeqLock payload byte atomics must be lock-free");
93
94 void store_payload(const T& value) noexcept {
95 const auto* bytes = reinterpret_cast<const std::uint8_t*>(&value);
96 for (std::size_t i = 0; i < kWordCount; ++i) {
97 PayloadWord word = 0;
98 std::memcpy(&word, bytes + i * kWordSize, kWordSize);
99 payload_words_[i].store(word, std::memory_order_relaxed);
100 }
101 for (std::size_t i = 0; i < kTailBytes; ++i) {
102 payload_tail_[i].store(bytes[kWordCount * kWordSize + i],
103 std::memory_order_relaxed);
104 }
105 }
106
107 void load_payload(T& result) const noexcept {
108 auto* bytes = reinterpret_cast<std::uint8_t*>(&result);
109 for (std::size_t i = 0; i < kWordCount; ++i) {
110 const PayloadWord word =
111 payload_words_[i].load(std::memory_order_relaxed);
112 std::memcpy(bytes + i * kWordSize, &word, kWordSize);
113 }
114 for (std::size_t i = 0; i < kTailBytes; ++i) {
115 bytes[kWordCount * kWordSize + i] =
116 payload_tail_[i].load(std::memory_order_relaxed);
117 }
118 }
119
120 alignas(64) std::array<std::atomic<PayloadWord>, kWordCount> payload_words_{};
121 std::array<std::atomic<std::uint8_t>, kTailBytes> payload_tail_{};
122 std::atomic<unsigned> seq_{0};
123};
124
125#ifdef _MSC_VER
126#pragma warning(pop)
127#endif
128
129} // namespace pulp::runtime
SeqLock(const T &initial)
Definition seqlock.hpp:53
SeqLock()
Definition seqlock.hpp:47
void write(const T &value)
Publish a new value.
Definition seqlock.hpp:59
T read() const
Read a consistent snapshot.
Definition seqlock.hpp:71
Definition generated_event_source.hpp:33
@ i
Definition formant_filter_bank.hpp:20