Added hopscotch table implementation and rewrote the class table to use it. This is done so that the class table and protocol tables can share the same code (currently there is no protocol table).

Note that concurrent resizing has not yet been implemented.  That means that there is a hard limit on the number of classes that can be loaded.  This is currently set to quite a small number for testing, to stress the hash table.  If you're experiencing problems as a result, then please let me know and I will increase it.
main
theraven 16 years ago
parent 9f602c083c
commit bdf97cf64e

@ -15,9 +15,12 @@ COPYING.RUNTIME contains the GCC runtime exemption to the GNU GPL.
Some individual files retain their original MIT license. These are:
blocks_runtime.m
class_table.c
hash_table.h
lock.h
mutation.m
runtime.c
string_hash.c
sync.m
objc/blocks_runtime.h
objc/runtime.h

@ -19,6 +19,7 @@ libobjc_OBJC_FILES = \
libobjc_C_FILES = \
archive.c\
class.c\
class_table.c\
encoding.c\
exception.c\
gc.c\
@ -62,7 +63,7 @@ libobjc_CPPFLAGS += -D__OBJC_RUNTIME_INTERNAL__=1 -D_XOPEN_SOURCE=500
# Note to Riccardo. Please do not 'fix' C99isms in this. The new ABI is only
# useful on compilers that support C99 (currently only clang), so there is no
# benefit from supporting platforms with no C99 compiler.
libobjc_CFLAGS += -Werror -std=c99 -g -fexceptions
libobjc_CFLAGS += -Werror -std=c99 -g -fexceptions #-fno-inline
libobjc_OBJCFLAGS += -g -std=c99
libobjc_LDFLAGS += -g

