(root)/
freetype-2.13.2/
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 */