diff --git a/ChangeLog b/ChangeLog index 94b1de6..c518f1e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2011-03-17 David Ayers , Richard Frith-Macdonald + + * sendmsg.c: Replace ugly thread-safety patch with nicer one by + David and a few further tweaks by Richard. + 2011-03-05 Richard Frith-Macdonald * sendmsg.c: ugly code changes to make +initialize thread-safe. diff --git a/sendmsg.c b/sendmsg.c index 72148a4..e808e08 100644 --- a/sendmsg.c +++ b/sendmsg.c @@ -49,15 +49,6 @@ Boston, MA 02110-1301, USA. */ #define INVISIBLE_STRUCT_RETURN 0 #endif -/* Maintain a linked list of classes being initialized */ -typedef struct InitializingList { - struct InitializingList *next; - Class class; - struct sarray *dtable; -} InitializingList_t; - -static InitializingList_t *initializing = 0; - /* The uninstalled dispatch table */ struct sarray *__objc_uninstalled_dtable = 0; /* !T:MUTEX */ @@ -74,115 +65,16 @@ IMP (*__objc_msg_forward2) (id, SEL) = NULL; /* Send +initialize to class */ static void __objc_send_initialize (Class); -static void __objc_begin_install_dispatch_table_for_class (Class, Class); -static void __objc_end_install_dispatch_table_for_class (Class); -static void __objc_install_dispatch_table_for_class (Class, Class); - /* Forward declare some functions */ -static void __objc_init_install_dtable (id, SEL); - -/* Now a functio9n to check to see if a class is being initialized. - */ -static int -__objc_is_initializing_dispatch_table(Class c) -{ - if (c->dtable == __objc_uninstalled_dtable) - { - InitializingList_t *list = initializing; - - while (list != 0) - { - if (list->class == c) - { - return 1; - } - list = list->next; - } - } - return 0; -} - -/* Set or remove the dispatch table for an initializing class in the list. - * Assumes that __objc_runtime_mutex is locked down. - */ -static void -__objc_set_initializing_dispatch_table(Class c, struct sarray *d) -{ - InitializingList_t *list = initializing; - - if (d == 0) - { - /* Setting 0 means removing the class */ - if (list != 0) - { - if (list->class == c) - { - initializing = list->next; - objc_free (list); - return; - } - else - { - while (list->next != 0) - { - if (list->next->class == c) - { - InitializingList_t *tmp = list->next; - - list->next = tmp->next; - objc_free (tmp); - return; - } - list = list->next; - } - } - } - } - else - { - while (list != 0 && list->class != c) - { - list = list->next; - } - if (list == 0) - { - list = (InitializingList_t *)objc_malloc (sizeof(InitializingList_t)); - list->class = c; - list->dtable = d; - list->next = initializing; - initializing = list; - } - else - { - list->dtable = d; - } - } -} +static void __objc_install_dtable_for_class (Class cls); +static void __objc_prepare_dtable_for_class (Class cls); +static void __objc_install_prepared_dtable_for_class (Class cls); -/* Return the dispatch table of a class in the process of initialising. - * Assumes that __objc_runtime_mutex is locked down. - * If the class is not initializing, this returns the table installed - * in the class itsself. - */ -static struct sarray * -__objc_dispatch_table (Class c) -{ - InitializingList_t *list = initializing; - - while (list != 0) - { - if (list->class == c) - { - return list->dtable; - } - list = list->next; - } - return c->dtable; -} +static struct sarray *__objc_prepared_dtable_for_class (Class cls); +static IMP __objc_get_prepared_imp (Class cls,SEL sel); /* Various forwarding functions that are used based upon the return type for the selector. -o __objc_block_forward for structures. __objc_double_forward for floats/doubles. __objc_word_forward for pointers or types that fit in registers. */ @@ -238,7 +130,16 @@ __objc_get_forward_imp (id rcv, SEL sel) } } -/* Given a class and selector, return the selector's implementation. */ +/* Given a CLASS and selector, return the implementation corresponding + to the method of the selector. + + If CLASS is a class, the instance method is returned. + If CLASS is a meta class, the class method is returned. + + Since this requires the dispatch table to be installed, this function + will implicitly invoke +initialize for CLASS if it hasn't been + invoked yet. This also insures that +initialize has been invoked + when the returned implementation is called directly. */ inline IMP get_imp (Class class, SEL sel) @@ -254,10 +155,8 @@ get_imp (Class class, SEL sel) void *res = sarray_get_safe (class->dtable, (size_t) sel->sel_id); if (res == 0) { - struct sarray *dtable; - /* Not a valid method */ - if ((dtable = class->dtable) == __objc_uninstalled_dtable) + if (class->dtable == __objc_uninstalled_dtable) { /* The dispatch table needs to be installed. */ objc_mutex_lock (__objc_runtime_mutex); @@ -266,60 +165,83 @@ get_imp (Class class, SEL sel) __objc_uninstalled_dtable again in case another thread installed the dtable while we were waiting for the lock to be released. */ + if (class->dtable == __objc_uninstalled_dtable) + __objc_install_dtable_for_class (class); + + /* If the dispatch table is not yet installed, + we are still in the process of executing +initialize. + But the implementation pointer should be avaliable + if it exists at all. */ + if (class->dtable == __objc_uninstalled_dtable) + { + assert (__objc_prepared_dtable_for_class (class) != 0); + res = __objc_get_prepared_imp (class, sel); + } - if ((dtable = class->dtable) == __objc_uninstalled_dtable) - { - dtable = __objc_dispatch_table (class); - if (dtable == __objc_uninstalled_dtable) - { - __objc_install_dispatch_table_for_class (class, class); - dtable = __objc_dispatch_table (class); - } - } objc_mutex_unlock (__objc_runtime_mutex); + /* Call ourselves with the installed dispatch table + and get the real method */ + if (! res) + res = get_imp (class, sel); } - /* Get the method from the dispatch table (we try to get it - again in case another thread has installed the dtable just - after we invoked sarray_get_safe, but before we checked - class->dtable == __objc_uninstalled_dtable). - */ - res = sarray_get_safe (dtable, (size_t) sel->sel_id); - if (res == 0) - { - /* The dispatch table has been installed, and the method - is not in the dispatch table. So the method just - doesn't exist for the class. Return the forwarding - implementation. */ - res = __objc_get_forward_imp ((id)class, sel); + else + { + /* The dispatch table has been installed. */ + + /* Get the method from the dispatch table (we try to get it + again in case another thread has installed the dtable just + after we invoked sarray_get_safe, but before we checked + class->dtable == __objc_uninstalled_dtable). + */ + res = sarray_get_safe (class->dtable, (size_t) sel->sel_id); + if (res == 0) + { + /* The dispatch table has been installed, and the method + is not in the dispatch table. So the method just + doesn't exist for the class. Return the forwarding + implementation. + We don't know the receiver (only it's class), so we + can't pass that to the function :-( + */ + res = __objc_get_forward_imp (nil, sel); + } } } return res; } /* Query if an object can respond to a selector, returns YES if the -object implements the selector otherwise NO. Does not check if the -method can be forwarded. */ + object implements the selector otherwise NO. Does not check if the + method can be forwarded. + Since this requires the dispatch table to installed, this function + will implicitly invoke +initialize for the class of OBJECT if it + hasn't been invoked yet. */ inline BOOL __objc_responds_to (id object, SEL sel) { - struct sarray *dtable; void *res; + struct sarray *dtable; /* Install dispatch table if need be */ - if ((dtable = object->class_pointer->dtable) == __objc_uninstalled_dtable) + dtable = object->class_pointer->dtable; + if (dtable == __objc_uninstalled_dtable) { objc_mutex_lock (__objc_runtime_mutex); - if ((dtable = object->class_pointer->dtable) == __objc_uninstalled_dtable) + if (object->class_pointer->dtable == __objc_uninstalled_dtable) + __objc_install_dtable_for_class (object->class_pointer); + + /* If the dispatch table is not yet installed, + we are still in the process of executing +initialize. + Yet the dispatch table should be available. */ + if (object->class_pointer->dtable == __objc_uninstalled_dtable) { - dtable = __objc_dispatch_table (object->class_pointer); - if (dtable == __objc_uninstalled_dtable) - { - __objc_install_dispatch_table_for_class (object->class_pointer, - object->class_pointer); - dtable = __objc_dispatch_table (object->class_pointer); - } + dtable = __objc_prepared_dtable_for_class (object->class_pointer); + assert (dtable); } + else + dtable = object->class_pointer->dtable; + objc_mutex_unlock (__objc_runtime_mutex); } @@ -342,27 +264,34 @@ objc_msg_lookup (id receiver, SEL op) (sidx)op->sel_id); if (result == 0) { - Class class = receiver->class_pointer; - struct sarray *dtable; - - if ((dtable = class->dtable) == __objc_uninstalled_dtable) - { - /* Double-checked locking pattern: Check - __objc_uninstalled_dtable again in case another thread - installed the dtable while we were waiting for the lock - to be released. */ + /* Not a valid method */ + if (receiver->class_pointer->dtable == __objc_uninstalled_dtable) + { objc_mutex_lock (__objc_runtime_mutex); - dtable = __objc_dispatch_table (class); - if (dtable == __objc_uninstalled_dtable) + + if (receiver->class_pointer->dtable == __objc_uninstalled_dtable) { /* The dispatch table needs to be installed. - This happens on the very first method call to the class. */ - __objc_init_install_dtable (receiver, op); + This happens on the very first method call to the + class. */ + __objc_install_dtable_for_class (receiver->class_pointer); + + /* If we can get the implementation from the newly installed + dtable, +initialize has completed. */ + result = get_imp (receiver->class_pointer, op); + + /* If not, messages are being sent during +initialize and + we should dispatch them without the installed dtable. + In this case we have locked the mutex recursively + so execution remains in this thread after our unlock. */ + if (result == 0 + && (receiver->class_pointer->dtable + == __objc_uninstalled_dtable)) + result + = __objc_get_prepared_imp (receiver->class_pointer, op); } - objc_mutex_unlock (__objc_runtime_mutex); - /* Get real method for this in newly installed dtable */ - result = get_imp (class, op); + objc_mutex_unlock (__objc_runtime_mutex); } else { @@ -416,55 +345,6 @@ __objc_init_dispatch_tables () __objc_uninstalled_dtable = sarray_new (200, 0); } -/* This function is called by objc_msg_lookup when the - dispatch table needs to be installed; thus it is called once - for each class, namely when the very first message is sent to it. */ -static void -__objc_init_install_dtable (id receiver, SEL op __attribute__ ((__unused__))) -{ - objc_mutex_lock (__objc_runtime_mutex); - - /* This may happen, if the programmer has taken the address of a - method before the dtable was initialized... too bad for him! */ - if (__objc_dispatch_table (receiver->class_pointer) - != __objc_uninstalled_dtable) - { - objc_mutex_unlock (__objc_runtime_mutex); - return; - } - - if (CLS_ISCLASS (receiver->class_pointer)) - { - /* receiver is an ordinary object */ - assert (CLS_ISCLASS (receiver->class_pointer)); - - /* install instance methods table */ - __objc_begin_install_dispatch_table_for_class - (receiver->class_pointer, receiver->class_pointer); - - /* call +initialize -- this will in turn install the factory - dispatch table if not already done :-) */ - __objc_send_initialize (receiver->class_pointer); - - __objc_end_install_dispatch_table_for_class (receiver->class_pointer); - } - else - { - /* receiver is a class object */ - assert (CLS_ISCLASS ((Class)receiver)); - assert (CLS_ISMETA (receiver->class_pointer)); - - /* Install real dtable for factory methods */ - __objc_begin_install_dispatch_table_for_class - (receiver->class_pointer, (Class)receiver); - - __objc_send_initialize ((Class)receiver); - - __objc_end_install_dispatch_table_for_class (receiver->class_pointer); - } - objc_mutex_unlock (__objc_runtime_mutex); -} - /* Install dummy table for class which causes the first message to that class (or instances hereof) to be initialized properly */ void @@ -482,6 +362,9 @@ __objc_send_initialize (Class class) assert (CLS_ISCLASS (class)); assert (! CLS_ISMETA (class)); + /* class_add_method_list/__objc_update_dispatch_table_for_class + may have reset the dispatch table. The canonical way to insure + that we send +initialize just once, is this flag. */ if (! CLS_ISINITIALIZED (class)) { CLS_SETINITIALIZED (class); @@ -490,6 +373,9 @@ __objc_send_initialize (Class class) /* Create the garbage collector type memory description */ __objc_generate_gc_type_description (class); + if (class->super_class) + __objc_send_initialize (class->super_class); + { SEL op = sel_register_name ("initialize"); IMP imp = 0; @@ -528,8 +414,7 @@ __objc_send_initialize (Class class) method nothing is guaranteed about what method will be used. Assumes that __objc_runtime_mutex is locked down. */ static void -__objc_install_methods_in_dtable (struct sarray *dtable, - MethodList_t method_list) +__objc_install_methods_in_dtable (struct sarray *dtable, MethodList_t method_list) { int i; @@ -537,9 +422,7 @@ __objc_install_methods_in_dtable (struct sarray *dtable, return; if (method_list->method_next) - { - __objc_install_methods_in_dtable (dtable, method_list->method_next); - } + __objc_install_methods_in_dtable (dtable, method_list->method_next); for (i = 0; i < method_list->method_count; i++) { @@ -550,136 +433,37 @@ __objc_install_methods_in_dtable (struct sarray *dtable, } } -/* Assumes that __objc_runtime_mutex is locked down. - * If receiver is not null, it is the object whos supercalss must be - * initialised if the superclass of the one we are installing the - * dispatch table for is not already installed. - */ -static void -__objc_begin_install_dispatch_table_for_class (Class class, Class receiver) -{ - struct sarray *dtable; - Class super; - - /* If the class has not yet had its class links resolved, we must - re-compute all class links */ - if (! CLS_ISRESOLV (class)) - __objc_resolve_class_links (); - - super = class->super_class; - - /* If the superclass has not had its dispatch table installed, - * we must do that (and send +initialize to it). - */ - if (super != 0 && __objc_dispatch_table(super) == __objc_uninstalled_dtable) - { - __objc_begin_install_dispatch_table_for_class - (super, receiver->super_class); - if (CLS_ISMETA (super)) - { - /* super is a metaclass, so we must send +initialize to the - * superclass of the receiver. - */ - __objc_send_initialize (receiver->super_class); - } - else - { - /* super is a class and must therefore receive +initialize - */ - __objc_send_initialize (super); - } - __objc_end_install_dispatch_table_for_class (super); - } - - /* Allocate dtable if necessary */ - if (super == 0) - { - dtable = sarray_new (__objc_selector_max_index, 0); - } - else - { - dtable = sarray_lazy_copy (__objc_dispatch_table (super)); - } - - __objc_install_methods_in_dtable (dtable, class->methods); - __objc_set_initializing_dispatch_table(class, dtable); -} - -/* Assumes that __objc_runtime_mutex is locked down. - * The __objc_begin_install_dispatch_table_for_class() function must have run. - */ -static void -__objc_end_install_dispatch_table_for_class (Class class) -{ - class->dtable = __objc_dispatch_table (class); - __objc_set_initializing_dispatch_table(class, 0); -} - -/* Assumes that __objc_runtime_mutex is locked down. - */ -static void -__objc_install_dispatch_table_for_class (Class class, Class receiver) -{ - if (__objc_is_initializing_dispatch_table(class)) - { - struct sarray *dtable; - Class super = class->super_class; - - /* Already in the process of installing/initialising the dispatch - * table, so we can assume that the code which started the process - * will also end it, and all we need to do is update the table now. - */ - if (super == 0) - { - dtable = sarray_new (__objc_selector_max_index, 0); - } - else - { - dtable = sarray_lazy_copy (__objc_dispatch_table (super)); - } - __objc_install_methods_in_dtable (dtable, class->methods); - __objc_set_initializing_dispatch_table(class, dtable); - } - else - { - __objc_begin_install_dispatch_table_for_class (class, receiver); - __objc_end_install_dispatch_table_for_class (class); - } -} - - void __objc_update_dispatch_table_for_class (Class class) { + Class next; struct sarray *arr; objc_mutex_lock (__objc_runtime_mutex); - /* not yet installed -- skip it */ - if ((arr = __objc_dispatch_table (class)) == __objc_uninstalled_dtable) + /* not yet installed -- skip it unless in +initialize */ + if (class->dtable == __objc_uninstalled_dtable) { + if (__objc_prepared_dtable_for_class (class)) + { + /* There is a prepared table so we must be initialising this + class ... we must re-do the table preparation. */ + __objc_prepare_dtable_for_class (class); + } objc_mutex_unlock (__objc_runtime_mutex); return; } - if (__objc_is_initializing_dispatch_table(class) == 0) - { - __objc_install_premature_dtable (class); /* someone might require it... */ - } + arr = class->dtable; + __objc_install_premature_dtable (class); /* someone might require it... */ sarray_free (arr); /* release memory */ /* could have been lazy... */ - __objc_install_dispatch_table_for_class (class, 0); + __objc_install_dtable_for_class (class); if (class->subclass_list) /* Traverse subclasses */ - { - Class next; - - for (next = class->subclass_list; next; next = next->sibling_class) - { - __objc_update_dispatch_table_for_class (next); - } - } + for (next = class->subclass_list; next; next = next->sibling_class) + __objc_update_dispatch_table_for_class (next); objc_mutex_unlock (__objc_runtime_mutex); } @@ -925,3 +709,222 @@ objc_get_uninstalled_dtable () { return __objc_uninstalled_dtable; } + +static cache_ptr prepared_dtable_table = 0; + +/* This function is called by: + objc_msg_lookup, get_imp and __objc_responds_to + (and the dispatch table installation functions themselves) + to install a dispatch table for a class. + + If CLS is a class, it installs instance methods. + If CLS is a meta class, it installs class methods. + + In either case +initialize is invoked for the corresponding class. + + The implementation must insure that the dispatch table is not + installed until +initialize completes. Otherwise it opens a + potential race since the installation of the dispatch table is + used as gate in regular method dispatch and we need to guarantee + that +initialize is the first method invoked an that no other + thread my dispatch messages to the class before +initialize + completes. + */ +static void +__objc_install_dtable_for_class (Class cls) +{ + /* If the class has not yet had its class links resolved, we must + re-compute all class links */ + if (! CLS_ISRESOLV (cls)) + __objc_resolve_class_links (); + + /* Make sure the super class has its dispatch table installed + or is at least preparing. + We do not need to send initialize for the super class since + __objc_send_initialize will insure that. + */ + if (cls->super_class + && cls->super_class->dtable == __objc_uninstalled_dtable + && !__objc_prepared_dtable_for_class (cls->super_class)) + { + __objc_install_dtable_for_class (cls->super_class); + /* The superclass initialisation may have also initialised the + current class, in which case there is no more to do. */ + if (cls->dtable != __objc_uninstalled_dtable) + { + return; + } + } + + /* We have already been prepared but +initialize hasn't completed. + The +initialize implementation is probably sending 'self' messages. + We rely on _objc_get_prepared_imp to retrieve the implementation + pointers. */ + if (__objc_prepared_dtable_for_class (cls)) + { + return; + } + + /* We have this function cache the implementation pointers + for _objc_get_prepared_imp but the dispatch table won't + be initilized until __objc_send_initialize completes. */ + __objc_prepare_dtable_for_class (cls); + + /* We may have already invoked +initialize but + __objc_update_dispatch_table_for_class invoked by + class_add_method_list may have reset dispatch table. */ + + /* Call +initialize. + If we are a real class, we are installing instance methods. + If we are a meta class, we are installing class methods. + The __objc_send_initialize itself will insure that the message + is called only once per class. */ + if (CLS_ISCLASS (cls)) + __objc_send_initialize (cls); + else + { + /* Retreive the class from the meta class. */ + Class c = objc_lookup_class (cls->name); + assert (CLS_ISMETA (cls)); + assert (c); + __objc_send_initialize (c); + } + + /* We install the dispatch table correctly when +initialize completed. */ + __objc_install_prepared_dtable_for_class (cls); +} + +/* Builds the dispatch table for the class CLS and stores + it in a place where it can be retrieved by + __objc_get_prepared_imp until __objc_install_prepared_dtable_for_class + installs it into the class. + The dispatch table should not be installed into the class until + +initialize has completed. */ +static void +__objc_prepare_dtable_for_class (Class cls) +{ + struct sarray *dtable; + struct sarray *super_dtable; + + /* This table could be initialized in init.c. + We can not use the class name since + the class maintains the instance methods and + the meta class maintains the the class methods yet + both share the same name. + Classes should be unique in any program. */ + if (! prepared_dtable_table) + prepared_dtable_table + = objc_hash_new(32, + (hash_func_type) objc_hash_ptr, + (compare_func_type) objc_compare_ptrs); + + /* If the class has not yet had its class links resolved, we must + re-compute all class links */ + if (! CLS_ISRESOLV (cls)) + __objc_resolve_class_links (); + + assert (cls); + assert (cls->dtable == __objc_uninstalled_dtable); + + /* If there is already a prepared dtable for this class, we must replace + it with a new version (since there must have been methods added to or + otherwise modified in the class while executing +initialize, and the + table needs to be recomputed. */ + dtable = __objc_prepared_dtable_for_class (cls); + if (0 != dtable) + { + objc_hash_remove (prepared_dtable_table, cls); + sarray_free (dtable); + } + + /* Now prepare the dtable for population. */ + assert (cls != cls->super_class); + if (cls->super_class) + { + /* Inherit the method list from the super class. + Yet the super class may still be initializing + in the case when a class cluster sub class initializes + its super classes. */ + if (cls->super_class->dtable == __objc_uninstalled_dtable) + __objc_install_dtable_for_class (cls->super_class); + + super_dtable = cls->super_class->dtable; + /* If the dispatch table is not yet installed, + we are still in the process of executing +initialize. + Yet the dispatch table should be available. */ + if (super_dtable == __objc_uninstalled_dtable) + super_dtable = __objc_prepared_dtable_for_class (cls->super_class); + + assert (super_dtable); + dtable = sarray_lazy_copy (super_dtable); + } + else + dtable = sarray_new (__objc_selector_max_index, 0); + + __objc_install_methods_in_dtable (dtable, cls->methods); + + objc_hash_add (&prepared_dtable_table, + cls, + dtable); +} + +/* This wrapper only exists to allow an easy replacement of + the lookup implementation and it is expected that the compiler + will optimize it away. */ +static struct sarray * +__objc_prepared_dtable_for_class (Class cls) +{ + struct sarray *dtable = 0; + assert (cls); + if (prepared_dtable_table) + dtable = objc_hash_value_for_key (prepared_dtable_table, cls); + /* dtable my be nil, + since we call this to check whether we are currently preparing + before we start preparing. */ + return dtable; +} + +/* Helper function for messages sent to CLS or implementation pointers + retrieved from CLS during +initialize before the dtable is installed. + When a class implicitly initializes another class which in turn + implicitly invokes methods in this class, before the implementation of + +initialize of CLS completes, this returns the expected implementation. + Forwarding remains the responsibility of objc_msg_lookup. + This function should only be called under the global lock. + */ +static IMP +__objc_get_prepared_imp (Class cls,SEL sel) +{ + struct sarray *dtable; + IMP imp; + + assert (cls); + assert (sel); + assert (cls->dtable == __objc_uninstalled_dtable); + dtable = __objc_prepared_dtable_for_class (cls); + + assert (dtable); + assert (dtable != __objc_uninstalled_dtable); + imp = sarray_get_safe (dtable, (size_t) sel->sel_id); + + /* imp may be Nil if the method does not exist and we + may fallback to the forwarding implementation later. */ + return imp; +} + +/* When this function is called +initialize should be completed. + So now we are safe to install the dispatch table for the + class so that they become available for other threads + that may be waiting in the lock. + */ +static void +__objc_install_prepared_dtable_for_class (Class cls) +{ + assert (cls); + assert (cls->dtable == __objc_uninstalled_dtable); + cls->dtable = __objc_prepared_dtable_for_class (cls); + + assert (cls->dtable); + assert (cls->dtable != __objc_uninstalled_dtable); + objc_hash_remove (prepared_dtable_table, cls); +}