@ -92,7 +92,10 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
#include "objc/objc.h"
#include "objc/objc-api.h"
#include "objc/thr.h"
#include "lock.h"
#include <stdlib.h>
/* We use a table which maps a class name to the corresponding class
* pointer. The first part of this file defines this table, and
@ -101,321 +104,26 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
* classes by using the functions provided in the first part to manage
* the table. */
/**
** Class Table Internals
**/
/* A node holding a class */
typedef struct class_node
{
struct class_node *next; /* Pointer to next entry on the list.
NULL indicates end of list. */
const char *name; /* The class name string */
int length; /* The class name string length */
Class pointer; /* The Class pointer */
} *class_node_ptr;
/* A table containing classes is a class_node_ptr (pointing to the
first entry in the table - if it is NULL, then the table is
empty). */
/* We have 1024 tables. Each table contains all class names which
have the same hash (which is a number between 0 and 1023). To look
up a class_name, we compute its hash, and get the corresponding
table. Once we have the table, we simply compare strings directly
till we find the one which we want (using the length first). The
number of tables is quite big on purpose (a normal big application
has less than 1000 classes), so that you shouldn't normally get any
collisions, and get away with a single comparison (which we can't
avoid since we need to know that you have got the right thing). */
#define CLASS_TABLE_SIZE 1024
#define CLASS_TABLE_MASK 1023
#include "lock.h"
static class_node_ptr class_table_array[CLASS_TABLE_SIZE];
static int __class_table_was_set_up = 0;
/* The table writing mutex - we lock on writing to avoid conflicts
between different writers, but we read without locks. That is
possible because we assume pointer assignment to be an atomic
operation. */
static mutex_t __class_table_lock;
/* CLASS_TABLE_HASH is how we compute the hash of a class name. It is
a macro - *not* a function - arguments *are* modified directly.
INDEX should be a variable holding an int;
HASH should be a variable holding an int;
CLASS_NAME should be a variable holding a (char *) to the class_name.
After the macro is executed, INDEX contains the length of the
string, and HASH the computed hash of the string; CLASS_NAME is
untouched. */
#define CLASS_TABLE_HASH(INDEX, HASH, CLASS_NAME) \
HASH = 0; \
for (INDEX = 0; CLASS_NAME[INDEX] != '\0'; INDEX++) \
{ \
HASH = (HASH << 4) ^ (HASH >> 28) ^ CLASS_NAME[INDEX]; \
} \
\
HASH = (HASH ^ (HASH >> 10) ^ (HASH >> 20)) & CLASS_TABLE_MASK;
/* Insert a class in the table (used when a new class is registered). */
static void
class_table_insert (const char *class_name, Class class_pointer)
{
int hash, length;
class_node_ptr new_node;
/* Find out the class name's hash and length. */
CLASS_TABLE_HASH (length, hash, class_name);
/* Prepare the new node holding the class. */
new_node = objc_malloc (sizeof (struct class_node));
new_node->name = class_name;
new_node->length = length;
new_node->pointer = class_pointer;
/* Lock the table for modifications. */
LOCK(&__class_table_lock);
/* Insert the new node in the table at the beginning of the table at
class_table_array[hash]. */
new_node->next = class_table_array[hash];
class_table_array[hash] = new_node;
UNLOCK(&__class_table_lock);
}
/* Replace a class in the table (used only by poseAs:). */
static void
class_table_replace (Class old_class_pointer, Class new_class_pointer)
{
int hash;
class_node_ptr node;
LOCK(&__class_table_lock);
hash = 0;
node = class_table_array[hash];
while (hash < CLASS_TABLE_SIZE)
{
if (node == NULL)
{
hash++;
if (hash < CLASS_TABLE_SIZE)
{
node = class_table_array[hash];
}
}
else
{
Class class1 = node->pointer;
if (class1 == old_class_pointer)
{
node->pointer = new_class_pointer;
}
node = node->next;
}
}
UNLOCK(&__class_table_lock);
}
void class_table_insert (Class class_pointer);
/* Get a class from the table. This does not need mutex protection.
Currently, this function is called each time you call a static
method, this is why it must be very fast. */
static inline Class
class_table_get_safe (const char *class_name)
{
class_node_ptr node;
int length, hash;
/* Compute length and hash. */
CLASS_TABLE_HASH (length, hash, class_name);
node = class_table_array[hash];
if (node != NULL)
{
do
{
if (node->length == length)
{
/* Compare the class names. */
if (strcmp(node->name, class_name) == 0)
{
return node->pointer;
}
}
}
while ((node = node->next) != NULL);
}
return Nil;
}
/* Enumerate over the class table. */
struct class_table_enumerator
{
int hash;
class_node_ptr node;
};
static Class
class_table_next (struct class_table_enumerator **e)
{
struct class_table_enumerator *enumerator = *e;
class_node_ptr next;
if (enumerator == NULL)
{
*e = objc_malloc (sizeof (struct class_table_enumerator));
enumerator = *e;
enumerator->hash = 0;
enumerator->node = NULL;
next = class_table_array[enumerator->hash];
}
else
{
next = enumerator->node->next;
}
if (next != NULL)
{
enumerator->node = next;
return enumerator->node->pointer;
}
else
{
enumerator->hash++;
while (enumerator->hash < CLASS_TABLE_SIZE)
{
next = class_table_array[enumerator->hash];
if (next != NULL)
{
enumerator->node = next;
return enumerator->node->pointer;
}
enumerator->hash++;
}
/* Ok - table finished - done. */
objc_free (enumerator);
return Nil;
}
}
#if 0 /* DEBUGGING FUNCTIONS */
/* Debugging function - print the class table. */
void
class_table_print (void)
{
int i;
for (i = 0; i < CLASS_TABLE_SIZE; i++)
{
class_node_ptr node;
printf ("%d:\n", i);
node = class_table_array[i];
while (node != NULL)
{
printf ("\t%s\n", node->name);
node = node->next;
}
}
}
/* Debugging function - print an histogram of number of classes in
function of hash key values. Useful to evaluate the hash function
in real cases. */
void
class_table_print_histogram (void)
{
int i, j;
int counter = 0;
for (i = 0; i < CLASS_TABLE_SIZE; i++)
{
class_node_ptr node;
node = class_table_array[i];
while (node != NULL)
{
counter++;
node = node->next;
}
if (((i + 1) % 50) == 0)
{
printf ("%4d:", i + 1);
for (j = 0; j < counter; j++)
{
printf ("X");
}
printf ("\n");
counter = 0;
}
}
printf ("%4d:", i + 1);
for (j = 0; j < counter; j++)
{
printf ("X");
}
printf ("\n");
}
#endif /* DEBUGGING FUNCTIONS */
Class class_table_next (void **e);
/**
** Objective-C runtime functions
**/
/* From now on, the only access to the class table data structure
should be via the class_table_* functions. */
/* This is a hook which is called by objc_get_class and
objc_lookup_class if the runtime is not able to find the class.
This may e.g. try to load in the class using dynamic loading. */
Class (*_objc_lookup_class) (const char *name) = 0; /* !T:SAFE */
/* True when class links has been resolved. */
BOOL __objc_class_links_resolved = NO; /* !T:UNUSED */
void
__objc_init_class_tables (void)
{
/* Allocate the class hash table. */
if (__class_table_was_set_up)
return;
LOCK(__objc_runtime_mutex);
if (!__class_table_was_set_up)
{
/* Start - nothing in the table. */
memset (class_table_array, 0, sizeof (class_node_ptr) * CLASS_TABLE_SIZE);
/* The table writing mutex. */
INIT_LOCK(__class_table_lock);
__class_table_was_set_up = 1;
}
UNLOCK(__objc_runtime_mutex);
}
Class class_table_get_safe(const char*);
/* This function adds a class to the class hash table, and assigns the
class a number, unless it's already known. */
@ -426,9 +134,6 @@ __objc_add_class_to_hash (Class class)
LOCK(__objc_runtime_mutex);
/* Make sure the table is there. */
//assert (__class_table_lock);
/* Make sure it's not a meta class. */
assert (CLS_ISCLASS (class));
@ -444,7 +149,7 @@ __objc_add_class_to_hash (Class class)
CLS_SETNUMBER (class->class_pointer, class_number);
++class_number;
class_table_insert (class->name, class);
class_table_insert (class);
}
UNLOCK(__objc_runtime_mutex);
@ -513,14 +218,7 @@ objc_next_class (void **enum_state)
{
Class class;
LOCK(__objc_runtime_mutex);
/* Make sure the table is there. */
//assert (__class_table_lock);
class = class_table_next ((struct class_table_enumerator **) enum_state);
UNLOCK(__objc_runtime_mutex);
class = class_table_next ( enum_state);
return class;
}
@ -531,7 +229,7 @@ objc_next_class (void **enum_state)
void
__objc_resolve_class_links (void)
{
struct class_table_enumerator *es = NULL;
void *es = NULL;
Class object_class = objc_get_class ("Object");
Class class1;
@ -605,85 +303,10 @@ __objc_resolve_class_links (void)
UNLOCK(__objc_runtime_mutex);
}
#define CLASSOF(c) ((c)->class_pointer)
Class
class_pose_as (Class impostor, Class super_class)
{
if (! CLS_ISRESOLV (impostor))
__objc_resolve_class_links ();
/* Preconditions */
assert (impostor);
assert (super_class);
assert (impostor->super_class == super_class);
assert (CLS_ISCLASS (impostor));
assert (CLS_ISCLASS (super_class));
assert (impostor->instance_size == super_class->instance_size);
{
Class *subclass = &(super_class->subclass_list);
/* Move subclasses of super_class to impostor. */
while (*subclass)
{
Class nextSub = (*subclass)->sibling_class;
if (*subclass != impostor)
{
Class sub = *subclass;
/* Classes */
sub->sibling_class = impostor->subclass_list;
sub->super_class = impostor;
impostor->subclass_list = sub;
/* It will happen that SUB is not a class object if it is
the top of the meta class hierarchy chain (root
meta-class objects inherit their class object). If
that is the case... don't mess with the meta-meta
class. */
if (CLS_ISCLASS (sub))
{
/* Meta classes */
CLASSOF (sub)->sibling_class =
CLASSOF (impostor)->subclass_list;
CLASSOF (sub)->super_class = CLASSOF (impostor);
CLASSOF (impostor)->subclass_list = CLASSOF (sub);
}
}
*subclass = nextSub;
}
/* Set subclasses of superclass to be impostor only. */
super_class->subclass_list = impostor;
CLASSOF (super_class)->subclass_list = CLASSOF (impostor);
/* Set impostor to have no sibling classes. */
impostor->sibling_class = 0;
CLASSOF (impostor)->sibling_class = 0;
}
/* Check relationship of impostor and super_class is kept. */
assert (impostor->super_class == super_class);
assert (CLASSOF (impostor)->super_class == CLASSOF (super_class));
/* This is how to update the lookup table. Regardless of what the
keys of the hashtable is, change all values that are superclass
into impostor. */
LOCK(__objc_runtime_mutex);
class_table_replace (super_class, impostor);
UNLOCK(__objc_runtime_mutex);
/* Next, we update the dispatch tables... */
__objc_update_dispatch_table_for_class (CLASSOF (impostor));
__objc_update_dispatch_table_for_class (impostor);
return impostor;
fprintf(stderr, "Class posing is no longer supported.\n");
fprintf(stderr, "Please use class_replaceMethod() instead.\n");
abort();
}

