diff --git a/COPYING b/COPYING index d741ff7..b93ae2a 100644 --- a/COPYING +++ b/COPYING @@ -19,6 +19,7 @@ class_table.c hash_table.h lock.h mutation.m +pool.h runtime.c string_hash.c sync.m diff --git a/GNUmakefile b/GNUmakefile index 5d5015e..d051ed8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -33,7 +33,9 @@ libobjc_C_FILES = \ protocol.c\ runtime.c\ sarray.c\ + sarray2.c\ selector.c\ + selector_table.c\ sendmsg.c\ thr.c diff --git a/class.h b/class.h new file mode 100644 index 0000000..b046d86 --- /dev/null +++ b/class.h @@ -0,0 +1,109 @@ + +struct objc_class +{ + /** + * Pointer to the metaclass for this class. The metaclass defines the + * methods use when a message is sent to the class, rather than an + * instance. + */ + struct objc_class *isa; + /** + * Pointer to the superclass. The compiler will set this to the name of + * the superclass, the runtime will initialize it to point to the real + * class. + */ + struct objc_class *super_class; + /** + * The name of this class. Set to the same value for both the class and + * its associated metaclass. + */ + const char* name; + /** + * The version of this class. This is not used by the language, but may be + * set explicitly at class load time. + */ + long version; + /** + * A bitfield containing various flags. + */ + unsigned long info; + /** + * The size of this class. For classes using the non-fragile ABI, the + * compiler will set this to a negative value The absolute value will be + * the size of the instance variables defined on just this class. When + * using the fragile ABI, the instance size is the size of instances of + * this class, including any instance variables defined on superclasses. + * + * In both cases, this will be set to the size of an instance of the class + * after the class is registered with the runtime. + */ + long instance_size; + /** + * Metadata describing the instance variables in this class. + */ + struct objc_ivar_list* ivars; + /** + * Metadata for for defining the mappings from selectors to IMPs. Linked + * list of method list structures, one per class and one per category. + */ + struct objc_method_list* methods; + /** + * The dispatch table for this class. Intialized and maintained by the + * runtime. + */ + struct sarray * dtable; + /** + * A pointer to the first subclass for this class. Filled in by the + * runtime. + */ + struct objc_class* subclass_list; + /** + * A pointer to the next sibling class to this. You may find all + * subclasses of a given class by following the subclass_list pointer and + * then subsequently following the sibling_class pointers in the + * subclasses. + */ + struct objc_class* sibling_class; + + /** + * Metadata describing the protocols adopted by this class. Not used by + * the runtime. + */ + struct objc_protocol_list *protocols; + /** + * Pointer used by the Boehm GC. + */ + void* gc_object_type; + /** + * New ABI. The following fields are only available with classes compiled to + * support the new ABI. You may test whether any given class supports this + * ABI by using the CLS_ISNEW_ABI() macro. + */ + + /** + * The version of the ABI used for this class. This is currently always zero. + */ + long abi_version; + + /** + * Array of pointers to variables where the runtime will store the ivar + * offset. These may be used for faster access to non-fragile ivars if all + * of the code is compiled for the new ABI. Each of these pointers should + * have the mangled name __objc_ivar_offset_value_{class name}.{ivar name} + * + * When using the compatible non-fragile ABI, this faster form should only be + * used for classes declared in the same compilation unit. + * + * The compiler should also emit symbols of the form + * __objc_ivar_offset_{class name}.{ivar name} which are pointers to the + * offset values. These should be emitted as weak symbols in every module + * where they are used. The legacy-compatible ABI uses these with a double + * layer of indirection. + */ + int **ivar_offsets; + /** + * List of declared properties on this class (NULL if none). This contains + * the accessor methods for each property. + */ + struct objc_property_list *properties; +}; diff --git a/hash_table.h b/hash_table.h index c1dbeeb..7ee4fb1 100644 --- a/hash_table.h +++ b/hash_table.h @@ -63,9 +63,8 @@ static BOOL PREFIX(_is_null)(void *value) } # define MAP_TABLE_VALUE_NULL PREFIX(_is_null) # define MAP_TABLE_VALUE_PLACEHOLDER NULL -#else -# define MAP_TABLE_ACCESS_BY_REFERENCE #endif + typedef struct PREFIX(_table_cell_struct) { uint32_t secondMaps; diff --git a/init.c b/init.c index fadb71d..d969b48 100644 --- a/init.c +++ b/init.c @@ -73,9 +73,7 @@ void (*_objc_load_callback) (Class class, Category *category); /* !T:SAFE */ /* Is all categories/classes resolved? */ BOOL __objc_dangling_categories = NO; /* !T:UNUSED */ -extern SEL -__sel_register_typed_name (const char *name, const char *types, - struct objc_selector *orig, BOOL is_const); +void __objc_register_selector_array(SEL selectors, unsigned long count); /* Sends +load to all classes and categories in certain situations. */ static void objc_send_load (void); @@ -535,9 +533,6 @@ __objc_exec_class (Module_t module) /* Entry used to traverse hash lists */ struct objc_list **cell; - /* The table of selector references for this module */ - SEL selectors = symtab->refs; - /* dummy counter */ int i; @@ -572,19 +567,9 @@ __objc_exec_class (Module_t module) __objc_module_list = list_cons (module, __objc_module_list); /* Replace referenced selectors from names to SEL's. */ - if (selectors) + if (symtab->refs) { - for (i = 0; selectors[i].sel_id; ++i) - { - const char *name, *type; - name = (char *) selectors[i].sel_id; - type = (char *) selectors[i].sel_types; - /* Constructors are constant static data so we can safely store - pointers to them in the runtime structures. is_const == YES */ - __sel_register_typed_name (name, type, - (struct objc_selector *) &(selectors[i]), - YES); - } + __objc_register_selector_array(symtab->refs, symtab->sel_ref_cnt); } /* Parse the classes in the load module and gather selector information. */ diff --git a/method_list.h b/method_list.h new file mode 100644 index 0000000..aaa8bd2 --- /dev/null +++ b/method_list.h @@ -0,0 +1,43 @@ +/** + * Metadata structure describing a method. + */ +struct objc_method +{ + /** + * Selector used to send messages to this method. The type encoding of + * this method should match the types field. + */ + SEL selector; + /** + * The type encoding for this selector. Used only for introspection, and + * only required because of the stupid selector handling in the old GNU + * runtime. In future, this field may be reused for something else. + */ + const char *types; + /** + * A pointer to the function implementing this method. + */ + IMP imp; +}; + +/** + * Method list. Each class or category defines a new one of these and they are + * all chained together in a linked list, with new ones inserted at the head. + * When constructing the dispatch table, methods in the start of the list are + * used in preference to ones at the end. + */ +struct objc_method_list +{ + /** + * The next group of methods in the list. + */ + struct objc_method_list *next; + /** + * The number of methods in this list. + */ + int count; + /** + * An array of methods. Note that the actual size of this is count, not 1. + */ + struct objc_method methods[1]; +}; diff --git a/objc/runtime.h b/objc/runtime.h index cb52fe2..9ba0d4f 100644 --- a/objc/runtime.h +++ b/objc/runtime.h @@ -36,7 +36,7 @@ typedef struct objc_ivar* Ivar; // Don't redefine these types if the old GNU header was included first. #ifndef __objc_INCLUDE_GNU -typedef const struct objc_selector *SEL; +typedef struct objc_selector *SEL; typedef struct objc_class *Class; @@ -272,49 +272,28 @@ BOOL sel_isEqual(SEL sel1, SEL sel2); SEL sel_registerName(const char *selName); +/** + * Register a typed selector. + */ +SEL sel_registerTypedName_np(const char *selName, const char *types); + +/** + * Returns the type encoding associated with a selector, or the empty string is + * there is no such type. + */ +const char *sel_getType_np(SEL aSel); + +/** + * Copies all of the type encodings associated with a given selector name into + * types, returning the number of types. + */ +unsigned sel_copyTypes(const char *selName, const char **types, unsigned count); + #else #include "runtime-legacy.h" #endif // __LEGACY_GNU_MODE__ +#include "slot.h" -/** - * The objc_slot structure is used to permit safe IMP caching. It is returned - * by the new lookup APIs. When you cache an IMP, you should store a copy of - * the version field and a pointer to the slot. - * - * The slot version is guaranteed never to be 0. When updating a cache, you - * should use code of the following form: - * - * 1) version = 0; - * 2) slot->cachedFor = receiver->isa; - * 3) slot_cache = slot; - * 4) version = slot->version; - * - * The runtime guarantees that the version in any cachable slot will never be - * 0. This should ensure that, if the version and cache pointer mismatch, the - * next access will cause a cache miss. - * - * When using a cached slot, you should compare the owner pointer to the isa - * pointer of the receiver and the message and the version of the slot to your - * cached version. - */ -struct objc_slot -{ - /** The class to which this slot is attached (used internally). */ - Class owner; - /** The class for which this slot was cached. Note that this can be - * modified by different cache owners, in different threads. Doing so may - * cause some cache misses, but if different methods are sending messages - * to the same object and sharing a cached slot then it may also improve - * cache hits. Profiling is probably required here. */ - Class cachedFor; - /** The type encoding for the method identified by this slot. */ - char *types; - /** The current version. This changes if the method changes or if a - * subclass overrides this method, potentially invalidating this cache. */ - int version; - /** The method pointer for this method. */ - IMP method; -} OBJC_NONPORTABLE; /** * New ABI lookup function. Receiver may be modified during lookup or proxy * forwarding and the sender may affect how lookup occurs. diff --git a/objc/slot.h b/objc/slot.h new file mode 100644 index 0000000..48c79cc --- /dev/null +++ b/objc/slot.h @@ -0,0 +1,39 @@ +/** + * The objc_slot structure is used to permit safe IMP caching. It is returned + * by the new lookup APIs. When you cache an IMP, you should store a copy of + * the version field and a pointer to the slot. + * + * The slot version is guaranteed never to be 0. When updating a cache, you + * should use code of the following form: + * + * 1) version = 0; + * 2) slot->cachedFor = receiver->isa; + * 3) slot_cache = slot; + * 4) version = slot->version; + * + * The runtime guarantees that the version in any cachable slot will never be + * 0. This should ensure that, if the version and cache pointer mismatch, the + * next access will cause a cache miss. + * + * When using a cached slot, you should compare the owner pointer to the isa + * pointer of the receiver and the message and the version of the slot to your + * cached version. + */ +struct objc_slot +{ + /** The class to which this slot is attached (used internally). */ + Class owner; + /** The class for which this slot was cached. Note that this can be + * modified by different cache owners, in different threads. Doing so may + * cause some cache misses, but if different methods are sending messages + * to the same object and sharing a cached slot then it may also improve + * cache hits. Profiling is probably required here. */ + Class cachedFor; + /** The type encoding for the method identified by this slot. */ + const char *types; + /** The current version. This changes if the method changes or if a + * subclass overrides this method, potentially invalidating this cache. */ + int version; + /** The method pointer for this method. */ + IMP method; +} OBJC_NONPORTABLE; diff --git a/pool.h b/pool.h new file mode 100644 index 0000000..ffa8a6a --- /dev/null +++ b/pool.h @@ -0,0 +1,53 @@ +#include +#include "lock.h" + +#ifndef POOL_TYPE +#error POOL_TYPE must be defined +#endif +#ifndef POOL_TYPE +#error POOL_NAME must be defined +#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) +#define NAME(x) PREFIX_SUFFIX(POOL_NAME, x) + +#define PAGE_SIZE 4096 + +// Malloc one page at a time. +#define POOL_SIZE ((PAGE_SIZE) / sizeof(POOL_TYPE)) +static POOL_TYPE* NAME(_pool); +static int NAME(_pool_next_index) = -1; + +#ifdef THREAD_SAFE_POOL +static mutex_t NAME(_lock); +#define LOCK_POOL() LOCK(&POOL_NAME##_lock) +#define UNLOCK_POOL() LOCK(&POOL_NAME##_lock) +#else +#define LOCK_POOL() +#define UNLOCK_POOL() +#endif + +static inline POOL_TYPE*NAME(_pool_alloc)(void) +{ + LOCK_POOL(); + if (0 > NAME(_pool_next_index)) + { + NAME(_pool) = malloc(PAGE_SIZE); + NAME(_pool_next_index) = POOL_SIZE - 1; + } + POOL_TYPE* new = &NAME(_pool)[NAME(_pool_next_index)--]; + UNLOCK_POOL(); + return new; +} +#undef NAME +#undef POOL_SIZE +#undef PAGE_SIZE +#undef POOL_NAME +#undef POOL_TYPE +#undef LOCK_POOL +#undef UNLOCK_POOL +#ifdef THREAD_SAFE_POOL +#undef THREAD_SAFE_POOL +#endif diff --git a/runtime.c b/runtime.c index 3ca2e15..fa4c772 100644 --- a/runtime.c +++ b/runtime.c @@ -602,7 +602,7 @@ IMP method_getImplementation(Method method) SEL method_getName(Method method) { - return method->method_name; + return (SEL)method->method_name; } unsigned method_getNumberOfArguments(Method method) @@ -845,23 +845,3 @@ void objc_registerClassPair(Class cls) objc_resolve_class(cls); } - -const char *sel_getName(SEL sel) -{ - return sel_get_name(sel); -} - -SEL sel_getUid(const char *selName) -{ - return sel_get_uid(selName); -} - -BOOL sel_isEqual(SEL sel1, SEL sel2) -{ - return sel_eq(sel1, sel2); -} - -SEL sel_registerName(const char *selName) -{ - return sel_register_name(selName); -} diff --git a/sarray2.c b/sarray2.c new file mode 100644 index 0000000..d9f19fc --- /dev/null +++ b/sarray2.c @@ -0,0 +1,105 @@ +#include +#ifdef BUILD_TESTS +#include +#endif + +#include "sarray2.h" + +static void *EmptyArrayData[256]; +static SparseArray EmptyArray = { 0, 0xff, (void**)&EmptyArrayData}; + +static void init_pointers(SparseArray * sarray) +{ + sarray->data = calloc(256, sizeof(void*)); + if(sarray->shift != 0) + { + for(unsigned i=0 ; i<256 ; i++) + { + sarray->data[i] = &EmptyArray; + } + } +} + +SparseArray * SparseArrayNew() +{ + SparseArray * sarray = calloc(1, sizeof(SparseArray)); + sarray->shift = 24; + sarray->mask = 0xff000000; + init_pointers(sarray); + return sarray; +} + + +void * SparseArrayNext(SparseArray * sarray, uint32_t * index) +{ + uint32_t j = MASK_INDEX((*index)); + uint32_t max = (sarray->mask >> sarray->shift) + 1; + if(sarray->shift == 0) + { + while(jdata[j] != SARRAY_EMPTY) + { + return sarray->data[j]; + } + j++; + } + } + else while(jmask >> 8); + while(jdata[j] != SARRAY_EMPTY) + { + void * ret = SparseArrayNext(sarray->data[j], index); + if(ret != SARRAY_EMPTY) + { + return ret; + } + } + //Go to the next child + j++; + //Add 2^n to index so j is still correct + (*index) += 1<shift; + //Zero off the next component of the index so we don't miss any. + *index &= zeromask; + } + } + return SARRAY_EMPTY; +} + +void SparseArrayInsert(SparseArray * sarray, uint32_t index, void * value) +{ + while(sarray->shift > 0) + { + uint32_t i = MASK_INDEX(index); + if(sarray->data[i] == &EmptyArray) + { + SparseArray * newsarray = calloc(1, sizeof(SparseArray)); + newsarray->shift = sarray->shift - 8; + newsarray->mask = sarray->mask >> 8; + init_pointers(newsarray); + sarray->data[i] = newsarray; + } + sarray = sarray->data[i]; + } + sarray->data[index & sarray->mask] = value; +} + +void SparseArrayDestroy(SparseArray * sarray) +{ + if(sarray->shift > 0) + { + uint32_t max = (sarray->mask >> sarray->shift) + 1; + for(uint32_t i=0 ; idata[i]); + } + } + free(sarray->data); + free(sarray); +} + diff --git a/sarray2.h b/sarray2.h new file mode 100644 index 0000000..e4729a3 --- /dev/null +++ b/sarray2.h @@ -0,0 +1,76 @@ +/** + * Sparse Array + * + * Author: David Chisnall + * + * License: See COPYING.MIT + * + */ + +#ifndef _SARRAY_H_INCLUDED_ +#define _SARRAY_H_INCLUDED_ +#include +#include + +/** + * Sparse arrays, used to implement dispatch tables. Current implementation is + * quite RAM-intensive and could be optimised. Maps 32-bit integers to pointers. + * + * Note that deletion from the array is not supported. This allows accesses to + * be done without locking; the worst that can happen is that the caller gets + * an old value (and if this is important to you then you should be doing your + * own locking). For this reason, you should be very careful when deleting a + * sparse array that there are no references to it held by other threads. + */ +typedef struct +{ + uint32_t mask; + uint32_t shift; + void ** data; +} SparseArray; + +/** + * Turn an index in the array into an index in the current depth. + */ +#define MASK_INDEX(index) \ + ((index & sarray->mask) >> sarray->shift) + +#define SARRAY_EMPTY ((void*)0) +/** + * Look up the specified value in the sparse array. This is used in message + * dispatch and so has been put in the header to allow compilers to inline it, + * even though this breaks the abstraction. + */ +static inline void* SparseArrayLookup(SparseArray * sarray, uint32_t index) +{ + while(sarray->shift > 0) + { + uint32_t i = MASK_INDEX(index); + sarray = (SparseArray*) sarray->data[i]; + } + uint32_t i = index & sarray->mask; + return sarray->data[i]; +} +/** + * Create a new sparse array. + */ +SparseArray * SparseArrayNew(); +/** + * Insert a value at the specified index. + */ +void SparseArrayInsert(SparseArray * sarray, uint32_t index, void * value); +/** + * Destroy the sparse array. Note that calling this while other threads are + * performing lookups is guaranteed to break. + */ +void SparseArrayDestroy(SparseArray * sarray); +/** + * Iterate through the array. Returns the next non-NULL value after index and + * sets index to the following value. For example, an array containing values + * at 0 and 10 will, if called with index set to 0 first return the value at 0 + * and set index to 1. A subsequent call with index set to 1 will return the + * value at 10 and set index to 11. + */ +void * SparseArrayNext(SparseArray * sarray, uint32_t * index); + +#endif //_SARRAY_H_INCLUDED_ diff --git a/selector.c b/selector.c index 346216f..e67bca3 100644 --- a/selector.c +++ b/selector.c @@ -27,77 +27,12 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see #include "objc/sarray.h" #include "objc/encoding.h" -/* Initial selector hash table size. Value doesn't matter much */ -#define SELECTOR_HASH_SIZE 128 - -/* Tables mapping selector names to uid and opposite */ -static struct sarray *__objc_selector_array = 0; /* uid -> sel !T:MUTEX */ -static struct sarray *__objc_selector_names = 0; /* uid -> name !T:MUTEX */ -static cache_ptr __objc_selector_hash = 0; /* name -> uid !T:MUTEX */ - -/* Number of selectors stored in each of the above tables */ -unsigned int __objc_selector_max_index = 0; /* !T:MUTEX */ - -void __objc_init_selector_tables (void) -{ - __objc_selector_array = sarray_new (SELECTOR_HASH_SIZE, 0); - __objc_selector_names = sarray_new (SELECTOR_HASH_SIZE, 0); - __objc_selector_hash - = objc_hash_new (SELECTOR_HASH_SIZE, - (hash_func_type) objc_hash_string, - (compare_func_type) objc_compare_strings); -} - -/* This routine is given a class and records all of the methods in its class - structure in the record table. */ -void -__objc_register_selectors_from_class (Class class) -{ - MethodList_t method_list; - - method_list = class->methods; - while (method_list) - { - __objc_register_selectors_from_list (method_list); - method_list = method_list->method_next; - } -} - - -/* This routine is given a list of methods and records each of the methods in - the record table. This is the routine that does the actual recording - work. - - The name and type pointers in the method list must be permanent and - immutable. - */ -void -__objc_register_selectors_from_list (MethodList_t method_list) -{ - int i = 0; - - objc_mutex_lock (__objc_runtime_mutex); - while (i < method_list->method_count) - { - Method_t method = &method_list->method_list[i]; - if (method->method_name) - { - method->method_name - = __sel_register_typed_name ((const char *) method->method_name, - method->method_types, 0, YES); - } - i += 1; - } - objc_mutex_unlock (__objc_runtime_mutex); -} - - /* Register instance methods as class methods for root classes */ void __objc_register_instance_methods_to_class (Class class) { MethodList_t method_list; MethodList_t class_method_list; - int max_methods_no = 16; + int max_methods_no = 32; MethodList_t new_list; Method_t curr_method; @@ -157,338 +92,3 @@ void __objc_register_instance_methods_to_class (Class class) __objc_update_dispatch_table_for_class (class->class_pointer); } - - -/* Returns YES iff t1 and t2 have same method types, but we ignore - the argframe layout */ -BOOL -sel_types_match (const char *t1, const char *t2) -{ - if (! t1 || ! t2) - return NO; - while (*t1 && *t2) - { - if (*t1 == '+') t1++; - if (*t2 == '+') t2++; - while (isdigit ((unsigned char) *t1)) t1++; - while (isdigit ((unsigned char) *t2)) t2++; - /* xxx Remove these next two lines when qualifiers are put in - all selectors, not just Protocol selectors. */ - t1 = objc_skip_type_qualifiers (t1); - t2 = objc_skip_type_qualifiers (t2); - if (! *t1 && ! *t2) - return YES; - if (*t1 != *t2) - return NO; - t1++; - t2++; - } - return NO; -} - -/* return selector representing name */ -SEL -sel_get_typed_uid (const char *name, const char *types) -{ - struct objc_list *l; - sidx i; - - objc_mutex_lock (__objc_runtime_mutex); - - i = (sidx) objc_hash_value_for_key (__objc_selector_hash, name); - if (i == 0) - { - objc_mutex_unlock (__objc_runtime_mutex); - return 0; - } - - for (l = (struct objc_list *) sarray_get_safe (__objc_selector_array, i); - l; l = l->tail) - { - SEL s = (SEL) l->head; - if (types == 0 || s->sel_types == 0) - { - if (s->sel_types == types) - { - objc_mutex_unlock (__objc_runtime_mutex); - return s; - } - } - else if (sel_types_match (s->sel_types, types)) - { - objc_mutex_unlock (__objc_runtime_mutex); - return s; - } - } - - objc_mutex_unlock (__objc_runtime_mutex); - return 0; -} - -/* Return selector representing name; prefer a selector with non-NULL type */ -SEL -sel_get_any_typed_uid (const char *name) -{ - struct objc_list *l; - sidx i; - SEL s = NULL; - - objc_mutex_lock (__objc_runtime_mutex); - - i = (sidx) objc_hash_value_for_key (__objc_selector_hash, name); - if (i == 0) - { - objc_mutex_unlock (__objc_runtime_mutex); - return 0; - } - - for (l = (struct objc_list *) sarray_get_safe (__objc_selector_array, i); - l; l = l->tail) - { - s = (SEL) l->head; - if (s->sel_types) - { - objc_mutex_unlock (__objc_runtime_mutex); - return s; - } - } - - objc_mutex_unlock (__objc_runtime_mutex); - return s; -} - -/* return selector representing name */ -SEL -sel_get_any_uid (const char *name) -{ - struct objc_list *l; - sidx i; - - objc_mutex_lock (__objc_runtime_mutex); - - i = (sidx) objc_hash_value_for_key (__objc_selector_hash, name); - if (soffset_decode (i) == 0) - { - objc_mutex_unlock (__objc_runtime_mutex); - return 0; - } - - l = (struct objc_list *) sarray_get_safe (__objc_selector_array, i); - objc_mutex_unlock (__objc_runtime_mutex); - - if (l == 0) - return 0; - - return (SEL) l->head; -} - -/* return selector representing name */ -SEL -sel_get_uid (const char *name) -{ - return sel_register_typed_name (name, 0); -} - -/* Get name of selector. If selector is unknown, the empty string "" - is returned */ -const char *sel_get_name (SEL selector) -{ - const char *ret; - - if (selector == 0) return ""; - objc_mutex_lock (__objc_runtime_mutex); - if ((soffset_decode ((sidx)selector->sel_id) > 0) - && (soffset_decode ((sidx)selector->sel_id) <= __objc_selector_max_index)) - ret = sarray_get_safe (__objc_selector_names, (sidx) selector->sel_id); - else - ret = 0; - objc_mutex_unlock (__objc_runtime_mutex); - return ret; -} - -BOOL -sel_is_mapped (SEL selector) -{ - unsigned int idx = soffset_decode ((sidx)selector->sel_id); - return ((idx > 0) && (idx <= __objc_selector_max_index)); -} - - -const char *sel_get_type (SEL selector) -{ - if (selector) - return selector->sel_types; - else - return 0; -} - -/* The uninstalled dispatch table */ -extern struct sarray *__objc_uninstalled_dtable; - -/* __sel_register_typed_name allocates lots of struct objc_selector:s - of 8 (16, if pointers are 64 bits) bytes at startup. To reduce the number - of malloc calls and memory lost to malloc overhead, we allocate - objc_selector:s in blocks here. This is only called from - __sel_register_typed_name, and __sel_register_typed_name may only be - called when __objc_runtime_mutex is locked. - - Note that the objc_selector:s allocated from __sel_register_typed_name - are never freed. - - 62 because 62 * sizeof (struct objc_selector) = 496 (992). This should - let malloc add some overhead and use a nice, round 512 (1024) byte chunk. - */ -#define SELECTOR_POOL_SIZE 62 -static struct objc_selector *selector_pool; -static int selector_pool_left; - -static struct objc_selector * -pool_alloc_selector(void) -{ - if (!selector_pool_left) - { - selector_pool = objc_malloc (sizeof (struct objc_selector) - * SELECTOR_POOL_SIZE); - selector_pool_left = SELECTOR_POOL_SIZE; - } - return &selector_pool[--selector_pool_left]; -} - -/* Store the passed selector name in the selector record and return its - selector value (value returned by sel_get_uid). - Assumes that the calling function has locked down __objc_runtime_mutex. */ -/* is_const parameter tells us if the name and types parameters - are really constant or not. If YES then they are constant and - we can just store the pointers. If NO then we need to copy - name and types because the pointers may disappear later on. */ -SEL -__sel_register_typed_name (const char *name, const char *types, - struct objc_selector *orig, BOOL is_const) -{ - struct objc_selector *j; - sidx i; - struct objc_list *l; - // FIXME: The linker is now going to be uniquing most of our selectors for - // us, so we want to eliminate this hash calculation except as a fallback - - if (name == 0) return 0; - i = (sidx) objc_hash_value_for_key (__objc_selector_hash, name); - if (soffset_decode (i) != 0) - { - for (l = (struct objc_list *) sarray_get_safe (__objc_selector_array, i); - l; l = l->tail) - { - SEL s = (SEL) l->head; - if (types == 0 || s->sel_types == 0) - { - if (s->sel_types == types) - { - if (orig) - { - orig->sel_id = (void *) i; - return orig; - } - else - return s; - } - } - else if (! strcmp (s->sel_types, types)) - { - if (orig) - { - orig->sel_id = (void *) i; - return orig; - } - else - return s; - } - } - if (orig) - j = orig; - else - j = pool_alloc_selector (); - - j->sel_id = (void *) i; - /* Can we use the pointer or must copy types? Don't copy if NULL */ - if ((is_const) || (types == 0)) - j->sel_types = (const char *) types; - else { - j->sel_types = (char *) objc_malloc (strlen (types) + 1); - strcpy ((char *) j->sel_types, types); - } - l = (struct objc_list *) sarray_get_safe (__objc_selector_array, i); - } - else - { - __objc_selector_max_index += 1; - i = soffset_encode (__objc_selector_max_index); - if (orig) - j = orig; - else - j = pool_alloc_selector (); - - j->sel_id = (void *) i; - /* Can we use the pointer or must copy types? Don't copy if NULL */ - if ((is_const) || (types == 0)) - j->sel_types = (const char *) types; - else { - j->sel_types = (char *) objc_malloc (strlen (types) + 1); - strcpy ((char *) j->sel_types, types); - } - l = 0; - } - - DEBUG_PRINTF ("Record selector %s[%s] as: %ld\n", name, types, - (long) soffset_decode (i)); - - { - int is_new = (l == 0); - const char *new_name; - - /* Can we use the pointer or must copy name? Don't copy if NULL */ - if ((is_const) || (name == 0)) - new_name = name; - else { - new_name = (char *) objc_malloc (strlen (name) + 1); - strcpy ((char *) new_name, name); - } - - l = list_cons ((void *) j, l); - sarray_at_put_safe (__objc_selector_names, i, (void *) new_name); - sarray_at_put_safe (__objc_selector_array, i, (void *) l); - if (is_new) - objc_hash_add (&__objc_selector_hash, (void *) new_name, (void *) i); - } - - sarray_realloc (__objc_uninstalled_dtable, __objc_selector_max_index + 1); - - return (SEL) j; -} - -SEL -sel_register_name (const char *name) -{ - SEL ret; - - objc_mutex_lock (__objc_runtime_mutex); - /* Assume that name is not constant static memory and needs to be - copied before put into a runtime structure. is_const == NO */ - ret = __sel_register_typed_name (name, 0, 0, NO); - objc_mutex_unlock (__objc_runtime_mutex); - - return ret; -} - -SEL -sel_register_typed_name (const char *name, const char *type) -{ - SEL ret; - - objc_mutex_lock (__objc_runtime_mutex); - /* Assume that name and type are not constant static memory and need to - be copied before put into a runtime structure. is_const == NO */ - ret = __sel_register_typed_name (name, type, 0, NO); - objc_mutex_unlock (__objc_runtime_mutex); - - return ret; -} diff --git a/selector.h b/selector.h new file mode 100644 index 0000000..fc9eec6 --- /dev/null +++ b/selector.h @@ -0,0 +1,26 @@ +/** + * Structure used to store the types for a selector. This allows for a quick + * test to see whether a selector is polymorphic and allows enumeration of all + * type encodings for a given selector. + * + * This is the same size as an objc_selector, so we can allocate them from the + * objc_selector pool. + * + * Note: For ABI v10, we can probably do something a bit more sensible here and + * make selectors into a linked list. + */ +struct sel_type_list +{ + const char *value; + struct sel_type_list *next; +}; + +/** + * Structure used to store selectors in the list. + */ +struct objc_selector +{ + const char * name; + const char * types; +}; + diff --git a/selector_table.c b/selector_table.c new file mode 100644 index 0000000..17b82f5 --- /dev/null +++ b/selector_table.c @@ -0,0 +1,433 @@ +/** + * Handle selector uniquing. + * + * When building, you may define TYPE_DEPENDENT_DISPATCH to enable message + * sends to depend on their types. + */ +#include +#include +#include +#include "lock.h" +#include "sarray2.h" +#include "objc/runtime.h" +#include "method_list.h" +#include "class.h" +#include "selector.h" + +#ifdef TYPE_DEPENDENT_DISPATCH +# define TDD(x) x +#else +# define TDD(x) +#endif + + +// Define the pool allocator for selectors. This is a simple bump-the-pointer +// allocator for low-overhead allocation. +#define POOL_NAME selector +#define POOL_TYPE struct objc_selector +#include "pool.h" + + +/** + * The number of selectors currently registered. When a selector is + * registered, its name field is replaced with its index in the selector_list + * array. + */ +uint32_t __objc_selector_max_index; +/** + * Mapping from selector numbers to selector names. + */ +static SparseArray *selector_list = NULL; + +// Get the functions for string hashing +#include "string_hash.h" + +inline static BOOL isSelRegistered(SEL sel) +{ + if ((uintptr_t)sel->name < (uintptr_t)__objc_selector_max_index) + { + return YES; + } + return NO; +} + +/** + * Compare selectors based on whether they are treated as equivalent for the + * purpose of dispatch. + */ +static int selector_equal(const void *k, + const SEL value) +{ + SEL key = (SEL)k; + return string_compare(sel_getName(key), sel_getName(value)) TDD(&& + string_compare(sel_getType_np(key), sel_getType_np(value))); +} +/** + * Compare whether two selectors are identical. + */ +static int selector_identical(const SEL key, + const SEL value) +{ + return string_compare(sel_getName(key), sel_getName(value)) && + string_compare(sel_getType_np(key), sel_getType_np(value)); +} +static inline uint32_t addStringToHash(uint32_t hash, const char *str) +{ + uint32_t c; + if(str != NULL) + { + while((c = (uint32_t)*str++)) + { + hash = hash * 33 + c; + } + } + return hash; +} +/** + * Hash a selector. + */ +static inline uint32_t hash_selector(const void *s) +{ + SEL sel = (SEL)s; + uint32_t hash = 5381; + hash = addStringToHash(hash, sel_getName(sel)); + hash = addStringToHash(hash, sel->types); + return hash; +} + +#define MAP_TABLE_NAME selector +#define MAP_TABLE_COMPARE_FUNCTION selector_equal +#define MAP_TABLE_HASH_KEY hash_selector +#define MAP_TABLE_HASH_VALUE hash_selector +#include "hash_table.h" +/** + * Table of registered selector. Maps from selector to selector. + */ +static selector_table *sel_table; + +/** + * Lock protecting the selector table. + */ +mutex_t selector_table_lock; + + +/** + * Hack to make the uninstalled dtable the right size. Won't be needed with sarray2. + */ +void objc_resize_uninstalled_dtable(void); + +/** + * Create data structures to store selectors. + */ +void __objc_init_selector_tables() +{ + selector_list = SparseArrayNew(); + INIT_LOCK(selector_table_lock); + sel_table = selector_create(40960); +} + +static SEL selector_lookup(const char *name, const char *types) +{ + struct objc_selector sel = {name, types}; + return selector_table_get(sel_table, &sel); +} +static inline void add_selector_to_table(SEL aSel, int32_t uid, uint32_t idx) +{ + struct sel_type_list *typeList = + (struct sel_type_list *)selector_pool_alloc(); + typeList->value = aSel->name; + typeList->next = 0; + // Store the name. + SparseArrayInsert(selector_list, idx, typeList); + // Store the selector. + selector_insert(sel_table, aSel); + // Set the selector's name to the uid. + aSel->name = (const char*)uid; +} +/** + * Really registers a selector. Must be called with the selector table locked. + */ +static inline void register_selector_locked(SEL aSel) +{ + uintptr_t idx = __objc_selector_max_index++; + if (NULL == aSel->types) + { + add_selector_to_table(aSel, idx, idx); + objc_resize_uninstalled_dtable(); + return; + } + SEL untyped = selector_lookup(aSel->name, 0); + // If this has a type encoding, store the untyped version too. + if (untyped == NULL) + { + untyped = selector_pool_alloc(); + untyped->name = aSel->name; + untyped->types = 0; + add_selector_to_table(untyped, idx, idx); + // If we are in type dependent dispatch mode, the uid for the typed + // and untyped versions will be different + idx++; __objc_selector_max_index++; + } + uintptr_t uid = (uintptr_t)untyped->name; + TDD(uid = idx); + add_selector_to_table(aSel, uid, idx); + + // Add this set of types to the list. + // This is quite horrible. Most selectors will only have one type + // encoding, so we're wasting a lot of memory like this. + struct sel_type_list *typeListHead = + SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)untyped->name); + struct sel_type_list *typeList = + (struct sel_type_list *)selector_pool_alloc(); + typeList->value = aSel->types; + typeList->next = typeListHead->next; + typeListHead->next = typeList; + objc_resize_uninstalled_dtable(); +} +/** + * Registers a selector. This assumes that the argument is never deallocated. + */ +static SEL objc_register_selector(SEL aSel) +{ + if (isSelRegistered(aSel)) + { + return aSel; + } + // Check that this isn't already registered, before we try + SEL registered = selector_lookup(aSel->name, aSel->types); + if (NULL != registered && (selector_identical(aSel, registered) || NULL == aSel->types)) + { + aSel->name = registered->name; + return registered; + } + LOCK(&selector_table_lock); + register_selector_locked(aSel); + UNLOCK(&selector_table_lock); + return aSel; +} + +/** + * Registers a selector by copying the argument. + */ +static SEL objc_register_selector_copy(SEL aSel) +{ + // If an identical selector is already registered, return it. + SEL copy = selector_lookup(aSel->name, aSel->types); + if (NULL != copy && (selector_identical(aSel, copy) || NULL == aSel->types)) + { + return copy; + } + LOCK(&selector_table_lock); + // Create a copy of this selector. + copy = selector_pool_alloc(); + copy->name = strdup(aSel->name); + copy->types = (NULL == aSel->types) ? NULL : strdup(aSel->types); + // Try to register the copy as the authoritative version + register_selector_locked(copy); + UNLOCK(&selector_table_lock); + return copy; +} + +/** + * Public API functions. + */ + +const char *sel_getName(SEL sel) +{ + const char *name = sel->name; + if (isSelRegistered(sel)) + { + struct sel_type_list * list = + SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)sel->name); + name = (list == NULL) ? NULL : list->value; + } + if (NULL == name) + { + name = ""; + } + return name; +} + +SEL sel_getUid(const char *selName) +{ + return selector_lookup(selName, 0); +} + +BOOL sel_isEqual(SEL sel1, SEL sel2) +{ + return selector_equal(sel1, sel2); +} + +SEL sel_registerName(const char *selName) +{ + struct objc_selector sel = {selName, 0}; + return objc_register_selector_copy(&sel); +} + +SEL sel_registerTypedName_np(const char *selName, const char *types) +{ + struct objc_selector sel = {selName, types}; + return objc_register_selector_copy(&sel); +} + +const char *sel_getType_np(SEL aSel) +{ + return (NULL == aSel->types) ? "" : aSel->types; +} + + +unsigned sel_copyTypes(const char *selName, const char **types, unsigned count) +{ + SEL untyped = selector_lookup(selName, 0); + if (untyped == NULL) { return 0; } + + struct sel_type_list *l = + SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)untyped->name); + // Skip the head, which just contains the name, not the types. + l = l->next; + + if (count == 0) + { + while (NULL != l) + { + count++; + l = l->next; + } + return count; + } + + unsigned found = 0; + while (NULL != l && foundvalue; + l = l->next; + } + return found; +} + +void __objc_register_selectors_from_list(struct objc_method_list *l) +{ + for (int i=0 ; icount ; i++) + { + Method m = &l->methods[i]; + struct objc_selector sel = { (const char*)m->selector, m->types }; + m->selector = objc_register_selector_copy(&sel); + } +} +/** + * Register all of the (unregistered) selectors that are used in a class. + */ +void __objc_register_selectors_from_class(Class class) +{ + for (struct objc_method_list *l=class->methods ; NULL!=l ; l=l->next) + { + __objc_register_selectors_from_list(l); + } +} +void __objc_register_selector_array(SEL selectors, unsigned long count) +{ + for (unsigned long i=0 ; (iname); + // Skip the head, which just contains the name, not the types. + l = l->next; + if (NULL != l) + { + sel = selector_lookup(name, l->value); + } + return sel; +} + +SEL sel_get_any_typed_uid (const char *name) +{ + return selector_lookup(name, 0); +} + +SEL sel_get_any_uid (const char *name) +{ + return selector_lookup(name, 0); +} + +SEL sel_get_uid(const char *name) +{ + return selector_lookup(name, 0); +} + +const char *sel_get_name(SEL selector) +{ + return sel_getName(selector); +} + +BOOL sel_is_mapped(SEL selector) +{ + return isSelRegistered(selector); +} + +const char *sel_get_type(SEL selector) +{ + return sel_getType_np(selector); +} + +SEL sel_register_name(const char *name) +{ + return sel_registerName(name); +} + +SEL sel_register_typed_name (const char *name, const char *type) +{ + return sel_registerTypedName_np(name, type); +} + +/* + * Some simple sanity tests. + */ +#ifdef SEL_TEST +static void logSelector(SEL sel) +{ + fprintf(stderr, "%s = {%p, %s}\n", sel_getName(sel), sel->name, sel_getType_np(sel)); +} +void objc_resize_uninstalled_dtable(void) {} + +int main(void) +{ + __objc_init_selector_tables(); + SEL a = sel_registerTypedName_np("foo:", "1234"); + logSelector(a); + a = sel_registerName("foo:"); + logSelector(a); + logSelector(sel_get_any_typed_uid("foo:")); + a = sel_registerTypedName_np("foo:", "1234"); + logSelector(a); + logSelector(sel_get_any_typed_uid("foo:")); + a = sel_registerTypedName_np("foo:", "456"); + logSelector(a); + unsigned count = sel_copyTypes("foo:", NULL, 0); + const char *types[count]; + sel_copyTypes("foo:", types, count); + for (unsigned i=0 ; i #include "objc/runtime-legacy.h" +#include "objc/slot.h" #include "objc/sarray.h" #include "objc/encoding.h" #include "lock.h" +#define POOL_NAME slot +#define POOL_TYPE struct objc_slot +#include "pool.h" void objc_resolve_class(Class); @@ -96,31 +100,6 @@ __objc_get_forward_imp (id rcv, SEL sel) abort(); } -struct objc_slot -{ - Class owner; - Class cachedFor; - const char *types; - int version; - IMP method; -}; - -// Malloc slots a page at a time. -#define SLOT_POOL_SIZE ((4096) / sizeof(struct objc_slot)) -static struct objc_slot *slot_pool; -static int slot_pool_left; - -static struct objc_slot * -pool_alloc_slot(void) -{ - if (!slot_pool_left) - { - slot_pool = objc_malloc (sizeof (struct objc_slot) - * SLOT_POOL_SIZE); - slot_pool_left = SLOT_POOL_SIZE; - } - return &slot_pool[--slot_pool_left]; -} static inline IMP sarray_get_imp (struct sarray *dtable, size_t key) { @@ -478,7 +457,7 @@ __objc_send_initialize (Class class) } static struct objc_slot *new_slot_for_method_in_class(Method *method, Class class) { - struct objc_slot *slot = pool_alloc_slot(); + struct objc_slot *slot = slot_pool_alloc(); slot->owner = class; slot->types = method->method_types; slot->method = method->method_imp; @@ -588,12 +567,12 @@ static void merge_method_list_to_class (Class class, } return; } - //struct objc_slot *firstslot = + /* + struct objc_slot *firstslot = // sarray_get_safe(dtable, (size_t)method_list->method_list[0].method_name->sel_id); // If we've already got the methods from this method list, we also have all // of the methods from all of the ones further along the chain, so don't // bother adding them again. - /* * FIXME: This doesn't take into account the lazy copying stuff in the sarray. if (NULL != firstslot && firstslot->owner == class && @@ -842,6 +821,12 @@ objc_get_uninstalled_dtable () return __objc_uninstalled_dtable; } +void objc_resize_uninstalled_dtable(void) +{ + assert(__objc_uninstalled_dtable != NULL); + sarray_realloc (__objc_uninstalled_dtable, __objc_selector_max_index + 1); +} + // This is an ugly hack to make sure that the compiler can do inlining into // sendmsg2.c from here. It should be removed and the two compiled separately // once we drop support for compilers that are too primitive to do cross-module diff --git a/sendmsg.d b/sendmsg.d index 2d3cc72..63716f1 100644 --- a/sendmsg.d +++ b/sendmsg.d @@ -1,6 +1,7 @@ sendmsg.o: sendmsg.c objc/runtime-legacy.h objc/objc.h objc/objc-api.h \ objc/hash.h objc/Availability.h objc/thr.h objc/objc-decls.h \ - objc/objc-list.h objc/sarray.h objc/encoding.h sendmsg2.c + objc/objc-list.h objc/slot.h objc/sarray.h objc/encoding.h lock.h \ + pool.h sendmsg2.c objc/runtime-legacy.h: @@ -18,8 +19,14 @@ objc/objc-decls.h: objc/objc-list.h: +objc/slot.h: + objc/sarray.h: objc/encoding.h: +lock.h: + +pool.h: + sendmsg2.c: diff --git a/slot.h b/slot.h new file mode 100644 index 0000000..b424ded --- /dev/null +++ b/slot.h @@ -0,0 +1,16 @@ + +/** + * A slot, stored in the dispatch table. A pointer to a slot is returned by + * every lookup function. The slot may be safely cached. + */ +struct objc_slot +{ + /** + * The class that owns this slot. + */ + Class owner; + Class cachedFor; + const char *types; + int version; + IMP method; +};