mirror of
git://sourceware.org/git/glibc.git
synced 2024-12-21 04:31:04 +08:00
235 lines
6.2 KiB
C
235 lines
6.2 KiB
C
/* Fully-inline hash table, used mainly for managing TLS descriptors.
|
|
Copyright (C) 1999-2014 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
Contributed by Alexandre Oliva <aoliva@redhat.com>
|
|
|
|
This file is derived from a 2003's version of libiberty's
|
|
hashtab.c, contributed by Vladimir Makarov (vmakarov@cygnus.com),
|
|
but with most adaptation points and support for deleting elements
|
|
removed.
|
|
|
|
The GNU C Library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
The GNU C Library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with the GNU C Library; if not, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#ifndef INLINE_HASHTAB_H
|
|
# define INLINE_HASHTAB_H 1
|
|
|
|
extern void weak_function free (void *ptr);
|
|
|
|
struct hashtab
|
|
{
|
|
/* Table itself. */
|
|
void **entries;
|
|
|
|
/* Current size (in entries) of the hash table */
|
|
size_t size;
|
|
|
|
/* Current number of elements. */
|
|
size_t n_elements;
|
|
|
|
/* Free function for the entries array. This may vary depending on
|
|
how early the array was allocated. If it is NULL, then the array
|
|
can't be freed. */
|
|
void (*free) (void *ptr);
|
|
};
|
|
|
|
inline static struct hashtab *
|
|
htab_create (void)
|
|
{
|
|
struct hashtab *ht = malloc (sizeof (struct hashtab));
|
|
|
|
if (! ht)
|
|
return NULL;
|
|
ht->size = 3;
|
|
ht->entries = malloc (sizeof (void *) * ht->size);
|
|
ht->free = free;
|
|
if (! ht->entries)
|
|
{
|
|
if (ht->free)
|
|
ht->free (ht);
|
|
return NULL;
|
|
}
|
|
|
|
ht->n_elements = 0;
|
|
|
|
memset (ht->entries, 0, sizeof (void *) * ht->size);
|
|
|
|
return ht;
|
|
}
|
|
|
|
/* This is only called from _dl_unmap, so it's safe to call
|
|
free(). */
|
|
inline static void
|
|
htab_delete (struct hashtab *htab)
|
|
{
|
|
int i;
|
|
|
|
for (i = htab->size - 1; i >= 0; i--)
|
|
free (htab->entries[i]);
|
|
|
|
if (htab->free)
|
|
htab->free (htab->entries);
|
|
free (htab);
|
|
}
|
|
|
|
/* Similar to htab_find_slot, but without several unwanted side effects:
|
|
- Does not call htab->eq_f when it finds an existing entry.
|
|
- Does not change the count of elements/searches/collisions in the
|
|
hash table.
|
|
This function also assumes there are no deleted entries in the table.
|
|
HASH is the hash value for the element to be inserted. */
|
|
|
|
inline static void **
|
|
find_empty_slot_for_expand (struct hashtab *htab, int hash)
|
|
{
|
|
size_t size = htab->size;
|
|
unsigned int index = hash % size;
|
|
void **slot = htab->entries + index;
|
|
int hash2;
|
|
|
|
if (! *slot)
|
|
return slot;
|
|
|
|
hash2 = 1 + hash % (size - 2);
|
|
for (;;)
|
|
{
|
|
index += hash2;
|
|
if (index >= size)
|
|
index -= size;
|
|
|
|
slot = htab->entries + index;
|
|
if (! *slot)
|
|
return slot;
|
|
}
|
|
}
|
|
|
|
/* The following function changes size of memory allocated for the
|
|
entries and repeatedly inserts the table elements. The occupancy
|
|
of the table after the call will be about 50%. Naturally the hash
|
|
table must already exist. Remember also that the place of the
|
|
table entries is changed. If memory allocation failures are allowed,
|
|
this function will return zero, indicating that the table could not be
|
|
expanded. If all goes well, it will return a non-zero value. */
|
|
|
|
inline static int
|
|
htab_expand (struct hashtab *htab, int (*hash_fn) (void *))
|
|
{
|
|
void **oentries;
|
|
void **olimit;
|
|
void **p;
|
|
void **nentries;
|
|
size_t nsize;
|
|
|
|
oentries = htab->entries;
|
|
olimit = oentries + htab->size;
|
|
|
|
/* Resize only when table after removal of unused elements is either
|
|
too full or too empty. */
|
|
if (htab->n_elements * 2 > htab->size)
|
|
nsize = _dl_higher_prime_number (htab->n_elements * 2);
|
|
else
|
|
nsize = htab->size;
|
|
|
|
nentries = calloc (sizeof (void *), nsize);
|
|
if (nentries == NULL)
|
|
return 0;
|
|
htab->entries = nentries;
|
|
htab->size = nsize;
|
|
|
|
p = oentries;
|
|
do
|
|
{
|
|
if (*p)
|
|
*find_empty_slot_for_expand (htab, hash_fn (*p))
|
|
= *p;
|
|
|
|
p++;
|
|
}
|
|
while (p < olimit);
|
|
|
|
/* Without recording the free corresponding to the malloc used to
|
|
allocate the table, we couldn't tell whether this was allocated
|
|
by the malloc() built into ld.so or the one in the main
|
|
executable or libc. Calling free() for something that was
|
|
allocated by the early malloc(), rather than the final run-time
|
|
malloc() could do Very Bad Things (TM). We will waste memory
|
|
allocated early as long as there's no corresponding free(), but
|
|
this isn't so much memory as to be significant. */
|
|
|
|
if (htab->free)
|
|
htab->free (oentries);
|
|
|
|
/* Use the free() corresponding to the malloc() above to free this
|
|
up. */
|
|
htab->free = free;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* This function searches for a hash table slot containing an entry
|
|
equal to the given element. To delete an entry, call this with
|
|
INSERT = 0, then call htab_clear_slot on the slot returned (possibly
|
|
after doing some checks). To insert an entry, call this with
|
|
INSERT = 1, then write the value you want into the returned slot.
|
|
When inserting an entry, NULL may be returned if memory allocation
|
|
fails. */
|
|
|
|
inline static void **
|
|
htab_find_slot (struct hashtab *htab, void *ptr, int insert,
|
|
int (*hash_fn)(void *), int (*eq_fn)(void *, void *))
|
|
{
|
|
unsigned int index;
|
|
int hash, hash2;
|
|
size_t size;
|
|
void **entry;
|
|
|
|
if (htab->size * 3 <= htab->n_elements * 4
|
|
&& htab_expand (htab, hash_fn) == 0)
|
|
return NULL;
|
|
|
|
hash = hash_fn (ptr);
|
|
|
|
size = htab->size;
|
|
index = hash % size;
|
|
|
|
entry = &htab->entries[index];
|
|
if (!*entry)
|
|
goto empty_entry;
|
|
else if (eq_fn (*entry, ptr))
|
|
return entry;
|
|
|
|
hash2 = 1 + hash % (size - 2);
|
|
for (;;)
|
|
{
|
|
index += hash2;
|
|
if (index >= size)
|
|
index -= size;
|
|
|
|
entry = &htab->entries[index];
|
|
if (!*entry)
|
|
goto empty_entry;
|
|
else if (eq_fn (*entry, ptr))
|
|
return entry;
|
|
}
|
|
|
|
empty_entry:
|
|
if (!insert)
|
|
return NULL;
|
|
|
|
htab->n_elements++;
|
|
return entry;
|
|
}
|
|
|
|
#endif /* INLINE_HASHTAB_H */
|