@ -0,0 +1,50 @@
#include "objc/objc-api.h"
#include "lock.h"
#include <stdlib.h>
// Get the functions for string hashing
#include "string_hash.h"
static int class_compare(const char *name, const Class class)
{
return string_compare(name, class->name);
}
static int class_hash(const Class class)
{
return string_hash(class->name);
}
#define MAP_TABLE_NAME class_table_internal
#define MAP_TABLE_COMPARE_FUNCTION class_compare
#define MAP_TABLE_HASH_KEY string_hash
#define MAP_TABLE_HASH_VALUE class_hash
// This defines the maximum number of classes that the runtime supports.
#define MAP_TABLE_STATIC_SIZE 2048
#include "hash_table.h"
static class_table_internal_table class_table;
static mutex_t class_table_lock;
void class_table_insert(Class class)
{
class_table_internal_insert(&class_table, class->name, class);
}
Class class_table_get_safe(const char *class_name)
{
return class_table_internal_table_get(&class_table, class_name);
}
Class
class_table_next (void **e)
{
return class_table_internal_next(&class_table,
(struct class_table_internal_table_enumerator**)e);
}
void __objc_init_class_tables(void)
{
LOCK(__objc_runtime_mutex);
INIT_LOCK(class_table_lock);
UNLOCK(__objc_runtime_mutex);
}

