Coverage Report

Created: 2025-07-07 10:01

/work/workdir/UnpackedTarball/cairo/src/cairo-cache.c
Line
Count
Source (jump to first uncovered line)
1
/* cairo - a vector graphics library with display and print output
2
 *
3
 * Copyright © 2004 Red Hat, Inc.
4
 * Copyright © 2005 Red Hat, Inc.
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it either under the terms of the GNU Lesser General Public
8
 * License version 2.1 as published by the Free Software Foundation
9
 * (the "LGPL") or, at your option, under the terms of the Mozilla
10
 * Public License Version 1.1 (the "MPL"). If you do not alter this
11
 * notice, a recipient may use your version of this file under either
12
 * the MPL or the LGPL.
13
 *
14
 * You should have received a copy of the LGPL along with this library
15
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
16
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
17
 * You should have received a copy of the MPL along with this library
18
 * in the file COPYING-MPL-1.1
19
 *
20
 * The contents of this file are subject to the Mozilla Public License
21
 * Version 1.1 (the "License"); you may not use this file except in
22
 * compliance with the License. You may obtain a copy of the License at
23
 * http://www.mozilla.org/MPL/
24
 *
25
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
26
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
27
 * the specific language governing rights and limitations.
28
 *
29
 * The Original Code is the cairo graphics library.
30
 *
31
 * The Initial Developer of the Original Code is Red Hat, Inc.
32
 *
33
 * Contributor(s):
34
 *      Keith Packard <keithp@keithp.com>
35
 *  Graydon Hoare <graydon@redhat.com>
36
 *  Carl Worth <cworth@cworth.org>
37
 */
38
39
#include "cairoint.h"
40
#include "cairo-error-private.h"
41
42
static void
43
_cairo_cache_shrink_to_accommodate (cairo_cache_t *cache,
44
            unsigned long  additional);
45
46
static cairo_bool_t
47
_cairo_cache_entry_is_non_zero (const void *entry)
48
0
{
49
0
    return ((const cairo_cache_entry_t *) entry)->size;
50
0
}
51
52
53
/**
54
 * _cairo_cache_init:
55
 * @cache: the #cairo_cache_t to initialise
56
 * @keys_equal: a function to return %TRUE if two keys are equal
57
 * @entry_destroy: destroy notifier for cache entries
58
 * @max_size: the maximum size for this cache
59
 * Returns: the newly created #cairo_cache_t
60
 *
61
 * Creates a new cache using the keys_equal() function to determine
62
 * the equality of entries.
63
 *
64
 * Data is provided to the cache in the form of user-derived version
65
 * of #cairo_cache_entry_t. A cache entry must be able to hold hash
66
 * code, a size, and the key/value pair being stored in the
67
 * cache. Sometimes only the key will be necessary, (as in
68
 * _cairo_cache_lookup()), and in these cases the value portion of the
69
 * entry need not be initialized.
70
 *
71
 * The units for max_size can be chosen by the caller, but should be
72
 * consistent with the units of the size field of cache entries. When
73
 * adding an entry with _cairo_cache_insert() if the total size of
74
 * entries in the cache would exceed max_size then entries will be
75
 * removed at random until the new entry would fit or the cache is
76
 * empty. Then the new entry is inserted.
77
 *
78
 * There are cases in which the automatic removal of entries is
79
 * undesired. If the cache entries have reference counts, then it is a
80
 * simple matter to use the reference counts to ensure that entries
81
 * continue to live even after being ejected from the cache. However,
82
 * in some cases the memory overhead of adding a reference count to
83
 * the entry would be objectionable. In such cases, the
84
 * _cairo_cache_freeze() and _cairo_cache_thaw() calls can be
85
 * used to establish a window during which no automatic removal of
86
 * entries will occur.
87
 **/
88
cairo_status_t
89
_cairo_cache_init (cairo_cache_t    *cache,
90
       cairo_cache_keys_equal_func_t keys_equal,
91
       cairo_cache_predicate_func_t  predicate,
92
       cairo_destroy_func_t    entry_destroy,
93
       unsigned long     max_size)
