/* GNU Objective C Runtime class related functions
- Copyright (C) 1993, 1995, 1996, 1997, 2001, 2002, 2009
- Free Software Foundation, Inc.
+ Copyright (C) 1993-2017 Free Software Foundation, Inc.
Contributed by Kresten Krab Thorup and Dennis Glatting.
Lock-free class table code designed and written from scratch by
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
-/*
- The code in this file critically affects class method invocation
+/* The code in this file critically affects class method invocation
speed. This long preamble comment explains why, and the issues
- involved.
-
+ involved.
One of the traditional weaknesses of the GNU Objective-C runtime is
that class method invocations are slow. The reason is that when you
objc_get_class returns the class pointer corresponding to the string
`NSArray'; and because of the lookup, the operation is more
- complicated and slow than a simple instance method invocation.
+ complicated and slow than a simple instance method invocation.
Most high performance Objective-C code (using the GNU Objc runtime)
I had the opportunity to read (or write) work around this problem by
In this case, you always perform a class lookup (the first one), but
then all the [arrayClass new] methods run exactly as fast as an
instance method invocation. It helps if you have many class method
- invocations to the same class.
+ invocations to the same class.
The long-term solution to this problem would be to modify the
compiler to output tables of class pointers corresponding to all the
to perform precisely as fast as instance method invocations, because
no class lookup would be involved. I think the Apple Objective-C
runtime uses this technique. Doing this involves synchronized
- modifications in the runtime and in the compiler.
+ modifications in the runtime and in the compiler.
As a first medicine to the problem, I [NP] have redesigned and
rewritten the way the runtime is performing class lookup. This
doesn't give as much speed as the other (definitive) approach, but
at least a class method invocation now takes approximately 4.5 times
an instance method invocation on my machine (it would take approx 12
- times before the rewriting), which is a lot better.
+ times before the rewriting), which is a lot better.
One of the main reason the new class lookup is so faster is because
I implemented it in a way that can safely run multithreaded without
classes from the table - and the difficult thing with lock-free data
structures is freeing data when is removed from the structures. */
-#include "objc/objc.h"
-#include "objc/objc-api.h"
+#include "objc-private/common.h"
+#include "objc-private/error.h"
+#include "objc/runtime.h"
#include "objc/thr.h"
-#include "objc-private/runtime.h" /* the kitchen sink */
+#include "objc-private/module-abi-8.h" /* For CLS_ISCLASS and similar. */
+#include "objc-private/runtime.h" /* the kitchen sink */
+#include "objc-private/sarray.h" /* For sarray_put_at_safe. */
+#include "objc-private/selector.h" /* For sarray_put_at_safe. */
+#include <string.h> /* For memset */
/* We use a table which maps a class name to the corresponding class
- * pointer. The first part of this file defines this table, and
- * functions to do basic operations on the table. The second part of
- * the file implements some higher level Objective-C functionality for
- * classes by using the functions provided in the first part to manage
- * the table. */
+ pointer. The first part of this file defines this table, and
+ functions to do basic operations on the table. The second part of
+ the file implements some higher level Objective-C functionality for
+ classes by using the functions provided in the first part to manage
+ the table. */
/**
** Class Table Internals
/* 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. */
+ operation. TODO: This is only true under certain circumstances,
+ which should be clarified. */
static objc_mutex_t __class_table_lock = NULL;
/* CLASS_TABLE_HASH is how we compute the hash of a class name. It is
- a macro - *not* a function - arguments *are* modified directly.
+ 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;
}
-/* Insert a class in the table (used when a new class is registered). */
+/* 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)
{
objc_mutex_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;
-
- objc_mutex_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;
- }
- }
-
- objc_mutex_unlock (__class_table_lock);
-}
-
-
/* 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. */
for (i = 0; i < length; i++)
{
if ((node->name)[i] != class_name[i])
- {
- break;
- }
+ break;
}
if (i == length)
next = class_table_array[enumerator->hash];
}
else
- {
- next = enumerator->node->next;
- }
+ next = enumerator->node->next;
if (next != NULL)
{
{
printf ("%4d:", i + 1);
for (j = 0; j < counter; j++)
- {
- printf ("X");
- }
+ printf ("X");
+
printf ("\n");
counter = 0;
}
}
printf ("%4d:", i + 1);
for (j = 0; j < counter; j++)
- {
- printf ("X");
- }
+ printf ("X");
+
printf ("\n");
}
#endif /* DEBUGGING FUNCTIONS */
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. */
+ 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.
+
+ This hook was a public, global variable in the Traditional GNU
+ Objective-C Runtime API (objc/objc-api.h). The modern GNU
+ Objective-C Runtime API (objc/runtime.h) provides the
+ objc_setGetUnknownClassHandler() function instead.
+*/
Class (*_objc_lookup_class) (const char *name) = 0; /* !T:SAFE */
+/* The handler currently in use. PS: if both
+ __obj_get_unknown_class_handler and _objc_lookup_class are defined,
+ __objc_get_unknown_class_handler is called first. */
+static objc_get_unknown_class_handler
+__objc_get_unknown_class_handler = NULL;
+
+objc_get_unknown_class_handler
+objc_setGetUnknownClassHandler (objc_get_unknown_class_handler
+ new_handler)
+{
+ objc_get_unknown_class_handler old_handler
+ = __objc_get_unknown_class_handler;
+ __objc_get_unknown_class_handler = new_handler;
+ return old_handler;
+}
+
/* True when class links has been resolved. */
BOOL __objc_class_links_resolved = NO; /* !T:UNUSED */
}
/* This function adds a class to the class hash table, and assigns the
- class a number, unless it's already known. */
-void
+ class a number, unless it's already known. Return 'YES' if the
+ class was added. Return 'NO' if the class was already known. */
+BOOL
__objc_add_class_to_hash (Class class)
{
- Class h_class;
+ Class existing_class;
objc_mutex_lock (__objc_runtime_mutex);
assert (CLS_ISCLASS (class));
/* Check to see if the class is already in the hash table. */
- h_class = class_table_get_safe (class->name);
- if (! h_class)
+ existing_class = class_table_get_safe (class->name);
+
+ if (existing_class)
{
- /* The class isn't in the hash table. Add the class and assign a class
- number. */
+ objc_mutex_unlock (__objc_runtime_mutex);
+ return NO;
+ }
+ else
+ {
+ /* The class isn't in the hash table. Add the class and assign
+ a class number. */
static unsigned int class_number = 1;
-
+
CLS_SETNUMBER (class, class_number);
CLS_SETNUMBER (class->class_pointer, class_number);
++class_number;
class_table_insert (class->name, class);
- }
- objc_mutex_unlock (__objc_runtime_mutex);
+ objc_mutex_unlock (__objc_runtime_mutex);
+ return YES;
+ }
}
-/* Get the class object for the class named NAME. If NAME does not
- identify a known class, the hook _objc_lookup_class is called. If
- this fails, nil is returned. */
Class
-objc_lookup_class (const char *name)
+objc_getClass (const char *name)
{
Class class;
- class = class_table_get_safe (name);
+ if (name == NULL)
+ return Nil;
+ class = class_table_get_safe (name);
+
if (class)
return class;
+ if (__objc_get_unknown_class_handler)
+ return (*__objc_get_unknown_class_handler) (name);
+
if (_objc_lookup_class)
return (*_objc_lookup_class) (name);
+
+ return Nil;
+}
+
+Class
+objc_lookUpClass (const char *name)
+{
+ if (name == NULL)
+ return Nil;
else
- return 0;
+ return class_table_get_safe (name);
+}
+
+Class
+objc_getMetaClass (const char *name)
+{
+ Class class = objc_getClass (name);
+
+ if (class)
+ return class->class_pointer;
+ else
+ return Nil;
+}
+
+Class
+objc_getRequiredClass (const char *name)
+{
+ Class class = objc_getClass (name);
+
+ if (class)
+ return class;
+ else
+ _objc_abort ("objc_getRequiredClass ('%s') failed: class not found\n", name);
+}
+
+int
+objc_getClassList (Class *returnValue, int maxNumberOfClassesToReturn)
+{
+ /* Iterate over all entries in the table. */
+ int hash, count = 0;
+
+ for (hash = 0; hash < CLASS_TABLE_SIZE; hash++)
+ {
+ class_node_ptr node = class_table_array[hash];
+
+ while (node != NULL)
+ {
+ if (returnValue)
+ {
+ if (count < maxNumberOfClassesToReturn)
+ returnValue[count] = node->pointer;
+ else
+ return count;
+ }
+ count++;
+ node = node->next;
+ }
+ }
+
+ return count;
+}
+
+Class
+objc_allocateClassPair (Class super_class, const char *class_name, size_t extraBytes)
+{
+ Class new_class;
+ Class new_meta_class;
+
+ if (class_name == NULL)
+ return Nil;
+
+ if (objc_getClass (class_name))
+ return Nil;
+
+ if (super_class)
+ {
+ /* If you want to build a hierarchy of classes, you need to
+ build and register them one at a time. The risk is that you
+ are able to cause confusion by registering a subclass before
+ the superclass or similar. */
+ if (CLS_IS_IN_CONSTRUCTION (super_class))
+ return Nil;
+ }
+
+ /* Technically, we should create the metaclass first, then use
+ class_createInstance() to create the class. That complication
+ would be relevant if we had class variables, but we don't, so we
+ just ignore it and create everything directly and assume all
+ classes have the same size. */
+ new_class = objc_calloc (1, sizeof (struct objc_class) + extraBytes);
+ new_meta_class = objc_calloc (1, sizeof (struct objc_class) + extraBytes);
+
+ /* We create an unresolved class, similar to one generated by the
+ compiler. It will be resolved later when we register it.
+
+ Note how the metaclass details are not that important; when the
+ class is resolved, the ones that matter will be fixed up. */
+ new_class->class_pointer = new_meta_class;
+ new_meta_class->class_pointer = 0;
+
+ if (super_class)
+ {
+ /* Force the name of the superclass in place of the link to the
+ actual superclass, which will be put there when the class is
+ resolved. */
+ const char *super_class_name = class_getName (super_class);
+ new_class->super_class = (void *)super_class_name;
+ new_meta_class->super_class = (void *)super_class_name;
+ }
+ else
+ {
+ new_class->super_class = (void *)0;
+ new_meta_class->super_class = (void *)0;
+ }
+
+ new_class->name = objc_malloc (strlen (class_name) + 1);
+ strcpy ((char*)new_class->name, class_name);
+ new_meta_class->name = new_class->name;
+
+ new_class->version = 0;
+ new_meta_class->version = 0;
+
+ new_class->info = _CLS_CLASS | _CLS_IN_CONSTRUCTION;
+ new_meta_class->info = _CLS_META | _CLS_IN_CONSTRUCTION;
+
+ if (super_class)
+ new_class->instance_size = super_class->instance_size;
+ else
+ new_class->instance_size = 0;
+ new_meta_class->instance_size = sizeof (struct objc_class);
+
+ return new_class;
+}
+
+void
+objc_registerClassPair (Class class_)
+{
+ if (class_ == Nil)
+ return;
+
+ if ((! CLS_ISCLASS (class_)) || (! CLS_IS_IN_CONSTRUCTION (class_)))
+ return;
+
+ if ((! CLS_ISMETA (class_->class_pointer)) || (! CLS_IS_IN_CONSTRUCTION (class_->class_pointer)))
+ return;
+
+ objc_mutex_lock (__objc_runtime_mutex);
+
+ if (objc_getClass (class_->name))
+ {
+ objc_mutex_unlock (__objc_runtime_mutex);
+ return;
+ }
+
+ CLS_SET_NOT_IN_CONSTRUCTION (class_);
+ CLS_SET_NOT_IN_CONSTRUCTION (class_->class_pointer);
+
+ __objc_init_class (class_);
+
+ /* Resolve class links immediately. No point in waiting. */
+ __objc_resolve_class_links ();
+
+ objc_mutex_unlock (__objc_runtime_mutex);
+}
+
+void
+objc_disposeClassPair (Class class_)
+{
+ if (class_ == Nil)
+ return;
+
+ if ((! CLS_ISCLASS (class_)) || (! CLS_IS_IN_CONSTRUCTION (class_)))
+ return;
+
+ if ((! CLS_ISMETA (class_->class_pointer)) || (! CLS_IS_IN_CONSTRUCTION (class_->class_pointer)))
+ return;
+
+ /* Undo any class_addIvar(). */
+ if (class_->ivars)
+ {
+ int i;
+ for (i = 0; i < class_->ivars->ivar_count; i++)
+ {
+ struct objc_ivar *ivar = &(class_->ivars->ivar_list[i]);
+
+ objc_free ((char *)ivar->ivar_name);
+ objc_free ((char *)ivar->ivar_type);
+ }
+
+ objc_free (class_->ivars);
+ }
+
+ /* Undo any class_addMethod(). */
+ if (class_->methods)
+ {
+ struct objc_method_list *list = class_->methods;
+ while (list)
+ {
+ int i;
+ struct objc_method_list *next = list->method_next;
+
+ for (i = 0; i < list->method_count; i++)
+ {
+ struct objc_method *method = &(list->method_list[i]);
+
+ objc_free ((char *)method->method_name);
+ objc_free ((char *)method->method_types);
+ }
+
+ objc_free (list);
+ list = next;
+ }
+ }
+
+ /* Undo any class_addProtocol(). */
+ if (class_->protocols)
+ {
+ struct objc_protocol_list *list = class_->protocols;
+ while (list)
+ {
+ struct objc_protocol_list *next = list->next;
+
+ objc_free (list);
+ list = next;
+ }
+ }
+
+ /* Undo any class_addMethod() on the meta-class. */
+ if (class_->class_pointer->methods)
+ {
+ struct objc_method_list *list = class_->class_pointer->methods;
+ while (list)
+ {
+ int i;
+ struct objc_method_list *next = list->method_next;
+
+ for (i = 0; i < list->method_count; i++)
+ {
+ struct objc_method *method = &(list->method_list[i]);
+
+ objc_free ((char *)method->method_name);
+ objc_free ((char *)method->method_types);
+ }
+
+ objc_free (list);
+ list = next;
+ }
+ }
+
+ /* Undo objc_allocateClassPair(). */
+ objc_free ((char *)(class_->name));
+ objc_free (class_->class_pointer);
+ objc_free (class_);
}
+/* Traditional GNU Objective-C Runtime API. Important: this method is
+ called automatically by the compiler while messaging (if using the
+ traditional ABI), so it is worth keeping it fast; don't make it
+ just a wrapper around objc_getClass(). */
+/* Note that this is roughly equivalent to objc_getRequiredClass(). */
/* Get the class object for the class named NAME. If NAME does not
identify a known class, the hook _objc_lookup_class is called. If
this fails, an error message is issued and the system aborts. */
if (class)
return class;
- if (_objc_lookup_class)
+ if (__objc_get_unknown_class_handler)
+ class = (*__objc_get_unknown_class_handler) (name);
+
+ if ((!class) && _objc_lookup_class)
class = (*_objc_lookup_class) (name);
if (class)
return class;
- objc_error (nil, OBJC_ERR_BAD_CLASS,
- "objc runtime: cannot find class %s\n", name);
+ _objc_abort ("objc runtime: cannot find class %s\n", name);
+
return 0;
}
-MetaClass
+/* This is used by the compiler too. */
+Class
objc_get_meta_class (const char *name)
{
return objc_get_class (name)->class_pointer;
}
-/* This function provides a way to enumerate all the classes in the
- executable. Pass *ENUM_STATE == NULL to start the enumeration. The
- function will return 0 when there are no more classes.
- For example:
- id class;
- void *es = NULL;
- while ((class = objc_next_class (&es)))
- ... do something with class;
-*/
+/* This is not used by GCC, but the clang compiler seems to use it
+ when targeting the GNU runtime. That's wrong, but we have it to
+ be compatible. */
Class
-objc_next_class (void **enum_state)
+objc_lookup_class (const char *name)
{
- Class class;
-
- objc_mutex_lock (__objc_runtime_mutex);
-
- /* Make sure the table is there. */
- assert (__class_table_lock);
+ return objc_getClass (name);
+}
- class = class_table_next ((struct class_table_enumerator **) enum_state);
+/* This is used when the implementation of a method changes. It goes
+ through all classes, looking for the ones that have these methods
+ (either method_a or method_b; method_b can be NULL), and reloads
+ the implementation for these. You should call this with the
+ runtime mutex already locked. */
+void
+__objc_update_classes_with_methods (struct objc_method *method_a, struct objc_method *method_b)
+{
+ int hash;
- objc_mutex_unlock (__objc_runtime_mutex);
-
- return class;
+ /* Iterate over all classes. */
+ for (hash = 0; hash < CLASS_TABLE_SIZE; hash++)
+ {
+ class_node_ptr node = class_table_array[hash];
+
+ while (node != NULL)
+ {
+ /* We execute this loop twice: the first time, we iterate
+ over all methods in the class (instance methods), while
+ the second time we iterate over all methods in the meta
+ class (class methods). */
+ Class class = Nil;
+ BOOL done = NO;
+
+ while (done == NO)
+ {
+ struct objc_method_list * method_list;
+
+ if (class == Nil)
+ {
+ /* The first time, we work on the class. */
+ class = node->pointer;
+ }
+ else
+ {
+ /* The second time, we work on the meta class. */
+ class = class->class_pointer;
+ done = YES;
+ }
+
+ method_list = class->methods;
+
+ while (method_list)
+ {
+ int i;
+
+ for (i = 0; i < method_list->method_count; ++i)
+ {
+ struct objc_method *method = &method_list->method_list[i];
+
+ /* If the method is one of the ones we are
+ looking for, update the implementation. */
+ if (method == method_a)
+ sarray_at_put_safe (class->dtable,
+ (sidx) method_a->method_name->sel_id,
+ method_a->method_imp);
+
+ if (method == method_b)
+ {
+ if (method_b != NULL)
+ sarray_at_put_safe (class->dtable,
+ (sidx) method_b->method_name->sel_id,
+ method_b->method_imp);
+ }
+ }
+
+ method_list = method_list->method_next;
+ }
+ }
+ node = node->next;
+ }
+ }
}
/* Resolve super/subclass links for all classes. The only thing we
objc_mutex_unlock (__objc_runtime_mutex);
}
+const char *
+class_getName (Class class_)
+{
+ if (class_ == Nil)
+ return "nil";
+ return class_->name;
+}
-#define CLASSOF(c) ((c)->class_pointer)
+BOOL
+class_isMetaClass (Class class_)
+{
+ /* CLS_ISMETA includes the check for Nil class_. */
+ return CLS_ISMETA (class_);
+}
+/* Even inside libobjc it may be worth using class_getSuperclass
+ instead of accessing class_->super_class directly because it
+ resolves the class links if needed. If you access
+ class_->super_class directly, make sure to deal with the situation
+ where the class is not resolved yet! */
Class
-class_pose_as (Class impostor, Class super_class)
+class_getSuperclass (Class class_)
{
- if (! CLS_ISRESOLV (impostor))
- __objc_resolve_class_links ();
+ if (class_ == Nil)
+ return Nil;
+
+ /* Classes that are in construction are not resolved, and still have
+ the class name (instead of a class pointer) in the
+ class_->super_class field. In that case we need to lookup the
+ superclass name to return the superclass. We can not resolve the
+ class until it is registered. */
+ if (CLS_IS_IN_CONSTRUCTION (class_))
+ {
+ if (CLS_ISMETA (class_))
+ return object_getClass ((id)objc_lookUpClass ((const char *)(class_->super_class)));
+ else
+ return objc_lookUpClass ((const char *)(class_->super_class));
+ }
- /* 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;
- }
+ /* If the class is not resolved yet, super_class would point to a
+ string (the name of the super class) as opposed to the actual
+ super class. In that case, we need to resolve the class links
+ before we can return super_class. */
+ if (! CLS_ISRESOLV (class_))
+ __objc_resolve_class_links ();
- /* Check relationship of impostor and super_class is kept. */
- assert (impostor->super_class == super_class);
- assert (CLASSOF (impostor)->super_class == CLASSOF (super_class));
+ return class_->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. */
+int
+class_getVersion (Class class_)
+{
+ if (class_ == Nil)
+ return 0;
- objc_mutex_lock (__objc_runtime_mutex);
+ return (int)(class_->version);
+}
- class_table_replace (super_class, impostor);
+void
+class_setVersion (Class class_, int version)
+{
+ if (class_ == Nil)
+ return;
- objc_mutex_unlock (__objc_runtime_mutex);
+ class_->version = version;
+}
- /* Next, we update the dispatch tables... */
- __objc_update_dispatch_table_for_class (CLASSOF (impostor));
- __objc_update_dispatch_table_for_class (impostor);
+size_t
+class_getInstanceSize (Class class_)
+{
+ if (class_ == Nil)
+ return 0;
- return impostor;
+ return class_->instance_size;
}
+