Rewrite the selector table in C++.
This replaces a few home-grown datastructures with third-party ones that get a lot more testing: - The home-grown hopscotch hash table is moved to using robin map. The original was designed to be lock free, but we've been using it behind a lock for ages. - The selector list is now a std::vector. - The types list now use std::forward_list. This also removes a couple of code paths that haven't been used since we started using the new ABI data structures internally and upgrading at load time. The new code tries to differentiate in the static type system between registered and unregistered selectors. The check for whether a selector is registered is fragile and depends on no selector being mapped or allocated in memory below the total number of selectors. This check can now disappear on most code paths. On a single test machine (not guaranteed to be representative) the test suite now completes around 20% faster.main
parent
7c23a07bb4
commit
e23882fb23
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
#include "safewindows.h"
|
||||
#if defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP && _WIN32_WINNT >= 0x0A00
|
||||
// Prefer the *FromApp versions when we're being built in a Windows Store App context on
|
||||
// Windows >= 10. *FromApp require the application to be manifested for "codeGeneration".
|
||||
#define VirtualAlloc VirtualAllocFromApp
|
||||
#define VirtualProtect VirtualProtectFromApp
|
||||
#endif // App family partition
|
||||
|
||||
inline void *allocate_pages(size_t size)
|
||||
{
|
||||
return VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
}
|
||||
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
inline void *allocate_pages(size_t size)
|
||||
{
|
||||
void *ret = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
return ret == MAP_FAILED ? nullptr : ret;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
class PoolAllocate
|
||||
{
|
||||
static constexpr size_t PageSize = 4096;
|
||||
static constexpr size_t ChunkSize = sizeof(T) * PageSize;
|
||||
static inline size_t index = PageSize;
|
||||
static inline T *buffer = nullptr;
|
||||
public:
|
||||
static T *allocate()
|
||||
{
|
||||
if (index == PageSize)
|
||||
{
|
||||
index = 0;
|
||||
buffer = static_cast<T*>(allocate_pages(ChunkSize));
|
||||
}
|
||||
return &buffer[index++];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,711 +0,0 @@
|
||||
/**
|
||||
* Handle selector uniquing.
|
||||
*
|
||||
* When building, you may define TYPE_DEPENDENT_DISPATCH to enable message
|
||||
* sends to depend on their types.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include "lock.h"
|
||||
#include "objc/runtime.h"
|
||||
#include "method.h"
|
||||
#include "class.h"
|
||||
#include "selector.h"
|
||||
#include "visibility.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.
|
||||
*/
|
||||
static uint32_t selector_count = 1;
|
||||
/**
|
||||
* Size of the buffer.
|
||||
*/
|
||||
static size_t table_size;
|
||||
/**
|
||||
* Mapping from selector numbers to selector names.
|
||||
*/
|
||||
PRIVATE struct sel_type_list **selector_list = NULL;
|
||||
|
||||
#ifdef DEBUG_SELECTOR_TABLE
|
||||
#define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_LOG(...)
|
||||
#endif
|
||||
|
||||
// Get the functions for string hashing
|
||||
#include "string_hash.h"
|
||||
|
||||
|
||||
/**
|
||||
* Lock protecting the selector table.
|
||||
*/
|
||||
mutex_t selector_table_lock;
|
||||
|
||||
static inline struct sel_type_list *selLookup_locked(uint32_t idx)
|
||||
{
|
||||
if (idx > selector_count)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return selector_list[idx];
|
||||
}
|
||||
|
||||
static inline struct sel_type_list *selLookup(uint32_t idx)
|
||||
{
|
||||
LOCK_FOR_SCOPE(&selector_table_lock);
|
||||
return selLookup_locked(idx);
|
||||
}
|
||||
|
||||
PRIVATE BOOL isSelRegistered(SEL sel)
|
||||
{
|
||||
if ((uintptr_t)sel->name < (uintptr_t)selector_count)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
static const char *sel_getNameNonUnique(SEL sel)
|
||||
{
|
||||
const char *name = sel->name;
|
||||
if (isSelRegistered(sel))
|
||||
{
|
||||
struct sel_type_list * list = selLookup_locked(sel->index);
|
||||
name = (list == NULL) ? NULL : list->value;
|
||||
}
|
||||
if (NULL == name)
|
||||
{
|
||||
name = "";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip anything in a type encoding that is irrelevant to the comparison
|
||||
* between selectors, including type qualifiers and argframe info.
|
||||
*/
|
||||
static const char *skip_irrelevant_type_info(const char *t)
|
||||
{
|
||||
switch (*t)
|
||||
{
|
||||
default: return t;
|
||||
case 'r': case 'n': case 'N': case 'o': case 'O': case 'R':
|
||||
case 'V': case 'A': case '!': case '0'...'9':
|
||||
return skip_irrelevant_type_info(t+1);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL selector_types_equal(const char *t1, const char *t2)
|
||||
{
|
||||
if (t1 == NULL || t2 == NULL) { return t1 == t2; }
|
||||
|
||||
while (('\0' != *t1) && ('\0' != *t1))
|
||||
{
|
||||
t1 = skip_irrelevant_type_info(t1);
|
||||
t2 = skip_irrelevant_type_info(t2);
|
||||
// This is a really ugly hack. For some stupid reason, the people
|
||||
// designing Objective-C type encodings decided to allow * as a
|
||||
// shorthand for char*, because strings are 'special'. Unfortunately,
|
||||
// FSF GCC generates "*" for @encode(BOOL*), while Clang and Apple GCC
|
||||
// generate "^c" or "^C" (depending on whether BOOL is declared
|
||||
// unsigned).
|
||||
//
|
||||
// The correct fix is to remove * completely from type encodings, but
|
||||
// unfortunately my time machine is broken so I can't travel to 1986
|
||||
// and apply a cluebat to those responsible.
|
||||
if ((*t1 == '*') && (*t2 != '*'))
|
||||
{
|
||||
if (*t2 == '^' && (((*(t2+1) == 'C') || (*(t2+2) == 'c'))))
|
||||
{
|
||||
t2++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
else if ((*t2 == '*') && (*t1 != '*'))
|
||||
{
|
||||
if (*t1 == '^' && (((*(t1+1) == 'C') || (*(t1+1) == 'c'))))
|
||||
{
|
||||
t1++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
else if (*t1 != *t2)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ('\0' != *t1) { t1++; }
|
||||
if ('\0' != *t2) { t2++; }
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#ifdef TYPE_DEPENDENT_DISPATCH
|
||||
|
||||
static BOOL selector_types_equivalent(const char *t1, const char *t2)
|
||||
{
|
||||
// We always treat untyped selectors as having the same type as typed
|
||||
// selectors, for dispatch purposes.
|
||||
if (t1 == NULL || t2 == NULL) { return YES; }
|
||||
|
||||
return selector_types_equal(t1, t2);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Compare whether two selectors are identical.
|
||||
*/
|
||||
static int selector_identical(const void *k,
|
||||
const SEL value)
|
||||
{
|
||||
SEL key = (SEL)k;
|
||||
DEBUG_LOG("Comparing %s %s, %s %s\n", sel_getNameNonUnique(key), sel_getNameNonUnique(value), sel_getType_np(key), sel_getType_np(value));
|
||||
return string_compare(sel_getNameNonUnique(key), sel_getNameNonUnique(value)) &&
|
||||
selector_types_equal(sel_getType_np(key), sel_getType_np(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
#ifdef TYPE_DEPENDENT_DISPATCH
|
||||
return selector_identical(k, value);
|
||||
#else
|
||||
SEL key = (SEL)k;
|
||||
return string_compare(sel_getNameNonUnique(key), sel_getNameNonUnique(value));
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a selector.
|
||||
*/
|
||||
static inline uint32_t hash_selector(const void *s)
|
||||
{
|
||||
SEL sel = (SEL)s;
|
||||
uint32_t hash = 5381;
|
||||
const char *str = sel_getNameNonUnique(sel);
|
||||
uint32_t c;
|
||||
while((c = (uint32_t)*str++))
|
||||
{
|
||||
hash = hash * 33 + c;
|
||||
}
|
||||
#ifdef TYPE_DEPENDENT_DISPATCH
|
||||
// We can't use all of the values in the type encoding for the hash,
|
||||
// because our equality test is a bit more complex than simple string
|
||||
// encoding (for example, * and ^C have to be considered equivalent, since
|
||||
// they are both used as encodings for C strings in different situations)
|
||||
if ((str = sel_getType_np(sel)))
|
||||
{
|
||||
while((c = (uint32_t)*str++))
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '@': case 'i': case 'I': case 'l': case 'L':
|
||||
case 'q': case 'Q': case 's': case 'S':
|
||||
hash = hash * 33 + c;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return hash;
|
||||
}
|
||||
|
||||
#define MAP_TABLE_NAME selector
|
||||
#define MAP_TABLE_SINGLE_THREAD
|
||||
#define MAP_TABLE_COMPARE_FUNCTION selector_identical
|
||||
#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;
|
||||
|
||||
static int selector_name_copies;
|
||||
|
||||
PRIVATE void log_selector_memory_usage(void)
|
||||
{
|
||||
fprintf(stderr, "%lu bytes in selector name list.\n", (unsigned long)(table_size * sizeof(void*)));
|
||||
fprintf(stderr, "%d bytes in selector names.\n", selector_name_copies);
|
||||
fprintf(stderr, "%d bytes (%d entries) in selector hash table.\n", (int)(sel_table->table_size *
|
||||
sizeof(struct selector_table_cell_struct)), sel_table->table_size);
|
||||
fprintf(stderr, "%d selectors registered.\n", selector_count);
|
||||
fprintf(stderr, "%d hash table cells per selector (%.2f%% full)\n", sel_table->table_size / selector_count, ((float)selector_count) / sel_table->table_size * 100);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resizes the dtables to ensure that they can store as many selectors as
|
||||
* exist.
|
||||
*/
|
||||
void objc_resize_dtables(uint32_t);
|
||||
|
||||
/**
|
||||
* Create data structures to store selectors.
|
||||
*/
|
||||
PRIVATE void init_selector_tables()
|
||||
{
|
||||
selector_list = calloc(sizeof(void*), 4096);
|
||||
table_size = 4096;
|
||||
INIT_LOCK(selector_table_lock);
|
||||
selector_initialize(&sel_table, 4096);
|
||||
}
|
||||
|
||||
static SEL selector_lookup(const char *name, const char *types)
|
||||
{
|
||||
struct objc_selector sel = {{name}, types};
|
||||
LOCK_FOR_SCOPE(&selector_table_lock);
|
||||
return selector_table_get(sel_table, &sel);
|
||||
}
|
||||
static inline void add_selector_to_table(SEL aSel, int32_t uid, uint32_t idx)
|
||||
{
|
||||
DEBUG_LOG("Sel %s uid: %d, idx: %d, hash: %d\n", sel_getNameNonUnique(aSel), uid, idx, hash_selector(aSel));
|
||||
struct sel_type_list *typeList =
|
||||
(struct sel_type_list *)selector_pool_alloc();
|
||||
typeList->value = aSel->name;
|
||||
typeList->next = 0;
|
||||
// Store the name.
|
||||
if (idx >= table_size)
|
||||
{
|
||||
table_size *= 2;
|
||||
struct sel_type_list **newList = calloc(sizeof(struct sel_type_list*), table_size);
|
||||
if (newList == NULL)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
memcpy(newList, selector_list, sizeof(void*)*(table_size/2));
|
||||
free(selector_list);
|
||||
selector_list = newList;
|
||||
}
|
||||
selector_list[idx] = typeList;
|
||||
// Store the selector.
|
||||
selector_insert(sel_table, aSel);
|
||||
// Set the selector's name to the uid.
|
||||
aSel->name = (const char*)(uintptr_t)uid;
|
||||
}
|
||||
/**
|
||||
* Really registers a selector. Must be called with the selector table locked.
|
||||
*/
|
||||
static inline void register_selector_locked(SEL aSel)
|
||||
{
|
||||
if (aSel->name == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
uintptr_t idx = selector_count++;
|
||||
if (NULL == aSel->types)
|
||||
{
|
||||
DEBUG_LOG("Registering selector %d %s\n", (int)idx, sel_getNameNonUnique(aSel));
|
||||
add_selector_to_table(aSel, idx, idx);
|
||||
objc_resize_dtables(selector_count);
|
||||
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;
|
||||
DEBUG_LOG("Registering selector %d %s\n", (int)idx, sel_getNameNonUnique(aSel));
|
||||
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++; selector_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure we only store one name
|
||||
aSel->name = sel_getNameNonUnique(untyped);
|
||||
}
|
||||
uintptr_t uid = (uintptr_t)untyped->name;
|
||||
TDD(uid = idx);
|
||||
DEBUG_LOG("Registering typed selector %d %s %s\n", (int)uid, sel_getNameNonUnique(aSel), sel_getType_np(aSel));
|
||||
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 = selLookup_locked(untyped->index);
|
||||
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_dtables(selector_count);
|
||||
}
|
||||
/**
|
||||
* Registers a selector. This assumes that the argument is never deallocated.
|
||||
*/
|
||||
PRIVATE 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_equal(aSel, registered))
|
||||
{
|
||||
aSel->name = registered->name;
|
||||
return registered;
|
||||
}
|
||||
assert(!(aSel->types && (strstr(aSel->types, "@\"") != NULL)));
|
||||
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, BOOL copyArgs)
|
||||
{
|
||||
// If an identical selector is already registered, return it.
|
||||
SEL copy = selector_lookup(aSel->name, aSel->types);
|
||||
DEBUG_LOG("Checking if old selector is registered: %d (%d)\n", NULL != copy ? selector_equal(aSel, copy) : 0, ((NULL != copy) && selector_equal(aSel, copy)));
|
||||
if ((NULL != copy) && selector_identical(aSel, copy))
|
||||
{
|
||||
DEBUG_LOG("Not adding new copy\n");
|
||||
return copy;
|
||||
}
|
||||
LOCK_FOR_SCOPE(&selector_table_lock);
|
||||
copy = selector_lookup(aSel->name, aSel->types);
|
||||
if (NULL != copy && selector_identical(aSel, copy))
|
||||
{
|
||||
return copy;
|
||||
}
|
||||
assert(!(aSel->types && (strstr(aSel->types, "@\"") != NULL)));
|
||||
// Create a copy of this selector.
|
||||
copy = selector_pool_alloc();
|
||||
copy->name = aSel->name;
|
||||
copy->types = (NULL == aSel->types) ? NULL : aSel->types;
|
||||
if (copyArgs)
|
||||
{
|
||||
SEL untyped = selector_lookup(aSel->name, 0);
|
||||
if (untyped != NULL)
|
||||
{
|
||||
copy->name = sel_getName(untyped);
|
||||
}
|
||||
else
|
||||
{
|
||||
copy->name = strdup(aSel->name);
|
||||
if (copy->name == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate memory for selector %s\n", aSel->name);
|
||||
abort();
|
||||
}
|
||||
assert(copy->name);
|
||||
selector_name_copies += strlen(copy->name);
|
||||
}
|
||||
if (copy->types != NULL)
|
||||
{
|
||||
copy->types = strdup(copy->types);
|
||||
if (copy->name == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate memory for selector %s\n", aSel->name);
|
||||
abort();
|
||||
}
|
||||
selector_name_copies += strlen(copy->types);
|
||||
}
|
||||
}
|
||||
// Try to register the copy as the authoritative version
|
||||
register_selector_locked(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
PRIVATE uint32_t sel_nextTypeIndex(uint32_t untypedIdx, uint32_t idx)
|
||||
{
|
||||
struct sel_type_list *list = selLookup(untypedIdx);
|
||||
|
||||
if (NULL == list) { return 0; }
|
||||
|
||||
const char *selName = list->value;
|
||||
list = list->next;
|
||||
BOOL found = untypedIdx == idx;
|
||||
while (NULL != list)
|
||||
{
|
||||
SEL sel = selector_lookup(selName, list->value);
|
||||
if (sel->index == untypedIdx) { return 0; }
|
||||
if (found)
|
||||
{
|
||||
return sel->index;
|
||||
}
|
||||
found = (sel->index == idx);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public API functions.
|
||||
*/
|
||||
|
||||
const char *sel_getName(SEL sel)
|
||||
{
|
||||
if (NULL == sel) { return "<null selector>"; }
|
||||
const char *name = sel->name;
|
||||
if (isSelRegistered(sel))
|
||||
{
|
||||
struct sel_type_list * list = selLookup(sel->index);
|
||||
name = (list == NULL) ? NULL : list->value;
|
||||
}
|
||||
else
|
||||
{
|
||||
SEL old = selector_lookup(sel->name, sel->types);
|
||||
if (NULL != old)
|
||||
{
|
||||
return sel_getName(old);
|
||||
}
|
||||
}
|
||||
if (NULL == name)
|
||||
{
|
||||
name = "";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
SEL sel_getUid(const char *selName)
|
||||
{
|
||||
return sel_registerName(selName);
|
||||
}
|
||||
|
||||
BOOL sel_isEqual(SEL sel1, SEL sel2)
|
||||
{
|
||||
if ((0 == sel1) || (0 == sel2))
|
||||
{
|
||||
return sel1 == sel2;
|
||||
}
|
||||
if (sel1->name == sel2->name)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
// Otherwise, do a slow compare
|
||||
return string_compare(sel_getNameNonUnique(sel1), sel_getNameNonUnique(sel2)) TDD(&&
|
||||
(sel1->types == NULL || sel2->types == NULL ||
|
||||
selector_types_equivalent(sel_getType_np(sel1), sel_getType_np(sel2))));
|
||||
}
|
||||
|
||||
SEL sel_registerName(const char *selName)
|
||||
{
|
||||
if (NULL == selName) { return NULL; }
|
||||
struct objc_selector sel = {{selName}, 0};
|
||||
return objc_register_selector_copy(&sel, YES);
|
||||
}
|
||||
|
||||
SEL sel_registerTypedName_np(const char *selName, const char *types)
|
||||
{
|
||||
if (NULL == selName) { return NULL; }
|
||||
struct objc_selector sel = {{selName}, types};
|
||||
return objc_register_selector_copy(&sel, YES);
|
||||
}
|
||||
|
||||
const char *sel_getType_np(SEL aSel)
|
||||
{
|
||||
if (NULL == aSel) { return NULL; }
|
||||
return aSel->types;
|
||||
}
|
||||
|
||||
|
||||
unsigned sel_copyTypes_np(const char *selName, const char **types, unsigned count)
|
||||
{
|
||||
if (NULL == selName) { return 0; }
|
||||
SEL untyped = selector_lookup(selName, 0);
|
||||
if (untyped == NULL) { return 0; }
|
||||
|
||||
struct sel_type_list *l = selLookup(untyped->index);
|
||||
// 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)
|
||||
{
|
||||
if (found<count)
|
||||
{
|
||||
types[found] = l->value;
|
||||
}
|
||||
found++;
|
||||
l = l->next;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
unsigned sel_copyTypedSelectors_np(const char *selName, SEL *const sels, unsigned count)
|
||||
{
|
||||
if (NULL == selName) { return 0; }
|
||||
SEL untyped = selector_lookup(selName, 0);
|
||||
if (untyped == NULL) { return 0; }
|
||||
|
||||
struct sel_type_list *l = selLookup(untyped->index);
|
||||
// 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 && found<count)
|
||||
{
|
||||
sels[found++] = selector_lookup(selName, l->value);
|
||||
l = l->next;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
PRIVATE void objc_register_selectors_from_list(struct objc_method_list *l)
|
||||
{
|
||||
for (int i=0 ; i<l->count ; i++)
|
||||
{
|
||||
Method m = method_at_index(l, i);
|
||||
struct objc_selector sel = { {(const char*)m->selector}, m->types };
|
||||
m->selector = objc_register_selector_copy(&sel, NO);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register all of the (unregistered) selectors that are used in a class.
|
||||
*/
|
||||
PRIVATE 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);
|
||||
}
|
||||
}
|
||||
PRIVATE void objc_register_selector_array(SEL selectors, unsigned long count)
|
||||
{
|
||||
// GCC is broken and always sets the count to 0, so we ignore count until
|
||||
// we can throw stupid and buggy compilers in the bin.
|
||||
for (unsigned long i=0 ; (NULL != selectors[i].name) ; i++)
|
||||
{
|
||||
objc_register_selector(&selectors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Legacy GNU runtime compatibility.
|
||||
*
|
||||
* All of the functions in this section are deprecated and should not be used
|
||||
* in new code.
|
||||
*/
|
||||
#ifndef NO_LEGACY
|
||||
SEL sel_get_typed_uid (const char *name, const char *types)
|
||||
{
|
||||
if (NULL == name) { return NULL; }
|
||||
SEL sel = selector_lookup(name, types);
|
||||
if (NULL == sel) { return sel_registerTypedName_np(name, types); }
|
||||
|
||||
struct sel_type_list *l = selLookup(sel->index);
|
||||
// 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)
|
||||
{
|
||||
if (NULL == name) { return NULL; }
|
||||
SEL sel = selector_lookup(name, 0);
|
||||
if (NULL == sel) { return sel_registerName(name); }
|
||||
|
||||
struct sel_type_list *l = selLookup(sel->index);
|
||||
// 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_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_getNameNonUnique(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);
|
||||
}
|
||||
|
||||
BOOL sel_eq(SEL s1, SEL s2)
|
||||
{
|
||||
return sel_isEqual(s1, s2);
|
||||
}
|
||||
|
||||
#endif // NO_LEGACY
|
||||
@ -0,0 +1,741 @@
|
||||
/**
|
||||
* Handle selector uniquing.
|
||||
*
|
||||
* When building, you may define TYPE_DEPENDENT_DISPATCH to enable message
|
||||
* sends to depend on their types.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <forward_list>
|
||||
#include "third_party/robin-map/include/tsl/robin_set.h"
|
||||
#include "class.h"
|
||||
#include "lock.h"
|
||||
#include "method.h"
|
||||
#include "objc/runtime.h"
|
||||
#include "pool.hh"
|
||||
#include "selector.h"
|
||||
#include "string_hash.h"
|
||||
#include "visibility.h"
|
||||
|
||||
#ifdef TYPE_DEPENDENT_DISPATCH
|
||||
# define TDD(x) x
|
||||
#else
|
||||
# define TDD(x)
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Type representing a selector that has not been registered with the runtime.
|
||||
*
|
||||
* This is used only for looking up entries in the selector table, it is never
|
||||
* stored.
|
||||
*/
|
||||
struct UnregisteredSelector
|
||||
{
|
||||
/// The selector name.
|
||||
const char *name;
|
||||
|
||||
/// The type encoding of the selector.
|
||||
const char *types;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for holding the name and list of types for a selector. With
|
||||
* type-dependent dispatch, we store all of the types that we've seen for each
|
||||
* selector name alongside the untyped variant of the selector. When a
|
||||
* selector is registered with the runtime, its name is replaced with the UID
|
||||
* (dtable index) used for dispatch and we use the first element of the types
|
||||
* list to store the name.
|
||||
*
|
||||
* In the common case, this will have 1-2 entries.
|
||||
*/
|
||||
struct TypeList : public std::forward_list<const char*>
|
||||
{
|
||||
/// The superclass type.
|
||||
using Super = std::forward_list<const char*>;
|
||||
/// Inherit constructors
|
||||
using Super::forward_list;
|
||||
|
||||
/// Get the name of the selector represented by this list
|
||||
const char *name()
|
||||
{
|
||||
return front();
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin iterator. This skips the name and returns an iterator to the
|
||||
* first type.
|
||||
*/
|
||||
auto begin()
|
||||
{
|
||||
return ++(std::forward_list<const char*>::begin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a type. The order of types is not defined and so, for simplicity,
|
||||
* we store new ones immediately after the name element.
|
||||
*/
|
||||
void add_types(const char *types)
|
||||
{
|
||||
// Types cannot be added to an empty type list, a name is the first element.
|
||||
assert(!empty());
|
||||
insert_after(Super::begin(), types);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping from selector numbers to selector names, followed by types.
|
||||
*
|
||||
* Note: This must be a pointer so that we do not hit issues with
|
||||
*/
|
||||
std::vector<TypeList> *selector_list;
|
||||
|
||||
/**
|
||||
* Lock protecting the selector table.
|
||||
*/
|
||||
RecursiveMutex selector_table_lock;
|
||||
|
||||
/// Type to use as a lock guard
|
||||
using LockGuard = std::lock_guard<decltype(selector_table_lock)>;
|
||||
|
||||
inline TypeList *selLookup_locked(uint32_t idx)
|
||||
{
|
||||
if (idx >= selector_list->size())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return &(*selector_list)[idx];
|
||||
}
|
||||
|
||||
inline TypeList *selLookup(uint32_t idx)
|
||||
{
|
||||
LockGuard g{selector_table_lock};
|
||||
return selLookup_locked(idx);
|
||||
}
|
||||
|
||||
BOOL isSelRegistered(SEL sel)
|
||||
{
|
||||
if (sel->index < selector_list->size())
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
/// Gets the name of a registered selector.
|
||||
const char *sel_getNameRegistered(SEL sel)
|
||||
{
|
||||
const char *name = sel->name;
|
||||
return selLookup_locked(sel->index)->name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of a selector that might not have been registered. This
|
||||
* should be used only on legacy-ABI compatibility code paths.
|
||||
*/
|
||||
const char *sel_getNameNonUnique(SEL sel)
|
||||
{
|
||||
const char *name = sel->name;
|
||||
if (isSelRegistered(sel))
|
||||
{
|
||||
auto* list = selLookup_locked(sel->index);
|
||||
name = (list == nullptr) ? nullptr : list->name();
|
||||
}
|
||||
if (nullptr == name)
|
||||
{
|
||||
name = "";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip anything in a type encoding that is irrelevant to the comparison
|
||||
* between selectors, including type qualifiers and argframe info.
|
||||
*/
|
||||
static const char *skip_irrelevant_type_info(const char *t)
|
||||
{
|
||||
switch (*t)
|
||||
{
|
||||
default: return t;
|
||||
case 'r': case 'n': case 'N': case 'o': case 'O': case 'R':
|
||||
case 'V': case 'A': case '!': case '0'...'9':
|
||||
return skip_irrelevant_type_info(t+1);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL selector_types_equal(const char *t1, const char *t2)
|
||||
{
|
||||
if (t1 == nullptr || t2 == nullptr) { return t1 == t2; }
|
||||
|
||||
while (('\0' != *t1) && ('\0' != *t1))
|
||||
{
|
||||
t1 = skip_irrelevant_type_info(t1);
|
||||
t2 = skip_irrelevant_type_info(t2);
|
||||
// This is a really ugly hack. For some stupid reason, the people
|
||||
// designing Objective-C type encodings decided to allow * as a
|
||||
// shorthand for char*, because strings are 'special'. Unfortunately,
|
||||
// FSF GCC generates "*" for @encode(BOOL*), while Clang and Apple GCC
|
||||
// generate "^c" or "^C" (depending on whether BOOL is declared
|
||||
// unsigned).
|
||||
//
|
||||
// The correct fix is to remove * completely from type encodings, but
|
||||
// unfortunately my time machine is broken so I can't travel to 1986
|
||||
// and apply a cluebat to those responsible.
|
||||
if ((*t1 == '*') && (*t2 != '*'))
|
||||
{
|
||||
if (*t2 == '^' && (((*(t2+1) == 'C') || (*(t2+2) == 'c'))))
|
||||
{
|
||||
t2++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
else if ((*t2 == '*') && (*t1 != '*'))
|
||||
{
|
||||
if (*t1 == '^' && (((*(t1+1) == 'C') || (*(t1+1) == 'c'))))
|
||||
{
|
||||
t1++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
else if (*t1 != *t2)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ('\0' != *t1) { t1++; }
|
||||
if ('\0' != *t2) { t2++; }
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#ifdef TYPE_DEPENDENT_DISPATCH
|
||||
|
||||
static BOOL selector_types_equivalent(const char *t1, const char *t2)
|
||||
{
|
||||
// We always treat untyped selectors as having the same type as typed
|
||||
// selectors, for dispatch purposes.
|
||||
if (t1 == nullptr || t2 == nullptr) { return YES; }
|
||||
|
||||
return selector_types_equal(t1, t2);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Compare selectors based on whether they are treated as equivalent for the
|
||||
* purpose of dispatch.
|
||||
*/
|
||||
struct SelectorEqual
|
||||
{
|
||||
/// Opt into heterogeneous lookup
|
||||
using is_transparent = void;
|
||||
|
||||
/// Compare two registered selectors
|
||||
bool operator()(const SEL a, const SEL b) const
|
||||
{
|
||||
#ifdef TYPE_DEPENDENT_DISPATCH
|
||||
return string_compare(sel_getNameRegistered(a), sel_getNameRegistered(b)) &&
|
||||
selector_types_equal(sel_getType_np(a), sel_getType_np(b));
|
||||
#else
|
||||
return string_compare(sel_getNameRegistered(a), sel_getNameRegistered(b));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Compare an unregistered and registered selector
|
||||
bool operator()(const UnregisteredSelector &a, const SEL b) const
|
||||
{
|
||||
#ifdef TYPE_DEPENDENT_DISPATCH
|
||||
return string_compare(a.name, sel_getNameRegistered(b)) &&
|
||||
selector_types_equal(a.types, sel_getType_np(b));
|
||||
#else
|
||||
return string_compare(a.name, sel_getNameRegistered(b));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Compare a registered and unregistered selector
|
||||
bool operator()(const SEL b, const UnregisteredSelector &a) const
|
||||
{
|
||||
return (*this)(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare whether two selectors are identical.
|
||||
*/
|
||||
static int selector_identical(const UnregisteredSelector &key,
|
||||
const SEL value)
|
||||
{
|
||||
return SelectorEqual{}(key, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hash a selector.
|
||||
*/
|
||||
struct SelectorHash
|
||||
{
|
||||
size_t hash(const char *name, const char *types) const
|
||||
{
|
||||
size_t hash = 5381;
|
||||
const char *str = name;
|
||||
size_t c;
|
||||
while((c = (size_t)*str++))
|
||||
{
|
||||
hash = hash * 33 + c;
|
||||
}
|
||||
#ifdef TYPE_DEPENDENT_DISPATCH
|
||||
// We can't use all of the values in the type encoding for the hash,
|
||||
// because our equality test is a bit more complex than simple string
|
||||
// encoding (for example, * and ^C have to be considered equivalent, since
|
||||
// they are both used as encodings for C strings in different situations)
|
||||
if ((str = types))
|
||||
{
|
||||
while((c = (size_t)*str++))
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '@': case 'i': case 'I': case 'l': case 'L':
|
||||
case 'q': case 'Q': case 's': case 'S':
|
||||
hash = hash * 33 + c;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return hash;
|
||||
}
|
||||
|
||||
size_t operator()(objc_selector *sel) const
|
||||
{
|
||||
return hash(sel_getNameNonUnique(sel), sel_getType_np(sel));
|
||||
}
|
||||
|
||||
size_t operator()(const UnregisteredSelector &sel) const
|
||||
{
|
||||
return hash(sel.name, sel.types);
|
||||
}
|
||||
};
|
||||
|
||||
using SelectorAllocator = PoolAllocate<objc_selector>;
|
||||
using SelectorTable = tsl::robin_set<objc_selector*, SelectorHash, SelectorEqual>;
|
||||
|
||||
/**
|
||||
* Table of registered selector. Maps from selector to selector.
|
||||
*/
|
||||
static SelectorTable *selector_table;
|
||||
|
||||
static int selector_name_copies;
|
||||
}
|
||||
|
||||
extern "C" PRIVATE void log_selector_memory_usage(void)
|
||||
{
|
||||
#if 0
|
||||
fprintf(stderr, "%lu bytes in selector name list.\n", (unsigned long)(table_size * sizeof(void*)));
|
||||
fprintf(stderr, "%d bytes in selector names.\n", selector_name_copies);
|
||||
fprintf(stderr, "%d bytes (%d entries) in selector hash table.\n", (int)(sel_table->table_size *
|
||||
sizeof(struct selector_table_cell_struct)), sel_table->table_size);
|
||||
fprintf(stderr, "%d selectors registered.\n", selector_count);
|
||||
fprintf(stderr, "%d hash table cells per selector (%.2f%% full)\n", sel_table->table_size / selector_count, ((float)selector_count) / sel_table->table_size * 100);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resizes the dtables to ensure that they can store as many selectors as
|
||||
* exist.
|
||||
*/
|
||||
extern "C" void objc_resize_dtables(uint32_t);
|
||||
|
||||
/**
|
||||
* Create data structures to store selectors.
|
||||
*/
|
||||
extern "C" PRIVATE void init_selector_tables()
|
||||
{
|
||||
selector_list = new std::vector<TypeList>(1<<16);
|
||||
selector_table = new SelectorTable(1024);
|
||||
selector_table_lock.init();
|
||||
}
|
||||
|
||||
static SEL selector_lookup(const char *name, const char *types)
|
||||
{
|
||||
UnregisteredSelector sel = {name, types};
|
||||
LockGuard g{selector_table_lock};
|
||||
auto result = selector_table->find(sel);
|
||||
return (result == selector_table->end()) ? nullptr : *result;
|
||||
}
|
||||
|
||||
static inline void add_selector_to_table(SEL aSel)
|
||||
{
|
||||
// Store the name at the head of the list.
|
||||
if (selector_list->capacity() == selector_list->size())
|
||||
{
|
||||
selector_list->reserve(selector_list->capacity() * 2);
|
||||
}
|
||||
selector_list->push_back({aSel->name});
|
||||
// Set the selector's name to the uid.
|
||||
aSel->index = selector_list->size() - 1;
|
||||
// Store the selector in the set.
|
||||
selector_table->insert(aSel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Really registers a selector. Must be called with the selector table locked.
|
||||
*/
|
||||
static inline void register_selector_locked(SEL aSel)
|
||||
{
|
||||
if (aSel->name == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (nullptr == aSel->types)
|
||||
{
|
||||
add_selector_to_table(aSel);
|
||||
objc_resize_dtables(selector_list->size());
|
||||
return;
|
||||
}
|
||||
SEL untyped = selector_lookup(aSel->name, 0);
|
||||
// If this has a type encoding, store the untyped version too.
|
||||
if (untyped == nullptr)
|
||||
{
|
||||
untyped = SelectorAllocator::allocate();
|
||||
untyped->name = aSel->name;
|
||||
untyped->types = 0;
|
||||
add_selector_to_table(untyped);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure we only store one copy of the name
|
||||
aSel->name = sel_getNameNonUnique(untyped);
|
||||
}
|
||||
add_selector_to_table(aSel);
|
||||
|
||||
// Add this set of types to the list.
|
||||
if (aSel->types)
|
||||
{
|
||||
(*selector_list)[aSel->index].add_types(aSel->types);
|
||||
TDD((*selector_list)[untyped->index].add_types(aSel->types));
|
||||
}
|
||||
objc_resize_dtables(selector_list->size());
|
||||
}
|
||||
/**
|
||||
* Registers a selector. This assumes that the argument is never deallocated.
|
||||
*/
|
||||
extern "C" PRIVATE SEL objc_register_selector(SEL aSel)
|
||||
{
|
||||
if (isSelRegistered(aSel))
|
||||
{
|
||||
return aSel;
|
||||
}
|
||||
UnregisteredSelector unregistered{aSel->name, aSel->types};
|
||||
// Check that this isn't already registered, before we try
|
||||
SEL registered = selector_lookup(aSel->name, aSel->types);
|
||||
SelectorEqual eq;
|
||||
if (nullptr != registered && eq(unregistered, registered))
|
||||
{
|
||||
aSel->name = registered->name;
|
||||
return registered;
|
||||
}
|
||||
assert(!(aSel->types && (strstr(aSel->types, "@\"") != nullptr)));
|
||||
LockGuard g{selector_table_lock};
|
||||
register_selector_locked(aSel);
|
||||
return aSel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a selector by copying the argument.
|
||||
*/
|
||||
SEL objc_register_selector_copy(UnregisteredSelector &aSel, BOOL copyArgs)
|
||||
{
|
||||
// If an identical selector is already registered, return it.
|
||||
SEL copy = selector_lookup(aSel.name, aSel.types);
|
||||
if ((nullptr != copy) && selector_identical(aSel, copy))
|
||||
{
|
||||
return copy;
|
||||
}
|
||||
LockGuard g{selector_table_lock};
|
||||
copy = selector_lookup(aSel.name, aSel.types);
|
||||
if (nullptr != copy && selector_identical(aSel, copy))
|
||||
{
|
||||
return copy;
|
||||
}
|
||||
assert(!(aSel.types && (strstr(aSel.types, "@\"") != nullptr)));
|
||||
// Create a copy of this selector.
|
||||
copy = SelectorAllocator::allocate();
|
||||
copy->name = aSel.name;
|
||||
copy->types = (nullptr == aSel.types) ? nullptr : aSel.types;
|
||||
if (copyArgs)
|
||||
{
|
||||
SEL untyped = selector_lookup(aSel.name, 0);
|
||||
if (untyped != nullptr)
|
||||
{
|
||||
copy->name = sel_getName(untyped);
|
||||
}
|
||||
else
|
||||
{
|
||||
copy->name = strdup(aSel.name);
|
||||
if (copy->name == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate memory for selector %s\n", aSel.name);
|
||||
abort();
|
||||
}
|
||||
assert(copy->name);
|
||||
selector_name_copies += strlen(copy->name);
|
||||
}
|
||||
if (copy->types != nullptr)
|
||||
{
|
||||
copy->types = strdup(copy->types);
|
||||
if (copy->name == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate memory for selector %s\n", aSel.name);
|
||||
abort();
|
||||
}
|
||||
selector_name_copies += strlen(copy->types);
|
||||
}
|
||||
}
|
||||
// Try to register the copy as the authoritative version
|
||||
register_selector_locked(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public API functions.
|
||||
*/
|
||||
extern "C"
|
||||
{
|
||||
|
||||
const char *sel_getName(SEL sel)
|
||||
{
|
||||
if (nullptr == sel) { return "<null selector>"; }
|
||||
auto list = selLookup(sel->index);
|
||||
return (list == nullptr) ? "" : list->front();
|
||||
}
|
||||
|
||||
SEL sel_getUid(const char *selName)
|
||||
{
|
||||
return sel_registerName(selName);
|
||||
}
|
||||
|
||||
BOOL sel_isEqual(SEL sel1, SEL sel2)
|
||||
{
|
||||
if ((0 == sel1) || (0 == sel2))
|
||||
{
|
||||
return sel1 == sel2;
|
||||
}
|
||||
if (sel1->name == sel2->name)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
// Otherwise, do a slow compare
|
||||
return string_compare(sel_getNameNonUnique(sel1), sel_getNameNonUnique(sel2)) TDD(&&
|
||||
(sel1->types == nullptr || sel2->types == nullptr ||
|
||||
selector_types_equivalent(sel_getType_np(sel1), sel_getType_np(sel2))));
|
||||
}
|
||||
|
||||
SEL sel_registerName(const char *selName)
|
||||
{
|
||||
if (nullptr == selName) { return nullptr; }
|
||||
UnregisteredSelector sel = {selName, nullptr};
|
||||
return objc_register_selector_copy(sel, YES);
|
||||
}
|
||||
|
||||
SEL sel_registerTypedName_np(const char *selName, const char *types)
|
||||
{
|
||||
if (nullptr == selName) { return nullptr; }
|
||||
UnregisteredSelector sel = {selName, types};
|
||||
return objc_register_selector_copy(sel, YES);
|
||||
}
|
||||
|
||||
const char *sel_getType_np(SEL aSel)
|
||||
{
|
||||
if (nullptr == aSel) { return nullptr; }
|
||||
return aSel->types;
|
||||
}
|
||||
|
||||
|
||||
unsigned sel_copyTypes_np(const char *selName, const char **types, unsigned count)
|
||||
{
|
||||
if (nullptr == selName) { return 0; }
|
||||
SEL untyped = selector_lookup(selName, 0);
|
||||
if (untyped == nullptr) { return 0; }
|
||||
|
||||
auto *l = selLookup(untyped->index);
|
||||
if (l == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (count == 0)
|
||||
{
|
||||
for (auto type : *l)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
unsigned found = 0;
|
||||
for (auto type : *l)
|
||||
{
|
||||
if (found < count)
|
||||
{
|
||||
types[found] = type;
|
||||
}
|
||||
found++;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
unsigned sel_copyTypedSelectors_np(const char *selName, SEL *const sels, unsigned count)
|
||||
{
|
||||
if (nullptr == selName) { return 0; }
|
||||
SEL untyped = selector_lookup(selName, 0);
|
||||
if (untyped == nullptr) { return 0; }
|
||||
|
||||
auto *l = selLookup(untyped->index);
|
||||
if (l == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
for (auto type : *l)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
unsigned found = 0;
|
||||
for (auto type : *l)
|
||||
{
|
||||
if (found > count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
sels[found++] = selector_lookup(selName, type);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
extern "C" PRIVATE void objc_register_selectors_from_list(struct objc_method_list *l)
|
||||
{
|
||||
for (int i=0 ; i<l->count ; i++)
|
||||
{
|
||||
Method m = method_at_index(l, i);
|
||||
UnregisteredSelector sel{(const char*)m->selector, m->types};
|
||||
m->selector = objc_register_selector_copy(sel, NO);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register all of the (unregistered) selectors that are used in a class.
|
||||
*/
|
||||
extern "C" PRIVATE void objc_register_selectors_from_class(Class aClass)
|
||||
{
|
||||
for (struct objc_method_list *l=aClass->methods ; nullptr!=l ; l=l->next)
|
||||
{
|
||||
objc_register_selectors_from_list(l);
|
||||
}
|
||||
}
|
||||
extern "C" PRIVATE void objc_register_selector_array(SEL selectors, unsigned long count)
|
||||
{
|
||||
// GCC is broken and always sets the count to 0, so we ignore count until
|
||||
// we can throw stupid and buggy compilers in the bin.
|
||||
for (unsigned long i=0 ; (nullptr != selectors[i].name) ; i++)
|
||||
{
|
||||
objc_register_selector(&selectors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Legacy GNU runtime compatibility.
|
||||
*
|
||||
* All of the functions in this section are deprecated and should not be used
|
||||
* in new code.
|
||||
*/
|
||||
#ifndef NO_LEGACY
|
||||
SEL sel_get_typed_uid (const char *name, const char *types)
|
||||
{
|
||||
if (nullptr == name) { return nullptr; }
|
||||
SEL sel = selector_lookup(name, types);
|
||||
if (nullptr == sel) { return sel_registerTypedName_np(name, types); }
|
||||
|
||||
struct sel_type_list *l = selLookup(sel->index);
|
||||
// Skip the head, which just contains the name, not the types.
|
||||
l = l->next;
|
||||
if (nullptr != l)
|
||||
{
|
||||
sel = selector_lookup(name, l->value);
|
||||
}
|
||||
return sel;
|
||||
}
|
||||
|
||||
SEL sel_get_any_typed_uid (const char *name)
|
||||
{
|
||||
if (nullptr == name) { return nullptr; }
|
||||
SEL sel = selector_lookup(name, 0);
|
||||
if (nullptr == sel) { return sel_registerName(name); }
|
||||
|
||||
struct sel_type_list *l = selLookup(sel->index);
|
||||
// Skip the head, which just contains the name, not the types.
|
||||
l = l->next;
|
||||
if (nullptr != l)
|
||||
{
|
||||
sel = selector_lookup(name, l->value);
|
||||
}
|
||||
return sel;
|
||||
}
|
||||
|
||||
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_getNameNonUnique(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);
|
||||
}
|
||||
|
||||
BOOL sel_eq(SEL s1, SEL s2)
|
||||
{
|
||||
return sel_isEqual(s1, s2);
|
||||
}
|
||||
|
||||
#endif // NO_LEGACY
|
||||
}
|
||||
@ -1 +1 @@
|
||||
Subproject commit 757de829927489bee55ab02147484850c687b620
|
||||
Subproject commit d37a41003bfbc7e12e34601f93c18ca2ff6d7c07
|
||||
Loading…
Reference in New Issue