94
2
{
95
2
    cache->hash_table = _cairo_hash_table_create (keys_equal);
96
2
    if (unlikely (cache->hash_table == NULL))
97
0
  return _cairo_error (CAIRO_STATUS_NO_MEMORY);
98
99
2
    if (predicate == NULL)
100
0
  predicate = _cairo_cache_entry_is_non_zero;
101
2
    cache->predicate = predicate;
102
2
    cache->entry_destroy = entry_destroy;
103
104
2
    cache->max_size = max_size;
105
2
    cache->size = 0;
106
107
2
    cache->freeze_count = 0;
108
109
2
    return CAIRO_STATUS_SUCCESS;
110
2
}
111
112
static void
113
_cairo_cache_pluck (void *entry, void *closure)
114
0
{
115
0
    _cairo_cache_remove (closure, entry);
116
0
}
117
118
/**
119
 * _cairo_cache_fini:
120
 * @cache: a cache to destroy
121
 *
122
 * Immediately destroys the given cache, freeing all resources
123
 * associated with it. As part of this process, the entry_destroy()
124
 * function, (as passed to _cairo_cache_init()), will be called for
125
 * each entry in the cache.
126
 **/
127
void
128
_cairo_cache_fini (cairo_cache_t *cache)
129
0
{
130
0
    _cairo_hash_table_foreach (cache->hash_table,
131
0
             _cairo_cache_pluck,
132
0
             cache);
133
0
    assert (cache->size == 0);
134
0
    _cairo_hash_table_destroy (cache->hash_table);
135
0
}
136
137
/**
138
 * _cairo_cache_freeze:
139
 * @cache: a cache with some precious entries in it (or about to be
140
 * added)
141
 *
142
 * Disable the automatic ejection of entries from the cache. For as
143
 * long as the cache is "frozen", calls to _cairo_cache_insert() will
144
 * add new entries to the cache regardless of how large the cache
145
 * grows. See _cairo_cache_thaw().
146
 *
147
 * Note: Multiple calls to _cairo_cache_freeze() will stack, in that
148
 * the cache will remain "frozen" until a corresponding number of
149
 * calls are made to _cairo_cache_thaw().
150
 **/
151
void
152
_cairo_cache_freeze (cairo_cache_t *cache)
153
494
{
154
494
    assert (cache->freeze_count >= 0);
155
156
494
    cache->freeze_count++;
157
494
}
158
159
/**
160
 * _cairo_cache_thaw:
161
 * @cache: a cache, just after the entries in it have become less
162
 * precious
163
 *
164
 * Cancels the effects of _cairo_cache_freeze().
165
 *
166
 * When a number of calls to _cairo_cache_thaw() is made corresponding
167
 * to the number of calls to _cairo_cache_freeze() the cache will no
168
 * longer be "frozen". If the cache had grown larger than max_size
169
 * while frozen, entries will immediately be ejected (by random) from
170
 * the cache until the cache is smaller than max_size. Also, the
171
 * automatic ejection of entries on _cairo_cache_insert() will resume.
172
 **/
173
void
174
_cairo_cache_thaw (cairo_cache_t *cache)
175
494
{
176
494
    assert (cache->freeze_count > 0);
177
178
494
    if (--cache->freeze_count == 0)
179
494
  _cairo_cache_shrink_to_accommodate (cache, 0);
180
494
}
181
182
/**
183
 * _cairo_cache_lookup:
184
 * @cache: a cache
185
 * @key: the key of interest
186
 * @entry_return: pointer for return value
187
 *
188
 * Performs a lookup in @cache looking for an entry which has a key
189
 * that matches @key, (as determined by the keys_equal() function
190
 * passed to _cairo_cache_init()).
191
 *
192
 * Return value: %TRUE if there is an entry in the cache that matches
193
 * @key, (which will now be in *entry_return). %FALSE otherwise, (in
194
 * which case *entry_return will be %NULL).
195
 **/
196
void *
197
_cairo_cache_lookup (cairo_cache_t    *cache,
198
         cairo_cache_entry_t  *key)
199
0
{
200
0
    return _cairo_hash_table_lookup (cache->hash_table,
201
0
             (cairo_hash_entry_t *) key);
202
0
}
203
204
/**
205
 * _cairo_cache_remove_random:
206
 * @cache: a cache
207
 *
208
 * Remove a random entry from the cache.
209
 *
210
 * Return value: %TRUE if an entry was successfully removed.
211
 * %FALSE if there are no entries that can be removed.
212
 **/
