Copyright 1987, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1998, 1999,
2000, 2001, 2002, 2003, 2005
Free Software Foundation, Inc.
This file is part of GAS, the GNU Assembler.
GAS is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GAS 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with GAS; see the file COPYING. If not, write to the Free
Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
02110-1301, USA. */
the old hash table code, which was fairly bad. This is based on
the hash table code in BFD, but optimized slightly for the
assembler. The assembler does not need to derive structures that
are stored in the hash table. Instead, it always stores a pointer.
The assembler uses the hash table mostly to store symbols, and we
don't need to confuse the symbol structure with a hash table
structure. */
#include "as.h"
#include "safe-ctype.h"
#include "obstack.h"
struct hash_entry {
struct hash_entry *next;
const char *string;
table. */
unsigned long hash;
PTR data;
};
struct hash_control {
struct hash_entry **table;
unsigned int size;
struct obstack memory;
#ifdef HASH_STATISTICS
unsigned long lookups;
unsigned long hash_compares;
unsigned long string_compares;
unsigned long insertions;
unsigned long replacements;
unsigned long deletions;
#endif
};
Note this value can be reduced to 4051 by using the command line
switch --reduce-memory-overheads, or set to other values by using
the --hash-size=<NUMBER> switch. */
static unsigned long gas_hash_table_size = 65537;
void
set_gas_hash_table_size (unsigned long size)
{
gas_hash_table_size = size;
}
static unsigned long
get_gas_hash_table_size (void)
{
static const unsigned long hash_size_primes[] =
{
1021, 4051, 8599, 16699, 65537
};
unsigned int index;
FIXME: This could be a more sophisticated algorithm,
but is it really worth implementing it ? */
for (index = 0; index < ARRAY_SIZE (hash_size_primes) - 1; ++index)
if (gas_hash_table_size <= hash_size_primes[index])
break;
return hash_size_primes[index];
}
struct hash_control *
hash_new (void)
{
unsigned long size;
unsigned long alloc;
struct hash_control *ret;
size = get_gas_hash_table_size ();
ret = xmalloc (sizeof *ret);
obstack_begin (&ret->memory, chunksize);
alloc = size * sizeof (struct hash_entry *);
ret->table = obstack_alloc (&ret->memory, alloc);
memset (ret->table, 0, alloc);
ret->size = size;
#ifdef HASH_STATISTICS
ret->lookups = 0;
ret->hash_compares = 0;
ret->string_compares = 0;
ret->insertions = 0;
ret->replacements = 0;
ret->deletions = 0;
#endif
return ret;
}
void
hash_die (struct hash_control *table)
{
obstack_free (&table->memory, 0);
free (table);
}
hash_entry, or NULL if the string is not in the table. If PLIST is
not NULL, this sets *PLIST to point to the start of the list which
would hold this hash entry. If PHASH is not NULL, this sets *PHASH
to the hash code for KEY.
Each time we look up a string, we move it to the start of the list
for its hash code, to take advantage of referential locality. */
static struct hash_entry *
hash_lookup (struct hash_control *table, const char *key, size_t len,
struct hash_entry ***plist, unsigned long *phash)
{
unsigned long hash;
size_t n;
unsigned int c;
unsigned int index;
struct hash_entry **list;
struct hash_entry *p;
struct hash_entry *prev;
#ifdef HASH_STATISTICS
++table->lookups;
#endif
hash = 0;
for (n = 0; n < len; n++)
{
c = key[n];
hash += c + (c << 17);
hash ^= hash >> 2;
}
hash += len + (len << 17);
hash ^= hash >> 2;
if (phash != NULL)
*phash = hash;
index = hash % table->size;
list = table->table + index;
if (plist != NULL)
*plist = list;
prev = NULL;
for (p = *list; p != NULL; p = p->next)
{
#ifdef HASH_STATISTICS
++table->hash_compares;
#endif
if (p->hash == hash)
{
#ifdef HASH_STATISTICS
++table->string_compares;
#endif
if (strncmp (p->string, key, len) == 0 && p->string[len] == '\0')
{
if (prev != NULL)
{
prev->next = p->next;
p->next = *list;
*list = p;
}
return p;
}
}
prev = p;
}
return NULL;
}
On error, it returns a printable string indicating the error. It
is considered to be an error if the entry already exists in the
hash table. */
const char *
hash_insert (struct hash_control *table, const char *key, PTR value)
{
struct hash_entry *p;
struct hash_entry **list;
unsigned long hash;
p = hash_lookup (table, key, strlen (key), &list, &hash);
if (p != NULL)
return "exists";
#ifdef HASH_STATISTICS
++table->insertions;
#endif
p = (struct hash_entry *) obstack_alloc (&table->memory, sizeof (*p));
p->string = key;
p->hash = hash;
p->data = value;
p->next = *list;
*list = p;
return NULL;
}
success. On error, it returns a printable string indicating the
error. If an entry already exists, its value is replaced. */
const char *
hash_jam (struct hash_control *table, const char *key, PTR value)
{
struct hash_entry *p;
struct hash_entry **list;
unsigned long hash;
p = hash_lookup (table, key, strlen (key), &list, &hash);
if (p != NULL)
{
#ifdef HASH_STATISTICS
++table->replacements;
#endif
p->data = value;
}
else
{
#ifdef HASH_STATISTICS
++table->insertions;
#endif
p = (struct hash_entry *) obstack_alloc (&table->memory, sizeof (*p));
p->string = key;
p->hash = hash;
p->data = value;
p->next = *list;
*list = p;
}
return NULL;
}
value stored for the entry. If the entry is not found in the hash
table, this does nothing and returns NULL. */
PTR
hash_replace (struct hash_control *table, const char *key, PTR value)
{
struct hash_entry *p;
PTR ret;
p = hash_lookup (table, key, strlen (key), NULL, NULL);
if (p == NULL)
return NULL;
#ifdef HASH_STATISTICS
++table->replacements;
#endif
ret = p->data;
p->data = value;
return ret;
}
if the entry is not found. */
PTR
hash_find (struct hash_control *table, const char *key)
{
struct hash_entry *p;
p = hash_lookup (table, key, strlen (key), NULL, NULL);
if (p == NULL)
return NULL;
return p->data;
}
NUL-terminated. */
PTR
hash_find_n (struct hash_control *table, const char *key, size_t len)
{
struct hash_entry *p;
p = hash_lookup (table, key, len, NULL, NULL);
if (p == NULL)
return NULL;
return p->data;
}
for that entry, or NULL if there is no such entry. */
PTR
hash_delete (struct hash_control *table, const char *key)
{
struct hash_entry *p;
struct hash_entry **list;
p = hash_lookup (table, key, strlen (key), &list, NULL);
if (p == NULL)
return NULL;
if (p != *list)
abort ();
#ifdef HASH_STATISTICS
++table->deletions;
#endif
*list = p->next;
ever starts deleting hash table entries in a big way, this will
have to change. */
return p->data;
}
hash table. */
void
hash_traverse (struct hash_control *table,
void (*pfn) (const char *key, PTR value))
{
unsigned int i;
for (i = 0; i < table->size; ++i)
{
struct hash_entry *p;
for (p = table->table[i]; p != NULL; p = p->next)
(*pfn) (p->string, p->data);
}
}
name of the hash table, used for printing a header. */
void
hash_print_statistics (FILE *f ATTRIBUTE_UNUSED,
const char *name ATTRIBUTE_UNUSED,
struct hash_control *table ATTRIBUTE_UNUSED)
{
#ifdef HASH_STATISTICS
unsigned int i;
unsigned long total;
unsigned long empty;
fprintf (f, "%s hash statistics:\n", name);
fprintf (f, "\t%lu lookups\n", table->lookups);
fprintf (f, "\t%lu hash comparisons\n", table->hash_compares);
fprintf (f, "\t%lu string comparisons\n", table->string_compares);
fprintf (f, "\t%lu insertions\n", table->insertions);
fprintf (f, "\t%lu replacements\n", table->replacements);
fprintf (f, "\t%lu deletions\n", table->deletions);
total = 0;
empty = 0;
for (i = 0; i < table->size; ++i)
{
struct hash_entry *p;
if (table->table[i] == NULL)
++empty;
else
{
for (p = table->table[i]; p != NULL; p = p->next)
++total;
}
}
fprintf (f, "\t%g average chain length\n", (double) total / table->size);
fprintf (f, "\t%lu empty slots\n", empty);
#endif
}
#ifdef TEST
#define TABLES (6)
#define STATBUFSIZE (12)
int statbuf[STATBUFSIZE];
char answer[100];
char *hashtable[TABLES];
char *h;
char **pp;
char *p;
char *name;
char *value;
int size;
int used;
char command;
int number;
int
main ()
{
void applicatee ();
void destroy ();
char *what ();
int *ip;
number = 0;
h = 0;
printf ("type h <RETURN> for help\n");
for (;;)
{
printf ("hash_test command: ");
gets (answer);
command = answer[0];
command = TOLOWER (command);
switch (command)
{
case '#':
printf ("old hash table #=%d.\n", number);
whattable ();
break;
case '?':
for (pp = hashtable; pp < hashtable + TABLES; pp++)
{
printf ("address of hash table #%d control block is %xx\n",
pp - hashtable, *pp);
}
break;
case 'a':
hash_traverse (h, applicatee);
break;
case 'd':
hash_traverse (h, destroy);
hash_die (h);
break;
case 'f':
p = hash_find (h, name = what ("symbol"));
printf ("value of \"%s\" is \"%s\"\n", name, p ? p : "NOT-PRESENT");
break;
case 'h':
printf ("# show old, select new default hash table number\n");
printf ("? display all hashtable control block addresses\n");
printf ("a apply a simple display-er to each symbol in table\n");
printf ("d die: destroy hashtable\n");
printf ("f find value of nominated symbol\n");
printf ("h this help\n");
printf ("i insert value into symbol\n");
printf ("j jam value into symbol\n");
printf ("n new hashtable\n");
printf ("r replace a value with another\n");
printf ("s say what %% of table is used\n");
printf ("q exit this program\n");
printf ("x delete a symbol from table, report its value\n");
break;
case 'i':
p = hash_insert (h, name = what ("symbol"), value = what ("value"));
if (p)
{
printf ("symbol=\"%s\" value=\"%s\" error=%s\n", name, value,
p);
}
break;
case 'j':
p = hash_jam (h, name = what ("symbol"), value = what ("value"));
if (p)
{
printf ("symbol=\"%s\" value=\"%s\" error=%s\n", name, value, p);
}
break;
case 'n':
h = hashtable[number] = (char *) hash_new ();
break;
case 'q':
exit (EXIT_SUCCESS);
case 'r':
p = hash_replace (h, name = what ("symbol"), value = what ("value"));
printf ("old value was \"%s\"\n", p ? p : "{}");
break;
case 's':
hash_say (h, statbuf, STATBUFSIZE);
for (ip = statbuf; ip < statbuf + STATBUFSIZE; ip++)
{
printf ("%d ", *ip);
}
printf ("\n");
break;
case 'x':
p = hash_delete (h, name = what ("symbol"));
printf ("old value was \"%s\"\n", p ? p : "{}");
break;
default:
printf ("I can't understand command \"%c\"\n", command);
break;
}
}
}
char *
what (description)
char *description;
{
printf (" %s : ", description);
gets (answer);
return xstrdup (answer);
}
void
destroy (string, value)
char *string;
char *value;
{
free (string);
free (value);
}
void
applicatee (string, value)
char *string;
char *value;
{
printf ("%.20s-%.20s\n", string, value);
}
Also determine h: points to hash_control. */
void
whattable ()
{
for (;;)
{
printf (" what hash table (%d:%d) ? ", 0, TABLES - 1);
gets (answer);
sscanf (answer, "%d", &number);
if (number >= 0 && number < TABLES)
{
h = hashtable[number];
if (!h)
{
printf ("warning: current hash-table-#%d. has no hash-control\n", number);
}
return;
}
else
{
printf ("invalid hash table number: %d\n", number);
}
}
}
#endif