1/**************************************************************************/
2/* safe_refcount.h */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#ifndef SAFE_REFCOUNT_H
32#define SAFE_REFCOUNT_H
33
34#include "core/typedefs.h"
35
36#ifdef DEV_ENABLED
37#include "core/error/error_macros.h"
38#endif
39
40#include <atomic>
41#include <type_traits>
42
43// Design goals for these classes:
44// - No automatic conversions or arithmetic operators,
45// to keep explicit the use of atomics everywhere.
46// - Using acquire-release semantics, even to set the first value.
47// The first value may be set relaxedly in many cases, but adding the distinction
48// between relaxed and unrelaxed operation to the interface would make it needlessly
49// flexible. There's negligible waste in having release semantics for the initial
50// value and, as an important benefit, you can be sure the value is properly synchronized
51// even with threads that are already running.
52
53// These are used in very specific areas of the engine where it's critical that these guarantees are held
54#define SAFE_NUMERIC_TYPE_PUN_GUARANTEES(m_type) \
55 static_assert(sizeof(SafeNumeric<m_type>) == sizeof(m_type)); \
56 static_assert(alignof(SafeNumeric<m_type>) == alignof(m_type)); \
57 static_assert(std::is_trivially_destructible<std::atomic<m_type>>::value);
58#define SAFE_FLAG_TYPE_PUN_GUARANTEES \
59 static_assert(sizeof(SafeFlag) == sizeof(bool)); \
60 static_assert(alignof(SafeFlag) == alignof(bool));
61
62template <class T>
63class SafeNumeric {
64 std::atomic<T> value;
65
66 static_assert(std::atomic<T>::is_always_lock_free);
67
68public:
69 _ALWAYS_INLINE_ void set(T p_value) {
70 value.store(p_value, std::memory_order_release);
71 }
72
73 _ALWAYS_INLINE_ T get() const {
74 return value.load(std::memory_order_acquire);
75 }
76
77 _ALWAYS_INLINE_ T increment() {
78 return value.fetch_add(1, std::memory_order_acq_rel) + 1;
79 }
80
81 // Returns the original value instead of the new one
82 _ALWAYS_INLINE_ T postincrement() {
83 return value.fetch_add(1, std::memory_order_acq_rel);
84 }
85
86 _ALWAYS_INLINE_ T decrement() {
87 return value.fetch_sub(1, std::memory_order_acq_rel) - 1;
88 }
89
90 // Returns the original value instead of the new one
91 _ALWAYS_INLINE_ T postdecrement() {
92 return value.fetch_sub(1, std::memory_order_acq_rel);
93 }
94
95 _ALWAYS_INLINE_ T add(T p_value) {
96 return value.fetch_add(p_value, std::memory_order_acq_rel) + p_value;
97 }
98
99 // Returns the original value instead of the new one
100 _ALWAYS_INLINE_ T postadd(T p_value) {
101 return value.fetch_add(p_value, std::memory_order_acq_rel);
102 }
103
104 _ALWAYS_INLINE_ T sub(T p_value) {
105 return value.fetch_sub(p_value, std::memory_order_acq_rel) - p_value;
106 }
107
108 _ALWAYS_INLINE_ T bit_or(T p_value) {
109 return value.fetch_or(p_value, std::memory_order_acq_rel);
110 }
111 _ALWAYS_INLINE_ T bit_and(T p_value) {
112 return value.fetch_and(p_value, std::memory_order_acq_rel);
113 }
114
115 _ALWAYS_INLINE_ T bit_xor(T p_value) {
116 return value.fetch_xor(p_value, std::memory_order_acq_rel);
117 }
118
119 // Returns the original value instead of the new one
120 _ALWAYS_INLINE_ T postsub(T p_value) {
121 return value.fetch_sub(p_value, std::memory_order_acq_rel);
122 }
123
124 _ALWAYS_INLINE_ T exchange_if_greater(T p_value) {
125 while (true) {
126 T tmp = value.load(std::memory_order_acquire);
127 if (tmp >= p_value) {
128 return tmp; // already greater, or equal
129 }
130
131 if (value.compare_exchange_weak(tmp, p_value, std::memory_order_acq_rel)) {
132 return p_value;
133 }
134 }
135 }
136
137 _ALWAYS_INLINE_ T conditional_increment() {
138 while (true) {
139 T c = value.load(std::memory_order_acquire);
140 if (c == 0) {
141 return 0;
142 }
143 if (value.compare_exchange_weak(c, c + 1, std::memory_order_acq_rel)) {
144 return c + 1;
145 }
146 }
147 }
148
149 _ALWAYS_INLINE_ explicit SafeNumeric<T>(T p_value = static_cast<T>(0)) {
150 set(p_value);
151 }
152};
153
154class SafeFlag {
155 std::atomic_bool flag;
156
157 static_assert(std::atomic_bool::is_always_lock_free);
158
159public:
160 _ALWAYS_INLINE_ bool is_set() const {
161 return flag.load(std::memory_order_acquire);
162 }
163
164 _ALWAYS_INLINE_ void set() {
165 flag.store(true, std::memory_order_release);
166 }
167
168 _ALWAYS_INLINE_ void clear() {
169 flag.store(false, std::memory_order_release);
170 }
171
172 _ALWAYS_INLINE_ void set_to(bool p_value) {
173 flag.store(p_value, std::memory_order_release);
174 }
175
176 _ALWAYS_INLINE_ explicit SafeFlag(bool p_value = false) {
177 set_to(p_value);
178 }
179};
180
181class SafeRefCount {
182 SafeNumeric<uint32_t> count;
183
184#ifdef DEV_ENABLED
185 _ALWAYS_INLINE_ void _check_unref_sanity() {
186 // This won't catch every misuse, but it's better than nothing.
187 CRASH_COND_MSG(count.get() == 0,
188 "Trying to unreference a SafeRefCount which is already zero is wrong and a symptom of it being misused.\n"
189 "Upon a SafeRefCount reaching zero any object whose lifetime is tied to it, as well as the ref count itself, must be destroyed.\n"
190 "Moreover, to guarantee that, no multiple threads should be racing to do the final unreferencing to zero.");
191 }
192#endif
193
194public:
195 _ALWAYS_INLINE_ bool ref() { // true on success
196 return count.conditional_increment() != 0;
197 }
198
199 _ALWAYS_INLINE_ uint32_t refval() { // none-zero on success
200 return count.conditional_increment();
201 }
202
203 _ALWAYS_INLINE_ bool unref() { // true if must be disposed of
204#ifdef DEV_ENABLED
205 _check_unref_sanity();
206#endif
207 return count.decrement() == 0;
208 }
209
210 _ALWAYS_INLINE_ uint32_t unrefval() { // 0 if must be disposed of
211#ifdef DEV_ENABLED
212 _check_unref_sanity();
213#endif
214 return count.decrement();
215 }
216
217 _ALWAYS_INLINE_ uint32_t get() const {
218 return count.get();
219 }
220
221 _ALWAYS_INLINE_ void init(uint32_t p_value = 1) {
222 count.set(p_value);
223 }
224};
225
226#endif // SAFE_REFCOUNT_H
227