2 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
23 * @APPLE_LICENSE_HEADER_END@
27 Copyright 1989-1996 NeXT Software, Inc.
28 Created by Bertrand Serlet, Feb 89
31 #import <objc/hashtable2.h>
32 #import "objc-private.h"
34 /* Is this in the right spot ? <jf> */
42 /* In order to improve efficiency, buckets contain a pointer to an array or directly the data when the array size is 1 */
47 /* an optimization consists of storing directly data when count = 1 */
53 /* private data structure; may change */
55 /*************************************************************************
57 * Macros and utilities
59 *************************************************************************/
61 static unsigned log2 (unsigned x) { return (x<2) ? 0 : log2 (x>>1)+1; };
63 static unsigned exp2m1 (unsigned x) { return (1 << x) - 1; };
65 #define PTRSIZE sizeof(void *)
67 #define ALLOCTABLE(z) ((NXHashTable *) malloc_zone_malloc (z,sizeof (NXHashTable)))
68 #define ALLOCBUCKETS(z,nb)((HashBucket *) malloc_zone_calloc (z, nb, sizeof (HashBucket)))
69 #define ALLOCPAIRS(z,nb) ((const void **) malloc_zone_calloc (z, nb, sizeof (void *)))
71 /* iff necessary this modulo can be optimized since the nbBuckets is of the form 2**n-1 */
72 #define BUCKETOF(table, data) (((HashBucket *)table->buckets)+((*table->prototype->hash)(table->info, data) % table->nbBuckets))
74 #define ISEQUAL(table, data1, data2) ((data1 == data2) || (*table->prototype->isEqual)(table->info, data1, data2))
75 /* beware of double evaluation */
77 /*************************************************************************
79 * Global data and bootstrap
81 *************************************************************************/
83 static int isEqualPrototype (const void *info, const void *data1, const void *data2) {
84 NXHashTablePrototype *proto1 = (NXHashTablePrototype *) data1;
85 NXHashTablePrototype *proto2 = (NXHashTablePrototype *) data2;
87 return (proto1->hash == proto2->hash) && (proto1->isEqual == proto2->isEqual) && (proto1->free == proto2->free) && (proto1->style == proto2->style);
90 static uarith_t hashPrototype (const void *info, const void *data) {
91 NXHashTablePrototype *proto = (NXHashTablePrototype *) data;
93 return NXPtrHash(info, proto->hash) ^ NXPtrHash(info, proto->isEqual) ^ NXPtrHash(info, proto->free) ^ (uarith_t) proto->style;
96 void NXNoEffectFree (const void *info, void *data) {};
98 static NXHashTablePrototype protoPrototype = {
99 hashPrototype, isEqualPrototype, NXNoEffectFree, 0
102 static NXHashTable *prototypes = NULL;
103 /* table of all prototypes */
105 static void bootstrap (void) {
107 prototypes = ALLOCTABLE (malloc_default_zone());
108 prototypes->prototype = &protoPrototype;
109 prototypes->count = 1;
110 prototypes->nbBuckets = 1; /* has to be 1 so that the right bucket is 0 */
111 prototypes->buckets = ALLOCBUCKETS(malloc_default_zone(), 1);
112 prototypes->info = NULL;
113 ((HashBucket *) prototypes->buckets)[0].count = 1;
114 ((HashBucket *) prototypes->buckets)[0].elements.one = &protoPrototype;
117 int NXPtrIsEqual (const void *info, const void *data1, const void *data2) {
118 return data1 == data2;
121 /*************************************************************************
125 *************************************************************************/
127 NXHashTable *NXCreateHashTable (NXHashTablePrototype prototype, unsigned capacity, const void *info) {
128 return NXCreateHashTableFromZone(prototype, capacity, info, malloc_default_zone());
131 NXHashTable *NXCreateHashTableFromZone (NXHashTablePrototype prototype, unsigned capacity, const void *info, void *z) {
133 NXHashTablePrototype *proto;
135 table = ALLOCTABLE(z);
136 if (! prototypes) bootstrap ();
137 if (! prototype.hash) prototype.hash = NXPtrHash;
138 if (! prototype.isEqual) prototype.isEqual = NXPtrIsEqual;
139 if (! prototype.free) prototype.free = NXNoEffectFree;
140 if (prototype.style) {
141 _objc_syslog ("*** NXCreateHashTable: invalid style\n");
144 proto = NXHashGet (prototypes, &prototype);
147 = (NXHashTablePrototype *) malloc_zone_malloc (malloc_default_zone(),
148 sizeof (NXHashTablePrototype));
149 bcopy ((const char*)&prototype, (char*)proto, sizeof (NXHashTablePrototype));
150 (void) NXHashInsert (prototypes, proto);
151 proto = NXHashGet (prototypes, &prototype);
153 _objc_syslog ("*** NXCreateHashTable: bug\n");
157 table->prototype = proto; table->count = 0; table->info = info;
158 table->nbBuckets = exp2m1 (log2 (capacity)+1);
159 table->buckets = ALLOCBUCKETS(z, table->nbBuckets);
163 static void freeBucketPairs (void (*freeProc)(const void *info, void *data), HashBucket bucket, const void *info) {
164 unsigned j = bucket.count;
168 (*freeProc) (info, (void *) bucket.elements.one);
171 pairs = bucket.elements.many;
173 (*freeProc) (info, (void *) *pairs);
176 free (bucket.elements.many);
179 static void freeBuckets (NXHashTable *table, int freeObjects) {
180 unsigned i = table->nbBuckets;
181 HashBucket *buckets = (HashBucket *) table->buckets;
184 if (buckets->count) {
185 freeBucketPairs ((freeObjects) ? table->prototype->free : NXNoEffectFree, *buckets, table->info);
187 buckets->elements.one = NULL;
193 void NXFreeHashTable (NXHashTable *table) {
194 freeBuckets (table, YES);
195 free (table->buckets);
199 void NXEmptyHashTable (NXHashTable *table) {
200 freeBuckets (table, NO);
204 void NXResetHashTable (NXHashTable *table) {
205 freeBuckets (table, YES);
209 BOOL NXIsEqualHashTable (NXHashTable *table1, NXHashTable *table2) {
210 if (table1 == table2) return YES;
211 if (NXCountHashTable (table1) != NXCountHashTable (table2)) return NO;
214 NXHashState state = NXInitHashState (table1);
215 while (NXNextHashState (table1, &state, &data)) {
216 if (! NXHashMember (table2, data)) return NO;
222 BOOL NXCompareHashTables (NXHashTable *table1, NXHashTable *table2) {
223 if (table1 == table2) return YES;
224 if (NXCountHashTable (table1) != NXCountHashTable (table2)) return NO;
227 NXHashState state = NXInitHashState (table1);
228 while (NXNextHashState (table1, &state, &data)) {
229 if (! NXHashMember (table2, data)) return NO;
235 NXHashTable *NXCopyHashTable (NXHashTable *table) {
237 NXHashState state = NXInitHashState (table);
239 void *z = malloc_zone_from_ptr(table);
242 new->prototype = table->prototype; new->count = 0;
243 new->info = table->info;
244 new->nbBuckets = table->nbBuckets;
245 new->buckets = ALLOCBUCKETS(z, new->nbBuckets);
246 while (NXNextHashState (table, &state, &data))
247 (void) NXHashInsert (new, data);
251 unsigned NXCountHashTable (NXHashTable *table) {
255 int NXHashMember (NXHashTable *table, const void *data) {
256 HashBucket *bucket = BUCKETOF(table, data);
257 unsigned j = bucket->count;
262 return ISEQUAL(table, data, bucket->elements.one);
264 pairs = bucket->elements.many;
266 /* we don't cache isEqual because lists are short */
267 if (ISEQUAL(table, data, *pairs)) return 1;
273 void *NXHashGet (NXHashTable *table, const void *data) {
274 HashBucket *bucket = BUCKETOF(table, data);
275 unsigned j = bucket->count;
278 if (! j) return NULL;
280 return ISEQUAL(table, data, bucket->elements.one)
281 ? (void *) bucket->elements.one : NULL;
283 pairs = bucket->elements.many;
285 /* we don't cache isEqual because lists are short */
286 if (ISEQUAL(table, data, *pairs)) return (void *) *pairs;
292 static void _NXHashRehash (NXHashTable *table) {
293 /* Rehash: we create a pseudo table pointing really to the old guys,
294 extend self, copy the old pairs, and free the pseudo table */
298 void *z = malloc_zone_from_ptr(table);
301 old->prototype = table->prototype; old->count = table->count;
302 old->nbBuckets = table->nbBuckets; old->buckets = table->buckets;
303 table->nbBuckets += table->nbBuckets + 1; /* 2 times + 1 */
304 table->count = 0; table->buckets = ALLOCBUCKETS(z, table->nbBuckets);
305 state = NXInitHashState (old);
306 while (NXNextHashState (old, &state, &aux))
307 (void) NXHashInsert (table, aux);
308 freeBuckets (old, NO);
309 if (old->count != table->count)
310 _objc_syslog("*** hashtable: count differs after rehashing; probably indicates a broken invariant: there are x and y such as isEqual(x, y) is TRUE but hash(x) != hash (y)\n");
315 void *NXHashInsert (NXHashTable *table, const void *data) {
316 HashBucket *bucket = BUCKETOF(table, data);
317 unsigned j = bucket->count;
320 void *z = malloc_zone_from_ptr(table);
323 bucket->count++; bucket->elements.one = data;
328 if (ISEQUAL(table, data, bucket->elements.one)) {
329 const void *old = bucket->elements.one;
330 bucket->elements.one = data;
333 new = ALLOCPAIRS(z, 2);
334 new[1] = bucket->elements.one;
336 bucket->count++; bucket->elements.many = new;
338 if (table->count > table->nbBuckets) _NXHashRehash (table);
341 pairs = bucket->elements.many;
343 /* we don't cache isEqual because lists are short */
344 if (ISEQUAL(table, data, *pairs)) {
345 const void *old = *pairs;
351 /* we enlarge this bucket; and put new data in front */
352 new = ALLOCPAIRS(z, bucket->count+1);
353 if (bucket->count) bcopy ((const char*)bucket->elements.many, (char*)(new+1), bucket->count * PTRSIZE);
355 free (bucket->elements.many);
356 bucket->count++; bucket->elements.many = new;
358 if (table->count > table->nbBuckets) _NXHashRehash (table);
362 void *NXHashInsertIfAbsent (NXHashTable *table, const void *data) {
363 HashBucket *bucket = BUCKETOF(table, data);
364 unsigned j = bucket->count;
367 void *z = malloc_zone_from_ptr(table);
370 bucket->count++; bucket->elements.one = data;
372 return (void *) data;
375 if (ISEQUAL(table, data, bucket->elements.one))
376 return (void *) bucket->elements.one;
377 new = ALLOCPAIRS(z, 2);
378 new[1] = bucket->elements.one;
380 bucket->count++; bucket->elements.many = new;
382 if (table->count > table->nbBuckets) _NXHashRehash (table);
383 return (void *) data;
385 pairs = bucket->elements.many;
387 /* we don't cache isEqual because lists are short */
388 if (ISEQUAL(table, data, *pairs))
389 return (void *) *pairs;
392 /* we enlarge this bucket; and put new data in front */
393 new = ALLOCPAIRS(z, bucket->count+1);
394 if (bucket->count) bcopy ((const char*)bucket->elements.many, (char*)(new+1), bucket->count * PTRSIZE);
396 free (bucket->elements.many);
397 bucket->count++; bucket->elements.many = new;
399 if (table->count > table->nbBuckets) _NXHashRehash (table);
400 return (void *) data;
403 void *NXHashRemove (NXHashTable *table, const void *data) {
404 HashBucket *bucket = BUCKETOF(table, data);
405 unsigned j = bucket->count;
408 void *z = malloc_zone_from_ptr(table);
410 if (! j) return NULL;
412 if (! ISEQUAL(table, data, bucket->elements.one)) return NULL;
413 data = bucket->elements.one;
414 table->count--; bucket->count--; bucket->elements.one = NULL;
415 return (void *) data;
417 pairs = bucket->elements.many;
419 if (ISEQUAL(table, data, pairs[0])) {
420 bucket->elements.one = pairs[1]; data = pairs[0];
422 else if (ISEQUAL(table, data, pairs[1])) {
423 bucket->elements.one = pairs[0]; data = pairs[1];
427 table->count--; bucket->count--;
428 return (void *) data;
431 if (ISEQUAL(table, data, *pairs)) {
433 /* we shrink this bucket */
434 new = (bucket->count-1)
435 ? ALLOCPAIRS(z, bucket->count-1) : NULL;
436 if (bucket->count-1 != j)
437 bcopy ((const char*)bucket->elements.many, (char*)new, PTRSIZE*(bucket->count-j-1));
439 bcopy ((const char*)(bucket->elements.many + bucket->count-j), (char*)(new+bucket->count-j-1), PTRSIZE*j);
440 free (bucket->elements.many);
441 table->count--; bucket->count--; bucket->elements.many = new;
442 return (void *) data;
449 NXHashState NXInitHashState (NXHashTable *table) {
452 state.i = table->nbBuckets;
457 int NXNextHashState (NXHashTable *table, NXHashState *state, void **data) {
458 HashBucket *buckets = (HashBucket *) table->buckets;
460 while (state->j == 0) {
461 if (state->i == 0) return NO;
462 state->i--; state->j = buckets[state->i].count;
466 *data = (void *) ((buckets->count == 1)
467 ? buckets->elements.one : buckets->elements.many[state->j]);
471 /*************************************************************************
475 *************************************************************************/
477 uarith_t NXPtrHash (const void *info, const void *data) {
478 return (((uarith_t) data) >> 16) ^ ((uarith_t) data);
481 uarith_t NXStrHash (const void *info, const void *data) {
482 register uarith_t hash = 0;
483 register unsigned char *s = (unsigned char *) data;
484 /* unsigned to avoid a sign-extend */
485 /* unroll the loop */
487 if (*s == '\0') break;
488 hash ^= (uarith_t) *s++;
489 if (*s == '\0') break;
490 hash ^= (uarith_t) *s++ << 8;
491 if (*s == '\0') break;
492 hash ^= (uarith_t) *s++ << 16;
493 if (*s == '\0') break;
494 hash ^= (uarith_t) *s++ << 24;
499 int NXStrIsEqual (const void *info, const void *data1, const void *data2) {
500 if (data1 == data2) return YES;
501 if (! data1) return ! strlen ((char *) data2);
502 if (! data2) return ! strlen ((char *) data1);
503 if (((char *) data1)[0] != ((char *) data2)[0]) return NO;
504 return (strcmp ((char *) data1, (char *) data2)) ? NO : YES;
507 void NXReallyFree (const void *info, void *data) {
511 /* All the following functions are really private, made non-static only for the benefit of shlibs */
512 static uarith_t hashPtrStructKey (const void *info, const void *data) {
513 return NXPtrHash(info, *((void **) data));
516 static int isEqualPtrStructKey (const void *info, const void *data1, const void *data2) {
517 return NXPtrIsEqual (info, *((void **) data1), *((void **) data2));
520 static uarith_t hashStrStructKey (const void *info, const void *data) {
521 return NXStrHash(info, *((char **) data));
524 static int isEqualStrStructKey (const void *info, const void *data1, const void *data2) {
525 return NXStrIsEqual (info, *((char **) data1), *((char **) data2));
528 const NXHashTablePrototype NXPtrPrototype = {
529 NXPtrHash, NXPtrIsEqual, NXNoEffectFree, 0
532 const NXHashTablePrototype NXStrPrototype = {
533 NXStrHash, NXStrIsEqual, NXNoEffectFree, 0
536 const NXHashTablePrototype NXPtrStructKeyPrototype = {
537 hashPtrStructKey, isEqualPtrStructKey, NXReallyFree, 0
540 const NXHashTablePrototype NXStrStructKeyPrototype = {
541 hashStrStructKey, isEqualStrStructKey, NXReallyFree, 0
544 /*************************************************************************
548 *************************************************************************/
550 /* the implementation could be made faster at the expense of memory if the size of the strings were kept around */
551 static NXHashTable *uniqueStrings = NULL;
553 /* this is based on most apps using a few K of strings, and an average string size of 15 using sqrt(2*dataAlloced*perChunkOverhead) */
554 #define CHUNK_SIZE 360
556 static int accessUniqueString = 0;
558 static char *z = NULL;
559 static vm_size_t zSize = 0;
560 static mutex_t lock = (mutex_t)0;
562 static const char *CopyIntoReadOnly (const char *str) {
563 unsigned int len = strlen (str) + 1;
566 if (len > CHUNK_SIZE/2) { /* dont let big strings waste space */
568 bcopy (str, new, len);
573 lock = (mutex_t)mutex_alloc ();
579 zSize = CHUNK_SIZE *((len + CHUNK_SIZE - 1) / CHUNK_SIZE);
580 /* not enough room, we try to allocate. If no room left, too bad */
585 bcopy (str, new, len);
592 NXAtom NXUniqueString (const char *buffer) {
593 const char *previous;
595 if (! buffer) return buffer;
596 accessUniqueString++;
598 uniqueStrings = NXCreateHashTable (NXStrPrototype, 0, NULL);
599 previous = (const char *) NXHashGet (uniqueStrings, buffer);
600 if (previous) return previous;
601 previous = CopyIntoReadOnly (buffer);
602 if (NXHashInsert (uniqueStrings, previous)) {
603 _objc_syslog ("*** NXUniqueString: invariant broken\n");
609 NXAtom NXUniqueStringNoCopy (const char *string) {
610 accessUniqueString++;
612 uniqueStrings = NXCreateHashTable (NXStrPrototype, 0, NULL);
613 return (const char *) NXHashInsertIfAbsent (uniqueStrings, string);
618 NXAtom NXUniqueStringWithLength (const char *buffer, int length) {
621 char stackBuf[BUF_SIZE];
623 if (length+1 > BUF_SIZE)
624 nullTermStr = malloc (length+1);
626 nullTermStr = stackBuf;
627 bcopy (buffer, nullTermStr, length);
628 nullTermStr[length] = '\0';
629 atom = NXUniqueString (nullTermStr);
630 if (length+1 > BUF_SIZE)
635 char *NXCopyStringBufferFromZone (const char *str, void *z) {
636 return strcpy ((char *) malloc_zone_malloc(z, strlen (str) + 1), str);
639 char *NXCopyStringBuffer (const char *str) {
640 return NXCopyStringBufferFromZone(str, malloc_default_zone());