From 9206f48bce61478c6fb845a0e30d7dd68930175e Mon Sep 17 00:00:00 2001 From: sandyx86 Date: Sun, 18 Aug 2024 10:09:50 -0500 Subject: [PATCH] remove cmake --- Makefile | 54 ++ deps.txt | 5 + include/Block.h | 1 + include/Block_private.h | 1 + include/alias.h | 27 + include/asmconstants.h | 29 + include/blocks_runtime.h | 125 ++++ include/buffer.h | 62 ++ include/category.h | 62 ++ include/class.h | 468 +++++++++++++++ include/constant_string.h | 9 + include/dtable.h | 141 +++++ include/dwarf_eh.h | 327 ++++++++++ include/gc_ops.h | 60 ++ include/hash_table.h | 577 ++++++++++++++++++ include/ivar.h | 209 +++++++ include/legacy.h | 15 + include/loader.h | 67 +++ include/lock.h | 114 ++++ include/method.h | 103 ++++ include/module.h | 102 ++++ include/nsobject.h | 10 + include/objcxx_eh.h | 66 ++ include/objcxx_eh_private.h | 241 ++++++++ include/pool.h | 57 ++ include/pool.hh | 45 ++ include/properties.h | 239 ++++++++ include/protocol.h | 243 ++++++++ include/safewindows.h | 20 + include/sarray2.h | 154 +++++ include/selector.h | 72 +++ include/spinlock.h | 81 +++ include/string_hash.h | 34 ++ include/type_encoding_cases.h | 36 ++ include/unwind-arm.h | 217 +++++++ include/unwind-itanium.h | 188 ++++++ include/unwind.h | 5 + include/visibility.h | 24 + objc/objc-runtime.h | 2 + objc/objc-visibility.h | 2 +- objc/runtime.h | 1 + objc/slot.h | 4 +- src/NSBlocks.m | 79 +++ src/Protocol2.m | 45 ++ src/abi_version.c | 130 ++++ src/alias_table.c | 128 ++++ src/arc.mm | 1057 +++++++++++++++++++++++++++++++++ src/associate.m | 469 +++++++++++++++ src/block_to_imp.c | 338 +++++++++++ src/block_trampolines.S | 232 ++++++++ src/blocks_runtime.m | 324 ++++++++++ src/blocks_runtime_np.m | 56 ++ src/caps.c | 42 ++ src/category_loader.c | 100 ++++ src/class_table.c | 602 +++++++++++++++++++ src/common.S | 18 + src/dtable.c | 861 +++++++++++++++++++++++++++ src/eh_personality.c | 772 ++++++++++++++++++++++++ src/eh_trampoline.S | 118 ++++ src/eh_trampoline.cc | 9 + src/eh_win32_msvc.cc | 287 +++++++++ src/encoding2.c | 639 ++++++++++++++++++++ src/fast_paths.m | 62 ++ src/gc_none.c | 121 ++++ src/hooks.c | 3 + src/ivar.c | 190 ++++++ src/legacy.c | 461 ++++++++++++++ src/legacy_malloc.c | 43 ++ src/loader.c | 404 +++++++++++++ src/mutation.m | 13 + src/objc_msgSend.S | 20 + src/objc_msgSend.aarch64.S | 250 ++++++++ src/objc_msgSend.arm.S | 146 +++++ src/objc_msgSend.mips.S | 207 +++++++ src/objc_msgSend.riscv64.S | 141 +++++ src/objc_msgSend.x86-32.S | 132 ++++ src/objc_msgSend.x86-64.S | 315 ++++++++++ src/objcxx_eh.cc | 311 ++++++++++ src/objcxx_eh_mingw.cc | 134 +++++ src/properties.m | 613 +++++++++++++++++++ src/protocol.c | 712 ++++++++++++++++++++++ src/runtime.c | 849 ++++++++++++++++++++++++++ src/sarray2.c | 254 ++++++++ src/selector_table.cc | 741 +++++++++++++++++++++++ src/sendmsg2.c | 498 ++++++++++++++++ src/statics_loader.c | 72 +++ 86 files changed, 17295 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 deps.txt create mode 100644 include/Block.h create mode 100644 include/Block_private.h create mode 100644 include/alias.h create mode 100644 include/asmconstants.h create mode 100644 include/blocks_runtime.h create mode 100644 include/buffer.h create mode 100644 include/category.h create mode 100644 include/class.h create mode 100644 include/constant_string.h create mode 100644 include/dtable.h create mode 100644 include/dwarf_eh.h create mode 100644 include/gc_ops.h create mode 100644 include/hash_table.h create mode 100644 include/ivar.h create mode 100644 include/legacy.h create mode 100644 include/loader.h create mode 100644 include/lock.h create mode 100644 include/method.h create mode 100644 include/module.h create mode 100644 include/nsobject.h create mode 100644 include/objcxx_eh.h create mode 100644 include/objcxx_eh_private.h create mode 100644 include/pool.h create mode 100644 include/pool.hh create mode 100644 include/properties.h create mode 100644 include/protocol.h create mode 100644 include/safewindows.h create mode 100644 include/sarray2.h create mode 100644 include/selector.h create mode 100644 include/spinlock.h create mode 100644 include/string_hash.h create mode 100644 include/type_encoding_cases.h create mode 100644 include/unwind-arm.h create mode 100644 include/unwind-itanium.h create mode 100644 include/unwind.h create mode 100644 include/visibility.h create mode 100644 src/NSBlocks.m create mode 100644 src/Protocol2.m create mode 100644 src/abi_version.c create mode 100644 src/alias_table.c create mode 100644 src/arc.mm create mode 100644 src/associate.m create mode 100644 src/block_to_imp.c create mode 100644 src/block_trampolines.S create mode 100644 src/blocks_runtime.m create mode 100644 src/blocks_runtime_np.m create mode 100644 src/caps.c create mode 100644 src/category_loader.c create mode 100644 src/class_table.c create mode 100644 src/common.S create mode 100644 src/dtable.c create mode 100644 src/eh_personality.c create mode 100644 src/eh_trampoline.S create mode 100644 src/eh_trampoline.cc create mode 100644 src/eh_win32_msvc.cc create mode 100644 src/encoding2.c create mode 100644 src/fast_paths.m create mode 100644 src/gc_none.c create mode 100644 src/hooks.c create mode 100644 src/ivar.c create mode 100644 src/legacy.c create mode 100644 src/legacy_malloc.c create mode 100644 src/loader.c create mode 100644 src/mutation.m create mode 100644 src/objc_msgSend.S create mode 100644 src/objc_msgSend.aarch64.S create mode 100644 src/objc_msgSend.arm.S create mode 100644 src/objc_msgSend.mips.S create mode 100644 src/objc_msgSend.riscv64.S create mode 100644 src/objc_msgSend.x86-32.S create mode 100644 src/objc_msgSend.x86-64.S create mode 100644 src/objcxx_eh.cc create mode 100644 src/objcxx_eh_mingw.cc create mode 100644 src/properties.m create mode 100644 src/protocol.c create mode 100644 src/runtime.c create mode 100644 src/sarray2.c create mode 100644 src/selector_table.cc create mode 100644 src/sendmsg2.c create mode 100644 src/statics_loader.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0671567 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +SHELL=/bin/sh +CC=clang + +ifeq ($(CC),clang) + CCX=clan +endif + +OS= +ifeq ($(OS),windows) + CC=x86_64-w64-mingw32-gcc +endif + +LIB_NAME=objc2 + +SRC=src +BUILD=build +INCLUDE= -I include -I . +CFLAGS= -std=gnu11 +CPPFLAGS= -I /usr/include/x86_64-linux-gnu/c++/11 -I /usr/include/c++/11 + +C_FILES := $(shell find $(SRC) -name '*.c') +M_FILES := $(shell find $(SRC) -name '*.m') +CPP_FILES := $(shell find $(SRC) -name '*.cpp') +MM_FILES := $(shell find $(SRC) -name '*.mm') +O_FILES := $(patsubst $(SRC)/%.c, $(BUILD)/%.o, $(C_FILES)) +O_FILES += $(patsubst $(SRC)/%.m, $(BUILD)/%.o, $(M_FILES)) +O_FILES += $(patsubst $(SRC)/%.cc, $(BUILD)/%.o, $(CPP_FILES)) +O_FILES += $(patsubst $(SRC)/%.mm, $(BUILD)/%.o, $(MM_FILES)) + +.PHONY: all clean dll + +all: $(LIB_NAME) + +dll: $(O_FILES) + $(CC) $< -shared -o objc2.dll + +$(LIB_NAME): $(O_FILES) + ar rcs lib$(LIB_NAME).a $(O_FILES) + +$(BUILD)/%.o: $(SRC)/%.c + $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ + +$(BUILD)/%.o: $(SRC)/%.m + $(CC) -fobjc-exceptions $(CFLAGS) -DEMBEDDED_BLOCKS_RUNTIME $(INCLUDE) -c $< -o $@ + +$(BUILD)/%.o: $(SRC)/%.cc + $(CCX)g++ $(CPPFLAGS) $(INCLUDE) -c $< -o $@ + +$(BUILD)/%.o: $(SRC)/%.mm + $(CCX)g++ $(CPPFLAGS) $(INCLUDE) -c $< -o $@ + + +clean: build + rm $(BUILD)/*.o \ No newline at end of file diff --git a/deps.txt b/deps.txt new file mode 100644 index 0000000..f284118 --- /dev/null +++ b/deps.txt @@ -0,0 +1,5 @@ +//just a list of dependencies i had to install: + +gcc-multilib +g++-multilib +robin-map-dev \ No newline at end of file diff --git a/include/Block.h b/include/Block.h new file mode 100644 index 0000000..253e365 --- /dev/null +++ b/include/Block.h @@ -0,0 +1 @@ +#include diff --git a/include/Block_private.h b/include/Block_private.h new file mode 100644 index 0000000..c565ce7 --- /dev/null +++ b/include/Block_private.h @@ -0,0 +1 @@ +#include diff --git a/include/alias.h b/include/alias.h new file mode 100644 index 0000000..fbd4d94 --- /dev/null +++ b/include/alias.h @@ -0,0 +1,27 @@ +/** Declaration of a helper function for getting class references from aliases. + Copyright (c) 2011 Free Software Foundation, Inc. + + Written by: Niels Grewe + Created: March 2011 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#include "objc/runtime.h" + +OBJC_PUBLIC Class alias_getClass(const char *alias_name); diff --git a/include/asmconstants.h b/include/asmconstants.h new file mode 100644 index 0000000..d15581c --- /dev/null +++ b/include/asmconstants.h @@ -0,0 +1,29 @@ +#ifdef __LP64__ +#define DTABLE_OFFSET 64 +#define SMALLOBJ_BITS 3 +#define SHIFT_OFFSET 0 +#define DATA_OFFSET 8 +#define SLOT_OFFSET 0 +#elif defined(_WIN64) +// long is 32 bits on Win64, so struct objc_class is smaller. All other offsets are the same. +#define DTABLE_OFFSET 56 +#define SMALLOBJ_BITS 3 +#define SHIFT_OFFSET 0 +#define DATA_OFFSET 8 +#define SLOT_OFFSET 0 +#else +#define DTABLE_OFFSET 32 +#define SMALLOBJ_BITS 1 +#define SHIFT_OFFSET 0 +#define DATA_OFFSET 8 +#define SLOT_OFFSET 0 +#endif +#define SMALLOBJ_MASK ((1< + +#define BUFFER_SIZE 128 +static BUFFER_TYPE buffered_object_buffer[BUFFER_SIZE]; +static BUFFER_TYPE *buffered_object_overflow; +static int buffered_objects; +static int buffered_object_overflow_space; + +static void set_buffered_object_at_index(BUFFER_TYPE cat, unsigned int i) +{ + if (i < BUFFER_SIZE) + { + buffered_object_buffer[i] = cat; + } + else + { + i -= BUFFER_SIZE; + if (NULL == buffered_object_overflow) + { + buffered_object_overflow = + calloc(BUFFER_SIZE, sizeof(BUFFER_TYPE)); + buffered_object_overflow_space = BUFFER_SIZE; + } + while (i >= buffered_object_overflow_space) + { + buffered_object_overflow_space <<= 1; + buffered_object_overflow = realloc(buffered_object_overflow, + buffered_object_overflow_space * sizeof(BUFFER_TYPE)); + } + buffered_object_overflow[i] = cat; + } +} + +static BUFFER_TYPE buffered_object_at_index(unsigned int i) +{ + if (i + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * Overflow bitfield. Used for bitfields that are more than 63 bits. + */ +struct objc_bitfield +{ + /** + * The number of elements in the values array. + */ + int32_t length; + /** + * An array of values. Each 32 bits is stored in the native endian for the + * platform. + */ + int32_t values[0]; +}; + +static inline BOOL objc_bitfield_test(uintptr_t bitfield, uint64_t field) +{ + if (bitfield & 1) + { + uint64_t bit = 1<<(field+1); + return (bitfield & bit) == bit; + } + struct objc_bitfield *bf = (struct objc_bitfield*)bitfield; + uint64_t byte = field / 32; + if (byte >= bf->length) + { + return NO; + } + uint64_t bit = 1<<(field%32); + return (bf->values[byte] & bit) == bit; +} + +// begin: objc_class +struct objc_class +{ + /** + * Pointer to the metaclass for this class. The metaclass defines the + * methods use when a message is sent to the class, rather than an + * instance. + */ + Class isa; + /** + * Pointer to the superclass. The compiler will set this to the name of + * the superclass, the runtime will initialize it to point to the real + * class. + */ + Class super_class; + /** + * The name of this class. Set to the same value for both the class and + * its associated metaclass. + */ + const char *name; + /** + * The version of this class. This is not used by the language, but may be + * set explicitly at class load time. + */ + long version; + /** + * A bitfield containing various flags. See the objc_class_flags + * enumerated type for possible values. + */ + unsigned long info; + /** + * The size of this class. For classes using the non-fragile ABI, the + * compiler will set this to a negative value The absolute value will be + * the size of the instance variables defined on just this class. When + * using the fragile ABI, the instance size is the size of instances of + * this class, including any instance variables defined on superclasses. + * + * In both cases, this will be set to the size of an instance of the class + * after the class is registered with the runtime. + */ + long instance_size; + /** + * Metadata describing the instance variables in this class. + */ + struct objc_ivar_list *ivars; + /** + * Metadata for for defining the mappings from selectors to IMPs. Linked + * list of method list structures, one per class and one per category. + */ + struct objc_method_list *methods; + /** + * The dispatch table for this class. Intialized and maintained by the + * runtime. + */ + void *dtable; + /** + * A pointer to the first subclass for this class. Filled in by the + * runtime. + */ + Class subclass_list; + /** + * Pointer to the .cxx_construct method if one exists. This method needs + * to be called outside of the normal dispatch mechanism. + */ + IMP cxx_construct; + /** + * Pointer to the .cxx_destruct method if one exists. This method needs to + * be called outside of the normal dispatch mechanism. + */ + IMP cxx_destruct; + /** + * A pointer to the next sibling class to this. You may find all + * subclasses of a given class by following the subclass_list pointer and + * then subsequently following the sibling_class pointers in the + * subclasses. + */ + Class sibling_class; + + /** + * Metadata describing the protocols adopted by this class. Not used by + * the runtime. + */ + struct objc_protocol_list *protocols; + /** + * Linked list of extra data attached to this class. + */ + struct reference_list *extra_data; + /** + * The version of the ABI used for this class. Currently always zero for v2 + * ABI classes. + */ + long abi_version; + /** + * List of declared properties on this class (NULL if none). + */ + struct objc_property_list *properties; +}; +// end: objc_class + +struct objc_class_gsv1 +{ + /** + * Pointer to the metaclass for this class. The metaclass defines the + * methods use when a message is sent to the class, rather than an + * instance. + */ + Class isa; + /** + * Pointer to the superclass. The compiler will set this to the name of + * the superclass, the runtime will initialize it to point to the real + * class. + */ + Class super_class; + /** + * The name of this class. Set to the same value for both the class and + * its associated metaclass. + */ + const char *name; + /** + * The version of this class. This is not used by the language, but may be + * set explicitly at class load time. + */ + long version; + /** + * A bitfield containing various flags. See the objc_class_flags + * enumerated type for possible values. + */ + unsigned long info; + /** + * The size of this class. For classes using the non-fragile ABI, the + * compiler will set this to a negative value The absolute value will be + * the size of the instance variables defined on just this class. When + * using the fragile ABI, the instance size is the size of instances of + * this class, including any instance variables defined on superclasses. + * + * In both cases, this will be set to the size of an instance of the class + * after the class is registered with the runtime. + */ + long instance_size; + /** + * Metadata describing the instance variables in this class. + */ + struct objc_ivar_list_gcc *ivars; + /** + * Metadata for for defining the mappings from selectors to IMPs. Linked + * list of method list structures, one per class and one per category. + */ + struct objc_method_list_gcc *methods; + /** + * The dispatch table for this class. Intialized and maintained by the + * runtime. + */ + void *dtable; + /** + * A pointer to the first subclass for this class. Filled in by the + * runtime. + */ + Class subclass_list; + /** + * A pointer to the next sibling class to this. You may find all + * subclasses of a given class by following the subclass_list pointer and + * then subsequently following the sibling_class pointers in the + * subclasses. + */ + Class sibling_class; + + /** + * Metadata describing the protocols adopted by this class. Not used by + * the runtime. + */ + struct objc_protocol_list *protocols; + /** + * Linked list of extra data attached to this class. + */ + struct reference_list *extra_data; + /** + * New ABI. The following fields are only available with classes compiled to + * support the new ABI. You may test whether any given class supports this + * ABI by using the CLS_ISNEW_ABI() macro. + */ + + /** + * The version of the ABI used for this class. Zero indicates the ABI first + * implemented by clang 1.0. One indicates the presence of bitmaps + * indicating the offsets of strong, weak, and unretained ivars. Two + * indicates that the new ivar structure is used. + */ + long abi_version; + + /** + * Array of pointers to variables where the runtime will store the ivar + * offset. These may be used for faster access to non-fragile ivars if all + * of the code is compiled for the new ABI. Each of these pointers should + * have the mangled name __objc_ivar_offset_value_{class name}.{ivar name} + * + * When using the compatible non-fragile ABI, this faster form should only be + * used for classes declared in the same compilation unit. + * + * The compiler should also emit symbols of the form + * __objc_ivar_offset_{class name}.{ivar name} which are pointers to the + * offset values. These should be emitted as weak symbols in every module + * where they are used. The legacy-compatible ABI uses these with a double + * layer of indirection. + */ + int **ivar_offsets; + /** + * List of declared properties on this class (NULL if none). This contains + * the accessor methods for each property. + */ + struct objc_property_list_gsv1 *properties; + + /** + * GC / ARC ABI: Fields below this point only exist if abi_version is >= 1. + */ + + /** + * The location of all strong pointer ivars declared by this class. + * + * If the low bit of this field is 0, then this is a pointer to an + * objc_bitfield structure. If the low bit is 1, then the remaining 63 + * bits are set, from low to high, for each ivar in the object that is a + * strong pointer. + */ + uintptr_t strong_pointers; + /** + * The location of all zeroing weak pointer ivars declared by this class. + * The format of this field is the same as the format of the + * strong_pointers field. + */ + uintptr_t weak_pointers; +}; + +/** + * Structure representing the GCC ABI class structure. This is only ever + * required so that we can take its size - struct objc_class begins with the + * same fields, and you can test the new abi flag to tell whether it is safe to + * access the subsequent fields. + */ +struct objc_class_gcc +{ + Class isa; + Class super_class; + const char *name; + long version; + unsigned long info; + long instance_size; + struct objc_ivar_list_gcc *ivars; + struct objc_method_list *methods; + void *dtable; + Class subclass_list; + Class sibling_class; + struct objc_protocol_list *protocols; + void *gc_object_type; +}; + + +/** + * An enumerated type describing all of the valid flags that may be used in the + * info field of a class. + */ +enum objc_class_flags +{ + /** This class structure represents a metaclass. */ + objc_class_flag_meta = (1<<0), + /** Reserved for future ABI versions. */ + objc_class_flag_reserved1 = (1<<1), + /** Reserved for future ABI versions. */ + objc_class_flag_reserved2 = (1<<2), + /** Reserved for future ABI versions. */ + objc_class_flag_reserved3 = (1<<3), + /** Reserved for future ABI versions. */ + objc_class_flag_reserved4 = (1<<4), + /** Reserved for future ABI versions. */ + objc_class_flag_reserved5 = (1<<5), + /** Reserved for future ABI versions. */ + objc_class_flag_reserved6 = (1<<6), + /** Reserved for future ABI versions. */ + objc_class_flag_reserved7 = (1<<7), + /** + * This class has been sent a +initalize message. This message is sent + * exactly once to every class that is sent a message by the runtime, just + * before the first other message is sent. + * + * For direct method support, this is now part of the public ABI. + */ + objc_class_flag_initialized = (1<<8), + /** + * The class has been initialized by the runtime. Its super_class pointer + * should now point to a class, rather than a C string containing the class + * name, and its subclass and sibling class links will have been assigned, + * if applicable. + */ + objc_class_flag_resolved = (1<<9), + /** + * This class was created at run time and may be freed. + */ + objc_class_flag_user_created = (1<<10), + /** + * Instances of this class are provide ARC-safe retain / release / + * autorelease implementations. + */ + objc_class_flag_fast_arc = (1<<11), + /** + * This class is a hidden class (should not be registered in the class + * table nor returned from object_getClass()). + */ + objc_class_flag_hidden_class = (1<<12), + /** + * This class is a hidden class used to store associated values. + */ + objc_class_flag_assoc_class = (1<<13), + /** + * This class has instances that are never deallocated and are therefore + * safe to store directly into weak variables and to skip all reference + * count manipulations. + */ + objc_class_flag_permanent_instances = (1<<14), + /** + * On a metaclass, guarantees that `+alloc` and `+allocWithZone:` are + * trivial wrappers around `class_createInstance`. + * + * On a class, guarantees that `+init` is trivial. + */ + objc_class_flag_fast_alloc_init = (1<<15), + /** + * The class is a block class. Reference count management must be done by + * the underlying blocks runtime. + */ + objc_class_flag_is_block = (1 << 16), +}; + +/** + * Sets the specific class flag. Note: This is not atomic. + */ +static inline void objc_set_class_flag(Class aClass, + enum objc_class_flags flag) +{ + aClass->info |= (unsigned long)flag; +} +/** + * Unsets the specific class flag. Note: This is not atomic. + */ +static inline void objc_clear_class_flag(Class aClass, + enum objc_class_flags flag) +{ + aClass->info &= ~(unsigned long)flag; +} +/** + * Checks whether a specific class flag is set. + */ +static inline BOOL objc_test_class_flag(Class aClass, + enum objc_class_flags flag) +{ + return (aClass->info & (unsigned long)flag) == (unsigned long)flag; +} + + +/** + * Adds a class to the class table. + */ +void class_table_insert(Class cls); + +/** + * Removes a class from the class table. Must be called with the runtime lock + * held! + */ +void class_table_remove(Class cls); + +/** + * Array of classes used for small objects. Small objects are embedded in + * their pointer. In 32-bit mode, we have one small object class (typically + * used for storing 31-bit signed integers. In 64-bit mode then we can have 7, + * because classes are guaranteed to be word aligned. + */ +extern Class SmallObjectClasses[7]; + +static BOOL isSmallObject(id obj) +{ + uintptr_t addr = ((uintptr_t)obj); + return (addr & OBJC_SMALL_OBJECT_MASK) != 0; +} + +__attribute__((always_inline)) +static inline Class classForObject(id obj) +{ + if (UNLIKELY(isSmallObject(obj))) + { + if (sizeof(Class) == 4) + { + return SmallObjectClasses[0]; + } + else + { + uintptr_t addr = ((uintptr_t)obj); + return SmallObjectClasses[(addr & OBJC_SMALL_OBJECT_MASK)]; + } + } + return obj->isa; +} + +static inline BOOL classIsOrInherits(Class cls, Class base) +{ + for (Class c = cls ; + Nil != c ; + c = c->super_class) + { + if (c == base) { return YES; } + } + return NO; +} + +/** + * Free the instance variable lists associated with a class. + */ +void freeIvarLists(Class aClass); +/** + * Free the method lists associated with a class. + */ +void freeMethodLists(Class aClass); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif //__OBJC_CLASS_H_INCLUDED diff --git a/include/constant_string.h b/include/constant_string.h new file mode 100644 index 0000000..83688d3 --- /dev/null +++ b/include/constant_string.h @@ -0,0 +1,9 @@ +#ifndef CONSTANT_STRING_CLASS +# ifdef GNUSTEP +# define CONSTANT_STRING_CLASS "NSConstantString" +# elifdef YESLIB +# define CONSTANT_STRING_CLASS "YSConstantString" +# else +# define CONSTANT_STRING_CLASS "NXConstantString" +# endif +#endif diff --git a/include/dtable.h b/include/dtable.h new file mode 100644 index 0000000..f69ee77 --- /dev/null +++ b/include/dtable.h @@ -0,0 +1,141 @@ +#include "lock.h" +#include "class.h" +#include "sarray2.h" +#include "objc/slot.h" +#include "visibility.h" +#include +#include + +#ifdef __OBJC_LOW_MEMORY__ +typedef struct objc_dtable* dtable_t; +struct objc_slot* objc_dtable_lookup(dtable_t dtable, uint32_t uid); +#else +typedef SparseArray* dtable_t; +# define objc_dtable_lookup SparseArrayLookup +#endif + +/** + * Pointer to the sparse array representing the pretend (uninstalled) dtable. + */ +PRIVATE extern dtable_t uninstalled_dtable; +/** + * Structure for maintaining a linked list of temporary dtables. When sending + * an +initialize message to a class, we create a temporary dtables and store + * it in a linked list. This is then used when sending other messages to + * instances of classes in the middle of initialisation. + */ +typedef struct _InitializingDtable +{ + /** The class that owns the dtable. */ + Class class; + /** The dtable for this class. */ + dtable_t dtable; + /** The next uninstalled dtable in the list. */ + struct _InitializingDtable *next; +} InitializingDtable; + +/** Head of the list of temporary dtables. Protected by initialize_lock. */ +extern InitializingDtable *temporary_dtables; +extern mutex_t initialize_lock; + +/** + * Returns whether a class has an installed dtable. + */ +static inline int classHasInstalledDtable(struct objc_class *cls) +{ + return (cls->dtable != uninstalled_dtable); +} + +OBJC_PUBLIC +int objc_sync_enter(id object); +OBJC_PUBLIC +int objc_sync_exit(id object); +/** + * Returns the dtable for a given class. If we are currently in an +initialize + * method then this will block if called from a thread other than the one + * running the +initialize method. + */ +static inline dtable_t dtable_for_class(Class cls) +{ + if (classHasInstalledDtable(cls)) + { + return cls->dtable; + } + + dtable_t dtable = uninstalled_dtable; + + { + LOCK_FOR_SCOPE(&initialize_lock); + if (classHasInstalledDtable(cls)) + { + return cls->dtable; + } + /* This is a linear search, and so, in theory, could be very slow. It + * is O(n) where n is the number of +initialize methods on the stack. + * In practice, this is a very small number. Profiling with GNUstep + * showed that this peaks at 8. */ + InitializingDtable *buffer = temporary_dtables; + while (NULL != buffer) + { + if (buffer->class == cls) + { + dtable = buffer->dtable; + break; + } + buffer = buffer->next; + } + } + + if (dtable != uninstalled_dtable) + { + // Make sure that we block if +initialize is still running. We do this + // after we've released the initialize lock, so that the real dtable + // can be installed. This acquires / releases a recursive mutex, so if + // this mutex is already held by this thread then this will proceed + // immediately. If it's held by another thread (i.e. the one running + // +initialize) then we block here until it's run. We don't need to do + // this if the dtable is the uninstalled dtable, because that means + // +initialize has not yet been sent, so we can wait until something + // triggers it before needing any synchronisation. + objc_sync_enter((id)cls); + objc_sync_exit((id)cls); + } + return dtable; +} + +/** + * Returns whether a class has had a dtable created. The dtable may be + * installed, or stored in the look-aside buffer. + */ +static inline int classHasDtable(struct objc_class *cls) +{ + return (dtable_for_class(cls) != uninstalled_dtable); +} + +/** + * Updates the dtable for a class and its subclasses. Must be called after + * modifying a class's method list. + */ +void objc_update_dtable_for_class(Class); +/** + * Updates the dtable for a class and its subclasses. Must be called after + * changing and initializing a class's superclass. + */ +void objc_update_dtable_for_new_superclass(Class, Class); +/** + * Adds a single method list to a class. This is used when loading categories, + * and is faster than completely rebuilding the dtable. + */ +void add_method_list_to_class(Class cls, + struct objc_method_list *list); + +/** + * Destroys a dtable. + */ +void free_dtable(dtable_t dtable); + +/** + * Checks whether the class supports ARC. This can be used before the dtable + * is installed. + */ +void checkARCAccessorsSlow(Class cls); diff --git a/include/dwarf_eh.h b/include/dwarf_eh.h new file mode 100644 index 0000000..e06a585 --- /dev/null +++ b/include/dwarf_eh.h @@ -0,0 +1,327 @@ +/** + * This file is Copyright PathScale 2010. Permission granted to distribute + * according to the terms of the MIT license (see COPYING.MIT) + */ +#include +#include + +// _GNU_SOURCE must be defined for unwind.h to expose some of the functions +// that we want. If it isn't, then we define it and undefine it to make sure +// that it doesn't impact the rest of the program. +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +# include "unwind.h" +# undef _GNU_SOURCE +#else +# include "unwind.h" +#endif + +/** + * Type used to store pointers to values computed by DWARF expressions. + */ +typedef unsigned char *dw_eh_ptr_t; +// Flag indicating a signed quantity +#define DW_EH_PE_signed 0x08 +/// DWARF data encoding types +enum dwarf_data_encoding +{ + // Unsigned, little-endian, base 128-encoded (variable length) + DW_EH_PE_uleb128 = 0x01, + // uint16 + DW_EH_PE_udata2 = 0x02, + // uint32 + DW_EH_PE_udata4 = 0x03, + // uint64 + DW_EH_PE_udata8 = 0x04, + // Signed versions of the above: + DW_EH_PE_sleb128 = DW_EH_PE_uleb128 | DW_EH_PE_signed, + DW_EH_PE_sdata2 = DW_EH_PE_udata2 | DW_EH_PE_signed, + DW_EH_PE_sdata4 = DW_EH_PE_udata4 | DW_EH_PE_signed, + DW_EH_PE_sdata8 = DW_EH_PE_udata8 | DW_EH_PE_signed +}; + +static inline enum dwarf_data_encoding get_encoding(unsigned char x) +{ + return (enum dwarf_data_encoding)(x & 0xf); +} + +enum dwarf_data_relative +{ + // Value is omitted + DW_EH_PE_omit = 0xff, + // Absolute pointer value + DW_EH_PE_absptr = 0x00, + // Value relative to program counter + DW_EH_PE_pcrel = 0x10, + // Value relative to the text segment + DW_EH_PE_textrel = 0x20, + // Value relative to the data segment + DW_EH_PE_datarel = 0x30, + // Value relative to the start of the function + DW_EH_PE_funcrel = 0x40, + // Aligned pointer (Not supported yet - are they actually used?) + DW_EH_PE_aligned = 0x50, + // Pointer points to address of real value + DW_EH_PE_indirect = 0x80 +}; +static inline enum dwarf_data_relative get_base(unsigned char x) +{ + return (enum dwarf_data_relative)(x & 0x70); +} +static int is_indirect(unsigned char x) +{ + return (x & DW_EH_PE_indirect); +} + +static inline int dwarf_size_of_fixed_size_field(unsigned char type) +{ + // Low three bits indicate size... + switch (type & 7) + { + case DW_EH_PE_udata2: return 2; + case DW_EH_PE_udata4: return 4; + case DW_EH_PE_udata8: return 8; + case DW_EH_PE_absptr: return sizeof(void*); + } + abort(); +} + +/** + * Read an unsigned, little-endian, base-128, DWARF value. Updates *data to + * point to the end of the value. + */ +static uint64_t read_leb128(unsigned char** data, int *b) +{ + uint64_t uleb = 0; + unsigned int bit = 0; + unsigned char digit = 0; + // We have to read at least one octet, and keep reading until we get to one + // with the high bit unset + do + { + // This check is a bit too strict - we should also check the highest + // bit of the digit. + assert(bit < sizeof(uint64_t) * 8); + // Get the base 128 digit + digit = (**data) & 0x7f; + // Add it to the current value + uleb += digit << bit; + // Increase the shift value + bit += 7; + // Proceed to the next octet + (*data)++; + // Terminate when we reach a value that does not have the high bit set + // (i.e. which was not modified when we mask it with 0x7f) + } while ((*(*data - 1)) != digit); + *b = bit; + + return uleb; +} + +static int64_t read_uleb128(unsigned char** data) +{ + int b; + return read_leb128(data, &b); +} + + +static int64_t read_sleb128(unsigned char** data) +{ + int bits; + // Read as if it's signed + uint64_t uleb = read_leb128(data, &bits); + // If the most significant bit read is 1, then we need to sign extend it + if (uleb >> (bits-1) == 1) + { + // Sign extend by setting all bits in front of it to 1 + uleb |= ((int64_t)-1) << bits; + } + return (int64_t)uleb; +} + +static uint64_t read_value(char encoding, unsigned char **data) +{ + enum dwarf_data_encoding type = get_encoding(encoding); + uint64_t v; + switch ((int)type) + { + // Read fixed-length types +#define READ(dwarf, type) \ + case dwarf:\ + v = (uint64_t)(*(type*)(*data));\ + *data += sizeof(type);\ + break; + READ(DW_EH_PE_udata2, uint16_t) + READ(DW_EH_PE_udata4, uint32_t) + READ(DW_EH_PE_udata8, uint64_t) + READ(DW_EH_PE_sdata2, int16_t) + READ(DW_EH_PE_sdata4, int32_t) + READ(DW_EH_PE_sdata8, int64_t) + case DW_EH_PE_absptr: + v = (uint64_t)(*(intptr_t*)(*data)); + *data += sizeof(intptr_t); + break; + //READ(DW_EH_PE_absptr, intptr_t) +#undef READ + case DW_EH_PE_sleb128: + v = read_sleb128(data); + break; + case DW_EH_PE_uleb128: + v = read_uleb128(data); + break; + default: abort(); + } + + return v; +} + +static uint64_t resolve_indirect_value(struct _Unwind_Context *c, unsigned char encoding, int64_t v, dw_eh_ptr_t start) +{ + switch (get_base(encoding)) + { + case DW_EH_PE_pcrel: + v += (uint64_t)(uintptr_t)start; + break; + case DW_EH_PE_textrel: + v += (uint64_t)(uintptr_t)_Unwind_GetTextRelBase(c); + break; + case DW_EH_PE_datarel: + v += (uint64_t)(uintptr_t)_Unwind_GetDataRelBase(c); + break; + case DW_EH_PE_funcrel: + v += (uint64_t)(uintptr_t)_Unwind_GetRegionStart(c); + default: + break; + } + // If this is an indirect value, then it is really the address of the real + // value + // TODO: Check whether this should really always be a pointer - it seems to + // be a GCC extensions, so not properly documented... + if (is_indirect(encoding)) + { + v = (uint64_t)(uintptr_t)*(void**)(uintptr_t)v; + } + return v; +} + + +static inline void read_value_with_encoding(struct _Unwind_Context *context, + dw_eh_ptr_t *data, + uint64_t *out) +{ + dw_eh_ptr_t start = *data; + unsigned char encoding = *((*data)++); + // If this value is omitted, skip it and don't touch the output value + if (encoding == DW_EH_PE_omit) { return; } + + *out = read_value(encoding, data); + *out = resolve_indirect_value(context, encoding, *out, start); +} + + +struct dwarf_eh_lsda +{ + dw_eh_ptr_t region_start; + dw_eh_ptr_t landing_pads; + dw_eh_ptr_t type_table; + unsigned char type_table_encoding; + dw_eh_ptr_t call_site_table; + dw_eh_ptr_t action_table; + unsigned char callsite_encoding; +}; + +static inline struct dwarf_eh_lsda parse_lsda(struct _Unwind_Context *context, unsigned char *data) +{ + struct dwarf_eh_lsda lsda; + + lsda.region_start = (dw_eh_ptr_t)(uintptr_t)_Unwind_GetRegionStart(context); + + // If the landing pads are relative to anything other than the start of + // this region, find out where. This is @LPStart in the spec, although the + // encoding that GCC uses does not quite match the spec. + uint64_t v = (uint64_t)(uintptr_t)lsda.region_start; + read_value_with_encoding(context, &data, &v); + lsda.landing_pads = (dw_eh_ptr_t)(uintptr_t)v; + + // If there is a type table, find out where it is. This is @TTBase in the + // spec. Note: we find whether there is a type table pointer by checking + // whether the leading byte is DW_EH_PE_omit (0xff), which is not what the + // spec says, but does seem to be how G++ indicates this. + lsda.type_table = 0; + lsda.type_table_encoding = *data++; + if (lsda.type_table_encoding != DW_EH_PE_omit) + { + v = read_uleb128(&data); + dw_eh_ptr_t type_table = data; + type_table += v; + lsda.type_table = type_table; + //lsda.type_table = (uintptr_t*)(data + v); + } + +#if defined(__arm__) && !defined(__ARM_DWARF_EH__) + lsda.type_table_encoding = (DW_EH_PE_pcrel | DW_EH_PE_indirect); +#endif + + lsda.callsite_encoding = (enum dwarf_data_encoding)(*(data++)); + + // Action table is immediately after the call site table + lsda.action_table = data; + uintptr_t callsite_size = (uintptr_t)read_uleb128(&data); + lsda.action_table = data + callsite_size; + // Call site table is immediately after the header + lsda.call_site_table = (dw_eh_ptr_t)data; + + + return lsda; +} + +struct dwarf_eh_action +{ + dw_eh_ptr_t landing_pad; + dw_eh_ptr_t action_record; +}; + +/** + * Look up the landing pad that corresponds to the current invoke. + */ +__attribute__((unused)) +static struct dwarf_eh_action + dwarf_eh_find_callsite(struct _Unwind_Context *context, struct dwarf_eh_lsda *lsda) +{ + struct dwarf_eh_action result = { 0, 0 }; + uint64_t ip = _Unwind_GetIP(context) - _Unwind_GetRegionStart(context); + unsigned char *callsite_table = (unsigned char*)lsda->call_site_table; + while (callsite_table <= lsda->action_table) + { + // Once again, the layout deviates from the spec. + uint64_t call_site_start, call_site_size, landing_pad, action; + call_site_start = read_value(lsda->callsite_encoding, &callsite_table); + call_site_size = read_value(lsda->callsite_encoding, &callsite_table); + + // Call site entries are started + if (call_site_start > ip) { break; } + + landing_pad = read_value(lsda->callsite_encoding, &callsite_table); + action = read_uleb128(&callsite_table); + + if (call_site_start <= ip && ip <= call_site_start + call_site_size) + { + if (action) + { + // Action records are 1-biased so both no-record and zeroth + // record can be stored. + result.action_record = lsda->action_table + action - 1; + } + // No landing pad means keep unwinding. + if (landing_pad) + { + // Landing pad is the offset from the value in the header + result.landing_pad = lsda->landing_pads + landing_pad; + } + break; + } + } + return result; +} + +#define EXCEPTION_CLASS(a,b,c,d,e,f,g,h) ((((uint64_t)a) << 56) + (((uint64_t)b) << 48) + (((uint64_t)c) << 40) + (((uint64_t)d) << 32) + (((uint64_t)e) << 24) + (((uint64_t)f) << 16) + (((uint64_t)g) << 8) + (((uint64_t)h))) diff --git a/include/gc_ops.h b/include/gc_ops.h new file mode 100644 index 0000000..153b4b5 --- /dev/null +++ b/include/gc_ops.h @@ -0,0 +1,60 @@ + +/** + * Garbage collection operations. + */ +struct gc_ops +{ + /** + * Initialises this collector. + */ + void (*init)(void); + /** + * Allocates enough space for a class, followed by some extra bytes. + */ + id (*allocate_class)(Class, size_t); + /** + * Frees an object. + */ + void (*free_object)(id); + /** + * Allocates some memory that can be used to store pointers. This must be + * used instead of malloc() for internal data structures that will store + * pointers passed in from outside. The function is expected to zero the + * memory that it returns. + */ + void* (*malloc)(size_t); + /** + * Frees some memory that was previously used to store pointers. + */ + void (*free)(void*); +}; + +/** + * The mode for garbage collection + */ +enum objc_gc_mode +{ + /** This module neither uses, nor supports, garbage collection. */ + GC_None = 0, + /** + * This module uses garbage collection, but also sends retain / release + * messages. It can be used with or without GC. + */ + GC_Optional = 1, + /** + * This module expects garbage collection and will break without it. + */ + GC_Required = 2, + /** + * This module was compiled with automatic reference counting. This + * guarantees the use of the non-fragile ABI and means that we could + * potentially support GC, although we don't currently. + */ + GC_ARC = 3 +}; + +/** + * The current set of garbage collector operations to use. + */ +extern struct gc_ops *gc; + diff --git a/include/hash_table.h b/include/hash_table.h new file mode 100644 index 0000000..10c4a2b --- /dev/null +++ b/include/hash_table.h @@ -0,0 +1,577 @@ +/** + * hash_table.h provides a template for implementing hopscotch hash tables. + * + * Several macros must be defined before including this file: + * + * MAP_TABLE_NAME defines the name of the table. All of the operations and + * types related to this table will be prefixed with this value. + * + * MAP_TABLE_COMPARE_FUNCTION defines the function used for testing a key + * against a value in the table for equality. This must take two void* + * arguments. The first is the key and the second is the value. + * + * MAP_TABLE_HASH_KEY and MAP_TABLE_HASH_VALUE define a pair of functions that + * takes a key and a value pointer respectively as their argument and returns + * an int32_t representing the hash. + * + * Optionally, MAP_TABLE_STATIC_SIZE may be defined, to define a table type + * which has a static size. + */ +#include "lock.h" +#include +#include +#include + + +#ifdef ENABLE_GC +# include +# include +# define CALLOC(x,y) GC_MALLOC(x*y) +# define IF_NO_GC(x) +# define IF_GC(x) x +#else +# define CALLOC(x,y) calloc(x,y) +# define IF_NO_GC(x) x +# define IF_GC(x) +#endif + +#ifndef MAP_TABLE_NAME +# error You must define MAP_TABLE_NAME. +#endif +#ifndef MAP_TABLE_COMPARE_FUNCTION +# error You must define MAP_TABLE_COMPARE_FUNCTION. +#endif +#ifndef MAP_TABLE_HASH_KEY +# error You must define MAP_TABLE_HASH_KEY +#endif +#ifndef MAP_TABLE_HASH_VALUE +# error You must define MAP_TABLE_HASH_VALUE +#endif + +// Horrible multiple indirection to satisfy the weird precedence rules in cpp +#define REALLY_PREFIX_SUFFIX(x,y) x ## y +#define PREFIX_SUFFIX(x, y) REALLY_PREFIX_SUFFIX(x, y) + +/** + * PREFIX(x) macro adds the table name prefix to the argument. + */ +#define PREFIX(x) PREFIX_SUFFIX(MAP_TABLE_NAME, x) + + +/** + * Map tables are protected by a lock by default. Defining MAP_TABLE_NO_LOCK + * will prevent this and make you responsible for synchronization. + */ +#ifdef MAP_TABLE_NO_LOCK +# define MAP_LOCK() +# define MAP_UNLOCK() +#else +# define MAP_LOCK() (LOCK(&table->lock)) +# define MAP_UNLOCK() (UNLOCK(&table->lock)) +#endif +#ifndef MAP_TABLE_VALUE_TYPE +# define MAP_TABLE_VALUE_TYPE void* +static BOOL PREFIX(_is_null)(void *value) +{ + return value == NULL; +} +# define MAP_TABLE_TYPES_BITMAP 1 +# define MAP_TABLE_VALUE_NULL PREFIX(_is_null) +# define MAP_TABLE_VALUE_PLACEHOLDER NULL +#endif + +typedef struct PREFIX(_table_cell_struct) +{ + uint32_t secondMaps; + MAP_TABLE_VALUE_TYPE value; +} *PREFIX(_table_cell); + +#ifdef MAP_TABLE_STATIC_SIZE +typedef struct +{ + mutex_t lock; + unsigned int table_used; + IF_NO_GC(unsigned int enumerator_count;) + struct PREFIX(_table_cell_struct) table[MAP_TABLE_STATIC_SIZE]; +} PREFIX(_table); +static PREFIX(_table) MAP_TABLE_STATIC_NAME; +# ifndef MAP_TABLE_NO_LOCK +__attribute__((constructor)) void static PREFIX(_table_initializer)(void) +{ + INIT_LOCK(MAP_TABLE_STATIC_NAME.lock); +} +# endif +# define TABLE_SIZE(x) MAP_TABLE_STATIC_SIZE +#else +typedef struct PREFIX(_table_struct) +{ + mutex_t lock; + unsigned int table_size; + unsigned int table_used; + IF_NO_GC(unsigned int enumerator_count;) +# if defined(ENABLE_GC) && defined(MAP_TABLE_TYPES_BITMAP) + GC_descr descr; +# endif + struct PREFIX(_table_struct) *old; + struct PREFIX(_table_cell_struct) *table; +} PREFIX(_table); + +static struct PREFIX(_table_cell_struct) *PREFIX(alloc_cells)(PREFIX(_table) *table, int count) +{ +# if defined(ENABLE_GC) && defined(MAP_TABLE_TYPES_BITMAP) + return GC_CALLOC_EXPLICITLY_TYPED(count, + sizeof(struct PREFIX(_table_cell_struct)), table->descr); +# else + return CALLOC(count, sizeof(struct PREFIX(_table_cell_struct))); +# endif +} + +static PREFIX(_table) *PREFIX(_create)(uint32_t capacity) +{ + PREFIX(_table) *table = CALLOC(1, sizeof(PREFIX(_table))); +# ifndef MAP_TABLE_NO_LOCK + INIT_LOCK(table->lock); +# endif +# if defined(ENABLE_GC) && defined(MAP_TABLE_TYPES_BITMAP) + // The low word in the bitmap stores the offsets of the next entries + GC_word bitmap = (MAP_TABLE_TYPES_BITMAP << 1); + table->descr = GC_make_descriptor(&bitmap, + sizeof(struct PREFIX(_table_cell_struct)) / sizeof (void*)); +# endif + table->table = PREFIX(alloc_cells)(table, capacity); + table->table_size = capacity; + return table; +} + +static void PREFIX(_initialize)(PREFIX(_table) **table, uint32_t capacity) +{ +#ifdef ENABLE_GC + GC_add_roots(table, table+1); +#endif + *table = PREFIX(_create)(capacity); +} + +# define TABLE_SIZE(x) (x->table_size) +#endif + + +#ifdef MAP_TABLE_STATIC_SIZE +static int PREFIX(_table_resize)(PREFIX(_table) *table) +{ + return 0; +} +#else + +static int PREFIX(_insert)(PREFIX(_table) *table, MAP_TABLE_VALUE_TYPE value); + +static int PREFIX(_table_resize)(PREFIX(_table) *table) +{ + struct PREFIX(_table_cell_struct) *newArray = + PREFIX(alloc_cells)(table, table->table_size * 2); + if (NULL == newArray) { return 0; } + + // Allocate a new table structure and move the array into that. Now + // lookups will try using that one, if possible. + PREFIX(_table) *copy = CALLOC(1, sizeof(PREFIX(_table))); + memcpy(copy, table, sizeof(PREFIX(_table))); + table->old = copy; + + // Now we make the original table structure point to the new (empty) array. + table->table = newArray; + table->table_size *= 2; + // The table currently has no entries; the copy has them all. + table->table_used = 0; + + // Finally, copy everything into the new table + // Note: we should really do this in a background thread. At this stage, + // we can do the updates safely without worrying about read contention. + int copied = 0; + for (uint32_t i=0 ; itable_size ; i++) + { + MAP_TABLE_VALUE_TYPE value = copy->table[i].value; + if (!MAP_TABLE_VALUE_NULL(value)) + { + copied++; + PREFIX(_insert)(table, value); + } + } + __sync_synchronize(); + table->old = NULL; +# if !defined(ENABLE_GC) && defined(MAP_TABLE_SINGLE_THREAD) + free(copy->table); + free(copy); +# endif + return 1; +} +#endif + +struct PREFIX(_table_enumerator) +{ + PREFIX(_table) *table; + unsigned int seen; + unsigned int index; +}; + +static inline PREFIX(_table_cell) PREFIX(_table_lookup)(PREFIX(_table) *table, + uint32_t hash) +{ + hash = hash % TABLE_SIZE(table); + return &table->table[hash]; +} + +static int PREFIX(_table_move_gap)(PREFIX(_table) *table, uint32_t fromHash, + uint32_t toHash, PREFIX(_table_cell) emptyCell) +{ + for (uint32_t hash = fromHash - 32 ; hash < fromHash ; hash++) + { + // Get the cell n before the hash. + PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash); + // If this node is a primary entry move it down + if (MAP_TABLE_HASH_VALUE(cell->value) == hash) + { + emptyCell->value = cell->value; + cell->secondMaps |= (1 << ((fromHash - hash) - 1)); + cell->value = MAP_TABLE_VALUE_PLACEHOLDER; + if (hash - toHash < 32) + { + return 1; + } + return PREFIX(_table_move_gap)(table, hash, toHash, cell); + } + int hop = __builtin_ffs(cell->secondMaps); + if (hop > 0 && (hash + hop) < fromHash) + { + PREFIX(_table_cell) hopCell = PREFIX(_table_lookup)(table, hash+hop); + emptyCell->value = hopCell->value; + // Update the hop bit for the new offset + cell->secondMaps |= (1 << ((fromHash - hash) - 1)); + // Clear the hop bit in the original cell + cell->secondMaps &= ~(1 << (hop - 1)); + hopCell->value = MAP_TABLE_VALUE_PLACEHOLDER; + if (hash - toHash < 32) + { + return 1; + } + return PREFIX(_table_move_gap)(table, hash + hop, toHash, hopCell); + } + } + return 0; +} +static int PREFIX(_table_rebalance)(PREFIX(_table) *table, uint32_t hash) +{ + for (unsigned i=32 ; ivalue)) + { + // We've found a free space, try to move it up. + return PREFIX(_table_move_gap)(table, hash + i, hash, cell); + } + } + return 0; +} + +__attribute__((unused)) +static int PREFIX(_insert)(PREFIX(_table) *table, + MAP_TABLE_VALUE_TYPE value) +{ + MAP_LOCK(); + uint32_t hash = MAP_TABLE_HASH_VALUE(value); + PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash); + if (MAP_TABLE_VALUE_NULL(cell->value)) + { + cell->secondMaps = 0; + cell->value = value; + table->table_used++; + MAP_UNLOCK(); + return 1; + } + /* If this cell is full, try the next one. */ + for (unsigned int i=1 ; i<33 ; i++) + { + PREFIX(_table_cell) second = + PREFIX(_table_lookup)(table, hash+i); + if (MAP_TABLE_VALUE_NULL(second->value)) + { + cell->secondMaps |= (1 << (i-1)); + second->value = value; + table->table_used++; + MAP_UNLOCK(); + return 1; + } + } + /* If the table is full, or nearly full, then resize it. Note that we + * resize when the table is at 80% capacity because it's cheaper to copy + * everything than spend the next few updates shuffling everything around + * to reduce contention. A hopscotch hash table starts to degrade in + * performance at around 90% capacity, so stay below that. + */ + if (table->table_used > (0.8 * TABLE_SIZE(table))) + { + PREFIX(_table_resize)(table); + MAP_UNLOCK(); + return PREFIX(_insert)(table, value); + } + /* If this virtual cell is full, rebalance the hash from this point and + * try again. */ + if (PREFIX(_table_rebalance)(table, hash)) + { + MAP_UNLOCK(); + return PREFIX(_insert)(table, value); + } + /** If rebalancing failed, resize even if we are <80% full. This can + * happen if your hash function sucks. If you don't want this to happen, + * get a better hash function. */ + if (PREFIX(_table_resize)(table)) + { + MAP_UNLOCK(); + return PREFIX(_insert)(table, value); + } + fprintf(stderr, "Insert failed\n"); + MAP_UNLOCK(); + return 0; +} + +static void *PREFIX(_table_get_cell)(PREFIX(_table) *table, const void *key) +{ + uint32_t hash = MAP_TABLE_HASH_KEY(key); + PREFIX(_table_cell) cell = PREFIX(_table_lookup)(table, hash); + // Value does not exist. + if (!MAP_TABLE_VALUE_NULL(cell->value)) + { + if (MAP_TABLE_COMPARE_FUNCTION(key, cell->value)) + { + return cell; + } + uint32_t jump = cell->secondMaps; + // Look at each offset defined by the jump table to find the displaced location. + for (int hop = __builtin_ffs(jump) ; hop > 0 ; hop = __builtin_ffs(jump)) + { + PREFIX(_table_cell) hopCell = PREFIX(_table_lookup)(table, hash+hop); + if (MAP_TABLE_COMPARE_FUNCTION(key, hopCell->value)) + { + return hopCell; + } + // Clear the most significant bit and try again. + jump &= ~(1 << (hop-1)); + } + } +#ifndef MAP_TABLE_STATIC_SIZE + if (table->old) + { + return PREFIX(_table_get_cell)(table->old, key); + } +#endif + return NULL; +} + +__attribute__((unused)) +static void PREFIX(_table_move_second)(PREFIX(_table) *table, + PREFIX(_table_cell) emptyCell) +{ + uint32_t jump = emptyCell->secondMaps; + // Look at each offset defined by the jump table to find the displaced location. + int hop = __builtin_ffs(jump); + PREFIX(_table_cell) hopCell = + PREFIX(_table_lookup)(table, (emptyCell - table->table) + hop); + emptyCell->value = hopCell->value; + emptyCell->secondMaps &= ~(1 << (hop-1)); + if (0 == hopCell->secondMaps) + { + hopCell->value = MAP_TABLE_VALUE_PLACEHOLDER; + } + else + { + PREFIX(_table_move_second)(table, hopCell); + } +} +__attribute__((unused)) +static void PREFIX(_remove)(PREFIX(_table) *table, void *key) +{ + MAP_LOCK(); + PREFIX(_table_cell) cell = PREFIX(_table_get_cell)(table, key); + if (NULL == cell) { return; } + + uint32_t hash = MAP_TABLE_HASH_KEY(key); + PREFIX(_table_cell) baseCell = PREFIX(_table_lookup)(table, hash); + if (baseCell && baseCell != cell) + { + uint32_t displacement = (cell - baseCell + table->table_size) % table->table_size; + uint32_t jump = 1 << (displacement - 1); + if ((baseCell->secondMaps & jump)) + { + // If we are removing a cell stored adjacent to its base due to hash + // collision, we have to clear the base cell's neighbor bit. + // Otherwise, a later remove can move the new placeholder value to the head + // which will cause further chained lookups to fail. + baseCell->secondMaps &= ~jump; + } + } + + // If the cell contains a value, set it to the placeholder and shuffle up + // everything + if (0 == cell->secondMaps) + { + cell->value = MAP_TABLE_VALUE_PLACEHOLDER; + } + else + { + PREFIX(_table_move_second)(table, cell); + } + table->table_used--; + MAP_UNLOCK(); +} + +__attribute__((unused)) +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE +static MAP_TABLE_VALUE_TYPE* +#else +static MAP_TABLE_VALUE_TYPE +#endif + PREFIX(_table_get)(PREFIX(_table) *table, + const void *key) +{ + PREFIX(_table_cell) cell = PREFIX(_table_get_cell)(table, key); + if (NULL == cell) + { +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return NULL; +#else + return MAP_TABLE_VALUE_PLACEHOLDER; +#endif + } +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return &cell->value; +#else + return cell->value; +#endif +} +__attribute__((unused)) +static void PREFIX(_table_set)(PREFIX(_table) *table, const void *key, + MAP_TABLE_VALUE_TYPE value) +{ + PREFIX(_table_cell) cell = PREFIX(_table_get_cell)(table, key); + if (NULL == cell) + { + PREFIX(_insert)(table, value); + } + cell->value = value; +} + +__attribute__((unused)) +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE +static MAP_TABLE_VALUE_TYPE* +#else +static MAP_TABLE_VALUE_TYPE +#endif +PREFIX(_next)(PREFIX(_table) *table, + struct PREFIX(_table_enumerator) **state) +{ + if (NULL == *state) + { + *state = CALLOC(1, sizeof(struct PREFIX(_table_enumerator))); + // Make sure that we are not reallocating the table when we start + // enumerating + MAP_LOCK(); + (*state)->table = table; + (*state)->index = -1; + IF_NO_GC(__sync_fetch_and_add(&table->enumerator_count, 1);) + MAP_UNLOCK(); + } + if ((*state)->seen >= (*state)->table->table_used) + { +#ifndef ENABLE_GC + MAP_LOCK(); + __sync_fetch_and_sub(&table->enumerator_count, 1); + MAP_UNLOCK(); + free(*state); +#endif +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return NULL; +#else + return MAP_TABLE_VALUE_PLACEHOLDER; +#endif + } + while ((++((*state)->index)) < TABLE_SIZE((*state)->table)) + { + if (!MAP_TABLE_VALUE_NULL((*state)->table->table[(*state)->index].value)) + { + (*state)->seen++; +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return &(*state)->table->table[(*state)->index].value; +#else + return (*state)->table->table[(*state)->index].value; +#endif + } + } +#ifndef ENABLE_GC + // Should not be reached, but may be if the table is unsafely modified. + MAP_LOCK(); + table->enumerator_count--; + MAP_UNLOCK(); + free(*state); +#endif +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return NULL; +#else + return MAP_TABLE_VALUE_PLACEHOLDER; +#endif +} +/** + * Returns the current value for an enumerator. This is used when you remove + * objects during enumeration. It may cause others to be shuffled up the + * table. + */ +__attribute__((unused)) +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE +static MAP_TABLE_VALUE_TYPE* +#else +static MAP_TABLE_VALUE_TYPE +#endif +PREFIX(_current)(PREFIX(_table) *table, + struct PREFIX(_table_enumerator) **state) +{ +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE + return &(*state)->table->table[(*state)->index].value; +#else + return (*state)->table->table[(*state)->index].value; +#endif +} + +#undef TABLE_SIZE +#undef REALLY_PREFIX_SUFFIX +#undef PREFIX_SUFFIX +#undef PREFIX + +#undef MAP_TABLE_NAME +#undef MAP_TABLE_COMPARE_FUNCTION +#undef MAP_TABLE_HASH_KEY +#undef MAP_TABLE_HASH_VALUE + +#ifdef MAP_TABLE_STATIC_SIZE +# undef MAP_TABLE_STATIC_SIZE +#endif + +#undef MAP_TABLE_VALUE_TYPE + +#undef MAP_LOCK +#undef MAP_UNLOCK +#ifdef MAP_TABLE_NO_LOCK +# undef MAP_TABLE_NO_LOCK +#endif + +#ifdef MAP_TABLE_SINGLE_THREAD +# undef MAP_TABLE_SINGLE_THREAD +#endif + +#undef MAP_TABLE_VALUE_NULL +#undef MAP_TABLE_VALUE_PLACEHOLDER + +#ifdef MAP_TABLE_ACCESS_BY_REFERENCE +# undef MAP_TABLE_ACCESS_BY_REFERENCE +#endif + +#undef CALLOC +#undef IF_NO_GC +#undef IF_GC +#undef MAP_TABLE_TYPES_BITMAP diff --git a/include/ivar.h b/include/ivar.h new file mode 100644 index 0000000..d504557 --- /dev/null +++ b/include/ivar.h @@ -0,0 +1,209 @@ +#include +#include +#include +/** + * Metadata structure for an instance variable. + * + */ +// begin: objc_ivar +struct objc_ivar +{ + /** + * Name of this instance variable. + */ + const char *name; + /** + * Type encoding for this instance variable. + */ + const char *type; + /** + * The offset from the start of the object. When using the non-fragile + * ABI, this is initialized by the compiler to the offset from the start of + * the ivars declared by this class. It is then set by the runtime to the + * offset from the object pointer. + */ + int *offset; + /** + * The size of this ivar. Note that the current ABI limits ivars to 4GB. + */ + uint32_t size; + /** + * Flags for this instance variable. + */ + uint32_t flags; +}; +// end: objc_ivar + +/** + * Instance variable ownership. + */ +// begin: objc_ivar_ownership +typedef enum { + /** + * Invalid. Indicates that this is not an instance variable with ownership + * semantics. + */ + ownership_invalid = 0, + /** + * Strong ownership. Assignments to this instance variable should retain + * the assigned value. + */ + ownership_strong = 1, + /** + * Weak ownership. This ivar is a zeroing weak reference to an object. + */ + ownership_weak = 2, + /** + * Object that has `__unsafe_unretained` semantics. + */ + ownership_unsafe = 3 +} objc_ivar_ownership; +// end: objc_ivar_ownership + +/** + * Shift for instance variable alignment. */ +static const int ivar_align_shift = 3; + +typedef enum { + /** + * Mask applied to the flags field to indicate ownership. + */ + ivar_ownership_mask = (1<<0) | (1<<1), + /** + * Flag indicating that the ivar contains an extended type encoding. + */ + ivar_extended_type_encoding = (1<<2), + /** + * Mask for describing the alignment. We need 6 bits to represent any + * power of two aligmnent from 0 to 63-bit alignment. There is probably no + * point supporting more than 32-bit aligment, because various bits of + * offset assume objects are less than 4GB, but there's definitely no point + * in supporting 64-bit alignment because we currently don't support any + * architectures where an address space could contain more than one 2^64 + * byte aligned value. + */ + #ifdef __clang__ + ivar_align_mask = (((1<<6)-1) << ivar_align_shift) + #else + ivar_align_mask = 504 + #endif +} objc_ivar_flags; + + +static inline size_t ivarGetAlign(Ivar ivar) +{ + return 1<<((ivar->flags & ivar_align_mask) >> ivar_align_shift); +} + +static inline void ivarSetAlign(Ivar ivar, size_t align) +{ + if (align != 0) + { + if (sizeof(size_t) == 4) + { + align = 4 * 8 - __builtin_clz(align) - 1; + } + else if (sizeof(size_t) == 8) + { + align = 8 * 8 - __builtin_clzll(align) - 1; + } + _Static_assert((sizeof(size_t) == 4) || (sizeof(size_t) == 8), "Unexpected type for size_t"); + } + align <<= ivar_align_shift; + ivar->flags = (ivar->flags & ~ivar_align_mask) | align; +} + +static inline void ivarSetOwnership(Ivar ivar, objc_ivar_ownership o) +{ + ivar->flags = (ivar->flags & ~ivar_ownership_mask) | o; +} + + +/** + * Look up the ownership for a given instance variable. + */ +static inline objc_ivar_ownership ivarGetOwnership(Ivar ivar) +{ + return (objc_ivar_ownership)(ivar->flags & ivar_ownership_mask); +} + +/** + * Legacy ivar structure, inherited from the GCC ABI. + */ +struct objc_ivar_gcc +{ + /** + * Name of this instance variable. + */ + const char *name; + /** + * Type encoding for this instance variable. + */ + const char *type; + /** + * The offset from the start of the object. When using the non-fragile + * ABI, this is initialized by the compiler to the offset from the start of + * the ivars declared by this class. It is then set by the runtime to the + * offset from the object pointer. + */ + int offset; +}; + + +/** + * A list of instance variables declared on this class. Unlike the method + * list, this is a single array and size. Categories are not allowed to add + * instance variables, because that would require existing objects to be + * reallocated, which is only possible with accurate GC (i.e. not in C). + */ +// begin: objc_ivar_list +struct objc_ivar_list +{ + /** + * The number of instance variables in this list. + */ + int count; + /** + * The size of a `struct objc_ivar`. This allows the runtime to load + * versions of this that come from a newer compiler, if we ever need to do + * so. + */ + size_t size; + /** + * An array of instance variable metadata structures. Note that this array + * has count elements. + */ + struct objc_ivar ivar_list[]; +}; +// end: objc_ivar_list + +/** + * Returns a pointer to the ivar inside the `objc_ivar_list` structure. This + * structure is designed to allow the compiler to add other fields without + * breaking the ABI, so although the `ivar_list` field appears to be an array + * of `objc_ivar` structures, it may be an array of some future version of + * `objc_ivar` structs, which have fields appended that this version of the + * runtime does not know about. + */ +static inline struct objc_ivar *ivar_at_index(struct objc_ivar_list *l, int i) +{ + assert(l->size >= sizeof(struct objc_ivar)); + return (struct objc_ivar*)(((char*)l->ivar_list) + (i * l->size)); +} + +/** + * Legacy version of the ivar list + */ +struct objc_ivar_list_gcc +{ + /** + * The number of instance variables in this list. + */ + int count; + /** + * An array of instance variable metadata structures. Note that this array + * has count elements. + */ + struct objc_ivar_gcc ivar_list[]; +}; + diff --git a/include/legacy.h b/include/legacy.h new file mode 100644 index 0000000..a132619 --- /dev/null +++ b/include/legacy.h @@ -0,0 +1,15 @@ +#pragma once +#include "visibility.h" +#include "ivar.h" +#include "class.h" +#include "category.h" +#include "protocol.h" + +PRIVATE Class objc_upgrade_class(struct objc_class_gsv1 *oldClass); +PRIVATE struct objc_category *objc_upgrade_category(struct objc_category_gcc *); + +PRIVATE struct objc_class_gsv1* objc_legacy_class_for_class(Class); + +PRIVATE struct objc_protocol *objc_upgrade_protocol_gcc(struct objc_protocol_gcc*); +PRIVATE struct objc_protocol *objc_upgrade_protocol_gsv1(struct objc_protocol_gsv1*); + diff --git a/include/loader.h b/include/loader.h new file mode 100644 index 0000000..21ff1d6 --- /dev/null +++ b/include/loader.h @@ -0,0 +1,67 @@ +#ifndef __OBJC_LOADER_H_INCLUDED +#define __OBJC_LOADER_H_INCLUDED +#include "category.h" +#include "method.h" +#include "module.h" +#include "class.h" +#include "protocol.h" + +/** + * Checks whether it is safe to load a module with the specified version and + * module size. This depends on whether another module with an incompatible + * ABI has already been loaded. + */ +BOOL objc_check_abi_version(struct objc_module_abi_8 *module); +/** + * Initializes a protocol list, uniquing the protocols in the list. + */ +void objc_init_protocols(struct objc_protocol_list *protocols); +/** + * Registers a set of selectors from a method list. + */ +void objc_register_selectors_from_list(struct objc_method_list *l); +/** + * Register all of the (unregistered) selectors that are used in a class. + */ +void objc_register_selectors_from_class(Class class); +/** + * Registers all of the selectors in an array. + */ +void objc_register_selector_array(SEL selectors, unsigned long count); +/** + * Loads a class into the runtime system. If possible, the class is resolved + * (inserted into the class tree) immediately. If its superclass is not yet + * resolved, it is enqueued for later resolution. + */ +void objc_load_class(struct objc_class *class); +/** + * Resolves classes that have not yet been resolved, if their superclasses have + * subsequently been loaded. + */ +void objc_resolve_class_links(void); +/** + * Attaches a category to its class, if the class is already loaded. Buffers + * it for future resolution if not. + */ +void objc_try_load_category(struct objc_category *cat); +/** + * Tries to load all of the categories that could not previously be loaded + * because their classes were not yet loaded. + */ +void objc_load_buffered_categories(void); +/** + * Updates the dispatch table for a class. + */ +void objc_update_dtable_for_class(Class cls); +/** + * Initialises a list of static object instances belonging to the same class if + * possible, or defers initialisation until the class has been loaded it not. + */ +void objc_init_statics(struct objc_static_instance_list *statics); +/** + * Tries again to initialise static instances which could not be initialised + * earlier. + */ +void objc_init_buffered_statics(void); + +#endif //__OBJC_LOADER_H_INCLUDED diff --git a/include/lock.h b/include/lock.h new file mode 100644 index 0000000..77c8082 --- /dev/null +++ b/include/lock.h @@ -0,0 +1,114 @@ +/** + * libobjc requires recursive mutexes. These are delegated to the underlying + * threading implementation. This file contains a VERY thin wrapper over the + * Windows and POSIX mutex APIs. + */ + +#ifndef __LIBOBJC_LOCK_H_INCLUDED__ +#define __LIBOBJC_LOCK_H_INCLUDED__ +#ifdef _WIN32 +# include "safewindows.h" +typedef CRITICAL_SECTION mutex_t; +# define INIT_LOCK(x) InitializeCriticalSection(&(x)) +# define LOCK(x) EnterCriticalSection(x) +# define UNLOCK(x) LeaveCriticalSection(x) +# define DESTROY_LOCK(x) DeleteCriticalSection(x) +#else + +#include + +typedef pthread_mutex_t mutex_t; +// If this pthread implementation has a static initializer for recursive +// mutexes, use that, otherwise fall back to the portable version +# ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +# define INIT_LOCK(x) x = (pthread_mutex_t)PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +# elif defined(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) +# define INIT_LOCK(x) x = (pthread_mutex_t)PTHREAD_RECURSIVE_MUTEX_INITIALIZER +# else +# define INIT_LOCK(x) init_recursive_mutex(&(x)) + +static inline void init_recursive_mutex(pthread_mutex_t *x) +{ + pthread_mutexattr_t recursiveAttributes; + pthread_mutexattr_init(&recursiveAttributes); + pthread_mutexattr_settype(&recursiveAttributes, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(x, &recursiveAttributes); + pthread_mutexattr_destroy(&recursiveAttributes); +} +# endif + +# define LOCK(x) pthread_mutex_lock(x) +# define UNLOCK(x) pthread_mutex_unlock(x) +# define DESTROY_LOCK(x) pthread_mutex_destroy(x) +#endif + +__attribute__((unused)) static void objc_release_lock(void *x) +{ + mutex_t *lock = *(mutex_t**)x; + UNLOCK(lock); +} +/** + * Concatenate strings during macro expansion. + */ +#define LOCK_HOLDERN_NAME_CAT(x, y) x ## y +/** + * Concatenate string with unique variable during macro expansion. + */ +#define LOCK_HOLDER_NAME_COUNTER(x, y) LOCK_HOLDERN_NAME_CAT(x, y) +/** + * Create a unique name for a lock holder variable + */ +#define LOCK_HOLDER_NAME(x) LOCK_HOLDER_NAME_COUNTER(x, __COUNTER__) + +/** + * Acquires the lock and automatically releases it at the end of the current + * scope. + */ +#define LOCK_FOR_SCOPE(lock) \ + __attribute__((cleanup(objc_release_lock)))\ + __attribute__((unused)) mutex_t *LOCK_HOLDER_NAME(lock_pointer) = lock;\ + LOCK(lock) + +/** + * The global runtime mutex. + */ +extern mutex_t runtime_mutex; + +#define LOCK_RUNTIME() LOCK(&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/include/method.h b/include/method.h new file mode 100644 index 0000000..4e2997b --- /dev/null +++ b/include/method.h @@ -0,0 +1,103 @@ +#include + +/** + * Metadata structure describing a method. + */ +// begin: objc_method +struct objc_method +{ + /** + * A pointer to the function implementing this method. + */ + IMP imp; + /** + * Selector used to send messages to this method. + */ + SEL selector; + /** + * The extended type encoding for this method. + */ + const char *types; +}; +// end: objc_method + +struct objc_method_gcc +{ + /** + * Selector used to send messages to this method. The type encoding of + * this method should match the types field. + */ + SEL selector; + /** + * The type encoding for this selector. Used only for introspection, and + * only required because of the stupid selector handling in the old GNU + * runtime. In future, this field may be reused for something else. + */ + const char *types; + /** + * A pointer to the function implementing this method. + */ + IMP imp; +}; + +/** + * Method list. Each class or category defines a new one of these and they are + * all chained together in a linked list, with new ones inserted at the head. + * When constructing the dispatch table, methods in the start of the list are + * used in preference to ones at the end. + */ +// begin: objc_method_list +struct objc_method_list +{ + /** + * The next group of methods in the list. + */ + struct objc_method_list *next; + /** + * The number of methods in this list. + */ + int count; + /** + * Sze of `struct objc_method`. This allows runtimes downgrading newer + * versions of this structure. + */ + size_t size; + /** + * An array of methods. Note that the actual size of this is count. + */ + struct objc_method methods[]; +}; +// end: objc_method_list + +/** + * Returns a pointer to the method inside the `objc_method` structure. This + * structure is designed to allow the compiler to add other fields without + * breaking the ABI, so although the `methods` field appears to be an array + * of `objc_method` structures, it may be an array of some future version of + * `objc_method` structs, which have fields appended that this version of the + * runtime does not know about. + */ +static inline struct objc_method *method_at_index(struct objc_method_list *l, int i) +{ + assert(l->size >= sizeof(struct objc_method)); + return (struct objc_method*)(((char*)l->methods) + (i * l->size)); +} + +/** + * Legacy version of the method list. + */ +struct objc_method_list_gcc +{ + /** + * The next group of methods in the list. + */ + struct objc_method_list_gcc *next; + /** + * The number of methods in this list. + */ + int count; + /** + * An array of methods. Note that the actual size of this is count. + */ + struct objc_method_gcc methods[]; +}; diff --git a/include/module.h b/include/module.h new file mode 100644 index 0000000..96e0a77 --- /dev/null +++ b/include/module.h @@ -0,0 +1,102 @@ +/** + * Defines the module structures. + * + * When defining a new ABI, the + */ + +/** + * The symbol table for a module. This structure references all of the + * Objective-C symbols defined for a module, allowing the runtime to find and + * register them. + */ +struct objc_symbol_table_abi_8 +{ + /** + * The number of selectors referenced in this module. + */ + unsigned long selector_count; + /** + * An array of selectors used in this compilation unit. SEL is a pointer + * type and this points to the first element in an array of selectors. + */ + SEL selectors; + /** + * The number of classes defined in this module. + */ + unsigned short class_count; + /** + * The number of categories defined in this module. + */ + unsigned short category_count; + /** + * A null-terminated array of pointers to symbols defined in this module. + * This contains class_count pointers to class structures, category_count + * pointers to category structures, and then zero or more pointers to + * static object instances. + * + * Current compilers only use this for constant strings. The runtime + * permits other types. + */ + void *definitions[]; +}; + +/** + * The module structure is passed to the __objc_exec_class function by a + * constructor function when the module is loaded. + * + * When defining a new ABI version, the first two fields in this structure must + * be retained. + */ +struct objc_module_abi_8 +{ + /** + * The version of the ABI used by this module. This is checked against the + * list of ABIs that the runtime supports, and the list of incompatible + * ABIs. + */ + unsigned long version; + /** + * The size of the module. This is used for sanity checking, to ensure + * that the compiler and runtime's idea of the module size match. + */ + unsigned long size; + /** + * The full path name of the source for this module. Not currently used + * for anything, could be used for debugging in theory, but duplicates + * information available from DWARF data, so probably won't. + */ + const char *name; + /** + * A pointer to the symbol table for this compilation unit. + */ + struct objc_symbol_table_abi_8 *symbol_table; +}; + +struct objc_module_abi_10 +{ + /** + * Inherited fields from version 8 of the ABI. + */ + struct objc_module_abi_8 old; + /** + * GC mode. GC_Optional code can be mixed with anything, but GC_None code + * can't be mixed with GC_Required code. + */ + int gc_mode; +}; + +/** + * List of static instances of a named class provided in this module. + */ +struct objc_static_instance_list +{ + /** + * The name of the class. The isa pointer of all of the instances will be + * set to the class with this name. + */ + char *class_name; + /** + * NULL-terminated array of statically-allocated instances. + */ + id instances[]; +}; diff --git a/include/nsobject.h b/include/nsobject.h new file mode 100644 index 0000000..7703a22 --- /dev/null +++ b/include/nsobject.h @@ -0,0 +1,10 @@ +/** + * Stub declaration of NSObject. Lots of things in the runtime require the + */ +@interface NSObject +-retain; +-copy; +-(void)release; +-autorelease; +-(void)dealloc; +@end diff --git a/include/objcxx_eh.h b/include/objcxx_eh.h new file mode 100644 index 0000000..5ac70a3 --- /dev/null +++ b/include/objcxx_eh.h @@ -0,0 +1,66 @@ +#ifdef __cplusplus +extern "C" { +#endif +/** + * Allocates a C++ exception. This function is part of the Itanium C++ ABI and + * is provided externally. + */ +/* + * Note: Recent versions of libsupc++ already provide a prototype for + * __cxa__allocate_exception(). Since the libsupc++ version is defined with + * _GLIBCXX_NOTHROW, clang gives a type mismatch error. + */ +#ifndef __cplusplus +#undef CXA_ALLOCATE_EXCEPTION_SPECIFIER +#define CXA_ALLOCATE_EXCEPTION_SPECIFIER +#endif +void *__cxa_allocate_exception(size_t thrown_size) CXA_ALLOCATE_EXCEPTION_SPECIFIER; + +/** + * Initialises an exception object returned by __cxa_allocate_exception() for + * storing an Objective-C object. The return value is the location of the + * _Unwind_Exception structure within this structure, and should be passed to + * the C++ personality function. + */ +struct _Unwind_Exception *objc_init_cxx_exception(id thrown_exception); +/** + * The GNU C++ exception personality function, provided by libsupc++ (GNU) or + * libcxxrt (PathScale). + */ +__attribute__((weak)) DECLARE_PERSONALITY_FUNCTION(__gxx_personality_v0); +/** + * Frees an exception object allocated by __cxa_allocate_exception(). Part of + * the Itanium C++ ABI. + */ +void __cxa_free_exception(void *thrown_exception); +/** + * Tests whether a C++ exception contains an Objective-C object, and returns if + * if it does. The second argument is a pointer to a boolean value indicating + * whether this is a valid object. + */ +void *objc_object_for_cxx_exception(void *thrown_exception, int *isValid); + +/** + * Prints the type info associated with an exception. Used only when + * debugging, not compiled in the normal build. + */ +void print_type_info(void *thrown_exception); + +/** + * The exception class that we've detected that C++ runtime library uses. + */ +extern uint64_t cxx_exception_class; + +/** + * The exception class that libsupc++ and libcxxrt use. + */ +const uint64_t gnu_cxx_exception_class = EXCEPTION_CLASS('G','N','U','C','C','+','+','\0'); + +/** + * The exception class that libc++abi uses. + */ +const uint64_t llvm_cxx_exception_class = EXCEPTION_CLASS('C','L','N','G','C','+','+','\0'); + +#ifdef __cplusplus +} +#endif diff --git a/include/objcxx_eh_private.h b/include/objcxx_eh_private.h new file mode 100644 index 0000000..1642e9a --- /dev/null +++ b/include/objcxx_eh_private.h @@ -0,0 +1,241 @@ +typedef struct objc_object* id; + +#include "objc/runtime.h" +#include "visibility.h" + +#ifndef DEBUG_EXCEPTIONS +#define DEBUG_LOG(...) +#else +#define DEBUG_LOG(str, ...) fprintf(stderr, str, ## __VA_ARGS__) +#endif + + +/** + * Our own definitions of C++ ABI functions and types. These are provided + * because this file must not include cxxabi.h. We need to handle subtly + * different variations of the ABI and including one specific implementation + * would make that very difficult. + */ +namespace __cxxabiv1 +{ + /** + * Type info for classes. Forward declared because the GNU ABI provides a + * method on all type_info objects that the dynamic the dynamic cast header + * needs. + */ + struct __class_type_info; + /** + * The C++ in-flight exception object. We will derive the offset of fields + * in this, so we do not ever actually see a concrete definition of it. + */ + struct __cxa_exception; + /** + * The public ABI structure for current exception state. + */ + struct __cxa_eh_globals + { + /** + * The current exception that has been caught. + */ + __cxa_exception *caughtExceptions; + /** + * The number of uncaught exceptions still in flight. + */ + unsigned int uncaughtExceptions; + }; + /** + * Retrieve the above structure. + */ + extern "C" __cxa_eh_globals *__cxa_get_globals(); +} + +namespace std +{ + struct type_info; +} + +// Define some C++ ABI types here, rather than including them. This prevents +// conflicts with the libstdc++ headers, which expose only a subset of the +// type_info class (the part required for standards compliance, not the +// implementation details). + +typedef void (*unexpected_handler)(); +typedef void (*terminate_handler)(); + +namespace std +{ + /** + * std::type_info, containing the minimum requirements for the ABI. + * Public headers on some implementations also expose some implementation + * details. The layout of our subclasses must respect the layout of the + * C++ runtime library, but also needs to be portable across multiple + * implementations and so should not depend on internal symbols from those + * libraries. + */ + class type_info + { + public: + virtual ~type_info(); + bool operator==(const type_info &) const; + bool operator!=(const type_info &) const; + bool before(const type_info &) const; + type_info(); + private: + type_info(const type_info& rhs); + type_info& operator= (const type_info& rhs); + const char *__type_name; + protected: + type_info(const char *name): __type_name(name) { } + public: + const char* name() const { return __type_name; } + }; +} + +extern "C" void __cxa_throw(void*, std::type_info*, void(*)(void*)); +extern "C" void __cxa_rethrow(); + +/** + * Helper function to find a particular value scanning backwards in a + * structure. + */ +template +ptrdiff_t find_backwards(void *addr, T val) +{ + T *ptr = reinterpret_cast(addr); + for (ptrdiff_t disp = -1 ; (disp * sizeof(T) > -128) ; disp--) + { + if (ptr[disp] == val) + { + return disp * sizeof(T); + } + } + fprintf(stderr, "Unable to find field in C++ exception structure\n"); + abort(); +} + +/** + * Helper function to find a particular value scanning forwards in a + * structure. + */ +template +ptrdiff_t find_forwards(void *addr, T val) +{ + T *ptr = reinterpret_cast(addr); + for (ptrdiff_t disp = 0 ; (disp * sizeof(T) < 256) ; disp++) + { + if (ptr[disp] == val) + { + return disp * sizeof(T); + } + } + fprintf(stderr, "Unable to find field in C++ exception structure\n"); + abort(); +} + +template +T *pointer_add(void *ptr, ptrdiff_t offset) +{ + return reinterpret_cast(reinterpret_cast(ptr) + offset); +} + +namespace gnustep +{ + namespace libobjc + { + /** + * Superclass for the type info for Objective-C exceptions. + */ + struct OBJC_PUBLIC __objc_type_info : std::type_info + { + /** + * Constructor that sets the name. + */ + __objc_type_info(const char *name); + /** + * Helper function used by libsupc++ and libcxxrt to determine if + * this is a pointer type. If so, catches automatically + * dereference the pointer to the thrown pointer in + * `__cxa_begin_catch`. + */ + virtual bool __is_pointer_p() const; + /** + * Helper function used by libsupc++ and libcxxrt to determine if + * this is a function pointer type. Irrelevant for our purposes. + */ + virtual bool __is_function_p() const; + /** + * Catch handler. This is used in the C++ personality function. + * `thrown_type` is the type info of the thrown object, `this` is + * the type info at the catch site. `thrown_object` is a pointer + * to a pointer to the thrown object and may be adjusted by this + * function. + */ + virtual bool __do_catch(const type_info *thrown_type, + void **thrown_object, + unsigned) const; + /** + * Function used for `dynamic_cast` between two C++ class types in + * libsupc++ and libcxxrt. + * + * This should never be called on Objective-C types. + */ + virtual bool __do_upcast( + const __cxxabiv1::__class_type_info *target, + void **thrown_object) const; + }; + + /** + * Singleton type info for the `id` type. + */ + struct OBJC_PUBLIC __objc_id_type_info : __objc_type_info + { + __objc_id_type_info(); + virtual ~__objc_id_type_info(); + virtual bool __do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const; + }; + + struct OBJC_PUBLIC __objc_class_type_info : __objc_type_info + { + virtual ~__objc_class_type_info(); + virtual bool __do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const; + }; + } +} + +/** + * Public interface to the Objective-C++ exception mechanism + */ +extern "C" +{ +/** + * The public symbol that the compiler uses to indicate the Objective-C id type. + */ +extern OBJC_PUBLIC gnustep::libobjc::__objc_id_type_info __objc_id_type_info; +} // extern "C" + +/** + * C++ structure that is thrown through a frame with the `test_eh_personality` + * personality function. This contains a well-known value that we can search + * for after the unwind header. + */ +struct +PRIVATE +MagicValueHolder +{ + /** + * The constant that we will search for to identify the MagicValueHolder object. + */ + static constexpr uint32_t magic = 0x01020304; + /** + * The single field in this structure. + */ + uint32_t magic_value; + /** + * Constructor. Initialises the field with the magic constant. + */ + MagicValueHolder(); +}; diff --git a/include/pool.h b/include/pool.h new file mode 100644 index 0000000..f0bd545 --- /dev/null +++ b/include/pool.h @@ -0,0 +1,57 @@ +#include +#include "lock.h" + +#ifndef POOL_TYPE +#error POOL_TYPE must be defined +#endif +#ifndef POOL_TYPE +#error POOL_NAME must be defined +#endif + +// Horrible multiple indirection to satisfy the weird precedence rules in cpp +#define REALLY_PREFIX_SUFFIX(x,y) x ## y +#define PREFIX_SUFFIX(x, y) REALLY_PREFIX_SUFFIX(x, y) +#define NAME(x) PREFIX_SUFFIX(POOL_NAME, x) + +#define PAGE_SIZE 4096 + +// Malloc one page at a time. +#define POOL_SIZE ((PAGE_SIZE) / sizeof(POOL_TYPE)) +static POOL_TYPE* NAME(_pool); +static int NAME(_pool_next_index) = -1; + +#ifdef THREAD_SAFE_POOL +static mutex_t NAME(_lock); +#define LOCK_POOL() LOCK(&POOL_NAME##_lock) +#define UNLOCK_POOL() LOCK(&POOL_NAME##_lock) +#else +#define LOCK_POOL() +#define UNLOCK_POOL() +#endif + +static int pool_size = 0; +static int pool_allocs = 0; +static inline POOL_TYPE*NAME(_pool_alloc)(void) +{ + LOCK_POOL(); + pool_allocs++; + if (0 > NAME(_pool_next_index)) + { + NAME(_pool) = malloc(PAGE_SIZE); + NAME(_pool_next_index) = POOL_SIZE - 1; + pool_size += PAGE_SIZE; + } + POOL_TYPE* new = &NAME(_pool)[NAME(_pool_next_index)--]; + UNLOCK_POOL(); + return new; +} +#undef NAME +#undef POOL_SIZE +#undef PAGE_SIZE +#undef POOL_NAME +#undef POOL_TYPE +#undef LOCK_POOL +#undef UNLOCK_POOL +#ifdef THREAD_SAFE_POOL +#undef THREAD_SAFE_POOL +#endif diff --git a/include/pool.hh b/include/pool.hh new file mode 100644 index 0000000..4a2af59 --- /dev/null +++ b/include/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/include/properties.h b/include/properties.h new file mode 100644 index 0000000..02f2511 --- /dev/null +++ b/include/properties.h @@ -0,0 +1,239 @@ +#include "visibility.h" +#include + +enum PropertyAttributeKind +{ + /** + * Property has no attributes. + */ + OBJC_PR_noattr = 0x00, + /** + * The property is declared read-only. + */ + OBJC_PR_readonly = (1<<0), + /** + * The property has a getter. + */ + OBJC_PR_getter = (1<<1), + /** + * The property has assign semantics. + */ + OBJC_PR_assign = (1<<2), + /** + * The property is declared read-write. + */ + OBJC_PR_readwrite = (1<<3), + /** + * Property has retain semantics. + */ + OBJC_PR_retain = (1<<4), + /** + * Property has copy semantics. + */ + OBJC_PR_copy = (1<<5), + /** + * Property is marked as non-atomic. + */ + OBJC_PR_nonatomic = (1<<6), + /** + * Property has setter. + */ + OBJC_PR_setter = (1<<7) +}; + +/** + * Flags in the second attributes field in declared properties. + * Note: This field replaces the old 'is synthesized' field and so these values + * are shifted left one from their values in clang. + */ +enum PropertyAttributeKind2 +{ + /** + * No extended attributes. + */ + OBJC_PR_noextattr = 0, + /** + * The property is synthesized. This has no meaning in properties on + * protocols. + */ + OBJC_PR_synthesized = (1<<0), + /** + * The property is dynamic (i.e. the implementation is inherited or + * provided at run time). + */ + OBJC_PR_dynamic = (1<<1), + /** + * This property belongs to a protocol. + */ + OBJC_PR_protocol = OBJC_PR_synthesized | OBJC_PR_dynamic, + /** + * The property is atomic. + */ + OBJC_PR_atomic = (1<<2), + /** + * The property value is a zeroing weak reference. + */ + OBJC_PR_weak = (1<<3), + /** + * The property value is strong (retained). Currently, this is equivalent + * to the strong attribute. + */ + OBJC_PR_strong = (1<<4), + /** + * The property value is just copied. + */ + OBJC_PR_unsafe_unretained = (1<<5), +}; + +/** + * Structure used for property enumeration. Note that property enumeration is + * currently quite broken on OS X, so achieving full compatibility there is + * impossible. Instead, we strive to achieve compatibility with the + * documentation. + */ +// begin: objc_property +struct objc_property +{ + /** + * Name of this property. + */ + const char *name; + /** + * The type encoding of the property. + */ + const char *attributes; + /** + * The type encoding of the property. + */ + const char *type; + /** + * The selector for the getter for this property. + */ + SEL getter; + /** + * The selector for the setter for this property. + */ + SEL setter; +}; +// end: objc_property + +/** + * GNUstep v1 ABI version of `struct objc_property` + */ +struct objc_property_gsv1 +{ + /** + * Name of this property. + */ + const char *name; + /** + * Attributes for this property. Made by ORing together + * PropertyAttributeKinds. + */ + char attributes; + /** + * Flag set if the property is synthesized. + */ + char attributes2; + /** + * Padding field. These were implicit in the structure field alignment + * (four more on 64-bit platforms), but we'll make them explicit now for + * future use. + */ + char unused1; + /** + * More padding. + */ + char unused2; + /** + * Name of the getter for this property. + */ + const char *getter_name; + /** + * Type encoding for the get method for this property. + */ + const char *getter_types; + /** + * Name of the set method for this property. + */ + const char *setter_name; + /** + * Type encoding of the setter for this property. + */ + const char *setter_types; +}; + +/** + * List of property introspection data. + */ +struct objc_property_list_gsv1 +{ + /** + * Number of properties in this array. + */ + int count; + /* + * The next property in a linked list. + */ + struct objc_property_list *next; + /** + * List of properties. + */ + struct objc_property_gsv1 properties[]; +}; + +/** + * List of property introspection data. + */ +// begin: objc_property_list +struct objc_property_list +{ + /** + * Number of properties in this array. + */ + int count; + /** + * Size of `struct objc_property`. This allows the runtime to + * transparently support newer ABIs with more fields in the property + * metadata. + */ + int size; + /* + * The next property in a linked list. + */ + struct objc_property_list *next; + /** + * List of properties. + */ + struct objc_property properties[]; +}; +// end: objc_property_list + +/** + * Returns a pointer to the property inside the `objc_property` structure. + * This structure is designed to allow the compiler to add other fields without + * breaking the ABI, so although the `properties` field appears to be an array + * of `objc_property` structures, it may be an array of some future version of + * `objc_property` structs, which have fields appended that this version of the + * runtime does not know about. + */ +static inline struct objc_property *property_at_index(struct objc_property_list *l, int i) +{ + assert(l->size >= sizeof(struct objc_property)); + return (struct objc_property*)(((char*)l->properties) + (i * l->size)); +} + +/** + * Constructs a property description from a list of attributes, returning the + * instance variable name via the third parameter. + */ +PRIVATE struct objc_property propertyFromAttrs(const objc_property_attribute_t *attributes, + unsigned int attributeCount, + const char *name); + +/** + * Constructs and installs a property attribute string from the property + * attributes and, optionally, an ivar string. + */ +PRIVATE const char *constructPropertyAttributes(objc_property_t property, + const char *iVarName); diff --git a/include/protocol.h b/include/protocol.h new file mode 100644 index 0000000..15a0838 --- /dev/null +++ b/include/protocol.h @@ -0,0 +1,243 @@ +#ifndef PROTOCOL_H_INCLUDED +#define PROTOCOL_H_INCLUDED + +#include "selector.h" +#include +#include + +struct objc_protocol_method_description_list_gcc +{ + /** + * Number of method descriptions in this list. + */ + int count; + /** + * Methods in this list. Note: these selectors are NOT resolved. The name + * field points to the name, not to the index of the uniqued version of the + * name. You must not use them for dispatch. + */ + struct objc_selector methods[]; +}; + +/** + * Protocol versions. The compiler generates these in the `isa` field for + * protocols and they are replaced with class pointers by the runtime. + */ +enum protocol_version +{ + /** + * Legacy (GCC-compatible) protocol version. + */ + protocol_version_gcc = 2, + /** + * GNUstep V1 ABI protocol. + */ + protocol_version_gsv1 = 3, + /** + * GNUstep V2 ABI protocol. + */ + protocol_version_gsv2 = 4 +}; + + +/** + * A description of a method in a protocol. + */ +struct objc_protocol_method_description +{ + /** + * The selector for this method, includes traditional type encoding. + */ + SEL selector; + /** + * The extended type encoding. + */ + const char *types; +}; + +struct objc_protocol_method_description_list +{ + /** + * Number of method descriptions in this list. + */ + int count; + /** + * Size of `struct objc_method_description` + */ + int size; + /** + * Methods in this list. `count` elements long. + */ + struct objc_protocol_method_description methods[]; +}; + +/** + * Returns a pointer to the method inside the method description list + * structure. This structure is designed to allow the compiler to add other + * fields without breaking the ABI, so although the `methods` field appears to + * be an array of `objc_protocol_method_description` structures, it may be an + * array of some future version of these structs, which have fields appended + * that this version of the runtime does not know about. + */ +static inline struct objc_protocol_method_description * +protocol_method_at_index(struct objc_protocol_method_description_list *l, int i) +{ + assert(l->size >= sizeof(struct objc_protocol_method_description)); + return (struct objc_protocol_method_description*)(((char*)l->methods) + (i * l->size)); +} + +struct objc_protocol +{ + /** + * Class pointer. + */ + id isa; + /** + * Protocol name. + */ + char *name; + /** + * Protocols that this protocol conforms to. + */ + struct objc_protocol_list *protocol_list; + /** + * Required instance methods that classes conforming to this protocol must + * implement. + */ + struct objc_protocol_method_description_list *instance_methods; + /** + * Required class methods that classes conforming to this protocol must + * implement. + */ + struct objc_protocol_method_description_list *class_methods; + /** + * Instance methods that are declared as optional for this protocol. + */ + struct objc_protocol_method_description_list *optional_instance_methods; + /** + * Class methods that are declared as optional for this protocol. + */ + struct objc_protocol_method_description_list *optional_class_methods; + /** + * Properties that are required by this protocol. + */ + struct objc_property_list *properties; + /** + * Optional properties. + */ + struct objc_property_list *optional_properties; + /** + * Class properties that are required by this protocol. + */ + struct objc_property_list *class_properties; + /** + * Optional class properties. + */ + struct objc_property_list *optional_class_properties; +}; + +struct objc_protocol_gcc +{ + /** Class pointer. */ + id isa; + /** + * The name of this protocol. Two protocols are regarded as identical if + * they have the same name. + */ + char *name; + /** + * The list of protocols that this protocol conforms to. + */ + struct objc_protocol_list *protocol_list; + /** + * List of instance methods required by this protocol. + */ + struct objc_protocol_method_description_list_gcc *instance_methods; + /** + * List of class methods required by this protocol. + */ + struct objc_protocol_method_description_list_gcc *class_methods; +}; + +struct objc_protocol_gsv1 +{ + /** + * The first five ivars are shared with `objc_protocol_gcc`. + */ + id isa; + char *name; + struct objc_protocol_list *protocol_list; + struct objc_protocol_method_description_list_gcc *instance_methods; + struct objc_protocol_method_description_list_gcc *class_methods; + /** + * Instance methods that are declared as optional for this protocol. + */ + struct objc_protocol_method_description_list_gcc *optional_instance_methods; + /** + * Class methods that are declared as optional for this protocol. + */ + struct objc_protocol_method_description_list_gcc *optional_class_methods; + /** + * Properties that are required by this protocol. + */ + struct objc_property_list_gsv1 *properties; + /** + * Optional properties. + */ + struct objc_property_list_gsv1 *optional_properties; +}; + +#ifdef __OBJC__ +@interface Object { id isa; } @end +/** + * Definition of the Protocol type. Protocols are objects, but are rarely used + * as such. + */ +@interface Protocol : Object +@end + +@interface ProtocolGCC : Protocol +@end + +@interface ProtocolGSv1 : Protocol +@end + +#endif + +/** + * List of protocols. Attached to a class or a category by the compiler and to + * a class by the runtime. + */ +// begin: objc_protocol_list +struct objc_protocol_list +{ + /** + * Additional protocol lists. Loading a category that declares protocols + * will cause a new list to be prepended using this pointer to the protocol + * list for the class. Unlike methods, protocols can not be overridden, + * although it is possible for a protocol to appear twice. + */ + struct objc_protocol_list *next; + /** + * The number of protocols in this list. + */ + size_t count; + /** + * An array of protocols. Contains `count` elements. + * + * On load, this contains direct references to other protocols and should + * be updated to point to the canonical (possibly upgraded) version. + */ + struct objc_protocol *list[]; +}; +// end: objc_protocol_list + + +/** + * Function that ensures that protocol classes are linked. Calling this + * guarantees that the Protocol classes are linked into a statically linked + * runtime. + */ +void link_protocol_classes(void); + +#endif // PROTOCOL_H_INCLUDED diff --git a/include/safewindows.h b/include/safewindows.h new file mode 100644 index 0000000..98ad83b --- /dev/null +++ b/include/safewindows.h @@ -0,0 +1,20 @@ +#ifndef __LIBOBJC_SAFEWINDOWS_H_INCLUDED__ +#define __LIBOBJC_SAFEWINDOWS_H_INCLUDED__ + +#pragma push_macro("BOOL") + +#ifdef BOOL +#undef BOOL +#endif +#define BOOL _WINBOOL + +#include + +// Windows.h defines interface -> struct +#ifdef interface +#undef interface +#endif + +#pragma pop_macro("BOOL") + +#endif // __LIBOBJC_SAFEWINDOWS_H_INCLUDED__ diff --git a/include/sarray2.h b/include/sarray2.h new file mode 100644 index 0000000..02a98a1 --- /dev/null +++ b/include/sarray2.h @@ -0,0 +1,154 @@ +/** + * Sparse Array + * + * Author: David Chisnall + * + * License: See COPYING.MIT + * + */ + +#ifndef _SARRAY_H_INCLUDED_ +#define _SARRAY_H_INCLUDED_ +#include +#include +#include "visibility.h" + + +/** + * The size of the data array. The sparse array is a tree with this many + * children at each node depth. + */ +static const uint32_t data_size = 256; +/** + * The mask used to access the elements in the data array in a sparse array + * node. + */ +static const uint32_t data_mask = data_size - 1; +/** + * Sparse arrays, used to implement dispatch tables. Current implementation is + * quite RAM-intensive and could be optimised. Maps 32-bit integers to pointers. + * + * Note that deletion from the array is not supported. This allows accesses to + * be done without locking; the worst that can happen is that the caller gets + * an old value (and if this is important to you then you should be doing your + * own locking). For this reason, you should be very careful when deleting a + * sparse array that there are no references to it held by other threads. + */ +typedef struct +{ + /** + * Number of bits that the masked value should be right shifted by to get + * the index in the subarray. If this value is greater than zero, then the + * value in the array is another SparseArray*. + */ + uint32_t shift; + /** + * The reference count for this. Used for copy-on-write. When making a + * copy of a sparse array, we only copy the root node, and increment the + * reference count of the remaining nodes. When modifying any leaf node, + * we copy if its reference count is greater than one. + */ + uint32_t refCount; + /** + * The data stored in this sparse array node. + */ + #ifdef __clang__ + void *data[data_size]; + #else + void *data[256]; + #endif +} SparseArray; + +/** + * Turn an index in the array into an index in the current depth. + */ +#define MASK_INDEX(index) \ + ((index >> sarray->shift) & 0xff) + +#define SARRAY_EMPTY ((void*)0) +/** + * Look up the specified value in the sparse array. This is used in message + * dispatch and so has been put in the header to allow compilers to inline it, + * even though this breaks the abstraction. + */ +static inline void* SparseArrayLookup(SparseArray * sarray, uint32_t index) +{ + // This unrolled version of the commented-out segment below only works with + // sarrays that use one-byte leafs. It's really ugly, but seems to be faster. + // With this version, we get the same performance as the old GNU code, but + // with about half the memory usage. + uint32_t i = index; + switch (sarray->shift) + { + default: UNREACHABLE("broken sarray"); + case 0: + return sarray->data[i & 0xff]; + case 8: + return + ((SparseArray*)sarray->data[(i & 0xff00)>>8])->data[(i & 0xff)]; + case 16: + return + ((SparseArray*)((SparseArray*) + sarray->data[(i & 0xff0000)>>16])-> + data[(i & 0xff00)>>8])->data[(i & 0xff)]; + case 24: + return + ((SparseArray*)((SparseArray*)((SparseArray*) + sarray->data[(i & 0xff000000)>>24])-> + data[(i & 0xff0000)>>16])-> + data[(i & 0xff00)>>8])->data[(i & 0xff)]; + } + /* + while(sarray->shift > 0) + { + uint32_t i = MASK_INDEX(index); + sarray = (SparseArray*) sarray->data[i]; + } + uint32_t i = index & sarray->mask; + return sarray->data[i]; + */ +} +/** + * Create a new sparse array. + */ +SparseArray *SparseArrayNew(); +/** + * Creates a new sparse array with the specified capacity. The depth indicates + * the number of bits to use for the key. Must be a value between 8 and 32 and + * should ideally be a multiple of base_shift. + */ +SparseArray *SparseArrayNewWithDepth(uint32_t depth); +/** + * Returns a new sparse array created by adding this one as the first child + * node in an expanded one. + */ +SparseArray *SparseArrayExpandingArray(SparseArray *sarray, uint32_t new_depth); +/** + * Insert a value at the specified index. + */ +void SparseArrayInsert(SparseArray * sarray, uint32_t index, void * value); +/** + * Destroy the sparse array. Note that calling this while other threads are + * performing lookups is guaranteed to break. + */ +void SparseArrayDestroy(SparseArray * sarray); +/** + * Iterate through the array. Returns the next non-NULL value after index and + * sets index to the following value. For example, an array containing values + * at 0 and 10 will, if called with index set to 0 first return the value at 0 + * and set index to 1. A subsequent call with index set to 1 will return the + * value at 10 and set index to 11. + */ +void * SparseArrayNext(SparseArray * sarray, uint32_t * index); + +/** + * Creates a copy of the sparse array. + */ +SparseArray *SparseArrayCopy(SparseArray * sarray); + +/** + * Returns the total memory usage of a sparse array. + */ +int SparseArraySize(SparseArray *sarray); + +#endif //_SARRAY_H_INCLUDED_ diff --git a/include/selector.h b/include/selector.h new file mode 100644 index 0000000..1030749 --- /dev/null +++ b/include/selector.h @@ -0,0 +1,72 @@ +#ifndef OBJC_SELECTOR_H_INCLUDED +#define OBJC_SELECTOR_H_INCLUDED + +/** + * Structure used to store selectors in the list. + */ +// begin: objc_selector +struct objc_selector +{ + union + { + /** + * The name of this selector. Used for unregistered selectors. + */ + const char *name; + /** + * The index of this selector in the selector table. When a selector + * is registered with the runtime, its name is replaced by an index + * uniquely identifying this selector. The index is used for dispatch. + */ + uintptr_t index; + }; + /** + * The Objective-C type encoding of the message identified by this selector. + */ + const char * types; +}; +// end: objc_selector + + +/** + * Returns the untyped variant of a selector. + */ +__attribute__((unused)) +static uint32_t get_untyped_idx(SEL aSel) +{ + SEL untyped = sel_registerTypedName_np(sel_getName(aSel), 0); + return untyped->index; +} + +__attribute__((unused)) +static SEL sel_getUntyped(SEL aSel) +{ + return sel_registerTypedName_np(sel_getName(aSel), 0); +} + +#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 + * that the selector has the type that the runtime uses for selectors. + */ +#ifdef __clang__ +#define SELECTOR(x) @selector(x) +#else +#define SELECTOR(x) (SEL)@selector(x) +#endif + +#endif // OBJC_SELECTOR_H_INCLUDED diff --git a/include/spinlock.h b/include/spinlock.h new file mode 100644 index 0000000..2efec47 --- /dev/null +++ b/include/spinlock.h @@ -0,0 +1,81 @@ +#ifdef _WIN32 +#include "safewindows.h" +static unsigned sleep(unsigned seconds) +{ + Sleep(seconds*1000); + return 0; +} +#else +#include +#endif + +/** + * Number of spinlocks. This allocates one page on 32-bit platforms. + */ +#define spinlock_count (1<<10) +static const int spinlock_mask = spinlock_count - 1; +/** + * Integers used as spinlocks for atomic property access. + */ +extern int spinlocks[spinlock_count]; +/** + * Get a spin lock from a pointer. We want to prevent lock contention between + * properties in the same object - if someone is stupid enough to be using + * atomic property access, they are probably stupid enough to do it for + * multiple properties in the same object. We also want to try to avoid + * contention between the same property in different objects, so we can't just + * use the ivar offset. + */ +static inline volatile int *lock_for_pointer(const void *ptr) +{ + intptr_t hash = (intptr_t)ptr; + // Most properties will be pointers, so disregard the lowest few bits + hash >>= sizeof(void*) == 4 ? 2 : 8; + intptr_t low = hash & spinlock_mask; + hash >>= 16; + hash |= low; + return spinlocks + (hash & spinlock_mask); +} + +/** + * Unlocks the spinlock. This is not an atomic operation. We are only ever + * modifying the lowest bit of the spinlock word, so it doesn't matter if this + * is two writes because there is no contention among the high bit. There is + * no possibility of contention among calls to this, because it may only be + * called by the thread owning the spin lock. + */ +inline static void unlock_spinlock(volatile int *spinlock) +{ + __sync_synchronize(); + *spinlock = 0; +} +/** + * Attempts to lock a spinlock. This is heavily optimised for the uncontended + * case, because property access should (generally) not be contended. In the + * uncontended case, this is a single atomic compare and swap instruction and a + * branch. Atomic CAS is relatively expensive (can be a pipeline flush, and + * may require locking a cache line in a cache-coherent SMP system, but it's a + * lot cheaper than a system call). + * + * If the lock is contended, then we just sleep and then try again after the + * other threads have run. Note that there is no upper bound on the potential + * running time of this function, which is one of the great many reasons that + * using atomic accessors is a terrible idea, but in the common case it should + * be very fast. + */ +inline static void lock_spinlock(volatile int *spinlock) +{ + int count = 0; + // Set the spin lock value to 1 if it is 0. + while(!__sync_bool_compare_and_swap(spinlock, 0, 1)) + { + count++; + if (0 == count % 10) + { + // If it is already 1, let another thread play with the CPU for a + // bit then try again. + sleep(0); + } + } +} + diff --git a/include/string_hash.h b/include/string_hash.h new file mode 100644 index 0000000..d86ed0b --- /dev/null +++ b/include/string_hash.h @@ -0,0 +1,34 @@ +#include +#include + +/** + * Efficient string hash function. + */ +__attribute__((unused)) +static uint32_t string_hash(const char *str) +{ + uint32_t hash = 0; + int32_t c; + while ((c = *str++)) + { + hash = c + (hash << 6) + (hash << 16) - hash; + } + return hash; +} + +/** + * Test two strings for equality. + */ +__attribute__((unused)) +static int string_compare(const char *str1, const char *str2) +{ + if (str1 == str2) + { + return 1; + } + if (str1 == NULL || str2 == NULL) + { + return 0; + } + return strcmp(str1, str2) == 0; +} diff --git a/include/type_encoding_cases.h b/include/type_encoding_cases.h new file mode 100644 index 0000000..419dc86 --- /dev/null +++ b/include/type_encoding_cases.h @@ -0,0 +1,36 @@ +/** + * type_encoding_cases.h - expects the APPLY_TYPE macro to be defined. This + * macro is invoked once for every type and its Objective-C name. Use this + * file when implementing things like the -unsignedIntValue family of methods. + * For this case, the macro will be invoked with unsigned int as the type and + * unsignedInt as the name. + */ +#ifndef APPLY_TYPE +#error Define APPLY_TYPE(type, name, capitalizedName, encodingChar) before including this file +#endif +APPLY_TYPE(long double, long double, LongDouble, 'D') +APPLY_TYPE(double, double, Double, 'd') +APPLY_TYPE(float, float, Float, 'f') +APPLY_TYPE(signed char, char, Char, 'c') +APPLY_TYPE(int, int, Int, 'i') +APPLY_TYPE(short, short, Short, 's') +APPLY_TYPE(long, long, Long, 'l') +APPLY_TYPE(long long, longLong, LongLong, 'q') +//APPLY_TYPE(__int128, int128, Int128, 't') +APPLY_TYPE(unsigned char, unsignedChar, UnsignedChar, 'C') +APPLY_TYPE(unsigned short, unsignedShort, UnsignedShort, 'S') +APPLY_TYPE(unsigned int, unsignedInt, UnsignedInt, 'I') +APPLY_TYPE(unsigned long, unsignedLong, UnsignedLong, 'L') +APPLY_TYPE(unsigned long long, unsignedLongLong, UnsignedLongLong, 'Q') +//APPLY_TYPE(unsigned __int128, unsignedInt128, UnsignedInt128, 'T') +#ifdef NON_INTEGER_TYPES +#undef NON_INTEGER_TYPES +APPLY_TYPE(_Bool, bool, Bool, 'B') +#ifndef SKIP_ID +APPLY_TYPE(id, object, Object, '@') +#endif +APPLY_TYPE(Class, class, Class, '#') +APPLY_TYPE(SEL, selector, Selector, ':') +APPLY_TYPE(char*, cString, CString, '*') +#endif +#undef APPLY_TYPE diff --git a/include/unwind-arm.h b/include/unwind-arm.h new file mode 100644 index 0000000..724624e --- /dev/null +++ b/include/unwind-arm.h @@ -0,0 +1,217 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * ARM-specific unwind definitions. These are taken from the ARM EHABI + * specification. + */ + typedef enum +{ + _URC_NO_REASON = 0, + _URC_OK = 0, /* operation completed successfully */ + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8, + _URC_FAILURE = 9, /* unspecified failure of some kind */ + _URC_FATAL_PHASE1_ERROR = _URC_FAILURE +} _Unwind_Reason_Code; + +typedef uint32_t _Unwind_State; +#ifdef __clang__ +static const _Unwind_State _US_VIRTUAL_UNWIND_FRAME = 0; +static const _Unwind_State _US_UNWIND_FRAME_STARTING = 1; +static const _Unwind_State _US_UNWIND_FRAME_RESUME = 2; +static const _Unwind_State _US_FORCE_UNWIND = 8; +#else // GCC fails at knowing what a constant expression is +# define _US_VIRTUAL_UNWIND_FRAME 0 +# define _US_UNWIND_FRAME_STARTING 1 +# define _US_UNWIND_FRAME_RESUME 2 +# define _US_FORCE_UNWIND 8 +#endif + +typedef int _Unwind_Action; + +typedef struct _Unwind_Context _Unwind_Context; + +typedef uint32_t _Unwind_EHT_Header; + +struct _Unwind_Exception +{ + uint64_t exception_class; + void (*exception_cleanup)(_Unwind_Reason_Code, struct _Unwind_Exception *); + /* Unwinder cache, private fields for the unwinder's use */ + struct + { + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; + uint32_t reserved4; + uint32_t reserved5; + /* init reserved1 to 0, then don't touch */ + } unwinder_cache; + /* Propagation barrier cache (valid after phase 1): */ + struct + { + uint32_t sp; + uint32_t bitpattern[5]; + } barrier_cache; + /* Cleanup cache (preserved over cleanup): */ + struct + { + uint32_t bitpattern[4]; + } cleanup_cache; + /* Pr cache (for pr's benefit): */ + struct + { + /** function start address */ + uint32_t fnstart; + /** pointer to EHT entry header word */ + _Unwind_EHT_Header *ehtp; + /** additional data */ + uint32_t additional; + uint32_t reserved1; + } pr_cache; + /** Force alignment of next item to 8-byte boundary */ + long long int :0; +}; + +/* Unwinding functions */ +_Unwind_Reason_Code _Unwind_RaiseException(struct _Unwind_Exception *ucbp); +void _Unwind_Resume(struct _Unwind_Exception *ucbp); +_Unwind_Reason_Code _Unwind_Resume_or_Rethrow(struct _Unwind_Exception *); +void _Unwind_Complete(struct _Unwind_Exception *ucbp); +void _Unwind_DeleteException(struct _Unwind_Exception *ucbp); +void *_Unwind_GetLanguageSpecificData(struct _Unwind_Context*); + +typedef enum +{ + _UVRSR_OK = 0, + _UVRSR_NOT_IMPLEMENTED = 1, + _UVRSR_FAILED = 2 +} _Unwind_VRS_Result; +typedef enum +{ + _UVRSC_CORE = 0, + _UVRSC_VFP = 1, + _UVRSC_WMMXD = 3, + _UVRSC_WMMXC = 4 +} _Unwind_VRS_RegClass; +typedef enum +{ + _UVRSD_UINT32 = 0, + _UVRSD_VFPX = 1, + _UVRSD_UINT64 = 3, + _UVRSD_FLOAT = 4, + _UVRSD_DOUBLE = 5 +} _Unwind_VRS_DataRepresentation; + +_Unwind_VRS_Result _Unwind_VRS_Get(_Unwind_Context *context, + _Unwind_VRS_RegClass regclass, + uint32_t regno, + _Unwind_VRS_DataRepresentation representation, + void *valuep); +_Unwind_VRS_Result _Unwind_VRS_Set(_Unwind_Context *context, + _Unwind_VRS_RegClass regclass, + uint32_t regno, + _Unwind_VRS_DataRepresentation representation, + void *valuep); + +/* Return the base-address for data references. */ +extern unsigned long _Unwind_GetDataRelBase(struct _Unwind_Context *); + +/* Return the base-address for text references. */ +extern unsigned long _Unwind_GetTextRelBase(struct _Unwind_Context *); +extern unsigned long _Unwind_GetRegionStart(struct _Unwind_Context *); + +/** + * The next set of functions are compatibility extensions, implementing Itanium + * ABI functions on top of ARM ones. + */ + +#define _UA_SEARCH_PHASE 1 +#define _UA_CLEANUP_PHASE 2 +#define _UA_HANDLER_FRAME 4 +#define _UA_FORCE_UNWIND 8 + +static inline unsigned long _Unwind_GetGR(struct _Unwind_Context *context, int reg) +{ + unsigned long val; + _Unwind_VRS_Get(context, _UVRSC_CORE, reg, _UVRSD_UINT32, &val); + return val; +} +static inline void _Unwind_SetGR(struct _Unwind_Context *context, int reg, unsigned long val) +{ + _Unwind_VRS_Set(context, _UVRSC_CORE, reg, _UVRSD_UINT32, &val); +} +static inline unsigned long _Unwind_GetIP(_Unwind_Context *context) +{ + // Low bit store the thumb state - discard it + return _Unwind_GetGR(context, 15) & ~1; +} +static inline void _Unwind_SetIP(_Unwind_Context *context, unsigned long val) +{ + // The lowest bit of the instruction pointer indicates whether we're in + // thumb or ARM mode. This is assumed to be fixed throughout a function, + // so must be propagated when setting the program counter. + unsigned long thumbState = _Unwind_GetGR(context, 15) & 1; + _Unwind_SetGR(context, 15, (val | thumbState)); +} + +/** GNU API function that unwinds the frame */ +_Unwind_Reason_Code __gnu_unwind_frame(struct _Unwind_Exception*, struct _Unwind_Context*); + + +#define DECLARE_PERSONALITY_FUNCTION(name) \ +_Unwind_Reason_Code name(_Unwind_State state,\ + struct _Unwind_Exception *exceptionObject,\ + struct _Unwind_Context *context); + +#define BEGIN_PERSONALITY_FUNCTION(name) \ +_Unwind_Reason_Code name(_Unwind_State state,\ + struct _Unwind_Exception *exceptionObject,\ + struct _Unwind_Context *context)\ +{\ + int version = 1;\ + uint64_t exceptionClass = exceptionObject->exception_class;\ + int actions;\ + switch (state & ~_US_FORCE_UNWIND)\ + {\ + default: return _URC_FAILURE;\ + case _US_VIRTUAL_UNWIND_FRAME:\ + {\ + actions = _UA_SEARCH_PHASE;\ + break;\ + }\ + case _US_UNWIND_FRAME_STARTING:\ + {\ + actions = _UA_CLEANUP_PHASE;\ + if (exceptionObject->barrier_cache.sp == _Unwind_GetGR(context, 13))\ + {\ + actions |= _UA_HANDLER_FRAME;\ + }\ + break;\ + }\ + case _US_UNWIND_FRAME_RESUME:\ + {\ + return continueUnwinding(exceptionObject, context);\ + break;\ + }\ + }\ + _Unwind_SetGR (context, 12, (unsigned long)exceptionObject); + +#define CALL_PERSONALITY_FUNCTION(name) name(state,exceptionObject,context) + +#define COPY_EXCEPTION(dst, src) \ + (dst)->unwinder_cache = (src)->unwinder_cache; \ + (dst)->barrier_cache = (src)->barrier_cache; \ + (dst)->cleanup_cache = (src)->cleanup_cache; \ + (dst)->pr_cache = (src)->pr_cache; + +#ifdef __cplusplus +} +#endif diff --git a/include/unwind-itanium.h b/include/unwind-itanium.h new file mode 100644 index 0000000..b169421 --- /dev/null +++ b/include/unwind-itanium.h @@ -0,0 +1,188 @@ +/* libunwind - a platform-independent unwind library + Copyright (C) 2003 Hewlett-Packard Co + Contributed by David Mosberger-Tang + +This file is part of libunwind. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#ifndef _UNWIND_H +#define _UNWIND_H + +/* For uint64_t */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Minimal interface as per C++ ABI draft standard: + + http://www.codesourcery.com/cxx-abi/abi-eh.html */ + +typedef enum + { + _URC_NO_REASON = 0, + _URC_OK = 0, + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_FATAL_PHASE2_ERROR = 2, + _URC_FATAL_PHASE1_ERROR = 3, + _URC_NORMAL_STOP = 4, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8 + } +_Unwind_Reason_Code; + +typedef int _Unwind_Action; + +#define _UA_SEARCH_PHASE 1 +#define _UA_CLEANUP_PHASE 2 +#define _UA_HANDLER_FRAME 4 +#define _UA_FORCE_UNWIND 8 + +struct _Unwind_Context; /* opaque data-structure */ +struct _Unwind_Exception; /* forward-declaration */ + +typedef void (*_Unwind_Exception_Cleanup_Fn) (_Unwind_Reason_Code, + struct _Unwind_Exception *); + +typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) (int, _Unwind_Action, + uint64_t, + struct _Unwind_Exception *, + struct _Unwind_Context *, + void *); + +/* The C++ ABI requires exception_class, private_1, and private_2 to + be of type uint64 and the entire structure to be + double-word-aligned. Please note that exception_class stays 64-bit + even on 32-bit machines for gcc compatibility. */ +struct _Unwind_Exception + { + uint64_t exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; +#ifdef __SEH__ + uintptr_t private_[6]; +#else + uintptr_t private_1; + uintptr_t private_2; +#endif + } __attribute__((__aligned__)); + +extern _Unwind_Reason_Code _Unwind_RaiseException (struct _Unwind_Exception *); +extern _Unwind_Reason_Code _Unwind_ForcedUnwind (struct _Unwind_Exception *, + _Unwind_Stop_Fn, void *); +extern void _Unwind_Resume (struct _Unwind_Exception *); +extern void _Unwind_DeleteException (struct _Unwind_Exception *); +extern uintptr_t _Unwind_GetGR (struct _Unwind_Context *, int); +extern void _Unwind_SetGR (struct _Unwind_Context *, int, uintptr_t); +extern uintptr_t _Unwind_GetIP (struct _Unwind_Context *); +extern uintptr_t _Unwind_GetIPInfo (struct _Unwind_Context *, int *); +extern void _Unwind_SetIP (struct _Unwind_Context *, uintptr_t); +extern uintptr_t _Unwind_GetLanguageSpecificData (struct _Unwind_Context*); +extern uintptr_t _Unwind_GetRegionStart (struct _Unwind_Context *); + +#ifdef _GNU_SOURCE + +/* Callback for _Unwind_Backtrace(). The backtrace stops immediately + if the callback returns any value other than _URC_NO_REASON. */ +typedef _Unwind_Reason_Code (*_Unwind_Trace_Fn) (struct _Unwind_Context *, + void *); + +/* See http://gcc.gnu.org/ml/gcc-patches/2001-09/msg00082.html for why + _UA_END_OF_STACK exists. */ +# define _UA_END_OF_STACK 16 + +/* If the unwind was initiated due to a forced unwind, resume that + operation, else re-raise the exception. This is used by + __cxa_rethrow(). */ +extern _Unwind_Reason_Code + _Unwind_Resume_or_Rethrow (struct _Unwind_Exception *); + +/* See http://gcc.gnu.org/ml/gcc-patches/2003-09/msg00154.html for why + _Unwind_GetBSP() exists. */ +extern uintptr_t _Unwind_GetBSP (struct _Unwind_Context *); + +/* Return the "canonical frame address" for the given context. + This is used by NPTL... */ +extern uintptr_t _Unwind_GetCFA (struct _Unwind_Context *); + +/* Return the base-address for data references. */ +extern uintptr_t _Unwind_GetDataRelBase (struct _Unwind_Context *); + +/* Return the base-address for text references. */ +extern uintptr_t _Unwind_GetTextRelBase (struct _Unwind_Context *); + +/* Call _Unwind_Trace_Fn once for each stack-frame, without doing any + cleanup. The first frame for which the callback is invoked is the + one for the caller of _Unwind_Backtrace(). _Unwind_Backtrace() + returns _URC_END_OF_STACK when the backtrace stopped due to + reaching the end of the call-chain or _URC_FATAL_PHASE1_ERROR if it + stops for any other reason. */ +extern _Unwind_Reason_Code _Unwind_Backtrace (_Unwind_Trace_Fn, void *); + +/* Find the start-address of the procedure containing the specified IP + or NULL if it cannot be found (e.g., because the function has no + unwind info). Note: there is not necessarily a one-to-one + correspondence between source-level functions and procedures: some + functions don't have unwind-info and others are split into multiple + procedures. */ +extern void *_Unwind_FindEnclosingFunction (void *); + +/* See also Linux Standard Base Spec: + http://www.linuxbase.org/spec/refspecs/LSB_1.3.0/gLSB/gLSB/libgcc-s.html */ + +#endif /* _GNU_SOURCE */ + +#define DECLARE_PERSONALITY_FUNCTION(name) \ +_Unwind_Reason_Code name(int version,\ + _Unwind_Action actions,\ + uint64_t exceptionClass,\ + struct _Unwind_Exception *exceptionObject,\ + struct _Unwind_Context *context); +#define BEGIN_PERSONALITY_FUNCTION(name) \ +_Unwind_Reason_Code name(int version,\ + _Unwind_Action actions,\ + uint64_t exceptionClass,\ + struct _Unwind_Exception *exceptionObject,\ + struct _Unwind_Context *context)\ +{ + +#define CALL_PERSONALITY_FUNCTION(name) name(version, actions, exceptionClass, exceptionObject, context) + +#ifdef __SEH__ +#define COPY_EXCEPTION(dst, src) \ + do { \ + memcpy((dst)->private_, (src)->private_, sizeof((src)->private_)); \ + } while(0) +#else +#define COPY_EXCEPTION(dst, src) \ + do { \ + (dst)->private_1 = (src)->private_1; \ + (dst)->private_2 = (src)->private_2; \ + } while(0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _UNWIND_H */ \ No newline at end of file diff --git a/include/unwind.h b/include/unwind.h new file mode 100644 index 0000000..76b6780 --- /dev/null +++ b/include/unwind.h @@ -0,0 +1,5 @@ +#ifdef __arm__ +#include "unwind-arm.h" +#else +#include "unwind-itanium.h" +#endif diff --git a/include/visibility.h b/include/visibility.h new file mode 100644 index 0000000..c3910e5 --- /dev/null +++ b/include/visibility.h @@ -0,0 +1,24 @@ +#include "objc/objc-visibility.h" + +#if defined _WIN32 || defined __CYGWIN__ || __MINGW32__ || __MINGW64__ +# define PRIVATE +#else +# define PRIVATE __attribute__ ((visibility("hidden"))) +#endif +#ifdef NO_LEGACY +# define LEGACY PRIVATE +#else +# define LEGACY OBJC_PUBLIC +#endif + +#if defined(DEBUG) || (!defined(__clang__)) +# include +# define UNREACHABLE(x) assert(0 && x) +# define ASSERT(x) assert(x) +#else +# define UNREACHABLE(x) __builtin_unreachable() +# define ASSERT(x) do { if (!(x)) __builtin_unreachable(); } while(0) +#endif + +#define LIKELY(x) __builtin_expect(x, 1) +#define UNLIKELY(x) __builtin_expect(x, 0) diff --git a/objc/objc-runtime.h b/objc/objc-runtime.h index 4599f08..0c007e1 100644 --- a/objc/objc-runtime.h +++ b/objc/objc-runtime.h @@ -1,2 +1,4 @@ #include #include + + diff --git a/objc/objc-visibility.h b/objc/objc-visibility.h index 4999638..f5dfbf9 100644 --- a/objc/objc-visibility.h +++ b/objc/objc-visibility.h @@ -1,4 +1,4 @@ -#if defined _WIN32 || defined __CYGWIN__ +#if defined _WIN32 || defined __CYGWIN__ # ifdef __OBJC_RUNTIME_INTERNAL__ # define OBJC_PUBLIC __attribute__((dllexport)) # else diff --git a/objc/runtime.h b/objc/runtime.h index ba9cf85..fba427f 100644 --- a/objc/runtime.h +++ b/objc/runtime.h @@ -595,6 +595,7 @@ void objc_disposeClassPair(Class cls); * loaded, it calls the _objc_lookup_class() callback to allow an external * library to load the module providing this class. */ + OBJC_PUBLIC id objc_getClass(const char *name); diff --git a/objc/slot.h b/objc/slot.h index 85fbe42..4a847f3 100644 --- a/objc/slot.h +++ b/objc/slot.h @@ -33,7 +33,9 @@ struct objc_slot2 */ #if defined(__powerpc__) && !defined(__powerpc64__) #else -OBJC_PUBLIC extern _Atomic(uint64_t) objc_method_cache_version; + #if defined(__clang__) + OBJC_PUBLIC extern _Atomic(uint64_t) objc_method_cache_version; + #endif #endif /** diff --git a/src/NSBlocks.m b/src/NSBlocks.m new file mode 100644 index 0000000..49eee9d --- /dev/null +++ b/src/NSBlocks.m @@ -0,0 +1,79 @@ +#include "objc/runtime.h" +#include "class.h" +#include "loader.h" +#include "lock.h" +#include "objc/blocks_runtime.h" +#include "dtable.h" +#include + +#ifdef EMBEDDED_BLOCKS_RUNTIME +#define BLOCK_STORAGE OBJC_PUBLIC +#else +#define BLOCK_STORAGE extern +#endif + +BLOCK_STORAGE struct objc_class _NSConcreteGlobalBlock; +BLOCK_STORAGE struct objc_class _NSConcreteStackBlock; +BLOCK_STORAGE struct objc_class _NSConcreteMallocBlock; +BLOCK_STORAGE struct objc_class _NSConcreteAutoBlock; +BLOCK_STORAGE struct objc_class _NSConcreteFinalizingBlock; + +static struct objc_class _NSConcreteGlobalBlockMeta; +static struct objc_class _NSConcreteStackBlockMeta; +static struct objc_class _NSConcreteMallocBlockMeta; +static struct objc_class _NSConcreteAutoBlockMeta; +static struct objc_class _NSConcreteFinalizingBlockMeta; + +static struct objc_class _NSBlock; +static struct objc_class _NSBlockMeta; + +static void createNSBlockSubclass(Class superclass, Class newClass, + Class metaClass, char *name) +{ + // Initialize the metaclass + //metaClass->class_pointer = superclass->class_pointer; + //metaClass->super_class = superclass->class_pointer; + metaClass->info = objc_class_flag_meta; + metaClass->dtable = uninstalled_dtable; + + // Set up the new class + newClass->isa = metaClass; + newClass->super_class = superclass; + newClass->name = name; + newClass->dtable = uninstalled_dtable; + newClass->info = objc_class_flag_is_block; + + LOCK_RUNTIME_FOR_SCOPE(); + objc_load_class(newClass); + +} + +#define NEW_CLASS(super, sub) \ + createNSBlockSubclass(super, &sub, &sub ## Meta, #sub) + +OBJC_PUBLIC +BOOL objc_create_block_classes_as_subclasses_of(Class super) +{ + if (_NSBlock.super_class != NULL) { return NO; } + + NEW_CLASS(super, _NSBlock); + NEW_CLASS(&_NSBlock, _NSConcreteStackBlock); + NEW_CLASS(&_NSBlock, _NSConcreteGlobalBlock); + NEW_CLASS(&_NSBlock, _NSConcreteMallocBlock); + NEW_CLASS(&_NSBlock, _NSConcreteAutoBlock); + NEW_CLASS(&_NSBlock, _NSConcreteFinalizingBlock); + // Global blocks never need refcount manipulation. + objc_set_class_flag(&_NSConcreteGlobalBlock, + objc_class_flag_permanent_instances); + return YES; +} + +PRIVATE void init_early_blocks(void) +{ + if (_NSBlock.super_class != NULL) { return; } + _NSConcreteStackBlock.info = objc_class_flag_is_block; + _NSConcreteGlobalBlock.info = objc_class_flag_is_block | objc_class_flag_permanent_instances; + _NSConcreteMallocBlock.info = objc_class_flag_is_block; + _NSConcreteAutoBlock.info = objc_class_flag_is_block; + _NSConcreteFinalizingBlock.info = objc_class_flag_is_block; +} \ No newline at end of file diff --git a/src/Protocol2.m b/src/Protocol2.m new file mode 100644 index 0000000..154403b --- /dev/null +++ b/src/Protocol2.m @@ -0,0 +1,45 @@ +#include "objc/runtime.h" +#include "protocol.h" +#include "class.h" +#include +#include + +@implementation Protocol +// FIXME: This needs removing, but it's included for now because GNUstep's +// implementation of +[NSObject conformsToProtocol:] calls it. +- (BOOL)conformsTo: (Protocol*)p +{ + return protocol_conformsToProtocol(self, p); +} +- (id)retain +{ + return self; +} +- (void)release {} ++ (Class)class { return self; } +- (id)self { return self; } +@end +@interface __IncompleteProtocol : Protocol @end +@implementation __IncompleteProtocol @end + +/** + * This class exists for the sole reason that the legacy GNU ABI did not + * provide a way of registering protocols with the runtime. With the new ABI, + * every protocol in a compilation unit that is not referenced should be added + * in a category on this class. This ensures that the runtime sees every + * protocol at least once and can perform uniquing. + */ +@interface __ObjC_Protocol_Holder_Ugly_Hack { id isa; } @end +@implementation __ObjC_Protocol_Holder_Ugly_Hack @end + +@implementation Object @end + +@implementation ProtocolGCC @end +@implementation ProtocolGSv1 @end + +PRIVATE void link_protocol_classes(void) +{ + [Protocol class]; + [ProtocolGCC class]; + [ProtocolGSv1 class]; +} diff --git a/src/abi_version.c b/src/abi_version.c new file mode 100644 index 0000000..cfddfe0 --- /dev/null +++ b/src/abi_version.c @@ -0,0 +1,130 @@ +#include "visibility.h" +#include "objc/runtime.h" +#include "module.h" +#include "gc_ops.h" +#include +#include +#include + +/** + * The smallest ABI version number of loaded modules. + */ +static unsigned long min_loaded_version; +/** + * The largest ABI version number of loaded modules. + */ +static unsigned long max_loaded_version; + +/** + * Structure defining the compatibility between Objective-C ABI versions. + */ +struct objc_abi_version +{ + /** Version of this ABI. */ + unsigned long version; + /** Lowest ABI version that this is compatible with. */ + unsigned long min_compatible_version; + /** Highest ABI version compatible with this. */ + unsigned long max_compatible_version; + /** Size of the module structure for this ABI version. */ + unsigned long module_size; +}; + +enum +{ + gcc_abi = 8, + gnustep_abi = 9, + gc_abi = 10 +}; + +/** + * List of supported ABIs. + */ +static struct objc_abi_version known_abis[] = +{ + /* GCC ABI. */ + {gcc_abi, gcc_abi, gnustep_abi, sizeof(struct objc_module_abi_8)}, + /* Non-fragile ABI. */ + {gnustep_abi, gcc_abi, gc_abi, sizeof(struct objc_module_abi_8)}, + /* GC ABI. Adds a field describing the GC mode. */ + {gc_abi, gcc_abi, gc_abi, sizeof(struct objc_module_abi_10)} +}; + +static int known_abi_count = + (sizeof(known_abis) / sizeof(struct objc_abi_version)); + +#define FAIL_IF(x, msg) do {\ + if (x)\ + {\ + fprintf(stderr, "Objective-C ABI Error: %s while loading %s\n", msg, module->name);\ + return NO;\ + }\ +} while(0) + +static BOOL endsWith(const char *string, const char *suffix) +{ + if (NULL == string) { return NO; } + char *interior = strstr(string, suffix); + return (interior && (strlen(suffix) == strlen(interior))); +} + +PRIVATE BOOL objc_check_abi_version(struct objc_module_abi_8 *module) +{ + static int runtime_modules = 5; + // As a quick and ugly hack, skip these three tests for the .m files in the + // runtime. They should (in theory, at least) be aware of the GC mode and + // behave accordingly. + if (runtime_modules > 0) + { + if (endsWith(module->name, "properties.m") || + endsWith(module->name, "associate.m") || + endsWith(module->name, "arc.m") || + endsWith(module->name, "blocks_runtime.m") || + endsWith(module->name, "Protocol2.m")) + { + runtime_modules--; + return YES; + } + } + unsigned long version = module->version; + unsigned long module_size = module->size; + enum objc_gc_mode gc_mode = (version < gc_abi) ? GC_None + : ((struct objc_module_abi_10*)module)->gc_mode; + struct objc_abi_version *v = NULL; + for (int i=0 ; imodule_size != module_size), "Incorrect module size"); + // Only check for ABI compatibility if + if (min_loaded_version > 0) + { + FAIL_IF((v->min_compatible_version > min_loaded_version), + "Loading modules from incompatible ABIs"); + FAIL_IF((v->max_compatible_version < max_loaded_version), + "Loading modules from incompatible ABIs"); + if (min_loaded_version > version) + { + min_loaded_version = version; + } + if (max_loaded_version < version) + { + max_loaded_version = version; + } + } + else + { + min_loaded_version = version; + max_loaded_version = version; + } + + // We can't mix GC_None and GC_Required code, but we can mix any other + // combination + FAIL_IF((gc_mode == GC_Required), "GC code is no longer supported!"); + return YES; +} diff --git a/src/alias_table.c b/src/alias_table.c new file mode 100644 index 0000000..e6d1071 --- /dev/null +++ b/src/alias_table.c @@ -0,0 +1,128 @@ +/** A hash table for mapping compatibility aliases to classes. + Copyright (c) 2011 Free Software Foundation, Inc. + + Written by: Niels Grewe + Created: March 2011 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include "visibility.h" +#include "objc/runtime.h" +#include "class.h" +#include "lock.h" +#include "string_hash.h" + +#include + +struct objc_alias +{ + const char* name; + Class class; +}; + +typedef struct objc_alias Alias; + +static int alias_compare(const char *name, const Alias alias) +{ + return string_compare(name, alias.name); +} + +static int alias_hash(const Alias alias) +{ + return string_hash(alias.name); +} +static int alias_is_null(const Alias alias) +{ + return alias.name == NULL; +} +static Alias NullAlias; +#define MAP_TABLE_NAME alias_table_internal +#define MAP_TABLE_COMPARE_FUNCTION alias_compare +#define MAP_TABLE_HASH_KEY string_hash +#define MAP_TABLE_HASH_VALUE alias_hash +#define MAP_TABLE_VALUE_TYPE struct objc_alias +#define MAP_TABLE_VALUE_NULL alias_is_null +#define MAP_TABLE_VALUE_PLACEHOLDER NullAlias + +#include "hash_table.h" + +static alias_table_internal_table *alias_table; + +PRIVATE void init_alias_table(void) +{ + alias_table_internal_initialize(&alias_table, 128); +} + + +static Alias alias_table_get_safe(const char *alias_name) +{ + return alias_table_internal_table_get(alias_table, alias_name); +} + + +OBJC_PUBLIC Class alias_getClass(const char *alias_name) +{ + if (NULL == alias_name) + { + return NULL; + } + + Alias alias = alias_table_get_safe(alias_name); + + if (NULL == alias.name) + { + return NULL; + } + + return alias.class; +} + +PRIVATE void alias_table_insert(Alias alias) +{ + alias_table_internal_insert(alias_table, alias); +} + +OBJC_PUBLIC BOOL class_registerAlias_np(Class class, const char *alias) +{ + if ((NULL == alias) || (NULL == class)) + { + return 0; + } + + class = (Class)objc_getClass(class->name); + + /* + * If there already exists a matching alias, determine whether we the existing + * alias is the correct one. Please note that objc_getClass() goes through the + * alias lookup and will create the alias table if necessary. + */ + Class existingClass = (Class)objc_getClass(alias); + if (NULL != existingClass) + { + /* + * Return YES if the alias has already been registered for this very + * class, and NO if the alias is already used for another class. + */ + return (class == existingClass); + } + Alias newAlias = { strdup(alias), class }; + alias_table_insert(newAlias); + return 1; +} diff --git a/src/arc.mm b/src/arc.mm new file mode 100644 index 0000000..ee3efbf --- /dev/null +++ b/src/arc.mm @@ -0,0 +1,1057 @@ +#define _LIBCPP_NO_EXCEPTIONS 1 +#define TSL_NO_EXCEPTIONS 1 +// Libc++ < 13 requires this for to be header only. It is ignored in +// libc++ >= 14 +#define _LIBCPP_DISABLE_EXTERN_TEMPLATE 1 +#include +#include +#include +#include +#include +#import "lock.h" +#import "objc/runtime.h" +#ifdef EMBEDDED_BLOCKS_RUNTIME +#import "objc/blocks_private.h" +#import "objc/blocks_runtime.h" +#else +#include +#include +#endif +#import "nsobject.h" +#import "class.h" +#import "selector.h" +#import "visibility.h" +#import "objc/hooks.h" +#import "objc/objc-arc.h" +#include "objc/message.h" + +/** + * Helper to send a manual message for retain / release. + * We cannot use [object retain] and friends because recent clang will turn + * that into a call to `objc_retain`, causing infinite recursion. + */ +#ifdef __GNUSTEP_MSGSEND__ +#define ManualRetainReleaseMessage(object, selName, types) \ + ((types)objc_msgSend)(object, @selector(selName)) +#else +#define ManualRetainReleaseMessage(object, selName, types) \ + ((types)(objc_msg_lookup(object, @selector(selName))))(object, @selector(selName)) +#endif + +extern "C" id (*_objc_weak_load)(id object); + +#if defined(_WIN32) +// We're using the Fiber-Local Storage APIs on Windows +// because the TLS APIs won't pass app certification. +// Additionally, the FLS API surface is 1:1 mapped to +// the TLS API surface when fibers are not in use. +# include "safewindows.h" +# define arc_tls_store FlsSetValue +# define arc_tls_load FlsGetValue +# define TLS_CALLBACK(name) void WINAPI name + +typedef DWORD arc_tls_key_t; +typedef void WINAPI(*arc_cleanup_function_t)(void*); +static inline arc_tls_key_t arc_tls_key_create(arc_cleanup_function_t cleanupFunction) +{ + return FlsAlloc(cleanupFunction); +} + +#else // if defined(_WIN32) + +# ifndef NO_PTHREADS +# include +# define arc_tls_store pthread_setspecific +# define arc_tls_load pthread_getspecific +# define TLS_CALLBACK(name) void name + +typedef pthread_key_t arc_tls_key_t; +typedef void (*arc_cleanup_function_t)(void*); +static inline arc_tls_key_t arc_tls_key_create(arc_cleanup_function_t cleanupFunction) +{ + pthread_key_t key; + pthread_key_create(&key, cleanupFunction); + return key; +} +# endif +#endif + +#ifdef arc_tls_store +arc_tls_key_t ARCThreadKey; +#endif + +#ifndef HAVE_BLOCK_USE_RR2 +extern "C" +{ + extern struct objc_class _NSConcreteMallocBlock; + extern struct objc_class _NSConcreteStackBlock; + extern struct objc_class _NSConcreteGlobalBlock; + extern struct objc_class _NSConcreteAutoBlock; + extern struct objc_class _NSConcreteFinalizingBlock; +} +#endif + +@interface NSAutoreleasePool ++ (Class)class; ++ (id)new; +- (void)release; +@end + +#define POOL_SIZE (4096 / sizeof(void*) - (2 * sizeof(void*))) +/** + * Structure used for ARC-managed autorelease pools. This structure should be + * exactly one page in size, so that it can be quickly allocated. This does + * not correspond directly to an autorelease pool. The 'pool' returned by + * objc_autoreleasePoolPush() may be an interior pointer to one of these + * structures. + */ +struct arc_autorelease_pool +{ + /** + * Pointer to the previous autorelease pool structure in the chain. Set + * when pushing a new structure on the stack, popped during cleanup. + */ + struct arc_autorelease_pool *previous; + /** + * The current insert point. + */ + id *insert; + /** + * The remainder of the page, an array of object pointers. + */ + id pool[POOL_SIZE]; +}; + +struct arc_tls +{ + struct arc_autorelease_pool *pool; + id returnRetained; +}; + +/** + * Type-safe wrapper around calloc. + */ +template +static inline T* new_zeroed() +{ + return static_cast(calloc(sizeof(T), 1)); +} + +static inline struct arc_tls* getARCThreadData(void) +{ +#ifndef arc_tls_store + return NULL; +#else // !defined arc_tls_store + auto tls = static_cast(arc_tls_load(ARCThreadKey)); + if (NULL == tls) + { + tls = new_zeroed(); + arc_tls_store(ARCThreadKey, tls); + } + return tls; +#endif +} +static inline void release(id obj); + +/** + * Empties objects from the autorelease pool, stating at the head of the list + * specified by pool and continuing until it reaches the stop point. If the stop point is NULL then + */ +static void emptyPool(struct arc_tls *tls, void *stop) +{ + struct arc_autorelease_pool *stopPool = NULL; + if (NULL != stop) + { + stopPool = tls->pool; + while (1) + { + // Invalid stop location + if (NULL == stopPool) + { + return; + } + // NULL is the placeholder for the top-level pool + if (NULL == stop && stopPool->previous == NULL) + { + break; + } + // Stop location was found in this pool + if ((stop >= stopPool->pool) && (stop < &stopPool->pool[POOL_SIZE])) + { + break; + } + stopPool = stopPool->previous; + } + } + do { + while (tls->pool != stopPool) + { + while (tls->pool->insert > tls->pool->pool) + { + tls->pool->insert--; + // This may autorelease some other objects, so we have to work in + // the case where the autorelease pool is extended during a -release. + release(*tls->pool->insert); + } + void *old = tls->pool; + tls->pool = tls->pool->previous; + free(old); + } + if (NULL == tls->pool) break; + while ((stop == NULL || (tls->pool->insert > stop)) && + (tls->pool->insert > tls->pool->pool)) + { + tls->pool->insert--; + release(*tls->pool->insert); + } + } while (tls->pool != stopPool); + //fprintf(stderr, "New insert: %p. Stop: %p\n", tls->pool->insert, stop); +} + +#ifdef arc_tls_store +static TLS_CALLBACK(cleanupPools)(struct arc_tls* tls) +{ + if (tls->returnRetained) + { + release(tls->returnRetained); + tls->returnRetained = nil; + } + if (NULL != tls->pool) + { + emptyPool(tls, NULL); + assert(NULL == tls->pool); + } + if (tls->returnRetained) + { + cleanupPools(tls); + } + free(tls); +} +#endif + + +static Class AutoreleasePool; +static IMP NewAutoreleasePool; +static IMP DeleteAutoreleasePool; +static IMP AutoreleaseAdd; + +static BOOL useARCAutoreleasePool; + +static const long refcount_shift = 1; +/** + * We use the top bit of the reference count to indicate whether an object has + * ever had a weak reference taken. This lets us avoid acquiring the weak + * table lock for most objects on deallocation. + */ +static const size_t weak_mask = ((size_t)1)<<((sizeof(size_t)*8)-refcount_shift); +/** + * All of the bits other than the top bit are the real reference count. + */ +static const size_t refcount_mask = ~weak_mask; +static const size_t refcount_max = refcount_mask - 1; + +extern "C" OBJC_PUBLIC size_t object_getRetainCount_np(id obj) +{ + uintptr_t *refCount = ((uintptr_t*)obj) - 1; + uintptr_t refCountVal = __sync_fetch_and_add(refCount, 0); + size_t realCount = refCountVal & refcount_mask; + return realCount == refcount_mask ? 0 : realCount + 1; +} + +static id retain_fast(id obj, BOOL isWeak) +{ + uintptr_t *refCount = ((uintptr_t*)obj) - 1; + uintptr_t refCountVal = __sync_fetch_and_add(refCount, 0); + uintptr_t newVal = refCountVal; + do { + refCountVal = newVal; + size_t realCount = refCountVal & refcount_mask; + // If this object's reference count is already less than 0, then + // this is a spurious retain. This can happen when one thread is + // attempting to acquire a strong reference from a weak reference + // and the other thread is attempting to destroy it. The + // deallocating thread will decrement the reference count with no + // locks held and will then acquire the weak ref table lock and + // attempt to zero the weak references. The caller of this will be + // `objc_loadWeakRetained`, which will also hold the lock. If the + // serialisation is such that the locked retain happens after the + // decrement, then we return nil here so that the weak-to-strong + // transition doesn't happen and the object is actually destroyed. + // If the serialisation happens the other way, then the locked + // check of the reference count will happen after we've referenced + // this and we don't zero the references or deallocate. + if (realCount == refcount_mask) + { + return isWeak ? nil : obj; + } + // If the reference count is saturated, don't increment it. + if (realCount == refcount_max) + { + return obj; + } + realCount++; + realCount |= refCountVal & weak_mask; + uintptr_t updated = (uintptr_t)realCount; + newVal = __sync_val_compare_and_swap(refCount, refCountVal, updated); + } while (newVal != refCountVal); + return obj; +} + +extern "C" OBJC_PUBLIC id objc_retain_fast_np(id obj) +{ + return retain_fast(obj, NO); +} + +__attribute__((always_inline)) +static inline BOOL isPersistentObject(id obj) +{ + // No reference count manipulations on nil objects. + if (obj == nil) + { + return YES; + } + // Small objects are never accessibly by reference + if (isSmallObject(obj)) + { + return YES; + } + // Persistent objects are persistent. Safe to access isa directly here + // because we've already handled the small object case separately. + return objc_test_class_flag(obj->isa, objc_class_flag_permanent_instances); +} + +static inline id retain(id obj, BOOL isWeak) +{ + if (isPersistentObject(obj)) { return obj; } + Class cls = obj->isa; + if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block))) + { + return Block_copy(obj); + } + if (objc_test_class_flag(cls, objc_class_flag_fast_arc)) + { + return retain_fast(obj, isWeak); + } + return ManualRetainReleaseMessage(obj, retain, id(*)(id, SEL)); +} + +extern "C" OBJC_PUBLIC BOOL objc_release_fast_no_destroy_np(id obj) +{ + uintptr_t *refCount = ((uintptr_t*)obj) - 1; + uintptr_t refCountVal = __sync_fetch_and_add(refCount, 0); + uintptr_t newVal = refCountVal; + bool isWeak; + bool shouldFree; + do { + refCountVal = newVal; + size_t realCount = refCountVal & refcount_mask; + // If the reference count is saturated or deallocating, don't decrement it. + if (realCount >= refcount_max) + { + return NO; + } + realCount--; + isWeak = (refCountVal & weak_mask) == weak_mask; + shouldFree = realCount == -1; + realCount |= refCountVal & weak_mask; + uintptr_t updated = (uintptr_t)realCount; + newVal = __sync_val_compare_and_swap(refCount, refCountVal, updated); + } while (newVal != refCountVal); + + if (shouldFree) + { + if (isWeak) + { + if (!objc_delete_weak_refs(obj)) + { + return NO; + } + } + return YES; + } + return NO; +} + +extern "C" OBJC_PUBLIC void objc_release_fast_np(id obj) +{ + if (objc_release_fast_no_destroy_np(obj)) + { + [obj dealloc]; + } +} + +static inline void release(id obj) +{ + if (isPersistentObject(obj)) { return; } + Class cls = obj->isa; + if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block))) + { + if (cls == static_cast(&_NSConcreteStackBlock)) + { + return; + } + _Block_release(obj); + return; + } + if (objc_test_class_flag(cls, objc_class_flag_fast_arc)) + { + objc_release_fast_np(obj); + return; + } + return ManualRetainReleaseMessage(obj, release, void(*)(id, SEL)); +} + +static inline void initAutorelease(void) +{ + if (Nil == AutoreleasePool) + { + AutoreleasePool = objc_getClass("NSAutoreleasePool"); + if (Nil == AutoreleasePool) + { + useARCAutoreleasePool = YES; + } + else + { + useARCAutoreleasePool = (0 != class_getInstanceMethod(AutoreleasePool, + SELECTOR(_ARCCompatibleAutoreleasePool))); + if (!useARCAutoreleasePool) + { + [AutoreleasePool class]; + NewAutoreleasePool = class_getMethodImplementation(object_getClass(AutoreleasePool), + SELECTOR(new)); + DeleteAutoreleasePool = class_getMethodImplementation(AutoreleasePool, + SELECTOR(release)); + AutoreleaseAdd = class_getMethodImplementation(object_getClass(AutoreleasePool), + SELECTOR(addObject:)); + } + } + } +} + +static inline id autorelease(id obj) +{ + //fprintf(stderr, "Autoreleasing %p\n", obj); + if (useARCAutoreleasePool) + { + struct arc_tls *tls = getARCThreadData(); + if (NULL != tls) + { + struct arc_autorelease_pool *pool = tls->pool; + if (NULL == pool || (pool->insert >= &pool->pool[POOL_SIZE])) + { + pool = new_zeroed(); + pool->previous = tls->pool; + pool->insert = pool->pool; + tls->pool = pool; + } + *pool->insert = obj; + pool->insert++; + return obj; + } + } + if (objc_test_class_flag(classForObject(obj), objc_class_flag_fast_arc)) + { + initAutorelease(); + if (0 != AutoreleaseAdd) + { + AutoreleaseAdd(AutoreleasePool, SELECTOR(addObject:), obj); + } + return obj; + } + return ManualRetainReleaseMessage(obj, autorelease, id(*)(id, SEL)); +} + +extern "C" OBJC_PUBLIC unsigned long objc_arc_autorelease_count_np(void) +{ + struct arc_tls* tls = getARCThreadData(); + unsigned long count = 0; + if (!tls) { return 0; } + + for (struct arc_autorelease_pool *pool=tls->pool ; + NULL != pool ; + pool = pool->previous) + { + count += (((intptr_t)pool->insert) - ((intptr_t)pool->pool)) / sizeof(id); + } + return count; +} +extern "C" OBJC_PUBLIC unsigned long objc_arc_autorelease_count_for_object_np(id obj) +{ + struct arc_tls* tls = getARCThreadData(); + unsigned long count = 0; + if (!tls) { return 0; } + + for (struct arc_autorelease_pool *pool=tls->pool ; + NULL != pool ; + pool = pool->previous) + { + for (id* o = pool->insert-1 ; o >= pool->pool ; o--) + { + if (*o == obj) + { + count++; + } + } + } + return count; +} + +extern "C" OBJC_PUBLIC void *objc_autoreleasePoolPush(void) +{ + initAutorelease(); + struct arc_tls* tls = getARCThreadData(); + // If there is an object in the return-retained slot, then we need to + // promote it to the real autorelease pool BEFORE pushing the new + // autorelease pool. If we don't, then it may be prematurely autoreleased. + if ((NULL != tls) && (nil != tls->returnRetained)) + { + autorelease(tls->returnRetained); + tls->returnRetained = nil; + } + if (useARCAutoreleasePool) + { + if (NULL != tls) + { + struct arc_autorelease_pool *pool = tls->pool; + if (NULL == pool || (pool->insert >= &pool->pool[POOL_SIZE])) + { + pool = new_zeroed(); + pool->previous = tls->pool; + pool->insert = pool->pool; + tls->pool = pool; + } + // If there is no autorelease pool allocated for this thread, then + // we lazily allocate one the first time something is autoreleased. + return (NULL != tls->pool) ? tls->pool->insert : NULL; + } + } + initAutorelease(); + if (0 == NewAutoreleasePool) { return NULL; } + return NewAutoreleasePool(AutoreleasePool, SELECTOR(new)); +} +extern "C" OBJC_PUBLIC void objc_autoreleasePoolPop(void *pool) +{ + if (useARCAutoreleasePool) + { + struct arc_tls* tls = getARCThreadData(); + if (NULL != tls) + { + if (NULL != tls->pool) + { + emptyPool(tls, pool); + } + return; + } + } + DeleteAutoreleasePool(static_cast(pool), SELECTOR(release)); + struct arc_tls* tls = getARCThreadData(); + if (tls && tls->returnRetained) + { + release(tls->returnRetained); + tls->returnRetained = nil; + } +} + +extern "C" OBJC_PUBLIC id objc_autorelease(id obj) +{ + if (nil != obj) + { + obj = autorelease(obj); + } + return obj; +} + +extern "C" OBJC_PUBLIC id objc_autoreleaseReturnValue(id obj) +{ + if (!useARCAutoreleasePool) + { + struct arc_tls* tls = getARCThreadData(); + if (NULL != tls) + { + objc_autorelease(tls->returnRetained); + tls->returnRetained = obj; + return obj; + } + } + return objc_autorelease(obj); +} + +extern "C" OBJC_PUBLIC id objc_retainAutoreleasedReturnValue(id obj) +{ + // If the previous object was released with objc_autoreleaseReturnValue() + // just before return, then it will not have actually been autoreleased. + // Instead, it will have been stored in TLS. We just remove it from TLS + // and undo the fake autorelease. + // + // If the object was not returned with objc_autoreleaseReturnValue() then + // we actually autorelease the fake object. and then retain the argument. + // In tis case, this is equivalent to objc_retain(). + struct arc_tls* tls = getARCThreadData(); + if (NULL != tls) + { + // If we're using our own autorelease pool, just pop the object from the top + if (useARCAutoreleasePool) + { + if ((NULL != tls->pool) && + (*(tls->pool->insert-1) == obj)) + { + tls->pool->insert--; + return obj; + } + } + else if (obj == tls->returnRetained) + { + tls->returnRetained = NULL; + return obj; + } + } + return objc_retain(obj); +} + +extern "C" OBJC_PUBLIC id objc_retain(id obj) +{ + if (nil == obj) { return nil; } + return retain(obj, NO); +} + +extern "C" OBJC_PUBLIC id objc_retainAutorelease(id obj) +{ + return objc_autorelease(objc_retain(obj)); +} + +extern "C" OBJC_PUBLIC id objc_retainAutoreleaseReturnValue(id obj) +{ + if (nil == obj) { return obj; } + return objc_autoreleaseReturnValue(retain(obj, NO)); +} + + +extern "C" OBJC_PUBLIC id objc_retainBlock(id b) +{ + return static_cast(_Block_copy(b)); +} + +extern "C" OBJC_PUBLIC void objc_release(id obj) +{ + if (nil == obj) { return; } + release(obj); +} + +extern "C" OBJC_PUBLIC id objc_storeStrong(id *addr, id value) +{ + value = objc_retain(value); + id oldValue = *addr; + *addr = value; + objc_release(oldValue); + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +// Weak references +//////////////////////////////////////////////////////////////////////////////// + +static int weakref_class; + +namespace { + +struct WeakRef +{ + void *isa = &weakref_class; + id obj = nullptr; + size_t weak_count = 1; + WeakRef(id o) : obj(o) {} +}; + +template +struct malloc_allocator +{ + typedef T value_type; + T* allocate(std::size_t n) + { + return static_cast(malloc(sizeof(T) * n)); + } + + void deallocate(T* p, std::size_t) + { + free(p); + } + + template + malloc_allocator &operator=(const malloc_allocator&) const + { + return *this; + } + + bool operator==(const malloc_allocator &) const + { + return true; + } + + template + operator malloc_allocator() const + { + return malloc_allocator(); + } +}; + +using weak_ref_table = tsl::robin_pg_map, + std::equal_to, + malloc_allocator>>; + +weak_ref_table &weakRefs() +{ + static weak_ref_table w{128}; + return w; +} + +mutex_t weakRefLock; + +} + +#ifdef HAVE_BLOCK_USE_RR2 +static const struct Block_callbacks_RR blocks_runtime_callbacks = { + sizeof(Block_callbacks_RR), + (void (*)(const void*))objc_retain, + (void (*)(const void*))objc_release, + (void (*)(const void*))objc_delete_weak_refs + }; +#endif + +PRIVATE extern "C" void init_arc(void) +{ + INIT_LOCK(weakRefLock); +#ifdef arc_tls_store + ARCThreadKey = arc_tls_key_create((arc_cleanup_function_t)cleanupPools); +#endif +#ifdef HAVE_BLOCK_USE_RR2 + _Block_use_RR2(&blocks_runtime_callbacks); +#endif +} + +/** + * Load from a weak pointer and return whether this really was a weak + * reference or a strong (not deallocatable) object in a weak pointer. The + * object will be stored in `obj` and the weak reference in `ref`, if one + * exists. + */ +__attribute__((always_inline)) +static BOOL loadWeakPointer(id *addr, id *obj, WeakRef **ref) +{ + id oldObj = *addr; + if (oldObj == nil) + { + *ref = NULL; + *obj = nil; + return NO; + } + if (classForObject(oldObj) == (Class)&weakref_class) + { + *ref = (WeakRef*)oldObj; + *obj = (*ref)->obj; + return YES; + } + *ref = NULL; + *obj = oldObj; + return NO; +} + +__attribute__((always_inline)) +static inline BOOL weakRefRelease(WeakRef *ref) +{ + ref->weak_count--; + if (ref->weak_count == 0) + { + weakRefs().erase(ref->obj); + delete ref; + return YES; + } + return NO; +} + +extern "C" void* block_load_weak(void *block); + +static BOOL setObjectHasWeakRefs(id obj) +{ + BOOL isGlobalObject = isPersistentObject(obj); + Class cls = isGlobalObject ? Nil : obj->isa; + if (obj && cls && objc_test_class_flag(cls, objc_class_flag_fast_arc)) + { + uintptr_t *refCount = ((uintptr_t*)obj) - 1; + if (obj) + { + uintptr_t refCountVal = __sync_fetch_and_add(refCount, 0); + uintptr_t newVal = refCountVal; + do { + refCountVal = newVal; + size_t realCount = refCountVal & refcount_mask; + // If this object has already been deallocated (or is in the + // process of being deallocated) then don't bother storing it. + if (realCount == refcount_mask) + { + obj = nil; + cls = Nil; + break; + } + // The weak ref flag is monotonic (it is set, never cleared) so + // don't bother trying to re-set it. + if ((refCountVal & weak_mask) == weak_mask) + { + break; + } + // Set the flag in the reference count to indicate that a weak + // reference has been taken. + // + // We currently hold the weak ref lock, so another thread + // racing to deallocate this object will have to wait to do so + // if we manage to do the reference count update first. This + // shouldn't be possible, because `obj` should be a strong + // reference and so it shouldn't be possible to deallocate it + // while we're assigning it. + uintptr_t updated = ((uintptr_t)realCount | weak_mask); + newVal = __sync_val_compare_and_swap(refCount, refCountVal, updated); + } while (newVal != refCountVal); + } + } + return isGlobalObject; +} + +WeakRef *incrementWeakRefCount(id obj) +{ + WeakRef *&ref = weakRefs()[obj]; + if (ref == nullptr) + { + ref = new WeakRef(obj); + } + else + { + assert(ref->obj == obj); + ref->weak_count++; + } + return ref; +} + +extern "C" OBJC_PUBLIC id objc_storeWeak(id *addr, id obj) +{ + LOCK_FOR_SCOPE(&weakRefLock); + WeakRef *oldRef; + id old; + loadWeakPointer(addr, &old, &oldRef); + // If the old and new values are the same, then we don't need to do anything. + if (old == obj) + { + return obj; + } + BOOL isGlobalObject = setObjectHasWeakRefs(obj); + // If we old ref exists, decrement its reference count. This may also + // delete the weak reference control block. + if (oldRef != NULL) + { + weakRefRelease(oldRef); + } + // If we're storing nil, then just write a null pointer. + if (nil == obj) + { + *addr = obj; + return nil; + } + if (isGlobalObject) + { + // If this is a global object, it's never deallocated, so secretly make + // this a strong reference. + *addr = obj; + return obj; + } + Class cls = classForObject(obj); + if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block))) + { + // Check whether the block is being deallocated and return nil if so + if (_Block_isDeallocating(obj)) { + *addr = nil; + return nil; + } + } + else if (object_getRetainCount_np(obj) == 0) + { + // If the object is being deallocated return nil. + *addr = nil; + return nil; + } + if (nil != obj) + { + *addr = (id)incrementWeakRefCount(obj); + } + return obj; +} + +extern "C" OBJC_PUBLIC BOOL objc_delete_weak_refs(id obj) +{ + LOCK_FOR_SCOPE(&weakRefLock); + if (objc_test_class_flag(classForObject(obj), objc_class_flag_fast_arc)) + { + // Don't proceed if the object isn't deallocating. + uintptr_t *refCount = ((uintptr_t*)obj) - 1; + uintptr_t refCountVal = __sync_fetch_and_add(refCount, 0); + size_t realCount = refCountVal & refcount_mask; + if (realCount != refcount_mask) + { + return NO; + } + } + auto &table = weakRefs(); + auto old = table.find(obj); + if (old != table.end()) + { + WeakRef *oldRef = old->second; + // The address of obj is likely to be reused, so remove it from + // the table so that we don't accidentally alias weak + // references + table.erase(old); + // Zero the object pointer. This prevents any other weak + // accesses from loading from this. This must be done after + // removing the ref from the table, because the compare operation + // tests the obj field. + oldRef->obj = nil; + // If the weak reference count is zero, then we should have + // already removed this. + assert(oldRef->weak_count > 0); + } + return YES; +} + +extern "C" OBJC_PUBLIC id objc_loadWeakRetained(id* addr) +{ + LOCK_FOR_SCOPE(&weakRefLock); + id obj; + WeakRef *ref; + // If this is really a strong reference (nil, or an non-deallocatable + // object), just return it. + if (!loadWeakPointer(addr, &obj, &ref)) + { + return obj; + } + // The object cannot be deallocated while we hold the lock (release + // will acquire the lock before attempting to deallocate) + if (obj == nil) + { + // If the object is destroyed, drop this reference to the WeakRef + // struct. + if (ref != NULL) + { + weakRefRelease(ref); + *addr = nil; + } + return nil; + } + Class cls = classForObject(obj); + if (objc_test_class_flag(cls, objc_class_flag_permanent_instances)) + { + return obj; + } + else if (UNLIKELY(objc_test_class_flag(cls, objc_class_flag_is_block))) + { + obj = static_cast(block_load_weak(obj)); + if (obj == nil) + { + return nil; + } + // This is a defeasible retain operation that protects against another thread concurrently + // starting to deallocate the block. + if (_Block_tryRetain(obj)) + { + return obj; + } + return nil; + + } + else if (!objc_test_class_flag(cls, objc_class_flag_fast_arc)) + { + obj = _objc_weak_load(obj); + } + // _objc_weak_load() can return nil + if (obj == nil) { return nil; } + return retain(obj, YES); +} + +extern "C" OBJC_PUBLIC id objc_loadWeak(id* object) +{ + return objc_autorelease(objc_loadWeakRetained(object)); +} + +extern "C" OBJC_PUBLIC void objc_copyWeak(id *dest, id *src) +{ + // Don't retain or release. + // `src` is a valid pointer to a __weak pointer or nil. + // `dest` is a valid pointer to uninitialised memory. + // After this operation, `dest` should contain whatever `src` contained. + LOCK_FOR_SCOPE(&weakRefLock); + id obj; + WeakRef *srcRef; + loadWeakPointer(src, &obj, &srcRef); + *dest = *src; + if (srcRef) + { + srcRef->weak_count++; + } +} + +extern "C" OBJC_PUBLIC void objc_moveWeak(id *dest, id *src) +{ + // Don't retain or release. + // `dest` is a valid pointer to uninitialized memory. + // `src` is a valid pointer to a __weak pointer. + // This operation moves from *src to *dest and must be atomic with respect + // to other stores to *src via `objc_storeWeak`. + // + // Acquire the lock so that we guarantee the atomicity. We could probably + // optimise this by doing an atomic exchange of `*src` with `nil` and + // storing the result in `dest`, but it's probably not worth it unless weak + // references are a bottleneck. + LOCK_FOR_SCOPE(&weakRefLock); + *dest = *src; + *src = nil; +} + +extern "C" OBJC_PUBLIC void objc_destroyWeak(id* obj) +{ + LOCK_FOR_SCOPE(&weakRefLock); + WeakRef *oldRef; + id old; + loadWeakPointer(obj, &old, &oldRef); + // If the old ref exists, decrement its reference count. This may also + // delete the weak reference control block. + if (oldRef != NULL) + { + weakRefRelease(oldRef); + } +} + +extern "C" OBJC_PUBLIC id objc_initWeak(id *addr, id obj) +{ + if (obj == nil) + { + *addr = nil; + return nil; + } + LOCK_FOR_SCOPE(&weakRefLock); + BOOL isGlobalObject = setObjectHasWeakRefs(obj); + if (isGlobalObject) + { + // If this is a global object, it's never deallocated, so secretly make + // this a strong reference. + *addr = obj; + return obj; + } + // If the object is being deallocated return nil. + if (object_getRetainCount_np(obj) == 0) + { + *addr = nil; + return nil; + } + if (nil != obj) + { + *(WeakRef**)addr = incrementWeakRefCount(obj); + } + return obj; +} diff --git a/src/associate.m b/src/associate.m new file mode 100644 index 0000000..79c9713 --- /dev/null +++ b/src/associate.m @@ -0,0 +1,469 @@ +#include +#include +#include +#include +#include "objc/runtime.h" +#include "objc/objc-arc.h" +#include "nsobject.h" +#include "spinlock.h" +#include "class.h" +#include "dtable.h" +#include "selector.h" +#include "lock.h" +#include "gc_ops.h" + +/** + * A single associative reference. Contains the key, value, and association + * policy. + */ +struct reference +{ + /** + * The key used for identifying this object. Opaque pointer, should be set + * to 0 when this slot is unused. + */ + const void *key; + /** + * The associated object. Note, if the policy is assign then this may be + * some other type of pointer... + */ + void *object; + /** + * Association policy. + */ + uintptr_t policy; +}; + +#define REFERENCE_LIST_SIZE 10 + +/** + * Linked list of references associated with an object. We assume that there + * won't be very many, so we don't bother with a proper hash table, and just + * iterate over a list. + */ +struct reference_list +{ + /** + * Next group of references. This is only ever used if we have more than + * 10 references associated with an object, which seems highly unlikely. + */ + struct reference_list *next; + /** + * Mutex. Only set for the first reference list in a chain. Used for + * @syncronize(). + */ + mutex_t lock; + /** + * Garbage collection type. This stores the location of all of the + * instance variables in the object that may contain pointers. + */ + void *gc_type; + /** + * Array of references. + */ + struct reference list[REFERENCE_LIST_SIZE]; +}; +enum +{ + OBJC_ASSOCIATION_ATOMIC = 0x300, +}; + +static BOOL isAtomic(uintptr_t policy) +{ + return (policy & OBJC_ASSOCIATION_ATOMIC) == OBJC_ASSOCIATION_ATOMIC; +} + +static struct reference* findReference(struct reference_list *list, const void *key) +{ + while (list) + { + for (int i=0 ; ilist[i].key == key) + { + return &list->list[i]; + } + } + list = list->next; + } + return NULL; +} +static void cleanupReferenceList(struct reference_list *list) +{ + if (NULL == list) { return; } + + cleanupReferenceList(list->next); + + for (int i=0 ; ilist[i]; + if (0 != r->key) + { + r->key = 0; + if (OBJC_ASSOCIATION_ASSIGN != r->policy) + { + // Full barrier - ensure that we've zero'd the key before doing + // this! + __sync_synchronize(); + objc_release(r->object); + } + r->object = 0; + r->policy = 0; + } + } +} + +static void freeReferenceList(struct reference_list *l) +{ + if (NULL == l) { return; } + freeReferenceList(l->next); + gc->free(l); +} + +static void setReference(struct reference_list *list, + const void *key, + void *obj, + uintptr_t policy) +{ + switch (policy) + { + // Ignore any unknown association policies + default: return; + case OBJC_ASSOCIATION_COPY_NONATOMIC: + case OBJC_ASSOCIATION_COPY: + obj = [(id)obj copy]; + break; + case OBJC_ASSOCIATION_RETAIN_NONATOMIC: + case OBJC_ASSOCIATION_RETAIN: + obj = objc_retain(obj); + case OBJC_ASSOCIATION_ASSIGN: + break; + } + // While inserting into the list, we need to lock it temporarily. + volatile int *lock = lock_for_pointer(list); + lock_spinlock(lock); + struct reference *r = findReference(list, key); + // If there's an existing reference, then we can update it, otherwise we + // have to install a new one + if (NULL == r) + { + // Search for an unused slot + r = findReference(list, 0); + if (NULL == r) + { + struct reference_list *l = list; + + while (NULL != l->next) { l = l->next; } + + l->next = gc->malloc(sizeof(struct reference_list)); + r = &l->next->list[0]; + } + r->key = key; + } + unlock_spinlock(lock); + // Now we only need to lock if the old or new property is atomic + BOOL needLock = isAtomic(r->policy) || isAtomic(policy); + if (needLock) + { + lock = lock_for_pointer(r); + lock_spinlock(lock); + } + @try + { + if (OBJC_ASSOCIATION_ASSIGN != r->policy) + { + objc_release(r->object); + } + } + @finally + { + r->policy = policy; + r->object = obj; + } + if (needLock) + { + unlock_spinlock(lock); + } +} + +static void deallocHiddenClass(id obj, SEL _cmd); + +static inline Class findHiddenClass(id obj) +{ + Class cls = obj->isa; + while (Nil != cls && + !objc_test_class_flag(cls, objc_class_flag_assoc_class)) + { + cls = class_getSuperclass(cls); + } + return cls; +} + +static Class allocateHiddenClass(Class superclass) +{ + Class newClass = + calloc(1, sizeof(struct objc_class) + sizeof(struct reference_list)); + + if (Nil == newClass) { return Nil; } + + // Set up the new class + newClass->isa = superclass->isa; + newClass->name = superclass->name; + // Uncomment this for debugging: it makes it easier to track which hidden + // class is which + // static int count; + //asprintf(&newClass->name, "%s%d", superclass->name, count++); + newClass->info = objc_class_flag_resolved | objc_class_flag_user_created | + objc_class_flag_hidden_class | objc_class_flag_assoc_class; + newClass->super_class = superclass; + newClass->dtable = uninstalled_dtable; + newClass->instance_size = superclass->instance_size; + + LOCK_RUNTIME_FOR_SCOPE(); + newClass->sibling_class = superclass->subclass_list; + superclass->subclass_list = newClass; + + return newClass; +} + +static inline Class initHiddenClassForObject(id obj) +{ + Class hiddenClass = allocateHiddenClass(obj->isa); + assert(!class_isMetaClass(obj->isa)); + static SEL cxx_destruct; + if (NULL == cxx_destruct) + { + cxx_destruct = sel_registerName(".cxx_destruct"); + } + const char *types = sizeof(void*) == 4 ? "v8@0:4" : "v16@0:8"; + class_addMethod(hiddenClass, cxx_destruct, + (IMP)deallocHiddenClass, types); + obj->isa = hiddenClass; + return hiddenClass; +} + +static void deallocHiddenClass(id obj, SEL _cmd) +{ + LOCK_RUNTIME_FOR_SCOPE(); + Class hiddenClass = findHiddenClass(obj); + // After calling [super dealloc], the object will no longer exist. + // Free the hidden class. + struct reference_list *list = object_getIndexedIvars(hiddenClass); + DESTROY_LOCK(&list->lock); + cleanupReferenceList(list); + freeReferenceList(list->next); + //fprintf(stderr, "Deallocating dtable %p\n", hiddenClass->dtable); + free_dtable(hiddenClass->dtable); + // We shouldn't have any subclasses left at this point + assert(hiddenClass->subclass_list == 0); + // Remove the class from the subclass list of its superclass + Class sub = hiddenClass->super_class->subclass_list; + if (sub == hiddenClass) + { + hiddenClass->super_class->subclass_list = hiddenClass->sibling_class; + } + else + { + while (sub != NULL) + { + if ((Class)sub->sibling_class == hiddenClass) + { + sub->sibling_class = hiddenClass->sibling_class; + break; + } + sub = sub->sibling_class; + } + } + obj->isa = hiddenClass->super_class; + // Free the introspection structures: + freeMethodLists(hiddenClass); + freeIvarLists(hiddenClass); + // Free the class + free(hiddenClass); +} + +static struct reference_list* referenceListForObject(id object, BOOL create) +{ + if (class_isMetaClass(object->isa)) + { + Class cls = (Class)object; + if ((NULL == cls->extra_data) && create) + { + volatile int *lock = lock_for_pointer(cls); + struct reference_list *list = gc->malloc(sizeof(struct reference_list)); + lock_spinlock(lock); + if (NULL == cls->extra_data) + { + INIT_LOCK(list->lock); + cls->extra_data = list; + unlock_spinlock(lock); + } + else + { + unlock_spinlock(lock); + gc->free(list); + } + } + return cls->extra_data; + } + Class hiddenClass = findHiddenClass(object); + if ((NULL == hiddenClass) && create) + { + volatile int *lock = lock_for_pointer(object); + lock_spinlock(lock); + hiddenClass = findHiddenClass(object); + if (NULL == hiddenClass) + { + hiddenClass = initHiddenClassForObject(object); + struct reference_list *list = object_getIndexedIvars(hiddenClass); + INIT_LOCK(list->lock); + } + unlock_spinlock(lock); + } + return hiddenClass ? object_getIndexedIvars(hiddenClass) : NULL; +} + +void objc_setAssociatedObject(id object, + const void *key, + id value, + objc_AssociationPolicy policy) +{ + if (isSmallObject(object)) { return; } + struct reference_list *list = referenceListForObject(object, YES); + setReference(list, key, value, policy); +} + +id objc_getAssociatedObject(id object, const void *key) +{ + if (isSmallObject(object)) { return nil; } + struct reference_list *list = referenceListForObject(object, NO); + if (NULL == list) { return nil; } + struct reference *r = findReference(list, key); + if (NULL != r) + { + return r->object; + } + if (class_isMetaClass(object->isa)) + { + return nil; + } + Class cls = object->isa; + while (Nil != cls) + { + while (Nil != cls && + !objc_test_class_flag(cls, objc_class_flag_assoc_class)) + { + cls = class_getSuperclass(cls); + } + if (Nil != cls) + { + struct reference_list *next_list = object_getIndexedIvars(cls); + if (list != next_list) + { + list = next_list; + struct reference *r = findReference(list, key); + if (NULL != r) + { + return r->object; + } + } + cls = class_getSuperclass(cls); + } + } + return nil; +} + + +void objc_removeAssociatedObjects(id object) +{ + if (isSmallObject(object)) { return; } + cleanupReferenceList(referenceListForObject(object, NO)); +} + +PRIVATE void *gc_typeForClass(Class cls) +{ + struct reference_list *list = referenceListForObject(cls, YES); + return list->gc_type; +} +PRIVATE void gc_setTypeForClass(Class cls, void *type) +{ + struct reference_list *list = referenceListForObject(cls, YES); + list->gc_type = type; +} + +OBJC_PUBLIC +int objc_sync_enter(id object) +{ + if ((object == 0) || isSmallObject(object)) { return 0; } + struct reference_list *list = referenceListForObject(object, YES); + LOCK(&list->lock); + return 0; +} + +OBJC_PUBLIC +int objc_sync_exit(id object) +{ + if ((object == 0) || isSmallObject(object)) { return 0; } + struct reference_list *list = referenceListForObject(object, NO); + if (NULL != list) + { + UNLOCK(&list->lock); + return 0; + } + return 1; +} + +static Class hiddenClassForObject(id object) +{ + if (isSmallObject(object)) { return nil; } + if (class_isMetaClass(object->isa)) + { + return object->isa; + } + Class hiddenClass = findHiddenClass(object); + if (NULL == hiddenClass) + { + volatile int *lock = lock_for_pointer(object); + lock_spinlock(lock); + hiddenClass = findHiddenClass(object); + if (NULL == hiddenClass) + { + hiddenClass = initHiddenClassForObject(object); + struct reference_list *list = object_getIndexedIvars(hiddenClass); + INIT_LOCK(list->lock); + } + unlock_spinlock(lock); + } + return hiddenClass; +} + +BOOL object_addMethod_np(id object, SEL name, IMP imp, const char *types) +{ + return class_addMethod(hiddenClassForObject(object), name, imp, types); +} + +IMP object_replaceMethod_np(id object, SEL name, IMP imp, const char *types) +{ + return class_replaceMethod(hiddenClassForObject(object), name, imp, types); +} +static char prototypeKey; + +id object_clone_np(id object) +{ + if (isSmallObject(object)) { return object; } + // Make sure that the prototype has a hidden class, so that methods added + // to it will appear in the clone. + referenceListForObject(object, YES); + id new = class_createInstance(object->isa, 0); + Class hiddenClass = initHiddenClassForObject(new); + struct reference_list *list = object_getIndexedIvars(hiddenClass); + INIT_LOCK(list->lock); + objc_setAssociatedObject(new, &prototypeKey, object, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return new; +} + +id object_getPrototype_np(id object) +{ + return objc_getAssociatedObject(object, &prototypeKey); +} diff --git a/src/block_to_imp.c b/src/block_to_imp.c new file mode 100644 index 0000000..e3aa00a --- /dev/null +++ b/src/block_to_imp.c @@ -0,0 +1,338 @@ +// On some platforms, we need _GNU_SOURCE to expose asprintf() +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#else +#include "safewindows.h" +#endif +#include "objc/runtime.h" +#include "objc/blocks_runtime.h" +#include "blocks_runtime.h" +#include "lock.h" +#include "visibility.h" +#include "asmconstants.h" // For PAGE_SIZE + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#if defined(_WIN32) && (defined(__arm__) || defined(__aarch64__)) + static inline void __clear_cache(void* start, void* end) { + FlushInstructionCache(GetCurrentProcess(), start, end - start); + } + #define clear_cache __clear_cache +#elif __has_builtin(__builtin___clear_cache) + #define clear_cache __builtin___clear_cache +#else + void __clear_cache(void* start, void* end); + #define clear_cache __clear_cache +#endif + + +/* QNX needs a special header for asprintf() */ +#ifdef __QNXNTO__ +#include +#endif + +#ifdef _WIN32 +#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 + +#ifndef PROT_READ +#define PROT_READ 0x4 +#endif + +#ifndef PROT_WRITE +#define PROT_WRITE 0x2 +#endif + +#ifndef PROT_EXEC +#define PROT_EXEC 0x1 +#endif + +static int mprotect(void *buffer, size_t len, int prot) +{ + DWORD oldProt = 0, newProt = PAGE_NOACCESS; + // Windows doesn't offer values that can be ORed together... + if ((prot & PROT_WRITE)) + { + // promote to readwrite as there's no writeonly protection constant + newProt = PAGE_READWRITE; + } + else if ((prot & PROT_READ)) + { + newProt = PAGE_READONLY; + } + + if ((prot & PROT_EXEC)) + { + switch (newProt) + { + case PAGE_NOACCESS: newProt = PAGE_EXECUTE; break; + case PAGE_READONLY: newProt = PAGE_EXECUTE_READ; break; + case PAGE_READWRITE: newProt = PAGE_EXECUTE_READWRITE; break; + } + } + + return 0 != VirtualProtect(buffer, len, newProt, &oldProt); +} +#else +# ifndef MAP_ANONYMOUS +# define MAP_ANONYMOUS MAP_ANON +# endif +#endif + +struct block_header +{ + void *block; + void(*fnptr)(void); + /** + * On 64-bit platforms, we have 16 bytes for instructions, which ought to + * be enough without padding. + * Note: If we add too much padding, then we waste space but have no other + * ill effects. If we get this too small, then the assert in + * `init_trampolines` will fire on library load. + * + * PowerPC: We need INSTR_CNT * INSTR_LEN = 7*4 = 28 bytes + * for instruction. sizeof(block_header) must be a divisor of + * PAGE_SIZE, so we need to pad block_header to 32 bytes. + * On PowerPC 64-bit where sizeof(void *) = 8 bytes, we + * add 16 bytes of padding. + */ +#if defined(__i386__) || (defined(__mips__) && !defined(__mips_n64)) || (defined(__powerpc__) && !defined(__powerpc64__)) + uint64_t padding[3]; +#elif defined(__mips__) || defined(__powerpc64__) + uint64_t padding[2]; +#elif defined(__arm__) + uint64_t padding; +#endif +}; + +#define HEADERS_PER_PAGE (PAGE_SIZE/sizeof(struct block_header)) + +/** + * Structure containing a two pages of block trampolines. Each trampoline + * loads its block and target method address from the corresponding + * block_header (one page before the start of the block structure). + */ +struct trampoline_buffers +{ + struct block_header headers[HEADERS_PER_PAGE]; + char rx_buffer[PAGE_SIZE]; +}; +_Static_assert(__builtin_offsetof(struct trampoline_buffers, rx_buffer) == PAGE_SIZE, + "Incorrect offset for read-execute buffer"); +_Static_assert(sizeof(struct trampoline_buffers) == 2*PAGE_SIZE, + "Incorrect size for trampoline buffers"); + +struct trampoline_set +{ + struct trampoline_buffers *buffers; + struct trampoline_set *next; + int first_free; +}; + +static mutex_t trampoline_lock; + +struct wx_buffer +{ + void *w; + void *x; +}; +extern char __objc_block_trampoline; +extern char __objc_block_trampoline_end; +extern char __objc_block_trampoline_sret; +extern char __objc_block_trampoline_end_sret; + +PRIVATE void init_trampolines(void) +{ + assert(&__objc_block_trampoline_end - &__objc_block_trampoline <= sizeof(struct block_header)); + assert(&__objc_block_trampoline_end_sret - &__objc_block_trampoline_sret <= sizeof(struct block_header)); + INIT_LOCK(trampoline_lock); +} + +static id invalid(id self, SEL _cmd) +{ + fprintf(stderr, "Invalid block method called for [%s %s]\n", + class_getName(object_getClass(self)), sel_getName(_cmd)); + return nil; +} + +static struct trampoline_set *alloc_trampolines(char *start, char *end) +{ + struct trampoline_set *metadata = calloc(1, sizeof(struct trampoline_set)); +#if _WIN32 + metadata->buffers = VirtualAlloc(NULL, sizeof(struct trampoline_buffers), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#else + metadata->buffers = mmap(NULL, sizeof(struct trampoline_buffers), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); +#endif + for (int i=0 ; ibuffers->headers[i].fnptr = (void(*)(void))invalid; + metadata->buffers->headers[i].block = &metadata->buffers->headers[i+1].block; + char *block = metadata->buffers->rx_buffer + (i * sizeof(struct block_header)); + memcpy(block, start, end-start); + } + metadata->buffers->headers[HEADERS_PER_PAGE-1].block = NULL; + mprotect(metadata->buffers->rx_buffer, PAGE_SIZE, PROT_READ | PROT_EXEC); + clear_cache(metadata->buffers->rx_buffer, &metadata->buffers->rx_buffer[PAGE_SIZE]); + + return metadata; +} + +static struct trampoline_set *sret_trampolines; +static struct trampoline_set *trampolines; + +IMP imp_implementationWithBlock(id block) +{ + struct Block_layout *b = (struct Block_layout *)block; + void *start; + void *end; + LOCK_FOR_SCOPE(&trampoline_lock); + struct trampoline_set **setptr; + + if ((b->flags & BLOCK_USE_SRET) == BLOCK_USE_SRET) + { + setptr = &sret_trampolines; + start = &__objc_block_trampoline_sret; + end = &__objc_block_trampoline_end_sret; + } + else + { + setptr = &trampolines; + start = &__objc_block_trampoline; + end = &__objc_block_trampoline_end; + } + size_t trampolineSize = end - start; + // If we don't have a trampoline intrinsic for this architecture, return a + // null IMP. + if (0 >= trampolineSize) { return 0; } + block = Block_copy(block); + // Allocate some trampolines if this is the first time that we need to do this. + if (*setptr == NULL) + { + *setptr = alloc_trampolines(start, end); + } + for (struct trampoline_set *set=*setptr ; set!=NULL ; set=set->next) + { + if (set->first_free != -1) + { + int i = set->first_free; + struct block_header *h = &set->buffers->headers[i]; + struct block_header *next = h->block; + set->first_free = next ? (next - set->buffers->headers) : -1; + assert(set->first_free < HEADERS_PER_PAGE); + assert(set->first_free >= -1); + h->fnptr = (void(*)(void))b->invoke; + h->block = b; + uintptr_t addr = (uintptr_t)&set->buffers->rx_buffer[i*sizeof(struct block_header)]; +#if (__ARM_ARCH_ISA_THUMB == 2) + // If the trampoline is Thumb-2 code, then we must set the low bit + // to 1 so that b[l]x instructions put the CPU in the correct mode. + addr |= 1; +#endif + return (IMP)addr; + } + } + UNREACHABLE("Failed to allocate block"); +} + +static int indexForIMP(IMP anIMP, struct trampoline_set **setptr) +{ + for (struct trampoline_set *set=*setptr ; set!=NULL ; set=set->next) + { + if (((char*)anIMP >= set->buffers->rx_buffer) && + ((char*)anIMP < &set->buffers->rx_buffer[PAGE_SIZE])) + { + *setptr = set; + ptrdiff_t offset = (char*)anIMP - set->buffers->rx_buffer; + return offset / sizeof(struct block_header); + } + } + return -1; +} + +id imp_getBlock(IMP anImp) +{ + LOCK_FOR_SCOPE(&trampoline_lock); + struct trampoline_set *set = trampolines; + int idx = indexForIMP(anImp, &set); + if (idx == -1) + { + set = sret_trampolines; + indexForIMP(anImp, &set); + } + if (idx == -1) + { + return NULL; + } + return set->buffers->headers[idx].block; +} + +BOOL imp_removeBlock(IMP anImp) +{ + LOCK_FOR_SCOPE(&trampoline_lock); + struct trampoline_set *set = trampolines; + int idx = indexForIMP(anImp, &set); + if (idx == -1) + { + set = sret_trampolines; + indexForIMP(anImp, &set); + } + if (idx == -1) + { + return NO; + } + struct block_header *h = &set->buffers->headers[idx]; + Block_release(h->block); + h->fnptr = (void(*)(void))invalid; + h->block = set->first_free == -1 ? NULL : &set->buffers->headers[set->first_free]; + set->first_free = h - set->buffers->headers; + return YES; +} + +PRIVATE size_t lengthOfTypeEncoding(const char *types); + +char *block_copyIMPTypeEncoding_np(id block) +{ + char *buffer = strdup(block_getType_np(block)); + if (NULL == buffer) { return NULL; } + char *replace = buffer; + // Skip the return type + replace += lengthOfTypeEncoding(replace); + while (isdigit(*replace)) { replace++; } + // The first argument type should be @? (block), and we need to transform + // it to @, so we have to delete the ?. Assert here because this isn't a + // block encoding at all if the first argument is not a block, and since we + // got it from block_getType_np(), this means something is badly wrong. + assert('@' == *replace); + replace++; + assert('?' == *replace); + // Use strlen(replace) not replace+1, because we want to copy the NULL + // terminator as well. + memmove(replace, replace+1, strlen(replace)); + // The next argument should be an object, and we want to replace it with a + // selector + while (isdigit(*replace)) { replace++; } + if ('@' != *replace) + { + free(buffer); + return NULL; + } + *replace = ':'; + return buffer; +} diff --git a/src/block_trampolines.S b/src/block_trampolines.S new file mode 100644 index 0000000..e943818 --- /dev/null +++ b/src/block_trampolines.S @@ -0,0 +1,232 @@ +#include "common.S" +#include "asmconstants.h" + +# +# This file defines some trampolines for calling blocks. A block function +# looks like this: +# +# retType blockFn(block*, ...) +# +# An IMP looks like this: +# +# retType imp(id, SEL,...) +# +# The trampoline must find the block pointer and then call the block function +# with the correct first argument, the self pointer moved to the second real +# argument (the first block argument) and the _cmd parameter excised + +.file "block_trampolines.S" + + +#if __x86_64 +//////////////////////////////////////////////////////////////////////////////// +// x86-64 trampoline +//////////////////////////////////////////////////////////////////////////////// +.macro trampoline arg0, arg1 + mov -0x1007(%rip), \arg1 # Load the block pointer into the second argument + xchg \arg1, \arg0 # Swap the first and second arguments + jmp *-0x1008(%rip) # Call the block function +.endm +// The Win64 and SysV x86-64 ABIs use different registers +# ifdef _WIN64 +# define ARG0 %rcx +# define ARG1 %rdx +# define SARG1 %r8 +# else +# define ARG0 %rdi +# define ARG1 %rsi +# define SARG1 %rdx +# endif +# define SARG0 ARG1 + +#elif __i386 +//////////////////////////////////////////////////////////////////////////////// +// x86-32 trampoline +//////////////////////////////////////////////////////////////////////////////// + +#ifdef _WIN32 +// Mark this compilation unit as SEH-safe +.text +.def @feat.00; +.scl 3; +.type 0; +.endef +.globl @feat.00 +.set @feat.00, 1 +.data +#endif + +.macro trampoline arg0, arg1 + call 1f # Store the instruction pointer on the stack +1: + pop %eax # Load the old instruction pointer + mov \arg0(%esp), %ebx # Load the self parameter + mov %ebx, \arg1(%esp) # Store self as the second argument + mov -0x1005(%eax), %ebx # Load the block pointer to %ebx + mov %ebx, \arg0(%esp) # Store the block pointer in the first argument + jmp *-0x1001(%eax) # Call the block function +.endm +// All arguments on i386 are passed on the stack. These values are stack +// offsets - on other platforms they're register values. +# define ARG0 4 +# define ARG1 8 +# define SARG0 8 +# define SARG1 12 + +#elif __mips__ +//////////////////////////////////////////////////////////////////////////////// +// MIPS trampoline +//////////////////////////////////////////////////////////////////////////////// +# ifdef _ABI64 +.macro trampoline arg0, arg1 + move \arg1, \arg0 + ld \arg0, -4096($25) + ld $25, -4088($25) + jr $25 +.endm +# else +// 32-bit variant. This ought to work with both n32 and o32, because they both +// use 32-bit pointers and both use the same registers for the first four +// arguments (and we only care about the first three). +.macro trampoline arg0, arg1 + move \arg1, \arg0 + lw \arg0, -4096($25) + lw $25, -4092($25) + jr $25 +.endm +# endif +#define ARG0 $a0 +#define ARG1 $a1 +#define ARG2 $a2 + +#elif defined(__powerpc__) +//////////////////////////////////////////////////////////////////////////////// +// PowerPC trampoline +//////////////////////////////////////////////////////////////////////////////// + +#if defined(__powerpc64__) +#define LOAD ld +#define OFFSET 8 +#else +#define LOAD lwz +#define OFFSET 4 +#endif + +.macro trampoline arg0, arg1 + mfctr %r12 # The block trampoline is always called + # via a function pointer. We can thus + # assume that ctr contains the trampline + # entry point address from the previous + # branch to this trampoline (bctrl). + + #if PAGE_SHIFT < 16 + addi %r12, %r12, -PAGE_SIZE # Substract page size from entry point + #else + addis %r12, %r12, (-0x1 << (PAGE_SHIFT - 16)) + #endif + + mr \arg1, \arg0 + LOAD \arg0, 0(%r12) + LOAD %r12, OFFSET(%r12) + mtctr %r12 # Move block function pointer into ctr + bctr # Branch to block function +.endm + +#define ARG0 %r3 +#define ARG1 %r4 +#define ARG2 %r5 +#define SARG0 ARG1 +#define SARG1 ARG2 + +#elif defined(__riscv) && (__riscv_xlen == 64) +//////////////////////////////////////////////////////////////////////////////// +// RISC-V trampoline +//////////////////////////////////////////////////////////////////////////////// +.macro trampoline arg0, arg1 + auipc t6, 0xFFFFF // pc + -0x1000 + mv \arg1, \arg0 + ld \arg0, 0(t6) + ld t6, 8(t6) + jr t6 +.endm + +#define ARG0 a0 +#define ARG1 a1 +#define ARG2 a2 +#define SARG0 ARG1 +#define SARG1 ARG2 + +#elif defined(__ARM_ARCH_ISA_A64) +//////////////////////////////////////////////////////////////////////////////// +// AArch64 (ARM64) trampoline +//////////////////////////////////////////////////////////////////////////////// +.macro trampoline arg0, arg1 + adr x17, #-4096 + mov \arg1, \arg0 + ldp \arg0, x17, [x17] + br x17 +.endm +#define ARG0 x0 +#define ARG1 x1 +#define SARG0 x0 +#define SARG1 x1 + +#elif __arm__ +//////////////////////////////////////////////////////////////////////////////// +// AArch32 (ARM) trampoline +//////////////////////////////////////////////////////////////////////////////// + +# if (__ARM_ARCH_ISA_THUMB == 2) +// If we're on a target that supports Thumb 2, then we need slightly more +// instructions to support Thumb/ARM code for the IMP and so we need to make +// the trampolines thumb to be able to fit them in 16 bytes (they fit exactly +// when assembled as Thumb-2). +.thumb +.macro trampoline arg0, arg1 + sub r12, pc, #4095 + mov \arg1, \arg0 // Move self over _cmd + ldr \arg0, [r12, #-5] // Load the block pointer over self + ldr r12, [r12, #-1] // Jump to the block function + bx r12 +.endm +# else +.macro trampoline arg0, arg1 + sub r12, pc, #4096 + mov \arg1, \arg0 // Move self over _cmd + ldr \arg0, [r12, #-8] // Load the block pointer over self + ldr pc, [r12, #-4] // Jump to the block function +.endm +# endif // (__ARM_ARCH_ISA_THUMB == 2) +#define ARG0 r0 +#define ARG1 r1 +#define SARG0 r1 +#define SARG1 r2 + +#else + +#warning imp_implementationWithBlock() not implemented for your architecture +.macro trampoline arg0, arg1 +.endm +#define ARG0 0 +#define ARG1 0 +#define SARG0 0 +#define SARG1 0 + +#endif + + +.globl CDECL(__objc_block_trampoline) +CDECL(__objc_block_trampoline): + trampoline ARG0, ARG1 +.globl CDECL(__objc_block_trampoline_end) +CDECL(__objc_block_trampoline_end): +.globl CDECL(__objc_block_trampoline_sret) +CDECL(__objc_block_trampoline_sret): + trampoline SARG0, SARG1 +.globl CDECL(__objc_block_trampoline_end_sret) +CDECL(__objc_block_trampoline_end_sret): + + +#ifdef __ELF__ +.section .note.GNU-stack,"",%progbits +#endif diff --git a/src/blocks_runtime.m b/src/blocks_runtime.m new file mode 100644 index 0000000..91ab85d --- /dev/null +++ b/src/blocks_runtime.m @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2009 Remy Demarest + * Portions Copyright (c) 2009 David Chisnall + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#import "objc/blocks_runtime.h" +#include "objc/blocks_private.h" +#import "objc/runtime.h" +#import "objc/objc-arc.h" +#include "blocks_runtime.h" +#include "gc_ops.h" +#include "visibility.h" +#include +#include +#include +#include +#include + + +static void *_HeapBlockByRef = (void*)1; + + +OBJC_PUBLIC bool _Block_has_signature(void *b) +{ + const struct Block_layout *block = (struct Block_layout*)b; + return ((NULL != block) && (block->flags & BLOCK_HAS_SIGNATURE)); +} +/** + * Returns the Objective-C type encoding for the block. + */ +OBJC_PUBLIC const char * _Block_signature(void *b) +{ + const struct Block_layout *block = (struct Block_layout*)b; + if ((NULL == block) || !(block->flags & BLOCK_HAS_SIGNATURE)) + { + return NULL; + } + if (!(block->flags & BLOCK_HAS_COPY_DISPOSE)) + { + return ((struct Block_descriptor_basic*)block->descriptor)->encoding; + } + return block->descriptor->encoding; +} + +static int increment24(int *ref) +{ + int old = *ref; + int val = old & BLOCK_REFCOUNT_MASK; + if (val == BLOCK_REFCOUNT_MASK) + { + return val; + } + assert(val < BLOCK_REFCOUNT_MASK); + if (!__sync_bool_compare_and_swap(ref, old, old+1)) + { + return increment24(ref); + } + return val + 1; +} + +static int decrement24(int *ref) +{ + int old = *ref; + int val = old & BLOCK_REFCOUNT_MASK; + if (val == BLOCK_REFCOUNT_MASK) + { + return val; + } + assert(val > 0); + if (!__sync_bool_compare_and_swap(ref, old, old-1)) + { + return decrement24(ref); + } + return val - 1; +} + +// This is a really ugly hack that works around a buggy register allocator in +// GCC. Compiling nontrivial code using __sync_bool_compare_and_swap() with +// GCC (4.2.1, at least), causes the register allocator to run out of registers +// and fall over and die. We work around this by wrapping this CAS in a +// function, which means the register allocator can trivially handle it. Do +// not remove the noinline attribute - without it, gcc will inline it early on +// and then crash later. +#ifndef __clang__ +__attribute__((noinline)) +static int cas(void *ptr, void *old, void *new) +{ + return __sync_bool_compare_and_swap((void**)ptr, old, new); +} +#define __sync_bool_compare_and_swap cas +#endif + +/* Certain field types require runtime assistance when being copied to the + * heap. The following function is used to copy fields of types: blocks, + * pointers to byref structures, and objects (including + * __attribute__((NSObject)) pointers. BLOCK_FIELD_IS_WEAK is orthogonal to + * the other choices which are mutually exclusive. Only in a Block copy helper + * will one see BLOCK_FIELD_IS_BYREF. + */ +OBJC_PUBLIC void _Block_object_assign(void *destAddr, const void *object, const int flags) +{ + //printf("Copying %x to %x with flags %x\n", object, destAddr, flags); + // FIXME: Needs to be implemented + //if(flags & BLOCK_FIELD_IS_WEAK) + { + } + //else + { + if (IS_SET(flags, BLOCK_FIELD_IS_BYREF)) + { + struct block_byref_obj *src = (struct block_byref_obj *)object; + struct block_byref_obj **dst = destAddr; + src = src->forwarding; + + if ((src->flags & BLOCK_REFCOUNT_MASK) == 0) + { + *dst = gc->malloc(src->size); + memcpy(*dst, src, src->size); + (*dst)->isa = _HeapBlockByRef; + // Refcount must be two; one for the copy and one for the + // on-stack version that will point to it. + (*dst)->flags += 2; + if (IS_SET(src->flags, BLOCK_HAS_COPY_DISPOSE)) + { + src->byref_keep(*dst, src); + } + (*dst)->forwarding = *dst; + // Concurrency. If we try copying the same byref structure + // from two threads simultaneously, we could end up with two + // versions on the heap that are unaware of each other. That + // would be bad. So we first set up the copy, then try to do + // an atomic compare-and-exchange to point the old version at + // it. If the forwarding pointer in src has changed, then we + // recover - clean up and then return the structure that the + // other thread created. + if (!__sync_bool_compare_and_swap(&src->forwarding, src, *dst)) + { + if((size_t)src->size >= sizeof(struct block_byref_obj)) + { + src->byref_dispose(*dst); + } + gc->free(*dst); + *dst = src->forwarding; + } + } + else + { + *dst = (struct block_byref_obj*)src; + increment24(&(*dst)->flags); + } + } + else if (IS_SET(flags, BLOCK_FIELD_IS_BLOCK)) + { + struct Block_layout *src = (struct Block_layout*)object; + struct Block_layout **dst = destAddr; + + *dst = Block_copy(src); + } + else if (IS_SET(flags, BLOCK_FIELD_IS_OBJECT) && + !IS_SET(flags, BLOCK_BYREF_CALLER)) + { + id src = (id)object; + void **dst = destAddr; + *dst = src; + *dst = objc_retain(src); + } + } +} + +/* Similarly a compiler generated dispose helper needs to call back for each + * field of the byref data structure. (Currently the implementation only packs + * one field into the byref structure but in principle there could be more). + * The same flags used in the copy helper should be used for each call + * generated to this function: + */ +OBJC_PUBLIC void _Block_object_dispose(const void *object, const int flags) +{ + // FIXME: Needs to be implemented + //if(flags & BLOCK_FIELD_IS_WEAK) + { + } + //else + { + if (IS_SET(flags, BLOCK_FIELD_IS_BYREF)) + { + struct block_byref_obj *src = + (struct block_byref_obj*)object; + src = src->forwarding; + if (src->isa == _HeapBlockByRef) + { + int refcount = (src->flags & BLOCK_REFCOUNT_MASK) == 0 ? 0 : decrement24(&src->flags); + if (refcount == 0) + { + if(IS_SET(src->flags, BLOCK_HAS_COPY_DISPOSE) && (0 != src->byref_dispose)) + { + src->byref_dispose(src); + } + gc->free(src); + } + } + } + else if (IS_SET(flags, BLOCK_FIELD_IS_BLOCK)) + { + struct Block_layout *src = (struct Block_layout*)object; + Block_release(src); + } + else if (IS_SET(flags, BLOCK_FIELD_IS_OBJECT) && + !IS_SET(flags, BLOCK_BYREF_CALLER)) + { + id src = (id)object; + objc_release(src); + } + } +} + + +// Copy a block to the heap if it's still on the stack or increments its retain count. +OBJC_PUBLIC void *_Block_copy(const void *src) +{ + if (NULL == src) { return NULL; } + struct Block_layout *self = (struct Block_layout*)src; + struct Block_layout *ret = self; + + extern void _NSConcreteStackBlock; + extern void _NSConcreteMallocBlock; + + // If the block is Global, there's no need to copy it on the heap. + if(self->isa == &_NSConcreteStackBlock) + { + ret = gc->malloc(self->descriptor->size); + memcpy(ret, self, self->descriptor->size); + ret->isa = &_NSConcreteMallocBlock; + if(self->flags & BLOCK_HAS_COPY_DISPOSE) + { + self->descriptor->copy_helper(ret, self); + } + // We don't need any atomic operations here, because on-stack blocks + // can not be aliased across threads (unless you've done something + // badly wrong). + ret->reserved = 1; + } + else if (self->isa == &_NSConcreteMallocBlock) + { + // We need an atomic increment for malloc'd blocks, because they may be + // shared. + __sync_fetch_and_add(&ret->reserved, 1); + } + return ret; +} + +// Release a block and frees the memory when the retain count hits zero. +OBJC_PUBLIC void _Block_release(const void *src) +{ + if (NULL == src) { return; } + struct Block_layout *self = (struct Block_layout*)src; + + extern void _NSConcreteStackBlock; + extern void _NSConcreteMallocBlock; + + if (&_NSConcreteStackBlock == self->isa) + { + fprintf(stderr, "Block_release called upon a stack Block: %p, ignored\n", self); + } + else if (&_NSConcreteMallocBlock == self->isa) + { + if (__sync_sub_and_fetch(&self->reserved, 1) == 0) + { + if(self->flags & BLOCK_HAS_COPY_DISPOSE) + self->descriptor->dispose_helper(self); + objc_delete_weak_refs((id)self); + gc->free(self); + } + } +} + +OBJC_PUBLIC bool _Block_isDeallocating(const void* arg) +{ + struct Block_layout *block = (struct Block_layout*)arg; + int *refCountPtr = &((struct Block_layout*)arg)->reserved; + int refCount = __sync_fetch_and_add(refCountPtr, 0); + return refCount == 0; +} + +OBJC_PUBLIC bool _Block_tryRetain(const void* arg) +{ + /* This is used by the weak reference management in ARC. The implementation + * follows the reasoning of `retain_fast()` in arc.mm: We want to abandon the + * retain operation if another thread has started deallocating the object between + * loading the weak pointer and executing the retain operation. + */ + struct Block_layout *block = (struct Block_layout*)arg; + int *refCountPtr = &block->reserved; + int refCountVal = __sync_fetch_and_add(refCountPtr, 0); + int newVal = refCountVal; + do { + refCountVal = newVal; + if (refCountVal <= 0) + { + return false; + } + newVal = __sync_val_compare_and_swap(refCountPtr, refCountVal, newVal + 1); + } while (newVal != refCountVal); + return true; +} \ No newline at end of file diff --git a/src/blocks_runtime_np.m b/src/blocks_runtime_np.m new file mode 100644 index 0000000..86a97c5 --- /dev/null +++ b/src/blocks_runtime_np.m @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2009 Remy Demarest + * Portions Copyright (c) 2009 David Chisnall + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifdef EMBEDDED_BLOCKS_RUNTIME +#import "objc/blocks_runtime.h" +#include "blocks_runtime.h" +#else +#import +#import +#endif +#include "visibility.h" + + +OBJC_PUBLIC const char *block_getType_np(const void *b) +{ + return _Block_signature((void*)b); +} + +/** + * Returns the block pointer, or NULL if the block is already + * being deallocated. The implementation does not employ atomic + * operations, so this function must only be called by the ARC + * subsystem after obtaining the weak-reference lock. + */ +PRIVATE void* block_load_weak(void *block) +{ + struct Block_layout *self = block; + #ifdef EMBEDDED_BLOCKS_RUNTIME + return (self->reserved) > 0 ? block : 0; + #else + return (self->flags) & BLOCK_REFCOUNT_MASK ? block : 0; + #endif +} diff --git a/src/caps.c b/src/caps.c new file mode 100644 index 0000000..96a6c1b --- /dev/null +++ b/src/caps.c @@ -0,0 +1,42 @@ +#include "objc/capabilities.h" +#include + +/** + * Bitmask of all of the capabilities compiled into this version of the + * runtime. + */ +static const int32_t caps = + (1<= 32) { return 0; } + if (caps & (1< +#include "objc/runtime.h" +#include "visibility.h" +#include "loader.h" +#include "dtable.h" +#include "properties.h" + +#define BUFFER_TYPE struct objc_category * +#include "buffer.h" + +void objc_send_load_message(Class class); + +static void register_methods(struct objc_class *cls, struct objc_method_list *l) +{ + if (NULL == l) { return; } + + // Add the method list at the head of the list of lists. + l->next = cls->methods; + cls->methods = l; + // Update the dtable to catch the new methods, if the dtable has been + // created (don't bother creating dtables for classes when categories are + // loaded if the class hasn't received any messages yet. + if (classHasDtable(cls)) + { + add_method_list_to_class(cls, l); + } +} + +static void load_category(struct objc_category *cat, struct objc_class *class) +{ + register_methods(class, cat->instance_methods); + register_methods(class->isa, cat->class_methods); + //fprintf(stderr, "Loading %s (%s)\n", cat->class_name, cat->name); + + if (cat->protocols) + { + objc_init_protocols(cat->protocols); + cat->protocols->next = class->protocols; + class->protocols = cat->protocols; + } + if (cat->properties) + { + cat->properties->next = class->properties; + class->properties = cat->properties; + } + if (cat->class_properties) + { + cat->class_properties->next = class->isa->properties; + class->isa->properties = cat->class_properties; + } +} + +static BOOL try_load_category(struct objc_category *cat) +{ + Class class = (Class)objc_getClass(cat->class_name); + //fprintf(stderr, "Trying to load %s (%s)\n", cat->class_name, cat->name); + if (Nil != class) + { + load_category(cat, class); + return YES; + } + //fprintf(stderr, "waiting to load %s (%s)\n", cat->class_name, cat->name); + return NO; +} + +/** + * Attaches a category to its class, if the class is already loaded. Buffers + * it for future resolution if not. + */ +PRIVATE void objc_try_load_category(struct objc_category *cat) +{ + if (!try_load_category(cat)) + { + set_buffered_object_at_index(cat, buffered_objects++); + } +} + +PRIVATE void objc_load_buffered_categories(void) +{ + BOOL shouldReshuffle = NO; + + for (unsigned i=0 ; i +#include + +void objc_init_protocols(struct objc_protocol_list *protos); +void objc_compute_ivar_offsets(Class class); + +//////////////////////////////////////////////////////////////////////////////// +// +load method hash table +//////////////////////////////////////////////////////////////////////////////// +static int imp_compare(const void *i1, void *i2) +{ + return i1 == i2; +} +static int32_t imp_hash(const void *imp) +{ + return (int32_t)(((uintptr_t)imp) >> 4); +} +#define MAP_TABLE_NAME load_messages +#define MAP_TABLE_COMPARE_FUNCTION imp_compare +#define MAP_TABLE_HASH_KEY imp_hash +#define MAP_TABLE_HASH_VALUE imp_hash +#include "hash_table.h" + +static load_messages_table *load_table; + +SEL loadSel; + +PRIVATE void objc_init_load_messages_table(void) +{ + load_messages_initialize(&load_table, 4096); + loadSel = sel_registerName("load"); +} + +PRIVATE void objc_send_load_message(Class class) +{ + Class meta = class->isa; + for (struct objc_method_list *l=meta->methods ; NULL!=l ; l=l->next) + { + for (int i=0 ; icount ; i++) + { + Method m = method_at_index(l, i); + if (sel_isEqual(m->selector, loadSel)) + { + if (load_messages_table_get(load_table, m->imp) == 0) + { + m->imp((id)class, loadSel); + load_messages_insert(load_table, m->imp); + } + } + } + } +} + +// Get the functions for string hashing +#include "string_hash.h" + +static int class_compare(const char *name, const Class class) +{ + return string_compare(name, class->name); +} +static int class_hash(const Class class) +{ + return string_hash(class->name); +} +#define MAP_TABLE_NAME class_table_internal +#define MAP_TABLE_COMPARE_FUNCTION class_compare +#define MAP_TABLE_HASH_KEY string_hash +#define MAP_TABLE_HASH_VALUE class_hash +// This defines the maximum number of classes that the runtime supports. +/* +#define MAP_TABLE_STATIC_SIZE 2048 +#define MAP_TABLE_STATIC_NAME class_table +*/ +#include "hash_table.h" + +static class_table_internal_table *class_table; + + +#define unresolved_class_next subclass_list +#define unresolved_class_prev sibling_class +/** + * Linked list using the subclass_list pointer in unresolved classes. + */ +static Class unresolved_class_list; + +static enum objc_developer_mode_np mode; + +void objc_setDeveloperMode_np(enum objc_developer_mode_np newMode) +{ + mode = newMode; +} + +//////////////////////////////////////////////////////////////////////////////// +// Class table manipulation +//////////////////////////////////////////////////////////////////////////////// + +PRIVATE Class zombie_class; + +PRIVATE void class_table_insert(Class class) +{ + if (!objc_test_class_flag(class, objc_class_flag_resolved)) + { + if (Nil != unresolved_class_list) + { + unresolved_class_list->unresolved_class_prev = class; + } + class->unresolved_class_next = unresolved_class_list; + unresolved_class_list = class; + } + if ((0 == zombie_class) && (strcmp("NSZombie", class->name) == 0)) + { + zombie_class = class; + } + class_table_internal_insert(class_table, class); +} + +PRIVATE Class class_table_get_safe(const char *class_name) +{ + if (NULL == class_name) { return Nil; } + return class_table_internal_table_get(class_table, class_name); +} + +PRIVATE Class class_table_next(void **e) +{ + return class_table_internal_next(class_table, + (struct class_table_internal_table_enumerator**)e); +} + +PRIVATE void init_class_tables(void) +{ + class_table_internal_initialize(&class_table, 4096); + objc_init_load_messages_table(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Loader functions +//////////////////////////////////////////////////////////////////////////////// + +PRIVATE BOOL objc_resolve_class(Class cls) +{ + // Skip this if the class is already resolved. + if (objc_test_class_flag(cls, objc_class_flag_resolved)) { return YES; } + + // We can only resolve the class if its superclass is resolved. + if (cls->super_class) + { + Class super = cls->super_class; + + if (!objc_test_class_flag(super, objc_class_flag_resolved)) + { + if (!objc_resolve_class(super)) + { + return NO; + } + } + } +#ifdef OLDABI_COMPAT + else + { + struct objc_class_gsv1 *ocls = objc_legacy_class_for_class(cls); + if (ocls != NULL) + { + const char *super_name = (const char*)ocls->super_class; + if (super_name) + { + Class super = (Class)objc_getClass(super_name); + if (super == Nil) + { + return NO; + } + cls->super_class = super; + return objc_resolve_class(cls); + } + } + } +#endif + + + // Remove the class from the unresolved class list + if (Nil == cls->unresolved_class_prev) + { + unresolved_class_list = cls->unresolved_class_next; + } + else + { + cls->unresolved_class_prev->unresolved_class_next = + cls->unresolved_class_next; + } + if (Nil != cls->unresolved_class_next) + { + cls->unresolved_class_next->unresolved_class_prev = + cls->unresolved_class_prev; + } + cls->unresolved_class_prev = Nil; + cls->unresolved_class_next = Nil; + + // The superclass for the metaclass. This is the metaclass for the + // superclass if one exists, otherwise it is the root class itself + Class superMeta = Nil; + // The metaclass for the metaclass. This is always the root class's + // metaclass. + Class metaMeta = Nil; + + // Resolve the superclass pointer + + if (NULL == cls->super_class) + { + superMeta = cls; + metaMeta = cls->isa; + } + else + { + // Resolve the superclass if it isn't already resolved + Class super = cls->super_class; + if (!objc_test_class_flag(super, objc_class_flag_resolved)) + { + objc_resolve_class(super); + } + superMeta = super->isa; + // Set the superclass pointer for the class and the superclass + do + { + metaMeta = super->isa; + super = super->super_class; + } while (Nil != super); + } + Class meta = cls->isa; + + // Make the root class the superclass of the metaclass (e.g. NSObject is + // the superclass of all metaclasses in classes that inherit from NSObject) + meta->super_class = superMeta; + meta->isa = metaMeta; + + // Don't register root classes as children of anything + if (Nil != cls->super_class) + { + // Set up the class links + cls->sibling_class = cls->super_class->subclass_list; + cls->super_class->subclass_list = cls; + } + // Set up the metaclass links + meta->sibling_class = superMeta->subclass_list; + superMeta->subclass_list = meta; + + // Mark this class (and its metaclass) as resolved + objc_set_class_flag(cls, objc_class_flag_resolved); + objc_set_class_flag(cls->isa, objc_class_flag_resolved); + + + // Fix up the ivar offsets + objc_compute_ivar_offsets(cls); +#ifdef OLDABI_COMPAT + struct objc_class_gsv1 *oldCls = objc_legacy_class_for_class(cls); + if (oldCls) + { + oldCls->super_class = cls->super_class; + oldCls->isa->super_class = cls->isa->super_class; + } +#endif + // Send the +load message, if required + if (!objc_test_class_flag(cls, objc_class_flag_user_created)) + { + objc_send_load_message(cls); + } + if (_objc_load_callback) + { + _objc_load_callback(cls, 0); + } + return YES; +} + +PRIVATE void objc_resolve_class_links(void) +{ + LOCK_RUNTIME_FOR_SCOPE(); + BOOL resolvedClass; + do + { + Class class = unresolved_class_list; + resolvedClass = NO; + while ((Nil != class)) + { + Class next = class->unresolved_class_next; + // If the class has been resolved, then this means that the last + // call to objc_resolve_class resolved it as part of resolving + // superclasses and removed it from the list. We now don't have a + // pointer into the linked list, so abort and try again from the + // start. + if (objc_test_class_flag(class, objc_class_flag_resolved)) + { + assert(resolvedClass); + break; + } + objc_resolve_class(class); + if (resolvedClass || + objc_test_class_flag(class, objc_class_flag_resolved)) + { + resolvedClass = YES; + } + class = next; + } + } while (resolvedClass); +} +PRIVATE void __objc_resolve_class_links(void) +{ + static BOOL warned = NO; + if (!warned) + { + fprintf(stderr, + "Warning: Calling deprecated private ObjC runtime function %s\n", __func__); + warned = YES; + } + objc_resolve_class_links(); +} + +static void reload_class(struct objc_class *class, struct objc_class *old) +{ + const char *superclassName = (char*)class->super_class; + class->super_class = class_table_get_safe(superclassName); + // Checking the instance sizes are equal here is a quick-and-dirty test. + // It's not actually needed, because we're testing the ivars are at the + // same locations next, but it lets us skip those tests if the total size + // is different. + BOOL equalLayouts = (class->super_class == old->super_class) && + (class->instance_size == old->instance_size); + // If either of the classes has an empty ivar list, then the other one must too. + if ((NULL == class->ivars) || (NULL == old->ivars)) + { + equalLayouts &= (class->ivars == old->ivars); + } + else + { + // If the class sizes are the same, ensure that the ivars have the same + // types, names, and offsets. Note: Renaming an ivar is treated as a + // conflict because name changes are often accompanied by semantic + // changes. For example, an object ivar at offset 16 goes from being + // called 'delegate' to being called 'view' - we almost certainly don't + // want methods that expect to be working with the delegate ivar to + // work with the view ivar now! + for (int i=0 ; equalLayouts && (iivars->count) ; i++) + { + struct objc_ivar *oldIvar = ivar_at_index(old->ivars, i); + struct objc_ivar *newIvar = ivar_at_index(class->ivars, i); + equalLayouts &= strcmp(oldIvar->name, newIvar->name) == 0; + equalLayouts &= strcmp(oldIvar->type, newIvar->type) == 0; + equalLayouts &= (oldIvar->offset == newIvar->offset); + } + } + + // If the layouts are equal, then we can simply tack the class's method + // list on to the front of the old class and update the dtable. + if (equalLayouts) + { + class->methods->next = old->methods; + old->methods = class->methods; + objc_update_dtable_for_class(old); + return; + } + + // If we get to here, then we are adding a new class. This is where things + // start to get a bit tricky... + + // Ideally, we'd want to capture the subclass list here. Unfortunately, + // this is not possible because the subclass will contain methods that + // refer to ivars in the superclass. + // + // We can't use the non-fragile ABI's offset facility easily, because we'd + // have to have two (or more) offsets for the same ivar. This gets messy + // very quickly. Ideally, we'd want every class to include ivar offsets + // for every single (public) ivar in its superclasses. These could then be + // updated by copies of the class. Defining a development ABI is something + // to consider for a future release. + class->subclass_list = NULL; + + // Replace the old class with this one in the class table. New lookups for + // this class will now return this class. + class_table_internal_table_set(class_table, (void*)class->name, class); + + // Set the uninstalled dtable. The compiler could do this as well. + class->dtable = uninstalled_dtable; + class->isa->dtable = uninstalled_dtable; + + // If this is a root class, make the class into the metaclass's superclass. + // This means that all instance methods will be available to the class. + if (NULL == superclassName) + { + class->isa->super_class = class; + } + + if (class->protocols) + { + objc_init_protocols(class->protocols); + } +} + +/** + * Loads a class. This function assumes that the runtime mutex is locked. + */ +PRIVATE void objc_load_class(struct objc_class *class) +{ + struct objc_class *existingClass = class_table_get_safe(class->name); + if (Nil != existingClass) + { + if (objc_developer_mode_developer != mode) + { + fprintf(stderr, + "Loading two versions of %s. The class that will be used is undefined\n", + class->name); + return; + } + reload_class(class, existingClass); + return; + } + +#ifdef _WIN32 + // On Windows, the super_class pointer may point to the local __imp_ + // symbol, rather than to the external symbol. The runtime must remove the + // extra indirection. + if (class->super_class) + { + Class superMeta = class->super_class->isa; + if (!class_isMetaClass(superMeta)) + { + class->super_class = superMeta; + } + } +#endif + + // Work around a bug in some versions of GCC that don't initialize the + // class structure correctly. + class->subclass_list = NULL; + + // Insert the class into the class table + class_table_insert(class); + + // Set the uninstalled dtable. The compiler could do this as well. + class->dtable = uninstalled_dtable; + class->isa->dtable = uninstalled_dtable; + + // Mark constant string instances as never needing refcount manipulation. + if (strcmp(class->name, "NSConstantString") == 0) + { + objc_set_class_flag(class, objc_class_flag_permanent_instances); + } + + // If this is a root class, make the class into the metaclass's superclass. + // This means that all instance methods will be available to the class. + if (NULL == class->super_class) + { + class->isa->super_class = class; + } + + if (class->protocols) + { + objc_init_protocols(class->protocols); + } +} + +PRIVATE Class SmallObjectClasses[7]; + +BOOL objc_registerSmallObjectClass_np(Class class, uintptr_t mask) +{ + if ((mask & OBJC_SMALL_OBJECT_MASK) != mask) + { + return NO; + } + if (sizeof(void*) == 4) + { + if (Nil == SmallObjectClasses[0]) + { + SmallObjectClasses[0] = class; + return YES; + } + return NO; + } + if (Nil != SmallObjectClasses[mask]) + { + return NO; + } + SmallObjectClasses[mask] = class; + return YES; +} + +PRIVATE void class_table_remove(Class cls) +{ + assert(objc_test_class_flag(cls, objc_class_flag_user_created)); + class_table_internal_remove(class_table, (void*)cls->name); +} + + +//////////////////////////////////////////////////////////////////////////////// +// Public API +//////////////////////////////////////////////////////////////////////////////// + +int objc_getClassList(Class *buffer, int bufferLen) +{ + if (buffer == NULL || bufferLen == 0) + { + return class_table->table_used; + } + int count = 0; + struct class_table_internal_table_enumerator *e = NULL; + Class next; + while (count < bufferLen && + (next = class_table_internal_next(class_table, &e))) + { + buffer[count++] = next; + } + return count; +} +Class *objc_copyClassList(unsigned int *outCount) +{ + int count = class_table->table_used; + Class *buffer = calloc(sizeof(Class), count); + if (NULL != outCount) + { + *outCount = count; + } + objc_getClassList(buffer, count); + return buffer; +} + +Class class_getSuperclass(Class cls) +{ + if (Nil == cls) { return Nil; } + if (!objc_test_class_flag(cls, objc_class_flag_resolved)) + { + objc_resolve_class(cls); + } + return cls->super_class; +} + + +id objc_getClass(const char *name) +{ + id class = (id)class_table_get_safe(name); + + if (nil != class) { return class; } + + // Second chance lookup via @compatibilty_alias: + class = (id)alias_getClass(name); + if (nil != class) { return class; } + + // Third chance lookup via the hook: + if (0 != _objc_lookup_class) + { + class = (id)_objc_lookup_class(name); + } + + return class; +} + +id objc_lookUpClass(const char *name) +{ + return (id)class_table_get_safe(name); +} + + +id objc_getMetaClass(const char *name) +{ + Class cls = (Class)objc_getClass(name); + return cls == Nil ? nil : (id)cls->isa; +} + +// Legacy interface compatibility + +id objc_get_class(const char *name) +{ + return objc_getClass(name); +} + +id objc_lookup_class(const char *name) +{ + return objc_getClass(name); +} + +id objc_get_meta_class(const char *name) +{ + return objc_getMetaClass(name); +} + +Class objc_next_class(void **enum_state) +{ + return class_table_next ( enum_state); +} + +Class class_pose_as(Class impostor, Class super_class) +{ + fprintf(stderr, "Class posing is no longer supported.\n"); + fprintf(stderr, "Please use class_replaceMethod() instead.\n"); + abort(); +} diff --git a/src/common.S b/src/common.S new file mode 100644 index 0000000..182d967 --- /dev/null +++ b/src/common.S @@ -0,0 +1,18 @@ +#if ((defined(_WIN32) || defined(__CYGWIN__)) && defined(__i386__)) || defined(__APPLE__) +#define CDECL(symbol) _##symbol +#else +#define CDECL(symbol) symbol +#endif + +#if __ELF__ +#define TYPE_DIRECTIVE(symbol, symboltype) .type symbol, symboltype +#else +#define TYPE_DIRECTIVE(symbol, symboltype) +#endif + +#if defined(_MSC_VER) && defined(__i386__) +#define STRINGIFY(a) #a +#define EXPORT_SYMBOL(symbol) .ascii " " STRINGIFY(/EXPORT:_##symbol) +#else +#define EXPORT_SYMBOL(symbol) .ascii " /EXPORT:" #symbol +#endif diff --git a/src/dtable.c b/src/dtable.c new file mode 100644 index 0000000..a6f1641 --- /dev/null +++ b/src/dtable.c @@ -0,0 +1,861 @@ +#define __BSD_VISIBLE 1 +#include +#include +#include +#include +#include "objc/runtime.h" +#include "objc/hooks.h" +#include "sarray2.h" +#include "selector.h" +#include "class.h" +#include "lock.h" +#include "method.h" +#include "dtable.h" +#include "visibility.h" +#include "asmconstants.h" + +_Static_assert(__builtin_offsetof(struct objc_class, dtable) == DTABLE_OFFSET, + "Incorrect dtable offset for assembly"); +_Static_assert(__builtin_offsetof(SparseArray, shift) == SHIFT_OFFSET, + "Incorrect shift offset for assembly"); +_Static_assert(__builtin_offsetof(SparseArray, data) == DATA_OFFSET, + "Incorrect data offset for assembly"); +// Slots are now a public interface to part of the method structure, so make +// sure that it's safe to use method and slot structures interchangeably. +_Static_assert(__builtin_offsetof(struct objc_slot2, method) == SLOT_OFFSET, + "Incorrect slot offset for assembly"); +_Static_assert(__builtin_offsetof(struct objc_method, imp) == SLOT_OFFSET, + "Incorrect slot offset for assembly"); + +PRIVATE dtable_t uninstalled_dtable; +#if defined(WITH_TRACING) && defined (__x86_64) +PRIVATE dtable_t tracing_dtable; +#endif +#ifndef ENOTSUP +# define ENOTSUP -1 +#endif + +/** Head of the list of temporary dtables. Protected by initialize_lock. */ +PRIVATE InitializingDtable *temporary_dtables; +/** Lock used to protect the temporary dtables list. */ +PRIVATE mutex_t initialize_lock; +/** The size of the largest dtable. This is a sparse array shift value, so is + * 2^x in increments of 8. */ +static uint32_t dtable_depth = 8; + +#ifndef NO_SAFE_CACHING +_Atomic(uint64_t) objc_method_cache_version; +#endif + +/** + * Starting at `cls`, finds the class that provides the implementation of the + * method identified by `sel`. + */ +static Class ownerForMethod(Class cls, SEL sel) +{ + struct objc_slot2 *slot = objc_get_slot2(cls, sel, NULL); + if (slot == NULL) + { + return Nil; + } + if (cls->super_class == NULL) + { + return cls; + } + if (objc_get_slot2(cls->super_class, sel, NULL) == slot) + { + return ownerForMethod(cls->super_class, sel); + } + return cls; +} + +/** + * Returns YES if the class implements a method for the specified selector, NO + * otherwise. + */ +static BOOL ownsMethod(Class cls, SEL sel) +{ + return ownerForMethod(cls, sel) == cls; +} + + +#ifdef DEBUG_ARC_COMPAT +#define ARC_DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +#else +#define ARC_DEBUG_LOG(...) do {} while(0) +#endif + +/** + * Check whether this class pair implement or override `+alloc`, + * `+allocWithZone`, or `-init` in a way that requires the methods to be + * called. + */ +static void checkFastAllocInit(Class cls) +{ + // This needs to be called on the class, not the metaclass + if (class_isMetaClass(cls)) + { + return; + } + static SEL alloc, allocWithZone, init, isTrivialAllocInit; + if (NULL == alloc) + { + alloc = sel_registerName("alloc"); + allocWithZone = sel_registerName("allocWithZone:"); + init = sel_registerName("init"); + isTrivialAllocInit = sel_registerName("_TrivialAllocInit"); + } + Class metaclass = cls->isa; + Class isTrivialOwner = ownerForMethod(metaclass, isTrivialAllocInit); + // If nothing in this hierarchy opts in to trivial alloc / init behaviour, give up. + if (isTrivialOwner == nil) + { + objc_clear_class_flag(cls, objc_class_flag_fast_alloc_init); + objc_clear_class_flag(metaclass, objc_class_flag_fast_alloc_init); + return; + } + // Check for overrides of alloc or allocWithZone:. + // This check has some false negatives. If you override only one of alloc + // or allocWithZone, both will hit the slow path. That's fine because the + // fast path is an optimisation, not a guarantee. + Class allocOwner = ownerForMethod(metaclass, alloc); + Class allocWithZoneOwner = ownerForMethod(metaclass, allocWithZone); + if (((allocOwner == nil) || (allocOwner == isTrivialOwner)) && + ((allocWithZoneOwner == nil) || (allocWithZoneOwner == isTrivialOwner))) + { + objc_set_class_flag(metaclass, objc_class_flag_fast_alloc_init); + } + else + { + objc_clear_class_flag(metaclass, objc_class_flag_fast_alloc_init); + } + Class initOwner = ownerForMethod(cls, init); + if ((initOwner == nil) || (initOwner->isa == isTrivialOwner)) + { + objc_set_class_flag(cls, objc_class_flag_fast_alloc_init); + } + else + { + objc_clear_class_flag(cls, objc_class_flag_fast_alloc_init); + } +} + +/** + * Checks whether the class implements memory management methods, and whether + * they are safe to use with ARC. + */ +static void checkARCAccessors(Class cls) +{ + checkFastAllocInit(cls); + static SEL retain, release, autorelease, isARC; + if (NULL == retain) + { + retain = sel_registerName("retain"); + release = sel_registerName("release"); + autorelease = sel_registerName("autorelease"); + isARC = sel_registerName("_ARCCompliantRetainRelease"); + } + Class owner = ownerForMethod(cls, retain); + if ((NULL != owner) && !ownsMethod(owner, isARC)) + { + ARC_DEBUG_LOG("%s does not support ARC correctly (implements retain)\n", cls->name); + objc_clear_class_flag(cls, objc_class_flag_fast_arc); + return; + } + owner = ownerForMethod(cls, release); + if ((NULL != owner) && !ownsMethod(owner, isARC)) + { + ARC_DEBUG_LOG("%s does not support ARC correctly (implements release)\n", cls->name); + objc_clear_class_flag(cls, objc_class_flag_fast_arc); + return; + } + owner = ownerForMethod(cls, autorelease); + if ((NULL != owner) && !ownsMethod(owner, isARC)) + { + ARC_DEBUG_LOG("%s does not support ARC correctly (implements autorelease)\n", cls->name); + objc_clear_class_flag(cls, objc_class_flag_fast_arc); + return; + } + objc_set_class_flag(cls, objc_class_flag_fast_arc); +} + +static BOOL selEqualUnTyped(SEL expected, SEL untyped) +{ + return (expected->index == untyped->index) +#ifdef TYPE_DEPENDENT_DISPATCH + || (get_untyped_idx(expected) == untyped->index) +#endif + ; +} + +PRIVATE void checkARCAccessorsSlow(Class cls) +{ + if (cls->dtable != uninstalled_dtable) + { + return; + } + static SEL retain, release, autorelease, isARC; + if (NULL == retain) + { + retain = sel_registerName("retain"); + release = sel_registerName("release"); + autorelease = sel_registerName("autorelease"); + isARC = sel_registerName("_ARCCompliantRetainRelease"); + } + BOOL superIsFast = YES; + if (cls->super_class != Nil) + { + checkARCAccessorsSlow(cls->super_class); + superIsFast = objc_test_class_flag(cls->super_class, objc_class_flag_fast_arc); + } + BOOL selfImplementsRetainRelease = NO; + for (struct objc_method_list *l=cls->methods ; l != NULL ; l= l->next) + { + for (int i=0 ; icount ; i++) + { + SEL s = method_at_index(l, i)->selector; + if (selEqualUnTyped(s, retain) || + selEqualUnTyped(s, release) || + selEqualUnTyped(s, autorelease)) + { + selfImplementsRetainRelease = YES; + } + else if (selEqualUnTyped(s, isARC)) + { + objc_set_class_flag(cls, objc_class_flag_fast_arc); + return; + } + } + } + if (superIsFast && !selfImplementsRetainRelease) + { + objc_set_class_flag(cls, objc_class_flag_fast_arc); + } +} + +static void collectMethodsForMethodListToSparseArray( + struct objc_method_list *list, + SparseArray *sarray, + BOOL recurse) +{ + if (recurse && (NULL != list->next)) + { + collectMethodsForMethodListToSparseArray(list->next, sarray, YES); + } + for (unsigned i=0 ; icount ; i++) + { + SparseArrayInsert(sarray, method_at_index(list, i)->selector->index, + (void*)method_at_index(list, i)); + } +} + + +PRIVATE void init_dispatch_tables () +{ + INIT_LOCK(initialize_lock); + uninstalled_dtable = SparseArrayNewWithDepth(dtable_depth); +#if defined(WITH_TRACING) && defined (__x86_64) + tracing_dtable = SparseArrayNewWithDepth(dtable_depth); +#endif +} + +#if defined(WITH_TRACING) && defined (__x86_64) +static int init; + +static void free_thread_stack(void* x) +{ + free(*(void**)x); +} +static pthread_key_t thread_stack_key; +static void alloc_thread_stack(void) +{ + pthread_key_create(&thread_stack_key, free_thread_stack); + init = 1; +} + +PRIVATE void* pushTraceReturnStack(void) +{ + static pthread_once_t once_control = PTHREAD_ONCE_INIT; + if (!init) + { + pthread_once(&once_control, alloc_thread_stack); + } + void **stack = pthread_getspecific(thread_stack_key); + if (stack == 0) + { + stack = malloc(4096*sizeof(void*)); + } + pthread_setspecific(thread_stack_key, stack + 5); + return stack; +} + +PRIVATE void* popTraceReturnStack(void) +{ + void **stack = pthread_getspecific(thread_stack_key); + stack -= 5; + pthread_setspecific(thread_stack_key, stack); + return stack; +} +#endif + +int objc_registerTracingHook(SEL aSel, objc_tracing_hook aHook) +{ +#if defined(WITH_TRACING) && defined (__x86_64) + // If this is an untyped selector, register it for every typed variant + if (sel_getType_np(aSel) == 0) + { + SEL buffer[16]; + SEL *overflow = 0; + int count = sel_copyTypedSelectors_np(sel_getName(aSel), buffer, 16); + if (count > 16) + { + overflow = calloc(count, sizeof(SEL)); + sel_copyTypedSelectors_np(sel_getName(aSel), buffer, 16); + for (int i=0 ; iindex, aHook); + } + free(overflow); + } + else + { + for (int i=0 ; iindex, aHook); + } + } + } + SparseArrayInsert(tracing_dtable, aSel->index, aHook); + return 0; +#else + return ENOTSUP; +#endif +} + +/** + * Installs a new method in the dtable for `class`. If `replaceMethod` is + * `YES` then this will replace any dtable entry where the original is + * `method_to_replace`. This is used when a superclass method is replaced, to + * replace all subclass dtable entries that are inherited, but not ones that + * are overridden. + */ +static BOOL installMethodInDtable(Class class, + SparseArray *dtable, + struct objc_method *method, + struct objc_method *method_to_replace, + BOOL replaceExisting) +{ + ASSERT(uninstalled_dtable != dtable); + uint32_t sel_id = method->selector->index; + struct objc_method *oldMethod = SparseArrayLookup(dtable, sel_id); + // If we're being asked to replace an existing method, don't if it's the + // wrong one. + if ((replaceExisting) && (method_to_replace != oldMethod)) + { + return NO; + } + // If we're not being asked to replace existing methods and there is an + // existing one, don't replace it. + if (!replaceExisting && (oldMethod != NULL)) + { + return NO; + } + // If this method is the one already installed, pretend to install it again. + if (NULL != oldMethod && (oldMethod->imp == method->imp)) + { + return NO; + } + SparseArrayInsert(dtable, sel_id, method); + // In TDD mode, we also register the first typed method that we + // encounter as the untyped version. +#ifdef TYPE_DEPENDENT_DISPATCH + uint32_t untyped_idx = get_untyped_idx(method->selector); + SparseArrayInsert(dtable, untyped_idx, method); +#endif + + static SEL cxx_construct, cxx_destruct; + if (NULL == cxx_construct) + { + cxx_construct = sel_registerName(".cxx_construct"); + cxx_destruct = sel_registerName(".cxx_destruct"); + } + if (selEqualUnTyped(method->selector, cxx_construct)) + { + class->cxx_construct = method->imp; + } + else if (selEqualUnTyped(method->selector, cxx_destruct)) + { + class->cxx_destruct = method->imp; + } + + for (struct objc_class *subclass=class->subclass_list ; + Nil != subclass ; subclass = subclass->sibling_class) + { + // Don't bother updating dtables for subclasses that haven't been + // initialized yet + if (!classHasDtable(subclass)) { continue; } + + // Recursively install this method in all subclasses + installMethodInDtable(subclass, + dtable_for_class(subclass), + method, + oldMethod, + YES); + } + + // Invalidate the old slot, if there is one. + if (NULL != oldMethod) + { +#ifndef NO_SAFE_CACHING + objc_method_cache_version++; +#endif + } + return YES; +} + +static void installMethodsInClass(Class cls, + SparseArray *methods_to_replace, + SparseArray *methods, + BOOL replaceExisting) +{ + SparseArray *dtable = dtable_for_class(cls); + assert(uninstalled_dtable != dtable); + + uint32_t idx = 0; + struct objc_method *m; + while ((m = SparseArrayNext(methods, &idx))) + { + struct objc_method *method_to_replace = methods_to_replace + ? SparseArrayLookup(methods_to_replace, m->selector->index) + : NULL; + if (!installMethodInDtable(cls, dtable, m, method_to_replace, replaceExisting)) + { + // Remove this method from the list, if it wasn't actually installed + SparseArrayInsert(methods, idx, 0); + } + } +} + +Class class_getSuperclass(Class); + +PRIVATE void objc_update_dtable_for_class(Class cls) +{ + // Only update real dtables + if (!classHasDtable(cls)) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + + SparseArray *methods = SparseArrayNewWithDepth(dtable_depth); + collectMethodsForMethodListToSparseArray((void*)cls->methods, methods, YES); + SparseArray *super_dtable = cls->super_class ? dtable_for_class(cls->super_class) + : NULL; + installMethodsInClass(cls, super_dtable, methods, YES); + SparseArrayDestroy(methods); + checkARCAccessors(cls); +} + +static void rebaseDtableRecursive(Class cls, Class newSuper) +{ + dtable_t parentDtable = dtable_for_class(newSuper); + // Collect all of the methods for this class: + dtable_t temporaryDtable = SparseArrayNewWithDepth(dtable_depth); + + for (struct objc_method_list *list = cls->methods ; list != NULL ; list = list->next) + { + for (unsigned i=0 ; icount ; i++) + { + struct objc_method *m = method_at_index(list, i); + uint32_t idx = m->selector->index; + // Don't replace existing methods - we're doing the traversal + // pre-order so we'll see methods from categories first. + if (SparseArrayLookup(temporaryDtable, idx) == NULL) + { + SparseArrayInsert(temporaryDtable, idx, m); + } + } + } + + + dtable_t dtable = dtable_for_class(cls); + uint32_t idx = 0; + struct objc_method *method; + // Install all methods from the parent that aren't overridden here. + while ((method = SparseArrayNext(parentDtable, &idx))) + { + if (SparseArrayLookup(temporaryDtable, idx) == NULL) + { + SparseArrayInsert(dtable, idx, method); + SparseArrayInsert(temporaryDtable, idx, method); + } + } + idx = 0; + // Now look at all of the methods in the dtable. If they're not ones from + // the dtable that we've just created, then they must have come from the + // original superclass, so remove them by replacing them with NULL. + while ((method = SparseArrayNext(dtable, &idx))) + { + if (SparseArrayLookup(temporaryDtable, idx) == NULL) + { + SparseArrayInsert(dtable, idx, NULL); + } + } + SparseArrayDestroy(temporaryDtable); + + // merge can make a class ARC-compatible. + checkARCAccessors(cls); + + // Now visit all of our subclasses and propagate the changes downwards. + for (struct objc_class *subclass=cls->subclass_list ; + Nil != subclass ; subclass = subclass->sibling_class) + { + // Don't bother updating dtables for subclasses that haven't been + // initialized yet + if (!classHasDtable(subclass)) { continue; } + rebaseDtableRecursive(subclass, cls); + } + +} + +PRIVATE void objc_update_dtable_for_new_superclass(Class cls, Class newSuper) +{ + // Only update real dtables + if (!classHasDtable(cls)) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + rebaseDtableRecursive(cls, newSuper); + // Invalidate all caches after this operation. +#ifndef NO_SAFE_CACHING + objc_method_cache_version++; +#endif + + return; +} + +PRIVATE void add_method_list_to_class(Class cls, + struct objc_method_list *list) +{ + // Only update real dtables + if (!classHasDtable(cls)) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + + SparseArray *methods = SparseArrayNewWithDepth(dtable_depth); + SparseArray *super_dtable = cls->super_class ? dtable_for_class(cls->super_class) + : NULL; + collectMethodsForMethodListToSparseArray(list, methods, NO); + installMethodsInClass(cls, super_dtable, methods, YES); + // Methods now contains only the new methods for this class. + SparseArrayDestroy(methods); + checkARCAccessors(cls); +} + +PRIVATE dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) +{ + // Don't create a dtable for a class that already has one + if (classHasDtable(class)) { return dtable_for_class(class); } + + LOCK_RUNTIME_FOR_SCOPE(); + + // Make sure that another thread didn't create the dtable while we were + // waiting on the lock. + if (classHasDtable(class)) { return dtable_for_class(class); } + + Class super = class_getSuperclass(class); + dtable_t dtable; + dtable_t super_dtable = NULL; + + if (Nil == super) + { + dtable = SparseArrayNewWithDepth(dtable_depth); + } + else + { + super_dtable = dtable_for_class(super); + if (super_dtable == uninstalled_dtable) + { + if (super->isa == class) + { + super_dtable = root_dtable; + } + else + { + abort(); + } + } + dtable = SparseArrayCopy(super_dtable); + } + + // When constructing the initial dtable for a class, we iterate along the + // method list in forward-traversal order. The first method that we + // encounter is always the one that we want to keep, so we instruct + // installMethodInDtable() to replace only methods that are inherited from + // the superclass. + struct objc_method_list *list = (void*)class->methods; + + while (NULL != list) + { + for (unsigned i=0 ; icount ; i++) + { + struct objc_method *super_method = super_dtable + ? SparseArrayLookup(super_dtable, method_at_index(list, i)->selector->index) + : NULL; + installMethodInDtable(class, dtable, method_at_index(list, i), super_method, YES); + } + list = list->next; + } + + return dtable; +} + + +Class class_table_next(void **e); + +PRIVATE void objc_resize_dtables(uint32_t newSize) +{ + // If dtables already have enough space to store all registered selectors, do nothing + if (1< newSize) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + + if (1< newSize) { return; } + + dtable_depth += 8; + + uint32_t oldShift = uninstalled_dtable->shift; + dtable_t old_uninstalled_dtable = uninstalled_dtable; + + uninstalled_dtable = SparseArrayExpandingArray(uninstalled_dtable, dtable_depth); +#if defined(WITH_TRACING) && defined (__x86_64) + tracing_dtable = SparseArrayExpandingArray(tracing_dtable, dtable_depth); +#endif + { + LOCK_FOR_SCOPE(&initialize_lock); + for (InitializingDtable *buffer = temporary_dtables ; NULL != buffer ; buffer = buffer->next) + { + buffer->dtable = SparseArrayExpandingArray(buffer->dtable, dtable_depth); + } + } + // Resize all existing dtables + void *e = NULL; + struct objc_class *next; + while ((next = class_table_next(&e))) + { + if (next->dtable == old_uninstalled_dtable) + { + next->dtable = uninstalled_dtable; + next->isa->dtable = uninstalled_dtable; + continue; + } + if (NULL != next->dtable && + ((SparseArray*)next->dtable)->shift == oldShift) + { + next->dtable = SparseArrayExpandingArray((void*)next->dtable, dtable_depth); + next->isa->dtable = SparseArrayExpandingArray((void*)next->isa->dtable, dtable_depth); + } + } +} + +PRIVATE dtable_t objc_copy_dtable_for_class(dtable_t old, Class cls) +{ + return SparseArrayCopy(old); +} + +PRIVATE void free_dtable(dtable_t dtable) +{ + SparseArrayDestroy(dtable); +} + +LEGACY void update_dispatch_table_for_class(Class cls) +{ + static BOOL warned = NO; + if (!warned) + { + fprintf(stderr, + "Warning: Calling deprecated private ObjC runtime function %s\n", __func__); + warned = YES; + } + objc_update_dtable_for_class(cls); +} + +void objc_resolve_class(Class); + +__attribute__((unused)) static void objc_release_object_lock(id *x) +{ + objc_sync_exit(*x); +} +/** + * Macro that is equivalent to @synchronize, for use in C code. + */ +#define LOCK_OBJECT_FOR_SCOPE(obj) \ + __attribute__((cleanup(objc_release_object_lock)))\ + __attribute__((unused)) id lock_object_pointer = obj;\ + objc_sync_enter(obj); + +/** + * Remove a buffer from an entry in the initializing dtables list. This is + * called as a cleanup to ensure that it runs even if +initialize throws an + * exception. + */ +static void remove_dtable(InitializingDtable* meta_buffer) +{ + LOCK(&initialize_lock); + InitializingDtable *buffer = meta_buffer->next; + // Install the dtable: + meta_buffer->class->dtable = meta_buffer->dtable; + buffer->class->dtable = buffer->dtable; + // Remove the look-aside buffer entry. + if (temporary_dtables == meta_buffer) + { + temporary_dtables = buffer->next; + } + else + { + InitializingDtable *prev = temporary_dtables; + while (prev->next->class != meta_buffer->class) + { + prev = prev->next; + } + prev->next = buffer->next; + } + UNLOCK(&initialize_lock); +} + +/** + * Send a +initialize message to the receiver, if required. + */ +OBJC_PUBLIC void objc_send_initialize(id object) +{ + Class class = classForObject(object); + // If the first message is sent to an instance (weird, but possible and + // likely for things like NSConstantString, make sure +initialize goes to + // the class not the metaclass. + if (objc_test_class_flag(class, objc_class_flag_meta)) + { + class = (Class)object; + } + Class meta = class->isa; + + + // Make sure that the class is resolved. + objc_resolve_class(class); + + // Make sure that the superclass is initialized first. + if (Nil != class->super_class) + { + objc_send_initialize((id)class->super_class); + } + + // Lock the runtime while we're creating dtables and before we acquire the + // init lock. This prevents a lock-order reversal when dtable_for_class is + // called from something holding the runtime lock while we're still holding + // the initialize lock. We should ensure that we never acquire the runtime + // lock after acquiring the initialize lock. + LOCK_RUNTIME(); + + // Superclass +initialize might possibly send a message to this class, in + // which case this method would be called again. See NSObject and + // NSAutoreleasePool +initialize interaction in GNUstep. + if (objc_test_class_flag(class, objc_class_flag_initialized)) + { + // We know that initialization has started because the flag is set. + // Check that it's finished by grabbing the class lock. This will be + // released once the class has been fully initialized. The runtime + // lock needs to be released first to prevent a deadlock between the + // runtime lock and the class-specific lock. + UNLOCK_RUNTIME(); + + objc_sync_enter((id)meta); + objc_sync_exit((id)meta); + assert(dtable_for_class(class) != uninstalled_dtable); + return; + } + + // We should try to acquire the class lock before any runtime/init locks. + // If another thread is in the middle of running `allocateHiddenClass()` it + // has acquired a spinlock and will be trying to acquire the runtime lock. + // When this happens there is a small chance we could hit the same spinlock + // and deadlock the process (as any further attempts to acquire the runtime + // will also block forever). + UNLOCK_RUNTIME(); + + LOCK_OBJECT_FOR_SCOPE((id)meta); + LOCK_RUNTIME(); + LOCK(&initialize_lock); + if (objc_test_class_flag(class, objc_class_flag_initialized)) + { + UNLOCK(&initialize_lock); + UNLOCK_RUNTIME(); + return; + } + BOOL skipMeta = objc_test_class_flag(meta, objc_class_flag_initialized); + // Mark metaclasses as never needing refcount manipulation for their + // instances (classes). + if (!skipMeta) + { + objc_set_class_flag(meta, objc_class_flag_permanent_instances); + } + + // Set the initialized flag on both this class and its metaclass, to make + // sure that +initialize is only ever sent once. + objc_set_class_flag(class, objc_class_flag_initialized); + objc_set_class_flag(meta, objc_class_flag_initialized); + + dtable_t class_dtable = create_dtable_for_class(class, uninstalled_dtable); + dtable_t dtable = skipMeta ? 0 : create_dtable_for_class(meta, class_dtable); + // Now we've finished doing things that may acquire the runtime lock, so we + // can hold onto the initialise lock to make anything doing + // dtable_for_class block until we've finished updating temporary dtable + // lists. + // If another thread holds the runtime lock, it can now proceed until it + // gets into a dtable_for_class call, and then block there waiting for us + // to finish setting up the temporary dtable. + UNLOCK_RUNTIME(); + + static SEL initializeSel = 0; + if (0 == initializeSel) + { + initializeSel = sel_registerName("initialize"); + } + + struct objc_method *initializeSlot = skipMeta ? 0 : + objc_dtable_lookup(dtable, initializeSel->index); + + // If there's no initialize method, then don't bother installing and + // removing the initialize dtable, just install both dtables correctly now + if (0 == initializeSlot) + { + if (!skipMeta) + { + meta->dtable = dtable; + } + class->dtable = class_dtable; + checkARCAccessors(class); + UNLOCK(&initialize_lock); + return; + } + + + + // Create an entry in the dtable look-aside buffer for this. When sending + // a message to this class in future, the lookup function will check this + // buffer if the receiver's dtable is not installed, and block if + // attempting to send a message to this class. + InitializingDtable buffer = { class, class_dtable, temporary_dtables }; + __attribute__((cleanup(remove_dtable))) + InitializingDtable meta_buffer = { meta, dtable, &buffer }; + temporary_dtables = &meta_buffer; + // We now release the initialize lock. We'll reacquire it later when we do + // the cleanup, but at this point we allow other threads to get the + // temporary dtable and call +initialize in other threads. + UNLOCK(&initialize_lock); + // We still hold the class lock at this point. dtable_for_class will block + // there after acquiring the temporary dtable. + + checkARCAccessors(class); + + // Store the buffer in the temporary dtables list. Note that it is safe to + // insert it into a global list, even though it's a temporary variable, + // because we will clean it up after this function. + initializeSlot->imp((id)class, initializeSel); +} + diff --git a/src/eh_personality.c b/src/eh_personality.c new file mode 100644 index 0000000..d638d97 --- /dev/null +++ b/src/eh_personality.c @@ -0,0 +1,772 @@ +#include +#include +#include +#include "dwarf_eh.h" +#include "objc/runtime.h" +#include "objc/hooks.h" +#include "objc/objc-exception.h" +#include "class.h" +#include "objcxx_eh.h" + +#ifndef DEBUG_EXCEPTIONS +#define DEBUG_LOG(...) +#else +#define DEBUG_LOG(str, ...) fprintf(stderr, str, ## __VA_ARGS__) +#endif + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif +#if !__has_builtin(__builtin_unreachable) +#define __builtin_unreachable abort +#endif + +void test_cxx_eh_implementation(); +/** + * The Itanium C++ public structure for in-flight exception status. + */ +struct __cxa_eh_globals +{ + /** + * The head exception object. By convention, this is actually the end of + * the `__cxa_exception` structure and points to the address of the thrown + * object. This is either an `id*` or a pointer to a C++ type that we're + * not going to look at. + */ + struct __cxa_exception *caughtExceptions; + /** + * The number of in-flight exceptions thrown. + */ + unsigned int uncaughtExceptions; +}; + + +// Weak references to C++ runtime functions. We don't bother testing that +// these are 0 before calling them, because if they are not resolved then we +// should not be in a code path that involves a C++ exception. +__attribute__((weak)) void *__cxa_begin_catch(void *e); +__attribute__((weak)) void __cxa_end_catch(void); +__attribute__((weak)) void __cxa_rethrow(void); +__attribute__((weak)) struct __cxa_eh_globals *__cxa_get_globals(void); + + +/** + * Class of exceptions to distinguish between this and other exception types. + */ +static const uint64_t objc_exception_class = EXCEPTION_CLASS('G','N','U','C','O','B','J','C'); + +/** + * Structure used as a header on thrown exceptions. + */ +struct objc_exception +{ + /** The selector value to be returned when installing the catch handler. + * Used at the call site to determine which catch() block should execute. + * This is found in phase 1 of unwinding then installed in phase 2.*/ + int handlerSwitchValue; + /** The cached landing pad for the catch handler.*/ + void *landingPad; + /** + * Next pointer for chained exceptions. + */ + struct objc_exception *next; + /** + * The number of nested catches that may hold this exception. This is + * negative while an exception is being rethrown. + */ + int catch_count; + /** The language-agnostic part of the exception header. */ + struct _Unwind_Exception unwindHeader; + /** Thrown object. This is after the unwind header so that the C++ + * exception handler can catch this as a foreign exception. */ + id object; + /** C++ exception structure. Used for mixed exceptions. When we are in + * Objective-C++ code, we create this structure for passing to the C++ + * exception personality function. It will then handle installing + * exceptions for us. */ + struct _Unwind_Exception *cxx_exception; +}; + +struct objc_exception *objc_exception_from_header(struct _Unwind_Exception *ex) +{ + return (struct objc_exception*)((char*)ex - + offsetof(struct objc_exception, unwindHeader)); +} + +typedef enum +{ + handler_none, + handler_cleanup, + handler_catchall_id, + handler_catchall, + handler_class +} handler_type; + +enum exception_type +{ + NONE, + CXX, + OBJC, + FOREIGN, + BOXED_FOREIGN +}; +struct thread_data +{ + enum exception_type current_exception_type; + BOOL cxxCaughtException; + struct objc_exception *caughtExceptions; +}; + +static __thread struct thread_data thread_data; + +static struct thread_data *get_thread_data(void) +{ + return &thread_data; +} + +static struct thread_data *get_thread_data_fast(void) +{ + return &thread_data; +} + + +/** + * Saves the result of the landing pad that we have found. For ARM, this is + * stored in the generic unwind structure, while on other platforms it is + * stored in the Objective-C exception. + */ +static void saveLandingPad(struct _Unwind_Context *context, + struct _Unwind_Exception *ucb, + struct objc_exception *ex, + int selector, + dw_eh_ptr_t landingPad) +{ +#if defined(__arm__) && !defined(__ARM_DWARF_EH__) + // On ARM, we store the saved exception in the generic part of the structure + ucb->barrier_cache.sp = _Unwind_GetGR(context, 13); + ucb->barrier_cache.bitpattern[1] = (uint32_t)selector; + ucb->barrier_cache.bitpattern[3] = (uint32_t)landingPad; +#else + // Cache the results for the phase 2 unwind, if we found a handler + // and this is not a foreign exception. We can't cache foreign exceptions + // because we don't know their structure (although we could cache C++ + // exceptions...) + if (ex) + { + ex->handlerSwitchValue = selector; + ex->landingPad = landingPad; + } +#endif +} + +/** + * Loads the saved landing pad. Returns 1 on success, 0 on failure. + */ +static int loadLandingPad(struct _Unwind_Context *context, + struct _Unwind_Exception *ucb, + struct objc_exception *ex, + unsigned long *selector, + dw_eh_ptr_t *landingPad) +{ +#if defined(__arm__) && !defined(__ARM_DWARF_EH__) + *selector = ucb->barrier_cache.bitpattern[1]; + *landingPad = (dw_eh_ptr_t)ucb->barrier_cache.bitpattern[3]; + return 1; +#else + if (ex) + { + *selector = ex->handlerSwitchValue; + *landingPad = ex->landingPad; + return 0; + } + return 0; +#endif +} + +static inline _Unwind_Reason_Code continueUnwinding(struct _Unwind_Exception *ex, + struct _Unwind_Context *context) +{ +#if defined(__arm__) && !defined(__ARM_DWARF_EH__) + if (__gnu_unwind_frame(ex, context) != _URC_OK) { return _URC_FAILURE; } +#endif + return _URC_CONTINUE_UNWIND; +} + +static void cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *e) +{ + /* + if (header->exceptionDestructor) + header->exceptionDestructor (e + 1); + + free((struct objc_exception*) ((char*)e - offsetof(struct objc_exception, + unwindHeader))); + */ +} + +void objc_exception_rethrow(struct _Unwind_Exception *e); + +/** + * Throws an Objective-C exception. This function is, unfortunately, used for + * rethrowing caught exceptions too, even in @finally() blocks. Unfortunately, + * this means that we have some problems if the exception is boxed. + */ +void objc_exception_throw(id object) +{ + struct thread_data *td = get_thread_data(); + DEBUG_LOG("Throwing %p, in flight exception: %p\n", object, td->lastThrownObject); + DEBUG_LOG("Exception caught by C++: %d\n", td->cxxCaughtException); + // If C++ caught the exception, then we may need to make C++ rethrow it if + // we want to preserve exception state. Rethrows should be handled with + // objc_exception_rethrow, but clang appears to do the wrong thing for some + // cases. + if (td->cxxCaughtException) + { + struct __cxa_eh_globals *globals = __cxa_get_globals(); + if ((globals->caughtExceptions != NULL) && + (*(id*)globals->caughtExceptions == object)) + { + __cxa_rethrow(); + } + } + + SEL rethrow_sel = sel_registerName("rethrow"); + if ((nil != object) && + (class_respondsToSelector(classForObject(object), rethrow_sel))) + { + DEBUG_LOG("Rethrowing\n"); + IMP rethrow = objc_msg_lookup(object, rethrow_sel); + rethrow(object, rethrow_sel); + // Should not be reached! If it is, then the rethrow method actually + // didn't, so we throw it normally. + } + + DEBUG_LOG("Throwing %p\n", object); + + struct objc_exception *ex = calloc(1, sizeof(struct objc_exception)); + + ex->unwindHeader.exception_class = objc_exception_class; + ex->unwindHeader.exception_cleanup = cleanup; + + ex->object = object; + + td->cxxCaughtException = NO; + + _Unwind_Reason_Code err = _Unwind_RaiseException(&ex->unwindHeader); + free(ex); + if (_URC_END_OF_STACK == err && 0 != _objc_unexpected_exception) + { + _objc_unexpected_exception(object); + } + DEBUG_LOG("Throw returned %d\n",(int) err); + abort(); +} + +static Class get_type_table_entry(struct _Unwind_Context *context, + struct dwarf_eh_lsda *lsda, + int filter) +{ + dw_eh_ptr_t record = lsda->type_table - + dwarf_size_of_fixed_size_field(lsda->type_table_encoding)*filter; + dw_eh_ptr_t start = record; + int64_t offset = read_value(lsda->type_table_encoding, &record); + + if (0 == offset) { return Nil; } + + // ...so we need to resolve it + char *class_name = (char*)(intptr_t)resolve_indirect_value(context, + lsda->type_table_encoding, offset, start); + + if (0 == class_name) { return Nil; } + + DEBUG_LOG("Class name: %s\n", class_name); + + if (strcmp("@id", class_name) == 0) { return (Class)1; } + + return (Class)objc_getClass(class_name); +} + +static BOOL isKindOfClass(Class thrown, Class type) +{ + do + { + if (thrown == type) + { + return YES; + } + thrown = class_getSuperclass(thrown); + } while (Nil != thrown); + + return NO; +} + + +static handler_type check_action_record(struct _Unwind_Context *context, + BOOL foreignException, + struct dwarf_eh_lsda *lsda, + dw_eh_ptr_t action_record, + Class thrown_class, + unsigned long *selector) +{ + if (!action_record) { return handler_cleanup; } + while (action_record) + { + int filter = read_sleb128(&action_record); + dw_eh_ptr_t action_record_offset_base = action_record; + int displacement = read_sleb128(&action_record); + *selector = filter; + DEBUG_LOG("Filter: %d\n", filter); + if (filter > 0) + { + Class type = get_type_table_entry(context, lsda, filter); + DEBUG_LOG("%p type: %d\n", type, !foreignException); + // Catchall + if (Nil == type) + { + return handler_catchall; + } + // We treat id catches as catchalls when an object is thrown and as + // nothing when a foreign exception is thrown + else if ((Class)1 == type) + { + DEBUG_LOG("Found id catch\n"); + if (!foreignException) + { + return handler_catchall_id; + } + } + else if (!foreignException && isKindOfClass(thrown_class, type)) + { + DEBUG_LOG("found handler for %s\n", type->name); + return handler_class; + } + else if (thrown_class == type) + { + return handler_class; + } + } + else if (filter == 0) + { + DEBUG_LOG("0 filter\n"); + // Cleanup? I think the GNU ABI doesn't actually use this, but it + // would be a good way of indicating a non-id catchall... + return handler_cleanup; + } + else + { + DEBUG_LOG("Filter value: %d\n" + "Your compiler and I disagree on the correct layout of EH data.\n", + filter); + abort(); + } + *selector = 0; + action_record = displacement ? + action_record_offset_base + displacement : 0; + } + return handler_none; +} + +/** + * The Objective-C exception personality function implementation. This is + * shared by the GCC-compatible and the new implementation. + * + * The key difference is that the new implementation always returns the + * exception object and boxes it. + */ +static inline _Unwind_Reason_Code internal_objc_personality(int version, + _Unwind_Action actions, + uint64_t exceptionClass, + struct _Unwind_Exception *exceptionObject, + struct _Unwind_Context *context, + BOOL isNew) +{ + DEBUG_LOG("%s personality function called %p\n", isNew ? "New" : "Old", exceptionObject); + + // This personality function is for version 1 of the ABI. If you use it + // with a future version of the ABI, it won't know what to do, so it + // reports a fatal error and give up before it breaks anything. + if (1 != version) + { + return _URC_FATAL_PHASE1_ERROR; + } + struct objc_exception *ex = 0; +#ifdef DEBUG_EXCEPTIONS + char *cls = (char*)&exceptionClass; +#endif + DEBUG_LOG("Class: %c%c%c%c%c%c%c%c\n", cls[7], cls[6], cls[5], cls[4], cls[3], cls[2], cls[1], cls[0]); + + // Check if this is a foreign exception. If it is a C++ exception, then we + // have to box it. If it's something else, like a LanguageKit exception + // then we ignore it (for now) + BOOL foreignException = exceptionClass != objc_exception_class; + // Is this a C++ exception containing an Objective-C++ object? + BOOL objcxxException = NO; + // The object to return + void *object = NULL; + +#ifndef NO_OBJCXX + if (cxx_exception_class == 0) + { + test_cxx_eh_implementation(); + } + + if (exceptionClass == cxx_exception_class) + { + int objcxx; + id obj = objc_object_for_cxx_exception(exceptionObject, &objcxx); + objcxxException = objcxx; + if (objcxxException) + { + object = obj; + DEBUG_LOG("ObjC++ object exception %p\n", object); + // This is a foreign exception, buy for the purposes of exception + // matching, we pretend that it isn't. + foreignException = NO; + } + } +#endif + + Class thrown_class = Nil; + + if (objcxxException) + { + thrown_class = (object == 0) ? Nil : classForObject((id)object); + } + // If it's not a foreign exception, then we know the layout of the + // language-specific exception stuff. + else if (!foreignException) + { + ex = objc_exception_from_header(exceptionObject); + if (ex->object != nil) + { + thrown_class = classForObject(ex->object); + } + } + else if (_objc_class_for_boxing_foreign_exception) + { + thrown_class = _objc_class_for_boxing_foreign_exception(exceptionClass); + DEBUG_LOG("Foreign class: %p\n", thrown_class); + } + unsigned char *lsda_addr = (void*)_Unwind_GetLanguageSpecificData(context); + DEBUG_LOG("LSDA: %p\n", lsda_addr); + + // No LSDA implies no landing pads - try the next frame + if (0 == lsda_addr) + { + return continueUnwinding(exceptionObject, context); + } + + // These two variables define how the exception will be handled. + struct dwarf_eh_action action = {0}; + unsigned long selector = 0; + + if (actions & _UA_SEARCH_PHASE) + { + DEBUG_LOG("Search phase...\n"); + struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr); + action = dwarf_eh_find_callsite(context, &lsda); + handler_type handler = check_action_record(context, foreignException, + &lsda, action.action_record, thrown_class, &selector); + DEBUG_LOG("handler: %d\n", handler); + // If there's no action record, we've only found a cleanup, so keep + // searching for something real + if (handler == handler_class || + ((handler == handler_catchall_id) && !foreignException) || + (handler == handler_catchall)) + { + saveLandingPad(context, exceptionObject, ex, selector, action.landing_pad); + DEBUG_LOG("Found handler! %d\n", handler); + return _URC_HANDLER_FOUND; + } + return continueUnwinding(exceptionObject, context); + } + DEBUG_LOG("Phase 2: Fight!\n"); + + // TODO: If this is a C++ exception, we can cache the lookup and cheat a + // bit + if (!(actions & _UA_HANDLER_FRAME)) + { + DEBUG_LOG("Not the handler frame, looking up the cleanup again\n"); + struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr); + action = dwarf_eh_find_callsite(context, &lsda); + // If there's no cleanup here, continue unwinding. + if (0 == action.landing_pad) + { + return continueUnwinding(exceptionObject, context); + } + handler_type handler = check_action_record(context, foreignException, + &lsda, action.action_record, thrown_class, &selector); + DEBUG_LOG("handler! %d %d\n", (int)handler, (int)selector); + // On ARM, we occasionally get called to install a handler without + // phase 1 running (no idea why, I suspect a bug in the generic + // unwinder), so skip this check. +#if !(defined(__arm__) && !defined(__ARM_DWARF_EH__)) + // If this is not a cleanup, ignore it and keep unwinding. + if ((handler != handler_cleanup) && !objcxxException) + { + DEBUG_LOG("Ignoring handler! %d\n",handler); + return continueUnwinding(exceptionObject, context); + } +#endif + DEBUG_LOG("Installing cleanup...\n"); + // If there is a cleanup, we need to return the exception structure + // (not the object) to the calling frame. The exception object + object = exceptionObject; + } + else if (foreignException || objcxxException) + { + struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr); + action = dwarf_eh_find_callsite(context, &lsda); + check_action_record(context, foreignException, &lsda, + action.action_record, thrown_class, &selector); + // If it's a foreign exception, then box it. If it's an Objective-C++ + // exception, then we need to delete the exception object. + if (foreignException) + { + DEBUG_LOG("Doing the foreign exception thing...\n"); + //[thrown_class exceptionWithForeignException: exceptionObject]; + SEL box_sel = sel_registerName("exceptionWithForeignException:"); + IMP boxfunction = objc_msg_lookup((id)thrown_class, box_sel); + if (!isNew) + { + object = boxfunction((id)thrown_class, box_sel, exceptionObject); + DEBUG_LOG("Boxed as %p\n", object); + } + } + else if (!isNew) // ObjCXX exception + { + _Unwind_DeleteException(exceptionObject); + } + // In the new EH ABI, we call objc_begin_catch() / and + // objc_end_catch(), which will wrap their __cxa* versions. + } + else + { + // Restore the saved info if we saved some last time. + loadLandingPad(context, exceptionObject, ex, &selector, &action.landing_pad); + object = ex->object; + if (!isNew) + { + free(ex); + } + } + + _Unwind_SetIP(context, (uintptr_t)action.landing_pad); + _Unwind_SetGR(context, __builtin_eh_return_data_regno(0), + (uintptr_t)(isNew ? exceptionObject : object)); + _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), selector); + + DEBUG_LOG("Installing context, selector %d\n", (int)selector); + get_thread_data()->cxxCaughtException = NO; + return _URC_INSTALL_CONTEXT; +} + +OBJC_PUBLIC +BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0) + return internal_objc_personality(version, actions, exceptionClass, + exceptionObject, context, NO); +} + +OBJC_PUBLIC +BEGIN_PERSONALITY_FUNCTION(__gnustep_objc_personality_v0) + return internal_objc_personality(version, actions, exceptionClass, + exceptionObject, context, YES); +} + +OBJC_PUBLIC +BEGIN_PERSONALITY_FUNCTION(__gnustep_objcxx_personality_v0) +#ifndef NO_OBJCXX + if (cxx_exception_class == 0) + { + test_cxx_eh_implementation(); + } + if (exceptionClass == objc_exception_class) + { + struct objc_exception *ex = objc_exception_from_header(exceptionObject); + if (0 == ex->cxx_exception) + { + ex->cxx_exception = objc_init_cxx_exception(ex->object); + } + // We now have two copies of the _Unwind_Exception object (which stores + // state for the unwinder) in flight. Make sure that they're in sync. + COPY_EXCEPTION(ex->cxx_exception, exceptionObject); + exceptionObject = ex->cxx_exception; + exceptionClass = cxx_exception_class; + int ret = CALL_PERSONALITY_FUNCTION(__gxx_personality_v0); + COPY_EXCEPTION(exceptionObject, ex->cxx_exception); + if (ret == _URC_INSTALL_CONTEXT) + { + get_thread_data()->cxxCaughtException = YES; + } + return ret; + } +#endif + return CALL_PERSONALITY_FUNCTION(__gxx_personality_v0); +} + +OBJC_PUBLIC id objc_begin_catch(struct _Unwind_Exception *exceptionObject) +{ + struct thread_data *td = get_thread_data(); + DEBUG_LOG("Beginning catch %p\n", exceptionObject); + td->cxxCaughtException = NO; + if (exceptionObject->exception_class == objc_exception_class) + { + td->current_exception_type = OBJC; + struct objc_exception *ex = objc_exception_from_header(exceptionObject); + if (ex->catch_count == 0) + { + // If this is the first catch, add it to the list. + ex->catch_count = 1; + ex->next = td->caughtExceptions; + td->caughtExceptions = ex; + } + else if (ex->catch_count < 0) + { + // If this is being thrown, mark it as caught again and increment + // the refcount + ex->catch_count = -ex->catch_count + 1; + } + else + { + // Otherwise, just increment the catch count + ex->catch_count++; + } + DEBUG_LOG("objc catch\n"); + return ex->object; + } + // If we have a foreign exception while we have stacked exceptions, we have + // a problem. We can't chain them, so we follow the example of C++ and + // just abort. + if (td->caughtExceptions != 0) + { + // FIXME: Actually, we can handle a C++ exception if only ObjC + // exceptions are in-flight + abort(); + } +#ifndef NO_OBJCXX + // If this is a C++ exception, let the C++ runtime handle it. + if (exceptionObject->exception_class == cxx_exception_class) + { + DEBUG_LOG("c++ catch\n"); + td->current_exception_type = CXX; + return __cxa_begin_catch(exceptionObject); + } +#endif + DEBUG_LOG("foreign exception catch\n"); + // Box if we have a boxing function. + if (_objc_class_for_boxing_foreign_exception) + { + Class thrown_class = + _objc_class_for_boxing_foreign_exception(exceptionObject->exception_class); + SEL box_sel = sel_registerName("exceptionWithForeignException:"); + id(*boxfunction)(Class,SEL,struct _Unwind_Exception*) = + (id(*)(Class,SEL,struct _Unwind_Exception*))objc_msg_lookup((id)thrown_class, box_sel); + if (boxfunction != 0) + { + id boxed = boxfunction(thrown_class, box_sel, exceptionObject); + td->caughtExceptions = (struct objc_exception*)boxed; + td->current_exception_type = BOXED_FOREIGN; + return boxed; + } + } + td->current_exception_type = FOREIGN; + td->caughtExceptions = (struct objc_exception*)exceptionObject; + // If this is some other kind of exception, then assume that the value is + // at the end of the exception header. + return (id)((char*)exceptionObject + sizeof(struct _Unwind_Exception)); +} + +OBJC_PUBLIC void objc_end_catch(void) +{ + struct thread_data *td = get_thread_data_fast(); + // If this is a boxed foreign exception then the boxing class is + // responsible for cleaning it up + if (td->current_exception_type == BOXED_FOREIGN) + { + td->caughtExceptions = 0; + td->current_exception_type = NONE; + return; + } + DEBUG_LOG("Ending catch\n"); + // If this is a C++ exception, then just let the C++ runtime handle it. + if (td->current_exception_type == CXX) + { + __cxa_end_catch(); + td->current_exception_type = OBJC; + return; + } + if (td->current_exception_type == FOREIGN) + { + struct _Unwind_Exception *e = ((struct _Unwind_Exception*)td->caughtExceptions); + e->exception_cleanup(_URC_FOREIGN_EXCEPTION_CAUGHT, e); + td->current_exception_type = NONE; + td->caughtExceptions = 0; + return; + } + // Otherwise we should do the cleanup thing. Nested catches are possible, + // so we only clean up the exception if this is the last reference. + assert(td->caughtExceptions != 0); + struct objc_exception *ex = td->caughtExceptions; + // If this is being rethrown decrement its (negated) catch count, but don't + // delete it even if its catch count would be 0. + if (ex->catch_count < 0) + { + ex->catch_count++; + return; + } + ex->catch_count--; + if (ex->catch_count == 0) + { + td->caughtExceptions = ex->next; + free(ex); + } +} + +OBJC_PUBLIC void objc_exception_rethrow(struct _Unwind_Exception *e) +{ + struct thread_data *td = get_thread_data_fast(); + // If this is an Objective-C exception, then + if (td->current_exception_type == OBJC) + { + struct objc_exception *ex = objc_exception_from_header(e); + assert(e->exception_class == objc_exception_class); + assert(ex == td->caughtExceptions); + assert(ex->catch_count > 0); + // Negate the catch count, so that we can detect that this is a + // rethrown exception in objc_end_catch + ex->catch_count = -ex->catch_count; + _Unwind_Reason_Code err = _Unwind_Resume_or_Rethrow(e); + free(ex); + if (_URC_END_OF_STACK == err && 0 != _objc_unexpected_exception) + { + _objc_unexpected_exception(ex->object); + } + abort(); + } +#ifndef NO_OBJCXX + else if (td->current_exception_type == CXX) + { + assert(e->exception_class == cxx_exception_class); + __cxa_rethrow(); + } +#endif + if (td->current_exception_type == BOXED_FOREIGN) + { + SEL rethrow_sel = sel_registerName("rethrow"); + id object = (id)td->caughtExceptions; + if ((nil != object) && + (class_respondsToSelector(classForObject(object), rethrow_sel))) + { + DEBUG_LOG("Rethrowing boxed exception\n"); + IMP rethrow = objc_msg_lookup(object, rethrow_sel); + rethrow(object, rethrow_sel); + } + } + assert(e == (struct _Unwind_Exception*)td->caughtExceptions); + _Unwind_Resume_or_Rethrow(e); + abort(); +} + +objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler) +{ + return __atomic_exchange_n(&_objc_unexpected_exception, handler, __ATOMIC_SEQ_CST); +} diff --git a/src/eh_trampoline.S b/src/eh_trampoline.S new file mode 100644 index 0000000..d8a270a --- /dev/null +++ b/src/eh_trampoline.S @@ -0,0 +1,118 @@ + .file "eh_trampoline.cc" + .text + .align 2 + .type _ZZ13eh_trampolinevEN1XD2Ev, @function +_ZZ13eh_trampolinevEN1XD2Ev: +.LFB2: + .cfi_startproc + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset 6, -16 + movq %rsp, %rbp + .cfi_def_cfa_register 6 + movq %rdi, -8(%rbp) + nop + popq %rbp + .cfi_def_cfa 7, 8 + ret + .cfi_endproc +.LFE2: + .size _ZZ13eh_trampolinevEN1XD2Ev, .-_ZZ13eh_trampolinevEN1XD2Ev + .set _ZZ13eh_trampolinevEN1XD1Ev,_ZZ13eh_trampolinevEN1XD2Ev + .globl _Z13eh_trampolinev + .hidden _Z13eh_trampolinev + .type _Z13eh_trampolinev, @function +_Z13eh_trampolinev: +.LFB0: + .cfi_startproc + .cfi_personality 0x9b,DW.ref.test_eh_personality + .cfi_lsda 0x1b,.LLSDA0 + endbr64 + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset 6, -16 + movq %rsp, %rbp + .cfi_def_cfa_register 6 + pushq %rbx + subq $24, %rsp + .cfi_offset 3, -24 + movq %fs:40, %rax + movq %rax, -24(%rbp) + xorl %eax, %eax +.LEHB0: + call _Z9cxx_throwv@PLT +.LEHE0: + movl $0, %ebx + leaq -25(%rbp), %rax + movq %rax, %rdi + call _ZZ13eh_trampolinevEN1XD1Ev + movl %ebx, %eax + movq -24(%rbp), %rdx + subq %fs:40, %rdx + je .L5 + jmp .L7 +.L6: + endbr64 + movq %rax, %rbx + leaq -25(%rbp), %rax + movq %rax, %rdi + call _ZZ13eh_trampolinevEN1XD1Ev + movq %rbx, %rax + movq %rax, %rdi +.LEHB1: + call _Unwind_Resume@PLT +.LEHE1: +.L7: + call __stack_chk_fail@PLT +.L5: + movq -8(%rbp), %rbx + leave + .cfi_def_cfa 7, 8 + ret + .cfi_endproc +.LFE0: + .globl test_eh_personality + .section .gcc_except_table,"a",@progbits +.LLSDA0: + .byte 0xff + .byte 0xff + .byte 0x1 + .uleb128 .LLSDACSE0-.LLSDACSB0 +.LLSDACSB0: + .uleb128 .LEHB0-.LFB0 + .uleb128 .LEHE0-.LEHB0 + .uleb128 .L6-.LFB0 + .uleb128 0 + .uleb128 .LEHB1-.LFB0 + .uleb128 .LEHE1-.LEHB1 + .uleb128 0 + .uleb128 0 +.LLSDACSE0: + .text + .size _Z13eh_trampolinev, .-_Z13eh_trampolinev + .hidden DW.ref.test_eh_personality + .weak DW.ref.test_eh_personality + .section .data.rel.local.DW.ref.test_eh_personality,"awG",@progbits,DW.ref.test_eh_personality,comdat + .align 8 + .type DW.ref.test_eh_personality, @object + .size DW.ref.test_eh_personality, 8 +DW.ref.test_eh_personality: + .quad test_eh_personality + .ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0" + .section .note.GNU-stack,"",@progbits + .section .note.gnu.property,"a" + .align 8 + .long 1f - 0f + .long 4f - 1f + .long 5 +0: + .string "GNU" +1: + .align 8 + .long 0xc0000002 + .long 3f - 2f +2: + .long 0x3 +3: + .align 8 +4: diff --git a/src/eh_trampoline.cc b/src/eh_trampoline.cc new file mode 100644 index 0000000..77849e1 --- /dev/null +++ b/src/eh_trampoline.cc @@ -0,0 +1,9 @@ +void cxx_throw(); + +__attribute((visibility("hidden"))) +int eh_trampoline() +{ + struct X { ~X() {} } x; + cxx_throw(); + return 0; +} diff --git a/src/eh_win32_msvc.cc b/src/eh_win32_msvc.cc new file mode 100644 index 0000000..997dce5 --- /dev/null +++ b/src/eh_win32_msvc.cc @@ -0,0 +1,287 @@ +#include +#include +#include +#include + +#include "objc/runtime.h" +#include "objc/objc-exception.h" +#include "visibility.h" + +#include +#define RtlAddGrowableFunctionTable ClangIsConfusedByTypedefReturnTypes +#include + + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#if !__has_builtin(__builtin_unreachable) +#define __builtin_unreachable abort +#endif + +#define EH_EXCEPTION_NUMBER ('msc' | 0xE0000000) +#define EH_MAGIC_NUMBER1 0x19930520 +#define EXCEPTION_NONCONTINUABLE 0x1 + +struct _MSVC_TypeDescriptor +{ + const void* pVFTable; + void* spare; + char name[0]; +}; + +struct _MSVC_CatchableType +{ + unsigned int flags; + unsigned long type; + int mdisp; + int pdisp; + int vdisp; + int size; + unsigned long copyFunction; +}; + +struct _MSVC_CatchableTypeArray +{ + int count; + unsigned long types[0]; +}; + +struct _MSVC_ThrowInfo +{ + unsigned int attributes; + unsigned long pfnUnwind; + unsigned long pfnForwardCompat; + unsigned long pCatchableTypeArray; +}; + +static LPTOP_LEVEL_EXCEPTION_FILTER originalUnhandledExceptionFilter = nullptr; +void (*_objc_unexpected_exception)(id exception); +LONG WINAPI _objc_unhandled_exception_filter(struct _EXCEPTION_POINTERS* exceptionInfo); + +#if defined(_WIN64) +#define IMAGE_RELATIVE(ptr, base) (static_cast((ptr ? ((uintptr_t)ptr - (uintptr_t)base) : (uintptr_t)nullptr))) +#else +#define IMAGE_RELATIVE(ptr, base) reinterpret_cast((ptr)) +#endif + +extern "C" void __stdcall _CxxThrowException(void*, _MSVC_ThrowInfo*); + +namespace +{ + +static std::string mangleObjcObject() +{ +#if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8 + return ".PEAUobjc_object@@"; +#else + return ".PAUobjc_object@@"; +#endif +} + +static std::string mangleStructNamed(const char* className) +{ + // 32-bit: + // .PAUxxx@@ = ?? struct xxx * `RTTI Type Descriptor' + // 64-bit: + // .PEAUxxx@@ = ?? struct xxx * __ptr64 `RTTI Type Descriptor' + //return + auto r = +#if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8 + std::string(".PEAU") + +#else + std::string(".PAU") + +#endif + className + "@@"; + return r; +} + +void fillCatchableType(_MSVC_CatchableType* exceptType) +{ + exceptType->flags = 1; + exceptType->mdisp = 0; + exceptType->pdisp = -1; + exceptType->vdisp = 0; + exceptType->size = sizeof(id); + exceptType->copyFunction = 0; +} + +} // + +struct X {}; +OBJC_PUBLIC extern "C" void objc_exception_rethrow(void* exc); + +OBJC_PUBLIC extern "C" void objc_exception_throw(id object) +{ + // Base used for image-relative addresses. + char x; + // This is the base vtable for all RTTI entries + static const void* typeid_vtable = *(void**)&typeid(void *); + + SEL rethrow_sel = sel_registerName("rethrow"); + if ((nil != object) && + (class_respondsToSelector(object_getClass(object), rethrow_sel))) + { + IMP rethrow = objc_msg_lookup(object, rethrow_sel); + rethrow(object, rethrow_sel); + // Should not be reached! If it is, then the rethrow method actually + // didn't, so we throw it normally. + } + + SEL processException_sel = sel_registerName("_processException"); + if ((nil != object) && + (class_respondsToSelector(object_getClass(object), processException_sel))) + { + IMP processException = objc_msg_lookup(object, processException_sel); + processException(object, processException_sel); + } + + // The 'id' base type will be taking up a spot in the list: + size_t typeCount = 1; + + // Get count of all types in exception + for (Class cls = object_getClass(object); cls != Nil; cls = class_getSuperclass(cls), ++typeCount) + ; + + // Unfortunately we can't put this in a real function since the alloca has to be in this stack frame: +#define CREATE_TYPE_DESCRIPTOR(desc, symName) \ + desc = reinterpret_cast<_MSVC_TypeDescriptor*>(alloca(sizeof(_MSVC_TypeDescriptor) + symName.size() + 1 /* null terminator */)); \ + desc->pVFTable = typeid_vtable; \ + desc->spare = nullptr; \ + strcpy_s(desc->name, symName.size() + 1, symName.c_str()); + + auto exceptTypes = + (_MSVC_CatchableTypeArray*)_alloca(sizeof(_MSVC_CatchableTypeArray) + sizeof(_MSVC_CatchableType*) * typeCount); + exceptTypes->count = typeCount; + + // Add exception type and all base types to throw information + size_t curTypeIndex = 0; + for (Class cls = object_getClass(object); cls != Nil; cls = class_getSuperclass(cls)) + { + auto exceptType = (_MSVC_CatchableType*)_alloca(sizeof(_MSVC_CatchableType)); + fillCatchableType(exceptType); + + auto mangledName = mangleStructNamed(class_getName(cls)); + _MSVC_TypeDescriptor *ty; + CREATE_TYPE_DESCRIPTOR(ty, mangledName); + exceptType->type = IMAGE_RELATIVE(ty, &x); + exceptTypes->types[curTypeIndex++] = IMAGE_RELATIVE(exceptType, &x); + } + + // Add id (struct objc_object*) + auto exceptType = (_MSVC_CatchableType*)_alloca(sizeof(_MSVC_CatchableType)); + fillCatchableType(exceptType); + auto idName = mangleObjcObject(); + _MSVC_TypeDescriptor *ty; + CREATE_TYPE_DESCRIPTOR(ty, idName); + exceptType->type = IMAGE_RELATIVE(ty, &x); + exceptTypes->types[curTypeIndex++] = IMAGE_RELATIVE(exceptType, &x); + + _MSVC_ThrowInfo ti = { + 0, // attributes + 0, // pfnUnwind + 0, // pfnForwardCompat + IMAGE_RELATIVE(exceptTypes, &x) // pCatchableTypeArray + }; + EXCEPTION_RECORD exception; + exception.ExceptionCode = EH_EXCEPTION_NUMBER; + exception.ExceptionFlags = EXCEPTION_NONCONTINUABLE; + exception.ExceptionRecord = nullptr; + exception.ExceptionAddress = nullptr; + // The fourth parameter is the base address of the image (for us, this stack + // frame), but we only use image-relative 32-bit addresses on 64-bit + // platforms. On 32-bit platforms, we use 32-bit absolute addresses. + exception.NumberParameters = sizeof(void*) == 4 ? 3 : 4; + exception.ExceptionInformation[0] = EH_MAGIC_NUMBER1; + exception.ExceptionInformation[1] = reinterpret_cast(&object); + exception.ExceptionInformation[2] = reinterpret_cast(&ti); + exception.ExceptionInformation[3] = reinterpret_cast(&x); + +#ifdef _WIN64 + RtlRaiseException(&exception); +#else + RaiseException(exception.ExceptionCode, + exception.ExceptionFlags, + exception.NumberParameters, + exception.ExceptionInformation); +#endif + __builtin_unreachable(); +} + +OBJC_PUBLIC extern "C" void objc_exception_rethrow(void* exc) +{ + _CxxThrowException(nullptr, nullptr); + __builtin_unreachable(); +} + +// rebase_and_cast adds a constant offset to a U value, converting it into a T +template +static std::add_const_t> rebase_and_cast(intptr_t base, U value) { + // U value -> const T* (base+value) + return reinterpret_cast>>(base + (long)(value)); +} + +/** + * Unhandled exception filter that we install to get called when an exception is + * not otherwise handled in a process that is not being debugged. In here we + * check if the exception is an Objective C exception raised by + * objc_exception_throw() above, and if so call the _objc_unexpected_exception + * hook with the Objective-C exception object. + * + * https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setunhandledexceptionfilter + */ +LONG WINAPI _objc_unhandled_exception_filter(struct _EXCEPTION_POINTERS* exceptionInfo) +{ + const EXCEPTION_RECORD* ex = exceptionInfo->ExceptionRecord; + + if (_objc_unexpected_exception != 0 + && ex->ExceptionCode == EH_EXCEPTION_NUMBER + && ex->ExceptionInformation[0] == EH_MAGIC_NUMBER1 + && ex->NumberParameters >= 3) + { + // On 64-bit platforms, thrown exception catch data are relative virtual addresses off the module base. + intptr_t imageBase = ex->NumberParameters >= 4 ? (intptr_t)(ex->ExceptionInformation[3]) : 0; + + auto throwInfo = reinterpret_cast<_MSVC_ThrowInfo*>(ex->ExceptionInformation[2]); + if (throwInfo && throwInfo->pCatchableTypeArray) { + auto catchableTypes = rebase_and_cast<_MSVC_CatchableTypeArray*>(imageBase, throwInfo->pCatchableTypeArray); + bool foundobjc_object = false; + for (int i = 0; i < catchableTypes->count; ++i) { + const _MSVC_CatchableType* catchableType = rebase_and_cast<_MSVC_CatchableType*>(imageBase, catchableTypes->types[i]); + const _MSVC_TypeDescriptor* typeDescriptor = rebase_and_cast<_MSVC_TypeDescriptor*>(imageBase, catchableType->type); + if (strcmp(typeDescriptor->name, mangleObjcObject().c_str()) == 0) { + foundobjc_object = true; + break; + } + } + + if (foundobjc_object) { + id exception = *reinterpret_cast(ex->ExceptionInformation[1]); + _objc_unexpected_exception(exception); + } + } + } + + // call original exception filter if any + if (originalUnhandledExceptionFilter) { + return originalUnhandledExceptionFilter(exceptionInfo); + } + + // EXCEPTION_CONTINUE_SEARCH instructs the exception handler to continue searching for appropriate exception handlers. + // Since this is the last one, it is not likely to find any more. + return EXCEPTION_CONTINUE_SEARCH; +} + +OBJC_PUBLIC extern "C" objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler) +{ + objc_uncaught_exception_handler previousHandler = __atomic_exchange_n(&_objc_unexpected_exception, handler, __ATOMIC_SEQ_CST); + + // set unhandled exception filter to support hook + LPTOP_LEVEL_EXCEPTION_FILTER previousExceptionFilter = SetUnhandledExceptionFilter(&_objc_unhandled_exception_filter); + if (previousExceptionFilter != &_objc_unhandled_exception_filter) { + originalUnhandledExceptionFilter = previousExceptionFilter; + } + + return previousHandler; +} diff --git a/src/encoding2.c b/src/encoding2.c new file mode 100644 index 0000000..5fed3b2 --- /dev/null +++ b/src/encoding2.c @@ -0,0 +1,639 @@ +#include +#include +#include +#include + +#include "objc/runtime.h" +#include "objc/encoding.h" +#include "method.h" +#include "visibility.h" + +#ifdef max +# undef max +#endif + +size_t objc_alignof_type (const char *type); + +// It would be so nice if this works, but in fact it returns nonsense: +//#define alignof(x) __alignof__(x) +// +#define alignof(type) __builtin_offsetof(struct { const char c; type member; }, member) + +OBJC_PUBLIC +const char *objc_skip_type_qualifiers (const char *type) +{ + static const char *type_qualifiers = "rnNoORVA"; + while('\0' != *type && strchr(type_qualifiers, *type)) + { + type++; + } + return type; +} + +static const char *sizeof_type(const char *type, size_t *size); + +OBJC_PUBLIC +const char *objc_skip_typespec(const char *type) +{ + size_t ignored = 0; + return sizeof_type(type, &ignored); +} + +OBJC_PUBLIC +const char *objc_skip_argspec(const char *type) +{ + type = objc_skip_typespec(type); + while(isdigit(*type)) { type++; } + return type; +} + +PRIVATE size_t lengthOfTypeEncoding(const char *types) +{ + if ((NULL == types) || ('\0' == types[0])) { return 0; } + const char *end = objc_skip_typespec(types); + size_t length = end - types; + return length; +} + +static char* copyTypeEncoding(const char *types) +{ + size_t length = lengthOfTypeEncoding(types); + char *copy = malloc(length + 1); + memcpy(copy, types, length); + copy[length] = '\0'; + return copy; +} + +static const char * findParameterStart(const char *types, unsigned int index) +{ + // the upper bound of the loop is inclusive because the return type + // is the first element in the method signature + for (unsigned int i=0 ; i <= index ; i++) + { + types = objc_skip_argspec(types); + if ('\0' == *types) + { + return NULL; + } + } + return types; +} + + +typedef const char *(*type_parser)(const char*, void*); + +static int parse_array(const char **type, type_parser callback, void *context) +{ + // skip [ + (*type)++; + int element_count = (int)strtol(*type, (char**)type, 10); + *type = callback(*type, context); + // skip ] + (*type)++; + return element_count; +} + +static void parse_struct_or_union(const char **type, type_parser callback, void *context, char endchar) +{ + // Skip the ( and structure name + do + { + (*type)++; + // Opaque type has no =definition + if (endchar == **type) { (*type)++; return; } + } while('=' != **type); + // Skip = + (*type)++; + + while (**type != endchar) + { + // Structure elements sometimes have their names in front of each + // element, as in {NSPoint="x"f"y"f} - We need to skip the type name + // here. + // + // TODO: In a future version we should provide a callback that lets + // users of this code get the field name + if ('"'== **type) + { + + do + { + (*type)++; + } while ('"' != **type); + // Skip the closing " + (*type)++; + } + *type = callback(*type, context); + } + // skip } + (*type)++; +} + +static void parse_union(const char **type, type_parser callback, void *context) +{ + parse_struct_or_union(type, callback, context, ')'); +} + +static void parse_struct(const char **type, type_parser callback, void *context) +{ + parse_struct_or_union(type, callback, context, '}'); +} + +inline static void round_up(size_t *v, size_t b) +{ + if (0 == b) + { + return; + } + + if (*v % b) + { + *v += b - (*v % b); + } +} +inline static size_t max(size_t v, size_t v2) +{ + return v>v2 ? v : v2; +} + +static const char *skip_object_extended_qualifiers(const char *type) +{ + if (*(type+1) == '?') + { + type++; + if (*(type+1) == '<') + { + type += 2; + while (*type != '>') + { + type++; + } + } + } + else if (type[1] == '"') + { + type += 2; + while (*type != '"') + { + type++; + } + } + return type; +} + +static const char *sizeof_union_field(const char *type, size_t *size); + +static const char *sizeof_type(const char *type, size_t *size) +{ + type = objc_skip_type_qualifiers(type); + switch (*type) + { + // For all primitive types, we round up the current size to the + // required alignment of the type, then add the size +#define APPLY_TYPE(typeName, name, capitalizedName, encodingChar) \ + case encodingChar:\ + {\ + round_up(size, (alignof(typeName) * 8));\ + *size += (sizeof(typeName) * 8);\ + return type + 1;\ + } +#define SKIP_ID 1 +#define NON_INTEGER_TYPES 1 +#include "type_encoding_cases.h" + case '@': + { + round_up(size, (alignof(id) * 8)); + *size += (sizeof(id) * 8); + return skip_object_extended_qualifiers(type) + 1; + } + case '?': + case 'v': return type+1; + case 'j': + { + type++; + switch (*type) + { +#define APPLY_TYPE(typeName, name, capitalizedName, encodingChar) \ + case encodingChar:\ + {\ + round_up(size, (alignof(_Complex typeName) * 8));\ + *size += (sizeof(_Complex typeName) * 8);\ + return type + 1;\ + } +#include "type_encoding_cases.h" + } + } + case '{': + { + const char *t = type; + parse_struct(&t, (type_parser)sizeof_type, size); + size_t align = objc_alignof_type(type); + round_up(size, align * 8); + return t; + } + case '[': + { + const char *t = type; + size_t element_size = 0; + // FIXME: aligned size + int element_count = parse_array(&t, (type_parser)sizeof_type, &element_size); + (*size) += element_size * element_count; + return t; + } + case '(': + { + const char *t = type; + size_t union_size = 0; + parse_union(&t, (type_parser)sizeof_union_field, &union_size); + *size += union_size; + return t; + } + case 'b': + { + // Consume the b + type++; + // Ignore the offset + strtol(type, (char**)&type, 10); + // Consume the element type + type++; + // Read the number of bits + *size += strtol(type, (char**)&type, 10); + return type; + } + case '^': + { + // All pointers look the same to me. + *size += sizeof(void*) * 8; + size_t ignored = 0; + // Skip the definition of the pointeee type. + return sizeof_type(type+1, &ignored); + } + } + abort(); + return NULL; +} + +static const char *sizeof_union_field(const char *type, size_t *size) +{ + size_t field_size = 0; + const char *end = sizeof_type(type, &field_size); + *size = max(*size, field_size); + return end; +} + +static const char *alignof_type(const char *type, size_t *align) +{ + type = objc_skip_type_qualifiers(type); + switch (*type) + { + // For all primitive types, we return the maximum of the new alignment + // and the old one +#define APPLY_TYPE(typeName, name, capitalizedName, encodingChar) \ + case encodingChar:\ + {\ + *align = max((alignof(typeName) * 8), *align);\ + return type + 1;\ + } +#define NON_INTEGER_TYPES 1 +#define SKIP_ID 1 +#include "type_encoding_cases.h" + case '@': + { + *align = max((alignof(id) * 8), *align);\ + return skip_object_extended_qualifiers(type) + 1; + } + case '?': + case 'v': return type+1; + case 'j': + { + type++; + switch (*type) + { +#define APPLY_TYPE(typeName, name, capitalizedName, encodingChar) \ + case encodingChar:\ + {\ + *align = max((alignof(_Complex typeName) * 8), *align);\ + return type + 1;\ + } +#include "type_encoding_cases.h" + } + } + case '{': + { + const char *t = type; + parse_struct(&t, (type_parser)alignof_type, align); + return t; + } + case '(': + { + const char *t = type; + parse_union(&t, (type_parser)alignof_type, align); + return t; + } + case '[': + { + const char *t = type; + parse_array(&t, (type_parser)alignof_type, &align); + return t; + } + case 'b': + { + // Consume the b + type++; + // Ignore the offset + strtol(type, (char**)&type, 10); + // Alignment of a bitfield is the alignment of the type that + // contains it + type = alignof_type(type, align); + // Ignore the number of bits + strtol(type, (char**)&type, 10); + return type; + } + case '^': + { + *align = max((alignof(void*) * 8), *align); + // All pointers look the same to me. + size_t ignored = 0; + // Skip the definition of the pointeee type. + return alignof_type(type+1, &ignored); + } + } + abort(); + return NULL; +} + +OBJC_PUBLIC +size_t objc_sizeof_type(const char *type) +{ + size_t size = 0; + sizeof_type(type, &size); + return size / 8; +} + +OBJC_PUBLIC +size_t objc_alignof_type (const char *type) +{ + size_t align = 0; + alignof_type(type, &align); + return align / 8; +} + +OBJC_PUBLIC +size_t objc_aligned_size(const char *type) +{ + size_t size = objc_sizeof_type(type); + size_t align = objc_alignof_type(type); + return size + (size % align); +} + +OBJC_PUBLIC +size_t objc_promoted_size(const char *type) +{ + size_t size = objc_sizeof_type(type); + return size + (size % sizeof(void*)); +} + +OBJC_PUBLIC +void method_getReturnType(Method method, char *dst, size_t dst_len) +{ + if (NULL == method) { return; } + //TODO: Coped and pasted code. Factor it out. + const char *types = method_getTypeEncoding(method); + size_t length = lengthOfTypeEncoding(types); + if (length < dst_len) + { + memcpy(dst, types, length); + dst[length] = '\0'; + } + else + { + memcpy(dst, types, dst_len); + } +} + +OBJC_PUBLIC +const char *method_getTypeEncoding(Method method) +{ + if (NULL == method) { return NULL; } + return sel_getType_np(method->selector); +} + +OBJC_PUBLIC +void method_getArgumentType(Method method, + unsigned int index, + char *dst, + size_t dst_len) +{ + if (NULL == method) { return; } + const char *types = findParameterStart(method_getTypeEncoding(method), index); + if (NULL == types) + { + if (dst_len > 0) + { + *dst = '\0'; + } + return; + } + size_t length = lengthOfTypeEncoding(types); + if (length < dst_len) + { + memcpy(dst, types, length); + dst[length] = '\0'; + } + else + { + memcpy(dst, types, dst_len); + } +} + +OBJC_PUBLIC +unsigned method_getNumberOfArguments(Method method) +{ + if (NULL == method) { return 0; } + const char *types = method_getTypeEncoding(method); + unsigned int count = 0; + while('\0' != *types) + { + types = objc_skip_argspec(types); + count++; + } + return count - 1; +} + +OBJC_PUBLIC +unsigned method_get_number_of_arguments(struct objc_method *method) +{ + return method_getNumberOfArguments(method); +} + +OBJC_PUBLIC +char* method_copyArgumentType(Method method, unsigned int index) +{ + if (NULL == method) { return NULL; } + const char *types = findParameterStart(method_getTypeEncoding(method), index); + if (NULL == types) + { + return NULL; + } + return copyTypeEncoding(types); +} + +OBJC_PUBLIC +char* method_copyReturnType(Method method) +{ + if (NULL == method) { return NULL; } + return copyTypeEncoding(method_getTypeEncoding(method)); +} + +OBJC_PUBLIC +unsigned objc_get_type_qualifiers (const char *type) +{ + unsigned flags = 0; +#define MAP(chr, bit) case chr: flags |= bit; break; + do + { + switch (*(type++)) + { + default: return flags; + MAP('r', _F_CONST) + MAP('n', _F_IN) + MAP('o', _F_OUT) + MAP('N', _F_INOUT) + MAP('O', _F_BYCOPY) + MAP('V', _F_ONEWAY) + MAP('R', _F_BYREF) + } + } while (1); +} + +// Note: The implementations of these functions is horrible. +OBJC_PUBLIC +void objc_layout_structure (const char *type, + struct objc_struct_layout *layout) +{ + layout->original_type = type; + layout->type = 0; +} + +static const char *layout_structure_callback(const char *type, struct objc_struct_layout *layout) +{ + size_t align = 0; + size_t size = 0; + const char *end = sizeof_type(type, &size); + alignof_type(type, &align); + //printf("Callback called with %s\n", type); + if (layout->prev_type < type) + { + if (layout->record_align == 0) + { + layout->record_align = align; + layout->type = type; + } + } + else + { + size_t rsize = (size_t)layout->record_size; + round_up(&rsize, align); + layout->record_size = rsize + size; + } + return end; +} + +OBJC_PUBLIC +BOOL objc_layout_structure_next_member(struct objc_struct_layout *layout) +{ + const char *end = layout->type; + layout->record_size = 0; + layout->record_align = 0; + layout->prev_type = layout->type; + const char *type = layout->original_type; + parse_struct(&type, (type_parser)layout_structure_callback, layout); + //printf("Calculated: (%s) %s %d %d\n", layout->original_type, layout->type, layout->record_size, layout->record_align); + //printf("old start %s, new start %s\n", end, layout->type); + return layout->type != end; +} + +OBJC_PUBLIC +void objc_layout_structure_get_info (struct objc_struct_layout *layout, + unsigned int *offset, + unsigned int *align, + const char **type) +{ + //printf("%p\n", layout); + *type = layout->type; + size_t off = layout->record_size / 8; + *align= layout->record_align / 8; + round_up(&off, (size_t)*align); + *offset = (unsigned int)off; +} + +#ifdef ENCODING_TESTS + +#define TEST(type) do {\ + if (alignof(type) != objc_alignof_type(@encode(type)))\ + printf("Incorrect alignment for %s: %d != %d\n", @encode(type), objc_alignof_type(@encode(type)), alignof(type));\ + if (sizeof(type) != objc_sizeof_type(@encode(type)))\ + printf("Incorrect size for %s: %d != %d\n", @encode(type), objc_sizeof_type(@encode(type)), sizeof(type));\ + } while(0) + +struct foo +{ + int a[2]; + int b:5; + struct + { + double d; + const char *str; + float e; + }c; + long long **g; + union { const char c; long long b; } h; + long long f; + _Complex int z; + _Complex double y; + char v; +}; + +typedef struct +{ + float x,y; +} Point; + +typedef struct +{ + Point a, b; +} Rect; + + +int main(void) +{ + TEST(int); + TEST(const char); + TEST(unsigned long long); + TEST(_Complex int); + TEST(struct foo); + struct objc_struct_layout layout; + + objc_layout_structure(@encode(Rect), &layout); + while (objc_layout_structure_next_member (&layout)) + { + unsigned offset; + unsigned align; + const char *ftype; + struct objc_struct_layout layout2; + objc_layout_structure_get_info (&layout, &offset, &align, &ftype); + printf("%s: offset: %d, alignment: %d\n", ftype, offset, align); + objc_layout_structure(ftype, &layout2); + while (objc_layout_structure_next_member (&layout2)) + { + objc_layout_structure_get_info (&layout2, &offset, &align, &ftype); + printf("%s: offset: %d, alignment: %d\n", ftype, offset, align); + } + } + printf("%d\n", offsetof(Rect, a.x)); + printf("%d\n", offsetof(Rect, a.y)); + printf("%d\n", offsetof(Rect, b.x)); + printf("%d\n", offsetof(Rect, b.y)); + +} +#endif diff --git a/src/fast_paths.m b/src/fast_paths.m new file mode 100644 index 0000000..0eda113 --- /dev/null +++ b/src/fast_paths.m @@ -0,0 +1,62 @@ +#include "objc/runtime.h" +#include "class.h" + +typedef struct _NSZone NSZone; +@interface RootMethods +- (id)alloc; +- (id)allocWithZone: (NSZone*)aZone; +- (id)init; +@end +#include + +/** + * Equivalent to [cls alloc]. If there's a fast path opt-in, then this skips the message send. + */ +OBJC_PUBLIC +id +objc_alloc(Class cls) +{ + if (UNLIKELY(!objc_test_class_flag(cls->isa, objc_class_flag_initialized))) + { + objc_send_initialize(cls); + } + if (objc_test_class_flag(cls->isa, objc_class_flag_fast_alloc_init)) + { + return class_createInstance(cls, 0); + } + return [cls alloc]; +} + +/** + * Equivalent to [cls allocWithZone: null]. If there's a fast path opt-in, then this skips the message send. + */ +OBJC_PUBLIC +id +objc_allocWithZone(Class cls) +{ + if (UNLIKELY(!objc_test_class_flag(cls->isa, objc_class_flag_initialized))) + { + objc_send_initialize(cls); + } + if (objc_test_class_flag(cls->isa, objc_class_flag_fast_alloc_init)) + { + return class_createInstance(cls, 0); + } + return [cls allocWithZone: NULL]; +} + +/** + * Equivalent to [[cls alloc] init]. If there's a fast path opt-in, then this + * skips the message send. + */ +OBJC_PUBLIC +id +objc_alloc_init(Class cls) +{ + id instance = objc_alloc(cls); + if (objc_test_class_flag(cls, objc_class_flag_fast_alloc_init)) + { + return instance; + } + return [instance init]; +} diff --git a/src/gc_none.c b/src/gc_none.c new file mode 100644 index 0000000..f263f37 --- /dev/null +++ b/src/gc_none.c @@ -0,0 +1,121 @@ +#include "visibility.h" +#include "objc/runtime.h" +#include "gc_ops.h" +#include "class.h" +#include +#include +#include + +static id allocate_class(Class cls, size_t extraBytes) +{ + size_t size = cls->instance_size + extraBytes + sizeof(intptr_t); + intptr_t *addr = +#ifdef _WIN32 + // Malloc on Windows doesn't guarantee 32-byte alignment, but we + // require this for any class that may contain vectors + _aligned_malloc(size, 32); + memset(addr, 0, size); +#else + calloc(size, 1); +#endif + return (id)(addr + 1); +} + +static void free_object(id obj) +{ +#ifdef _WIN32 + _aligned_free((void*)(((intptr_t*)obj) - 1)); +#else + free((void*)(((intptr_t*)obj) - 1)); +#endif +} + +static void *alloc(size_t size) +{ + return calloc(size, 1); +} + +void objc_registerThreadWithCollector(void) {} +void objc_unregisterThreadWithCollector(void) {} +void objc_assertRegisteredThreadWithCollector() {} + +PRIVATE struct gc_ops gc_ops_none = +{ + .allocate_class = allocate_class, + .free_object = free_object, + .malloc = alloc, + .free = free +}; +PRIVATE struct gc_ops *gc = &gc_ops_none; + +void objc_set_collection_threshold(size_t threshold) {} +void objc_set_collection_ratio(size_t ratio) {} +void objc_collect(unsigned long options) {} +BOOL objc_collectingEnabled(void) { return NO; } +BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) +{ + return __sync_bool_compare_and_swap(objectLocation, predicate, replacement); +} +BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) +{ + return __sync_bool_compare_and_swap(objectLocation, predicate, replacement); +} + +BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) +{ + return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); +} +BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) +{ + return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); +} +BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) +{ + return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); +} +BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) +{ + return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); +} + +id objc_assign_strongCast(id val, id *ptr) +{ + *ptr = val; + return val; +} + +id objc_assign_global(id val, id *ptr) +{ + *ptr = val; + return val; +} + +id objc_assign_ivar(id val, id dest, ptrdiff_t offset) +{ + *(id*)((char*)dest+offset) = val; + return val; +} + +void *objc_memmove_collectable(void *dst, const void *src, size_t size) +{ + return memmove(dst, src, size); +} +id objc_read_weak(id *location) +{ + return *location; +} +id objc_assign_weak(id value, id *location) +{ + *location = value; + return value; +} +id objc_allocate_object(Class cls, int extra) +{ + return class_createInstance(cls, extra); +} + +BOOL objc_collecting_enabled(void) { return NO; } +void objc_startCollectorThread(void) {} +void objc_clear_stack(unsigned long options) {} +BOOL objc_is_finalized(void *ptr) { return NO; } +void objc_finalizeOnMainThread(Class cls) {} diff --git a/src/hooks.c b/src/hooks.c new file mode 100644 index 0000000..b7524d9 --- /dev/null +++ b/src/hooks.c @@ -0,0 +1,3 @@ +#include "objc/runtime.h" +#define OBJC_HOOK OBJC_PUBLIC +#include "objc/hooks.h" diff --git a/src/ivar.c b/src/ivar.c new file mode 100644 index 0000000..5391a92 --- /dev/null +++ b/src/ivar.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include "objc/runtime.h" +#include "objc/objc-arc.h" +#include "class.h" +#include "visibility.h" +#include "gc_ops.h" +#include "legacy.h" + +ptrdiff_t objc_alignof_type(const char *); +ptrdiff_t objc_sizeof_type(const char *); + +PRIVATE void objc_compute_ivar_offsets(Class class) +{ + if (class->ivars == NULL) + { + Class super_class = class_getSuperclass(class); + if (super_class != Nil) + { + class->instance_size = super_class->instance_size; + } + return; + } + if (class->ivars->size != sizeof(struct objc_ivar)) + { + fprintf(stderr, "Downgrading ivar struct not yet implemented"); + abort(); + } + int i = 0; + /* If this class was compiled with support for late-bound ivars, the + * instance_size field will contain 0 - {the size of the instance variables + * declared for just this class}. The individual instance variable offset + * fields will then be the offsets from the start of the class, and so must + * have the size of the parent class prepended. */ + if (class->instance_size <= 0) + { + Class super = class_getSuperclass(class); + long ivar_start = 0; + if (Nil != super) + { + if (super->instance_size <= 0) + { + objc_compute_ivar_offsets(super); + } + ivar_start = super->instance_size; + } + class->instance_size = ivar_start; + /* For each instance variable, we add the offset if required (it will be zero + * if this class is compiled with a static ivar layout). We then set the + * value of a global variable to the offset value. + * + * Any class compiled with support for the non-fragile ABI, but not actually + * using it, will export the ivar offset field as a symbol. + * + * Note that using non-fragile ivars breaks @defs(). If you need equivalent + * functionality, provide an alternative @interface with all variables + * declared @public. + */ + if (class->ivars) + { + // If the first instance variable had any alignment padding, then we need + // to discard it. We will recompute padding ourself later. + long next_ivar = ivar_start; + long last_offset = LONG_MIN; + long last_size = 0; + long last_computed_offset = -1; + size_t refcount_size = sizeof(uintptr_t); + for (i = 0 ; i < class->ivars->count ; i++) + { + struct objc_ivar *ivar = ivar_at_index(class->ivars, i); + // Clang 7 and 8 have a bug where the size of _Bool is encoded + // as 0, not 1. Silently fix this up when we see it. + if (ivar->size == 0 && ivar->type[0] == 'B') + { + ivar->size = 1; + } + // We are going to be allocating an extra word for the reference count + // in front of the object. This doesn't matter for aligment most of + // the time, but if we have an instance variable that is a vector type + // then we will need to ensure that we are properly aligned again. + long ivar_size = ivar->size; + // Bitfields have the same offset - the base of the variable + // that contains them. If we are in a bitfield, then we need + // to make sure that we don't add any displacement from the + // previous value. + if (*ivar->offset < last_offset + last_size) + { + *ivar->offset = last_computed_offset + (*ivar->offset - last_offset); + ivar_size = 0; + continue; + } + last_offset = *ivar->offset; + *ivar->offset = next_ivar; + last_computed_offset = *ivar->offset; + next_ivar += ivar_size; + last_size = ivar->size; + size_t align = ivarGetAlign(ivar); + if ((*ivar->offset + refcount_size) % align != 0) + { + long padding = align - ((*ivar->offset + refcount_size) % align); + *ivar->offset += padding; + class->instance_size += padding; + next_ivar += padding; + } + assert((*ivar->offset + sizeof(uintptr_t)) % ivarGetAlign(ivar) == 0); + class->instance_size += ivar_size; + } +#ifdef OLDABI_COMPAT + // If we have a legacy ivar list, update the offset in it too - + // code from older compilers may access this directly! + struct objc_class_gsv1* legacy = objc_legacy_class_for_class(class); + if (legacy) + { + for (i = 0 ; i < class->ivars->count ; i++) + { + legacy->ivars->ivar_list[i].offset = *ivar_at_index(class->ivars, i)->offset; + } + } +#endif + } + } +} + + + +//////////////////////////////////////////////////////////////////////////////// +// Public API functions +//////////////////////////////////////////////////////////////////////////////// + +void object_setIvar(id object, Ivar ivar, id value) +{ + id *addr = (id*)((char*)object + ivar_getOffset(ivar)); + switch (ivarGetOwnership(ivar)) + { + case ownership_strong: + objc_storeStrong(addr, value); + break; + case ownership_weak: + objc_storeWeak(addr, value); + break; + case ownership_unsafe: + case ownership_invalid: + *addr = value; + break; + } +} + +Ivar object_setInstanceVariable(id obj, const char *name, void *value) +{ + Ivar ivar = class_getInstanceVariable(object_getClass(obj), name); + if (ivar_getTypeEncoding(ivar)[0] == '@') + { + object_setIvar(obj, ivar, *(id*)value); + } + else + { + size_t size = objc_sizeof_type(ivar_getTypeEncoding(ivar)); + memcpy((char*)obj + ivar_getOffset(ivar), value, size); + } + return ivar; +} + +id object_getIvar(id object, Ivar ivar) +{ + id *addr = (id*)((char*)object + ivar_getOffset(ivar)); + switch (ivarGetOwnership(ivar)) + { + case ownership_strong: + return objc_retainAutoreleaseReturnValue(*addr); + case ownership_weak: + return objc_loadWeak(addr); + break; + case ownership_unsafe: + case ownership_invalid: + return *addr; + return nil; + } +} + +Ivar object_getInstanceVariable(id obj, const char *name, void **outValue) +{ + Ivar ivar = class_getInstanceVariable(object_getClass(obj), name); + if (NULL != outValue) + { + *outValue = (((char*)obj) + ivar_getOffset(ivar)); + } + return ivar; +} diff --git a/src/legacy.c b/src/legacy.c new file mode 100644 index 0000000..51ae17b --- /dev/null +++ b/src/legacy.c @@ -0,0 +1,461 @@ +#include +#include +#include +#include + +#include "objc/runtime.h" +#include "objc/encoding.h" +#include "legacy.h" +#include "properties.h" +#include "class.h" +#include "loader.h" + +PRIVATE size_t lengthOfTypeEncoding(const char *types); + +enum objc_class_flags_gsv1 +{ + /** This class structure represents a class. */ + objc_class_flag_class_gsv1 = (1<<0), + /** This class structure represents a metaclass. */ + objc_class_flag_meta_gsv1 = (1<<1), + /** + * The class uses the new, Objective-C 2, runtime ABI. This ABI defines an + * ABI version field inside the class, and so will be used for all + * subsequent versions that retain some degree of compatibility. + */ + objc_class_flag_new_abi_gsv1 = (1<<4) +}; + +static inline BOOL objc_test_class_flag_gsv1(struct objc_class_gsv1 *aClass, + enum objc_class_flags_gsv1 flag) +{ + return (aClass->info & (unsigned long)flag) == (unsigned long)flag; +} +/** + * Checks the version of a class. Return values are: + * 0. Legacy GCC ABI compatible class. + * 1. First release of GNUstep ABI. + * 2. Second release of the GNUstep ABI, adds strong / weak ivar bitmaps. + * 3. Third release of the GNUstep ABI. Many cleanups. + */ +static inline int objc_get_class_version_gsv1(struct objc_class_gsv1 *aClass) +{ + if (!objc_test_class_flag_gsv1(aClass, objc_class_flag_new_abi_gsv1)) + { + return 0; + } + return aClass->abi_version + 1; +} + +static objc_ivar_ownership ownershipForIvar(struct objc_class_gsv1 *cls, int idx) +{ + if (objc_get_class_version_gsv1(cls) < 2) + { + return ownership_unsafe; + } + if (objc_bitfield_test(cls->strong_pointers, idx)) + { + return ownership_strong; + } + if (objc_bitfield_test(cls->weak_pointers, idx)) + { + return ownership_weak; + } + return ownership_unsafe; +} + +static struct objc_ivar_list *upgradeIvarList(struct objc_class_gsv1 *cls) +{ + struct objc_ivar_list_gcc *l = cls->ivars; + if (l == NULL) + { + return NULL; + } + struct objc_ivar_list *n = calloc(1, sizeof(struct objc_ivar_list) + + l->count*sizeof(struct objc_ivar)); + n->size = sizeof(struct objc_ivar); + n->count = l->count; + for (int i=0 ; icount ; i++) + { + BOOL isBitfield = NO; + int bitfieldSize = 0; + int nextOffset; + // Bitfields have the same offset, but should have their size set to + // the size of the bitfield. We calculate the size of the bitfield by + // looking for the next ivar after the current one that has a different + // offset. + if (i+1 < l->count) + { + nextOffset = l->ivar_list[i+1].offset; + if (l->ivar_list[i].offset == l->ivar_list[i+1].offset) + { + isBitfield = YES; + for (int j=i+2 ; jcount ; j++) + { + if (l->ivar_list[i].offset != l->ivar_list[j].offset) + { + bitfieldSize = l->ivar_list[j].offset - l->ivar_list[i].offset; + break; + } + } + if (bitfieldSize == 0) + { + bitfieldSize = cls->instance_size - l->ivar_list[i].offset; + } + } + } + else + { + nextOffset = cls->instance_size; + } + if (nextOffset < 0) + { + nextOffset = -nextOffset; + } + const char *type = l->ivar_list[i].type; + int size = nextOffset - l->ivar_list[i].offset; + n->ivar_list[i].name = l->ivar_list[i].name; + n->ivar_list[i].type = type; + n->ivar_list[i].size = isBitfield ? bitfieldSize : size; + if (objc_test_class_flag_gsv1(cls, objc_class_flag_new_abi_gsv1)) + { + n->ivar_list[i].offset = cls->ivar_offsets[i]; + } + else + { + n->ivar_list[i].offset = &l->ivar_list[i].offset; + } + ivarSetAlign(&n->ivar_list[i], ((type == NULL) || type[0] == 0) ? __alignof__(void*) : objc_alignof_type(type)); + if (type[0] == '\0') + { + ivarSetAlign(&n->ivar_list[i], size); + } + ivarSetOwnership(&n->ivar_list[i], ownershipForIvar(cls, i)); + } + return n; +} + +static struct objc_method_list *upgradeMethodList(struct objc_method_list_gcc *old) +{ + if (old == NULL) + { + return NULL; + } + if (old->count == 0) + { + return NULL; + } + struct objc_method_list *l = calloc(sizeof(struct objc_method_list) + old->count * sizeof(struct objc_method), 1); + l->count = old->count; + if (old->next) + { + l->next = upgradeMethodList(old->next); + } + l->size = sizeof(struct objc_method); + for (int i=0 ; icount ; i++) + { + l->methods[i].imp = old->methods[i].imp; + l->methods[i].selector = old->methods[i].selector; + l->methods[i].types = old->methods[i].types; + } + return l; +} + +static inline BOOL checkAttribute(char field, int attr) +{ + return (field & attr) == attr; +} + +static void upgradeProperty(struct objc_property *n, struct objc_property_gsv1 *o) +{ + char *typeEncoding; + ptrdiff_t typeSize; + if (o->name[0] == '\0') + { + n->name = o->name + o->name[1]; + n->attributes = o->name + 2; + // If we have an attribute string, then it will contain a more accurate + // version of the types than we'll find in the getter (qualifiers such + // as _Atomic and volatile may be dropped) + assert(n->attributes[0] == 'T'); + const char *type_start = &n->attributes[1]; + const char *type_end = strchr(type_start, ','); + if (type_end == NULL) + { + type_end = type_start + strlen(type_start); + } + typeSize = type_end - type_start; + typeEncoding = malloc(typeSize + 1); + memcpy(typeEncoding, type_start, typeSize); + typeEncoding[typeSize] = 0; + } + else + { + typeSize = (ptrdiff_t)lengthOfTypeEncoding(o->getter_types); + typeEncoding = malloc(typeSize + 1); + memcpy(typeEncoding, o->getter_types, typeSize); + typeEncoding[typeSize] = 0; + } + n->type = typeEncoding; + + if (o->getter_name) + { + n->getter = sel_registerTypedName_np(o->getter_name, o->getter_types); + } + if (o->setter_name) + { + n->setter = sel_registerTypedName_np(o->setter_name, o->setter_types); + } + + if (o->name[0] == '\0') + { + return; + } + + n->name = o->name; + + const char *name = o->name; + size_t nameSize = (NULL == name) ? 0 : strlen(name); + // Encoding is T{type},V{name}, so 4 bytes for the "T,V" that we always + // need. We also need two bytes for the leading null and the length. + size_t encodingSize = typeSize + nameSize + 6; + char flags[20]; + size_t i = 0; + // Flags that are a comma then a character + if (checkAttribute(o->attributes, OBJC_PR_readonly)) + { + flags[i++] = ','; + flags[i++] = 'R'; + } + if (checkAttribute(o->attributes, OBJC_PR_retain)) + { + flags[i++] = ','; + flags[i++] = '&'; + } + if (checkAttribute(o->attributes, OBJC_PR_copy)) + { + flags[i++] = ','; + flags[i++] = 'C'; + } + if (checkAttribute(o->attributes2, OBJC_PR_weak)) + { + flags[i++] = ','; + flags[i++] = 'W'; + } + if (checkAttribute(o->attributes2, OBJC_PR_dynamic)) + { + flags[i++] = ','; + flags[i++] = 'D'; + } + if ((o->attributes & OBJC_PR_nonatomic) == OBJC_PR_nonatomic) + { + flags[i++] = ','; + flags[i++] = 'N'; + } + encodingSize += i; + flags[i] = '\0'; + size_t setterLength = 0; + size_t getterLength = 0; + if ((o->attributes & OBJC_PR_getter) == OBJC_PR_getter) + { + getterLength = strlen(o->getter_name); + encodingSize += 2 + getterLength; + } + if ((o->attributes & OBJC_PR_setter) == OBJC_PR_setter) + { + setterLength = strlen(o->setter_name); + encodingSize += 2 + setterLength; + } + unsigned char *encoding = malloc(encodingSize); + // Set the leading 0 and the offset of the name + unsigned char *insert = encoding; + BOOL needsComma = NO; + *(insert++) = 0; + *(insert++) = 0; + // Set the type encoding + *(insert++) = 'T'; + memcpy(insert, typeEncoding, typeSize); + insert += typeSize; + needsComma = YES; + // Set the flags + memcpy(insert, flags, i); + insert += i; + if ((o->attributes & OBJC_PR_getter) == OBJC_PR_getter) + { + if (needsComma) + { + *(insert++) = ','; + } + i++; + needsComma = YES; + *(insert++) = 'G'; + memcpy(insert, o->getter_name, getterLength); + insert += getterLength; + } + if ((o->attributes & OBJC_PR_setter) == OBJC_PR_setter) + { + if (needsComma) + { + *(insert++) = ','; + } + i++; + needsComma = YES; + *(insert++) = 'S'; + memcpy(insert, o->setter_name, setterLength); + insert += setterLength; + } + if (needsComma) + { + *(insert++) = ','; + } + *(insert++) = 'V'; + memcpy(insert, name, nameSize); + insert += nameSize; + *(insert++) = '\0'; + + n->attributes = (const char*)encoding; +} + +static struct objc_property_list *upgradePropertyList(struct objc_property_list_gsv1 *l) +{ + if (l == NULL) + { + return NULL; + } + size_t data_size = l->count * sizeof(struct objc_property); + struct objc_property_list *n = calloc(1, sizeof(struct objc_property_list) + data_size); + n->count = l->count; + n->size = sizeof(struct objc_property); + for (int i=0 ; icount ; i++) + { + upgradeProperty(&n->properties[i], &l->properties[i]); + } + return n; +} + +static int legacy_key; + +PRIVATE struct objc_class_gsv1* objc_legacy_class_for_class(Class cls) +{ + return (struct objc_class_gsv1*)objc_getAssociatedObject((id)cls, &legacy_key); +} + +PRIVATE Class objc_upgrade_class(struct objc_class_gsv1 *oldClass) +{ + Class cls = calloc(sizeof(struct objc_class), 1); + cls->isa = oldClass->isa; + // super_class is left nil and we upgrade it later. + cls->name = oldClass->name; + cls->version = oldClass->version; + cls->info = objc_class_flag_meta; + cls->instance_size = oldClass->instance_size; + cls->ivars = upgradeIvarList(oldClass); + cls->methods = upgradeMethodList(oldClass->methods); + cls->protocols = oldClass->protocols; + cls->abi_version = oldClass->abi_version; + cls->properties = upgradePropertyList(oldClass->properties); + objc_register_selectors_from_class(cls); + if (!objc_test_class_flag_gsv1(oldClass, objc_class_flag_meta_gsv1)) + { + cls->info = 0; + cls->isa = objc_upgrade_class((struct objc_class_gsv1*)cls->isa); + objc_setAssociatedObject((id)cls, &legacy_key, (id)oldClass, OBJC_ASSOCIATION_ASSIGN); + } + else + { + cls->instance_size = sizeof(struct objc_class); + } + return cls; +} +PRIVATE struct objc_category *objc_upgrade_category(struct objc_category_gcc *old) +{ + struct objc_category *cat = calloc(1, sizeof(struct objc_category)); + memcpy(cat, old, sizeof(struct objc_category_gcc)); + cat->instance_methods = upgradeMethodList(old->instance_methods); + cat->class_methods = upgradeMethodList(old->class_methods); + if (cat->instance_methods != NULL) + { + objc_register_selectors_from_list(cat->instance_methods); + } + if (cat->class_methods != NULL) + { + objc_register_selectors_from_list(cat->class_methods); + } + for (int i=0 ; iprotocols->count ; i++) + { + objc_init_protocols(cat->protocols); + } + return cat; +} + +static struct objc_protocol_method_description_list* +upgrade_protocol_method_list_gcc(struct objc_protocol_method_description_list_gcc *l) +{ + if ((l == NULL) || (l->count == 0)) + { + return NULL; + } + struct objc_protocol_method_description_list *n = + malloc(sizeof(struct objc_protocol_method_description_list) + + l->count * sizeof(struct objc_protocol_method_description)); + n->count = l->count; + n->size = sizeof(struct objc_protocol_method_description); + for (int i=0 ; icount ; i++) + { + n->methods[i].selector = sel_registerTypedName_np(l->methods[i].name, l->methods[i].types); + n->methods[i].types = l->methods[i].types; + } + return n; +} + +PRIVATE struct objc_protocol *objc_upgrade_protocol_gcc(struct objc_protocol_gcc *p) +{ + // If the protocol has already been upgraded, the don't try to upgrade it twice. + if (p->isa == objc_getClass("ProtocolGCC")) + { + return objc_getProtocol(p->name); + } + p->isa = objc_getClass("ProtocolGCC"); + Protocol *proto = + (Protocol*)class_createInstance((Class)objc_getClass("Protocol"), + sizeof(struct objc_protocol) - sizeof(id)); + proto->name = p->name; + // Aliasing of this between the new and old structures means that when this + // returns these will all be updated. + proto->protocol_list = p->protocol_list; + proto->instance_methods = upgrade_protocol_method_list_gcc(p->instance_methods); + proto->class_methods = upgrade_protocol_method_list_gcc(p->class_methods); + assert(proto->isa); + return proto; +} + +PRIVATE struct objc_protocol *objc_upgrade_protocol_gsv1(struct objc_protocol_gsv1 *p) +{ + // If the protocol has already been upgraded, the don't try to upgrade it twice. + if (p->isa == objc_getClass("ProtocolGSv1")) + { + return objc_getProtocol(p->name); + } + Protocol *n = + (Protocol*)class_createInstance((Class)objc_getClass("Protocol"), + sizeof(struct objc_protocol) - sizeof(id)); + n->instance_methods = upgrade_protocol_method_list_gcc(p->instance_methods); + // Aliasing of this between the new and old structures means that when this + // returns these will all be updated. + n->name = p->name; + n->protocol_list = p->protocol_list; + n->class_methods = upgrade_protocol_method_list_gcc(p->class_methods); + n->properties = upgradePropertyList(p->properties); + n->optional_properties = upgradePropertyList(p->optional_properties); + n->isa = objc_getClass("Protocol"); + // We do in-place upgrading of these, because they might be referenced + // directly + p->instance_methods = (struct objc_protocol_method_description_list_gcc*)n->instance_methods; + p->class_methods = (struct objc_protocol_method_description_list_gcc*)n->class_methods; + p->properties = (struct objc_property_list_gsv1*)n->properties; + p->optional_properties = (struct objc_property_list_gsv1*)n->optional_properties; + p->isa = objc_getClass("ProtocolGSv1"); + assert(p->isa); + return n; +} + diff --git a/src/legacy_malloc.c b/src/legacy_malloc.c new file mode 100644 index 0000000..3eedf27 --- /dev/null +++ b/src/legacy_malloc.c @@ -0,0 +1,43 @@ +#include + +void *valloc(size_t); + +// Stubs that just call the libc implementations when you call these. + +void *objc_malloc(size_t size) +{ + return malloc(size); +} + +void *objc_atomic_malloc(size_t size) +{ + return malloc(size); +} + +#ifdef __MINGW32__ +void *objc_valloc(size_t size) +{ + return malloc(size); +} +#else +void *objc_valloc(size_t size) +{ + return valloc(size); +} +#endif + +void *objc_realloc(void *mem, size_t size) +{ + return realloc(mem, size); +} + +void * objc_calloc(size_t nelem, size_t size) +{ + return calloc(nelem, size); +} + +void objc_free(void *mem) +{ + free(mem); +} + diff --git a/src/loader.c b/src/loader.c new file mode 100644 index 0000000..8f1a4b3 --- /dev/null +++ b/src/loader.c @@ -0,0 +1,404 @@ +#include +#include +#include "objc/runtime.h" +#include "objc/objc-auto.h" +#include "objc/objc-arc.h" +#include "lock.h" +#include "loader.h" +#include "visibility.h" +#include "legacy.h" +#ifdef ENABLE_GC +#include +#endif +#include +#include + +/** + * Runtime lock. This is exposed in + */ +PRIVATE mutex_t runtime_mutex; +#ifndef __MINGW32__ || __MINGW64__ +LEGACY void *__objc_runtime_mutex = &runtime_mutex; +#else +void *__objc_runtime_mutex = &runtime_mutex; +#endif + +void init_alias_table(void); +void init_arc(void); +void init_class_tables(void); +void init_dispatch_tables(void); +void init_gc(void); +void init_protocol_table(void); +void init_selector_tables(void); +void init_trampolines(void); +void init_early_blocks(void); +void objc_send_load_message(Class class); + +void log_selector_memory_usage(void); + +static void log_memory_stats(void) +{ + log_selector_memory_usage(); +} + +/* Number of threads that are alive. */ +int __objc_runtime_threads_alive = 1; /* !T:MUTEX */ + +// libdispatch hooks for registering threads +__attribute__((weak)) void (*dispatch_begin_thread_4GC)(void); +__attribute__((weak)) void (*dispatch_end_thread_4GC)(void); +__attribute__((weak)) void *(*_dispatch_begin_NSAutoReleasePool)(void); +__attribute__((weak)) void (*_dispatch_end_NSAutoReleasePool)(void *); + +__attribute__((used)) +static void link_protos(void) +{ + link_protocol_classes(); +} + +static void init_runtime(void) +{ + static BOOL first_run = YES; + if (first_run) + { +#if ENABLE_GC + init_gc(); +#endif + // Create the main runtime lock. This is not safe in theory, but in + // practice the first time that this function is called will be in the + // loader, from the main thread. Future loaders may run concurrently, + // but that is likely to break the semantics of a lot of languages, so + // we don't have to worry about it for a long time. + // + // The only case when this can potentially go badly wrong is when a + // pure-C main() function spawns two threads which then, concurrently, + // call dlopen() or equivalent, and the platform's implementation of + // this does not perform any synchronization. + 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_alias_table(); + init_early_blocks(); + init_arc(); + init_trampolines(); + first_run = NO; + if (getenv("LIBOBJC_MEMORY_PROFILE")) + { + atexit(log_memory_stats); + } + if (dispatch_begin_thread_4GC != 0) { + dispatch_begin_thread_4GC = objc_registerThreadWithCollector; + } + if (dispatch_end_thread_4GC != 0) { + dispatch_end_thread_4GC = objc_unregisterThreadWithCollector; + } + if (_dispatch_begin_NSAutoReleasePool != 0) { + _dispatch_begin_NSAutoReleasePool = objc_autoreleasePoolPush; + } + if (_dispatch_end_NSAutoReleasePool != 0) { + _dispatch_end_NSAutoReleasePool = objc_autoreleasePoolPop; + } + } +} + +/** + * Structure for a class alias. + */ +struct objc_alias +{ + /** + * The name by which this class is referenced. + */ + const char *alias_name; + /** + * A pointer to the indirection variable for this class. + */ + Class *alias; +}; + +/** + * Type of the NSConstantString structure. + */ +struct nsstr +{ + /** Class pointer. */ + id isa; + /** + * Flags. Low 2 bits store the encoding: + * 0: ASCII + * 1: UTF-8 + * 2: UTF-16 + * 3: UTF-32 + * + * Low 16 bits are reserved for the compiler, high 32 bits are reserved for + * the Foundation framework. + */ + uint32_t flags; + /** + * Number of UTF-16 code units in the string. + */ + uint32_t length; + /** + * Number of bytes in the string. + */ + uint32_t size; + /** + * Hash (Foundation framework defines the hash algorithm). + */ + uint32_t hash; + /** + * Character data. + */ + const char *data; +}; + +// begin: objc_init +struct objc_init +{ + uint64_t version; + SEL sel_begin; + SEL sel_end; + Class *cls_begin; + Class *cls_end; + Class *cls_ref_begin; + Class *cls_ref_end; + struct objc_category *cat_begin; + struct objc_category *cat_end; + struct objc_protocol *proto_begin; + struct objc_protocol *proto_end; + struct objc_protocol **proto_ref_begin; + struct objc_protocol **proto_ref_end; + struct objc_alias *alias_begin; + struct objc_alias *alias_end; + struct nsstr *strings_begin; + struct nsstr *strings_end; +}; +// end: objc_init + +#ifdef DEBUG_LOADING +#include +#endif + +static enum { + LegacyABI, + NewABI, + UnknownABI +} CurrentABI = UnknownABI; + +void registerProtocol(Protocol *proto); + +OBJC_PUBLIC void __objc_load(struct objc_init *init) +{ + init_runtime(); +#ifdef DEBUG_LOADING + Dl_info info; + if (dladdr(init, &info)) + { + fprintf(stderr, "Loading %p from object: %s (%p)\n", init, info.dli_fname, __builtin_return_address(0)); + } + else + { + fprintf(stderr, "Loading %p from unknown object\n", init); + } +#endif + LOCK_RUNTIME_FOR_SCOPE(); + BOOL isFirstLoad = NO; + switch (CurrentABI) + { + case LegacyABI: + fprintf(stderr, "Version 2 Objective-C ABI may not be mixed with earlier versions.\n"); + abort(); + case UnknownABI: + isFirstLoad = YES; + CurrentABI = NewABI; + break; + case NewABI: + break; + } + + // If we've already loaded this module, don't load it again. + if (init->version == ULONG_MAX) + { + return; + } + + assert(init->version == 0); + assert((((uintptr_t)init->sel_end-(uintptr_t)init->sel_begin) % sizeof(*init->sel_begin)) == 0); + assert((((uintptr_t)init->cls_end-(uintptr_t)init->cls_begin) % sizeof(*init->cls_begin)) == 0); + assert((((uintptr_t)init->cat_end-(uintptr_t)init->cat_begin) % sizeof(*init->cat_begin)) == 0); + for (SEL sel = init->sel_begin ; sel < init->sel_end ; sel++) + { + if (sel->name == 0) + { + continue; + } + objc_register_selector(sel); + } + for (struct objc_protocol *proto = init->proto_begin ; proto < init->proto_end ; + proto++) + { + if (proto->name == NULL) + { + continue; + } + registerProtocol((struct objc_protocol*)proto); + } + for (struct objc_protocol **proto = init->proto_ref_begin ; proto < init->proto_ref_end ; + proto++) + { + if (*proto == NULL) + { + continue; + } + struct objc_protocol *p = objc_getProtocol((*proto)->name); + assert(p); + *proto = p; + } + for (Class *cls = init->cls_begin ; cls < init->cls_end ; cls++) + { + if (*cls == NULL) + { + continue; + } + // As a special case, allow using legacy ABI code with a new runtime. + if (isFirstLoad && (strcmp((*cls)->name, "Protocol") == 0)) + { + CurrentABI = UnknownABI; + } +#ifdef DEBUG_LOADING + fprintf(stderr, "Loading class %s\n", (*cls)->name); +#endif + objc_load_class(*cls); + } +#if 0 + // We currently don't do anything with these pointers. They exist to + // provide a level of indirection that will permit us to completely change + // the `objc_class` struct without breaking the ABI (again) + for (Class *cls = init->cls_ref_begin ; cls < init->cls_ref_end ; cls++) + { + } +#endif + for (struct objc_category *cat = init->cat_begin ; cat < init->cat_end ; + cat++) + { + if ((cat == NULL) || (cat->class_name == NULL)) + { + continue; + } + objc_try_load_category(cat); +#ifdef DEBUG_LOADING + fprintf(stderr, "Loading category %s (%s)\n", cat->class_name, cat->name); +#endif + } + // Load categories and statics that were deferred. + objc_load_buffered_categories(); + // Fix up the class links for loaded classes. + objc_resolve_class_links(); + for (struct objc_category *cat = init->cat_begin ; cat < init->cat_end ; + cat++) + { + Class class = (Class)objc_getClass(cat->class_name); + if ((Nil != class) && + objc_test_class_flag(class, objc_class_flag_resolved)) + { + objc_send_load_message(class); + } + } + // Register aliases + for (struct objc_alias *alias = init->alias_begin ; alias < init->alias_end ; + alias++) + { + if (alias->alias_name) + { + class_registerAlias_np(*alias->alias, alias->alias_name); + } + } +#if 0 + // If future versions of the ABI need to do anything with constant strings, + // they may do so here. + for (struct nsstr *string = init->strings_begin ; string < init->strings_end ; + string++) + { + if (string->isa) + { + } + } +#endif + init->version = ULONG_MAX; +} + +#ifdef OLDABI_COMPAT +OBJC_PUBLIC void __objc_exec_class(struct objc_module_abi_8 *module) +{ + init_runtime(); + + switch (CurrentABI) + { + case UnknownABI: + CurrentABI = LegacyABI; + break; + case LegacyABI: + break; + case NewABI: + fprintf(stderr, "Version 2 Objective-C ABI may not be mixed with earlier versions.\n"); + abort(); + } + + // Check that this module uses an ABI version that we recognise. + // In future, we should pass the ABI version to the class / category load + // functions so that we can change various structures more easily. + assert(objc_check_abi_version(module)); + + + // The runtime mutex is held for the entire duration of a load. It does + // not need to be acquired or released in any of the called load functions. + LOCK_RUNTIME_FOR_SCOPE(); + + struct objc_symbol_table_abi_8 *symbols = module->symbol_table; + // Register all of the selectors used in this module. + if (symbols->selectors) + { + objc_register_selector_array(symbols->selectors, + symbols->selector_count); + } + + unsigned short defs = 0; + // Load the classes from this module + for (unsigned short i=0 ; iclass_count ; i++) + { + objc_load_class(objc_upgrade_class(symbols->definitions[defs++])); + } + unsigned int category_start = defs; + // Load the categories from this module + for (unsigned short i=0 ; icategory_count; i++) + { + objc_try_load_category(objc_upgrade_category(symbols->definitions[defs++])); + } + // Load the static instances + struct objc_static_instance_list **statics = (void*)symbols->definitions[defs]; + while (NULL != statics && NULL != *statics) + { + objc_init_statics(*(statics++)); + } + + // Load categories and statics that were deferred. + objc_load_buffered_categories(); + objc_init_buffered_statics(); + // Fix up the class links for loaded classes. + objc_resolve_class_links(); + for (unsigned short i=0 ; icategory_count; i++) + { + struct objc_category *cat = (struct objc_category*) + symbols->definitions[category_start++]; + Class class = (Class)objc_getClass(cat->class_name); + if ((Nil != class) && + objc_test_class_flag(class, objc_class_flag_resolved)) + { + objc_send_load_message(class); + } + } +} +#endif diff --git a/src/mutation.m b/src/mutation.m new file mode 100644 index 0000000..6a679b5 --- /dev/null +++ b/src/mutation.m @@ -0,0 +1,13 @@ +#include +#include +#include "objc/runtime.h" + +// This function is exported as a weak symbol to enable GNUstep or some other +// framework to replace it trivially +OBJC_PUBLIC +void __attribute__((weak)) objc_enumerationMutation(id obj) +{ + fprintf(stderr, "Mutation occurred during enumeration."); + abort(); +} + diff --git a/src/objc_msgSend.S b/src/objc_msgSend.S new file mode 100644 index 0000000..5427594 --- /dev/null +++ b/src/objc_msgSend.S @@ -0,0 +1,20 @@ +#include "common.S" +#include "asmconstants.h" +#if __x86_64 +#include "objc_msgSend.x86-64.S" +#elif __i386 +#include "objc_msgSend.x86-32.S" +#elif __arm__ +#include "objc_msgSend.arm.S" +#elif defined(__ARM_ARCH_ISA_A64) +#include "objc_msgSend.aarch64.S" +#elif defined(__riscv) && (__riscv_xlen == 64) && defined(__riscv_float_abi_double) +#include "objc_msgSend.riscv64.S" +#elif defined(__mips_n64) || defined(__mips_n32) +#include "objc_msgSend.mips.S" +#else +#warning objc_msgSend() not implemented for your architecture +#endif +#ifdef __ELF__ +.section .note.GNU-stack,"",%progbits +#endif diff --git a/src/objc_msgSend.aarch64.S b/src/objc_msgSend.aarch64.S new file mode 100644 index 0000000..a41cba5 --- /dev/null +++ b/src/objc_msgSend.aarch64.S @@ -0,0 +1,250 @@ +#define ARGUMENT_SPILL_SIZE (8*10 + 8*16) + +/* Windows ARM64 Exception Handling + * + * Structured Exception Handling (SEH) on Windows ARM64 differs from the x64 + * implementation. Functions consist of a single prologue and zero or more + * epilogues. Instead of using offsets for the .seh* directives to manipulate the + * stack frame, each directive corresponds to a single instruction. + * + * This presents a challenge for our objc_msgSend function, which only modifies + * the stack when a slow lookup is needed (see label "5"). + * + * To address this, we move the directive marking the start of a function deep + * into the msgSend body to prevent marking every instruction as ".seh_nop." + * + * For Windows: + * - EH_START(x): Start of function (no effect on Windows) + * - EH_END(x): End of function (no effect on Windows) + * - EH_START_AT_OFFSET(x): Mark Start of function (Delayed) + * - EH_END_AT_OFFSET(x): Mark End of function (Delayed) + * - EH_END_PROLOGUE: End of function prologue + * - EH_START_EPILOGUE: Start of function epilogue + * - EH_END_EPILOGUE: End of function epilogue + * - EH_SAVE_FP_LR(x): Save Frame Pointer and Link Register + * - EH_STACK_ALLOC(x): Stack allocation (inside prologue) + * - EH_ADD_FP(x): Add to Frame Pointer + * - EH_NOP: Mark instruction with no unwinding relevance + * + * For non-64-bit Windows systems or other platforms, these macros have no effect and can be used without causing issues. + */ + +#ifdef _WIN32 +# define EH_START +# define EH_END + +# define EH_START_AT_OFFSET(x) .seh_proc x +# define EH_END_AT_OFFSET(x) .seh_endproc x + +# define EH_END_PROLOGUE .seh_endprologue +# define EH_START_EPILOGUE .seh_startepilogue +# define EH_END_EPILOGUE .seh_endepilogue + +# define EH_SAVE_FP_LR(x) .seh_save_fplr x +# define EH_STACK_ALLOC(x) .seh_stackalloc x +# define EH_ADD_FP(x) .seh_add_fp x + +# define EH_NOP .seh_nop +#else +// Marks the real start and end of the function +# define EH_START .cfi_startproc +# define EH_END .cfi_endproc + +// The following directives are either not +// needed or not available with CFI +# define EH_START_AT_OFFSET(x) +# define EH_END_AT_OFFSET(x) +# define EH_END_PROLOGUE +# define EH_START_EPILOGUE +# define EH_END_EPILOGUE +# define EH_SAVE_FP_LR(x) +# define EH_STACK_ALLOC(x) +# define EH_ADD_FP(x) +# define EH_NOP +#endif + +.macro MSGSEND fnname receiver, sel + EH_START + + cbz \receiver, 4f // Skip everything if the receiver is nil + // Jump to 6: if this is a small object + ubfx x9, \receiver, #0, #SMALLOBJ_BITS + cbnz x9, 6f + + ldr x9, [\receiver] // Load class to x9 if not a small int +1: + ldr x9, [x9, #DTABLE_OFFSET] // Dtable -> x9 + ldr w10, [\sel] // selector->index -> x10 + ldr w11, [x9, #SHIFT_OFFSET] // dtable->shift -> x11 + + cmp x11, #8 // If this is a small dtable, jump to the + // small dtable handlers + b.eq 2f + cbz x11, 3f + + ubfx x11, x10, #16, #8 // Put byte 3 of the sel id in x12 + add x11, x9, x11, lsl #3 // x11 = dtable address + dtable data offset + ldr x9, [x11, #DATA_OFFSET] // Load, adding in the data offset +2: // dtable16 + ubfx x11, x10, #8, #8 // Put byte 2 of the sel id in x12 + add x11, x9, x11, lsl #3 // x11 = dtable address + dtable data offset + ldr x9, [x11, #DATA_OFFSET] // Load, adding in the data offset +3: // dtable8 + ubfx x11, x10, #0, #8 // Put low byte of the sel id in x12 + add x11, x9, x11, lsl #3 // x11 = dtable address + dtable data offset + ldr x9, [x11, #DATA_OFFSET] // Load, adding in the data offset. + // Slot pointer is now in x9 + + cbz x9, 5f // If the slot is nil, go to the C path + + ldr x9, [x9, #SLOT_OFFSET] // Load the method from the slot + br x9 // Tail-call the method + +4: // Nil receiver + mov \receiver, #0 + mov v0.d[0], \receiver + mov v0.d[1], \receiver + br lr +5: // Slow lookup + EH_START_AT_OFFSET(\fnname) + + // Save anything that will be clobbered by + // the call. + // Note that we pre-index (see "!"), meaning + // that we adjust the sp before storing the pair + // of registers. + stp x0, x1, [sp, #-(ARGUMENT_SPILL_SIZE)]! + EH_STACK_ALLOC((ARGUMENT_SPILL_SIZE)) + + stp x2, x3, [sp, #16] + EH_NOP // The following instructions can be ignored by SEH + stp x4, x5, [sp, #32] + EH_NOP + stp x6, x7, [sp, #48] + EH_NOP + stp q0, q1, [sp, #64] + EH_NOP + stp q2, q3, [sp, #96] + EH_NOP + stp q4, q5, [sp, #128] + EH_NOP + stp q6, q7, [sp, #160] + EH_NOP + stp fp, lr, [sp, #192] // The order is arbitrary, except that + EH_SAVE_FP_LR(192) // fp and lr must be spilled together + + add fp, sp, 192 // Adjust frame pointer + EH_ADD_FP(192) + stp \receiver, x8, [sp, #-16]! // it's convenient if \receiver is spilled at sp + EH_STACK_ALLOC(16) // stp performed pre-indexing by sp-16 + + EH_END_PROLOGUE + + #ifndef _WIN32 + .cfi_def_cfa fp, 16 + .cfi_offset fp, -16 + .cfi_offset lr, -8 + #endif + // We now have all argument registers, the link + // register and the receiver spilled on the + // stack, with sp containing + // the address of the receiver + + mov x0, sp // &self, _cmd in arguments + mov x1, \sel + bl CDECL(slowMsgLookup) // This is the only place where the EH directives + // have to be accurate... + mov x9, x0 // IMP -> x9 + + EH_START_EPILOGUE + ldp x0, x1, [sp, #16] // Reload spilled argument registers + EH_NOP + ldp x2, x3, [sp, #32] + EH_NOP + ldp x4, x5, [sp, #48] + EH_NOP + ldp x6, x7, [sp, #64] + EH_NOP + ldp q0, q1, [sp, #80] + EH_NOP + ldp q2, q3, [sp, #112] + EH_NOP + ldp q4, q5, [sp, #144] + EH_NOP + ldp q6, q7, [sp, #176] + EH_NOP + ldp fp, lr, [sp, #208] + EH_SAVE_FP_LR(208) + + // Post-increment sp += ARGUMENT_SPILL_SIZE +16 + ldp \receiver, x8, [sp], #(ARGUMENT_SPILL_SIZE + 16) + EH_STACK_ALLOC((ARGUMENT_SPILL_SIZE + 16)) + + EH_END_EPILOGUE + EH_END_AT_OFFSET(\fnname) + + br x9 +6: + // Load 63:12 of SmallObjectClasses address + // We use the CDECL macro as Windows prefixes + // cdecl conforming symbols with "_". + adrp x10, CDECL(SmallObjectClasses) // The macro handles this transparently. + + // Add lower 12-bits of SmallObjectClasses address to x10 + add x10, x10, :lo12:CDECL(SmallObjectClasses) + ldr x9, [x10, x9, lsl #3] + + b 1b + EH_END +.endm + +.globl CDECL(objc_msgSend_fpret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), %function) +.globl CDECL(objc_msgSend) +TYPE_DIRECTIVE(CDECL(objc_msgSend), %function) +.globl CDECL(objc_msgSend_stret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_stret), %function) +CDECL(objc_msgSend): +CDECL(objc_msgSend_fpret): +CDECL(objc_msgSend_stret): + MSGSEND objc_msgSend, x0, x1 + +/* + In AAPCS, an SRet is passed in x8, not x0 like a normal pointer parameter. + On Windows, this is only the case for POD (plain old data) types. Non trivial + types with constructors and destructors are passed in x0 on sret. + + We thus need two objc_msgSend functions on Windows on ARM64 for Sret: + 1. objc_msgSend_stret for POD Sret + 2. objc_msgSend_stret2 for non-trivial Sret (like C++ class instances) + */ +#ifdef _WIN32 +.globl CDECL(objc_msgSend_stret2) +TYPE_DIRECTIVE(CDECL(objc_msgSend_stret2), %function) +CDECL(objc_msgSend_stret2): + MSGSEND objc_msgSend_stret2, x1, x2 + +.text +.def objc_msgSend; +.scl 2; +.type 32; +.endef +.def objc_msgSend_fpret; +.scl 2; +.type 32; +.endef +.def objc_msgSend_stret; +.scl 2; +.type 32; +.endef +.def objc_msgSend_stret2; +.scl 2; +.type 32; +.endef + +.section .drectve,"yn" +.ascii " /EXPORT:objc_msgSend" +.ascii " /EXPORT:objc_msgSend_fpret" +.ascii " /EXPORT:objc_msgSend_stret" +.ascii " /EXPORT:objc_msgSend_stret2" +#endif \ No newline at end of file diff --git a/src/objc_msgSend.arm.S b/src/objc_msgSend.arm.S new file mode 100644 index 0000000..6c455e3 --- /dev/null +++ b/src/objc_msgSend.arm.S @@ -0,0 +1,146 @@ +.syntax unified +.fpu neon +#if ((__ARM_ARCH >= 7) || defined (__ARM_ARCH_6T2__)) +#define RELOC_OFFSET 4 +// If we're using a CPU that supports Thumb-2, use it. This makes the +// objc_msgSend function 130 bytes instead of 176. The fast path drops from 108 +// bytes to 82, meaning that it will fit in 3 32-byte i-cache lines, rather +// than 4. For comparison, the i386 version is 119 for objc_msgSend and +// another 117 for objc_msgSend_fpret (the two are the same on ARM), with 70 +// bytes for the fast path.. +.thumb +.macro byte1 dst, src + uxtb \dst, \src +.endm +.macro byte2 dst, src + ubfx \dst, \src, #8, #8 +.endm +.macro byte3 dst, src + ubfx \dst, \src, #16, #8 +.endm +#else +#define RELOC_OFFSET 8 +.macro byte1 dst, src + and \dst, \src, #0xff +.endm +.macro byte2 dst, src + and \dst, \src, #0xff00 + lsr \dst, \dst, 8 +.endm +.macro byte3 dst, src + and \dst, \src, #0xff00 + lsr \dst, \dst, 16 +.endm +#endif + +// Macro for testing: logs a register value to standard error +.macro LOG reg + push {r0-r3, ip,lr} + mov r0, \reg + bl logInt(PLT) + pop {r0-r3, ip,lr} +.endm + +.macro MSGSEND receiver, sel + .fnstart + teq \receiver, 0 + beq 4f // Skip everything if the receiver is nil + push {r4-r6} // We're going to use these three as + .save {r4-r6} + // scratch registers, so save them now. + // These are callee-save, so the unwind library + // must be able to restore them, so we need CFI + // directives for them, but not for any other pushes + tst \receiver, SMALLOBJ_MASK // Sets Z if this is not a small int + + ldr r4, 7f +6: + add r4, pc + itte ne + ldrne r4, [r4] + ldrne r4, [r4] // Small Int class -> r4 if this is a small int + ldreq r4, [\receiver] // Load class to r4 if not a small int + + ldr r4, [r4, #DTABLE_OFFSET] // Dtable -> r4 + + ldr r5, [\sel] // selector->index -> r5 + + ldr r6, [r4, #SHIFT_OFFSET] // dtable->shift -> r6 + + teq r6, #8 // If this is a small dtable, jump to the small dtable handlers + beq 1f + teq r6, #0 + beq 2f + + byte3 r6, r5 // Put byte 3 of the sel id in r6 + add r6, r4, r6, lsl #2 // r6 = dtable address + dtable data offset + ldr r4, [r6, #DATA_OFFSET] // Load, adding in the data offset +1: // dtable16 + byte2 r6, r5 // Put byte 2 of the sel id in r6 + add r6, r4, r6, lsl #2 // r6 = dtable address + dtable data offset + ldr r4, [r6, #DATA_OFFSET] // Load, adding in the data offset +2: // dtable8 + byte1 r6, r5 // Low byte of sel id into r5 + add r6, r4, r6, lsl #2 // r6 = dtable address + dtable data offset + ldr ip, [r6, #DATA_OFFSET] // Load, adding in the data offset + + cmp ip, #0 // If the slot is nil + ittt ne + ldrne ip, [ip, #SLOT_OFFSET] // Load the method from the slot + popne {r4-r6} // Restore the saved callee-save registers + bxne ip + +5: // Slow lookup + push {r0-r4, lr} // Save anything that will be clobbered by the call + .save {r0-r4, lr} +#ifndef __SOFTFP__ + vpush {q0-q3} + .vsave {q0-q3} +#endif + + push {\receiver} // &self, _cmd in arguments + .save {\receiver} + + mov r0, sp + mov r1, \sel + + bl CDECL(slowMsgLookup)(PLT) // This is the only place where the CFI directives have to be accurate... + mov ip, r0 // IMP -> ip + + pop {r5} // restore (modified) self to r5 +#ifndef __SOFTFP__ + vpop {q0-q3} +#endif + pop {r0-r4, lr} // Load clobbered registers + mov \receiver, r5 + pop {r4-r6} // Restore the saved callee-save registers + bx ip +4: // Nil receiver + mov r0, 0 + mov r1, 0 +#ifndef __SOFTFP__ +# ifdef __ARM_NEON__ + vmov.i64 d0, #0 // Return 0 as a float / double +# else + fmdrr d0, r0, r1 +# endif +#endif + bx lr +7: + .long SmallObjectClasses(GOT_PREL)-((6b+RELOC_OFFSET)-7b) + .align 2 +.fnend +.endm + +.globl CDECL(objc_msgSend_fpret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), %function) +.globl CDECL(objc_msgSend) +TYPE_DIRECTIVE(CDECL(objc_msgSend), %function) +CDECL(objc_msgSend): +CDECL(objc_msgSend_fpret): + MSGSEND r0, r1 +.globl CDECL(objc_msgSend_stret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_stret), %function) +CDECL(objc_msgSend_stret): + MSGSEND r1, r2 + diff --git a/src/objc_msgSend.mips.S b/src/objc_msgSend.mips.S new file mode 100644 index 0000000..7c4fe35 --- /dev/null +++ b/src/objc_msgSend.mips.S @@ -0,0 +1,207 @@ +.set noreorder +# Some macros for n32 / n64 compatibility +#ifdef _ABI64 +#define LP ld +#define SP sd +#else +#warning N32 is untested, O32 is unsupported. +#define LP lw +#define SP sw +#endif + +.macro dump_and_crash reg +nop +move $a0, \reg +ld $25, %got_disp(logInt)($t8) +jalr $25 +nop +lw $zero, ($zero) +.endm + +// FIXME: CHERI needs (or, at least, strongly encourages) 32-byte aligned +// stacks. +#ifndef __mips_soft_float +#define SAVE_SIZE 136 +#else +#define SAVE_SIZE 72 +#endif + +.macro MSGSEND receiver, sel +0: + .cfi_startproc # Start emitting unwind data. We + # don't actually care about any of + # the stuff except the slow call, + # because that's the only one that + # can throw. + beq \receiver, $0, 4f # If the receiver is nil, return nil + nop + + lui $t8, %hi(%neg(%gp_rel(0b))) # Load the GOT address that we use for relocations into $t8 + daddu $t8, $t8, $t9 + daddiu $t8, $t8, %lo(%neg(%gp_rel(0b))) + + + andi $t0, \receiver, SMALLOBJ_MASK # Check if the receiver is a small object + bne $t0, $0, 6f # Get the small object class + nop + + LP $t1, (\sel) + + # By this point, we have a non-nil + # receiver that is a real pointer + LP $t0, (\receiver) # Load the class + +1: # class loaded, stored in $t0 + LP $t0, DTABLE_OFFSET($t0) # Load the dtable from the class + lw $t2, SHIFT_OFFSET($t0) # Load the shift (dtable size) + # $t0 = dtable, $t1 = sel index + daddi $t3, $t0, DATA_OFFSET # Compute the address of the start of the array + + + beq $0, $t2, 3f # If this is a small dtable, jump to the small dtable handlers + daddi $v0, $t2, -8 + + beq $0, $v0, 2f + lui $t2, 0x00ff # The mask for a big dtable won't fit in an and immediate + and $t2, $t2, $t1 # mask the selector +#ifdef _ABI64 + dsrl $t2, $t2, 13 # Right shift 16, but then left shift by pointer size +#else + srl $t2, $t2, 14 +#endif + dadd $t2, $t2, $t3 + LP $t3, ($t2) + daddi $t3, $t3, DATA_OFFSET # Compute the address of the start of the array +2: # dtable16: + andi $t2, $t1, 0xff00 # mask the selector +#ifdef _ABI64 + dsrl $t2, $t2, 5 # Right shift 8, but then left shift by pointer size +#else + srl $t2, $t2, 6 +#endif + dadd $t2, $t2, $t3 + LP $t3, ($t2) + daddi $t3, $t3, DATA_OFFSET # Compute the address of the start of the array +3: # dtable8: + andi $t2, $t1, 0xff # mask the selector +#ifdef _ABI64 + dsll $t2, $t2, 3 # Left shift by pointer size +#else + sll $t2, $t2, 2 +#endif + dadd $t2, $t2, $t3 + LP $t3, ($t2) + + + beq $0, $t3, 5f # Nil slot - invoke some kind of forwarding mechanism + nop + + LP $25, SLOT_OFFSET($t3) + jr $25 + nop +4: # returnNil: + # All of the return registers are + # callee-save, so we can + # return 0 in both in the same code: +#ifndef __mips_soft_float + dmtc1 $0, $f0 # Return 0 as a floating point value (only if we're not a soft-float target) + dmtc1 $0, $f2 +#endif + daddi $v0, $0, 0 # Return 0 as an integer + jr $ra + daddi $v1, $0, 0 + +5: # slowSend: + # Load the address of the slow lookup function now, so that we don't get + # pipeline stalls on the jump. This is more important on CHERI than proper + # MIPS implementations. + # Note: A better linker ought to be able to turn this into a single + # jump-immediate, so revisit this decision later... + LP $25, %got_disp(CDECL(slowMsgLookup))($t8) + + daddiu $sp, $sp, -SAVE_SIZE # We need to preserve all registers that may contain arguments: + + SP $a0, ($sp) + SP $a1, 8($sp) + SP $a2, 16($sp) + SP $a3, 24($sp) + SP $a4, 32($sp) + SP $a5, 40($sp) + SP $a6, 48($sp) + SP $a7, 56($sp) + SP $ra, 64($sp) +#ifndef __mips_soft_float + sdc1 $f12, 72($sp) + sdc1 $f13, 80($sp) + sdc1 $f14, 88($sp) + sdc1 $f15, 96($sp) + sdc1 $f16, 104($sp) + sdc1 $f17, 112($sp) + sdc1 $f18, 120($sp) + sdc1 $f19, 128($sp) +#endif + + # We're (potentially) modifying the self argument with the lookup. Use the + # address of the stack save slot for the address so that when we reload it + # we get the old or new version automatically. Note that we must reload it + # anyway, because argument registers are not guaranteed to be preserved + # across calls. +.ifc "\receiver", "$a0" + daddiu $a0, $sp, 0 # replace self with &self in $a0 +.else + daddiu $a0, $sp, 8 # replace sret pointer with &self in $a0 + daddiu $a1, $a2, 0 # replace self with _cmd in $a1 +.endif + + .cfi_def_cfa_offset SAVE_SIZE + .cfi_offset 31, (64 - SAVE_SIZE) + jalr $25 # Call the slow lookup function + nop + + move $25, $v0 # Move the return value to $25 for use with the call + + LP $a0, ($sp) # Restore all of the arguments. Note + LP $a1, 8($sp) # that the receiver may have been + LP $a2, 16($sp) # modified during the call + LP $a3, 24($sp) + LP $a4, 32($sp) + LP $a5, 40($sp) + LP $a6, 48($sp) + LP $a7, 56($sp) + LP $ra, 64($sp) +#ifndef __mips_soft_float + ldc1 $f12, 72($sp) + ldc1 $f13, 80($sp) + ldc1 $f14, 88($sp) + ldc1 $f15, 96($sp) + ldc1 $f16, 104($sp) + ldc1 $f17, 112($sp) + ldc1 $f18, 120($sp) + ldc1 $f19, 128($sp) +#endif + jr $25 + daddiu $sp, $sp, SAVE_SIZE +6: # smallObject: +#if _ABI64 + dsll $t0, $t0, 3 # Convert tag to pointer offset + LP $t2, %got_disp(CDECL(SmallObjectClasses))($t8) # Load small object classes array address + daddu $t0, $t0, $t2 # Add the base address to the offset + b 1b # Return to the normal path + LP $t0, ($t0) # Load the class (in delay slot) +#else + b 1b + LP $t0, %got_disp(CDECL(SmallIntClass))($t8) +#endif + .cfi_endproc +.endm +.globl CDECL(objc_msgSend) +TYPE_DIRECTIVE(CDECL(objc_msgSend), @function) +.globl CDECL(objc_msgSend_fpret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), @function) +CDECL(objc_msgSend_fpret): +CDECL(objc_msgSend): + MSGSEND $a0, $a1 +.globl CDECL(objc_msgSend_stret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_stret), @function) +CDECL(objc_msgSend_stret): + MSGSEND $a1, $a2 diff --git a/src/objc_msgSend.riscv64.S b/src/objc_msgSend.riscv64.S new file mode 100644 index 0000000..c99a4dd --- /dev/null +++ b/src/objc_msgSend.riscv64.S @@ -0,0 +1,141 @@ +#define ARGUMENT_SPILL_SIZE (10*8 + 8*8) + +.macro MSGSEND receiver, sel + .cfi_startproc + beqz \receiver, 3f // Skip everything if receiver is nil + + andi t0, \receiver, SMALLOBJ_MASK + bnez t0, 5f + + ld t0, 0(\receiver) // Load class into t0 +0: + ld t0, DTABLE_OFFSET(t0) // dtable -> t0 + ld t1, 0(\sel) // selector->index -> t1 + ld t2, SHIFT_OFFSET(t0) // dtable->shift -> t2 + + li t3, 8 + beq t2, t3, 1f + beqz t2, 2f + + srli t2, t1, 16-3 // Extract byte 3 of sel index and multiply by 2^3 + and t2, t2, 0x7F8 // Mask target byte + // Example: ((0xCAFEBA >> 13) & 0x7f8) == (0xCA << 3) + add t2, t0, t2 // t2 = dtable address + offset + ld t0, DATA_OFFSET(t2) // Load, adding in the data offset +1: + srli t2, t1, 8-3 // Extract byte 2 of sel index and multiply by 2^3 + and t2, t2, 0x7F8 // Mask target byte + add t2, t0, t2 // t2 = dtable address + offset + ld t0, DATA_OFFSET(t2) // Load, adding in the data offset +2: + slli t2, t1, 3 // Multiply by 2^3 + and t2, t2, 0x7F8 // Mask target byte + add t2, t0, t2 // t2 = dtable address + offset + ld t0, DATA_OFFSET(t2) // Load, adding in the data offset + // Slot pointer is now in t0 + + beqz t0, 4f // If the slot is nil, go to the C path + + ld t0, SLOT_OFFSET(t0) // Load the method from the slot + jalr zero, t0, 0 // Tail-call the method + +3: + li \receiver, 0 + li \sel, 0 + fmv.d.x fa0, zero + fmv.d.x fa1, zero + jalr zero, ra, 0 + +4: + add sp, sp, -(ARGUMENT_SPILL_SIZE) + + // Spill function arguments + sd a0, 0(sp) + sd a1, 8(sp) + sd a2, 16(sp) + sd a3, 24(sp) + sd a4, 32(sp) + sd a5, 40(sp) + sd a6, 48(sp) + sd a7, 56(sp) + + // Spill FP arguments + fsd fa0, 64(sp) + fsd fa1, 72(sp) + fsd fa2, 80(sp) + fsd fa3, 88(sp) + fsd fa4, 96(sp) + fsd fa5, 104(sp) + fsd fa6, 112(sp) + fsd fa7, 120(sp) + + sd fp, 128(sp) + sd ra, 136(sp) + + add fp, sp, 128 + add sp, sp, -16 + + sd \receiver, 0(sp) // it is convenient if \receiver is spilled at sp + + .cfi_def_cfa fp, 16 + .cfi_offset fp, -16 + .cfi_offset ra, -8 + + add a0, sp, zero // &self in first argument + call CDECL(slowMsgLookup) + + add t0, a0, zero // IMP -> t0 + + ld a0, 16(sp) + ld a1, 24(sp) + ld a2, 32(sp) + ld a3, 40(sp) + ld a4, 48(sp) + ld a5, 56(sp) + ld a6, 64(sp) + ld a7, 72(sp) + + fld fa0, 80(sp) + fld fa1, 88(sp) + fld fa2, 96(sp) + fld fa3, 104(sp) + fld fa4, 112(sp) + fld fa5, 120(sp) + fld fa6, 128(sp) + fld fa7, 136(sp) + + ld fp, 144(sp) + ld ra, 152(sp) + + ld \receiver, 0(sp) + + add sp, sp, ARGUMENT_SPILL_SIZE + add sp, sp, 16 + + jalr zero, t0, 0 // Tail-call the method + +5: + // Load address of SmallObjectClasses + auipc t1, %pcrel_hi(CDECL(SmallObjectClasses)) + addi t1, t1, %pcrel_lo(5b) + + // Calculate array offset (INDEX * 2^3) + slli t0, t0, 3 + add t0, t1, t0 + + ld t0, 0(t0) + + j 0b + .cfi_endproc +.endm + +.globl CDECL(objc_msgSend_fpret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), %function) +.globl CDECL(objc_msgSend) +TYPE_DIRECTIVE(CDECL(objc_msgSend), %function) +.globl CDECL(objc_msgSend_stret) +CDECL(objc_msgSend): +CDECL(objc_msgSend_fpret): + MSGSEND a0, a1 +CDECL(objc_msgSend_stret): + MSGSEND a1, a2 // Pointer to stack frame in a0 diff --git a/src/objc_msgSend.x86-32.S b/src/objc_msgSend.x86-32.S new file mode 100644 index 0000000..99b4f4e --- /dev/null +++ b/src/objc_msgSend.x86-32.S @@ -0,0 +1,132 @@ +.macro MSGSEND receiver, sel, fpret + .cfi_startproc + movl \receiver(%esp), %eax + test %eax, %eax # If the receiver is nil + jz 4f # return nil + test $SMALLOBJ_MASK, %eax # Check if the receiver is a small object + jnz 6f # Get the small object class + + mov (%eax), %eax # Load the class +1: # classLoaded + movl \sel(%esp), %ecx + mov DTABLE_OFFSET(%eax), %eax # Load the dtable from the class + + mov (%ecx), %ecx # Load the selector index + + # Register use at this point: + # %eax: dtable + # %ecx: Selector index + # %edx: selector index fragment + + mov SHIFT_OFFSET(%eax), %edx # Load the shift (dtable size) + cmpl $8, %edx # If this is a small dtable, jump to the small dtable handlers + je 2f + cmpl $0, %edx + je 3f + + mov %ecx, %edx + shrl $16, %edx + movl DATA_OFFSET(%eax, %edx, 4), %eax +2: # dtable16: + movzbl %ch, %edx + movl DATA_OFFSET(%eax, %edx, 4), %eax +3: # dtable8: + movzbl %cl, %edx + movl DATA_OFFSET(%eax, %edx, 4), %eax + + test %eax, %eax + jz 5f # Nil slot - invoke some kind of forwarding mechanism + mov SLOT_OFFSET(%eax), %ecx +#ifdef _MSC_VER + call *CDECL(__guard_check_icall_fptr) +#endif + jmp *%ecx +4: # returnNil: +.if \fpret + fldz +.else + xor %eax, %eax # return 0 (int) + xor %edx, %edx # Return 64-bit zero (%edx is + # caller-save, so it's safe to do this in the general case. +.endif + ret +5: # slowSend: + mov \sel(%esp), %ecx + lea \receiver(%esp), %eax + + push %ecx # Unused, stack alignment + push %ecx # _cmd + push %eax # &self + .cfi_def_cfa_offset 16 + call CDECL(slowMsgLookup)@PLT + add $12, %esp # restore the stack + + +#ifdef _MSC_VER + mov %eax, %ecx + call *CDECL(__guard_check_icall_fptr) + jmp *%ecx +#else + jmp *%eax +#endif +6: # smallObject: + push %ebx # Save old %ebx + calll 7f +7: + popl %ebx; +8: +#if __ELF__ + # ELF can support GOT-relative addressing; + # PE/COFF and Mach-O need a text relocation. + addl $_GLOBAL_OFFSET_TABLE_+(8b-7b), %ebx + leal SmallObjectClasses@GOTOFF(%ebx), %eax +#else + leal CDECL(SmallObjectClasses), %eax +#endif + mov (%eax), %eax + popl %ebx + jmp 1b + .cfi_endproc +.endm + +#ifdef _WIN32 +.text +.def @feat.00; +.scl 3; +.type 0; +.endef +.globl @feat.00 +@feat.00 = 1 +.def _objc_msgSend; +.scl 2; +.type 32; +.endef +.def _objc_msgSend_fpret; +.scl 2; +.type 32; +.endef +.def _objc_msgSend_stret; +.scl 2; +.type 32; +.endef +#endif + +.globl CDECL(objc_msgSend_fpret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), @function) +CDECL(objc_msgSend_fpret): + MSGSEND 4, 8, 1 +.globl CDECL(objc_msgSend) +TYPE_DIRECTIVE(CDECL(objc_msgSend), @function) +CDECL(objc_msgSend): + MSGSEND 4, 8, 0 +.globl CDECL(objc_msgSend_stret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_stret), @function) +CDECL(objc_msgSend_stret): + MSGSEND 8, 12, 0 + +#ifdef _WIN32 + .section .drectve,"yn" + EXPORT_SYMBOL(objc_msgSend) + EXPORT_SYMBOL(objc_msgSend_stret) + EXPORT_SYMBOL(objc_msgSend_fpret) +#endif diff --git a/src/objc_msgSend.x86-64.S b/src/objc_msgSend.x86-64.S new file mode 100644 index 0000000..2d15314 --- /dev/null +++ b/src/objc_msgSend.x86-64.S @@ -0,0 +1,315 @@ + +#ifdef _WIN64 +# define START_PROC(x) .seh_proc x +# define END_PROC(x) .seh_endproc +# define FRAME_OFFSET(x) .seh_stackalloc x +# define FIRST_ARGUMENT_STR "%rcx" +# define FIRST_ARGUMENT %rcx +# define SECOND_ARGUMENT %rdx +# define THIRD_ARGUMENT %r8 +#else +# define START_PROC(x) .cfi_startproc +# define END_PROC(x) .cfi_endproc +# define FRAME_OFFSET(x) .cfi_adjust_cfa_offset x +# define FIRST_ARGUMENT_STR "%rdi" +# define FIRST_ARGUMENT %rdi +# define SECOND_ARGUMENT %rsi +# define THIRD_ARGUMENT %rdx +#endif + +.macro MSGSEND fnname receiver, sel + START_PROC(\fnname) # Start emitting unwind data. We + # don't actually care about any of + # the stuff except the slow call, + # because that's the only one that + # can throw. + + test \receiver, \receiver # If the receiver is nil + jz 4f # return nil + movq $SMALLOBJ_MASK, %r10 # Load the small object mask + test \receiver, %r10 # Check if the receiver is a small object + jnz 6f # Get the small object class + + mov (\receiver), %r10 # Load the dtable from the class +1: # classLoaded + mov DTABLE_OFFSET(%r10), %r10 # Load the dtable from the class into r10 + mov %rax, -8(%rsp) # %rax contains information for variadic calls + mov %rbx, -16(%rsp) # On the fast path, spill into the red zone + mov (\sel), %eax # Load the selector index into %eax + mov SHIFT_OFFSET(%r10), %r11d # Load the shift (dtable size) into r11 + cmpl $8, %r11d # If this is a small dtable, jump to the small dtable handlers + je 2f + cmpl $0, %r11d + je 3f + + movl %eax, %r11d + shrl $16, %r11d + movq DATA_OFFSET(%r10, %r11, 8), %r10 +2: # dtable16: + movzbl %ah, %ebx + movq DATA_OFFSET(%r10, %rbx, 8), %r10 +3: # dtable8: + movzbl %al, %ebx + mov -8(%rsp), %rax + movq DATA_OFFSET(%r10, %rbx, 8), %r10 + mov -16(%rsp), %rbx + test %r10, %r10 + jz 5f # Nil slot - invoke some kind of forwarding mechanism + mov SLOT_OFFSET(%r10), %r10 + +7: +#ifdef WITH_TRACING + + push %r12 + push %r13 + push %r10 + + mov (\sel), %r11 # Load the selector index + lea tracing_dtable(%rip), %r10 + mov (%r10), %r10 + + mov SHIFT_OFFSET(%r10), %r13 # Load the shift (dtable size) + mov DATA_OFFSET(%r10), %r12 # load the address of the start of the array + pop %r10 + cmpl $8, %r13d # If this is a small dtable, jump to the small dtable handlers + je 10f + cmpl $0, %r13d + je 11f + + mov %r11, %r13 + and $0xff0000, %r13 + shrl $13, %r13d # Right shift 16, but then left shift by 3 *sizeof(void*) + add %r13, %r12 + mov (%r12), %r12 + mov DATA_OFFSET(%r12), %r12 +10: # dtable16: + mov %r11, %r13 + and $0xff00, %r13 + shrl $5, %r13d + add %r13, %r12 + mov (%r12), %r12 + mov DATA_OFFSET(%r12), %r12 +11: # dtable8: + mov %r11, %r13 + and $0xff, %r13 + shll $3, %r13d + add %r13, %r12 + mov (%r12), %r11 + pop %r13 + pop %r12 + test %r11, %r11 + jz 12f + + push %rax # We need to preserve all registers that may contain arguments: + push %rdi + push %rsi + push %rdx + push %rcx + push %r8 + push %r9 + push %r10 + push %r11 + mov \receiver, %rdi # Arg 0 is receiver + mov \sel, %rsi # Arg 1 is selector + mov %r10, %rdx # Arg 2 is IMP + mov $0, %rcx # Arg 3 is entry / exit (0/1) + mov $0, %r8 # Arg 4 is return value (0 on entry) + + call *%r11 # Call the tracing function + cmpq $0, %rax + jz 13f # If it returns 0, don't call the end-tracing function. + cmpq $1, %rax # If it returns 1, do call the tracing function + jne 14f # Any other value is an interposition + # function to call instead of the method + + call pushTraceReturnStack # rax now contains a thread-local buffer for storing returns + + pop %r11 # Restore all of the argument registers + pop %r10 # except rax, which we'll need before the call + pop %r9 + pop %r8 + pop %rcx + pop %rdx + pop %rsi + pop %rdi + + mov \receiver, (%rax) # Store the receiver in TLS + mov \sel, 8(%rax) # Store the selector in TLS + mov %r10, 16(%rax) # Store the method in TLS + mov %r11, 24(%rax) # Store the tracing function in TLS + mov 8(%rsp), %r11 # r11 now contains the return address + mov %r11, 32(%rax) # Store the method-return address in TLS + + pop %rax + pop %r11 # r11 now contains the return address, but we don't care + + call *%r10 # Call the IMP. The stack should now be in the same state + # that it was on entry into this function + + push %rax # Now we are free to clobber argument + push %rdx # registers, but we must preserve return registers... + + call popTraceReturnStack # rax now contains a thread-local buffer for storing returns + + push %rax # save the return value, because we'll need it after the tracing function call + mov (%rax), %rdi # Load the receiver into arg 0 + mov 8(%rax), %rsi # Load the selector into arg 1 + mov 16(%rax), %rdx # Load the IMP into arg 3 + mov $1, %rcx # Arg 4 is 1 (tracing on exit) + mov %rax, %r8 # Arg 5 is the return result + + mov 24(%rax), %r11 # Reload the address of the tracing function + + call *%r11 # Call the tracing function + pop %rax # Reload the real return address + mov 32(%rax), %r11 + pop %rdx # Reload saved values + pop %rax + jmp *%r11 # Simulate a return by jumping to the cached return address + +13: # Skip tracing on exit and just tail-call the method + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rcx + pop %rdx + pop %rsi + pop %rdi + pop %rax + jmp *%r10 + +14: + mov %rax, %r10 + pop %r9 + pop %r9 + pop %r9 + pop %r8 + pop %rcx + pop %rdx + pop %rsi + pop %rdi + pop %rax + +12: +#endif // WITH_TRACING +#ifdef _MSC_VER + mov %r10, %rax + jmp *__guard_dispatch_icall_fptr(%rip) +#else + jmp *%r10 +#endif +4: # returnNil: + # Both of the return registers are + # callee-save on x86-64, so we can + # return 0 in both in the same code: + xor %rax, %rax # Return 0 as an integer + pxor %xmm0, %xmm0 # Return 0 as a floating point value + ret +5: # slowSend: + push %rax # We need to preserve all registers that may contain arguments: + push %rbx + push %rcx + push %r8 + push %r9 + + sub $0x98, %rsp + movups %xmm0, 0x80(%rsp) + movups %xmm1, 0x70(%rsp) + movups %xmm2, 0x60(%rsp) + movups %xmm3, 0x50(%rsp) + movups %xmm4, 0x40(%rsp) + movups %xmm5, 0x30(%rsp) + movups %xmm6, 0x20(%rsp) + movups %xmm7, 0x10(%rsp) + +#rdi rsi rdx + # We're (potentially) modifying the self argument with the lookup, so we don't want to be +.ifc "\receiver", FIRST_ARGUMENT_STR + push FIRST_ARGUMENT + mov %rsp, FIRST_ARGUMENT + push SECOND_ARGUMENT # Save _cmd (not preserved across calls) + push THIRD_ARGUMENT +.else + push FIRST_ARGUMENT # Save the sret pointer + push SECOND_ARGUMENT # Save self where it can be modified + mov %rsp, FIRST_ARGUMENT + push THIRD_ARGUMENT + mov THIRD_ARGUMENT, SECOND_ARGUMENT # move _cmd to where the callee expects it to be +.endif + + FRAME_OFFSET(0xD8) + call CDECL(slowMsgLookup) # Call the slow lookup function + mov %rax, %r10 # Load the returned IMP + + pop THIRD_ARGUMENT + pop SECOND_ARGUMENT + pop FIRST_ARGUMENT + + movups 0x80(%rsp), %xmm0 + movups 0x70(%rsp), %xmm1 + movups 0x60(%rsp), %xmm2 + movups 0x50(%rsp), %xmm3 + movups 0x40(%rsp), %xmm4 + movups 0x30(%rsp), %xmm5 + movups 0x20(%rsp), %xmm6 + movups 0x10(%rsp), %xmm7 + add $0x98, %rsp + + pop %r9 + pop %r8 + pop %rcx + pop %rbx + pop %rax + jmp 7b +6: # smallObject: + and \receiver, %r10 # Find the small int type + lea CDECL(SmallObjectClasses)(%rip), %r11 + mov (%r11, %r10, 8), %r10 + jmp 1b + END_PROC(\fnname) +.endm +#ifdef _WIN64 +.text +.def objc_msgSend; +.scl 2; +.type 32; +.endef +.def objc_msgSend_fpret; +.scl 2; +.type 32; +.endef +.def objc_msgSend_stret; +.scl 2; +.type 32; +.endef +.globl CDECL(objc_msgSend_fpret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), @function) +.globl CDECL(objc_msgSend) +TYPE_DIRECTIVE(CDECL(objc_msgSend), @function) +CDECL(objc_msgSend_fpret): +CDECL(objc_msgSend): + MSGSEND objc_msgSend, %rcx, %rdx +.globl CDECL(objc_msgSend_stret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_stret), @function) +CDECL(objc_msgSend_stret): + MSGSEND objc_msgSend_stret, %rdx, %r8 +.section .drectve,"yn" +EXPORT_SYMBOL(objc_msgSend) + +EXPORT_SYMBOL(objc_msgSend_fpret) + +EXPORT_SYMBOL(objc_msgSend_stret) +#else +.globl CDECL(objc_msgSend) +TYPE_DIRECTIVE(CDECL(objc_msgSend), @function) +.globl CDECL(objc_msgSend_fpret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_fpret), @function) +CDECL(objc_msgSend_fpret): +CDECL(objc_msgSend): + MSGSEND objc_msgSend, %rdi, %rsi +.globl CDECL(objc_msgSend_stret) +TYPE_DIRECTIVE(CDECL(objc_msgSend_stret), @function) +CDECL(objc_msgSend_stret): + MSGSEND objc_msgSend_stret, %rsi, %rdx +#endif diff --git a/src/objcxx_eh.cc b/src/objcxx_eh.cc new file mode 100644 index 0000000..8ef5baf --- /dev/null +++ b/src/objcxx_eh.cc @@ -0,0 +1,311 @@ +#include +#include +#include +#include "dwarf_eh.h" +#include "objcxx_eh_private.h" +#include "objcxx_eh.h" +#include "objc/objc-arc.h" + +/** + * Helper function that has a custom personality function. + * This calls `cxx_throw` and has a destructor that must be run. We intercept + * the personality function calls and inspect the in-flight C++ exception. + */ +int eh_trampoline(); + +uint64_t cxx_exception_class; + +using namespace __cxxabiv1; + +namespace +{ +/** + * Helper needed by the unwind helper headers. + */ +inline _Unwind_Reason_Code continueUnwinding(struct _Unwind_Exception *ex, + struct _Unwind_Context *context) +{ +#if defined(__arm__) && !defined(__ARM_DWARF_EH__) + if (__gnu_unwind_frame(ex, context) != _URC_OK) { return _URC_FAILURE; } +#endif + return _URC_CONTINUE_UNWIND; +} + + +/** + * Flag indicating that we've already inspected a C++ exception and found all + * of the offsets. + */ +std::atomic done_setup; +/** + * The offset of the C++ type_info object in a thrown exception from the unwind + * header in a `__cxa_exception`. + */ +std::atomic type_info_offset; +/** + * The size of the `_Unwind_Exception` (including padding) in a + * `__cxa_exception`. + */ +std::atomic exception_struct_size; + + +/** + * Exception cleanup function for C++ exceptions that wrap Objective-C + * exceptions. + */ +void exception_cleanup(_Unwind_Reason_Code reason, + struct _Unwind_Exception *ex) +{ + // __cxa_exception takes a pointer to the end of the __cxa_exception + // structure, and so we find that by adding the size of the generic + // exception structure + padding to the pointer to the generic exception + // structure field of the enclosing structure. + auto *cxxEx = pointer_add<__cxa_exception>(ex, exception_struct_size); + __cxa_free_exception(cxxEx); +} + +} + +using namespace std; + + +static BOOL isKindOfClass(Class thrown, Class type) +{ + do + { + if (thrown == type) + { + return YES; + } + thrown = class_getSuperclass(thrown); + } while (Nil != thrown); + + return NO; +} + + + + +namespace gnustep +{ + namespace libobjc + { + __objc_type_info::__objc_type_info(const char *name) : type_info(name) {} + + bool __objc_type_info::__is_pointer_p() const { return true; } + + bool __objc_type_info::__is_function_p() const { return false; } + + bool __objc_type_info::__do_catch(const type_info *thrown_type, + void **thrown_object, + unsigned) const + { + assert(0); + return false; + }; + + bool __objc_type_info::__do_upcast( + const __class_type_info *target, + void **thrown_object) const + { + return false; + }; + + + /** + * The `id` type is mangled to `@id`, which is not a valid mangling + * of anything else. + */ + __objc_id_type_info::__objc_id_type_info() : __objc_type_info("@id") {}; + } + + static inline id dereference_thrown_object_pointer(void** obj) { + /* libc++-abi does not have __is_pointer_p and won't do the double dereference + * required to get the object pointer. We need to do it ourselves if we have + * caught an exception with libc++'s exception class. */ +#ifndef __MINGW32__ + if (cxx_exception_class == llvm_cxx_exception_class) { + return **(id**)obj; + } + return *(id*)obj; +#else +#ifdef _LIBCPP_VERSION + return **(id**)obj; +#else + return *(id*)obj; +#endif // _LIBCPP_VERSION +#endif // __MINGW32__ + } +}; + + +static bool AppleCompatibleMode = true; +extern "C" int objc_set_apple_compatible_objcxx_exceptions(int newValue) +{ + bool old = AppleCompatibleMode; + AppleCompatibleMode = newValue; + return old; +} + +gnustep::libobjc::__objc_class_type_info::~__objc_class_type_info() {} +gnustep::libobjc::__objc_id_type_info::~__objc_id_type_info() {} +bool gnustep::libobjc::__objc_class_type_info::__do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const +{ + id thrown = nullptr; + bool found = false; + // Id throw matches any ObjC catch. This may be a silly idea! + if (dynamic_cast(thrownType) + || (AppleCompatibleMode && + dynamic_cast(thrownType))) + { + thrown = dereference_thrown_object_pointer(obj); + // nil only matches id catch handlers in Apple-compatible mode, or when thrown as an id + if (0 == thrown) + { + return false; + } + // Check whether the real thrown object matches the catch type. + found = isKindOfClass(object_getClass(thrown), + (Class)objc_getClass(name())); + } + else if (dynamic_cast(thrownType)) + { + thrown = dereference_thrown_object_pointer(obj); + found = isKindOfClass((Class)objc_getClass(thrownType->name()), + (Class)objc_getClass(name())); + } + if (found) + { + *obj = (void*)thrown; + } + + return found; +}; + +bool gnustep::libobjc::__objc_id_type_info::__do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const +{ + // Id catch matches any ObjC throw + if (dynamic_cast(thrownType)) + { + *obj = dereference_thrown_object_pointer(obj); + DEBUG_LOG("gnustep::libobjc::__objc_id_type_info::__do_catch caught 0x%x\n", *obj); + return true; + } + if (dynamic_cast(thrownType)) + { + *obj = dereference_thrown_object_pointer(obj); + DEBUG_LOG("gnustep::libobjc::__objc_id_type_info::__do_catch caught 0x%x\n", *obj); + return true; + } + DEBUG_LOG("gnustep::libobjc::__objc_id_type_info::__do_catch returning false\n"); + return false; +}; + +/** + * Public interface to the Objective-C++ exception mechanism + */ +extern "C" +{ +/** + * The public symbol that the compiler uses to indicate the Objective-C id type. + */ +OBJC_PUBLIC gnustep::libobjc::__objc_id_type_info __objc_id_type_info; + +struct _Unwind_Exception *objc_init_cxx_exception(id obj) +{ + id *newEx = static_cast(__cxa_allocate_exception(sizeof(id))); + *newEx = obj; + _Unwind_Exception *ex = pointer_add<_Unwind_Exception>(newEx, -exception_struct_size); + *pointer_add(ex, type_info_offset) = &__objc_id_type_info; + ex->exception_class = cxx_exception_class; + ex->exception_cleanup = exception_cleanup; + __cxa_get_globals()->uncaughtExceptions++; + return ex; +} + +void* objc_object_for_cxx_exception(void *thrown_exception, int *isValid) +{ + ptrdiff_t type_offset = type_info_offset; + if (type_offset == 0) + { + *isValid = 0; + return nullptr; + } + + const std::type_info *thrownType = + *pointer_add(thrown_exception, type_offset); + + if (!dynamic_cast(thrownType) && + !dynamic_cast(thrownType)) + { + *isValid = 0; + return 0; + } + *isValid = 1; + return *pointer_add(thrown_exception, exception_struct_size); +} + +} // extern "C" + + +MagicValueHolder::MagicValueHolder() { magic_value = magic; } + +/** + * Function that simply throws an instance of `MagicValueHolder`. + */ +PRIVATE void cxx_throw() +{ + MagicValueHolder x; + throw x; +} + +/** + * Personality function that wraps the C++ personality and inspects the C++ + * exception structure on the way past. This should be used only for the + * `eh_trampoline` function. + */ +extern "C" +PRIVATE +BEGIN_PERSONALITY_FUNCTION(test_eh_personality) + // Don't bother with a mutex here. It doesn't matter if two threads set + // these values at the same time. + if (!done_setup) + { + uint64_t cls = __builtin_bswap64(exceptionClass); + type_info_offset = find_backwards(exceptionObject, &typeid(MagicValueHolder)); + exception_struct_size = find_forwards(exceptionObject, MagicValueHolder::magic); + cxx_exception_class = exceptionClass; + done_setup = true; + } + return CALL_PERSONALITY_FUNCTION(__gxx_personality_v0); +} + +/** + * Probe the C++ exception handling implementation. This throws a C++ + * exception through a function that uses `test_eh_personality` as its + * personality function, allowing us to inspect a C++ exception that is in a + * known state. + */ +#ifndef __MINGW32__ +extern "C" void test_cxx_eh_implementation() +{ + if (done_setup) + { + return; + } + bool caught = false; + try + { + eh_trampoline(); + } + catch(MagicValueHolder) + { + caught = true; + } + assert(caught); +} +#endif diff --git a/src/objcxx_eh_mingw.cc b/src/objcxx_eh_mingw.cc new file mode 100644 index 0000000..a42c7ea --- /dev/null +++ b/src/objcxx_eh_mingw.cc @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include "dwarf_eh.h" +#include "objcxx_eh_private.h" +#include "objcxx_eh.h" +#include "objc/runtime.h" +#include "objc/objc-arc.h" +#include "objc/objc-exception.h" +#include "objc/hooks.h" + +namespace __cxxabiv1 +{ + struct __cxa_refcounted_exception + { + int referenceCount; + }; +} + +using namespace __cxxabiv1; + +extern "C" __cxa_refcounted_exception* __cxa_init_primary_exception(void *obj, std::type_info *tinfo, void (*dest) (void *)); + +static void eh_cleanup(void *exception) +{ + DEBUG_LOG("eh_cleanup: Releasing 0x%x\n", *(id*)exception); + objc_release(*(id*)exception); +} + +/** + * Flag indicating that we've already inspected a C++ exception and found all + * of the offsets. + */ +std::atomic done_setup; + +/** + * The size of the `_Unwind_Exception` (including padding) in a + * `__cxa_exception`. + */ +std::atomic exception_struct_size; + +extern "C" +OBJC_PUBLIC +void objc_exception_throw(id object) +{ + // Don't bother with a mutex here. It doesn't matter if two threads set + // these values at the same time. + if (!done_setup) + { + DEBUG_LOG("objc_exception_throw: Doing initial setup\n"); + MagicValueHolder *magicExc = (MagicValueHolder *)__cxa_allocate_exception(sizeof(MagicValueHolder)); + MagicValueHolder x; + *magicExc = x; + + __cxa_refcounted_exception *header = + __cxa_init_primary_exception(magicExc, & __objc_id_type_info, NULL); + exception_struct_size = find_forwards(header, MagicValueHolder::magic); + __cxa_free_exception(magicExc); + + DEBUG_LOG("objc_exception_throw: exception_struct_size: 0x%x\n", unsigned(exception_struct_size)); + + done_setup = true; + } + + id *exc = (id *)__cxa_allocate_exception(sizeof(id)); + *exc = object; + objc_retain(object); + DEBUG_LOG("objc_exception_throw: Throwing 0x%x\n", *exc); + + __cxa_eh_globals *globals = __cxa_get_globals (); + globals->uncaughtExceptions += 1; + __cxa_refcounted_exception *header = + __cxa_init_primary_exception(exc, & __objc_id_type_info, eh_cleanup); + header->referenceCount = 1; + + _Unwind_Exception *unwindHeader = pointer_add<_Unwind_Exception>(header, exception_struct_size - sizeof(_Unwind_Exception)); + _Unwind_Reason_Code err = _Unwind_RaiseException (unwindHeader); + + if (_URC_END_OF_STACK == err && 0 != _objc_unexpected_exception) + { + DEBUG_LOG("Invoking _objc_unexpected_exception\n"); + _objc_unexpected_exception(object); + } + DEBUG_LOG("Throw returned %d\n",(int) err); + abort(); +} + +OBJC_PUBLIC extern objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler) +{ + return __atomic_exchange_n(&_objc_unexpected_exception, handler, __ATOMIC_SEQ_CST); +} + +extern "C" void* __cxa_begin_catch(void *object); + +extern "C" +OBJC_PUBLIC +void* objc_begin_catch(void* object) +{ + return __cxa_begin_catch(object); +} + +extern "C" void __cxa_end_catch(); + +extern "C" +OBJC_PUBLIC +void objc_end_catch() +{ + __cxa_end_catch(); +} + +extern "C" void __cxa_rethrow(); + +extern "C" +OBJC_PUBLIC +void objc_exception_rethrow() +{ + __cxa_rethrow(); +} + +extern "C" EXCEPTION_DISPOSITION __gxx_personality_seh0(PEXCEPTION_RECORD ms_exc, + void *this_frame, + PCONTEXT ms_orig_context, + PDISPATCHER_CONTEXT ms_disp); + +extern "C" +OBJC_PUBLIC +EXCEPTION_DISPOSITION __gnu_objc_personality_seh0(PEXCEPTION_RECORD ms_exc, + void *this_frame, + PCONTEXT ms_orig_context, + PDISPATCHER_CONTEXT ms_disp) +{ + return __gxx_personality_seh0(ms_exc, this_frame, ms_orig_context, ms_disp); +} diff --git a/src/properties.m b/src/properties.m new file mode 100644 index 0000000..7c3dcc3 --- /dev/null +++ b/src/properties.m @@ -0,0 +1,613 @@ +#include "objc/runtime.h" +#include "objc/objc-arc.h" +#include +#include +#include +#include +#include "class.h" +#include "properties.h" +#include "spinlock.h" +#include "visibility.h" +#include "nsobject.h" +#include "gc_ops.h" +#include "lock.h" + +PRIVATE int spinlocks[spinlock_count]; + +/** + * Public function for getting a property. + */ +OBJC_PUBLIC +id objc_getProperty(id obj, SEL _cmd, ptrdiff_t offset, BOOL isAtomic) +{ + if (nil == obj) { return nil; } + char *addr = (char*)obj; + addr += offset; + id ret; + if (isAtomic) + { + volatile int *lock = lock_for_pointer(addr); + lock_spinlock(lock); + ret = *(id*)addr; + ret = objc_retain(ret); + unlock_spinlock(lock); + ret = objc_autoreleaseReturnValue(ret); + } + else + { + ret = *(id*)addr; + ret = objc_retainAutoreleaseReturnValue(ret); + } + return ret; +} + +OBJC_PUBLIC +void objc_setProperty(id obj, SEL _cmd, ptrdiff_t offset, id arg, BOOL isAtomic, BOOL isCopy) +{ + if (nil == obj) { return; } + char *addr = (char*)obj; + addr += offset; + + if (isCopy) + { + arg = [arg copy]; + } + else + { + arg = objc_retain(arg); + } + id old; + if (isAtomic) + { + volatile int *lock = lock_for_pointer(addr); + lock_spinlock(lock); + old = *(id*)addr; + *(id*)addr = arg; + unlock_spinlock(lock); + } + else + { + old = *(id*)addr; + *(id*)addr = arg; + } + objc_release(old); +} + +OBJC_PUBLIC +void objc_setProperty_atomic(id obj, SEL _cmd, id arg, ptrdiff_t offset) +{ + char *addr = (char*)obj; + addr += offset; + arg = objc_retain(arg); + volatile int *lock = lock_for_pointer(addr); + lock_spinlock(lock); + id old = *(id*)addr; + *(id*)addr = arg; + unlock_spinlock(lock); + objc_release(old); +} + +OBJC_PUBLIC +void objc_setProperty_atomic_copy(id obj, SEL _cmd, id arg, ptrdiff_t offset) +{ + char *addr = (char*)obj; + addr += offset; + + arg = [arg copy]; + volatile int *lock = lock_for_pointer(addr); + lock_spinlock(lock); + id old = *(id*)addr; + *(id*)addr = arg; + unlock_spinlock(lock); + objc_release(old); +} + +OBJC_PUBLIC +void objc_setProperty_nonatomic(id obj, SEL _cmd, id arg, ptrdiff_t offset) +{ + char *addr = (char*)obj; + addr += offset; + arg = objc_retain(arg); + id old = *(id*)addr; + *(id*)addr = arg; + objc_release(old); +} + +OBJC_PUBLIC +void objc_setProperty_nonatomic_copy(id obj, SEL _cmd, id arg, ptrdiff_t offset) +{ + char *addr = (char*)obj; + addr += offset; + id old = *(id*)addr; + *(id*)addr = [arg copy]; + objc_release(old); +} + +OBJC_PUBLIC +void objc_copyCppObjectAtomic(void *dest, const void *src, + void (*copyHelper) (void *dest, const void *source)) +{ + volatile int *lock = lock_for_pointer(src < dest ? src : dest); + volatile int *lock2 = lock_for_pointer(src < dest ? dest : src); + lock_spinlock(lock); + lock_spinlock(lock2); + copyHelper(dest, src); + unlock_spinlock(lock); + unlock_spinlock(lock2); +} + +OBJC_PUBLIC +void objc_getCppObjectAtomic(void *dest, const void *src, + void (*copyHelper) (void *dest, const void *source)) +{ + volatile int *lock = lock_for_pointer(src); + lock_spinlock(lock); + copyHelper(dest, src); + unlock_spinlock(lock); +} + +OBJC_PUBLIC +void objc_setCppObjectAtomic(void *dest, const void *src, + void (*copyHelper) (void *dest, const void *source)) +{ + volatile int *lock = lock_for_pointer(dest); + lock_spinlock(lock); + copyHelper(dest, src); + unlock_spinlock(lock); +} + +/** + * Structure copy function. This is provided for compatibility with the Apple + * APIs (it's an ABI function, so it's semi-public), but it's a bad design so + * it's not used. The problem is that it does not identify which of the + * pointers corresponds to the object, which causes some excessive locking to + * be needed. + */ +OBJC_PUBLIC +void objc_copyPropertyStruct(void *dest, + void *src, + ptrdiff_t size, + BOOL atomic, + BOOL strong) +{ + if (atomic) + { + volatile int *lock = lock_for_pointer(src < dest ? src : dest); + volatile int *lock2 = lock_for_pointer(src < dest ? dest : src); + lock_spinlock(lock); + lock_spinlock(lock2); + memcpy(dest, src, size); + unlock_spinlock(lock); + unlock_spinlock(lock2); + } + else + { + memcpy(dest, src, size); + } +} + +/** + * Get property structure function. Copies a structure from an ivar to another + * variable. Locks on the address of src. + */ +OBJC_PUBLIC +void objc_getPropertyStruct(void *dest, + void *src, + ptrdiff_t size, + BOOL atomic, + BOOL strong) +{ + if (atomic) + { + volatile int *lock = lock_for_pointer(src); + lock_spinlock(lock); + memcpy(dest, src, size); + unlock_spinlock(lock); + } + else + { + memcpy(dest, src, size); + } +} + +/** + * Set property structure function. Copes a structure to an ivar. Locks on + * dest. + */ +OBJC_PUBLIC +void objc_setPropertyStruct(void *dest, + void *src, + ptrdiff_t size, + BOOL atomic, + BOOL strong) +{ + if (atomic) + { + volatile int *lock = lock_for_pointer(dest); + lock_spinlock(lock); + memcpy(dest, src, size); + unlock_spinlock(lock); + } + else + { + memcpy(dest, src, size); + } +} + + +OBJC_PUBLIC +objc_property_t class_getProperty(Class cls, const char *name) +{ + if (Nil == cls) + { + return NULL; + } + struct objc_property_list *properties = cls->properties; + while (NULL != properties) + { + for (int i=0 ; icount ; i++) + { + objc_property_t p = property_at_index(properties, i); + if (strcmp(property_getName(p), name) == 0) + { + return p; + } + } + properties = properties->next; + } + return NULL; +} + +OBJC_PUBLIC +objc_property_t* class_copyPropertyList(Class cls, unsigned int *outCount) +{ + if (Nil == cls) + { + if (NULL != outCount) { *outCount = 0; } + return NULL; + } + struct objc_property_list *properties = cls->properties; + if (!properties) + { + if (NULL != outCount) { *outCount = 0; } + return NULL; + } + unsigned int count = 0; + for (struct objc_property_list *l=properties ; NULL!=l ; l=l->next) + { + count += l->count; + } + if (NULL != outCount) + { + *outCount = count; + } + if (0 == count) + { + return NULL; + } + objc_property_t *list = calloc(sizeof(objc_property_t), count); + unsigned int out = 0; + for (struct objc_property_list *l=properties ; NULL!=l ; l=l->next) + { + for (int i=0 ; icount ; i++) + { + list[out++] = property_at_index(l, i); + } + } + return list; +} +static const char* property_getIVar(objc_property_t property) +{ + const char *iVar = property_getAttributes(property); + if (iVar != 0) + { + while ((*iVar != 0) && (*iVar != 'V')) + { + iVar++; + } + if (*iVar == 'V') + { + return iVar+1; + } + } + return 0; +} + +OBJC_PUBLIC +const char *property_getName(objc_property_t property) +{ + if (NULL == property) { return NULL; } + + const char *name = property->name; + if (NULL == name) { return NULL; } + if (name[0] == 0) + { + name += name[1]; + } + return name; +} + +/* + * The compiler stores the type encoding of the getter. We replace this with + * the type encoding of the property itself. We use a 0 byte at the start to + * indicate that the swap has taken place. + */ +static const char *property_getTypeEncoding(objc_property_t property) +{ + if (NULL == property) { return NULL; } + return property->type; +} + +OBJC_PUBLIC +const char *property_getAttributes(objc_property_t property) +{ + if (NULL == property) { return NULL; } + return property->attributes; +} + + +OBJC_PUBLIC +objc_property_attribute_t *property_copyAttributeList(objc_property_t property, + unsigned int *outCount) +{ + if (NULL == property) + { + if (NULL != outCount) + { + *outCount = 0; + } + return NULL; + } + objc_property_attribute_t attrs[12]; + int count = 0; + + const char *types = property_getTypeEncoding(property); + if (NULL != types) + { + attrs[count].name = "T"; + attrs[count].value = types; + count++; + } + // If the compiler provides a type encoding string, then it's more + // informative than the bitfields and should be treated as canonical. If + // the compiler didn't provide a type encoding string, then this will + // create a best-effort one. + const char *attributes = property_getAttributes(property); + for (int i=strlen(types)+1 ; attributes[i] != 0 ; i++) + { + assert(count<12); + if (attributes[i] == ',') + { + // Comma is never the last character in the string, so this should + // never push us past the end. + i++; + } + attrs[count].value = ""; + switch (attributes[i]) + { + case 'R': + attrs[count].name = "R"; + break; + case 'C': + attrs[count].name = "C"; + break; + case '&': + attrs[count].name = "&"; + break; + case 'D': + attrs[count].name = "D"; + break; + case 'W': + attrs[count].name = "W"; + break; + case 'N': + attrs[count].name = "N"; + break; + case 'G': + attrs[count].name = "G"; + attrs[count].value = sel_getName(property->getter); + i += strlen(attrs[count].value); + break; + case 'S': + attrs[count].name = "S"; + attrs[count].value = sel_getName(property->setter); + i += strlen(attrs[count].value); + break; + case 'V': + attrs[count].name = "V"; + attrs[count].value = attributes+i+1; + i += strlen(attributes+i)-1; + break; + default: + continue; + } + count++; + } + objc_property_attribute_t *propAttrs = calloc(sizeof(objc_property_attribute_t), count); + memcpy(propAttrs, attrs, count * sizeof(objc_property_attribute_t)); + if (NULL != outCount) + { + *outCount = count; + } + return propAttrs; +} + +static const objc_property_attribute_t *findAttribute(char attr, + const objc_property_attribute_t *attributes, + unsigned int attributeCount) +{ + // This linear scan is N^2 in the worst case, but that's still probably + // cheaper than sorting the array because N<12 + for (int i=0 ; iname[0]; + if (attr->value) + { + size_t len = strlen(attr->value); + memcpy(buffer, attr->value, len); + buffer += len; + } + *(buffer++) = ','; + } + return buffer; +} + +static const char *encodingFromAttrs(const objc_property_attribute_t *attributes, + unsigned int attributeCount) +{ + // Length of the attributes string (initially the number of keys and commas and trailing null) + size_t attributesSize = 2 * attributeCount; + for (int i=0 ; ivalue); + } + p.getter = NULL; + attr = findAttribute('G', attributes, attributeCount); + if (attr) + { + // TODO: We should be able to construct the full type encoding if we + // also have a type, but for now use an untyped selector. + p.getter = sel_registerName(attr->value); + } + p.setter = NULL; + attr = findAttribute('S', attributes, attributeCount); + if (attr) + { + // TODO: We should be able to construct the full type encoding if we + // also have a type, but for now use an untyped selector. + p.setter = sel_registerName(attr->value); + } + return p; +} + + +OBJC_PUBLIC +BOOL class_addProperty(Class cls, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount) +{ + if ((Nil == cls) || (NULL == name) || (class_getProperty(cls, name) != 0)) { return NO; } + + struct objc_property p = propertyFromAttrs(attributes, attributeCount, name); + + struct objc_property_list *l = calloc(1, sizeof(struct objc_property_list) + + sizeof(struct objc_property)); + l->count = 1; + l->size = sizeof(struct objc_property); + memcpy(&l->properties, &p, sizeof(struct objc_property)); + LOCK_RUNTIME_FOR_SCOPE(); + l->next = cls->properties; + cls->properties = l; + return YES; +} + +OBJC_PUBLIC +void class_replaceProperty(Class cls, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount) +{ + if ((Nil == cls) || (NULL == name)) { return; } + objc_property_t old = class_getProperty(cls, name); + if (NULL == old) + { + class_addProperty(cls, name, attributes, attributeCount); + return; + } + struct objc_property p = propertyFromAttrs(attributes, attributeCount, name); + LOCK_RUNTIME_FOR_SCOPE(); + memcpy(old, &p, sizeof(struct objc_property)); +} +OBJC_PUBLIC +char *property_copyAttributeValue(objc_property_t property, + const char *attributeName) +{ + if ((NULL == property) || (NULL == attributeName)) { return NULL; } + const char *attributes = property_getAttributes(property); + switch (attributeName[0]) + { + case 'T': + { + const char *types = property_getTypeEncoding(property); + return (NULL == types) ? NULL : strdup(types); + } + case 'D': + case 'R': + case 'W': + case 'C': + case '&': + case 'N': + { + return strchr(attributes, attributeName[0]) ? strdup("") : 0; + } + case 'V': + { + return strdup(property_getIVar(property)); + } + case 'S': + { + return strdup(sel_getName(property->setter)); + } + case 'G': + { + return strdup(sel_getName(property->getter)); + } + } + return 0; +} diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 0000000..e0144e2 --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,712 @@ +#include "objc/runtime.h" +#include "protocol.h" +#include "properties.h" +#include "class.h" +#include "lock.h" +#include "legacy.h" +#include +#include + +#define BUFFER_TYPE struct objc_protocol_list * +#include "buffer.h" + +// Get the functions for string hashing +#include "string_hash.h" + +static int protocol_compare(const char *name, + const struct objc_protocol *protocol) +{ + return string_compare(name, protocol->name); +} +static int protocol_hash(const struct objc_protocol *protocol) +{ + return string_hash(protocol->name); +} +#define MAP_TABLE_NAME protocol +#define MAP_TABLE_COMPARE_FUNCTION protocol_compare +#define MAP_TABLE_HASH_KEY string_hash +#define MAP_TABLE_HASH_VALUE protocol_hash +#include "hash_table.h" + +static protocol_table *known_protocol_table; +mutex_t protocol_table_lock; + +PRIVATE void init_protocol_table(void) +{ + protocol_initialize(&known_protocol_table, 128); + INIT_LOCK(protocol_table_lock); +} + +static void protocol_table_insert(const struct objc_protocol *protocol) +{ + protocol_insert(known_protocol_table, (void*)protocol); +} + +struct objc_protocol *protocol_for_name(const char *name) +{ + return protocol_table_get(known_protocol_table, name); +} + +static id incompleteProtocolClass(void) +{ + static id IncompleteProtocolClass = 0; + if (IncompleteProtocolClass == nil) + { + IncompleteProtocolClass = objc_getClass("__IncompleteProtocol"); + } + return IncompleteProtocolClass; +} + +/** + * Class used for legacy GCC protocols (`ProtocolGCC`). + */ +static id protocol_class_gcc; +/** + * Class used for legacy GNUstep V1 ABI protocols (`ProtocolGSv1`). + */ +static id protocol_class_gsv1; +/** + * Class used for protocols (`Protocol`). + */ +static id protocol_class_gsv2; + +static BOOL init_protocol_classes(void) +{ + if (nil == protocol_class_gcc) + { + protocol_class_gcc = objc_getClass("ProtocolGCC"); + } + if (nil == protocol_class_gsv1) + { + protocol_class_gsv1 = objc_getClass("ProtocolGSv1"); + } + if (nil == protocol_class_gsv2) + { + protocol_class_gsv2 = objc_getClass("Protocol"); + } + if ((nil == protocol_class_gcc) || + (nil == protocol_class_gsv1) || + (nil == protocol_class_gsv2)) + { + return NO; + } + return YES; +} + +static BOOL protocol_hasClassProperties(struct objc_protocol *p) +{ + if (!init_protocol_classes()) + { + return NO; + } + return p->isa == protocol_class_gsv2; +} + +static BOOL protocol_hasOptionalMethodsAndProperties(struct objc_protocol *p) +{ + if (!init_protocol_classes()) + { + return NO; + } + if (p->isa == protocol_class_gcc) + { + return NO; + } + return YES; +} + +static int isEmptyProtocol(struct objc_protocol *aProto) +{ + int isEmpty = + ((aProto->instance_methods == NULL) || + (aProto->instance_methods->count == 0)) && + ((aProto->class_methods == NULL) || + (aProto->class_methods->count == 0)) && + ((aProto->protocol_list == NULL) || + (aProto->protocol_list->count == 0)); + if (protocol_hasOptionalMethodsAndProperties(aProto)) + { + isEmpty &= (aProto->optional_instance_methods == NULL) || + (aProto->optional_instance_methods->count == 0); + isEmpty &= (aProto->optional_class_methods == NULL) || + (aProto->optional_class_methods->count == 0); + isEmpty &= (aProto->properties == 0) || (aProto->properties->count == 0); + isEmpty &= (aProto->optional_properties == 0) || (aProto->optional_properties->count == 0); + } + return isEmpty; +} + +// FIXME: Make p1 adopt all of the stuff in p2 +static void makeProtocolEqualToProtocol(struct objc_protocol *p1, + struct objc_protocol *p2) +{ +#define COPY(x) p1->x = p2->x + COPY(instance_methods); + COPY(class_methods); + COPY(protocol_list); + if (protocol_hasOptionalMethodsAndProperties(p1) && + protocol_hasOptionalMethodsAndProperties(p2)) + { + COPY(optional_instance_methods); + COPY(optional_class_methods); + COPY(properties); + COPY(optional_properties); + } +#undef COPY +} + +static struct objc_protocol *unique_protocol(struct objc_protocol *aProto) +{ + struct objc_protocol *oldProtocol = + protocol_for_name(aProto->name); + if (NULL == oldProtocol) + { + // This is the first time we've seen this protocol, so add it to the + // hash table and ignore it. + protocol_table_insert(aProto); + return aProto; + } + if (isEmptyProtocol(oldProtocol)) + { + if (isEmptyProtocol(aProto)) + { + return aProto; + // Add protocol to a list somehow. + } + else + { + // This protocol is not empty, so we use its definitions + makeProtocolEqualToProtocol(oldProtocol, aProto); + return aProto; + } + } + else + { + if (isEmptyProtocol(aProto)) + { + makeProtocolEqualToProtocol(aProto, oldProtocol); + return oldProtocol; + } + else + { + return oldProtocol; + //FIXME: We should really perform a check here to make sure the + //protocols are actually the same. + } + } +} + +static BOOL init_protocols(struct objc_protocol_list *protocols) +{ + if (!init_protocol_classes()) + { + return NO; + } + + for (unsigned i=0 ; icount ; i++) + { + struct objc_protocol *aProto = protocols->list[i]; + // Don't initialise a protocol twice + if ((aProto->isa == protocol_class_gcc) || + (aProto->isa == protocol_class_gsv1) || + (aProto->isa == protocol_class_gsv2)) + { + continue; + } + + // Protocols in the protocol list have their class pointers set to the + // version of the protocol class that they expect. + enum protocol_version version = + (enum protocol_version)(uintptr_t)aProto->isa; + switch (version) + { + default: + fprintf(stderr, "Unknown protocol version"); + abort(); +#ifdef OLDABI_COMPAT + case protocol_version_gcc: + protocols->list[i] = objc_upgrade_protocol_gcc((struct objc_protocol_gcc *)aProto); + assert(aProto->isa == protocol_class_gcc); + assert(protocols->list[i]->isa == protocol_class_gsv2); + aProto = protocols->list[i]; + break; + case protocol_version_gsv1: + protocols->list[i] = objc_upgrade_protocol_gsv1((struct objc_protocol_gsv1 *)aProto); + assert(aProto->isa == protocol_class_gsv1); + assert(protocols->list[i]->isa == protocol_class_gsv2); + aProto = protocols->list[i]; + break; +#endif + case protocol_version_gsv2: + aProto->isa = protocol_class_gsv2; + break; + } + // Initialize all of the protocols that this protocol refers to + if (NULL != aProto->protocol_list) + { + init_protocols(aProto->protocol_list); + } + // Replace this protocol with a unique version of it. + protocols->list[i] = unique_protocol(aProto); + } + return YES; +} + +PRIVATE void objc_init_protocols(struct objc_protocol_list *protocols) +{ + LOCK_FOR_SCOPE(&protocol_table_lock); + if (!init_protocols(protocols)) + { + set_buffered_object_at_index(protocols, buffered_objects++); + return; + } + if (buffered_objects == 0) { return; } + + // If we can load one protocol, then we can load all of them. + for (unsigned i=0 ; iname, p2->name) == 0) { return YES; } + + for (struct objc_protocol_list *list = p1->protocol_list ; + list != NULL ; list = list->next) + { + for (int i=0 ; icount ; i++) + { + if (strcmp(list->list[i]->name, p2->name) == 0) + { + return YES; + } + if (protocol_conformsToProtocol((Protocol*)list->list[i], p2)) + { + return YES; + } + } + } + return NO; +} + +BOOL class_conformsToProtocol(Class cls, Protocol *protocol) +{ + if (Nil == cls || NULL == protocol) { return NO; } + for ( ; Nil != cls ; cls = class_getSuperclass(cls)) + { + for (struct objc_protocol_list *protocols = cls->protocols; + protocols != NULL ; protocols = protocols->next) + { + for (int i=0 ; icount ; i++) + { + Protocol *p1 = (Protocol*)protocols->list[i]; + if (protocol_conformsToProtocol(p1, protocol)) + { + return YES; + } + } + } + } + return NO; +} + +static struct objc_protocol_method_description_list * +get_method_list(Protocol *p, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + struct objc_protocol_method_description_list *list; + if (isRequiredMethod) + { + if (isInstanceMethod) + { + list = p->instance_methods; + } + else + { + list = p->class_methods; + } + } + else + { + if (!protocol_hasOptionalMethodsAndProperties(p)) { return NULL; } + + if (isInstanceMethod) + { + list = p->optional_instance_methods; + } + else + { + list = p->optional_class_methods; + } + } + return list; +} + +struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, + BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *count) +{ + if ((NULL == p) || (NULL == count)){ return NULL; } + struct objc_protocol_method_description_list *list = + get_method_list(p, isRequiredMethod, isInstanceMethod); + *count = 0; + if (NULL == list || list->count == 0) { return NULL; } + + *count = list->count; + struct objc_method_description *out = + calloc(sizeof(struct objc_method_description), list->count); + for (int i=0 ; i < (list->count) ; i++) + { + out[i].name = protocol_method_at_index(list, i)->selector; + out[i].types = sel_getType_np(protocol_method_at_index(list, i)->selector); + } + return out; +} + +Protocol*__unsafe_unretained* protocol_copyProtocolList(Protocol *p, unsigned int *count) +{ + if (NULL == p) { return NULL; } + *count = 0; + if (p->protocol_list == NULL || p->protocol_list->count ==0) + { + return NULL; + } + + *count = p->protocol_list->count; + Protocol **out = calloc(sizeof(Protocol*), p->protocol_list->count); + for (int i=0 ; iprotocol_list->count ; i++) + { + out[i] = (Protocol*)p->protocol_list->list[i]; + } + return out; +} + +objc_property_t *protocol_copyPropertyList2(Protocol *p, unsigned int *outCount, + BOOL isRequiredProperty, BOOL isInstanceProperty) +{ + struct objc_property_list *properties = + isInstanceProperty ? + (isRequiredProperty ? p->properties : p->optional_properties) : + (isRequiredProperty ? p->class_properties : p->optional_class_properties); + if (NULL == p) { return NULL; } + // If it's an old protocol, it won't have any of the other options. + if (!isRequiredProperty && !isInstanceProperty && + !protocol_hasOptionalMethodsAndProperties(p)) + { + return NULL; + } + if (properties == NULL) + { + return NULL; + } + unsigned int count = 0; + for (struct objc_property_list *l=properties ; l!=NULL ; l=l->next) + { + count += l->count; + } + if (0 == count) + { + return NULL; + } + objc_property_t *list = calloc(sizeof(objc_property_t), count); + unsigned int out = 0; + for (struct objc_property_list *l=properties ; l!=NULL ; l=l->next) + { + for (int i=0 ; icount ; i++) + { + list[out++] = property_at_index(l, i); + } + } + *outCount = count; + return list; +} + +objc_property_t *protocol_copyPropertyList(Protocol *p, + unsigned int *outCount) +{ + return protocol_copyPropertyList2(p, outCount, YES, YES); +} + +objc_property_t protocol_getProperty(Protocol *p, + const char *name, + BOOL isRequiredProperty, + BOOL isInstanceProperty) +{ + if (NULL == p) { return NULL; } + if (!protocol_hasOptionalMethodsAndProperties(p)) + { + return NULL; + } + if (!isInstanceProperty && !protocol_hasClassProperties(p)) + { + return NULL; + } + struct objc_property_list *properties = + isInstanceProperty ? + (isRequiredProperty ? p->properties : p->optional_properties) : + (isRequiredProperty ? p->class_properties : p->optional_class_properties); + while (NULL != properties) + { + for (int i=0 ; icount ; i++) + { + objc_property_t prop = property_at_index(properties, i); + if (strcmp(property_getName(prop), name) == 0) + { + return prop; + } + } + properties = properties->next; + } + return NULL; +} + +static struct objc_protocol_method_description * +get_method_description(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + struct objc_protocol_method_description_list *list = + get_method_list(p, isRequiredMethod, isInstanceMethod); + if (NULL == list) + { + return NULL; + } + for (int i=0 ; icount ; i++) + { + SEL s = protocol_method_at_index(list, i)->selector; + if (sel_isEqual(s, aSel)) + { + return protocol_method_at_index(list, i); + } + } + return NULL; +} + +struct objc_method_description +protocol_getMethodDescription(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + struct objc_method_description d = {0,0}; + struct objc_protocol_method_description *m = + get_method_description(p, aSel, isRequiredMethod, isInstanceMethod); + if (m != NULL) + { + SEL s = m->selector; + d.name = s; + d.types = sel_getType_np(s); + } + return d; +} + +const char *_protocol_getMethodTypeEncoding(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + struct objc_protocol_method_description *m = + get_method_description(p, aSel, isRequiredMethod, isInstanceMethod); + if (m != NULL) + { + return m->types; + } + return NULL; +} + + +const char *protocol_getName(Protocol *p) +{ + if (NULL != p) + { + return p->name; + } + return NULL; +} + +BOOL protocol_isEqual(Protocol *p, Protocol *other) +{ + if (NULL == p || NULL == other) + { + return NO; + } + if (p == other || + p->name == other->name || + 0 == strcmp(p->name, other->name)) + { + return YES; + } + return NO; +} + +Protocol*__unsafe_unretained* objc_copyProtocolList(unsigned int *outCount) +{ + LOCK_FOR_SCOPE(&protocol_table_lock); + unsigned int total = known_protocol_table->table_used; + Protocol **p = calloc(sizeof(Protocol*), known_protocol_table->table_used); + + struct protocol_table_enumerator *e = NULL; + Protocol *next; + + unsigned int count = 0; + while ((count < total) && (next = protocol_next(known_protocol_table, &e))) + { + p[count++] = next; + } + if (NULL != outCount) + { + *outCount = total; + } + return p; +} + + +Protocol *objc_allocateProtocol(const char *name) +{ + if (objc_getProtocol(name) != NULL) { return NULL; } + // Create this as an object and add extra space at the end for the properties. + Protocol *p = (Protocol*)class_createInstance((Class)incompleteProtocolClass(), + sizeof(struct objc_protocol) - sizeof(id)); + p->name = strdup(name); + return p; +} +void objc_registerProtocol(Protocol *proto) +{ + if (NULL == proto) { return; } + LOCK_FOR_SCOPE(&protocol_table_lock); + if (objc_getProtocol(proto->name) != NULL) { return; } + if (incompleteProtocolClass() != proto->isa) { return; } + init_protocol_classes(); + proto->isa = protocol_class_gsv2; + protocol_table_insert(proto); +} +PRIVATE void registerProtocol(Protocol *proto) +{ + init_protocol_classes(); + LOCK_FOR_SCOPE(&protocol_table_lock); + proto->isa = protocol_class_gsv2; + if (protocol_for_name(proto->name) == NULL) + { + protocol_table_insert(proto); + } +} +void protocol_addMethodDescription(Protocol *aProtocol, + SEL name, + const char *types, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + if ((NULL == aProtocol) || (NULL == name) || (NULL == types)) { return; } + if (incompleteProtocolClass() != aProtocol->isa) { return; } + struct objc_protocol_method_description_list **listPtr; + if (isInstanceMethod) + { + if (isRequiredMethod) + { + listPtr = &aProtocol->instance_methods; + } + else + { + listPtr = &aProtocol->optional_instance_methods; + } + } + else + { + if (isRequiredMethod) + { + listPtr = &aProtocol->class_methods; + } + else + { + listPtr = &aProtocol->optional_class_methods; + } + } + if (NULL == *listPtr) + { + // FIXME: Factor this out, we do the same thing in multiple places. + *listPtr = calloc(1, sizeof(struct objc_protocol_method_description_list) + + sizeof(struct objc_protocol_method_description)); + (*listPtr)->count = 1; + (*listPtr)->size = sizeof(struct objc_protocol_method_description); + } + else + { + (*listPtr)->count++; + *listPtr = realloc(*listPtr, sizeof(struct objc_protocol_method_description_list) + + sizeof(struct objc_protocol_method_description) * (*listPtr)->count); + } + struct objc_protocol_method_description_list *list = *listPtr; + int index = list->count-1; + protocol_method_at_index(list, index)->selector = sel_registerTypedName_np(sel_getName(name), types); + protocol_method_at_index(list, index)->types = types; +} +void protocol_addProtocol(Protocol *aProtocol, Protocol *addition) +{ + if ((NULL == aProtocol) || (NULL == addition)) { return; } + if (incompleteProtocolClass() != aProtocol->isa) { return; } + if (NULL == aProtocol->protocol_list) + { + aProtocol->protocol_list = calloc(1, sizeof(struct objc_property_list) + sizeof(Protocol*)); + aProtocol->protocol_list->count = 1; + } + else + { + aProtocol->protocol_list->count++; + aProtocol->protocol_list = realloc(aProtocol->protocol_list, sizeof(struct objc_property_list) + + aProtocol->protocol_list->count * sizeof(Protocol*)); + } + aProtocol->protocol_list->list[aProtocol->protocol_list->count-1] = (Protocol*)addition; +} +void protocol_addProperty(Protocol *aProtocol, + const char *name, + const objc_property_attribute_t *attributes, + unsigned int attributeCount, + BOOL isRequiredProperty, + BOOL isInstanceProperty) +{ + if ((NULL == aProtocol) || (NULL == name)) { return; } + if (incompleteProtocolClass() != aProtocol->isa) { return; } + if (!isInstanceProperty) { return; } + struct objc_property_list **listPtr = + isInstanceProperty ? + (isRequiredProperty ? &aProtocol->properties : &aProtocol->optional_properties) : + (isRequiredProperty ? &aProtocol->class_properties : &aProtocol->optional_class_properties); + if (NULL == *listPtr) + { + *listPtr = calloc(1, sizeof(struct objc_property_list) + sizeof(struct objc_property)); + (*listPtr)->size = sizeof(struct objc_property); + (*listPtr)->count = 1; + } + else + { + (*listPtr)->count++; + *listPtr = realloc(*listPtr, sizeof(struct objc_property_list) + + sizeof(struct objc_property) * (*listPtr)->count); + } + struct objc_property_list *list = *listPtr; + int index = list->count-1; + struct objc_property p = propertyFromAttrs(attributes, attributeCount, name); + assert(list->size == sizeof(p)); + memcpy(&(list->properties[index]), &p, sizeof(p)); +} + diff --git a/src/runtime.c b/src/runtime.c new file mode 100644 index 0000000..be03c3f --- /dev/null +++ b/src/runtime.c @@ -0,0 +1,849 @@ +#include "objc/runtime.h" +#include "selector.h" +#include "class.h" +#include "protocol.h" +#include "ivar.h" +#include "method.h" +#include "lock.h" +#include "dtable.h" +#include "gc_ops.h" + +/* Make glibc export strdup() */ + +#if defined __GLIBC__ + #define __USE_BSD 1 +#endif + +#include +#include +#include +#include + +#define CHECK_ARG(arg) if (0 == arg) { return 0; } + +static inline void safe_remove_from_subclass_list(Class cls); +PRIVATE void objc_resolve_class(Class); +void objc_send_initialize(id object); + +/** + * Calls C++ destructors in the correct order. + */ +PRIVATE void call_cxx_destruct(id obj) +{ + static SEL cxx_destruct; + if (NULL == cxx_destruct) + { + cxx_destruct = sel_registerName(".cxx_destruct"); + } + // Don't call object_getClass(), because we want to get hidden classes too + Class cls = classForObject(obj); + + while (cls) + { + // If we're deallocating a class with a hidden class, then the + // `.cxx_destruct` method may deallocate the class. + Class currentClass = cls; + cls = cls->super_class; + if (currentClass->cxx_destruct) + { + currentClass->cxx_destruct(obj, cxx_destruct); + } + } +} + +static void call_cxx_construct_for_class(Class cls, id obj) +{ + static SEL cxx_construct; + if (NULL == cxx_construct) + { + cxx_construct = sel_registerName(".cxx_construct"); + } + + if (cls->super_class) + { + call_cxx_construct_for_class(cls->super_class, obj); + } + if (cls->cxx_construct) + { + cls->cxx_construct(obj, cxx_construct); + } +} + +PRIVATE void call_cxx_construct(id obj) +{ + call_cxx_construct_for_class(classForObject(obj), obj); +} + +/** + * Looks up the instance method in a specific class, without recursing into + * superclasses. + */ +static Method class_getInstanceMethodNonrecursive(Class aClass, SEL aSelector) +{ + for (struct objc_method_list *methods = aClass->methods; + methods != NULL ; methods = methods->next) + { + for (int i=0 ; icount ; i++) + { + Method method = method_at_index(methods, i); + if (sel_isEqual(method->selector, aSelector)) + { + return method; + } + } + } + return NULL; +} + +BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, + const char *types) +{ + CHECK_ARG(cls); + CHECK_ARG(name); + CHECK_ARG(types); + // You can't add ivars to initialized classes. Note: We can't use the + // resolved flag here because class_getInstanceVariable() sets it. + if (objc_test_class_flag(cls, objc_class_flag_initialized)) + { + return NO; + } + + if (class_getInstanceVariable(cls, name) != NULL) + { + return NO; + } + + struct objc_ivar_list *ivarlist = cls->ivars; + + if (NULL == ivarlist) + { + cls->ivars = malloc(sizeof(struct objc_ivar_list) + sizeof(struct objc_ivar)); + cls->ivars->size = sizeof(struct objc_ivar); + cls->ivars->count = 1; + } + else + { + ivarlist->count++; + // objc_ivar_list contains one ivar. Others follow it. + cls->ivars = realloc(ivarlist, sizeof(struct objc_ivar_list) + + (ivarlist->count) * sizeof(struct objc_ivar)); + } + Ivar ivar = ivar_at_index(cls->ivars, cls->ivars->count - 1); + ivar->name = strdup(name); + ivar->type = strdup(types); + ivarSetAlign(ivar, alignment); + // Round up the offset of the ivar so it is correctly aligned. + long offset = cls->instance_size; + if (alignment != 0) + { + offset >>= alignment; + + if (offset << alignment != cls->instance_size) + { + offset++; + } + offset <<= alignment; + } + + ivar->offset = (int*)(uintptr_t)offset; + // Increase the instance size to make space for this. + cls->instance_size = offset + size; + return YES; +} + +BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) +{ + CHECK_ARG(cls); + CHECK_ARG(name); + CHECK_ARG(imp); + CHECK_ARG(types); + const char *methodName = sel_getName(name); + struct objc_method_list *methods; + for (methods=cls->methods; methods!=NULL ; methods=methods->next) + { + for (int i=0 ; icount ; i++) + { + Method method = method_at_index(methods, i); + if (strcmp(sel_getName(method->selector), methodName) == 0) + { + return NO; + } + } + } + + methods = malloc(sizeof(struct objc_method_list) + sizeof(struct objc_method)); + methods->next = cls->methods; + methods->size = sizeof(struct objc_method); + cls->methods = methods; + + methods->count = 1; + struct objc_method *m0 = method_at_index(methods, 0); + m0->selector = sel_registerTypedName_np(methodName, types); + m0->types = strdup(types); + m0->imp = imp; + + if (classHasDtable(cls)) + { + add_method_list_to_class(cls, methods); + } + + return YES; +} + +BOOL class_addProtocol(Class cls, Protocol *protocol) +{ + CHECK_ARG(cls); + CHECK_ARG(protocol); + if (class_conformsToProtocol(cls, protocol)) { return NO; } + struct objc_protocol_list *protocols = + malloc(sizeof(struct objc_protocol_list) + sizeof(Protocol*)); + if (protocols == NULL) { return NO; } + protocols->next = cls->protocols; + protocols->count = 1; + protocols->list[0] = protocol; + cls->protocols = protocols; + + return YES; +} + +Ivar * class_copyIvarList(Class cls, unsigned int *outCount) +{ + if (outCount != NULL) + { + *outCount = 0x0; + } + + CHECK_ARG(cls); + struct objc_ivar_list *ivarlist = NULL; + unsigned int count = 0; + unsigned int index; + Ivar *list; + + if (Nil != cls) + { + ivarlist = cls->ivars; + } + if (ivarlist != NULL) + { + count = ivarlist->count; + } + if (outCount != NULL) + { + *outCount = count; + } + if (count == 0) + { + return NULL; + } + + list = malloc((count + 1) * sizeof(struct objc_ivar *)); + list[count] = NULL; + count = 0; + for (index = 0; index < ivarlist->count; index++) + { + list[count++] = ivar_at_index(ivarlist, index); + } + + return list; +} + +Method * class_copyMethodList(Class cls, unsigned int *outCount) +{ + if (outCount != NULL) + { + *outCount = 0x0; + } + + CHECK_ARG(cls); + unsigned int count = 0; + Method *list; + struct objc_method_list *methods; + + if (cls != NULL) + { + for (methods = cls->methods; methods != NULL; methods = methods->next) + { + count += methods->count; + } + } + + if (outCount != NULL) + { + *outCount = count; + } + + if (count == 0) + { + return NULL; + } + + list = malloc((count + 1) * sizeof(struct objc_method *)); + list[count] = NULL; + count = 0; + for (methods = cls->methods; methods != NULL; methods = methods->next) + { + unsigned int index; + for (index = 0; index < methods->count; index++) + { + list[count++] = method_at_index(methods, index); + } + } + + return list; +} + +Protocol*__unsafe_unretained* class_copyProtocolList(Class cls, unsigned int *outCount) +{ + if (outCount != NULL) + { + *outCount = 0x0; + } + + CHECK_ARG(cls); + struct objc_protocol_list *protocolList = NULL; + struct objc_protocol_list *list; + unsigned int count = 0; + Protocol **protocols; + + if (Nil != cls) + { + protocolList = cls->protocols; + } + for (list = protocolList; list != NULL; list = list->next) + { + count += list->count; + } + if (outCount != NULL) + { + *outCount = count; + } + if (count == 0) + { + return NULL; + } + + protocols = malloc((count + 1) * sizeof(Protocol *)); + protocols[count] = NULL; + count = 0; + for (list = protocolList; list != NULL; list = list->next) + { + memcpy(&protocols[count], list->list, list->count * sizeof(Protocol *)); + count += list->count; + } + return protocols; +} + +id class_createInstance(Class cls, size_t extraBytes) +{ + CHECK_ARG(cls); + if (sizeof(id) == 4) + { + if (cls == SmallObjectClasses[0]) + { + return (id)1; + } + } + else + { + for (int i=0 ; i<4 ; i++) + { + if (cls == SmallObjectClasses[i]) + { + return (id)(uintptr_t)((i<<1)+1); + } + } + } + + if (Nil == cls) { return nil; } + // Don't try to allocate an object of size 0, because there's no space for + // its isa pointer! + if (cls->instance_size < sizeof(Class)) { return nil; } + id obj = gc->allocate_class(cls, extraBytes); + obj->isa = cls; + checkARCAccessorsSlow(cls); + call_cxx_construct(obj); + return obj; +} + +id object_copy(id obj, size_t size) +{ + Class cls = object_getClass(obj); + id cpy = class_createInstance(cls, size - class_getInstanceSize(cls)); + memcpy(((char*)cpy + sizeof(id)), ((char*)obj + sizeof(id)), size - sizeof(id)); + return cpy; +} + +id object_dispose(id obj) +{ + call_cxx_destruct(obj); + gc->free_object(obj); + return nil; +} + +Method class_getInstanceMethod(Class aClass, SEL aSelector) +{ + CHECK_ARG(aClass); + CHECK_ARG(aSelector); + // If the class has a dtable installed, then we can use the fast path + if (classHasInstalledDtable(aClass)) + { + // Do a dtable lookup to find out which class the method comes from. + struct objc_slot2 *slot = objc_get_slot2(aClass, aSelector, NULL); + if (NULL == slot) + { + slot = objc_get_slot2(aClass, sel_registerName(sel_getName(aSelector)), NULL); + if (NULL == slot) + { + return NULL; + } + } + // Slots are the same as methods. + return (struct objc_method*)slot; + } + Method m = class_getInstanceMethodNonrecursive(aClass, aSelector); + if (NULL != m) + { + return m; + } + return class_getInstanceMethod(class_getSuperclass(aClass), aSelector); +} + +Method class_getClassMethod(Class aClass, SEL aSelector) +{ + return class_getInstanceMethod(object_getClass((id)aClass), aSelector); +} + +Ivar class_getClassVariable(Class cls, const char* name) +{ + // Note: We don't have compiler support for cvars in ObjC + return class_getInstanceVariable(object_getClass((id)cls), name); +} + +size_t class_getInstanceSize(Class cls) +{ + if (Nil == cls) { return 0; } + return cls->instance_size; +} + +Ivar class_getInstanceVariable(Class cls, const char *name) +{ + if (name != NULL) + { + while (cls != Nil) + { + struct objc_ivar_list *ivarlist = cls->ivars; + + if (ivarlist != NULL) + { + for (int i = 0; i < ivarlist->count; i++) + { + Ivar ivar = ivar_at_index(ivarlist, i); + if (strcmp(ivar->name, name) == 0) + { + return ivar; + } + } + } + cls = class_getSuperclass(cls); + } + } + return NULL; +} + +// The format of the char* is undocumented. This function is only ever used in +// conjunction with class_setIvarLayout(). +const char *class_getIvarLayout(Class cls) +{ + CHECK_ARG(cls); + return (char*)cls->ivars; +} + + +const char * class_getName(Class cls) +{ + if (Nil == cls) { return "nil"; } + return cls->name; +} + +int class_getVersion(Class theClass) +{ + CHECK_ARG(theClass); + return theClass->version; +} + +const char *class_getWeakIvarLayout(Class cls) +{ + assert(0 && "Weak ivars not supported"); + return NULL; +} + +BOOL class_isMetaClass(Class cls) +{ + CHECK_ARG(cls); + return objc_test_class_flag(cls, objc_class_flag_meta); +} + +IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) +{ + if (Nil == cls) { return (IMP)0; } + SEL sel = sel_registerTypedName_np(sel_getName(name), types); + Method method = class_getInstanceMethodNonrecursive(cls, sel); + if (method == NULL) + { + class_addMethod(cls, sel, imp, types); + return NULL; + } + IMP old = (IMP)method->imp; + method->imp = imp; + return old; +} + + +void class_setIvarLayout(Class cls, const char *layout) +{ + if ((Nil == cls) || (NULL == layout)) { return; } + struct objc_ivar_list *list = (struct objc_ivar_list*)layout; + size_t listsize = sizeof(struct objc_ivar_list) + + sizeof(struct objc_ivar) * (list->count); + cls->ivars = malloc(listsize); + memcpy(cls->ivars, list, listsize); +} + +__attribute__((deprecated)) +Class class_setSuperclass(Class cls, Class newSuper) +{ + CHECK_ARG(cls); + CHECK_ARG(newSuper); + Class oldSuper; + if (Nil == cls) { return Nil; } + + { + LOCK_RUNTIME_FOR_SCOPE(); + + oldSuper = cls->super_class; + + if (oldSuper == newSuper) { return newSuper; } + + safe_remove_from_subclass_list(cls); + objc_resolve_class(newSuper); + + cls->super_class = newSuper; + + // The super class's subclass list is used in certain method resolution scenarios. + cls->sibling_class = cls->super_class->subclass_list; + cls->super_class->subclass_list = cls; + + if (UNLIKELY(class_isMetaClass(cls))) + { + // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. + cls->isa = newSuper->isa; + } + else + { + Class meta = cls->isa, newSuperMeta = newSuper->isa; + // Update the metaclass's superclass. + safe_remove_from_subclass_list(meta); + objc_resolve_class(newSuperMeta); + + meta->super_class = newSuperMeta; + meta->isa = newSuperMeta->isa; + + // The super class's subclass list is used in certain method resolution scenarios. + meta->sibling_class = newSuperMeta->subclass_list; + newSuperMeta->subclass_list = meta; + } + + LOCK_FOR_SCOPE(&initialize_lock); + if (!objc_test_class_flag(cls, objc_class_flag_initialized)) + { + // Uninitialized classes don't have dtables to update + // and don't need their superclasses initialized. + return oldSuper; + } + } + + objc_send_initialize((id)newSuper); // also initializes the metaclass + objc_update_dtable_for_new_superclass(cls->isa, newSuper->isa); + objc_update_dtable_for_new_superclass(cls, newSuper); + + return oldSuper; +} + +void class_setVersion(Class theClass, int version) +{ + if (Nil == theClass) { return; } + theClass->version = version; +} + +void class_setWeakIvarLayout(Class cls, const char *layout) +{ + assert(0 && "Not implemented"); +} + +const char * ivar_getName(Ivar ivar) +{ + CHECK_ARG(ivar); + return ivar->name; +} + +ptrdiff_t ivar_getOffset(Ivar ivar) +{ + CHECK_ARG(ivar); + return *ivar->offset; +} + +const char * ivar_getTypeEncoding(Ivar ivar) +{ + CHECK_ARG(ivar); + return ivar->type; +} + + +void method_exchangeImplementations(Method m1, Method m2) +{ + if (NULL == m1 || NULL == m2) { return; } + IMP tmp = (IMP)m1->imp; + m1->imp = m2->imp; + m2->imp = tmp; +} + +IMP method_getImplementation(Method method) +{ + if (NULL == method) { return (IMP)NULL; } + return (IMP)method->imp; +} + +SEL method_getName(Method method) +{ + if (NULL == method) { return (SEL)NULL; } + return (SEL)method->selector; +} + + +IMP method_setImplementation(Method method, IMP imp) +{ + if (NULL == method) { return (IMP)NULL; } + IMP old = (IMP)method->imp; + method->imp = imp; + return old; +} + +id objc_getRequiredClass(const char *name) +{ + CHECK_ARG(name); + id cls = objc_getClass(name); + if (nil == cls) + { + abort(); + } + return cls; +} + +PRIVATE void freeMethodLists(Class aClass) +{ + struct objc_method_list *methods = aClass->methods; + while(methods != NULL) + { + for (int i=0 ; icount ; i++) + { + free((void*)method_at_index(methods, i)->types); + } + struct objc_method_list *current = methods; + methods = methods->next; + free(current); + } +} + +PRIVATE void freeIvarLists(Class aClass) +{ + struct objc_ivar_list *ivarlist = aClass->ivars; + if (NULL == ivarlist) { return; } + + if (ivarlist->count > 0) + { + // For dynamically created classes, ivar offset variables are allocated + // as a contiguous range starting with the first one. + free(ivar_at_index(ivarlist, 0)->offset); + } + + for (int i=0 ; icount ; i++) + { + Ivar ivar = ivar_at_index(ivarlist, i); + free((void*)ivar->type); + free((void*)ivar->name); + } + free(ivarlist); +} + +/* + * Removes a class from the subclass list found on its super class. + * Must be called with the objc runtime mutex locked. + */ +static inline void safe_remove_from_subclass_list(Class cls) +{ + // If this class hasn't been added to the class hierarchy, then this is easy + if (!objc_test_class_flag(cls, objc_class_flag_resolved)) { return; } + Class sub = cls->super_class->subclass_list; + if (sub == cls) + { + cls->super_class->subclass_list = cls->sibling_class; + } + else + { + while (sub != NULL) + { + if (sub->sibling_class == cls) + { + sub->sibling_class = cls->sibling_class; + break; + } + sub = sub->sibling_class; + } + } +} + +void objc_disposeClassPair(Class cls) +{ + if (0 == cls) { return; } + Class meta = ((id)cls)->isa; + // Remove from the runtime system so nothing tries updating the dtable + // while we are freeing the class. + { + LOCK_RUNTIME_FOR_SCOPE(); + safe_remove_from_subclass_list(meta); + safe_remove_from_subclass_list(cls); + class_table_remove(cls); + } + + // Free the method and ivar lists. + freeMethodLists(cls); + freeMethodLists(meta); + freeIvarLists(cls); + if (cls->dtable != uninstalled_dtable) + { + free_dtable(cls->dtable); + } + if (meta->dtable != uninstalled_dtable) + { + free_dtable(meta->dtable); + } + + // Free the class and metaclass + gc->free(meta); + gc->free(cls); +} + +Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) +{ + // Check the class doesn't already exist. + if (nil != objc_lookUpClass(name)) { return Nil; } + + Class newClass = gc->malloc(sizeof(struct objc_class) + extraBytes); + + if (Nil == newClass) { return Nil; } + + // Create the metaclass + Class metaClass = gc->malloc(sizeof(struct objc_class)); + + if (Nil == superclass) + { + /* + * Metaclasses of root classes are precious little flowers and work a + * little differently: + */ + metaClass->isa = metaClass; + metaClass->super_class = newClass; + } + else + { + // Initialize the metaclass + // Set the meta-metaclass pointer to the name. The runtime will fix this + // in objc_resolve_class(). + // If the superclass is not yet resolved, then we need to look it up + // via the class table. + metaClass->isa = superclass->isa; + metaClass->super_class = superclass->isa; + } + metaClass->name = strdup(name); + metaClass->info = objc_class_flag_meta | objc_class_flag_user_created; + metaClass->dtable = uninstalled_dtable; + metaClass->instance_size = sizeof(struct objc_class); + + // Set up the new class + newClass->isa = metaClass; + newClass->super_class = superclass; + + newClass->name = strdup(name); + newClass->info = objc_class_flag_user_created; + newClass->dtable = uninstalled_dtable; + + newClass->abi_version = 2; + metaClass->abi_version = 2; + + if (Nil == superclass) + { + newClass->instance_size = sizeof(struct objc_class*); + } + else + { + newClass->instance_size = superclass->instance_size; + } + + return newClass; +} + + +void *object_getIndexedIvars(id obj) +{ + CHECK_ARG(obj); + size_t size = classForObject(obj)->instance_size; + if ((0 == size) && class_isMetaClass(classForObject(obj))) + { + size = sizeof(struct objc_class); + } + return ((char*)obj) + size; +} + +Class object_getClass(id obj) +{ + CHECK_ARG(obj); + Class isa = classForObject(obj); + while ((Nil != isa) && objc_test_class_flag(isa, objc_class_flag_hidden_class)) + { + isa = isa->super_class; + } + return isa; +} + +Class object_setClass(id obj, Class cls) +{ + CHECK_ARG(obj); + // If this is a small object, then don't set its class. + if (isSmallObject(obj)) { return classForObject(obj); } + Class oldClass = obj->isa; + obj->isa = cls; + return oldClass; +} + +const char *object_getClassName(id obj) +{ + CHECK_ARG(obj); + return class_getName(object_getClass(obj)); +} + +void objc_registerClassPair(Class cls) +{ + if (cls->ivars != NULL) + { + int *ptrs = calloc(cls->ivars->count, sizeof(int)); + for (int i=0 ; iivars->count ; i++) + { + ptrs[i] = (int)(intptr_t)ivar_at_index(cls->ivars, i)->offset; + ivar_at_index(cls->ivars, i)->offset = &ptrs[i]; + } + } + LOCK_RUNTIME_FOR_SCOPE(); + class_table_insert(cls); + objc_resolve_class(cls); +} + diff --git a/src/sarray2.c b/src/sarray2.c new file mode 100644 index 0000000..5f50f6d --- /dev/null +++ b/src/sarray2.c @@ -0,0 +1,254 @@ +#include +#include +#include +#include + +#include "sarray2.h" +#include "visibility.h" + +const static SparseArray EmptyArray = { 0, 0, .data[0 ... 255] = 0 }; +const static SparseArray EmptyArray8 = { 8, 0, .data[0 ... 255] = (void*)&EmptyArray}; +const static SparseArray EmptyArray16 = { 16, 0, .data[0 ... 255] = (void*)&EmptyArray8}; +const static SparseArray EmptyArray24 = { 24, 0, .data[0 ... 255] = (void*)&EmptyArray16}; + +#define MAX_INDEX(sarray) (0xff) + +// Tweak this value to trade speed for memory usage. Bigger values use more +// memory, but give faster lookups. +#define base_shift 8 +#define base_mask ((1<shift != 0) + { + void *data = EmptyChildForShift(sarray->shift); + for(unsigned i=0 ; i<=MAX_INDEX(sarray) ; i++) + { + sarray->data[i] = data; + } + } +} + +PRIVATE SparseArray * SparseArrayNewWithDepth(uint32_t depth) +{ + SparseArray * sarray = calloc(1, sizeof(SparseArray)); + sarray->refCount = 1; + sarray->shift = depth-base_shift; + init_pointers(sarray); + return sarray; +} + +PRIVATE SparseArray *SparseArrayNew() +{ + return SparseArrayNewWithDepth(32); +} +PRIVATE SparseArray *SparseArrayExpandingArray(SparseArray *sarray, uint32_t new_depth) +{ + if (new_depth == sarray->shift) + { + return sarray; + } + assert(new_depth > sarray->shift); + // Expanding a child sarray has undefined results. + assert(sarray->refCount == 1); + SparseArray *new = calloc(1, sizeof(SparseArray)); + new->refCount = 1; + new->shift = sarray->shift + 8; + new->data[0] = sarray; + void *data = EmptyChildForShift(new->shift); + for(unsigned i=1 ; i<=MAX_INDEX(sarray) ; i++) + { + new->data[i] = data; + } + // Now, any lookup in sarray for any value less than its capacity will have + // all non-zero values shifted away, resulting in 0. All lookups will + // therefore go to the new sarray. + return new; +} + +static void *SparseArrayFind(SparseArray * sarray, uint32_t * index) +{ + uint32_t j = MASK_INDEX((*index)); + uint32_t max = MAX_INDEX(sarray); + if (sarray->shift == 0) + { + while (j<=max) + { + if (sarray->data[j] != SARRAY_EMPTY) + { + return sarray->data[j]; + } + (*index)++; + j++; + } + } + else while (jshift) >> base_shift); + while (jdata[j]; + // Skip over known-empty children + if ((&EmptyArray == child) || + (&EmptyArray8 == child) || + (&EmptyArray16 == child) || + (&EmptyArray24 == child)) + { + //Add 2^n to index so j is still correct + (*index) += 1<shift; + //Zero off the next component of the index so we don't miss any. + *index &= zeromask; + } + else + { + // The recursive call will set index to the correct value for + // the next index, but won't update j + void * ret = SparseArrayFind(child, index); + if (ret != SARRAY_EMPTY) + { + return ret; + } + } + //Go to the next child + j++; + } + } + return SARRAY_EMPTY; +} + +PRIVATE void *SparseArrayNext(SparseArray * sarray, uint32_t * idx) +{ + (*idx)++; + return SparseArrayFind(sarray, idx); +} + +PRIVATE void SparseArrayInsert(SparseArray * sarray, uint32_t index, void *value) +{ + if (sarray->shift > 0) + { + uint32_t i = MASK_INDEX(index); + SparseArray *child = sarray->data[i]; + if ((&EmptyArray == child) || + (&EmptyArray8 == child) || + (&EmptyArray16 == child) || + (&EmptyArray24 == child)) + { + // Insert missing nodes + SparseArray * newsarray = calloc(1, sizeof(SparseArray)); + newsarray->refCount = 1; + if (base_shift >= sarray->shift) + { + newsarray->shift = 0; + } + else + { + newsarray->shift = sarray->shift - base_shift; + } + init_pointers(newsarray); + sarray->data[i] = newsarray; + child = newsarray; + } + else if (child->refCount > 1) + { + // Copy the copy-on-write part of the tree + sarray->data[i] = SparseArrayCopy(child); + SparseArrayDestroy(child); + child = sarray->data[i]; + } + SparseArrayInsert(child, index, value); + } + else + { + sarray->data[MASK_INDEX(index)] = value; + } +} + +PRIVATE SparseArray *SparseArrayCopy(SparseArray * sarray) +{ + SparseArray *copy = calloc(sizeof(SparseArray), 1); + memcpy(copy, sarray, sizeof(SparseArray)); + copy->refCount = 1; + // If the sarray has children, increase their refcounts and link them + if (sarray->shift > 0) + { + for (unsigned int i = 0 ; i<=MAX_INDEX(sarray); i++) + { + SparseArray *child = copy->data[i]; + if (!(child == &EmptyArray || + child == &EmptyArray8 || + child == &EmptyArray16 || + child == &EmptyArray24)) + { + __sync_fetch_and_add(&child->refCount, 1); + } + // Non-lazy copy. Uncomment if debugging + // copy->data[i] = SparseArrayCopy(copy->data[i]); + } + } + return copy; +} + +PRIVATE void SparseArrayDestroy(SparseArray * sarray) +{ + // Don't really delete this sarray if its ref count is > 0 + if (sarray == &EmptyArray || + sarray == &EmptyArray8 || + sarray == &EmptyArray16 || + sarray == &EmptyArray24 || + (__sync_sub_and_fetch(&sarray->refCount, 1) > 0)) + { + return; + } + + if(sarray->shift > 0) + { + for(uint32_t i=0 ; idata[i]); + } + } + free(sarray); +} + +#if 0 +// Unused function, but helpful when debugging. +PRIVATE int SparseArraySize(SparseArray *sarray) +{ + int size = 0; + if (sarray->shift == 0) + { + return 256*sizeof(void*) + sizeof(SparseArray); + } + size += 256*sizeof(void*) + sizeof(SparseArray); + for(unsigned i=0 ; i<=MAX_INDEX(sarray) ; i++) + { + SparseArray *child = sarray->data[i]; + if (child == &EmptyArray || + child == &EmptyArray8 || + child == &EmptyArray16) + { + continue; + } + size += SparseArraySize(child); + } + return size; +} +#endif diff --git a/src/selector_table.cc b/src/selector_table.cc new file mode 100644 index 0000000..3723ad5 --- /dev/null +++ b/src/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 +#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' != *t2)) + { + 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+1) == '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/src/sendmsg2.c b/src/sendmsg2.c new file mode 100644 index 0000000..f7adceb --- /dev/null +++ b/src/sendmsg2.c @@ -0,0 +1,498 @@ +#include "objc/runtime.h" +#include "lock.h" +#include "dtable.h" +#include "selector.h" +#include "loader.h" +#include "objc/hooks.h" +#include +#include + +#ifndef __clang__ +#define NO_SAFE_CACHING +#endif + +void objc_send_initialize(id object); + +static long long nil_method(id self, SEL _cmd) { return 0; } +static long double nil_method_D(id self, SEL _cmd) { return 0; } +static double nil_method_d(id self, SEL _cmd) { return 0; } +static float nil_method_f(id self, SEL _cmd) { return 0; } + +static struct objc_slot nil_slot_v1 = { Nil, Nil, 0, 1, (IMP)nil_method }; +static struct objc_slot nil_slot_D_v1 = { Nil, Nil, 0, 1, (IMP)nil_method_D }; +static struct objc_slot nil_slot_d_v1 = { Nil, Nil, 0, 1, (IMP)nil_method_d }; +static struct objc_slot nil_slot_f_v1 = { Nil, Nil, 0, 1, (IMP)nil_method_f }; + +static struct objc_method nil_slot = { (IMP)nil_method, NULL, NULL }; +static struct objc_method nil_slot_D = { (IMP)nil_method_D, NULL, NULL }; +static struct objc_method nil_slot_d = { (IMP)nil_method_d, NULL, NULL }; +static struct objc_method nil_slot_f = { (IMP)nil_method_f, NULL, NULL }; + +static struct objc_slot2* objc_slot_lookup(id *receiver, SEL selector); + +// Default implementations of the two new hooks. Return NULL. +static id objc_proxy_lookup_null(id receiver, SEL op) { return nil; } +static struct objc_slot *objc_msg_forward3_null(id receiver, SEL op) { return &nil_slot_v1; } + +id (*objc_proxy_lookup)(id receiver, SEL op) = objc_proxy_lookup_null; +struct objc_slot *(*__objc_msg_forward3)(id receiver, SEL op) = objc_msg_forward3_null; + +static IMP forward2(id self, SEL _cmd) +{ + return __objc_msg_forward3(self, _cmd)->method; +} +IMP (*__objc_msg_forward2)(id, SEL) = forward2; + +__thread struct objc_method uncacheable_slot = { (IMP)nil_method, NULL, NULL }; +__thread struct objc_slot uncacheable_slot_v1 = { Nil, Nil, 0, 0, (IMP)nil_method }; + +#ifndef NO_SELECTOR_MISMATCH_WARNINGS +static IMP objc_selector_type_mismatch(Class cls, SEL + selector, struct objc_slot2 *result) +{ + fprintf(stderr, "Calling [%s %c%s] with incorrect signature. " + "Method has %s (%s), selector has %s\n", + cls->name, + class_isMetaClass(cls) ? '+' : '-', + sel_getName(selector), + sel_getType_np(((struct objc_method*)result)->selector), + ((struct objc_method*)result)->types, + sel_getType_np(selector)); + return result->method; +} +#else +static IMP objc_selector_type_mismatch(Class cls, SEL + selector, struct objc_slot2 *result) +{ + return result->method; +} +#endif + +IMP (*_objc_selector_type_mismatch2)(Class cls, SEL + selector, struct objc_slot2 *result) = objc_selector_type_mismatch; +struct objc_slot *(*_objc_selector_type_mismatch)(Class cls, SEL + selector, struct objc_slot *result); + +static IMP call_mismatch_hook(Class cls, SEL sel, struct objc_slot2 *slot) +{ + if (_objc_selector_type_mismatch && + (!_objc_selector_type_mismatch2 || + (_objc_selector_type_mismatch2 == objc_selector_type_mismatch))) + { + struct objc_slot fwdslot; + fwdslot.types = ((struct objc_method*)slot)->types; + fwdslot.selector = sel; + fwdslot.method = slot->method; + struct objc_slot *slot_v1 = _objc_selector_type_mismatch(cls, sel, &uncacheable_slot_v1); + return slot_v1->method; + } + return _objc_selector_type_mismatch2(cls, sel, slot); +} + +static +// Uncomment for debugging +//__attribute__((noinline)) +__attribute__((always_inline)) +struct objc_slot2 *objc_msg_lookup_internal(id *receiver, SEL selector, uint64_t *version) +{ + if (version) + { +#ifdef NO_SAFE_CACHING + // Always write 0 to version, marking the slot as uncacheable. + *version = 0; +#else + *version = objc_method_cache_version; +#endif + } + Class class = classForObject((*receiver)); +retry:; + struct objc_slot2 * result = objc_dtable_lookup(class->dtable, selector->index); + if (UNLIKELY(0 == result)) + { + dtable_t dtable = dtable_for_class(class); + /* Install the dtable if it hasn't already been initialized. */ + if (dtable == uninstalled_dtable) + { + objc_send_initialize(*receiver); + dtable = dtable_for_class(class); + result = objc_dtable_lookup(dtable, selector->index); + } + else + { + // Check again incase another thread updated the dtable while we + // weren't looking + result = objc_dtable_lookup(dtable, selector->index); + } + if (0 == result) + { + if ((result = objc_dtable_lookup(dtable, get_untyped_idx(selector)))) + { +#ifndef NO_SAFE_CACHING + if (version) + { + *version = 0; + } +#endif + uncacheable_slot.imp = call_mismatch_hook(class, selector, result); + result = (struct objc_slot2*)&uncacheable_slot; + } + id newReceiver = objc_proxy_lookup(*receiver, selector); + // If some other library wants us to play forwarding games, try + // again with the new object. + if (nil != newReceiver) + { + *receiver = newReceiver; + return objc_slot_lookup(receiver, selector); + } + if (0 == result) + { +#ifndef NO_SAFE_CACHING + if (version) + { + *version = 0; + } +#endif + uncacheable_slot.imp = __objc_msg_forward2(*receiver, selector); + result = (struct objc_slot2*)&uncacheable_slot; + } + } + } + return result; +} + +PRIVATE IMP slowMsgLookup(id *receiver, SEL cmd) +{ + // By the time we've got here, the assembly version of this function has + // already done the nil checks. + return objc_msg_lookup_internal(receiver, cmd, NULL)->method; +} + +PRIVATE void logInt(void *a) +{ + fprintf(stderr, "Value: %p\n", a); +} + +/** + * New Objective-C lookup function. This permits the lookup to modify the + * receiver and also supports multi-dimensional dispatch based on the sender. + */ +struct objc_slot *objc_msg_lookup_sender(id *receiver, SEL selector, id sender) +{ + // Returning a nil slot allows the caller to cache the lookup for nil too, + // although this is not particularly useful because the nil method can be + // inlined trivially. + if (UNLIKELY(*receiver == nil)) + { + // Return the correct kind of zero, depending on the type encoding. + if (selector->types) + { + const char *t = selector->types; + // Skip type qualifiers + while ('r' == *t || 'n' == *t || 'N' == *t || 'o' == *t || + 'O' == *t || 'R' == *t || 'V' == *t || 'A' == *t) + { + t++; + } + switch (selector->types[0]) + { + case 'D': return &nil_slot_D_v1; + case 'd': return &nil_slot_d_v1; + case 'f': return &nil_slot_f_v1; + } + } + return &nil_slot_v1; + } + + struct objc_slot2 *slot = objc_msg_lookup_internal(receiver, selector, NULL); + uncacheable_slot_v1.owner = Nil; + uncacheable_slot_v1.types = sel_getType_np(((struct objc_method*)slot)->selector); + uncacheable_slot_v1.selector = selector; + uncacheable_slot_v1.method = slot->method; + return &uncacheable_slot_v1; +} + + +static struct objc_slot2* objc_slot_lookup(id *receiver, SEL selector) +{ + // Returning a nil slot allows the caller to cache the lookup for nil too, + // although this is not particularly useful because the nil method can be + // inlined trivially. + if (UNLIKELY(*receiver == nil)) + { + // Return the correct kind of zero, depending on the type encoding. + if (selector->types) + { + const char *t = selector->types; + // Skip type qualifiers + while ('r' == *t || 'n' == *t || 'N' == *t || 'o' == *t || + 'O' == *t || 'R' == *t || 'V' == *t || 'A' == *t) + { + t++; + } + switch (selector->types[0]) + { + case 'D': return (struct objc_slot2*)&nil_slot_D; + case 'd': return (struct objc_slot2*)&nil_slot_d; + case 'f': return (struct objc_slot2*)&nil_slot_f; + } + } + return (struct objc_slot2*)&nil_slot; + } + + return objc_msg_lookup_internal(receiver, selector, NULL); +} + +struct objc_slot2 *objc_slot_lookup_version(id *receiver, SEL selector, uint64_t *version) +{ + // Returning a nil slot allows the caller to cache the lookup for nil too, + // although this is not particularly useful because the nil method can be + // inlined trivially. + if (UNLIKELY(*receiver == nil)) + { +#ifndef NO_SAFE_CACHING + if (version) + { + *version = 0; + } +#endif + // Return the correct kind of zero, depending on the type encoding. + if (selector->types) + { + const char *t = selector->types; + // Skip type qualifiers + while ('r' == *t || 'n' == *t || 'N' == *t || 'o' == *t || + 'O' == *t || 'R' == *t || 'V' == *t || 'A' == *t) + { + t++; + } + switch (selector->types[0]) + { + case 'D': return (struct objc_slot2*)&nil_slot_D; + case 'd': return (struct objc_slot2*)&nil_slot_d; + case 'f': return (struct objc_slot2*)&nil_slot_f; + } + } + return (struct objc_slot2*)&nil_slot; + } + + return objc_msg_lookup_internal(receiver, selector, version); +} + +IMP objc_msg_lookup2(id *receiver, SEL selector) +{ + return objc_slot_lookup(receiver, selector)->method; +} + + +struct objc_slot2 *objc_slot_lookup_super2(struct objc_super *super, SEL selector) +{ + id receiver = super->receiver; + if (receiver) + { + Class class = super->class; + struct objc_slot2 * result = objc_dtable_lookup(dtable_for_class(class), + selector->index); + if (0 == result) + { + Class class = classForObject(receiver); + // Dtable should always be installed in the superclass in + // Objective-C, but may not be for other languages (Python). + if (dtable_for_class(class) == uninstalled_dtable) + { + if (class_isMetaClass(class)) + { + objc_send_initialize(receiver); + } + else + { + objc_send_initialize((id)class); + } + objc_send_initialize((id)class); + return objc_slot_lookup_super2(super, selector); + } + uncacheable_slot.imp = __objc_msg_forward2(receiver, selector); + return (struct objc_slot2*)&uncacheable_slot; + } + return result; + } + return (struct objc_slot2*)&nil_slot; +} + +OBJC_PUBLIC +struct objc_slot *objc_slot_lookup_super(struct objc_super *super, SEL selector) +{ + id receiver = super->receiver; + if (receiver) + { + Class class = super->class; + struct objc_slot2 * result = objc_dtable_lookup(dtable_for_class(class), + selector->index); + if (0 == result) + { + Class class = classForObject(receiver); + // Dtable should always be installed in the superclass in + // Objective-C, but may not be for other languages (Python). + if (dtable_for_class(class) == uninstalled_dtable) + { + if (class_isMetaClass(class)) + { + objc_send_initialize(receiver); + } + else + { + objc_send_initialize((id)class); + } + objc_send_initialize((id)class); + return objc_slot_lookup_super(super, selector); + } + uncacheable_slot_v1.owner = Nil; + uncacheable_slot_v1.types = sel_getType_np(selector); + uncacheable_slot_v1.selector = selector; + uncacheable_slot_v1.method = __objc_msg_forward2(receiver, selector); + return &uncacheable_slot_v1; + } + uncacheable_slot_v1.owner = Nil; + uncacheable_slot_v1.types = sel_getType_np(((struct objc_method*)result)->selector); + uncacheable_slot_v1.selector = selector; + uncacheable_slot_v1.method = result->method; + return &uncacheable_slot_v1; + } + return &nil_slot_v1; +} + +/** + * looks up a slot without invoking any forwarding mechanisms + */ +struct objc_slot2 *objc_get_slot2(Class cls, SEL selector, uint64_t *version) +{ + +#ifndef NO_SAFE_CACHING + if (version) + { + *version = objc_method_cache_version; + } +#endif + struct objc_slot2 * result = objc_dtable_lookup(cls->dtable, selector->index); + if (0 == result) + { + void *dtable = dtable_for_class(cls); + /* Install the dtable if it hasn't already been initialized. */ + if (dtable == uninstalled_dtable) + { + dtable = dtable_for_class(cls); + result = objc_dtable_lookup(dtable, selector->index); + } + else + { + // Check again incase another thread updated the dtable while we + // weren't looking + result = objc_dtable_lookup(dtable, selector->index); + } + if (NULL == result) + { + if ((result = objc_dtable_lookup(dtable, get_untyped_idx(selector)))) + { +#ifndef NO_SAFE_CACHING + if (version) + { + *version = 0; + } +#endif + uncacheable_slot.imp = call_mismatch_hook(cls, selector, result); + result = (struct objc_slot2*)&uncacheable_slot; + } + } + } + return result; +} + +struct objc_slot *objc_get_slot(Class cls, SEL selector) +{ + struct objc_slot2 *result = objc_get_slot2(cls, selector, NULL); + if (result == NULL) + { + return NULL; + } + uncacheable_slot_v1.owner = Nil; + // Don't leak extended type encodings! + uncacheable_slot_v1.types = sel_getType_np(((struct objc_method*)result)->selector); + uncacheable_slot_v1.selector = selector; + uncacheable_slot_v1.method = result->method; + return &uncacheable_slot_v1; +} + +//////////////////////////////////////////////////////////////////////////////// +// Public API +//////////////////////////////////////////////////////////////////////////////// + +BOOL class_respondsToSelector(Class cls, SEL selector) +{ + if (0 == selector || 0 == cls) { return NO; } + + return NULL != objc_get_slot2(cls, selector, NULL); +} + +IMP class_getMethodImplementation(Class cls, SEL name) +{ + if ((Nil == cls) || (NULL == name)) { return (IMP)0; } + struct objc_slot2 * slot = objc_get_slot2(cls, name, NULL); + return NULL != slot ? slot->method : __objc_msg_forward2(nil, name); +} + +IMP class_getMethodImplementation_stret(Class cls, SEL name) +{ + return class_getMethodImplementation(cls, name); +} + + +//////////////////////////////////////////////////////////////////////////////// +// Legacy compatibility +//////////////////////////////////////////////////////////////////////////////// + +#ifndef NO_LEGACY +/** + * Legacy message lookup function. + */ +BOOL __objc_responds_to(id object, SEL sel) +{ + return class_respondsToSelector(classForObject(object), sel); +} + +IMP get_imp(Class cls, SEL selector) +{ + return class_getMethodImplementation(cls, selector); +} + +/** + * Message send function that only ever worked on a small subset of compiler / + * architecture combinations. + */ +void *objc_msg_sendv(void) +{ + fprintf(stderr, "objc_msg_sendv() never worked correctly. Don't use it.\n"); + abort(); +} +#endif +/** + * Legacy message lookup function. Does not support fast proxies or safe IMP + * caching. + */ +IMP objc_msg_lookup(id receiver, SEL selector) +{ + if (nil == receiver) { return (IMP)nil_method; } + + id self = receiver; + struct objc_slot2 * slot = objc_msg_lookup_internal(&self, selector, NULL); + // If the receiver is changed by the lookup mechanism then we have to fall + // back to old-style forwarding. + if (self != receiver) + { + return __objc_msg_forward2(receiver, selector); + } + return slot->method; +} + +IMP objc_msg_lookup_super(struct objc_super *super, SEL selector) +{ + return objc_slot_lookup_super2(super, selector)->method; +} diff --git a/src/statics_loader.c b/src/statics_loader.c new file mode 100644 index 0000000..7dc440c --- /dev/null +++ b/src/statics_loader.c @@ -0,0 +1,72 @@ +#include +#include +#include "objc/runtime.h" +#include "module.h" +#include "constant_string.h" +#include "visibility.h" + +#define BUFFER_TYPE struct objc_static_instance_list * +#include "buffer.h" + +static BOOL try_init_statics(struct objc_static_instance_list *statics) +{ + const char *class_name = statics->class_name; + + // This is a horrible hack. + // + // Very bad things happen when you have more than one constant string class + // used in a program. Unfortunately, GCC defaults to using + // NXConstantString, and if you forget to specify + // -fconstant-string-class=NSConstantString for some compilation units then + // you will end up with some NSConstantString instances and some + // NXConstantString instances. This is a mess. We hack around this by + // silently assuming that the user meant NSConstantString when they said + // NXConstantString if NSConstantString is set as the constant string class + // in string_class.h or by an external -D flag. + if (strcmp(class_name, "NXConstantString") == 0) + { + class_name = CONSTANT_STRING_CLASS; + } + + Class class = (Class)objc_getClass(class_name); + + if (Nil == class) + { + return NO; + } + for (id *instance=statics->instances ; nil!=*instance ; instance++) + { + (*instance)->isa = class; + } + return YES; +} +PRIVATE void objc_init_statics(struct objc_static_instance_list *statics) +{ + if (!try_init_statics(statics)) + { + set_buffered_object_at_index(statics, buffered_objects++); + } +} + +PRIVATE void objc_init_buffered_statics(void) +{ + BOOL shouldReshuffle = NO; + + for (unsigned i=0 ; i