@ -0,0 +1,228 @@
/**
* hash_table.h provides a template for implementing hopscotch hash tables.
*
* Several macros must be defined before including this file:
*
* MAP_TABLE_NAME defines the name of the table. All of the operations and
* types related to this table will be prefixed with this value.
*
* MAP_TABLE_COMPARE_FUNCTION defines the function used for testing a key
* against a value in the table for equality. This must take two void*
* arguments. The first is the key and the second is the value.
*
* MAP_TABLE_HASH_KEY and MAP_TABLE_HASH_VALUE define a pair of functions that
* takes a key and a value pointer respectively as their argument and returns
* an int32_t representing the hash.
*
* Optionally, MAP_TABLE_STATIC_SIZE may be defined, to define a table type
* which has a static size.
*/
#ifndef MAP_TABLE_NAME
# error You must define MAP_TABLE_NAME.
#endif
#ifndef MAP_TABLE_COMPARE_FUNCTION
# error You must define MAP_TABLE_COMPARE_FUNCTION.
#endif
#ifndef MAP_TABLE_HASH_KEY
# error You must define MAP_TABLE_HASH_KEY
#endif
#ifndef MAP_TABLE_HASH_VALUE
# error You must define MAP_TABLE_HASH_VALUE
#endif
// Horrible multiple indirection to satisfy the weird precedence rules in cpp
#define REALLY_PREFIX_SUFFIX(x,y) x ## y
#define PREFIX_SUFFIX(x, y) REALLY_PREFIX_SUFFIX(x, y)
/**
* PREFIX(x) macro adds the table name prefix to the argument.
*/
#define PREFIX(x) PREFIX_SUFFIX(MAP_TABLE_NAME, x)
typedef struct PREFIX(_table_cell_struct)
{
uint32_t secondMaps;
void *value;
} *PREFIX(_table_cell);
#ifdef MAP_TABLE_STATIC_SIZE
typedef struct
{
unsigned int table_used;
struct PREFIX(_table_cell_struct) table[MAP_TABLE_STATIC_SIZE];
} PREFIX(_table);
# define TABLE_SIZE(x) MAP_TABLE_STATIC_SIZE
#else
typedef struct
{
unsigned int table_size;
unsigned int table_used;
struct PREFIX(_table_cell_struct) table[1];
} PREFIX(_table);
# define TABLE_SIZE(x) (x->table_size)
#endif
struct PREFIX(_table_enumerator)
{
unsigned int seen;
unsigned int index;
};
static inline PREFIX(_table_cell) PREFIX(_table_lookup)(PREFIX(_table) *table,
uint32_t hash)
{
hash = hash % TABLE_SIZE(table);
return &table->table[hash];
}
static int PREFIX(_table_move_gap)(PREFIX(_table) *table, uint32_t fromHash,
uint32_t toHash, PREFIX(_table_cell) emptyCell)
{
for (uint32_t hash = fromHash - 32 ; hash < fromHash ; hash++)
{
// Get the cell n before the hash.
PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash);
// If this node is a primary entry move it down
if (MAP_TABLE_HASH_VALUE(cell->value) == hash)
{
emptyCell->value = cell->value;
cell->secondMaps |= (1 << ((fromHash - hash) - 1));
cell->value = NULL;
if (hash - toHash < 32)
{
return 1;
}
return PREFIX(_table_move_gap)(table, hash, toHash, cell);
}
int hop = __builtin_ffs(cell->secondMaps);
if (hop > 0 && (hash + hop) < fromHash)
{
PREFIX(_table_cell) hopCell = PREFIX(_table_lookup)(table, hash+hop);
emptyCell->value = hopCell->value;
// Update the hop bit for the new offset
cell->secondMaps |= (1 << ((fromHash - hash) - 1));
// Clear the hop bit in the original cell
cell->secondMaps &= ~(1 << (hop - 1));
hopCell->value = NULL;
if (hash - toHash < 32)
{
return 1;
}
return PREFIX(_table_move_gap)(table, hash + hop, toHash, hopCell);
}
}
return 0;
}
static int PREFIX(_table_rebalance)(PREFIX(_table) *table, uint32_t hash)
{
for (unsigned i=32 ; i<TABLE_SIZE(table) ; i++)
{
PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash + i);
if (NULL == cell->value)
{
// We've found a free space, try to move it up.
return PREFIX(_table_move_gap)(table, hash + i, hash, cell);
}
}
return 0;
}
static int PREFIX(_insert)(PREFIX(_table) *table,
const void *key,
void *value)
{
uint32_t hash = MAP_TABLE_HASH_KEY(key);
PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash);
if (NULL == cell->value)
{
cell->value = value;
table->table_used++;
return 1;
}
/* If this cell is full, try the next one. */
for (unsigned int i=0 ; i<32 ; i++)
{
PREFIX(_table_cell) second =
PREFIX(_table_lookup)(table, hash+i);
if (NULL == second->value)
{
cell->secondMaps |= (1 << (i-1));
second->value = value;
table->table_used++;
return 1;
}
}
/* If this virtual cell is full, rebalance the hash from this point and
* try again. */
if (PREFIX(_table_rebalance)(table, hash))
{
return PREFIX(_insert)(table, key, value);
}
return 0;
}
static void *PREFIX(_table_get)(PREFIX(_table) *table, const void *key)
{
uint32_t hash = MAP_TABLE_HASH_KEY(key);
PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash);
// Value does not exist.
if (NULL == cell->value)
{
return NULL;
}
if (MAP_TABLE_COMPARE_FUNCTION(key, cell->value))
{
return cell->value;
}
uint32_t jump = cell->secondMaps;
// Look at each offset defined by the jump table to find the displaced location.
for (int hop = __builtin_ffs(jump) ; hop > 0 ; hop = __builtin_ffs(jump))
{
PREFIX(_table_cell) hopCell = PREFIX(_table_lookup)(table, hash+hop);
if (MAP_TABLE_COMPARE_FUNCTION(key, hopCell->value))
{
return hopCell->value;
}
// Clear the most significant bit and try again.
jump &= ~(1 << (hop-1));
}
return NULL;
}
void *PREFIX(_next)(PREFIX(_table) *table,
struct PREFIX(_table_enumerator) **state)
{
if (NULL == *state)
{
*state = calloc(1, sizeof(struct PREFIX(_table_enumerator)));
}
if ((*state)->seen >= table->table_used)
{
free(*state);
return NULL;
}
while ((++((*state)->index)) < TABLE_SIZE(table))
{
if (table->table[(*state)->index].value != NULL)
{
return table->table[(*state)->index].value;
}
}
// Should not be reached, but may be if the table is unsafely modified.
free(*state);
return NULL;
}
#undef TABLE_SIZE
#undef REALLY_PREFIX_SUFFIX
#undef PREFIX_SUFFIX
#undef PREFIX
#undef MAP_TABLE_NAME
#undef MAP_TABLE_COMPARE_FUNCTION
#undef MAP_TABLE_HASH_KEY
#undef MAP_TABLE_HASH_VALUE
#ifdef MAP_TABLE_STATIC_SIZE
# undef MAP_TABLE_STATIC_SIZE
#endif

@ -0,0 +1,32 @@
#include <string.h>
#include <stdint.h>
/**
* Efficient string hash function.
*/
static uint32_t string_hash(const char *str)
{
uint32_t hash = 0;
int32_t c;
while ((c = *str++))
{
hash = c + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
/**
* Test two strings for equality.
*/
static int string_compare(const char *str1, const char *str2)
{
if (str1 == str2)
{
return 1;
}
if (str2 == NULL)
{
return 0;
}
return strcmp(str1, str2) == 0;
}
Loading…
Cancel
Save