xref: /aosp_15_r20/external/freetype/src/cache/ftccache.c (revision 63949dbd25bcc50c4e1178497ff9e9574d44fc5a)
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
ftc_node_mru_link(FTC_Node node,FTC_Manager manager)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
ftc_node_mru_unlink(FTC_Node node,FTC_Manager manager)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
ftc_node_mru_up(FTC_Node node,FTC_Manager manager)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* )
ftc_get_top_node_for_hash(FTC_Cache cache,FT_Offset hash)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
ftc_cache_resize(FTC_Cache cache)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
ftc_node_hash_unlink(FTC_Node node0,FTC_Cache cache)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
ftc_node_hash_link(FTC_Node node,FTC_Cache cache)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 )
ftc_node_destroy(FTC_Node node,FTC_Manager manager)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 )
ftc_cache_init(FTC_Cache cache)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 )
FTC_Cache_Init(FTC_Cache cache)338   FTC_Cache_Init( FTC_Cache  cache )
339   {
340     return ftc_cache_init( cache );
341   }
342 
343 
344   FT_LOCAL_DEF( void )
ftc_cache_done(FTC_Cache cache)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 )
FTC_Cache_Done(FTC_Cache cache)388   FTC_Cache_Done( FTC_Cache  cache )
389   {
390     ftc_cache_done( cache );
391   }
392 
393 
394   static void
ftc_cache_add(FTC_Cache cache,FT_Offset hash,FTC_Node node)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 )
FTC_Cache_NewNode(FTC_Cache cache,FT_Offset hash,FT_Pointer query,FTC_Node * anode)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 )
FTC_Cache_Lookup(FTC_Cache cache,FT_Offset hash,FT_Pointer query,FTC_Node * anode)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 )
FTC_Cache_RemoveFaceID(FTC_Cache cache,FTC_FaceID face_id)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 */
600