diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c399425..0f5083e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/ANNOUNCE b/ANNOUNCE index 1d185f2..4a6adcb 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -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. diff --git a/CMakeLists.txt b/CMakeLists.txt index db58fa2..5c9cb98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/loader.c b/loader.c index 9b6d060..0046492 100644 --- a/loader.c +++ b/loader.c @@ -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(); diff --git a/lock.h b/lock.h index 08c0777..e5dacd3 100644 --- a/lock.h +++ b/lock.h @@ -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__ diff --git a/pool.hh b/pool.hh new file mode 100644 index 0000000..4a2af59 --- /dev/null +++ b/pool.hh @@ -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 +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 +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(allocate_pages(ChunkSize)); + } + return &buffer[index++]; + } +}; + + diff --git a/selector.h b/selector.h index 885c666..1030749 100644 --- a/selector.h +++ b/selector.h @@ -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 diff --git a/selector_table.c b/selector_table.c deleted file mode 100644 index b41b5b1..0000000 --- a/selector_table.c +++ /dev/null @@ -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 -#include -#include -#include -#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 ""; } - 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 (foundvalue; - } - 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 && foundvalue); - l = l->next; - } - return found; -} - -PRIVATE void objc_register_selectors_from_list(struct objc_method_list *l) -{ - for (int i=0 ; icount ; 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 diff --git a/selector_table.cc b/selector_table.cc new file mode 100644 index 0000000..e028144 --- /dev/null +++ b/selector_table.cc @@ -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 +#include +#include +#include +#include +#include +#include +#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 +{ + /// The superclass type. + using Super = std::forward_list; + /// 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::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 *selector_list; + +/** + * Lock protecting the selector table. + */ +RecursiveMutex selector_table_lock; + +/// Type to use as a lock guard +using LockGuard = std::lock_guard; + +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; +using SelectorTable = tsl::robin_set; + +/** + * 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(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 ""; } + 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 ; icount ; 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 +} diff --git a/sendmsg2.c b/sendmsg2.c index 54e0d9c..c11a992 100644 --- a/sendmsg2.c +++ b/sendmsg2.c @@ -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) diff --git a/third_party/robin-map b/third_party/robin-map index 757de82..d37a410 160000 --- a/third_party/robin-map +++ b/third_party/robin-map @@ -1 +1 @@ -Subproject commit 757de829927489bee55ab02147484850c687b620 +Subproject commit d37a41003bfbc7e12e34601f93c18ca2ff6d7c07