Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * This file is part of FFmpeg. | ||
3 | * | ||
4 | * FFmpeg is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU Lesser General Public | ||
6 | * License as published by the Free Software Foundation; either | ||
7 | * version 2.1 of the License, or (at your option) any later version. | ||
8 | * | ||
9 | * FFmpeg is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | * Lesser General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU Lesser General Public | ||
15 | * License along with FFmpeg; if not, write to the Free Software | ||
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
17 | */ | ||
18 | |||
19 | #include <stdatomic.h> | ||
20 | #include <stdint.h> | ||
21 | #include <string.h> | ||
22 | |||
23 | #include "refstruct.h" | ||
24 | |||
25 | #include "libavutil/avassert.h" | ||
26 | #include "libavutil/error.h" | ||
27 | #include "libavutil/macros.h" | ||
28 | #include "libavutil/mem.h" | ||
29 | #include "libavutil/mem_internal.h" | ||
30 | #include "libavutil/thread.h" | ||
31 | |||
32 | #ifndef REFSTRUCT_CHECKED | ||
33 | #ifndef ASSERT_LEVEL | ||
34 | #define ASSERT_LEVEL 0 | ||
35 | #endif | ||
36 | #define REFSTRUCT_CHECKED (ASSERT_LEVEL >= 1) | ||
37 | #endif | ||
38 | |||
39 | #if REFSTRUCT_CHECKED | ||
40 | #define ff_assert(cond) av_assert0(cond) | ||
41 | #else | ||
42 | #define ff_assert(cond) ((void)0) | ||
43 | #endif | ||
44 | |||
45 | #define REFSTRUCT_COOKIE AV_NE((uint64_t)MKBETAG('R', 'e', 'f', 'S') << 32 | MKBETAG('t', 'r', 'u', 'c'), \ | ||
46 | MKTAG('R', 'e', 'f', 'S') | (uint64_t)MKTAG('t', 'r', 'u', 'c') << 32) | ||
47 | |||
48 | #if __STDC_VERSION__ >= 201112L && !defined(_MSC_VER) | ||
49 | #define REFCOUNT_OFFSET FFALIGN(sizeof(RefCount), FFMAX(ALIGN_64, _Alignof(max_align_t))) | ||
50 | #else | ||
51 | #define REFCOUNT_OFFSET FFALIGN(sizeof(RefCount), ALIGN_64) | ||
52 | #endif | ||
53 | |||
54 | typedef struct RefCount { | ||
55 | /** | ||
56 | * An uintptr_t is big enough to hold the address of every reference, | ||
57 | * so no overflow can happen when incrementing the refcount as long as | ||
58 | * the user does not throw away references. | ||
59 | */ | ||
60 | atomic_uintptr_t refcount; | ||
61 | FFRefStructOpaque opaque; | ||
62 | void (*free_cb)(FFRefStructOpaque opaque, void *obj); | ||
63 | void (*free)(void *ref); | ||
64 | |||
65 | #if REFSTRUCT_CHECKED | ||
66 | uint64_t cookie; | ||
67 | #endif | ||
68 | } RefCount; | ||
69 | |||
70 | 4167743 | static RefCount *get_refcount(void *obj) | |
71 | { | ||
72 | 4167743 | RefCount *ref = (RefCount*)((char*)obj - REFCOUNT_OFFSET); | |
73 | ff_assert(ref->cookie == REFSTRUCT_COOKIE); | ||
74 | 4167743 | return ref; | |
75 | } | ||
76 | |||
77 | 307 | static const RefCount *cget_refcount(const void *obj) | |
78 | { | ||
79 | 307 | const RefCount *ref = (const RefCount*)((const char*)obj - REFCOUNT_OFFSET); | |
80 | ff_assert(ref->cookie == REFSTRUCT_COOKIE); | ||
81 | 307 | return ref; | |
82 | } | ||
83 | |||
84 | 2933297 | static void *get_userdata(void *buf) | |
85 | { | ||
86 | 2933297 | return (char*)buf + REFCOUNT_OFFSET; | |
87 | } | ||
88 | |||
89 | 564076 | static void refcount_init(RefCount *ref, FFRefStructOpaque opaque, | |
90 | void (*free_cb)(FFRefStructOpaque opaque, void *obj)) | ||
91 | { | ||
92 | 564076 | atomic_init(&ref->refcount, 1); | |
93 | 564076 | ref->opaque = opaque; | |
94 | 564076 | ref->free_cb = free_cb; | |
95 | 564076 | ref->free = av_free; | |
96 | |||
97 | #if REFSTRUCT_CHECKED | ||
98 | ref->cookie = REFSTRUCT_COOKIE; | ||
99 | #endif | ||
100 | 564076 | } | |
101 | |||
102 | 564076 | void *ff_refstruct_alloc_ext_c(size_t size, unsigned flags, FFRefStructOpaque opaque, | |
103 | void (*free_cb)(FFRefStructOpaque opaque, void *obj)) | ||
104 | { | ||
105 | void *buf, *obj; | ||
106 | |||
107 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 564076 times.
|
564076 | if (size > SIZE_MAX - REFCOUNT_OFFSET) |
108 | ✗ | return NULL; | |
109 | 564076 | buf = av_malloc(size + REFCOUNT_OFFSET); | |
110 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 564076 times.
|
564076 | if (!buf) |
111 | ✗ | return NULL; | |
112 | 564076 | refcount_init(buf, opaque, free_cb); | |
113 | 564076 | obj = get_userdata(buf); | |
114 |
2/2✓ Branch 0 taken 560328 times.
✓ Branch 1 taken 3748 times.
|
564076 | if (!(flags & FF_REFSTRUCT_FLAG_NO_ZEROING)) |
115 | 560328 | memset(obj, 0, size); | |
116 | |||
117 | 564076 | return obj; | |
118 | } | ||
119 | |||
120 | 6407726 | void ff_refstruct_unref(void *objp) | |
121 | { | ||
122 | void *obj; | ||
123 | RefCount *ref; | ||
124 | |||
125 | 6407726 | memcpy(&obj, objp, sizeof(obj)); | |
126 |
2/2✓ Branch 0 taken 3100913 times.
✓ Branch 1 taken 3306813 times.
|
6407726 | if (!obj) |
127 | 3100913 | return; | |
128 | 3306813 | memcpy(objp, &(void *){ NULL }, sizeof(obj)); | |
129 | |||
130 | 3306813 | ref = get_refcount(obj); | |
131 |
2/2✓ Branch 0 taken 2917550 times.
✓ Branch 1 taken 389263 times.
|
3306813 | if (atomic_fetch_sub_explicit(&ref->refcount, 1, memory_order_acq_rel) == 1) { |
132 |
2/2✓ Branch 0 taken 142062 times.
✓ Branch 1 taken 2775488 times.
|
2917550 | if (ref->free_cb) |
133 | 142062 | ref->free_cb(ref->opaque, obj); | |
134 | 2917550 | ref->free(ref); | |
135 | } | ||
136 | |||
137 | 3306813 | return; | |
138 | } | ||
139 | |||
140 | 40386 | void *ff_refstruct_ref(void *obj) | |
141 | { | ||
142 | 40386 | RefCount *ref = get_refcount(obj); | |
143 | |||
144 | 40386 | atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed); | |
145 | |||
146 | 40386 | return obj; | |
147 | } | ||
148 | |||
149 | 348877 | const void *ff_refstruct_ref_c(const void *obj) | |
150 | { | ||
151 | /* Casting const away here is fine, as it is only supposed | ||
152 | * to apply to the user's data and not our bookkeeping data. */ | ||
153 | 348877 | RefCount *ref = get_refcount((void*)obj); | |
154 | |||
155 | 348877 | atomic_fetch_add_explicit(&ref->refcount, 1, memory_order_relaxed); | |
156 | |||
157 | 348877 | return obj; | |
158 | } | ||
159 | |||
160 | 438442 | void ff_refstruct_replace(void *dstp, const void *src) | |
161 | { | ||
162 | const void *dst; | ||
163 | 438442 | memcpy(&dst, dstp, sizeof(dst)); | |
164 | |||
165 |
2/2✓ Branch 0 taken 146676 times.
✓ Branch 1 taken 291766 times.
|
438442 | if (src == dst) |
166 | 146676 | return; | |
167 | 291766 | ff_refstruct_unref(dstp); | |
168 |
1/2✓ Branch 0 taken 291766 times.
✗ Branch 1 not taken.
|
291766 | if (src) { |
169 | 291766 | dst = ff_refstruct_ref_c(src); | |
170 | 291766 | memcpy(dstp, &dst, sizeof(dst)); | |
171 | } | ||
172 | } | ||
173 | |||
174 | 307 | int ff_refstruct_exclusive(const void *obj) | |
175 | { | ||
176 | 307 | const RefCount *ref = cget_refcount(obj); | |
177 | /* Casting const away here is safe, because it is a load. | ||
178 | * It is necessary because atomic_load_explicit() does not | ||
179 | * accept const atomics in C11 (see also N1807). */ | ||
180 | 307 | return atomic_load_explicit((atomic_uintptr_t*)&ref->refcount, memory_order_acquire) == 1; | |
181 | } | ||
182 | |||
183 | struct FFRefStructPool { | ||
184 | size_t size; | ||
185 | FFRefStructOpaque opaque; | ||
186 | int (*init_cb)(FFRefStructOpaque opaque, void *obj); | ||
187 | void (*reset_cb)(FFRefStructOpaque opaque, void *obj); | ||
188 | void (*free_entry_cb)(FFRefStructOpaque opaque, void *obj); | ||
189 | void (*free_cb)(FFRefStructOpaque opaque); | ||
190 | |||
191 | int uninited; | ||
192 | unsigned entry_flags; | ||
193 | unsigned pool_flags; | ||
194 | |||
195 | /** The number of outstanding entries not in available_entries. */ | ||
196 | atomic_uintptr_t refcount; | ||
197 | /** | ||
198 | * This is a linked list of available entries; | ||
199 | * the RefCount's opaque pointer is used as next pointer | ||
200 | * for available entries. | ||
201 | * While the entries are in use, the opaque is a pointer | ||
202 | * to the corresponding FFRefStructPool. | ||
203 | */ | ||
204 | RefCount *available_entries; | ||
205 | AVMutex mutex; | ||
206 | }; | ||
207 | |||
208 | 11302 | static void pool_free(FFRefStructPool *pool) | |
209 | { | ||
210 | 11302 | ff_mutex_destroy(&pool->mutex); | |
211 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11302 times.
|
11302 | if (pool->free_cb) |
212 | ✗ | pool->free_cb(pool->opaque); | |
213 | 11302 | av_free(get_refcount(pool)); | |
214 | 11302 | } | |
215 | |||
216 | 449063 | static void pool_free_entry(FFRefStructPool *pool, RefCount *ref) | |
217 | { | ||
218 |
2/2✓ Branch 0 taken 4445 times.
✓ Branch 1 taken 444618 times.
|
449063 | if (pool->free_entry_cb) |
219 | 4445 | pool->free_entry_cb(pool->opaque, get_userdata(ref)); | |
220 | 449063 | av_free(ref); | |
221 | 449063 | } | |
222 | |||
223 | 2802537 | static void pool_return_entry(void *ref_) | |
224 | { | ||
225 | 2802537 | RefCount *ref = ref_; | |
226 | 2802537 | FFRefStructPool *pool = ref->opaque.nc; | |
227 | |||
228 | 2802537 | ff_mutex_lock(&pool->mutex); | |
229 |
2/2✓ Branch 0 taken 2782401 times.
✓ Branch 1 taken 20136 times.
|
2802537 | if (!pool->uninited) { |
230 | 2782401 | ref->opaque.nc = pool->available_entries; | |
231 | 2782401 | pool->available_entries = ref; | |
232 | 2782401 | ref = NULL; | |
233 | } | ||
234 | 2802537 | ff_mutex_unlock(&pool->mutex); | |
235 | |||
236 |
2/2✓ Branch 0 taken 20136 times.
✓ Branch 1 taken 2782401 times.
|
2802537 | if (ref) |
237 | 20136 | pool_free_entry(pool, ref); | |
238 | |||
239 |
2/2✓ Branch 0 taken 5396 times.
✓ Branch 1 taken 2797141 times.
|
2802537 | if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1) |
240 | 5396 | pool_free(pool); | |
241 | 2802537 | } | |
242 | |||
243 | 48259 | static void pool_reset_entry(FFRefStructOpaque opaque, void *entry) | |
244 | { | ||
245 | 48259 | FFRefStructPool *pool = opaque.nc; | |
246 | |||
247 | 48259 | pool->reset_cb(pool->opaque, entry); | |
248 | 48259 | } | |
249 | |||
250 | 2802537 | static int refstruct_pool_get_ext(void *datap, FFRefStructPool *pool) | |
251 | { | ||
252 | 2802537 | void *ret = NULL; | |
253 | |||
254 | 2802537 | memcpy(datap, &(void *){ NULL }, sizeof(void*)); | |
255 | |||
256 | 2802537 | ff_mutex_lock(&pool->mutex); | |
257 | ff_assert(!pool->uninited); | ||
258 |
2/2✓ Branch 0 taken 2353474 times.
✓ Branch 1 taken 449063 times.
|
2802537 | if (pool->available_entries) { |
259 | 2353474 | RefCount *ref = pool->available_entries; | |
260 | 2353474 | ret = get_userdata(ref); | |
261 | 2353474 | pool->available_entries = ref->opaque.nc; | |
262 | 2353474 | ref->opaque.nc = pool; | |
263 | 2353474 | atomic_init(&ref->refcount, 1); | |
264 | } | ||
265 | 2802537 | ff_mutex_unlock(&pool->mutex); | |
266 | |||
267 |
2/2✓ Branch 0 taken 449063 times.
✓ Branch 1 taken 2353474 times.
|
2802537 | if (!ret) { |
268 | RefCount *ref; | ||
269 | 449063 | ret = ff_refstruct_alloc_ext(pool->size, pool->entry_flags, pool, | |
270 |
2/2✓ Branch 0 taken 4445 times.
✓ Branch 1 taken 444618 times.
|
449063 | pool->reset_cb ? pool_reset_entry : NULL); |
271 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 449063 times.
|
449063 | if (!ret) |
272 | ✗ | return AVERROR(ENOMEM); | |
273 | 449063 | ref = get_refcount(ret); | |
274 | 449063 | ref->free = pool_return_entry; | |
275 |
2/2✓ Branch 0 taken 4445 times.
✓ Branch 1 taken 444618 times.
|
449063 | if (pool->init_cb) { |
276 | 4445 | int err = pool->init_cb(pool->opaque, ret); | |
277 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4445 times.
|
4445 | if (err < 0) { |
278 | ✗ | if (pool->pool_flags & FF_REFSTRUCT_POOL_FLAG_RESET_ON_INIT_ERROR) | |
279 | ✗ | pool->reset_cb(pool->opaque, ret); | |
280 | ✗ | if (pool->pool_flags & FF_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR) | |
281 | ✗ | pool->free_entry_cb(pool->opaque, ret); | |
282 | ✗ | av_free(ref); | |
283 | ✗ | return err; | |
284 | } | ||
285 | } | ||
286 | } | ||
287 | 2802537 | atomic_fetch_add_explicit(&pool->refcount, 1, memory_order_relaxed); | |
288 | |||
289 |
2/2✓ Branch 0 taken 41291 times.
✓ Branch 1 taken 2761246 times.
|
2802537 | if (pool->pool_flags & FF_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME) |
290 | 41291 | memset(ret, 0, pool->size); | |
291 | |||
292 | 2802537 | memcpy(datap, &ret, sizeof(ret)); | |
293 | |||
294 | 2802537 | return 0; | |
295 | } | ||
296 | |||
297 | 2802537 | void *ff_refstruct_pool_get(FFRefStructPool *pool) | |
298 | { | ||
299 | void *ret; | ||
300 | 2802537 | refstruct_pool_get_ext(&ret, pool); | |
301 | 2802537 | return ret; | |
302 | } | ||
303 | |||
304 | /** | ||
305 | * Hint: The content of pool_unref() and refstruct_pool_uninit() | ||
306 | * could currently be merged; they are only separate functions | ||
307 | * in case we would ever introduce weak references. | ||
308 | */ | ||
309 | 11302 | static void pool_unref(void *ref) | |
310 | { | ||
311 | 11302 | FFRefStructPool *pool = get_userdata(ref); | |
312 |
2/2✓ Branch 0 taken 5906 times.
✓ Branch 1 taken 5396 times.
|
11302 | if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1) |
313 | 5906 | pool_free(pool); | |
314 | 11302 | } | |
315 | |||
316 | 11302 | static void refstruct_pool_uninit(FFRefStructOpaque unused, void *obj) | |
317 | { | ||
318 | 11302 | FFRefStructPool *pool = obj; | |
319 | RefCount *entry; | ||
320 | |||
321 | 11302 | ff_mutex_lock(&pool->mutex); | |
322 | ff_assert(!pool->uninited); | ||
323 | 11302 | pool->uninited = 1; | |
324 | 11302 | entry = pool->available_entries; | |
325 | 11302 | pool->available_entries = NULL; | |
326 | 11302 | ff_mutex_unlock(&pool->mutex); | |
327 | |||
328 |
2/2✓ Branch 0 taken 428927 times.
✓ Branch 1 taken 11302 times.
|
440229 | while (entry) { |
329 | 428927 | void *next = entry->opaque.nc; | |
330 | 428927 | pool_free_entry(pool, entry); | |
331 | 428927 | entry = next; | |
332 | } | ||
333 | 11302 | } | |
334 | |||
335 | 8696 | FFRefStructPool *ff_refstruct_pool_alloc(size_t size, unsigned flags) | |
336 | { | ||
337 | 8696 | return ff_refstruct_pool_alloc_ext(size, flags, NULL, NULL, NULL, NULL, NULL); | |
338 | } | ||
339 | |||
340 | 11302 | FFRefStructPool *ff_refstruct_pool_alloc_ext_c(size_t size, unsigned flags, | |
341 | FFRefStructOpaque opaque, | ||
342 | int (*init_cb)(FFRefStructOpaque opaque, void *obj), | ||
343 | void (*reset_cb)(FFRefStructOpaque opaque, void *obj), | ||
344 | void (*free_entry_cb)(FFRefStructOpaque opaque, void *obj), | ||
345 | void (*free_cb)(FFRefStructOpaque opaque)) | ||
346 | { | ||
347 | 11302 | FFRefStructPool *pool = ff_refstruct_alloc_ext(sizeof(*pool), 0, NULL, | |
348 | refstruct_pool_uninit); | ||
349 | int err; | ||
350 | |||
351 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11302 times.
|
11302 | if (!pool) |
352 | ✗ | return NULL; | |
353 | 11302 | get_refcount(pool)->free = pool_unref; | |
354 | |||
355 | 11302 | pool->size = size; | |
356 | 11302 | pool->opaque = opaque; | |
357 | 11302 | pool->init_cb = init_cb; | |
358 | 11302 | pool->reset_cb = reset_cb; | |
359 | 11302 | pool->free_entry_cb = free_entry_cb; | |
360 | 11302 | pool->free_cb = free_cb; | |
361 | #define COMMON_FLAGS FF_REFSTRUCT_POOL_FLAG_NO_ZEROING | ||
362 | 11302 | pool->entry_flags = flags & COMMON_FLAGS; | |
363 | // Filter out nonsense combinations to avoid checks later. | ||
364 |
2/2✓ Branch 0 taken 8696 times.
✓ Branch 1 taken 2606 times.
|
11302 | if (!pool->reset_cb) |
365 | 8696 | flags &= ~FF_REFSTRUCT_POOL_FLAG_RESET_ON_INIT_ERROR; | |
366 |
2/2✓ Branch 0 taken 8696 times.
✓ Branch 1 taken 2606 times.
|
11302 | if (!pool->free_entry_cb) |
367 | 8696 | flags &= ~FF_REFSTRUCT_POOL_FLAG_FREE_ON_INIT_ERROR; | |
368 | 11302 | pool->pool_flags = flags; | |
369 | |||
370 |
2/2✓ Branch 0 taken 1524 times.
✓ Branch 1 taken 9778 times.
|
11302 | if (flags & FF_REFSTRUCT_POOL_FLAG_ZERO_EVERY_TIME) { |
371 | // We will zero the buffer before every use, so zeroing | ||
372 | // upon allocating the buffer is unnecessary. | ||
373 | 1524 | pool->entry_flags |= FF_REFSTRUCT_FLAG_NO_ZEROING; | |
374 | } | ||
375 | |||
376 | 11302 | atomic_init(&pool->refcount, 1); | |
377 | |||
378 | 11302 | err = ff_mutex_init(&pool->mutex, NULL); | |
379 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11302 times.
|
11302 | if (err) { |
380 | // Don't call ff_refstruct_uninit() on pool, as it hasn't been properly | ||
381 | // set up and is just a POD right now. | ||
382 | ✗ | av_free(get_refcount(pool)); | |
383 | ✗ | return NULL; | |
384 | } | ||
385 | 11302 | return pool; | |
386 | } | ||
387 |