CIRCT 23.0.0git
Loading...
Searching...
No Matches
Utils.h
Go to the documentation of this file.
1//===- Utils.h - ESI runtime utility code -----------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// DO NOT EDIT!
10// This file is distributed as part of an ESI package. The source for this file
11// should always be modified within CIRCT.
12//
13//===----------------------------------------------------------------------===//
14
15// NOLINTNEXTLINE(llvm-header-guard)
16#ifndef ESI_UTILS_H
17#define ESI_UTILS_H
18
19#include <atomic>
20#include <chrono>
21#include <condition_variable>
22#include <cstdint>
23#include <functional>
24#include <mutex>
25#include <optional>
26#include <queue>
27#include <string>
28#include <unordered_set>
29
30namespace esi {
31namespace utils {
32// Very basic base64 encoding.
33void encodeBase64(const void *data, size_t size, std::string &out);
34
35/// C++'s stdlib doesn't have a hash_combine function. This is a simple one.
36inline size_t hash_combine(size_t h1, size_t h2) {
37 return h1 + 0x9e3779b9 + (h2 << 6) + (h2 >> 2);
38}
39
40/// Thread safe queue. Just wraps std::queue protected with a lock. Long term,
41/// we need to avoid copying data. It has a lot of data copies currently.
42///
43/// Push operations notify a built-in condition variable so consumers can
44/// block on `waitPop()` rather than poll. `requestShutdown()` permanently
45/// wakes any waiters with `nullopt`, which is how owners cleanly retire a
46/// drainer thread.
47template <typename T>
48class TSQueue {
49 using Lock = std::lock_guard<std::mutex>;
50
51 /// The queue and its mutex.
52 mutable std::mutex qM;
53 std::queue<T> q;
54
55 /// CV signalled by `push` and `requestShutdown`. Waiters re-check the
56 /// predicate under `qM` so missed notifications are recovered.
57 std::condition_variable cv;
58 bool shutdown = false;
59
60 /// Optional external notifier invoked after `push` releases the queue lock.
61 /// Set once at construction; never mutated afterwards.
62 const std::function<void()> notifier;
63
64 /// A mutex to ensure that only one 'pop' operation is happening at a time. It
65 /// is critical that locks be obtained on this and `qM` same order in both pop
66 /// methods. This lock should be obtained first since one of the pop methods
67 /// must unlock `qM` then relock it.
68 std::mutex popM;
69
70public:
71 /// Default constructor: no external notifier.
72 TSQueue() = default;
73
74 /// Construct a queue that calls `notifier` after every successful `push`,
75 /// once the queue mutex has been released. Use this to ring an external
76 /// doorbell (e.g. a server-wide "ports with pending data" set) without
77 /// making every push site know about it. The notifier is fixed for the
78 /// lifetime of the queue — there is no setter, because changing it while
79 /// other threads are pushing would be a data race.
80 explicit TSQueue(std::function<void()> notifier)
81 : notifier(std::move(notifier)) {}
82
83 /// Push onto the queue. Wakes one `waitPop()` waiter (if any) and rings the
84 /// external notifier, if one was supplied to the constructor.
85 template <typename... E>
86 void push(E... t) {
87 {
88 Lock l(qM);
89 q.emplace(t...);
90 }
91 if (notifier)
92 notifier();
93 cv.notify_one();
94 }
95
96 /// Pop something off the queue but return nullopt if the queue is empty. Why
97 /// doesn't std::queue have anything like this?
98 std::optional<T> pop() {
99 Lock pl(popM);
100 Lock ql(qM);
101 if (q.size() == 0)
102 return std::nullopt;
103 auto t = q.front();
104 q.pop();
105 return t;
106 }
107
108 /// Block until an item is available or `requestShutdown()` is called.
109 /// Returns `nullopt` only in the shutdown case. Intended for single-consumer
110 /// drainer threads; do not interleave with `pop()` on the same queue.
111 std::optional<T> waitPop() {
112 std::unique_lock<std::mutex> l(qM);
113 cv.wait(l, [this] { return shutdown || !q.empty(); });
114 if (q.empty())
115 return std::nullopt;
116 auto t = q.front();
117 q.pop();
118 return t;
119 }
120
121 /// Call the callback for the front of the queue (if anything is there). Only
122 /// pop it off the queue if the callback returns true.
123 void pop(std::function<bool(const T &)> callback) {
124 // Since we need to unlock the mutex to call the callback, the queue
125 // could be pushed on to and its memory layout could thusly change,
126 // invalidating the reference returned by `.front()`. The easy solution here
127 // is to copy the data. TODO: Avoid copying the data.
128 Lock pl(popM);
129 T t;
130 {
131 Lock l(qM);
132 if (q.size() == 0)
133 return;
134 t = q.front();
135 }
136 if (callback(t)) {
137 Lock l(qM);
138 q.pop();
139 }
140 }
141
142 /// Is the queue empty?
143 bool empty() const {
144 Lock l(qM);
145 return q.empty();
146 }
147
148 /// Permanently retire the queue: wake every current and future `waitPop()`
149 /// with `nullopt` so consumer threads can exit cleanly. Pushes after this
150 /// still enqueue, but no one is expected to be waiting.
152 {
153 Lock l(qM);
154 shutdown = true;
155 }
156 cv.notify_all();
157 }
158
159 /// True once `requestShutdown()` has been called.
160 bool isShutdown() const {
161 Lock l(qM);
162 return shutdown;
163 }
164};
165
166/// Multi-producer / single-consumer dirty-set of channel ids, with CV-style
167/// blocking drain semantics. Producers call `markReady(id)`; the consumer
168/// thread calls `waitDrain()` to atomically swap out the current set.
169///
170/// Use this when many independent per-channel producers feed a single
171/// transport thread that needs to know which channels have work to do
172/// without polling each in turn. The CV pattern (lock-around-shutdown-set,
173/// notify-after-release, predicate captures both shutdown and !empty) is
174/// fiddly enough that centralising it here avoids subtle copy/paste bugs
175/// in each backend.
176template <typename ID = uint16_t>
178public:
179 /// Add `id` to the dirty set and wake the consumer (if any). Idempotent
180 /// w.r.t. an id that is already in the set.
181 void markReady(ID id) {
182 {
183 std::lock_guard<std::mutex> lock(mutex);
184 ready.insert(id);
185 }
186 cv.notify_one();
187 }
188
189 /// Block until either `requestShutdown()` is called or the set is
190 /// non-empty, then atomically swap the current set into `out`. With
191 /// `backoff` non-nullopt, returns after the timeout even if neither
192 /// condition holds (useful when the caller maintains its own retry set
193 /// it wants to re-process periodically). Returns `false` once shutdown
194 /// has been signalled, so consumer loops can write
195 /// `while (set.waitDrain(ids, backoff)) { ... }`.
196 bool waitDrain(std::unordered_set<ID> &out,
197 std::optional<std::chrono::milliseconds> backoff = {}) {
198 std::unique_lock<std::mutex> lock(mutex);
199 auto pred = [&] { return shutdown || !ready.empty(); };
200 if (backoff)
201 cv.wait_for(lock, *backoff, pred);
202 else
203 cv.wait(lock, pred);
204 out.swap(ready);
205 return !shutdown;
206 }
207
208 /// Signal a clean shutdown: wakes every current and future `waitDrain`
209 /// caller, which will then observe `false`. Idempotent.
211 {
212 std::lock_guard<std::mutex> lock(mutex);
213 shutdown = true;
214 }
215 cv.notify_all();
216 }
217
218 /// True once `requestShutdown()` has been called.
219 bool isShutdown() const {
220 std::lock_guard<std::mutex> lock(mutex);
221 return shutdown;
222 }
223
224private:
225 mutable std::mutex mutex;
226 std::condition_variable cv;
227 std::unordered_set<ID> ready;
228 bool shutdown = false;
229};
230
231/// Compute ceil(bits/8).
232inline uint64_t bitsToBytes(uint64_t bits) { return (bits + 7) / 8; }
233
234} // namespace utils
235} // namespace esi
236
237#endif // ESI_UTILS_H
Multi-producer / single-consumer dirty-set of channel ids, with CV-style blocking drain semantics.
Definition Utils.h:177
void requestShutdown()
Signal a clean shutdown: wakes every current and future waitDrain caller, which will then observe fal...
Definition Utils.h:210
bool isShutdown() const
True once requestShutdown() has been called.
Definition Utils.h:219
std::condition_variable cv
Definition Utils.h:226
bool waitDrain(std::unordered_set< ID > &out, std::optional< std::chrono::milliseconds > backoff={})
Block until either requestShutdown() is called or the set is non-empty, then atomically swap the curr...
Definition Utils.h:196
void markReady(ID id)
Add id to the dirty set and wake the consumer (if any).
Definition Utils.h:181
std::mutex mutex
Definition Utils.h:225
std::unordered_set< ID > ready
Definition Utils.h:227
Thread safe queue.
Definition Utils.h:48
std::mutex qM
The queue and its mutex.
Definition Utils.h:52
const std::function< void()> notifier
Optional external notifier invoked after push releases the queue lock.
Definition Utils.h:62
TSQueue()=default
Default constructor: no external notifier.
bool isShutdown() const
True once requestShutdown() has been called.
Definition Utils.h:160
TSQueue(std::function< void()> notifier)
Construct a queue that calls notifier after every successful push, once the queue mutex has been rele...
Definition Utils.h:80
std::mutex popM
A mutex to ensure that only one 'pop' operation is happening at a time.
Definition Utils.h:68
std::queue< T > q
Definition Utils.h:53
std::condition_variable cv
CV signalled by push and requestShutdown.
Definition Utils.h:57
bool empty() const
Is the queue empty?
Definition Utils.h:143
void push(E... t)
Push onto the queue.
Definition Utils.h:86
std::lock_guard< std::mutex > Lock
Definition Utils.h:49
void requestShutdown()
Permanently retire the queue: wake every current and future waitPop() with nullopt so consumer thread...
Definition Utils.h:151
void pop(std::function< bool(const T &)> callback)
Call the callback for the front of the queue (if anything is there).
Definition Utils.h:123
std::optional< T > waitPop()
Block until an item is available or requestShutdown() is called.
Definition Utils.h:111
std::optional< T > pop()
Pop something off the queue but return nullopt if the queue is empty.
Definition Utils.h:98
uint64_t bitsToBytes(uint64_t bits)
Compute ceil(bits/8).
Definition Utils.h:232
void encodeBase64(const void *data, size_t size, std::string &out)
Definition Utils.cpp:23
size_t hash_combine(size_t h1, size_t h2)
C++'s stdlib doesn't have a hash_combine function. This is a simple one.
Definition Utils.h:36
Definition esi.py:1