Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo 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 : : * libplacebo 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
12 : : * GNU 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 libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include <stdio.h>
19 : : #include <locale.h>
20 : : #include <limits.h>
21 : :
22 : : #include "common.h"
23 : : #include "cache.h"
24 : : #include "log.h"
25 : : #include "pl_thread.h"
26 : :
27 : : const struct pl_cache_params pl_cache_default_params = {0};
28 : :
29 : : struct priv {
30 : : pl_log log;
31 : : pl_mutex lock;
32 : : PL_ARRAY(pl_cache_obj) objects;
33 : : size_t total_size;
34 : : };
35 : :
36 : 14 : int pl_cache_objects(pl_cache cache)
37 : : {
38 [ + - ]: 14 : if (!cache)
39 : : return 0;
40 : :
41 : 14 : struct priv *p = PL_PRIV(cache);
42 : 14 : pl_mutex_lock(&p->lock);
43 : 14 : int num = p->objects.num;
44 : 14 : pl_mutex_unlock(&p->lock);
45 : 14 : return num;
46 : : }
47 : :
48 : 12 : size_t pl_cache_size(pl_cache cache)
49 : : {
50 [ + - ]: 12 : if (!cache)
51 : : return 0;
52 : :
53 : 12 : struct priv *p = PL_PRIV(cache);
54 : 12 : pl_mutex_lock(&p->lock);
55 : 12 : size_t size = p->total_size;
56 : 12 : pl_mutex_unlock(&p->lock);
57 : 12 : return size;
58 : : }
59 : :
60 : 5 : pl_cache pl_cache_create(const struct pl_cache_params *params)
61 : : {
62 : 5 : struct pl_cache_t *cache = pl_zalloc_obj(NULL, cache, struct priv);
63 : 5 : struct priv *p = PL_PRIV(cache);
64 [ - + ]: 5 : pl_mutex_init(&p->lock);
65 [ + - ]: 5 : if (params) {
66 : 5 : cache->params = *params;
67 : 5 : p->log = params->log;
68 : : }
69 : :
70 : : // Sanitize size limits
71 [ + + ]: 5 : size_t total_size = PL_DEF(cache->params.max_total_size, SIZE_MAX);
72 [ + + ]: 5 : size_t object_size = PL_DEF(cache->params.max_object_size, SIZE_MAX);
73 : 5 : object_size = PL_MIN(total_size, object_size);
74 : 5 : cache->params.max_total_size = total_size;
75 : 5 : cache->params.max_object_size = object_size;
76 : :
77 : 5 : return cache;
78 : : }
79 : :
80 : : static void remove_obj(pl_cache cache, pl_cache_obj obj)
81 : : {
82 : : struct priv *p = PL_PRIV(cache);
83 : :
84 : 16 : p->total_size -= obj.size;
85 [ + - + - ]: 10 : if (obj.free)
86 : 16 : obj.free(obj.data);
87 : : }
88 : :
89 : 16 : void pl_cache_destroy(pl_cache *pcache)
90 : : {
91 : 16 : pl_cache cache = *pcache;
92 [ + + ]: 16 : if (!cache)
93 : : return;
94 : :
95 : 5 : struct priv *p = PL_PRIV(cache);
96 [ + + ]: 11 : for (int i = 0; i < p->objects.num; i++)
97 [ + - ]: 6 : remove_obj(cache, p->objects.elem[i]);
98 : :
99 [ - + ]: 5 : pl_assert(p->total_size == 0);
100 : 5 : pl_mutex_destroy(&p->lock);
101 : 5 : pl_free((void *) cache);
102 : 5 : *pcache = NULL;
103 : : }
104 : :
105 : 0 : void pl_cache_reset(pl_cache cache)
106 : : {
107 [ # # ]: 0 : if (!cache)
108 : : return;
109 : :
110 : 0 : struct priv *p = PL_PRIV(cache);
111 : 0 : pl_mutex_lock(&p->lock);
112 [ # # ]: 0 : for (int i = 0; i < p->objects.num; i++)
113 [ # # ]: 0 : remove_obj(cache, p->objects.elem[i]);
114 : 0 : p->objects.num = 0;
115 [ # # ]: 0 : pl_assert(p->total_size == 0);
116 : 0 : pl_mutex_unlock(&p->lock);
117 : : }
118 : :
119 : 38 : static bool try_set(pl_cache cache, pl_cache_obj obj)
120 : : {
121 : 38 : struct priv *p = PL_PRIV(cache);
122 : :
123 : : // Remove any existing entry with this key
124 [ + + ]: 71 : for (int i = p->objects.num - 1; i >= 0; i--) {
125 : 41 : pl_cache_obj prev = p->objects.elem[i];
126 [ + + ]: 41 : if (prev.key == obj.key) {
127 : 8 : PL_TRACE(p, "Removing out-of-date object 0x%"PRIx64, prev.key);
128 : : remove_obj(cache, prev);
129 [ - + ]: 8 : PL_ARRAY_REMOVE_AT(p->objects, i);
130 : : break;
131 : : }
132 : : }
133 : :
134 [ + + ]: 38 : if (!obj.size) {
135 : 7 : PL_TRACE(p, "Deleted object 0x%"PRIx64, obj.key);
136 : 7 : return true;
137 : : }
138 : :
139 [ + + ]: 31 : if (obj.size > cache->params.max_object_size) {
140 : 1 : PL_DEBUG(p, "Object 0x%"PRIx64" (size %zu) exceeds max size %zu, discarding",
141 : : obj.key, obj.size, cache->params.max_object_size);
142 : 1 : return false;
143 : : }
144 : :
145 : : // Make space by deleting old objects
146 [ + + ]: 32 : while (p->total_size + obj.size > cache->params.max_total_size ||
147 [ - + ]: 30 : p->objects.num == INT_MAX)
148 : : {
149 [ - + ]: 2 : pl_assert(p->objects.num);
150 : 2 : pl_cache_obj old = p->objects.elem[0];
151 : 2 : PL_TRACE(p, "Removing object 0x%"PRIx64" (size %zu) to make room",
152 : : old.key, old.size);
153 : : remove_obj(cache, old);
154 [ - + ]: 2 : PL_ARRAY_REMOVE_AT(p->objects, 0);
155 : : }
156 : :
157 [ + + ]: 30 : if (!obj.free) {
158 : 7 : obj.data = pl_memdup(NULL, obj.data, obj.size);
159 : : obj.free = pl_free;
160 : : }
161 : :
162 : 30 : PL_TRACE(p, "Inserting new object 0x%"PRIx64" (size %zu)", obj.key, obj.size);
163 [ + + - + : 30 : PL_ARRAY_APPEND((void *) cache, p->objects, obj);
- + ]
164 : 30 : p->total_size += obj.size;
165 : 30 : return true;
166 : : }
167 : :
168 : : static pl_cache_obj strip_obj(pl_cache_obj obj)
169 : : {
170 : 1 : return (pl_cache_obj) { .key = obj.key };
171 : : }
172 : :
173 : 873 : bool pl_cache_try_set(pl_cache cache, pl_cache_obj *pobj)
174 : : {
175 [ + + ]: 873 : if (!cache)
176 : : return false;
177 : :
178 : 31 : pl_cache_obj obj = *pobj;
179 : 31 : struct priv *p = PL_PRIV(cache);
180 : 31 : pl_mutex_lock(&p->lock);
181 : 31 : bool ok = try_set(cache, obj);
182 : 31 : pl_mutex_unlock(&p->lock);
183 [ + + ]: 31 : if (ok) {
184 : 30 : *pobj = strip_obj(obj); // ownership transfers, clear ptr
185 : : } else {
186 : : obj = strip_obj(obj); // ownership remains with caller, clear copy
187 : : }
188 [ + + ]: 31 : if (cache->params.set)
189 : 5 : cache->params.set(cache->params.priv, obj);
190 : : return ok;
191 : : }
192 : :
193 : 858 : void pl_cache_set(pl_cache cache, pl_cache_obj *obj)
194 : : {
195 [ + + ]: 858 : if (!pl_cache_try_set(cache, obj)) {
196 [ + + ]: 842 : if (obj->free)
197 : 633 : obj->free(obj->data);
198 : 842 : *obj = (pl_cache_obj) { .key = obj->key };
199 : : }
200 : 858 : }
201 : :
202 : 2 : static void noop(void *ignored)
203 : : {
204 : : (void) ignored;
205 : 2 : }
206 : :
207 : 721 : bool pl_cache_get(pl_cache cache, pl_cache_obj *out_obj)
208 : : {
209 : 721 : const uint64_t key = out_obj->key;
210 [ + + ]: 721 : if (!cache)
211 : 698 : goto fail;
212 : :
213 : 23 : struct priv *p = PL_PRIV(cache);
214 : 23 : pl_mutex_lock(&p->lock);
215 : :
216 : : // Search backwards to prioritize recently added entries
217 [ + + ]: 42 : for (int i = p->objects.num - 1; i >= 0; i--) {
218 : 33 : pl_cache_obj obj = p->objects.elem[i];
219 [ + + ]: 33 : if (obj.key == key) {
220 [ - + ]: 14 : PL_ARRAY_REMOVE_AT(p->objects, i);
221 : 14 : p->total_size -= obj.size;
222 : 14 : pl_mutex_unlock(&p->lock);
223 [ - + ]: 14 : pl_assert(obj.free);
224 : 14 : *out_obj = obj;
225 : : return true;
226 : : }
227 : : }
228 : :
229 : 9 : pl_mutex_unlock(&p->lock);
230 [ + + ]: 9 : if (!cache->params.get)
231 : 7 : goto fail;
232 : :
233 : 2 : pl_cache_obj obj = cache->params.get(cache->params.priv, key);
234 [ - + ]: 2 : if (!obj.size)
235 : 0 : goto fail;
236 : :
237 : : // Sanitize object
238 : : obj.key = key;
239 [ - + ]: 2 : obj.free = PL_DEF(obj.free, noop);
240 : 2 : *out_obj = obj;
241 : 2 : return true;
242 : :
243 : 705 : fail:
244 : 705 : *out_obj = (pl_cache_obj) { .key = key };
245 : 705 : return false;
246 : : }
247 : :
248 : 0 : void pl_cache_iterate(pl_cache cache,
249 : : void (*cb)(void *priv, pl_cache_obj obj),
250 : : void *priv)
251 : : {
252 [ # # ]: 0 : if (!cache)
253 : : return;
254 : :
255 : 0 : struct priv *p = PL_PRIV(cache);
256 : 0 : pl_mutex_lock(&p->lock);
257 [ # # ]: 0 : for (int i = 0; i < p->objects.num; i++)
258 : 0 : cb(priv, p->objects.elem[i]);
259 : 0 : pl_mutex_unlock(&p->lock);
260 : : }
261 : :
262 : 7 : uint64_t pl_cache_signature(pl_cache cache)
263 : : {
264 : : uint64_t hash = 0;
265 [ + - ]: 7 : if (!cache)
266 : : return hash;
267 : :
268 : : // Simple XOR of all keys. This satisfies our order-invariant requirement,
269 : : // and does not pose issues because duplicate keys are not allowed, nor
270 : : // are keys with hash 0.
271 : 7 : struct priv *p = PL_PRIV(cache);
272 : 7 : pl_mutex_lock(&p->lock);
273 [ + + ]: 19 : for (int i = 0; i < p->objects.num; i++) {
274 [ - + ]: 12 : assert(p->objects.elem[i].key);
275 : 12 : hash ^= p->objects.elem[i].key;
276 : : }
277 : 7 : pl_mutex_unlock(&p->lock);
278 : 7 : return hash;
279 : : }
280 : :
281 : : // --- Saving/loading
282 : :
283 : : #define CACHE_MAGIC "pl_cache"
284 : : #define CACHE_VERSION 1
285 : : #define PAD_ALIGN(x) PL_ALIGN2(x, sizeof(uint32_t))
286 : :
287 : : struct __attribute__((__packed__)) cache_header {
288 : : char magic[8];
289 : : uint32_t version;
290 : : uint32_t num_entries;
291 : : };
292 : :
293 : : struct __attribute__((__packed__)) cache_entry {
294 : : uint64_t key;
295 : : uint64_t size;
296 : : uint64_t hash;
297 : : };
298 : :
299 : : pl_static_assert(sizeof(struct cache_header) % alignof(struct cache_entry) == 0);
300 : :
301 : 11 : int pl_cache_save_ex(pl_cache cache,
302 : : void (*write)(void *priv, size_t size, const void *ptr),
303 : : void *priv)
304 : : {
305 [ + - ]: 11 : if (!cache)
306 : : return 0;
307 : :
308 : 11 : struct priv *p = PL_PRIV(cache);
309 : 11 : pl_mutex_lock(&p->lock);
310 : : pl_clock_t start = pl_clock_now();
311 : :
312 : 11 : const int num_objects = p->objects.num;
313 : 11 : const size_t saved_bytes = p->total_size;
314 : 11 : write(priv, sizeof(struct cache_header), &(struct cache_header) {
315 : : .magic = CACHE_MAGIC,
316 : : .version = CACHE_VERSION,
317 : : .num_entries = num_objects,
318 : : });
319 : :
320 [ + + ]: 29 : for (int i = 0; i < num_objects; i++) {
321 : 18 : pl_cache_obj obj = p->objects.elem[i];
322 : 18 : PL_TRACE(p, "Saving object 0x%"PRIx64" (size %zu)", obj.key, obj.size);
323 : 18 : write(priv, sizeof(struct cache_entry), &(struct cache_entry) {
324 : : .key = obj.key,
325 : : .size = obj.size,
326 : : .hash = pl_mem_hash(obj.data, obj.size),
327 : : });
328 : : static const uint8_t padding[PAD_ALIGN(1)] = {0};
329 : 18 : write(priv, obj.size, obj.data);
330 : 18 : write(priv, PAD_ALIGN(obj.size) - obj.size, padding);
331 : : }
332 : :
333 : 11 : pl_mutex_unlock(&p->lock);
334 : 11 : pl_log_cpu_time(p->log, start, pl_clock_now(), "saving cache");
335 [ + - ]: 11 : if (num_objects)
336 : 11 : PL_DEBUG(p, "Saved %d objects, totalling %zu bytes", num_objects, saved_bytes);
337 : :
338 : : return num_objects;
339 : : }
340 : :
341 : 7 : int pl_cache_load_ex(pl_cache cache,
342 : : bool (*read)(void *priv, size_t size, void *ptr),
343 : : void *priv)
344 : : {
345 [ - + ]: 7 : if (!cache)
346 : : return 0;
347 : :
348 : 7 : struct priv *p = PL_PRIV(cache);
349 : : struct cache_header header;
350 [ + + ]: 7 : if (!read(priv, sizeof(header), &header)) {
351 : 2 : PL_ERR(p, "Failed loading cache: file seems empty or truncated");
352 : 2 : return -1;
353 : : }
354 [ - + ]: 5 : if (memcmp(header.magic, CACHE_MAGIC, sizeof(header.magic)) != 0) {
355 : 0 : PL_ERR(p, "Failed loading cache: invalid magic bytes");
356 : 0 : return -1;
357 : : }
358 [ - + ]: 5 : if (header.version != CACHE_VERSION) {
359 : 0 : PL_INFO(p, "Failed loading cache: wrong version... skipping");
360 : 0 : return 0;
361 : : }
362 [ - + ]: 5 : if (header.num_entries > INT_MAX) {
363 : 0 : PL_ERR(p, "Failed loading cache: %"PRIu32" entries overflows int",
364 : : header.num_entries);
365 : 0 : return 0;
366 : : }
367 : :
368 : : int num_loaded = 0;
369 : : size_t loaded_bytes = 0;
370 : 5 : pl_mutex_lock(&p->lock);
371 : : pl_clock_t start = pl_clock_now();
372 : :
373 [ + + ]: 12 : for (int i = 0; i < header.num_entries; i++) {
374 : : struct cache_entry entry;
375 [ + + ]: 9 : if (!read(priv, sizeof(entry), &entry)) {
376 : 1 : PL_WARN(p, "Cache seems truncated, missing objects.. ignoring rest");
377 : 2 : goto error;
378 : : }
379 : :
380 : : if (entry.size > SIZE_MAX) {
381 : : PL_WARN(p, "Cache object size %"PRIu64" overflows SIZE_MAX.. "
382 : : "suspect broken file, ignoring rest", entry.size);
383 : : goto error;
384 : : }
385 : :
386 : 8 : void *buf = pl_alloc(NULL, PAD_ALIGN(entry.size));
387 [ - + ]: 8 : if (!read(priv, PAD_ALIGN(entry.size), buf)) {
388 : 0 : PL_WARN(p, "Cache seems truncated, missing objects.. ignoring rest");
389 : 0 : pl_free(buf);
390 : 0 : goto error;
391 : : }
392 : :
393 [ + + ]: 8 : uint64_t checksum = pl_mem_hash(buf, entry.size);
394 [ + + ]: 8 : if (checksum != entry.hash) {
395 : 1 : PL_WARN(p, "Cache entry seems corrupt, checksum mismatch.. ignoring rest");
396 : 1 : pl_free(buf);
397 : 1 : goto error;
398 : : }
399 : :
400 : 7 : pl_cache_obj obj = {
401 : 7 : .key = entry.key,
402 : : .size = entry.size,
403 : : .data = buf,
404 : : .free = pl_free,
405 : : };
406 : :
407 : 7 : PL_TRACE(p, "Loading object 0x%"PRIx64" (size %zu)", obj.key, obj.size);
408 [ + - ]: 7 : if (try_set(cache, obj)) {
409 : 7 : num_loaded++;
410 : 7 : loaded_bytes += entry.size;
411 : : } else {
412 : 0 : pl_free(buf);
413 : : }
414 : : }
415 : :
416 : 3 : pl_log_cpu_time(p->log, start, pl_clock_now(), "loading cache");
417 [ - + ]: 3 : if (num_loaded)
418 : 3 : PL_DEBUG(p, "Loaded %d objects, totalling %zu bytes", num_loaded, loaded_bytes);
419 : :
420 : : // fall through
421 : 0 : error:
422 : 5 : pl_mutex_unlock(&p->lock);
423 : 5 : return num_loaded;
424 : : }
425 : :
426 : : // Save/load wrappers
427 : :
428 : : struct ptr_ctx {
429 : : uint8_t *data; // base pointer
430 : : size_t size; // total size
431 : : size_t pos; // read/write index
432 : : };
433 : :
434 : 65 : static void write_ptr(void *priv, size_t size, const void *ptr)
435 : : {
436 : : struct ptr_ctx *ctx = priv;
437 : 65 : size_t end = PL_MIN(ctx->pos + size, ctx->size);
438 [ + + ]: 65 : if (end > ctx->pos)
439 : 30 : memcpy(ctx->data + ctx->pos, ptr, end - ctx->pos);
440 : 65 : ctx->pos += size;
441 : 65 : }
442 : :
443 : 24 : static bool read_ptr(void *priv, size_t size, void *ptr)
444 : : {
445 : : struct ptr_ctx *ctx = priv;
446 [ + + ]: 24 : if (ctx->pos + size > ctx->size)
447 : : return false;
448 : 21 : memcpy(ptr, ctx->data + ctx->pos, size);
449 : 21 : ctx->pos += size;
450 : 21 : return true;
451 : : }
452 : :
453 : 11 : size_t pl_cache_save(pl_cache cache, uint8_t *data, size_t size)
454 : : {
455 : 11 : struct ptr_ctx ctx = { data, size };
456 : 11 : pl_cache_save_ex(cache, write_ptr, &ctx);
457 : 11 : return ctx.pos;
458 : : }
459 : :
460 : 7 : int pl_cache_load(pl_cache cache, const uint8_t *data, size_t size)
461 : : {
462 : 7 : return pl_cache_load_ex(cache, read_ptr, &(struct ptr_ctx) {
463 : : .data = (uint8_t *) data,
464 : : .size = size,
465 : : });
466 : : }
|