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