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
David Chisnall 3 years ago committed by David Chisnall
parent 7c23a07bb4
commit e23882fb23

@ -50,7 +50,7 @@ jobs:
run: |
export LDFLAGS=-L/usr/lib/llvm-${{ matrix.llvm-version }}/lib/
ls -lahR /usr/lib/llvm-${{ matrix.llvm-version }}/lib/
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -G Ninja -DCMAKE_C_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_ASM_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm-version}} -DCMAKE_CXX_FLAGS="-stdlib=${{matrix.cxxlib}}"
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -G Ninja -DTESTS=ON -DCMAKE_C_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_ASM_COMPILER=clang-${{matrix.llvm-version}} -DCMAKE_CXX_COMPILER=clang++-${{matrix.llvm-version}} -DCMAKE_CXX_FLAGS="-stdlib=${{matrix.cxxlib}}"
# Build with a nice ninja status line
- name: Build
working-directory: ${{github.workspace}}/build

@ -31,10 +31,10 @@ https://github.com/gnustep/libobjc2/archive/v2.2.tar.gz
The submodule is available from:
https://github.com/Tessil/robin-map/archive/757de82.zip
https://github.com/Tessil/robin-map/archive/757de82.tar.gz
https://github.com/Tessil/robin-map/archive/d37a410.zip
https://github.com/Tessil/robin-map/archive/d37a410.tar.gz
This will extract as robin-map-757de829927489bee55ab02147484850c687b620.
This will extract as robin-map-d37a41003bfbc7e12e34601f93c18ca2ff6d7c07.
You must move the contents of that directory into third_party/robin_map in the
libobjc2 tree.

@ -67,7 +67,6 @@ set(libobjc_C_SRCS
protocol.c
runtime.c
sarray2.c
selector_table.c
sendmsg2.c
)
set(libobjc_HDRS
@ -95,6 +94,9 @@ set(libBlocksRuntime_COMPATIBILITY_HDRS
Block.h
Block_private.h
)
set(libobjc_CXX_SRCS
selector_table.cc
)
# Windows does not use DWARF EH
if (WIN32)
list(APPEND libobjc_CXX_SRCS eh_win32_msvc.cc)

@ -72,9 +72,9 @@ static void init_runtime(void)
INIT_LOCK(runtime_mutex);
// Create the various tables that the runtime needs.
init_selector_tables();
init_dispatch_tables();
init_protocol_table();
init_class_tables();
init_dispatch_tables();
init_alias_table();
init_arc();
init_trampolines();

@ -78,4 +78,37 @@ extern mutex_t runtime_mutex;
#define UNLOCK_RUNTIME() UNLOCK(&runtime_mutex)
#define LOCK_RUNTIME_FOR_SCOPE() LOCK_FOR_SCOPE(&runtime_mutex)
#ifdef __cplusplus
/**
* C++ wrapper around our mutex, for use with std::lock_guard and friends.
*/
class RecursiveMutex
{
/// The underlying mutex
mutex_t mutex;
public:
/**
* Explicit initialisation of the underlying lock, so that this can be a
* global.
*/
void init()
{
INIT_LOCK(mutex);
}
/// Acquire the lock.
void lock()
{
LOCK(&mutex);
}
/// Release the lock.
void unlock()
{
UNLOCK(&mutex);
}
};
#endif
#endif // __LIBOBJC_LOCK_H_INCLUDED__

@ -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,21 +1,5 @@
#ifndef OBJC_SELECTOR_H_INCLUDED
#define OBJC_SELECTOR_H_INCLUDED
/**
* Structure used to store the types for a selector. This allows for a quick
* test to see whether a selector is polymorphic and allows enumeration of all
* type encodings for a given selector.
*
* This is the same size as an objc_selector, so we can allocate them from the
* objc_selector pool.
*
* Note: For ABI v10, we can probably do something a bit more sensible here and
* make selectors into a linked list.
*/
struct sel_type_list
{
const char *value;
struct sel_type_list *next;
};
/**
* Structure used to store selectors in the list.
@ -60,17 +44,20 @@ static SEL sel_getUntyped(SEL aSel)
return sel_registerTypedName_np(sel_getName(aSel), 0);
}
/**
* Returns whether a selector is mapped.
*/
BOOL isSelRegistered(SEL sel);
#ifdef __cplusplus
extern "C"
{
#endif
/**
* Registers the selector. This selector may be returned later, so it must not
* be freed.
*/
SEL objc_register_selector(SEL aSel);
#ifdef __cplusplus
}
#endif
/**
* SELECTOR() macro to work around the fact that GCC hard-codes the type of
* selectors. This is functionally equivalent to @selector(), but it ensures

@ -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
}

@ -116,13 +116,6 @@ retry:;
}
if (0 == result)
{
if (!isSelRegistered(selector))
{
objc_register_selector(selector);
// This should be a tail call, but GCC is stupid and won't let
// us tail call an always_inline function.
goto retry;
}
if ((result = objc_dtable_lookup(dtable, get_untyped_idx(selector))))
{
if (version)
@ -378,11 +371,6 @@ struct objc_slot2 *objc_get_slot2(Class cls, SEL selector, uint64_t *version)
}
if (NULL == result)
{
if (!isSelRegistered(selector))
{
objc_register_selector(selector);
return objc_get_slot2(cls, selector, version);
}
if ((result = objc_dtable_lookup(dtable, get_untyped_idx(selector))))
{
if (version)

@ -1 +1 @@
Subproject commit 757de829927489bee55ab02147484850c687b620
Subproject commit d37a41003bfbc7e12e34601f93c18ca2ff6d7c07
Loading…
Cancel
Save