• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /****************************************************************************
2  *
3  * ftccache.c
4  *
5  *   The FreeType internal cache interface (body).
6  *
7  * Copyright (C) 2000-2021 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 & ( 2 * 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  mask  = cache->mask;
118       FT_UFast  count = mask + p + 1;    /* number of buckets */
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 >= mask )
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_RENEW_ARRAY( cache->buckets,
138                                ( mask + 1 ) * 2, ( mask + 1 ) * 4 ) )
139             break;
140         }
141 
142         /* split a single bucket */
143         pnode = cache->buckets + p;
144 
145         for (;;)
146         {
147           node = *pnode;
148           if ( !node )
149             break;
150 
151           if ( node->hash & ( mask + 1 ) )
152           {
153             *pnode     = node->link;
154             node->link = new_list;
155             new_list   = node;
156           }
157           else
158             pnode = &node->link;
159         }
160 
161         cache->buckets[p + mask + 1] = new_list;
162 
163         cache->slack += FTC_HASH_MAX_LOAD;
164 
165         if ( p >= mask )
166         {
167           cache->mask = 2 * mask + 1;
168           cache->p    = 0;
169         }
170         else
171           cache->p = p + 1;
172       }
173 
174       /* do we need to shrink the buckets array? */
175       else if ( cache->slack > (FT_Long)count * FTC_HASH_SUB_LOAD )
176       {
177         FT_UFast   old_index = p + mask;
178         FTC_Node*  pold;
179 
180 
181         if ( old_index + 1 <= FTC_HASH_INITIAL_SIZE )
182           break;
183 
184         if ( p == 0 )
185         {
186           FT_Memory  memory = cache->memory;
187           FT_Error   error;
188 
189 
190           /* if we can't shrink the array, leave immediately */
191           if ( FT_QRENEW_ARRAY( cache->buckets,
192                                ( mask + 1 ) * 2, mask + 1 ) )
193             break;
194 
195           cache->mask >>= 1;
196           p             = cache->mask;
197         }
198         else
199           p--;
200 
201         pnode = cache->buckets + p;
202         while ( *pnode )
203           pnode = &(*pnode)->link;
204 
205         pold   = cache->buckets + old_index;
206         *pnode = *pold;
207         *pold  = NULL;
208 
209         cache->slack -= FTC_HASH_MAX_LOAD;
210         cache->p      = p;
211       }
212 
213       /* otherwise, the hash table is balanced */
214       else
215         break;
216     }
217   }
218 
219 
220   /* remove a node from its cache's hash table */
221   static void
ftc_node_hash_unlink(FTC_Node node0,FTC_Cache cache)222   ftc_node_hash_unlink( FTC_Node   node0,
223                         FTC_Cache  cache )
224   {
225     FTC_Node  *pnode = FTC_NODE_TOP_FOR_HASH( cache, node0->hash );
226 
227 
228     for (;;)
229     {
230       FTC_Node  node = *pnode;
231 
232 
233       if ( !node )
234       {
235         FT_TRACE0(( "ftc_node_hash_unlink: unknown node\n" ));
236         return;
237       }
238 
239       if ( node == node0 )
240         break;
241 
242       pnode = &(*pnode)->link;
243     }
244 
245     *pnode      = node0->link;
246     node0->link = NULL;
247 
248     cache->slack++;
249     ftc_cache_resize( cache );
250   }
251 
252 
253   /* add a node to the `top' of its cache's hash table */
254   static void
ftc_node_hash_link(FTC_Node node,FTC_Cache cache)255   ftc_node_hash_link( FTC_Node   node,
256                       FTC_Cache  cache )
257   {
258     FTC_Node  *pnode = FTC_NODE_TOP_FOR_HASH( cache, node->hash );
259 
260 
261     node->link = *pnode;
262     *pnode     = node;
263 
264     cache->slack--;
265     ftc_cache_resize( cache );
266   }
267 
268 
269   /* remove a node from the cache manager */
270   FT_LOCAL_DEF( void )
ftc_node_destroy(FTC_Node node,FTC_Manager manager)271   ftc_node_destroy( FTC_Node     node,
272                     FTC_Manager  manager )
273   {
274     FTC_Cache  cache;
275 
276 
277 #ifdef FT_DEBUG_ERROR
278     /* find node's cache */
279     if ( node->cache_index >= manager->num_caches )
280     {
281       FT_TRACE0(( "ftc_node_destroy: invalid node handle\n" ));
282       return;
283     }
284 #endif
285 
286     cache = manager->caches[node->cache_index];
287 
288 #ifdef FT_DEBUG_ERROR
289     if ( !cache )
290     {
291       FT_TRACE0(( "ftc_node_destroy: invalid node handle\n" ));
292       return;
293     }
294 #endif
295 
296     manager->cur_weight -= cache->clazz.node_weight( node, cache );
297 
298     /* remove node from mru list */
299     ftc_node_mru_unlink( node, manager );
300 
301     /* remove node from cache's hash table */
302     ftc_node_hash_unlink( node, cache );
303 
304     /* now finalize it */
305     cache->clazz.node_free( node, cache );
306 
307 #if 0
308     /* check, just in case of general corruption :-) */
309     if ( manager->num_nodes == 0 )
310       FT_TRACE0(( "ftc_node_destroy: invalid cache node count (%d)\n",
311                   manager->num_nodes ));
312 #endif
313   }
314 
315 
316   /*************************************************************************/
317   /*************************************************************************/
318   /*****                                                               *****/
319   /*****                    ABSTRACT CACHE CLASS                       *****/
320   /*****                                                               *****/
321   /*************************************************************************/
322   /*************************************************************************/
323 
324 
325   FT_LOCAL_DEF( FT_Error )
FTC_Cache_Init(FTC_Cache cache)326   FTC_Cache_Init( FTC_Cache  cache )
327   {
328     return ftc_cache_init( cache );
329   }
330 
331 
332   FT_LOCAL_DEF( FT_Error )
ftc_cache_init(FTC_Cache cache)333   ftc_cache_init( FTC_Cache  cache )
334   {
335     FT_Memory  memory = cache->memory;
336     FT_Error   error;
337 
338 
339     cache->p     = 0;
340     cache->mask  = FTC_HASH_INITIAL_SIZE - 1;
341     cache->slack = FTC_HASH_INITIAL_SIZE * FTC_HASH_MAX_LOAD;
342 
343     FT_MEM_NEW_ARRAY( cache->buckets, FTC_HASH_INITIAL_SIZE * 2 );
344     return error;
345   }
346 
347 
348   static void
FTC_Cache_Clear(FTC_Cache cache)349   FTC_Cache_Clear( FTC_Cache  cache )
350   {
351     if ( cache && cache->buckets )
352     {
353       FTC_Manager  manager = cache->manager;
354       FT_UFast     i;
355       FT_UFast     count;
356 
357 
358       count = cache->p + cache->mask + 1;
359 
360       for ( i = 0; i < count; i++ )
361       {
362         FTC_Node  node = cache->buckets[i], next;
363 
364 
365         while ( node )
366         {
367           next        = node->link;
368           node->link  = NULL;
369 
370           /* remove node from mru list */
371           ftc_node_mru_unlink( node, manager );
372 
373           /* now finalize it */
374           manager->cur_weight -= cache->clazz.node_weight( node, cache );
375 
376           cache->clazz.node_free( node, cache );
377           node = next;
378         }
379         cache->buckets[i] = NULL;
380       }
381       ftc_cache_resize( cache );
382     }
383   }
384 
385 
386   FT_LOCAL_DEF( void )
ftc_cache_done(FTC_Cache cache)387   ftc_cache_done( FTC_Cache  cache )
388   {
389     if ( cache->memory )
390     {
391       FT_Memory  memory = cache->memory;
392 
393 
394       FTC_Cache_Clear( cache );
395 
396       FT_FREE( cache->buckets );
397       cache->mask  = 0;
398       cache->p     = 0;
399       cache->slack = 0;
400 
401       cache->memory = NULL;
402     }
403   }
404 
405 
406   FT_LOCAL_DEF( void )
FTC_Cache_Done(FTC_Cache cache)407   FTC_Cache_Done( FTC_Cache  cache )
408   {
409     ftc_cache_done( cache );
410   }
411 
412 
413   static void
ftc_cache_add(FTC_Cache cache,FT_Offset hash,FTC_Node node)414   ftc_cache_add( FTC_Cache  cache,
415                  FT_Offset  hash,
416                  FTC_Node   node )
417   {
418     node->hash        = hash;
419     node->cache_index = (FT_UShort)cache->index;
420     node->ref_count   = 0;
421 
422     ftc_node_hash_link( node, cache );
423     ftc_node_mru_link( node, cache->manager );
424 
425     {
426       FTC_Manager  manager = cache->manager;
427 
428 
429       manager->cur_weight += cache->clazz.node_weight( node, cache );
430 
431       if ( manager->cur_weight >= manager->max_weight )
432       {
433         node->ref_count++;
434         FTC_Manager_Compress( manager );
435         node->ref_count--;
436       }
437     }
438   }
439 
440 
441   FT_LOCAL_DEF( FT_Error )
FTC_Cache_NewNode(FTC_Cache cache,FT_Offset hash,FT_Pointer query,FTC_Node * anode)442   FTC_Cache_NewNode( FTC_Cache   cache,
443                      FT_Offset   hash,
444                      FT_Pointer  query,
445                      FTC_Node   *anode )
446   {
447     FT_Error  error;
448     FTC_Node  node;
449 
450 
451     /*
452      * We use the FTC_CACHE_TRYLOOP macros to support out-of-memory
453      * errors (OOM) correctly, i.e., by flushing the cache progressively
454      * in order to make more room.
455      */
456 
457     FTC_CACHE_TRYLOOP( cache )
458     {
459       error = cache->clazz.node_new( &node, query, cache );
460     }
461     FTC_CACHE_TRYLOOP_END( NULL )
462 
463     if ( error )
464       node = NULL;
465     else
466     {
467      /* don't assume that the cache has the same number of buckets, since
468       * our allocation request might have triggered global cache flushing
469       */
470       ftc_cache_add( cache, hash, node );
471     }
472 
473     *anode = node;
474     return error;
475   }
476 
477 
478 #ifndef FTC_INLINE
479 
480   FT_LOCAL_DEF( FT_Error )
FTC_Cache_Lookup(FTC_Cache cache,FT_Offset hash,FT_Pointer query,FTC_Node * anode)481   FTC_Cache_Lookup( FTC_Cache   cache,
482                     FT_Offset   hash,
483                     FT_Pointer  query,
484                     FTC_Node   *anode )
485   {
486     FTC_Node*  bucket;
487     FTC_Node*  pnode;
488     FTC_Node   node;
489     FT_Error   error        = FT_Err_Ok;
490     FT_Bool    list_changed = FALSE;
491 
492     FTC_Node_CompareFunc  compare = cache->clazz.node_compare;
493 
494 
495     if ( !cache || !anode )
496       return FT_THROW( Invalid_Argument );
497 
498     /* Go to the `top' node of the list sharing same masked hash */
499     bucket = pnode = FTC_NODE_TOP_FOR_HASH( cache, hash );
500 
501     /* Lookup a node with exactly same hash and queried properties.  */
502     /* NOTE: _nodcomp() may change the linked list to reduce memory. */
503     for (;;)
504     {
505       node = *pnode;
506       if ( !node )
507         goto NewNode;
508 
509       if ( node->hash == hash                           &&
510            compare( node, query, cache, &list_changed ) )
511         break;
512 
513       pnode = &node->link;
514     }
515 
516     if ( list_changed )
517     {
518       /* Update bucket by modified linked list */
519       bucket = pnode = FTC_NODE_TOP_FOR_HASH( cache, hash );
520 
521       /* Update pnode by modified linked list */
522       while ( *pnode != node )
523       {
524         if ( !*pnode )
525         {
526           FT_ERROR(( "FTC_Cache_Lookup: oops!!!  node missing\n" ));
527           goto NewNode;
528         }
529         else
530           pnode = &(*pnode)->link;
531       }
532     }
533 
534     /* Reorder the list to move the found node to the `top' */
535     if ( node != *bucket )
536     {
537       *pnode     = node->link;
538       node->link = *bucket;
539       *bucket    = node;
540     }
541 
542     /* move to head of MRU list */
543     {
544       FTC_Manager  manager = cache->manager;
545 
546 
547       if ( node != manager->nodes_list )
548         ftc_node_mru_up( node, manager );
549     }
550     *anode = node;
551 
552     return error;
553 
554   NewNode:
555     return FTC_Cache_NewNode( cache, hash, query, anode );
556   }
557 
558 #endif /* !FTC_INLINE */
559 
560 
561   FT_LOCAL_DEF( void )
FTC_Cache_RemoveFaceID(FTC_Cache cache,FTC_FaceID face_id)562   FTC_Cache_RemoveFaceID( FTC_Cache   cache,
563                           FTC_FaceID  face_id )
564   {
565     FT_UFast     i, count;
566     FTC_Manager  manager = cache->manager;
567     FTC_Node     frees   = NULL;
568 
569 
570     count = cache->p + cache->mask + 1;
571     for ( i = 0; i < count; i++ )
572     {
573       FTC_Node*  pnode = cache->buckets + i;
574 
575 
576       for (;;)
577       {
578         FTC_Node  node = *pnode;
579         FT_Bool   list_changed = FALSE;
580 
581 
582         if ( !node )
583           break;
584 
585         if ( cache->clazz.node_remove_faceid( node, face_id,
586                                               cache, &list_changed ) )
587         {
588           *pnode     = node->link;
589           node->link = frees;
590           frees      = node;
591         }
592         else
593           pnode = &node->link;
594       }
595     }
596 
597     /* remove all nodes in the free list */
598     while ( frees )
599     {
600       FTC_Node  node;
601 
602 
603       node  = frees;
604       frees = node->link;
605 
606       manager->cur_weight -= cache->clazz.node_weight( node, cache );
607       ftc_node_mru_unlink( node, manager );
608 
609       cache->clazz.node_free( node, cache );
610 
611       cache->slack++;
612     }
613 
614     ftc_cache_resize( cache );
615   }
616 
617 
618 /* END */
619