Imported Upstream version 2.13.2
[platform/upstream/freetype2.git] / src / cache / ftccache.c
1 /****************************************************************************
2  *
3  * ftccache.c
4  *
5  *   The FreeType internal cache interface (body).
6  *
7  * Copyright (C) 2000-2023 by
8  * David Turner, Robert Wilhelm, and Werner Lemberg.
9  *
10  * This file is part of the FreeType project, and may only be used,
11  * modified, and distributed under the terms of the FreeType project
12  * license, LICENSE.TXT.  By continuing to use, modify, or distribute
13  * this file you indicate that you have read the license and
14  * understand and accept it fully.
15  *
16  */
17
18
19 #include "ftcmanag.h"
20 #include <freetype/internal/ftobjs.h>
21 #include <freetype/internal/ftdebug.h>
22
23 #include "ftccback.h"
24 #include "ftcerror.h"
25
26 #undef  FT_COMPONENT
27 #define FT_COMPONENT  cache
28
29
30 #define FTC_HASH_MAX_LOAD  2
31 #define FTC_HASH_MIN_LOAD  1
32 #define FTC_HASH_SUB_LOAD  ( FTC_HASH_MAX_LOAD - FTC_HASH_MIN_LOAD )
33
34   /* this one _must_ be a power of 2! */
35 #define FTC_HASH_INITIAL_SIZE  8
36
37
38   /*************************************************************************/
39   /*************************************************************************/
40   /*****                                                               *****/
41   /*****                   CACHE NODE DEFINITIONS                      *****/
42   /*****                                                               *****/
43   /*************************************************************************/
44   /*************************************************************************/
45
46   /* add a new node to the head of the manager's circular MRU list */
47   static void
48   ftc_node_mru_link( FTC_Node     node,
49                      FTC_Manager  manager )
50   {
51     void  *nl = &manager->nodes_list;
52
53
54     FTC_MruNode_Prepend( (FTC_MruNode*)nl,
55                          (FTC_MruNode)node );
56     manager->num_nodes++;
57   }
58
59
60   /* remove a node from the manager's MRU list */
61   static void
62   ftc_node_mru_unlink( FTC_Node     node,
63                        FTC_Manager  manager )
64   {
65     void  *nl = &manager->nodes_list;
66
67
68     FTC_MruNode_Remove( (FTC_MruNode*)nl,
69                         (FTC_MruNode)node );
70     manager->num_nodes--;
71   }
72
73
74 #ifndef FTC_INLINE
75
76   /* move a node to the head of the manager's MRU list */
77   static void
78   ftc_node_mru_up( FTC_Node     node,
79                    FTC_Manager  manager )
80   {
81     FTC_MruNode_Up( (FTC_MruNode*)&manager->nodes_list,
82                     (FTC_MruNode)node );
83   }
84
85
86   /* get a top bucket for specified hash from cache,
87    * body for FTC_NODE_TOP_FOR_HASH( cache, hash )
88    */
89   FT_LOCAL_DEF( FTC_Node* )
90   ftc_get_top_node_for_hash( FTC_Cache  cache,
91                              FT_Offset  hash )
92   {
93     FT_Offset  idx;
94
95
96     idx = hash & cache->mask;
97     if ( idx >= cache->p )
98       idx = hash & ( cache->mask >> 1 );
99
100     return cache->buckets + idx;
101   }
102
103 #endif /* !FTC_INLINE */
104
105
106   /* Note that this function cannot fail.  If we cannot re-size the
107    * buckets array appropriately, we simply degrade the hash table's
108    * performance!
109    */
110   static void
111   ftc_cache_resize( FTC_Cache  cache )
112   {
113     for (;;)
114     {
115       FTC_Node  node, *pnode;
116       FT_UFast  p    = cache->p;
117       FT_UFast  size = cache->mask + 1;  /* available size */
118       FT_UFast  half = size >> 1;
119
120
121       /* do we need to expand the buckets array? */
122       if ( cache->slack < 0 )
123       {
124         FTC_Node  new_list = NULL;
125
126
127         /* try to expand the buckets array _before_ splitting
128          * the bucket lists
129          */
130         if ( p == size )
131         {
132           FT_Memory  memory = cache->memory;
133           FT_Error   error;
134
135
136           /* if we can't expand the array, leave immediately */
137           if ( FT_QRENEW_ARRAY( cache->buckets, size, size * 2 ) )
138             break;
139
140           cache->mask = 2 * size - 1;
141           half        = size;
142         }
143
144         /* the bucket to split */
145         pnode = cache->buckets + p - half;
146
147         for (;;)
148         {
149           node = *pnode;
150           if ( !node )
151             break;
152
153           if ( node->hash & half )
154           {
155             *pnode     = node->link;
156             node->link = new_list;
157             new_list   = node;
158           }
159           else
160             pnode = &node->link;
161         }
162
163         cache->buckets[p] = new_list;
164
165         cache->slack += FTC_HASH_MAX_LOAD;
166         cache->p      = p + 1;
167
168         FT_TRACE2(( "ftc_cache_resize: cache %u increased to %u hashes\n",
169                     cache->index, cache->p ));
170       }
171
172       /* do we need to shrink the buckets array? */
173       else if ( cache->slack > (FT_Long)p * FTC_HASH_SUB_LOAD )
174       {
175         FTC_Node  old_list = cache->buckets[--p];
176
177
178         if ( p < FTC_HASH_INITIAL_SIZE )
179           break;
180
181         if ( p == half )
182         {
183           FT_Memory  memory = cache->memory;
184           FT_Error   error;
185
186
187           /* if we can't shrink the array, leave immediately */
188           if ( FT_QRENEW_ARRAY( cache->buckets, size, half ) )
189             break;
190
191           cache->mask = half - 1;
192         }
193
194         /* the bucket to merge */
195         pnode = cache->buckets + p - half;
196
197         while ( *pnode )
198           pnode = &(*pnode)->link;
199
200         *pnode = old_list;
201
202         cache->slack -= FTC_HASH_MAX_LOAD;
203         cache->p      = p;
204
205         FT_TRACE2(( "ftc_cache_resize: cache %u decreased to %u hashes\n",
206                     cache->index, cache->p ));
207       }
208
209       /* otherwise, the hash table is balanced */
210       else
211         break;
212     }
213   }
214
215
216   /* remove a node from its cache's hash table */
217   static void
218   ftc_node_hash_unlink( FTC_Node   node0,
219                         FTC_Cache  cache )
220   {
221     FTC_Node  *pnode = FTC_NODE_TOP_FOR_HASH( cache, node0->hash );
222
223
224     for (;;)
225     {
226       FTC_Node  node = *pnode;
227
228
229       if ( !node )
230       {
231         FT_TRACE0(( "ftc_node_hash_unlink: unknown node\n" ));
232         return;
233       }
234
235       if ( node == node0 )
236         break;
237
238       pnode = &node->link;
239     }
240
241     *pnode      = node0->link;
242     node0->link = NULL;
243
244     cache->slack++;
245     ftc_cache_resize( cache );
246   }
247
248
249   /* add a node to the `top' of its cache's hash table */
250   static void
251   ftc_node_hash_link( FTC_Node   node,
252                       FTC_Cache  cache )
253   {
254     FTC_Node  *pnode = FTC_NODE_TOP_FOR_HASH( cache, node->hash );
255
256
257     node->link = *pnode;
258     *pnode     = node;
259
260     cache->slack--;
261     ftc_cache_resize( cache );
262   }
263
264
265   /* remove a node from the cache manager */
266   FT_LOCAL_DEF( void )
267   ftc_node_destroy( FTC_Node     node,
268                     FTC_Manager  manager )
269   {
270     FTC_Cache  cache;
271
272
273 #ifdef FT_DEBUG_ERROR
274     /* find node's cache */
275     if ( node->cache_index >= manager->num_caches )
276     {
277       FT_TRACE0(( "ftc_node_destroy: invalid node handle\n" ));
278       return;
279     }
280 #endif
281
282     cache = manager->caches[node->cache_index];
283
284 #ifdef FT_DEBUG_ERROR
285     if ( !cache )
286     {
287       FT_TRACE0(( "ftc_node_destroy: invalid node handle\n" ));
288       return;
289     }
290 #endif
291
292     manager->cur_weight -= cache->clazz.node_weight( node, cache );
293
294     /* remove node from mru list */
295     ftc_node_mru_unlink( node, manager );
296
297     /* remove node from cache's hash table */
298     ftc_node_hash_unlink( node, cache );
299
300     /* now finalize it */
301     cache->clazz.node_free( node, cache );
302
303 #if 0
304     /* check, just in case of general corruption :-) */
305     if ( manager->num_nodes == 0 )
306       FT_TRACE0(( "ftc_node_destroy: invalid cache node count (%u)\n",
307                   manager->num_nodes ));
308 #endif
309   }
310
311
312   /*************************************************************************/
313   /*************************************************************************/
314   /*****                                                               *****/
315   /*****                    ABSTRACT CACHE CLASS                       *****/
316   /*****                                                               *****/
317   /*************************************************************************/
318   /*************************************************************************/
319
320
321   FT_LOCAL_DEF( FT_Error )
322   ftc_cache_init( FTC_Cache  cache )
323   {
324     FT_Memory  memory = cache->memory;
325     FT_Error   error;
326
327
328     cache->p     = FTC_HASH_INITIAL_SIZE;
329     cache->mask  = FTC_HASH_INITIAL_SIZE - 1;
330     cache->slack = FTC_HASH_INITIAL_SIZE * FTC_HASH_MAX_LOAD;
331
332     FT_MEM_NEW_ARRAY( cache->buckets, FTC_HASH_INITIAL_SIZE );
333     return error;
334   }
335
336
337   FT_LOCAL_DEF( FT_Error )
338   FTC_Cache_Init( FTC_Cache  cache )
339   {
340     return ftc_cache_init( cache );
341   }
342
343
344   FT_LOCAL_DEF( void )
345   ftc_cache_done( FTC_Cache  cache )
346   {
347     FT_Memory  memory = cache->memory;
348
349
350     if ( cache->buckets )
351     {
352       FTC_Manager  manager = cache->manager;
353       FT_UFast     count   = cache->p;
354       FT_UFast     i;
355
356
357       for ( i = 0; i < count; i++ )
358       {
359         FTC_Node  node = cache->buckets[i], next;
360
361
362         while ( node )
363         {
364           next        = node->link;
365           node->link  = NULL;
366
367           /* remove node from mru list */
368           ftc_node_mru_unlink( node, manager );
369
370           /* now finalize it */
371           manager->cur_weight -= cache->clazz.node_weight( node, cache );
372
373           cache->clazz.node_free( node, cache );
374           node = next;
375         }
376       }
377     }
378
379     FT_FREE( cache->buckets );
380
381     cache->p     = 0;
382     cache->mask  = 0;
383     cache->slack = 0;
384   }
385
386
387   FT_LOCAL_DEF( void )
388   FTC_Cache_Done( FTC_Cache  cache )
389   {
390     ftc_cache_done( cache );
391   }
392
393
394   static void
395   ftc_cache_add( FTC_Cache  cache,
396                  FT_Offset  hash,
397                  FTC_Node   node )
398   {
399     node->hash        = hash;
400     node->cache_index = (FT_UShort)cache->index;
401     node->ref_count   = 0;
402
403     ftc_node_hash_link( node, cache );
404     ftc_node_mru_link( node, cache->manager );
405
406     {
407       FTC_Manager  manager = cache->manager;
408
409
410       manager->cur_weight += cache->clazz.node_weight( node, cache );
411
412       if ( manager->cur_weight >= manager->max_weight )
413       {
414         node->ref_count++;
415         FTC_Manager_Compress( manager );
416         node->ref_count--;
417       }
418     }
419   }
420
421
422   FT_LOCAL_DEF( FT_Error )
423   FTC_Cache_NewNode( FTC_Cache   cache,
424                      FT_Offset   hash,
425                      FT_Pointer  query,
426                      FTC_Node   *anode )
427   {
428     FT_Error  error;
429     FTC_Node  node;
430
431
432     /*
433      * We use the FTC_CACHE_TRYLOOP macros to support out-of-memory
434      * errors (OOM) correctly, i.e., by flushing the cache progressively
435      * in order to make more room.
436      */
437
438     FTC_CACHE_TRYLOOP( cache )
439     {
440       error = cache->clazz.node_new( &node, query, cache );
441     }
442     FTC_CACHE_TRYLOOP_END( NULL )
443
444     if ( error )
445       node = NULL;
446     else
447     {
448      /* don't assume that the cache has the same number of buckets, since
449       * our allocation request might have triggered global cache flushing
450       */
451       ftc_cache_add( cache, hash, node );
452     }
453
454     *anode = node;
455     return error;
456   }
457
458
459 #ifndef FTC_INLINE
460
461   FT_LOCAL_DEF( FT_Error )
462   FTC_Cache_Lookup( FTC_Cache   cache,
463                     FT_Offset   hash,
464                     FT_Pointer  query,
465                     FTC_Node   *anode )
466   {
467     FTC_Node*  bucket;
468     FTC_Node*  pnode;
469     FTC_Node   node;
470     FT_Error   error        = FT_Err_Ok;
471     FT_Bool    list_changed = FALSE;
472
473     FTC_Node_CompareFunc  compare = cache->clazz.node_compare;
474
475
476     if ( !cache || !anode )
477       return FT_THROW( Invalid_Argument );
478
479     /* Go to the `top' node of the list sharing same masked hash */
480     bucket = pnode = FTC_NODE_TOP_FOR_HASH( cache, hash );
481
482     /* Lookup a node with exactly same hash and queried properties.  */
483     /* NOTE: _nodcomp() may change the linked list to reduce memory. */
484     for (;;)
485     {
486       node = *pnode;
487       if ( !node )
488         goto NewNode;
489
490       if ( node->hash == hash                           &&
491            compare( node, query, cache, &list_changed ) )
492         break;
493
494       pnode = &node->link;
495     }
496
497     if ( list_changed )
498     {
499       /* Update bucket by modified linked list */
500       bucket = pnode = FTC_NODE_TOP_FOR_HASH( cache, hash );
501
502       /* Update pnode by modified linked list */
503       while ( *pnode != node )
504       {
505         if ( !*pnode )
506         {
507           FT_ERROR(( "FTC_Cache_Lookup: oops!!!  node missing\n" ));
508           goto NewNode;
509         }
510         else
511           pnode = &(*pnode)->link;
512       }
513     }
514
515     /* Reorder the list to move the found node to the `top' */
516     if ( node != *bucket )
517     {
518       *pnode     = node->link;
519       node->link = *bucket;
520       *bucket    = node;
521     }
522
523     /* move to head of MRU list */
524     {
525       FTC_Manager  manager = cache->manager;
526
527
528       if ( node != manager->nodes_list )
529         ftc_node_mru_up( node, manager );
530     }
531     *anode = node;
532
533     return error;
534
535   NewNode:
536     return FTC_Cache_NewNode( cache, hash, query, anode );
537   }
538
539 #endif /* !FTC_INLINE */
540
541
542   FT_LOCAL_DEF( void )
543   FTC_Cache_RemoveFaceID( FTC_Cache   cache,
544                           FTC_FaceID  face_id )
545   {
546     FTC_Manager  manager = cache->manager;
547     FTC_Node     frees   = NULL;
548     FT_UFast     count   = cache->p;
549     FT_UFast     i;
550
551
552     for ( i = 0; i < count; i++ )
553     {
554       FTC_Node*  pnode = cache->buckets + i;
555
556
557       for (;;)
558       {
559         FTC_Node  node = *pnode;
560         FT_Bool   list_changed = FALSE;
561
562
563         if ( !node )
564           break;
565
566         if ( cache->clazz.node_remove_faceid( node, face_id,
567                                               cache, &list_changed ) )
568         {
569           *pnode     = node->link;
570           node->link = frees;
571           frees      = node;
572         }
573         else
574           pnode = &node->link;
575       }
576     }
577
578     /* remove all nodes in the free list */
579     while ( frees )
580     {
581       FTC_Node  node;
582
583
584       node  = frees;
585       frees = node->link;
586
587       manager->cur_weight -= cache->clazz.node_weight( node, cache );
588       ftc_node_mru_unlink( node, manager );
589
590       cache->clazz.node_free( node, cache );
591
592       cache->slack++;
593     }
594
595     ftc_cache_resize( cache );
596   }
597
598
599 /* END */