/proc/self/cwd/common/internal/reference_count.h
Line | Count | Source |
1 | | // Copyright 2023 Google LLC |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | // This header contains primitives for reference counting, roughly equivalent to |
16 | | // the primitives used to implement `std::shared_ptr`. These primitives should |
17 | | // not be used directly in most cases, instead `cel::Shared` should be |
18 | | // used instead. |
19 | | |
20 | | #ifndef THIRD_PARTY_CEL_CPP_COMMON_INTERNAL_REFERENCE_COUNT_H_ |
21 | | #define THIRD_PARTY_CEL_CPP_COMMON_INTERNAL_REFERENCE_COUNT_H_ |
22 | | |
23 | | #include <atomic> |
24 | | #include <cstdint> |
25 | | #include <new> |
26 | | #include <string> |
27 | | #include <type_traits> |
28 | | #include <utility> |
29 | | |
30 | | #include "absl/base/attributes.h" |
31 | | #include "absl/base/nullability.h" |
32 | | #include "absl/base/optimization.h" |
33 | | #include "absl/log/absl_check.h" |
34 | | #include "absl/strings/string_view.h" |
35 | | #include "common/data.h" |
36 | | #include "google/protobuf/arena.h" |
37 | | #include "google/protobuf/message_lite.h" |
38 | | |
39 | | namespace cel::common_internal { |
40 | | |
41 | | struct AdoptRef final { |
42 | | explicit AdoptRef() = default; |
43 | | }; |
44 | | |
45 | | inline constexpr AdoptRef kAdoptRef{}; |
46 | | |
47 | | class ReferenceCount; |
48 | | struct ReferenceCountFromThis; |
49 | | |
50 | | void SetReferenceCountForThat(ReferenceCountFromThis& that, |
51 | | ReferenceCount* absl_nullable refcount); |
52 | | |
53 | | ReferenceCount* absl_nullable GetReferenceCountForThat( |
54 | | const ReferenceCountFromThis& that); |
55 | | |
56 | | // `ReferenceCountFromThis` is similar to `std::enable_shared_from_this`. It |
57 | | // allows the derived object to inspect its own reference count. It should not |
58 | | // be used directly, but should be used through |
59 | | // `cel::EnableManagedMemoryFromThis`. |
60 | | struct ReferenceCountFromThis { |
61 | | private: |
62 | | friend void SetReferenceCountForThat(ReferenceCountFromThis& that, |
63 | | ReferenceCount* absl_nullable refcount); |
64 | | friend ReferenceCount* absl_nullable GetReferenceCountForThat( |
65 | | const ReferenceCountFromThis& that); |
66 | | |
67 | | static constexpr uintptr_t kNullPtr = uintptr_t{0}; |
68 | | static constexpr uintptr_t kSentinelPtr = ~kNullPtr; |
69 | | |
70 | | void* absl_nullable refcount = reinterpret_cast<void*>(kSentinelPtr); |
71 | | }; |
72 | | |
73 | | inline void SetReferenceCountForThat(ReferenceCountFromThis& that, |
74 | 0 | ReferenceCount* absl_nullable refcount) { |
75 | 0 | ABSL_DCHECK_EQ(that.refcount, |
76 | 0 | reinterpret_cast<void*>(ReferenceCountFromThis::kSentinelPtr)); |
77 | 0 | that.refcount = static_cast<void*>(refcount); |
78 | 0 | } |
79 | | |
80 | | inline ReferenceCount* absl_nullable GetReferenceCountForThat( |
81 | 0 | const ReferenceCountFromThis& that) { |
82 | 0 | ABSL_DCHECK_NE(that.refcount, |
83 | 0 | reinterpret_cast<void*>(ReferenceCountFromThis::kSentinelPtr)); |
84 | 0 | return static_cast<ReferenceCount*>(that.refcount); |
85 | 0 | } |
86 | | |
87 | | void StrongRef(const ReferenceCount& refcount) noexcept; |
88 | | |
89 | | void StrongRef(const ReferenceCount* absl_nullable refcount) noexcept; |
90 | | |
91 | | void StrongUnref(const ReferenceCount& refcount) noexcept; |
92 | | |
93 | | void StrongUnref(const ReferenceCount* absl_nullable refcount) noexcept; |
94 | | |
95 | | ABSL_MUST_USE_RESULT |
96 | | bool StrengthenRef(const ReferenceCount& refcount) noexcept; |
97 | | |
98 | | ABSL_MUST_USE_RESULT |
99 | | bool StrengthenRef(const ReferenceCount* absl_nullable refcount) noexcept; |
100 | | |
101 | | void WeakRef(const ReferenceCount& refcount) noexcept; |
102 | | |
103 | | void WeakRef(const ReferenceCount* absl_nullable refcount) noexcept; |
104 | | |
105 | | void WeakUnref(const ReferenceCount& refcount) noexcept; |
106 | | |
107 | | void WeakUnref(const ReferenceCount* absl_nullable refcount) noexcept; |
108 | | |
109 | | ABSL_MUST_USE_RESULT |
110 | | bool IsUniqueRef(const ReferenceCount& refcount) noexcept; |
111 | | |
112 | | ABSL_MUST_USE_RESULT |
113 | | bool IsUniqueRef(const ReferenceCount* absl_nullable refcount) noexcept; |
114 | | |
115 | | ABSL_MUST_USE_RESULT |
116 | | bool IsExpiredRef(const ReferenceCount& refcount) noexcept; |
117 | | |
118 | | ABSL_MUST_USE_RESULT |
119 | | bool IsExpiredRef(const ReferenceCount* absl_nullable refcount) noexcept; |
120 | | |
121 | | // `ReferenceCount` is similar to the control block used by `std::shared_ptr`. |
122 | | // It is not meant to be interacted with directly in most cases, instead |
123 | | // `cel::Shared` should be used. |
124 | | class alignas(8) ReferenceCount { |
125 | | public: |
126 | 5.46k | ReferenceCount() = default; |
127 | | |
128 | | ReferenceCount(const ReferenceCount&) = delete; |
129 | | ReferenceCount(ReferenceCount&&) = delete; |
130 | | ReferenceCount& operator=(const ReferenceCount&) = delete; |
131 | | ReferenceCount& operator=(ReferenceCount&&) = delete; |
132 | | |
133 | 5.46k | virtual ~ReferenceCount() = default; |
134 | | |
135 | | private: |
136 | | friend void StrongRef(const ReferenceCount& refcount) noexcept; |
137 | | friend void StrongUnref(const ReferenceCount& refcount) noexcept; |
138 | | friend bool StrengthenRef(const ReferenceCount& refcount) noexcept; |
139 | | friend void WeakRef(const ReferenceCount& refcount) noexcept; |
140 | | friend void WeakUnref(const ReferenceCount& refcount) noexcept; |
141 | | friend bool IsUniqueRef(const ReferenceCount& refcount) noexcept; |
142 | | friend bool IsExpiredRef(const ReferenceCount& refcount) noexcept; |
143 | | |
144 | | virtual void Finalize() noexcept = 0; |
145 | | |
146 | | virtual void Delete() noexcept = 0; |
147 | | |
148 | | mutable std::atomic<int32_t> strong_refcount_ = 1; |
149 | | mutable std::atomic<int32_t> weak_refcount_ = 1; |
150 | | }; |
151 | | |
152 | | // ReferenceCount and its derivations must be at least as aligned as |
153 | | // google::protobuf::Arena. This is a requirement for the pointer tagging defined in |
154 | | // common/internal/metadata.h. |
155 | | static_assert(alignof(ReferenceCount) >= alignof(google::protobuf::Arena)); |
156 | | |
157 | | // `ReferenceCounted` is a base class for classes which should be reference |
158 | | // counted. It provides default implementations for `Finalize()` and `Delete()`. |
159 | | class ReferenceCounted : public ReferenceCount { |
160 | | private: |
161 | 5.46k | void Finalize() noexcept override {} |
162 | | |
163 | 2 | void Delete() noexcept override { delete this; } |
164 | | }; |
165 | | |
166 | | // `EmplacedReferenceCount` adapts `T` to make it reference countable, by |
167 | | // storing `T` inside the reference count. This only works when `T` has not yet |
168 | | // been allocated. |
169 | | template <typename T> |
170 | | class EmplacedReferenceCount final : public ReferenceCounted { |
171 | | public: |
172 | | static_assert(std::is_destructible_v<T>, "T must be destructible"); |
173 | | static_assert(!std::is_reference_v<T>, "T must not be a reference"); |
174 | | static_assert(!std::is_volatile_v<T>, "T must not be volatile qualified"); |
175 | | static_assert(!std::is_const_v<T>, "T must not be const qualified"); |
176 | | static_assert(!std::is_array_v<T>, "T must not be an array"); |
177 | | |
178 | | template <typename... Args> |
179 | | explicit EmplacedReferenceCount(T*& value, Args&&... args) noexcept( |
180 | | std::is_nothrow_constructible_v<T, Args...>) { |
181 | | value = |
182 | | ::new (static_cast<void*>(&value_[0])) T(std::forward<Args>(args)...); |
183 | | } |
184 | | |
185 | | private: |
186 | | void Finalize() noexcept override { |
187 | | std::destroy_at(std::launder(reinterpret_cast<T*>(&value_[0]))); |
188 | | } |
189 | | |
190 | | // We store the instance of `T` in a char buffer and use placement new and |
191 | | // direct calls to the destructor. The reason for this is `Finalize()` is |
192 | | // called when the strong reference count hits 0. This allows us to destroy |
193 | | // our instance of `T` once we are no longer strongly reachable and deallocate |
194 | | // the memory once we are no longer weakly reachable. |
195 | | alignas(T) char value_[sizeof(T)]; |
196 | | }; |
197 | | |
198 | | // `DeletingReferenceCount` adapts `T` to make it reference countable, by taking |
199 | | // ownership of `T` and deleting it. This only works when `T` has already been |
200 | | // allocated and is to expensive to move or copy. |
201 | | template <typename T> |
202 | | class DeletingReferenceCount final : public ReferenceCounted { |
203 | | public: |
204 | | explicit DeletingReferenceCount(const T* absl_nonnull to_delete) noexcept |
205 | 0 | : to_delete_(to_delete) {} |
206 | | |
207 | | private: |
208 | 0 | void Finalize() noexcept override { delete to_delete_; } |
209 | | |
210 | | const T* absl_nonnull const to_delete_; |
211 | | }; |
212 | | |
213 | | extern template class DeletingReferenceCount<google::protobuf::MessageLite>; |
214 | | |
215 | | template <typename T> |
216 | | const ReferenceCount* absl_nonnull MakeDeletingReferenceCount( |
217 | | const T* absl_nonnull to_delete) { |
218 | | if constexpr (google::protobuf::Arena::is_arena_constructable<T>::value) { |
219 | | ABSL_DCHECK_EQ(to_delete->GetArena(), nullptr); |
220 | | } |
221 | | if constexpr (std::is_base_of_v<google::protobuf::MessageLite, T>) { |
222 | | return new DeletingReferenceCount<google::protobuf::MessageLite>(to_delete); |
223 | | } else { |
224 | | auto* refcount = new DeletingReferenceCount<T>(to_delete); |
225 | | if constexpr (std::is_base_of_v<Data, T>) { |
226 | | common_internal::SetDataReferenceCount(to_delete, refcount); |
227 | | } |
228 | | return refcount; |
229 | | } |
230 | | } |
231 | | |
232 | | template <typename T, typename... Args> |
233 | | std::pair<T* absl_nonnull, const ReferenceCount* absl_nonnull> |
234 | | MakeEmplacedReferenceCount(Args&&... args) { |
235 | | using U = std::remove_const_t<T>; |
236 | | U* pointer; |
237 | | auto* const refcount = |
238 | | new EmplacedReferenceCount<U>(pointer, std::forward<Args>(args)...); |
239 | | if constexpr (google::protobuf::Arena::is_arena_constructable<U>::value) { |
240 | | ABSL_DCHECK_EQ(pointer->GetArena(), nullptr); |
241 | | } |
242 | | if constexpr (std::is_base_of_v<Data, T>) { |
243 | | common_internal::SetDataReferenceCount(pointer, refcount); |
244 | | } |
245 | | return std::pair{static_cast<T* absl_nonnull>(pointer), |
246 | | static_cast<const ReferenceCount* absl_nonnull>(refcount)}; |
247 | | } |
248 | | |
249 | | template <typename T> |
250 | | class InlinedReferenceCount final : public ReferenceCounted { |
251 | | public: |
252 | | template <typename... Args> |
253 | | explicit InlinedReferenceCount(std::in_place_t, Args&&... args) |
254 | | : ReferenceCounted() { |
255 | | ::new (static_cast<void*>(value())) T(std::forward<Args>(args)...); |
256 | | } |
257 | | |
258 | | ABSL_ATTRIBUTE_ALWAYS_INLINE T* absl_nonnull value() { |
259 | | return reinterpret_cast<T*>(&value_[0]); |
260 | | } |
261 | | |
262 | | ABSL_ATTRIBUTE_ALWAYS_INLINE const T* absl_nonnull value() const { |
263 | | return reinterpret_cast<const T*>(&value_[0]); |
264 | | } |
265 | | |
266 | | private: |
267 | | void Finalize() noexcept override { value()->~T(); } |
268 | | |
269 | | // We store the instance of `T` in a char buffer and use placement new and |
270 | | // direct calls to the destructor. The reason for this is `Finalize()` is |
271 | | // called when the strong reference count hits 0. This allows us to destroy |
272 | | // our instance of `T` once we are no longer strongly reachable and deallocate |
273 | | // the memory once we are no longer weakly reachable. |
274 | | alignas(T) char value_[sizeof(T)]; |
275 | | }; |
276 | | |
277 | | template <typename T, typename... Args> |
278 | | std::pair<T* absl_nonnull, ReferenceCount* absl_nonnull> MakeReferenceCount( |
279 | | Args&&... args) { |
280 | | using U = std::remove_const_t<T>; |
281 | | auto* const refcount = |
282 | | new InlinedReferenceCount<U>(std::in_place, std::forward<Args>(args)...); |
283 | | auto* const pointer = refcount->value(); |
284 | | if constexpr (std::is_base_of_v<ReferenceCountFromThis, U>) { |
285 | | SetReferenceCountForThat(*pointer, refcount); |
286 | | } |
287 | | return std::make_pair(static_cast<T*>(pointer), |
288 | | static_cast<ReferenceCount*>(refcount)); |
289 | | } |
290 | | |
291 | 11.4k | inline void StrongRef(const ReferenceCount& refcount) noexcept { |
292 | 11.4k | const auto count = |
293 | 11.4k | refcount.strong_refcount_.fetch_add(1, std::memory_order_relaxed); |
294 | 11.4k | ABSL_DCHECK_GT(count, 0); |
295 | 11.4k | } |
296 | | |
297 | 12.2k | inline void StrongRef(const ReferenceCount* absl_nullable refcount) noexcept { |
298 | 12.2k | if (refcount != nullptr) { |
299 | 11.4k | StrongRef(*refcount); |
300 | 11.4k | } |
301 | 12.2k | } |
302 | | |
303 | 16.9k | inline void StrongUnref(const ReferenceCount& refcount) noexcept { |
304 | 16.9k | const auto count = |
305 | 16.9k | refcount.strong_refcount_.fetch_sub(1, std::memory_order_acq_rel); |
306 | 16.9k | ABSL_DCHECK_GT(count, 0); |
307 | 16.9k | ABSL_ASSUME(count > 0); |
308 | 16.9k | if (ABSL_PREDICT_FALSE(count == 1)) { |
309 | 5.46k | const_cast<ReferenceCount&>(refcount).Finalize(); |
310 | 5.46k | WeakUnref(refcount); |
311 | 5.46k | } |
312 | 16.9k | } |
313 | | |
314 | 76.4k | inline void StrongUnref(const ReferenceCount* absl_nullable refcount) noexcept { |
315 | 76.4k | if (refcount != nullptr) { |
316 | 16.9k | StrongUnref(*refcount); |
317 | 16.9k | } |
318 | 76.4k | } |
319 | | |
320 | | ABSL_MUST_USE_RESULT |
321 | 0 | inline bool StrengthenRef(const ReferenceCount& refcount) noexcept { |
322 | 0 | auto count = refcount.strong_refcount_.load(std::memory_order_relaxed); |
323 | 0 | while (true) { |
324 | 0 | ABSL_DCHECK_GE(count, 0); |
325 | 0 | ABSL_ASSUME(count >= 0); |
326 | 0 | if (count == 0) { |
327 | 0 | return false; |
328 | 0 | } |
329 | 0 | if (refcount.strong_refcount_.compare_exchange_weak( |
330 | 0 | count, count + 1, std::memory_order_release, |
331 | 0 | std::memory_order_relaxed)) { |
332 | 0 | return true; |
333 | 0 | } |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | | ABSL_MUST_USE_RESULT |
338 | | inline bool StrengthenRef( |
339 | 0 | const ReferenceCount* absl_nullable refcount) noexcept { |
340 | 0 | return refcount != nullptr ? StrengthenRef(*refcount) : false; |
341 | 0 | } |
342 | | |
343 | 0 | inline void WeakRef(const ReferenceCount& refcount) noexcept { |
344 | 0 | const auto count = |
345 | 0 | refcount.weak_refcount_.fetch_add(1, std::memory_order_relaxed); |
346 | 0 | ABSL_DCHECK_GT(count, 0); |
347 | 0 | } |
348 | | |
349 | 0 | inline void WeakRef(const ReferenceCount* absl_nullable refcount) noexcept { |
350 | 0 | if (refcount != nullptr) { |
351 | 0 | WeakRef(*refcount); |
352 | 0 | } |
353 | 0 | } |
354 | | |
355 | 5.46k | inline void WeakUnref(const ReferenceCount& refcount) noexcept { |
356 | 5.46k | const auto count = |
357 | 5.46k | refcount.weak_refcount_.fetch_sub(1, std::memory_order_acq_rel); |
358 | 5.46k | ABSL_DCHECK_GT(count, 0); |
359 | 5.46k | ABSL_ASSUME(count > 0); |
360 | 5.46k | if (ABSL_PREDICT_FALSE(count == 1)) { |
361 | 5.46k | const_cast<ReferenceCount&>(refcount).Delete(); |
362 | 5.46k | } |
363 | 5.46k | } |
364 | | |
365 | 0 | inline void WeakUnref(const ReferenceCount* absl_nullable refcount) noexcept { |
366 | 0 | if (refcount != nullptr) { |
367 | 0 | WeakUnref(*refcount); |
368 | 0 | } |
369 | 0 | } |
370 | | |
371 | | ABSL_MUST_USE_RESULT |
372 | 0 | inline bool IsUniqueRef(const ReferenceCount& refcount) noexcept { |
373 | 0 | const auto count = refcount.strong_refcount_.load(std::memory_order_acquire); |
374 | 0 | ABSL_DCHECK_GT(count, 0); |
375 | 0 | ABSL_ASSUME(count > 0); |
376 | 0 | return count == 1; |
377 | 0 | } |
378 | | |
379 | | ABSL_MUST_USE_RESULT |
380 | 0 | inline bool IsUniqueRef(const ReferenceCount* absl_nullable refcount) noexcept { |
381 | 0 | return refcount != nullptr ? IsUniqueRef(*refcount) : false; |
382 | 0 | } |
383 | | |
384 | | ABSL_MUST_USE_RESULT |
385 | 0 | inline bool IsExpiredRef(const ReferenceCount& refcount) noexcept { |
386 | 0 | const auto count = refcount.strong_refcount_.load(std::memory_order_acquire); |
387 | 0 | ABSL_DCHECK_GE(count, 0); |
388 | 0 | ABSL_ASSUME(count >= 0); |
389 | 0 | return count == 0; |
390 | 0 | } |
391 | | |
392 | | ABSL_MUST_USE_RESULT |
393 | | inline bool IsExpiredRef( |
394 | 0 | const ReferenceCount* absl_nullable refcount) noexcept { |
395 | 0 | return refcount != nullptr ? IsExpiredRef(*refcount) : false; |
396 | 0 | } |
397 | | |
398 | | std::pair<const ReferenceCount* absl_nonnull, absl::string_view> |
399 | | MakeReferenceCountedString(absl::string_view value); |
400 | | |
401 | | std::pair<const ReferenceCount* absl_nonnull, absl::string_view> |
402 | | MakeReferenceCountedString(std::string&& value); |
403 | | |
404 | | } // namespace cel::common_internal |
405 | | |
406 | | #endif // THIRD_PARTY_CEL_CPP_COMMON_INTERNAL_REFERENCE_COUNT_H_ |