213
static cairo_bool_t
214
_cairo_cache_remove_random (cairo_cache_t *cache)
215
710
{
216
710
    cairo_cache_entry_t *entry;
217
218
710
    entry = _cairo_hash_table_random_entry (cache->hash_table,
219
710
              cache->predicate);
220
710
    if (unlikely (entry == NULL))
221
0
  return FALSE;
222
223
710
    _cairo_cache_remove (cache, entry);
224
225
710
    return TRUE;
226
710
}
227
228
/**
229
 * _cairo_cache_shrink_to_accommodate:
230
 * @cache: a cache
231
 * @additional: additional size requested in bytes
232
 *
233
 * If cache is not frozen, eject entries randomly until the size of
234
 * the cache is at least @additional bytes less than
235
 * cache->max_size. That is, make enough room to accommodate a new
236
 * entry of size @additional.
237
 **/
238
static void
239
_cairo_cache_shrink_to_accommodate (cairo_cache_t *cache,
240
            unsigned long  additional)
241
494
{
242
1.20k
    while (cache->size + additional > cache->max_size) {
243
710
  if (! _cairo_cache_remove_random (cache))
244
0
      return;
245
710
    }
246
494
}
247
248
/**
249
 * _cairo_cache_insert:
250
 * @cache: a cache
251
 * @entry: an entry to be inserted
252
 *
253
 * Insert @entry into the cache. If an entry exists in the cache with
254
 * a matching key, then the old entry will be removed first, (and the
255
 * entry_destroy() callback will be called on it).
256
 *
257
 * Return value: %CAIRO_STATUS_SUCCESS if successful or
258
 * %CAIRO_STATUS_NO_MEMORY if insufficient memory is available.
259
 **/
260
cairo_status_t
261
_cairo_cache_insert (cairo_cache_t   *cache,
262
         cairo_cache_entry_t *entry)
263
1.22k
{
264
1.22k
    cairo_status_t status;
265
266
1.22k
    if (entry->size && ! cache->freeze_count)
267
0
  _cairo_cache_shrink_to_accommodate (cache, entry->size);
268
269
1.22k
    status = _cairo_hash_table_insert (cache->hash_table,
270
1.22k
               (cairo_hash_entry_t *) entry);
271
1.22k
    if (unlikely (status))
272
0
  return status;
273
274
1.22k
    cache->size += entry->size;
275
276
1.22k
    return CAIRO_STATUS_SUCCESS;
277
1.22k
}
278
279
/**
280
 * _cairo_cache_remove:
281
 * @cache: a cache
282
 * @entry: an entry that exists in the cache
283
 *
284
 * Remove an existing entry from the cache.
285
 **/
286
void
287
_cairo_cache_remove (cairo_cache_t   *cache,
288
         cairo_cache_entry_t *entry)
289
710
{
290
710
    cache->size -= entry->size;
291
292
710
    _cairo_hash_table_remove (cache->hash_table,
293
710
            (cairo_hash_entry_t *) entry);
294
295
710
    if (cache->entry_destroy)
296
710
  cache->entry_destroy (entry);
297
710
}
298
299
/**
300
 * _cairo_cache_foreach:
301
 * @cache: a cache
302
 * @cache_callback: function to be called for each entry
303
 * @closure: additional argument to be passed to @cache_callback
304
 *
305
 * Call @cache_callback for each entry in the cache, in a
306
 * non-specified order.
307
 **/
308
void
309
_cairo_cache_foreach (cairo_cache_t         *cache,
310
          cairo_cache_callback_func_t      cache_callback,
311
          void            *closure)
312
0
{
313
0
    _cairo_hash_table_foreach (cache->hash_table,
314
0
             cache_callback,
315
0
             closure);
316
0
}
317
318
uintptr_t
319
_cairo_hash_string (const char *c)
320
807
{
321
    /* This is the djb2 hash. */
322
807
    uintptr_t hash = _CAIRO_HASH_INIT_VALUE;
323
67.9k
    while (c && *c)
324
67.1k
  hash = ((hash << 5) + hash) + *c++;
325
807
    return hash;
326
807
}
327
328
uintptr_t
329
_cairo_hash_bytes (uintptr_t hash,
330
       const void *ptr,
331
       unsigned int length)
332
0
{
333
0
    const uint8_t *bytes = ptr;
334
    /* This is the djb2 hash. */
335
0
    while (length--)
336
0
  hash = ((hash << 5) + hash) + *bytes++;
337
0
    return hash;
338
0
}