From 84c0bfe7b03d454e68f3c6496beadfa4bbb956be Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 18 Dec 2015 13:43:50 +0000 Subject: [PATCH 01/42] Fix two small bugs in the hopscotch hash that meant that moving elements near the end of the array sometimes caused them to become lost. --- hash_table.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hash_table.h b/hash_table.h index 951bd2d..aaae772 100644 --- a/hash_table.h +++ b/hash_table.h @@ -288,7 +288,7 @@ static int PREFIX(_insert)(PREFIX(_table) *table, return 1; } /* If this cell is full, try the next one. */ - for (unsigned int i=0 ; i<32 ; i++) + for (unsigned int i=1 ; i<33 ; i++) { PREFIX(_table_cell) second = PREFIX(_table_lookup)(table, hash+i); @@ -374,7 +374,7 @@ static void PREFIX(_table_move_second)(PREFIX(_table) *table, // 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, MAP_TABLE_HASH_VALUE(emptyCell->value) + hop); + PREFIX(_table_lookup)(table, (emptyCell - table->table) + hop); emptyCell->value = hopCell->value; emptyCell->secondMaps &= ~(1 << (hop-1)); if (0 == hopCell->secondMaps) From 3135e30d71db16dbb1769d4f49381920da36afb5 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 18 Dec 2015 13:46:49 +0000 Subject: [PATCH 02/42] Fix ARC assignment of blocks. We were not correctly checking that the object being assigned had a reference count in the object header. We're also now avoiding assigning small objects to the weak reference table, which is quite important as they have no destructor. Fixes #12 --- Test/CMakeLists.txt | 1 + Test/WeakBlock_arc.m | 15 +++++++++++++++ arc.m | 35 +++++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 Test/WeakBlock_arc.m diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 749f671..c28bfaf 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -23,6 +23,7 @@ set(TESTS ProtocolCreation.m ResurrectInDealloc_arc.m RuntimeTest.m + WeakBlock_arc.m WeakReferences_arc.m objc_msgSend.m msgInterpose.m diff --git a/Test/WeakBlock_arc.m b/Test/WeakBlock_arc.m new file mode 100644 index 0000000..c21fe43 --- /dev/null +++ b/Test/WeakBlock_arc.m @@ -0,0 +1,15 @@ +#include "Test.h" + +int main(int argc, const char * argv[]) +{ + id __weak ref; + @autoreleasepool { + __block int val; + id block = ^() { return val++; }; + assert(block != nil); + ref = block; + assert(ref != nil); + } + assert(ref == nil); + return 0; +} diff --git a/arc.m b/arc.m index b31ff02..b8d177f 100644 --- a/arc.m +++ b/arc.m @@ -525,10 +525,27 @@ void* block_load_weak(void *block); id objc_storeWeak(id *addr, id obj) { id old = *addr; - intptr_t *refCount = ((intptr_t*)obj) - 1; - if (obj && *refCount < 0) + + BOOL isGlobalObject = (obj == nil) || isSmallObject(obj); + Class cls = Nil; + if (!isGlobalObject) + { + cls = classForObject(obj); + // TODO: We probably also want to do the same for constant strings and + // classes. + if (cls == &_NSConcreteGlobalBlock) + { + isGlobalObject = YES; + } + } + if (cls && objc_test_class_flag(cls, objc_class_flag_fast_arc)) { - obj = nil; + intptr_t *refCount = ((intptr_t*)obj) - 1; + if (obj && *refCount < 0) + { + obj = nil; + cls = Nil; + } } LOCK_FOR_SCOPE(&weakRefLock); if (nil != old) @@ -553,13 +570,10 @@ id objc_storeWeak(id *addr, id obj) *addr = obj; return nil; } - Class cls = classForObject(obj); - if (&_NSConcreteGlobalBlock == cls) + if (isGlobalObject) { - // If this is a global block, it's never deallocated, so secretly make + // If this is a global object, it's never deallocated, so secretly make // this a strong reference - // TODO: We probably also want to do the same for constant strings and - // classes. *addr = obj; return obj; } @@ -650,6 +664,11 @@ id objc_loadWeakRetained(id* addr) LOCK_FOR_SCOPE(&weakRefLock); id obj = *addr; if (nil == obj) { return nil; } + // Small objects don't need reference count modification + if (isSmallObject(obj)) + { + return obj; + } Class cls = classForObject(obj); if (&_NSConcreteMallocBlock == cls) { From 76ed20acdb5ff34dc0523ab077162fd9b78f5c5b Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 18 Dec 2015 13:58:30 +0000 Subject: [PATCH 03/42] Remove build systems that were deprecated a long time ago. --- GNUmakefile | 107 ------------------------------------ Makefile | 153 ---------------------------------------------------- 2 files changed, 260 deletions(-) delete mode 100644 GNUmakefile delete mode 100644 Makefile diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index aba0cff..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,107 +0,0 @@ - -# Check to see if GNUstep-config is available. -ifeq ($(GNUSTEP_MAKEFILES),) -GNUSTEP_MAKEFILES := $(shell gnustep-config --variable=GNUSTEP_MAKEFILES 2>/dev/null) -endif - -ifeq ($(GNUSTEP_MAKEFILES),) -# -# Start of section for building without GNUstep -# -$(warning GNUstep not found -\ -building for standalone installation.) - -include Makefile - -# -# End of section for building without GNUstep-make -# -else -# -# Start of GNUstep specific section. -# -$(warning GNUstep found -\ -building for install in the GNUstep filesystem.) - -PACKAGE_NAME = gnustep-objc2 -SVN_MODULE_NAME = libobjc2 -SVN_BASE_URL = svn+ssh://svn.gna.org/svn/gnustep/libs -SVN_TAG_NAME=objc2 - -GNUSTEP_INSTALLATION_DOMAIN ?= LOCAL - -ifeq ($(messages),yes) - SILENT ?= -endif - -INSTALL := dummy_install - -include Makefile -include $(GNUSTEP_MAKEFILES)/names.make - -# Hack to support -03 for Clang and get the __sync_* GCC builtins work -# -O3 requires -march=i586 on Linux x86-32, otherwise Clang compiles -# programs that segfaults if -fobjc-nonfragile-abi is used. -ifeq ($(findstring linux, $(GNUSTEP_TARGET_OS)), linux) - ifeq ($(GNUSTEP_TARGET_CPU), ix86) - CFLAGS += -march=i586 - endif -endif - -# Hack to get mingw to provide declaration for strdup (since it is non-standard) -ifeq ($(GNUSTEP_TARGET_OS), mingw32) - ${LIBOBJC}_CPPFLAGS += -U__STRICT_ANSI__ -endif - -# Shouldn't be needed starting with OpenBSD 5.2 -ifeq ($(findstring openbsd, `$CC -dumpmachine`), openbsd) - LDFLAGS += -pthread -else - LDFLAGS += -lpthread -endif - -LIB_DIR := $(shell gnustep-config --variable=GNUSTEP_$(GNUSTEP_INSTALLATION_DOMAIN)_LIBRARIES 2>/dev/null) -ifeq ($(LIB_DIR),) -LIB_DIR := $(shell gmake -s -f "$(GNUSTEP_MAKEFILES)/empty.make" print-gnustep-install-libraries GNUSTEP_INSTALLATION_DOMAIN=$(GNUSTEP_INSTALLATION_DOMAIN) quiet=yes 2>/dev/null) -endif -ifeq ($(LIB_DIR),) -$(error Unable to use gnustep-config to get install directory - is gnustep-config in your PATH?) -endif - -HEADER_DIR := $(shell gnustep-config --variable=GNUSTEP_$(GNUSTEP_INSTALLATION_DOMAIN)_HEADERS 2>/dev/null) -ifeq ($(HEADER_DIR),) -HEADER_DIR := $(shell gmake -s -f "$(GNUSTEP_MAKEFILES)/empty.make" print-gnustep-install-headers GNUSTEP_INSTALLATION_DOMAIN=$(GNUSTEP_INSTALLATION_DOMAIN) quiet=yes 2>/dev/null) -endif -ifeq ($(HEADER_DIR),) -$(error Unable to use gnustep-config to get install directory - is gnustep-config in your PATH?) -endif - -install: all - $(SILENT)echo Installing libraries... - $(SILENT)install -d $(LIB_DIR) - $(SILENT)install -m 444 $(STRIP) $(LIBOBJC).so.$(VERSION) $(LIB_DIR) - $(SILENT)install -m 444 $(STRIP) $(LIBOBJCXX).so.$(VERSION) $(LIB_DIR) - $(SILENT)install -m 444 $(STRIP) $(LIBOBJC).a $(LIB_DIR) - $(SILENT)echo Creating symbolic links... - $(SILENT)ln -sf $(LIBOBJC).so.$(VERSION) $(LIB_DIR)/$(LIBOBJC).so - $(SILENT)ln -sf $(LIBOBJC).so.$(VERSION) $(LIB_DIR)/$(LIBOBJC).so.$(MAJOR_VERSION) - $(SILENT)ln -sf $(LIBOBJC).so.$(VERSION) $(LIB_DIR)/$(LIBOBJC).so.$(MAJOR_VERSION).$(MINOR_VERSION) - $(SILENT)ln -sf $(LIBOBJCXX).so.$(VERSION) $(LIB_DIR)/$(LIBOBJCXX).so - $(SILENT)ln -sf $(LIBOBJCXX).so.$(VERSION) $(LIB_DIR)/$(LIBOBJCXX).so.$(MAJOR_VERSION) - $(SILENT)ln -sf $(LIBOBJCXX).so.$(VERSION) $(LIB_DIR)/$(LIBOBJCXX).so.$(MAJOR_VERSION).$(MINOR_VERSION) - $(SILENT)echo Installing headers... - $(SILENT)install -d $(HEADER_DIR)/objc - $(SILENT)install -m 444 objc/*.h $(HEADER_DIR)/objc - $(SILENT)install -m 444 objc/*.h $(HEADER_DIR)/objc - $(SILENT)echo "To use the newly installed objc2 library with GNUstep," - $(SILENT)echo "please change to your gnustep-make directory and" - $(SILENT)echo "type 'configure; make install' to tell gnustep-make" - $(SILENT)echo "to use the new library (you will then need to configure" - $(SILENT)echo "and build gnustep-base to use the new library too)." - -distclean: clean - -# -# End of GNUstep-make specific section. -# -endif diff --git a/Makefile b/Makefile deleted file mode 100644 index f5bf387..0000000 --- a/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -.POSIX: - -.SUFFIXES: .cc .c .m .o .S - -MAJOR_VERSION = 4 -MINOR_VERSION = 6 -SUBMINOR_VERSION = 0 -VERSION ?= $(MAJOR_VERSION).$(MINOR_VERSION).$(SUBMINOR_VERSION) - -LIBOBJCLIBNAME=objc -LIBOBJC=libobjc -LIBOBJCXX=libobjcxx - -INSTALL ?= install -SILENT ?= @ - -CFLAGS += -std=gnu99 -fPIC -fexceptions -#CFLAGS += -Wno-deprecated-objc-isa-usage -CXXFLAGS += -fPIC -fexceptions -CPPFLAGS += -DTYPE_DEPENDENT_DISPATCH -DGNUSTEP -CPPFLAGS += -D__OBJC_RUNTIME_INTERNAL__=1 -D_XOPEN_SOURCE=500 -D__BSD_VISIBLE=1 -D_BSD_SOURCE=1 - -ASMFLAGS += `if $(CC) -v 2>&1| grep -q 'clang' ; then echo -no-integrated-as ; fi` - -THE_LD=`if [ "$(LD)" = "" ]; then echo "ld"; else echo "$(LD)"; fi` - -STRIP=`if [ "$(strip)" = "yes" ] ; then echo -s ; fi` - -# Suppress warnings about incorrect selectors -CPPFLAGS += -DNO_SELECTOR_MISMATCH_WARNINGS -# Some helpful flags for debugging. -#CPPFLAGS += -g -O0 -fno-inline -CPPFLAGS += -O3 - -PREFIX?= /usr/local -LIB_DIR= ${PREFIX}/lib -HEADER_DIR= ${PREFIX}/include - -OBJCXX_OBJECTS = \ - objcxx_eh.o - -OBJECTS = \ - NSBlocks.o\ - Protocol2.o\ - abi_version.o\ - alias_table.o\ - arc.o\ - associate.o\ - blocks_runtime.o\ - block_to_imp.o\ - block_trampolines.o\ - objc_msgSend.o\ - caps.o\ - category_loader.o\ - class_table.o\ - dtable.o\ - eh_personality.o\ - encoding2.o\ - gc_none.o\ - hash_table.o\ - hooks.o\ - ivar.o\ - legacy_malloc.o\ - loader.o\ - mutation.o\ - properties.o\ - protocol.o\ - runtime.o\ - sarray2.o\ - selector_table.o\ - sendmsg2.o\ - statics_loader.o\ - toydispatch.o - - -all: warning $(LIBOBJC).a $(LIBOBJCXX).so.$(VERSION) - @echo '********************************************************************************' - @echo '*********************************** WARNING ************************************' - @echo '********************************************************************************' - @echo The Makfile build is deprecated, and should not be used. - @echo Please use cmake. The recommended procedure is: - @echo $ mkdir Build - @echo $ cd Build - @echo cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ - @echo "make && sudo -E make install" - -warning: - @echo '********************************************************************************' - @echo '*********************************** WARNING ************************************' - @echo '********************************************************************************' - @echo The Makfile build is deprecated, and should not be used. - @echo Please use cmake. The recommended procedure is: - @echo $ mkdir Build - @echo $ cd Build - @echo cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ - @echo "make && sudo -E make install" - -$(LIBOBJCXX).so.$(VERSION): $(LIBOBJC).so.$(VERSION) $(OBJCXX_OBJECTS) - $(SILENT)echo Linking shared Objective-C++ runtime library... - $(SILENT)$(CXX) -shared \ - -Wl,-soname=$(LIBOBJCXX).so.$(MAJOR_VERSION) $(LDFLAGS) \ - -o $@ $(OBJCXX_OBJECTS) - -$(LIBOBJC).so.$(VERSION): $(OBJECTS) - $(SILENT)echo Linking shared Objective-C runtime library... - $(SILENT)$(CC) -shared -rdynamic \ - -Wl,-soname=$(LIBOBJC).so.$(MAJOR_VERSION) $(LDFLAGS) \ - -o $@ $(OBJECTS) - -$(LIBOBJC).a: $(OBJECTS) - $(SILENT)echo Linking static Objective-C runtime library... - $(SILENT)$(THE_LD) -r -s -o $@ $(OBJECTS) - -.cc.o: Makefile - $(SILENT)echo Compiling `basename $<`... - $(SILENT)$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ - -.c.o: Makefile - $(SILENT)echo Compiling `basename $<`... - $(SILENT)$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ - -.m.o: Makefile - $(SILENT)echo Compiling `basename $<`... - $(SILENT)$(CC) $(CPPFLAGS) $(CFLAGS) -fobjc-exceptions -c $< -o $@ - -.S.o: Makefile - $(SILENT)echo Assembling `basename $<`... - $(SILENT)$(CC) $(CPPFLAGS) $(ASMFLAGS) -c $< -o $@ - -$(INSTALL): all - $(SILENT)echo Installing libraries... - $(SILENT)install -d $(LIB_DIR) - $(SILENT)install -m 444 $(STRIP) $(LIBOBJC).so.$(VERSION) $(LIB_DIR) - $(SILENT)install -m 444 $(STRIP) $(LIBOBJCXX).so.$(VERSION) $(LIB_DIR) - $(SILENT)install -m 444 $(STRIP) $(LIBOBJC).a $(LIB_DIR) - $(SILENT)echo Creating symbolic links... - $(SILENT)ln -sf $(LIBOBJC).so.$(VERSION) $(LIB_DIR)/$(LIBOBJC).so - $(SILENT)ln -sf $(LIBOBJC).so.$(VERSION) $(LIB_DIR)/$(LIBOBJC).so.$(MAJOR_VERSION) - $(SILENT)ln -sf $(LIBOBJC).so.$(VERSION) $(LIB_DIR)/$(LIBOBJC).so.$(MAJOR_VERSION).$(MINOR_VERSION) - $(SILENT)ln -sf $(LIBOBJCXX).so.$(VERSION) $(LIB_DIR)/$(LIBOBJCXX).so - $(SILENT)ln -sf $(LIBOBJCXX).so.$(VERSION) $(LIB_DIR)/$(LIBOBJCXX).so.$(MAJOR_VERSION) - $(SILENT)ln -sf $(LIBOBJCXX).so.$(VERSION) $(LIB_DIR)/$(LIBOBJCXX).so.$(MAJOR_VERSION).$(MINOR_VERSION) - $(SILENT)echo Installing headers... - $(SILENT)install -d $(HEADER_DIR)/objc - $(SILENT)install -m 444 objc/*.h $(HEADER_DIR)/objc - -clean: - $(SILENT)echo Cleaning... - $(SILENT)rm -f $(OBJECTS) - $(SILENT)rm -f $(OBJCXX_OBJECTS) - $(SILENT)rm -f $(LIBOBJC).so.$(VERSION) - $(SILENT)rm -f $(LIBOBJCXX).so.$(VERSION) - $(SILENT)rm -f $(LIBOBJC).a From 2f57b0b68876440cef3e3a9276561c55e0f05271 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 18 Dec 2015 14:43:52 +0000 Subject: [PATCH 04/42] Tweak the block trampolines to work with the win64 calling convention (untested!). --- block_trampolines.S | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/block_trampolines.S b/block_trampolines.S index e3ed411..f95d6a0 100644 --- a/block_trampolines.S +++ b/block_trampolines.S @@ -32,14 +32,23 @@ TYPE_DIRECTIVE(CDECL(__objc_block_trampoline), @function) .globl CDECL(__objc_block_trampoline_end) #endif #if __x86_64 +#ifdef _WIN64 +#define ARG0 %rcx +#define ARG1 %rdx +#define ARG2 %r8 +#else +#define ARG0 %rdi +#define ARG1 %rsi +#define ARG2 %rdx +#endif CDECL(__objc_block_trampoline): - mov -15(%rip), %rsi # Load the block pointer into the second argument - xchg %rdi, %rsi # Swap the first and second arguments + mov -15(%rip), ARG1 # Load the block pointer into the second argument + xchg ARG1, ARG0 # Swap the first and second arguments jmp *-32(%rip) # Call the block function CDECL(__objc_block_trampoline_end): CDECL(__objc_block_trampoline_sret): - mov -15(%rip), %rdx # Load the block pointer into the second argument - xchg %rdx, %rsi # Swap the first and second arguments + mov -15(%rip), ARG2 # Load the block pointer into the second argument + xchg ARG1, ARG2 # Swap the first and second arguments jmp *-32(%rip) # Call the block function CDECL(__objc_block_trampoline_end_sret): #elif __i386 From b5810988311d80ad10ec7a59b8183530336c5fe2 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 18 Dec 2015 15:32:10 +0000 Subject: [PATCH 05/42] Extend the objc_msgSend test to check for register saving. Ensure, when we bounce out from the assembly into C and back again, that the argument values are preserved. Tests 10 integer and 10 floating point arguments, which should cover all of the argument registers and at least one stack slot on all supported platforms. --- Test/objc_msgSend.m | 85 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/Test/objc_msgSend.m b/Test/objc_msgSend.m index 6d6803c..3703365 100644 --- a/Test/objc_msgSend.m +++ b/Test/objc_msgSend.m @@ -4,6 +4,7 @@ #include #include #include "../objc/runtime.h" +#include "../objc/hooks.h" //#define assert(x) if (!(x)) { printf("Failed %d\n", __LINE__); } @@ -23,7 +24,31 @@ Class TestCls; __attribute__((objc_root_class)) #endif #endif -@interface Test { id isa; }@end +@interface Test { id isa; } @end +@interface Test (Dynamic) ++ (void)manyArgs: (int)a0 + : (int) a1 + : (int) a2 + : (int) a3 + : (int) a4 + : (int) a5 + : (int) a6 + : (int) a7 + : (int) a8 + : (int) a9 + : (int) a10 + : (float) f0 + : (float) f1 + : (float) f2 + : (float) f3 + : (float) f4 + : (float) f5 + : (float) f6 + : (float) f7 + : (float) f8 + : (float) f9 + : (float) f10; +@end @implementation Test - foo { @@ -70,8 +95,65 @@ __attribute__((objc_root_class)) } + nothing { return 0; } @end + +int forwardcalls; +void fwd(int a0, + int a1, + int a2, + int a3, + int a4, + int a5, + int a6, + int a7, + int a8, + int a9, + int a10, + float f0, + float f1, + float f2, + float f3, + float f4, + float f5, + float f6, + float f7, + float f8, + float f9, + float f10) +{ + forwardcalls++; + assert(a0 == 0); + assert(a1 == 0); + assert(a2 == 0); + assert(a3 == 0); + assert(a4 == 0); + assert(a5 == 0); + assert(a6 == 0); + assert(a7 == 0); + assert(a8 == 0); + assert(a9 == 0); + assert(a10 == 10); + assert(f0 == 0); + assert(f1 == 0); + assert(f2 == 0); + assert(f3 == 0); + assert(f4 == 0); + assert(f5 == 0); + assert(f6 == 0); + assert(f7 == 0); + assert(f8 == 0); + assert(f9 == 0); + assert(f10 == 10); +} + +IMP forward(id o, SEL s) +{ + assert(o == objc_getClass("Test")); + return (IMP)fwd; +} + int main(void) { + __objc_msg_forward2 = forward; TestCls = objc_getClass("Test"); int exceptionThrown = 0; @try { @@ -114,6 +196,7 @@ int main(void) assert(0 == [f dzero]); assert(0 == [f ldzero]); assert(0 == [f fzero]); + [TestCls manyArgs: 0 : 1 : 2 : 3: 4: 5: 6: 7: 8: 9: 10 : 0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10]; #ifdef BENCHMARK clock_t c1, c2; c1 = clock(); From 8b1d4fbcc211008dddf1255c41913e16c6c0ba62 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Tue, 22 Dec 2015 08:59:02 +0000 Subject: [PATCH 06/42] TEST is a keyword in if() statements in recent CMake, rename a variable to avoid a conflict. --- Test/CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index c28bfaf..988b926 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -33,26 +33,26 @@ set(TESTS # Function for adding a test. This takes the name of the test and the list of # source files as arguments. -function(addtest_flags TEST FLAGS TEST_SOURCE) - if (TEST MATCHES ".*_arc") +function(addtest_flags TEST_NAME FLAGS TEST_SOURCE) + if (${TEST_NAME} MATCHES ".*_arc") # Only compile the main file with ARC set_source_files_properties(${TEST_SOURCE} COMPILE_FLAGS "-fobjc-arc") # Add the ARC-incompatible definitions of the test class. list(APPEND TEST_SOURCE "Test.m") endif() - add_executable(${TEST} ${TEST_SOURCE}) - add_test(${TEST} ${TEST}) + add_executable(${TEST_NAME} ${TEST_SOURCE}) + add_test(${TEST_NAME} ${TEST_NAME}) set(ARC "") - set_target_properties(${TEST} PROPERTIES + set_target_properties(${TEST_NAME} PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}" COMPILE_FLAGS "-fobjc-runtime=gnustep-1.7 -fblocks ${FLAGS}" LINKER_LANGUAGE C ) - set_property(TEST ${TEST} PROPERTY + set_property(TEST ${TEST_NAME} PROPERTY ENVIRONMENT "LD_LIBRARY_PATH=" ) - target_link_libraries(${TEST} objc) + target_link_libraries(${TEST_NAME} objc) endfunction(addtest_flags) foreach(TEST_SOURCE ${TESTS}) From de8e5f26eeb70f5b10b54e7e2dc98e045b11f034 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 23 Dec 2015 11:17:56 +0000 Subject: [PATCH 07/42] Use a simple C array for the selector types list. The sparse array is not a particularly good fit. It allowed us to do a few things without locking, but those things were not on the critical path anyway and it added complexity and memory usage for little gain. This removes the only non-dtable user of the sparse array, which should mean that we're able to optimise the sparse array for that single use a bit better. --- selector_table.c | 76 +++++++++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/selector_table.c b/selector_table.c index 13b6ca5..4446d16 100644 --- a/selector_table.c +++ b/selector_table.c @@ -9,7 +9,6 @@ #include #include #include "lock.h" -#include "sarray2.h" #include "objc/runtime.h" #include "method_list.h" #include "class.h" @@ -37,10 +36,14 @@ * array. */ static uint32_t selector_count = 1; +/** + * Size of the buffer. + */ +static size_t table_size; /** * Mapping from selector numbers to selector names. */ -PRIVATE SparseArray *selector_list = NULL; +PRIVATE struct sel_type_list **selector_list = NULL; #ifdef DEBUG_SELECTOR_TABLE #define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) @@ -51,6 +54,27 @@ PRIVATE SparseArray *selector_list = NULL; // Get the functions for string hashing #include "string_hash.h" + +/** + * Lock protecting the selector table. + */ +mutex_t selector_table_lock; + +static inline struct sel_type_list *selLookup_locked(uint32_t idx) +{ + if (idx > selector_count) + { + return NULL; + } + return selector_list[idx]; +} + +static inline struct sel_type_list *selLookup(uint32_t idx) +{ + LOCK_FOR_SCOPE(&selector_table_lock); + return selLookup_locked(idx); +} + PRIVATE inline BOOL isSelRegistered(SEL sel) { if ((uintptr_t)sel->name < (uintptr_t)selector_count) @@ -65,8 +89,7 @@ static const char *sel_getNameNonUnique(SEL sel) const char *name = sel->name; if (isSelRegistered(sel)) { - struct sel_type_list * list = - SparseArrayLookup(selector_list, sel->index); + struct sel_type_list * list = selLookup_locked(sel->index); name = (list == NULL) ? NULL : list->value; } if (NULL == name) @@ -226,16 +249,11 @@ static inline uint32_t hash_selector(const void *s) */ static selector_table *sel_table; -/** - * Lock protecting the selector table. - */ -mutex_t selector_table_lock; - static int selector_name_copies; PRIVATE void log_selector_memory_usage(void) { - fprintf(stderr, "%d bytes in selector name list.\n", SparseArraySize(selector_list)); + 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); @@ -257,7 +275,8 @@ void objc_resize_dtables(uint32_t); */ PRIVATE void init_selector_tables() { - selector_list = SparseArrayNew(); + selector_list = calloc(sizeof(void*), 4096); + table_size = 4096; INIT_LOCK(selector_table_lock); selector_initialize(&sel_table, 4096); } @@ -276,7 +295,19 @@ static inline void add_selector_to_table(SEL aSel, int32_t uid, uint32_t idx) typeList->value = aSel->name; typeList->next = 0; // Store the name. - SparseArrayInsert(selector_list, idx, typeList); + if (idx >= table_size) + { + table_size *= 2; + struct sel_type_list **newList = calloc(sizeof(struct sel_type_list*), table_size); + if (newList == NULL) + { + abort(); + } + memcpy(newList, selector_list, sizeof(void*)*(table_size/2)); + free(selector_list); + selector_list = newList; + } + selector_list[idx] = typeList; // Store the selector. selector_insert(sel_table, aSel); // Set the selector's name to the uid. @@ -321,8 +352,7 @@ static inline void register_selector_locked(SEL aSel) // Add this set of types to the list. // This is quite horrible. Most selectors will only have one type // encoding, so we're wasting a lot of memory like this. - struct sel_type_list *typeListHead = - SparseArrayLookup(selector_list, untyped->index); + struct sel_type_list *typeListHead = selLookup_locked(untyped->index); struct sel_type_list *typeList = (struct sel_type_list *)selector_pool_alloc(); typeList->value = aSel->types; @@ -400,8 +430,7 @@ static SEL objc_register_selector_copy(SEL aSel, BOOL copyArgs) PRIVATE uint32_t sel_nextTypeIndex(uint32_t untypedIdx, uint32_t idx) { - struct sel_type_list *list = - SparseArrayLookup(selector_list, untypedIdx); + struct sel_type_list *list = selLookup(untypedIdx); if (NULL == list) { return 0; } @@ -431,8 +460,7 @@ const char *sel_getName(SEL sel) const char *name = sel->name; if (isSelRegistered(sel)) { - struct sel_type_list * list = - SparseArrayLookup(selector_list, sel->index); + struct sel_type_list * list = selLookup(sel->index); name = (list == NULL) ? NULL : list->value; } else @@ -498,8 +526,7 @@ unsigned sel_copyTypes_np(const char *selName, const char **types, unsigned coun SEL untyped = selector_lookup(selName, 0); if (untyped == NULL) { return 0; } - struct sel_type_list *l = - SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)untyped->name); + struct sel_type_list *l = selLookup(untyped->index); // Skip the head, which just contains the name, not the types. l = l->next; @@ -532,8 +559,7 @@ unsigned sel_copyTypedSelectors_np(const char *selName, SEL *const sels, unsigne SEL untyped = selector_lookup(selName, 0); if (untyped == NULL) { return 0; } - struct sel_type_list *l = - SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)untyped->name); + struct sel_type_list *l = selLookup(untyped->index); // Skip the head, which just contains the name, not the types. l = l->next; @@ -599,8 +625,7 @@ SEL sel_get_typed_uid (const char *name, const char *types) SEL sel = selector_lookup(name, types); if (NULL == sel) { return sel_registerTypedName_np(name, types); } - struct sel_type_list *l = - SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)sel->name); + struct sel_type_list *l = selLookup(sel->index); // Skip the head, which just contains the name, not the types. l = l->next; if (NULL != l) @@ -616,8 +641,7 @@ SEL sel_get_any_typed_uid (const char *name) SEL sel = selector_lookup(name, 0); if (NULL == sel) { return sel_registerName(name); } - struct sel_type_list *l = - SparseArrayLookup(selector_list, (uint32_t)(uintptr_t)sel->name); + struct sel_type_list *l = selLookup(sel->index); // Skip the head, which just contains the name, not the types. l = l->next; if (NULL != l) From 51071302779fdf6231fa75de133de34bd3efd171 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 23 Dec 2015 15:25:34 +0000 Subject: [PATCH 08/42] Move the data for sparse arrays into the tree structure. This saves one load on the message send path for each tree depth (2 loads in the common case, 3 if you have a lot of selectors), which should improve cache usage considerably. Note: This is a checkpoint commit. Currently, every objc_msgSend() implementation except for x86-64 is broken. --- asmconstants.h | 13 ++++++++ dtable.c | 39 ++++++++++++++++++----- objc_msgSend.S | 1 + objc_msgSend.x86-64.S | 56 +++++++++------------------------ sarray2.c | 73 ++++++++++++++++++------------------------- sarray2.h | 20 +++++++----- 6 files changed, 103 insertions(+), 99 deletions(-) create mode 100644 asmconstants.h diff --git a/asmconstants.h b/asmconstants.h new file mode 100644 index 0000000..84b8d83 --- /dev/null +++ b/asmconstants.h @@ -0,0 +1,13 @@ +#ifdef __LP64__ +#define DTABLE_OFFSET 64 +#define SMALLOBJ_MASK 7 +#define SHIFT_OFFSET 0 +#define DATA_OFFSET 8 +#define SLOT_OFFSET 32 +#else +#define DTABLE_OFFSET 32 +#define SMALLOBJ_MASK 1 +#define SHIFT_OFFSET 0 +#define DATA_OFFSET 8 +#define SLOT_OFFSET 16 +#endif diff --git a/dtable.c b/dtable.c index 486916a..3b42382 100644 --- a/dtable.c +++ b/dtable.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "objc/runtime.h" #include "objc/hooks.h" #include "sarray2.h" @@ -12,7 +13,16 @@ #include "slot_pool.h" #include "dtable.h" #include "visibility.h" -#include "errno.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"); +_Static_assert(__builtin_offsetof(struct objc_slot, method) == SLOT_OFFSET, + "Incorrect slot offset for assembly"); PRIVATE dtable_t uninstalled_dtable; #if defined(WITH_TRACING) && defined (__x86_64) @@ -644,23 +654,36 @@ PRIVATE void objc_resize_dtables(uint32_t newSize) dtable_depth += 8; - uint32_t oldMask = uninstalled_dtable->mask; + uint32_t oldShift = uninstalled_dtable->shift; + dtable_t old_uninstalled_dtable = uninstalled_dtable; - SparseArrayExpandingArray(uninstalled_dtable, dtable_depth); + 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 != (void*)uninstalled_dtable && - NULL != next->dtable && - ((SparseArray*)next->dtable)->mask == oldMask) + 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) { - SparseArrayExpandingArray((void*)next->dtable, dtable_depth); - SparseArrayExpandingArray((void*)next->isa->dtable, dtable_depth); + next->dtable = SparseArrayExpandingArray((void*)next->dtable, dtable_depth); + next->isa->dtable = SparseArrayExpandingArray((void*)next->isa->dtable, dtable_depth); } } } diff --git a/objc_msgSend.S b/objc_msgSend.S index ed82718..892ff70 100644 --- a/objc_msgSend.S +++ b/objc_msgSend.S @@ -1,4 +1,5 @@ #include "common.S" +#include "asmconstants.h" #if __x86_64 #include "objc_msgSend.x86-64.S" #elif __i386 diff --git a/objc_msgSend.x86-64.S b/objc_msgSend.x86-64.S index 4be831b..20b8802 100644 --- a/objc_msgSend.x86-64.S +++ b/objc_msgSend.x86-64.S @@ -1,8 +1,3 @@ -#define DTABLE_OFFSET 64 -#define SMALLOBJ_MASK 7 -#define SHIFT_OFFSET 4 -#define DATA_OFFSET 16 -#define SLOT_OFFSET 32 .macro MSGSEND receiver, sel .cfi_startproc # Start emitting unwind data. We @@ -18,41 +13,24 @@ 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 - - push %r12 - push %r13 - - mov (\sel), %r11 # Load the selector index - mov SHIFT_OFFSET(%r10), %r13 # Load the shift (dtable size) - mov DATA_OFFSET(%r10), %r12 # load the address of the start of the array - cmpl $8, %r13d # If this is a small dtable, jump to the small dtable handlers +1: # classLoaded + mov DTABLE_OFFSET(%r10), %r10 # Load the dtable from the class into r10 + 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, %r13d + cmpl $0, %r11d je 3f - 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 + movl %eax, %r11d + shrl $16, %r11d + movq DATA_OFFSET(%r10, %r11, 8), %r10 2: # dtable16: - mov %r11, %r13 - and $0xff00, %r13 - shrl $5, %r13d - add %r13, %r12 - mov (%r12), %r12 - mov DATA_OFFSET(%r12), %r12 + movzbl %ah, %r11d + movq DATA_OFFSET(%r10, %r11, 8), %r10 3: # dtable8: - mov %r11, %r13 - and $0xff, %r13 - shll $3, %r13d - add %r13, %r12 - mov (%r12), %r10 - pop %r13 - pop %r12 + movzbl %al, %r11d + movq DATA_OFFSET(%r10, %r11, 8), %r10 test %r10, %r10 jz 5f # Nil slot - invoke some kind of forwarding mechanism mov SLOT_OFFSET(%r10), %r10 @@ -202,9 +180,7 @@ 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 %rcx # We need to preserve all registers that may contain arguments: push %r8 push %r9 @@ -233,7 +209,7 @@ mov %rdx, %rsi # move _cmd to where the callee expects it to be .endif - .cfi_adjust_cfa_offset 0xD8 + .cfi_adjust_cfa_offset 0xc8 call CDECL(slowMsgLookup) # Call the slow lookup function mov %rax, %r10 # Load the returned IMP @@ -254,8 +230,6 @@ pop %r9 pop %r8 pop %rcx - pop %rbx - pop %rax jmp 7b 6: # smallObject: and \receiver, %r10 # Find the small int type diff --git a/sarray2.c b/sarray2.c index c3c79d8..ba1cd22 100644 --- a/sarray2.c +++ b/sarray2.c @@ -6,17 +6,12 @@ #include "sarray2.h" #include "visibility.h" -static void *EmptyArrayData[256]; -static SparseArray EmptyArray = { 0xff, 0, 0, (void**)&EmptyArrayData}; -static void *EmptyArrayData8[256] = { [0 ... 255] = &EmptyArray }; -static SparseArray EmptyArray8 = { 0xff00, 8, 0, (void**)&EmptyArrayData8}; -static void *EmptyArrayData16[256] = { [0 ... 255] = &EmptyArray8 }; -static SparseArray EmptyArray16 = { 0xff0000, 16, 0, (void**)&EmptyArrayData16}; -static void *EmptyArrayData24[256] = { [0 ... 255] = &EmptyArray16 }; -static SparseArray EmptyArray24 = { 0xff0000, 24, 0, (void**)&EmptyArrayData24}; +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) (sarray->mask >> sarray->shift) -#define DATA_SIZE(sarray) ((sarray->mask >> sarray->shift) + 1) +#define MAX_INDEX(sarray) (0xff) // Tweak this value to trade speed for memory usage. Bigger values use more // memory, but give faster lookups. @@ -29,19 +24,18 @@ void *EmptyChildForShift(uint32_t shift) { default: UNREACHABLE("Broken sparse array"); case 8: - return &EmptyArray; + return (void*)&EmptyArray; case 16: - return &EmptyArray8; + return (void*)&EmptyArray8; case 24: - return &EmptyArray16; + return (void*)&EmptyArray16; case 32: - return &EmptyArray24; + return (void*)&EmptyArray24; } } static void init_pointers(SparseArray * sarray) { - sarray->data = calloc(DATA_SIZE(sarray), sizeof(void*)); if(sarray->shift != 0) { void *data = EmptyChildForShift(sarray->shift); @@ -57,7 +51,6 @@ PRIVATE SparseArray * SparseArrayNewWithDepth(uint32_t depth) SparseArray * sarray = calloc(1, sizeof(SparseArray)); sarray->refCount = 1; sarray->shift = depth-base_shift; - sarray->mask = base_mask << sarray->shift; init_pointers(sarray); return sarray; } @@ -77,24 +70,17 @@ PRIVATE SparseArray *SparseArrayExpandingArray(SparseArray *sarray, uint32_t new assert(sarray->refCount == 1); SparseArray *new = calloc(1, sizeof(SparseArray)); new->refCount = 1; - new->shift = sarray->shift; - new->mask = sarray->mask; - void **newData = malloc(DATA_SIZE(sarray) * sizeof(void*)); - void *data = EmptyChildForShift(new->shift + 8); + new->shift = sarray->shift + 8; + new->data[0] = sarray; + void *data = EmptyChildForShift(new->shift); for(unsigned i=1 ; i<=MAX_INDEX(sarray) ; i++) { - newData[i] = data; + new->data[i] = data; } - new->data = sarray->data; - newData[0] = new; - sarray->data = newData; // 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. - sarray->shift += base_shift; - // Finally, set the mask to the correct value. Now all lookups should work. - sarray->mask <<= base_shift; - return sarray; + return new; } static void *SparseArrayFind(SparseArray * sarray, uint32_t * index) @@ -117,7 +103,7 @@ static void *SparseArrayFind(SparseArray * sarray, uint32_t * index) { // If the shift is not 0, then we need to recursively look at child // nodes. - uint32_t zeromask = ~(sarray->mask >> base_shift); + uint32_t zeromask = ~((0xff << sarray->shift) >> base_shift); while (jshift = sarray->shift - base_shift; } - newsarray->mask = sarray->mask >> base_shift; init_pointers(newsarray); sarray->data[i] = newsarray; child = newsarray; @@ -194,25 +179,28 @@ PRIVATE void SparseArrayInsert(SparseArray * sarray, uint32_t index, void *value } else { - sarray->data[index & sarray->mask] = value; + sarray->data[MASK_INDEX(index)] = value; } } PRIVATE SparseArray *SparseArrayCopy(SparseArray * sarray) { - SparseArray *copy = calloc(1, sizeof(SparseArray)); + SparseArray *copy = calloc(sizeof(SparseArray), 1); + memcpy(copy, sarray, sizeof(SparseArray)); copy->refCount = 1; - copy->shift = sarray->shift; - copy->mask = sarray->mask; - copy->data = malloc(sizeof(void*) * DATA_SIZE(sarray)); - memcpy(copy->data, sarray->data, sizeof(void*) * DATA_SIZE(sarray)); // 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]; - __sync_fetch_and_add(&child->refCount, 1); + 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]); } @@ -223,9 +211,10 @@ PRIVATE SparseArray *SparseArrayCopy(SparseArray * sarray) PRIVATE void SparseArrayDestroy(SparseArray * sarray) { // Don't really delete this sarray if its ref count is > 0 - if (sarray == &EmptyArray || - sarray == &EmptyArray8 || - sarray == &EmptyArray16 || + if (sarray == &EmptyArray || + sarray == &EmptyArray8 || + sarray == &EmptyArray16 || + sarray == &EmptyArray24 || (__sync_sub_and_fetch(&sarray->refCount, 1) > 0)) { return; @@ -233,13 +222,11 @@ PRIVATE void SparseArrayDestroy(SparseArray * sarray) if(sarray->shift > 0) { - uint32_t max = (sarray->mask >> sarray->shift) + 1; - for(uint32_t i=0 ; idata[i]); } } - free(sarray->data); free(sarray); } diff --git a/sarray2.h b/sarray2.h index cefc521..36e99be 100644 --- a/sarray2.h +++ b/sarray2.h @@ -13,6 +13,17 @@ #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. @@ -25,11 +36,6 @@ */ typedef struct { - /** - * Mask value applied to the index when generating an index in this - * sub-array. - */ - uint32_t mask; /** * 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 @@ -46,14 +52,14 @@ typedef struct /** * The data stored in this sparse array node. */ - void ** data; + void *data[data_size]; } SparseArray; /** * Turn an index in the array into an index in the current depth. */ #define MASK_INDEX(index) \ - ((index & sarray->mask) >> sarray->shift) + ((index >> sarray->shift) & 0xff) #define SARRAY_EMPTY ((void*)0) /** From 5dd82df81e92f1d298f6a3e5f2dc979fde5af91e Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 23 Dec 2015 15:46:52 +0000 Subject: [PATCH 09/42] Fix x86-32 objc_msgSend for new sarray structure. --- objc_msgSend.x86-32.S | 27 ++++++--------------------- objc_msgSend.x86-64.S | 24 +++++++++++++++--------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/objc_msgSend.x86-32.S b/objc_msgSend.x86-32.S index 2b5406f..77193bd 100644 --- a/objc_msgSend.x86-32.S +++ b/objc_msgSend.x86-32.S @@ -1,8 +1,3 @@ -#define DTABLE_OFFSET 32 -#define SMALLOBJ_MASK 1 -#define SHIFT_OFFSET 4 -#define DATA_OFFSET 12 -#define SLOT_OFFSET 16 .macro MSGSEND receiver, sel, fpret .cfi_startproc movl \receiver(%esp), %eax @@ -24,30 +19,20 @@ # %edx: selector index fragment mov SHIFT_OFFSET(%eax), %edx # Load the shift (dtable size) - mov DATA_OFFSET(%eax), %eax # load the address of the start of the array 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 - and $0xff0000, %edx - shrl $14, %edx # Right shift 16, but then left shift by 2 (* sizeof(void*)) - add %edx, %eax - mov (%eax), %eax - mov DATA_OFFSET(%eax), %eax + shrl $16, %edx + movl DATA_OFFSET(%eax, %edx, 4), %eax 2: # dtable16: - mov %ecx, %edx - and $0xff00, %edx - shrl $6, %edx - add %edx, %eax - mov (%eax), %eax - mov DATA_OFFSET(%eax), %eax + movzbl %ch, %edx + movl DATA_OFFSET(%eax, %edx, 4), %eax 3: # dtable8: - and $0xff, %ecx - shll $2, %ecx - add %ecx, %eax - mov (%eax), %eax + movzbl %cl, %edx + movl DATA_OFFSET(%eax, %edx, 4), %eax test %eax, %eax jz 5f # Nil slot - invoke some kind of forwarding mechanism diff --git a/objc_msgSend.x86-64.S b/objc_msgSend.x86-64.S index 20b8802..ebe5e5e 100644 --- a/objc_msgSend.x86-64.S +++ b/objc_msgSend.x86-64.S @@ -15,6 +15,8 @@ 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 @@ -26,11 +28,13 @@ shrl $16, %r11d movq DATA_OFFSET(%r10, %r11, 8), %r10 2: # dtable16: - movzbl %ah, %r11d - movq DATA_OFFSET(%r10, %r11, 8), %r10 + movzbl %ah, %ebx + movq DATA_OFFSET(%r10, %rbx, 8), %r10 3: # dtable8: - movzbl %al, %r11d - movq DATA_OFFSET(%r10, %r11, 8), %r10 + 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 @@ -180,7 +184,9 @@ pxor %xmm0, %xmm0 # Return 0 as a floating point value ret 5: # slowSend: - push %rcx # We need to preserve all registers that may contain arguments: + push %rax # We need to preserve all registers that may contain arguments: + push %rbx + push %rcx push %r8 push %r9 @@ -209,7 +215,7 @@ mov %rdx, %rsi # move _cmd to where the callee expects it to be .endif - .cfi_adjust_cfa_offset 0xc8 + .cfi_adjust_cfa_offset 0xD8 call CDECL(slowMsgLookup) # Call the slow lookup function mov %rax, %r10 # Load the returned IMP @@ -230,13 +236,13 @@ pop %r9 pop %r8 pop %rcx + pop %rbx + pop %rax jmp 7b 6: # smallObject: and \receiver, %r10 # Find the small int type - shll $3, %r10d lea CDECL(SmallObjectClasses)(%rip), %r11 - add %r11, %r10 - mov (%r10), %r10 + mov (%r11, %r10, 8), %r10 jmp 1b .cfi_endproc .endm From f06508bfd97849fa7ddb702ad4443b17255ea5dc Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 10:10:44 +0000 Subject: [PATCH 10/42] Fix typo in comment. --- sarray2.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sarray2.h b/sarray2.h index 36e99be..b287438 100644 --- a/sarray2.h +++ b/sarray2.h @@ -70,7 +70,7 @@ typedef struct 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 leaves. It's really ugly, but seems to be faster. + // 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; From f1c323b0a23cd76d5e739ec84c7bcf221e168789 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 10:34:01 +0000 Subject: [PATCH 11/42] Add (untested!) updates to the MIPS / ARM asm paths. --- objc_msgSend.arm.S | 22 +++++++++------------- objc_msgSend.mips.S | 18 ++++-------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/objc_msgSend.arm.S b/objc_msgSend.arm.S index a7c48ac..0100dba 100644 --- a/objc_msgSend.arm.S +++ b/objc_msgSend.arm.S @@ -1,8 +1,3 @@ -#define DTABLE_OFFSET 32 -#define SMALLOBJ_MASK 1 -#define SHIFT_OFFSET 4 -#define DATA_OFFSET 12 -#define SLOT_OFFSET 16 .syntax unified .fpu neon @@ -44,16 +39,17 @@ teq r6, #0 beq 2f - and r6, r5, #0xff0000 - ldr r4, [r4, r6, asr#14] - ldr r4, [r4, #DATA_OFFSET] + ubfx r6, r5, #16, #8 // 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 - and r6, r5, #0xff00 - ldr r4, [r4, r6, asr#6] - ldr r4, [r4, #DATA_OFFSET] + ubfx r6, r5, #8, #8 // 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 - and r6, r5, #0xff - ldr ip, [r4, r6, asl#2] + uxtb r6, r5 // Low byte of sel id into r5 + add r6, r4, r6, lsl #2 // r6 = dtable address + dtable data offset + ldr r4, [r6, #DATA_OFFSET] // Load, adding in the data offset teq ip, #0 // If the slot is nil beq 5f // Go to the slow path and do the forwarding stuff diff --git a/objc_msgSend.mips.S b/objc_msgSend.mips.S index 4fb8607..5248d6c 100644 --- a/objc_msgSend.mips.S +++ b/objc_msgSend.mips.S @@ -3,20 +3,10 @@ #ifdef _ABI64 #define LP ld #define SP sd -#define DTABLE_OFFSET 64 -#define SMALLOBJ_MASK 7 -#define SHIFT_OFFSET 4 -#define DATA_OFFSET 16 -#define SLOT_OFFSET 32 #else #warning N32 is untested, O32 is unsupported. #define LP lw #define SP sw -#define DTABLE_OFFSET 32 -#define SMALLOBJ_MASK 1 -#define SHIFT_OFFSET 4 -#define DATA_OFFSET 12 -#define SLOT_OFFSET 16 #endif .macro dump_and_crash reg @@ -64,8 +54,8 @@ lw $zero, ($zero) 1: # class loaded, stored in $t4 LP $t4, DTABLE_OFFSET($t4) # Load the dtable from the class lw $t6, SHIFT_OFFSET($t4) # Load the shift (dtable size) - # $t4 = dtable, $t5 = sel index - LP $t7, DATA_OFFSET($t4) # Load the address of the start of the array + # $t4 = dtable, $t5 = sel index + daddi $t7, $t4, DATA_OFFSET # Compute the address of the start of the array beq $0, $t6, 3f # If this is a small dtable, jump to the small dtable handlers @@ -81,7 +71,7 @@ lw $zero, ($zero) #endif dadd $t6, $t6, $t7 LP $t7, ($t6) - LP $t7, DATA_OFFSET($t7) + daddi $t7, $t7, DATA_OFFSET # Compute the address of the start of the array 2: # dtable16: andi $t6, $t5, 0xff00 # mask the selector #ifdef _ABI64 @@ -91,7 +81,7 @@ lw $zero, ($zero) #endif dadd $t6, $t6, $t7 LP $t7, ($t6) - LP $t7, DATA_OFFSET($t7) + daddi $t7, $t7, DATA_OFFSET # Compute the address of the start of the array 3: # dtable8: andi $t6, $t5, 0xff # mask the selector #ifdef _ABI64 From b5e12e914c9f907bf6fab24253f177869cf9c54c Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 11:26:58 +0000 Subject: [PATCH 12/42] Make PropertyIntrospectionTest2 use arc. Recent versions of clang object to weak properties in non-ARC mode. --- Test/CMakeLists.txt | 2 +- ...ionTest2.m => PropertyIntrospectionTest2_arc.m} | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) rename Test/{PropertyIntrospectionTest2.m => PropertyIntrospectionTest2_arc.m} (99%) diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 988b926..88c9d52 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -19,7 +19,7 @@ set(TESTS NestedExceptions.m PropertyAttributeTest.m PropertyIntrospectionTest.m - PropertyIntrospectionTest2.m + PropertyIntrospectionTest2_arc.m ProtocolCreation.m ResurrectInDealloc_arc.m RuntimeTest.m diff --git a/Test/PropertyIntrospectionTest2.m b/Test/PropertyIntrospectionTest2_arc.m similarity index 99% rename from Test/PropertyIntrospectionTest2.m rename to Test/PropertyIntrospectionTest2_arc.m index b77721d..9af0ba6 100644 --- a/Test/PropertyIntrospectionTest2.m +++ b/Test/PropertyIntrospectionTest2_arc.m @@ -46,12 +46,12 @@ __attribute__((objc_root_class)) id idDefault; id idRetain; id idCopy; - id idWeak; + __weak id idWeak; id idStrong; int intNonatomic; id idReadonlyCopyNonatomic; id idReadonlyRetainNonatomic; - id idReadonlyWeakNonatomic; + __weak id idReadonlyWeakNonatomic; id _idOther; } @property char charDefault; @@ -196,12 +196,12 @@ __attribute__((objc_root_class)) id idDefault; id idRetain; id idCopy; - id idWeak; + __weak id idWeak; id idStrong; int intNonatomic; id idReadonlyCopyNonatomic; id idReadonlyRetainNonatomic; - id idReadonlyWeakNonatomic; + __weak id idReadonlyWeakNonatomic; id _idOther; } @end @@ -473,17 +473,17 @@ static int intDefault2Getter(id self, SEL _cmd) { static void intDefault2Setter(id self, SEL _cmd, int value) { Ivar ivar = class_getInstanceVariable(objc_getClass("PropertyTest"), "intDefault"); - object_setIvar(self, ivar, (id)(intptr_t)value); + object_setIvar(self, ivar, (__bridge id)(void*)(intptr_t)value); } static struct YorkshireTeaStruct* structDefault2Getter(id self, SEL _cmd) { Ivar ivar = class_getInstanceVariable(objc_getClass("PropertyTest"), "structDefault"); - return (struct YorkshireTeaStruct*)object_getIvar(self, ivar); + return (__bridge struct YorkshireTeaStruct*)object_getIvar(self, ivar); } void structDefault2Setter(id self, SEL _cmd, struct YorkshireTeaStruct* value) { Ivar ivar = class_getInstanceVariable(objc_getClass("PropertyTest"), "structDefault"); - object_setIvar(self, ivar, (id)value); + object_setIvar(self, ivar, (__bridge id)value); } int main(void) From 5c21e73f26ac7a4c4d341fafe01af37eac8e187f Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 12:49:51 +0000 Subject: [PATCH 13/42] Fix the property introspection test. --- Test/PropertyIntrospectionTest2_arc.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Test/PropertyIntrospectionTest2_arc.m b/Test/PropertyIntrospectionTest2_arc.m index 9af0ba6..157b50e 100644 --- a/Test/PropertyIntrospectionTest2_arc.m +++ b/Test/PropertyIntrospectionTest2_arc.m @@ -43,7 +43,7 @@ __attribute__((objc_root_class)) int intReadonlyGetter; int intReadwrite; int intAssign; - id idDefault; + __unsafe_unretained id idDefault; id idRetain; id idCopy; __weak id idWeak; @@ -74,7 +74,7 @@ __attribute__((objc_root_class)) @property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; @property(readwrite) int intReadwrite; @property(assign) int intAssign; -@property id idDefault; +@property(unsafe_unretained) id idDefault; @property(retain) id idRetain; @property(copy) id idCopy; @property(weak) id idWeak; @@ -129,6 +129,7 @@ __attribute__((objc_root_class)) @synthesize idOther = _idOther; @dynamic idDynamic; @dynamic idDynamicGetterSetter; +- (void)_ARCCompliantRetainRelease {} @end @protocol ProtocolTest @@ -152,7 +153,7 @@ __attribute__((objc_root_class)) @property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; @property(readwrite) int intReadwrite; @property(assign) int intAssign; -@property id idDefault; +@property(unsafe_unretained) id idDefault; @property(retain) id idRetain; @property(copy) id idCopy; @property(weak) id idWeak; @@ -193,7 +194,7 @@ __attribute__((objc_root_class)) int intReadonlyGetter; int intReadwrite; int intAssign; - id idDefault; + __unsafe_unretained id idDefault; id idRetain; id idCopy; __weak id idWeak; @@ -239,6 +240,7 @@ __attribute__((objc_root_class)) @synthesize idOther = _idOther; @dynamic idDynamic; @dynamic idDynamicGetterSetter; +- (void)_ARCCompliantRetainRelease {} @end #define ATTR(n, v) (objc_property_attribute_t){(n), (v)} @@ -666,6 +668,7 @@ int main(void) ATTR("V", "structDefault"))); PropertyTest* t = class_createInstance(testClass, 0); + assert(t != nil); object_setClass(t, testClass); t.intDefault = 2; assert(t.intDefault == 2); From d44bf5655b02491be15e64283175c157c74a6e40 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 12:50:53 +0000 Subject: [PATCH 14/42] Ensure that objects that support ARC will use ARC, even if they are created without sending a message to the class. Also ensure that protocols are always valid objects so that ARC doesn't become confused. --- Protocol2.m | 2 ++ dtable.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ dtable.h | 6 ++++++ protocol.c | 20 ++++++++++++++++---- runtime.c | 1 + 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Protocol2.m b/Protocol2.m index 7897b43..f65a339 100644 --- a/Protocol2.m +++ b/Protocol2.m @@ -20,6 +20,8 @@ - (id)self { return self; } @end @implementation Protocol2 @end +@interface __IncompleteProtocol : Protocol2 @end +@implementation __IncompleteProtocol @end /** * This class exists for the sole reason that the legacy GNU ABI did not diff --git a/dtable.c b/dtable.c index 3b42382..b537e9c 100644 --- a/dtable.c +++ b/dtable.c @@ -91,6 +91,50 @@ static void checkARCAccessors(Class cls) objc_set_class_flag(cls, objc_class_flag_fast_arc); } +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"); + } + if (cls->super_class != Nil) + { + checkARCAccessorsSlow(cls->super_class); + } + BOOL superIsFast = objc_test_class_flag(cls, 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 = l->methods[i].selector; + if (sel_isEqual(s, retain) || + sel_isEqual(s, release) || + sel_isEqual(s, autorelease)) + { + selfImplementsRetainRelease = YES; + } + else if (sel_isEqual(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, diff --git a/dtable.h b/dtable.h index 95eae20..c374892 100644 --- a/dtable.h +++ b/dtable.h @@ -126,3 +126,9 @@ void add_method_list_to_class(Class cls, * 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/protocol.c b/protocol.c index e88db48..4a58e96 100644 --- a/protocol.c +++ b/protocol.c @@ -45,6 +45,17 @@ struct objc_protocol2 *protocol_for_name(const char *name) static id ObjC2ProtocolClass = 0; +static id incompleteProtocolClass(void) +{ + static id IncompleteProtocolClass = 0; + if (IncompleteProtocolClass == nil) + { + IncompleteProtocolClass = objc_getClass("__IncompleteProtocol"); + } + return IncompleteProtocolClass; +} + + static int isEmptyProtocol(struct objc_protocol2 *aProto) { int isEmpty = @@ -497,7 +508,7 @@ Protocol*__unsafe_unretained* objc_copyProtocolList(unsigned int *outCount) Protocol *objc_allocateProtocol(const char *name) { if (objc_getProtocol(name) != NULL) { return NULL; } - Protocol *p = calloc(1, sizeof(Protocol2)); + Protocol *p = (Protocol*)class_createInstance((Class)incompleteProtocolClass(), 0); p->name = strdup(name); return p; } @@ -506,7 +517,7 @@ void objc_registerProtocol(Protocol *proto) if (NULL == proto) { return; } LOCK_RUNTIME_FOR_SCOPE(); if (objc_getProtocol(proto->name) != NULL) { return; } - if (nil != proto->isa) { return; } + if (incompleteProtocolClass() != proto->isa) { return; } proto->isa = ObjC2ProtocolClass; protocol_table_insert((struct objc_protocol2*)proto); } @@ -517,7 +528,7 @@ void protocol_addMethodDescription(Protocol *aProtocol, BOOL isInstanceMethod) { if ((NULL == aProtocol) || (NULL == name) || (NULL == types)) { return; } - if (nil != aProtocol->isa) { return; } + if (incompleteProtocolClass() != aProtocol->isa) { return; } Protocol2 *proto = (Protocol2*)aProtocol; struct objc_method_description_list **listPtr; if (isInstanceMethod) @@ -561,6 +572,7 @@ void protocol_addMethodDescription(Protocol *aProtocol, void protocol_addProtocol(Protocol *aProtocol, Protocol *addition) { if ((NULL == aProtocol) || (NULL == addition)) { return; } + if (incompleteProtocolClass() != aProtocol->isa) { return; } Protocol2 *proto = (Protocol2*)aProtocol; if (NULL == proto->protocol_list) { @@ -584,7 +596,7 @@ void protocol_addProperty(Protocol *aProtocol, BOOL isInstanceProperty) { if ((NULL == aProtocol) || (NULL == name)) { return; } - if (nil != aProtocol->isa) { return; } + if (incompleteProtocolClass() != aProtocol->isa) { return; } if (!isInstanceProperty) { return; } Protocol2 *proto = (Protocol2*)aProtocol; struct objc_property_list **listPtr; diff --git a/runtime.c b/runtime.c index 14a8d9e..27a8725 100644 --- a/runtime.c +++ b/runtime.c @@ -348,6 +348,7 @@ id class_createInstance(Class cls, size_t extraBytes) if (Nil == cls) { return nil; } id obj = gc->allocate_class(cls, extraBytes); obj->isa = cls; + checkARCAccessorsSlow(cls); call_cxx_construct(obj); return obj; } From 83913c329f47eaae597f01be948a125e81c10909 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 13:24:21 +0000 Subject: [PATCH 15/42] Fix the CFI directives in the MIPS objc_msgSend implementation. The frame adjustment was incorrect and there was nothing indicating where %ra was saved, which prevented unwinding. --- objc_msgSend.mips.S | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/objc_msgSend.mips.S b/objc_msgSend.mips.S index 5248d6c..df03249 100644 --- a/objc_msgSend.mips.S +++ b/objc_msgSend.mips.S @@ -153,7 +153,8 @@ lw $zero, ($zero) daddiu $a1, $a2, 0 # replace self with _cmd in $a1 .endif - .cfi_adjust_cfa_offset -SAVE_SIZE + .cfi_def_cfa_offset SAVE_SIZE + .cfi_offset 31, (64 - SAVE_SIZE) jalr $25 # Call the slow lookup function nop From 7d29bdc84abd92798aae1e8162dc6c6392b829f6 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 13:31:44 +0000 Subject: [PATCH 16/42] Use register names that don't make LLVM whine, rather than the correct ones. --- objc_msgSend.mips.S | 72 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/objc_msgSend.mips.S b/objc_msgSend.mips.S index df03249..7c4fe35 100644 --- a/objc_msgSend.mips.S +++ b/objc_msgSend.mips.S @@ -41,62 +41,62 @@ lw $zero, ($zero) daddiu $t8, $t8, %lo(%neg(%gp_rel(0b))) - andi $t4, \receiver, SMALLOBJ_MASK # Check if the receiver is a small object - bne $t4, $0, 6f # Get the small object class + andi $t0, \receiver, SMALLOBJ_MASK # Check if the receiver is a small object + bne $t0, $0, 6f # Get the small object class nop - LP $t5, (\sel) + LP $t1, (\sel) # By this point, we have a non-nil # receiver that is a real pointer - LP $t4, (\receiver) # Load the class + LP $t0, (\receiver) # Load the class -1: # class loaded, stored in $t4 - LP $t4, DTABLE_OFFSET($t4) # Load the dtable from the class - lw $t6, SHIFT_OFFSET($t4) # Load the shift (dtable size) - # $t4 = dtable, $t5 = sel index - daddi $t7, $t4, DATA_OFFSET # Compute the address of the start of the array +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, $t6, 3f # If this is a small dtable, jump to the small dtable handlers - daddi $v0, $t6, -8 + 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 $t6, 0x00ff # The mask for a big dtable won't fit in an and immediate - and $t6, $t6, $t5 # mask the selector + 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 $t6, $t6, 13 # Right shift 16, but then left shift by pointer size + dsrl $t2, $t2, 13 # Right shift 16, but then left shift by pointer size #else - srl $t6, $t6, 14 + srl $t2, $t2, 14 #endif - dadd $t6, $t6, $t7 - LP $t7, ($t6) - daddi $t7, $t7, DATA_OFFSET # Compute the address of the start of the array + dadd $t2, $t2, $t3 + LP $t3, ($t2) + daddi $t3, $t3, DATA_OFFSET # Compute the address of the start of the array 2: # dtable16: - andi $t6, $t5, 0xff00 # mask the selector + andi $t2, $t1, 0xff00 # mask the selector #ifdef _ABI64 - dsrl $t6, $t6, 5 # Right shift 8, but then left shift by pointer size + dsrl $t2, $t2, 5 # Right shift 8, but then left shift by pointer size #else - srl $t6, $t6, 6 + srl $t2, $t2, 6 #endif - dadd $t6, $t6, $t7 - LP $t7, ($t6) - daddi $t7, $t7, DATA_OFFSET # Compute the address of the start of the array + dadd $t2, $t2, $t3 + LP $t3, ($t2) + daddi $t3, $t3, DATA_OFFSET # Compute the address of the start of the array 3: # dtable8: - andi $t6, $t5, 0xff # mask the selector + andi $t2, $t1, 0xff # mask the selector #ifdef _ABI64 - dsll $t6, $t6, 3 # Left shift by pointer size + dsll $t2, $t2, 3 # Left shift by pointer size #else - sll $t6, $t6, 2 + sll $t2, $t2, 2 #endif - dadd $t6, $t6, $t7 - LP $t7, ($t6) + dadd $t2, $t2, $t3 + LP $t3, ($t2) - beq $0, $t7, 5f # Nil slot - invoke some kind of forwarding mechanism + beq $0, $t3, 5f # Nil slot - invoke some kind of forwarding mechanism nop - LP $25, SLOT_OFFSET($t7) + LP $25, SLOT_OFFSET($t3) jr $25 nop 4: # returnNil: @@ -183,14 +183,14 @@ lw $zero, ($zero) daddiu $sp, $sp, SAVE_SIZE 6: # smallObject: #if _ABI64 - dsll $t4, $t4, 3 # Convert tag to pointer offset - LP $t6, %got_disp(CDECL(SmallObjectClasses))($t8) # Load small object classes array address - daddu $t4, $t4, $t6 # Add the base address to the offset + 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 $t4, ($t4) # Load the class (in delay slot) + LP $t0, ($t0) # Load the class (in delay slot) #else b 1b - LP $t4, %got_disp(CDECL(SmallIntClass))($t8) + LP $t0, %got_disp(CDECL(SmallIntClass))($t8) #endif .cfi_endproc .endm From dbdc26523c8e1787f6d18731caa698a28bd3eb13 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 14:03:06 +0000 Subject: [PATCH 17/42] Fix the property introspection test some more. --- Test/PropertyIntrospectionTest2_arc.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Test/PropertyIntrospectionTest2_arc.m b/Test/PropertyIntrospectionTest2_arc.m index 157b50e..fd4b7d6 100644 --- a/Test/PropertyIntrospectionTest2_arc.m +++ b/Test/PropertyIntrospectionTest2_arc.m @@ -478,14 +478,14 @@ static void intDefault2Setter(id self, SEL _cmd, int value) { object_setIvar(self, ivar, (__bridge id)(void*)(intptr_t)value); } -static struct YorkshireTeaStruct* structDefault2Getter(id self, SEL _cmd) { - Ivar ivar = class_getInstanceVariable(objc_getClass("PropertyTest"), "structDefault"); - return (__bridge struct YorkshireTeaStruct*)object_getIvar(self, ivar); +static struct YorkshireTeaStruct structDefault2Getter(id self, SEL _cmd) { + struct YorkshireTeaStruct *s; + object_getInstanceVariable(self, "structDefault", &s); + return *s; } -void structDefault2Setter(id self, SEL _cmd, struct YorkshireTeaStruct* value) { - Ivar ivar = class_getInstanceVariable(objc_getClass("PropertyTest"), "structDefault"); - object_setIvar(self, ivar, (__bridge id)value); +void structDefault2Setter(id self, SEL _cmd, struct YorkshireTeaStruct value) { + object_setInstanceVariable(self, "structDefault", &value); } int main(void) From bfe5de572e327d8dbd092bce89d811797e9d0042 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 14:03:33 +0000 Subject: [PATCH 18/42] Fix the ivar access functions for objects that are not pointer sized. --- ivar.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ivar.c b/ivar.c index bf4f54f..c587bf2 100644 --- a/ivar.c +++ b/ivar.c @@ -1,4 +1,5 @@ #include +#include #include #include "objc/runtime.h" #include "class.h" @@ -150,13 +151,24 @@ void object_setIvar(id object, Ivar ivar, id value) { char *addr = (char*)object; addr += ivar_getOffset(ivar); + // FIXME: ARC ownership! We don't have enough information to do this + // correctly with the current ABI. We need to fix it with the next ABI + // bump. *(id*)addr = value; } Ivar object_setInstanceVariable(id obj, const char *name, void *value) { Ivar ivar = class_getInstanceVariable(object_getClass(obj), name); - object_setIvar(obj, ivar, value); + 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; } @@ -170,7 +182,7 @@ Ivar object_getInstanceVariable(id obj, const char *name, void **outValue) Ivar ivar = class_getInstanceVariable(object_getClass(obj), name); if (NULL != outValue) { - *outValue = object_getIvar(obj, ivar); + *outValue = (((char*)obj) + ivar_getOffset(ivar)); } return ivar; } From 57858add6e70dde0b0d391119d4dfad000c9752b Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 24 Dec 2015 14:30:42 +0000 Subject: [PATCH 19/42] Silence warning in test. --- Test/PropertyIntrospectionTest2_arc.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/PropertyIntrospectionTest2_arc.m b/Test/PropertyIntrospectionTest2_arc.m index fd4b7d6..a49e1c1 100644 --- a/Test/PropertyIntrospectionTest2_arc.m +++ b/Test/PropertyIntrospectionTest2_arc.m @@ -480,7 +480,7 @@ static void intDefault2Setter(id self, SEL _cmd, int value) { static struct YorkshireTeaStruct structDefault2Getter(id self, SEL _cmd) { struct YorkshireTeaStruct *s; - object_getInstanceVariable(self, "structDefault", &s); + object_getInstanceVariable(self, "structDefault", (void**)&s); return *s; } From 2777fea77aab2d4956b22913330383533ee41931 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 25 Dec 2015 09:15:01 +0000 Subject: [PATCH 20/42] Enable correct behaviour with respect to ARC for objc_{g,s}etIvar(). --- Test/CMakeLists.txt | 1 + Test/ivar_arc.m | 52 ++++++++++++++++++++++++++++ class.h | 22 ++++++++++-- ivar.c | 82 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 Test/ivar_arc.m diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 88c9d52..6425136 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -25,6 +25,7 @@ set(TESTS RuntimeTest.m WeakBlock_arc.m WeakReferences_arc.m + ivar_arc.m objc_msgSend.m msgInterpose.m NilException.m diff --git a/Test/ivar_arc.m b/Test/ivar_arc.m new file mode 100644 index 0000000..d45775d --- /dev/null +++ b/Test/ivar_arc.m @@ -0,0 +1,52 @@ +#include "Test.h" +#include "../objc/runtime.h" + +@interface Foo : Test +{ + @public + __weak id w; + __unsafe_unretained id u; + __strong id s; +} +@end +@implementation Foo @end +@interface Dealloc : Test +@end +int dealloc = 0; +@implementation Dealloc +- (void)dealloc +{ + dealloc++; +} +@end + +void setIvar(id obj, const char * name, id val) +{ + object_setIvar(obj, class_getInstanceVariable(object_getClass(obj), name), val); +} + +int main(void) +{ + Foo *f = [Foo new]; + Dealloc *d = [Dealloc new]; + __unsafe_unretained Dealloc *dead; + setIvar(f, "w", d); + assert(f->w == d); + assert(dealloc == 0); + d = 0; + assert(dealloc == 1); + assert(f->w == nil); + dealloc = 0; + d = [Dealloc new]; + dead = d; + setIvar(f, "s", d); + assert(dealloc == 0); + assert(f->s == d); + d = nil; + assert(dealloc == 0); + assert(f->s == dead); + setIvar(f, "s", nil); + assert(dealloc == 1); + assert(f->s == nil); + return 0; +} diff --git a/class.h b/class.h index e37ab8b..8da94eb 100644 --- a/class.h +++ b/class.h @@ -18,6 +18,24 @@ struct objc_bitfield 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; +} + + struct objc_class { /** @@ -141,13 +159,13 @@ struct objc_class * bits are set, from low to high, for each ivar in the object that is a * strong pointer. */ - intptr_t strong_pointers; + 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. */ - intptr_t weak_pointers; + uintptr_t weak_pointers; }; /** diff --git a/ivar.c b/ivar.c index c587bf2..8504dae 100644 --- a/ivar.c +++ b/ivar.c @@ -2,6 +2,7 @@ #include #include #include "objc/runtime.h" +#include "objc/objc-arc.h" #include "class.h" #include "ivar.h" #include "visibility.h" @@ -143,18 +144,69 @@ PRIVATE void objc_compute_ivar_offsets(Class class) } } +typedef enum { + ownership_invalid, + ownership_strong, + ownership_weak, + ownership_unsafe +} ownership; + +ownership ownershipForIvar(Class cls, Ivar ivar) +{ + struct objc_ivar_list *list = cls->ivars; + if ((ivar < list->ivar_list) || (ivar >= &list->ivar_list[list->count])) + { + // Try the superclass + if (cls->super_class) + { + return ownershipForIvar(cls->super_class, ivar); + } + return ownership_invalid; + } + if (!objc_test_class_flag(cls, objc_class_flag_new_abi)) + { + return ownership_unsafe; + } + if (cls->abi_version < 1) + { + return ownership_unsafe; + } + if (objc_bitfield_test(cls->strong_pointers, (ivar - list->ivar_list))) + { + return ownership_strong; + } + if (objc_bitfield_test(cls->weak_pointers, (ivar - list->ivar_list))) + { + return ownership_weak; + } + return ownership_unsafe; +} + //////////////////////////////////////////////////////////////////////////////// // Public API functions //////////////////////////////////////////////////////////////////////////////// void object_setIvar(id object, Ivar ivar, id value) { - char *addr = (char*)object; - addr += ivar_getOffset(ivar); - // FIXME: ARC ownership! We don't have enough information to do this - // correctly with the current ABI. We need to fix it with the next ABI - // bump. - *(id*)addr = value; + ownershipForIvar(object_getClass(object), ivar); + id *addr = (id*)((char*)object + ivar_getOffset(ivar)); + switch (ownershipForIvar(object_getClass(object), ivar)) + { + case ownership_strong: + objc_storeStrong(addr, value); + break; + case ownership_weak: + objc_storeWeak(addr, value); + break; + case ownership_unsafe: + *addr = value; + break; + case ownership_invalid: +#ifndef NDEBUG + fprintf(stderr, "Ivar does not belong to this class!\n"); +#endif + break; + } } Ivar object_setInstanceVariable(id obj, const char *name, void *value) @@ -174,7 +226,23 @@ Ivar object_setInstanceVariable(id obj, const char *name, void *value) id object_getIvar(id object, Ivar ivar) { - return *(id*)(((char*)object) + ivar_getOffset(ivar)); + ownershipForIvar(object_getClass(object), ivar); + id *addr = (id*)((char*)object + ivar_getOffset(ivar)); + switch (ownershipForIvar(object_getClass(object), ivar)) + { + case ownership_strong: + return objc_retainAutoreleaseReturnValue(*addr); + case ownership_weak: + return objc_loadWeak(addr); + break; + case ownership_unsafe: + return *addr; + case ownership_invalid: +#ifndef NDEBUG + fprintf(stderr, "Ivar does not belong to this class!\n"); +#endif + return nil; + } } Ivar object_getInstanceVariable(id obj, const char *name, void **outValue) From 0a847e10beaf66cd94d60d63f699d06a9341930d Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 25 Dec 2015 09:15:38 +0000 Subject: [PATCH 21/42] Tweak the message sending microbenchmark a bit so that it produces output compatible with ministat. --- Test/objc_msgSend.m | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Test/objc_msgSend.m b/Test/objc_msgSend.m index 3703365..7cb7eca 100644 --- a/Test/objc_msgSend.m +++ b/Test/objc_msgSend.m @@ -198,32 +198,35 @@ int main(void) assert(0 == [f fzero]); [TestCls manyArgs: 0 : 1 : 2 : 3: 4: 5: 6: 7: 8: 9: 10 : 0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10]; #ifdef BENCHMARK + const int iterations = 1000000000; + double times[3]; clock_t c1, c2; c1 = clock(); - for (int i=0 ; i<100000000 ; i++) + for (int i=0 ; i Date: Fri, 25 Dec 2015 09:16:08 +0000 Subject: [PATCH 22/42] Work in progress tweaks to the release announcement. --- ANNOUNCE | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ANNOUNCE b/ANNOUNCE index 9d1029b..928662b 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -1,4 +1,4 @@ -GNUstep Objective-C Runtime 1.8 +GNUstep Objective-C Runtime 1.x =============================== This the ninth official release of the GNUstep Objective-C runtime (a.k.a. @@ -6,19 +6,19 @@ libobjc2). This runtime was designed to support the features of modern dialects of Objective-C for use with GNUstep and other Objective-C programs. Highlights of this release include: -- Added API for tracing, allowing interposition on all message sends matching a - given selector. +- Improved the dispatch table representation to improve performance and cache + usage on the fast path. -- Numerous bug fixes and stability improvements. +- Numerous improvements to the interaction between runtime functions and ARC. -You may obtain the code for this release from git and use the 1.8 branch: +You may obtain the code for this release from git and use the 1.x branch: https://github.com/gnustep/libobjc2.git Alternatively, a tarball is available from: -https://github.com/gnustep/libobjc2/archive/v1.8.zip -https://github.com/gnustep/libobjc2/archive/v1.8.tar.gz +https://github.com/gnustep/libobjc2/archive/v1.x.zip +https://github.com/gnustep/libobjc2/archive/v1.x.tar.gz The runtime library is responsible for implementing the core features of the object model, as well as exposing introspection features to the user. The From 7eeffe9d09b5fe5773b4e8fbea4dd103d6233b40 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 25 Dec 2015 13:59:25 +0000 Subject: [PATCH 23/42] Rewrite the block-to-imp code so that: - We reuse the memory allocated for blocks - We no longer have the same physical page mapped write and execute --- Test/BlockImpTest.m | 6 +- block_to_imp.c | 210 ++++++++++++++++++++++++++++---------------- block_trampolines.S | 50 ++++++----- 3 files changed, 167 insertions(+), 99 deletions(-) diff --git a/Test/BlockImpTest.m b/Test/BlockImpTest.m index 713ebcc..190779a 100644 --- a/Test/BlockImpTest.m +++ b/Test/BlockImpTest.m @@ -21,6 +21,7 @@ __attribute__((objc_root_class)) +(struct big)sret; @end + int main(void) { __block int b = 0; @@ -37,7 +38,10 @@ int main(void) assert(4 == [Foo count: 2]); assert(6 == [Foo count: 2]); assert(imp_getBlock(imp) == (blk)); - imp_removeBlock(blk); + IMP imp2 = imp_implementationWithBlock(blk); + assert(imp != imp2); + imp_removeBlock(imp); + assert(imp_getBlock(imp) != (blk)); blk = ^(id self) { struct big b = {1, 2, 3, 4, 5}; diff --git a/block_to_imp.c b/block_to_imp.c index 9fadea2..c927be1 100644 --- a/block_to_imp.c +++ b/block_to_imp.c @@ -35,136 +35,198 @@ void __clear_cache(void* start, void* end); #define PAGE_SIZE 4096 -static void *executeBuffer; -static void *writeBuffer; -static ptrdiff_t offset; -static mutex_t trampoline_lock; -#ifndef SHM_ANON -static char *tmpPattern; -static void initTmpFile(void) +struct block_header { - char *tmp = getenv("TMPDIR"); - if (NULL == tmp) - { - tmp = "/tmp/"; - } - if (0 > asprintf(&tmpPattern, "%s/objc_trampolinesXXXXXXXXXXX", tmp)) - { - abort(); - } -} -static int getAnonMemFd(void) + void *block; + void(*fnptr)(void); + /** + * On 64-bit platforms, we have 16 bytes for instructions, which ought to + * be enough without padding. On MIPS, we need + * 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. + */ +#if defined(__i386__) || (defined(__mips__) && !defined(__mips_n64)) + uint64_t padding[3]; +#elif defined(__mips__) + 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 { - const char *pattern = strdup(tmpPattern); - int fd = mkstemp(pattern); - unlink(pattern); - free(pattern); - return fd; -} -#else -static void initTmpFile(void) {} -static int getAnonMemFd(void) + 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 { - return shm_open(SHM_ANON, O_CREAT | O_RDWR, 0); -} -#endif + 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) { + fprintf(stderr, "Trampoline size: %d, header size: %d\n", (int)(&__objc_block_trampoline_end - &__objc_block_trampoline), (int)sizeof(struct block_header)); + 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); - initTmpFile(); } -static struct wx_buffer alloc_buffer(size_t size) +static id invalid(id self, SEL _cmd) { - LOCK_FOR_SCOPE(&trampoline_lock); - if ((0 == offset) || (offset + size >= PAGE_SIZE)) + 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)); + metadata->buffers = valloc(sizeof(struct trampoline_buffers)); + 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 trampoline_buffers)); + memcpy(block, start, end-start); } - struct wx_buffer b = { writeBuffer + offset, executeBuffer + offset }; - offset += size; - return b; + 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; } -extern void __objc_block_trampoline; -extern void __objc_block_trampoline_end; -extern void __objc_block_trampoline_sret; -extern void __objc_block_trampoline_end_sret; +static struct trampoline_set *sret_trampolines; +static struct trampoline_set *trampolines; IMP imp_implementationWithBlock(void *block) { struct Block_layout *b = 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; } + // 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; + fprintf(stderr, "Putting block %d invoke %p (block %p) at %p (block at %p)\n", i, b->invoke, &b, &h->fnptr, &h->block); + fprintf(stderr, "Returning imp: %p\n", &set->buffers->rx_buffer[i*sizeof(struct block_header)]); - struct wx_buffer buf = alloc_buffer(trampolineSize + 2*sizeof(void*)); - void **out = buf.w; - out[0] = (void*)b->invoke; - out[1] = Block_copy(b); - memcpy(&out[2], start, trampolineSize); - out = buf.x; - char *newIMP = (char*)&out[2]; - clear_cache(newIMP, newIMP+trampolineSize); - return (IMP)newIMP; + return (IMP)&set->buffers->rx_buffer[i*sizeof(struct block_header)]; + } + } + UNREACHABLE("Failed to allocate block"); } -static void* isBlockIMP(void *anIMP) +static int indexForIMP(IMP anIMP, struct trampoline_set **setptr) { - LOCK(&trampoline_lock); - void *e = executeBuffer; - void *w = writeBuffer; - UNLOCK(&trampoline_lock); - while (e) + for (struct trampoline_set *set=*setptr ; set!=NULL ; set=set->next) { - if ((anIMP > e) && (anIMP < e + PAGE_SIZE)) + if (((char*)anIMP >= set->buffers->rx_buffer) && + ((char*)anIMP < &set->buffers->rx_buffer[PAGE_SIZE])) { - return ((char*)w) + ((char*)anIMP - (char*)e); + *setptr = set; + ptrdiff_t offset = (char*)anIMP - set->buffers->rx_buffer; + return offset / sizeof(struct block_header); } - e = *(void**)e; - w = *(void**)w; } - return 0; + return -1; } void *imp_getBlock(IMP anImp) { - if (0 == isBlockIMP((void*)anImp)) { return 0; } - return *(((void**)anImp) - 1); + 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) { - void *w = isBlockIMP((void*)anImp); - if (0 == w) { return NO; } - Block_release(((void**)anImp) - 1); + 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; } diff --git a/block_trampolines.S b/block_trampolines.S index f95d6a0..1ea96a8 100644 --- a/block_trampolines.S +++ b/block_trampolines.S @@ -42,74 +42,76 @@ TYPE_DIRECTIVE(CDECL(__objc_block_trampoline), @function) #define ARG2 %rdx #endif CDECL(__objc_block_trampoline): - mov -15(%rip), ARG1 # Load the block pointer into the second argument + mov -0x1007(%rip), ARG1 # Load the block pointer into the second argument xchg ARG1, ARG0 # Swap the first and second arguments - jmp *-32(%rip) # Call the block function + jmp *-0x1008(%rip) # Call the block function CDECL(__objc_block_trampoline_end): CDECL(__objc_block_trampoline_sret): - mov -15(%rip), ARG2 # Load the block pointer into the second argument + mov -0x1007(%rip), ARG2 # Load the block pointer into the second argument xchg ARG1, ARG2 # Swap the first and second arguments - jmp *-32(%rip) # Call the block function + jmp *-0x1008(%rip) # Call the block function CDECL(__objc_block_trampoline_end_sret): #elif __i386 CDECL(__objc_block_trampoline): - call next_line # Store the instruction pointer on the stack -next_line: + call Lnext_line # Store the instruction pointer on the stack +Lnext_line: pop %eax # Load the old instruction pointer mov 4(%esp), %ebx # Load the self parameter mov %ebx, 8(%esp) # Store self as the second argument - mov -9(%eax), %ebx # Load the block pointer to %ebx + mov -0x1005(%eax), %ebx # Load the block pointer to %ebx mov %ebx, 4(%esp) # Store the block pointer in the first argument - jmp *-13(%eax) # Call the block function + jmp *-0x1001(%eax) # Call the block function CDECL(__objc_block_trampoline_end): CDECL(__objc_block_trampoline_sret): - call next_line2 # Store the instruction pointer on the stack -next_line2: + call Lnext_line2 # Store the instruction pointer on the stack +Lnext_line2: pop %eax # Load the old instruction pointer mov 8(%esp), %ebx # Load the self parameter mov %ebx, 12(%esp) # Store self as the second argument - mov -9(%eax), %ebx # Load the block pointer to %ebx + mov -0x1005(%eax), %ebx # Load the block pointer to %ebx mov %ebx, 8(%esp) # Store the block pointer in the first argument - jmp *-13(%eax) # Call the block function + jmp *-0x1001(%eax) # Call the block function CDECL(__objc_block_trampoline_end_sret): #elif __mips__ # ifdef _ABI64 CDECL(__objc_block_trampoline): move $a1, $a0 - ld $a0, -16($25) - ld $25, -8($25) + ld $a0, -4096($25) + ld $25, -4088($25) jr $25 CDECL(__objc_block_trampoline_end): CDECL(__objc_block_trampoline_sret): move $a2, $a1 - ld $a1, -16($25) - ld $25, -8($25) + ld $a1, -4096($25) + ld $25, -4088($25) jr $25 CDECL(__objc_block_trampoline_end_sret): # else CDECL(__objc_block_trampoline): move $a1, $a0 - lw $a0, -8($25) - lw $25, -4($25) + lw $a0, -4096($25) + lw $25, -4092($25) jr $25 CDECL(__objc_block_trampoline_end): CDECL(__objc_block_trampoline_sret): move $a2, $a1 - lw $a1, -8($25) - lw $25, -4($25) + lw $a1, -4096($25) + lw $25, -4092($25) jr $25 CDECL(__objc_block_trampoline_end_sret): # endif #elif __arm__ CDECL(__objc_block_trampoline): + sub r12, pc, #4096 mov r1, r0 // Move self over _cmd - ldr r0, [pc, #-16] // Load the block pointer over self - ldr pc, [pc, #-24] // Jump to the block function + ldr r0, [r12, #-4] // Load the block pointer over self + ldr pc, [r12] // Jump to the block function CDECL(__objc_block_trampoline_end): CDECL(__objc_block_trampoline_sret): + sub r12, pc, #4096 mov r2, r1 // Move self over _cmd - ldr r1, [pc, #-16] // Load the block pointer over self - ldr pc, [pc, #-24] // Jump to the block function + ldr r0, [r12, #-4] // Load the block pointer over self + ldr pc, [r12] // Jump to the block function CDECL(__objc_block_trampoline_end_sret): #else #warning imp_implementationWithBlock() not implemented for your architecture From afc784175cc0c0556eee8b41bf9a74315af7cfd2 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 25 Dec 2015 15:02:13 +0100 Subject: [PATCH 24/42] Update release notes for last commit. --- ANNOUNCE | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ANNOUNCE b/ANNOUNCE index 928662b..7bf7b9c 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -9,6 +9,10 @@ Highlights of this release include: - Improved the dispatch table representation to improve performance and cache usage on the fast path. +- The implementation of `imp_implementationWithBlock`, the function that allows + blocks to be used as methods, no longer requires physical pages to be mapped + both writeable and executable. + - Numerous improvements to the interaction between runtime functions and ARC. You may obtain the code for this release from git and use the 1.x branch: From 77df8d3d928af30b1bfcdc2c48f477d01ec1da66 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 25 Dec 2015 14:54:52 +0000 Subject: [PATCH 25/42] Remove debugging line. --- block_to_imp.c | 1 - 1 file changed, 1 deletion(-) diff --git a/block_to_imp.c b/block_to_imp.c index c927be1..f4472b1 100644 --- a/block_to_imp.c +++ b/block_to_imp.c @@ -93,7 +93,6 @@ extern char __objc_block_trampoline_end_sret; PRIVATE void init_trampolines(void) { - fprintf(stderr, "Trampoline size: %d, header size: %d\n", (int)(&__objc_block_trampoline_end - &__objc_block_trampoline), (int)sizeof(struct block_header)); 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); From 42b78dba9b1dbd9e078b9d16430b712ae6b11ce1 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 25 Dec 2015 14:57:05 +0000 Subject: [PATCH 26/42] (Hopefully) fix C++ exception interop on ARM. --- dwarf_eh.h | 4 ++++ eh_personality.c | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dwarf_eh.h b/dwarf_eh.h index 56cb280..e06a585 100644 --- a/dwarf_eh.h +++ b/dwarf_eh.h @@ -258,6 +258,10 @@ static inline struct dwarf_eh_lsda parse_lsda(struct _Unwind_Context *context, u //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 diff --git a/eh_personality.c b/eh_personality.c index e5abf5a..31aecc1 100644 --- a/eh_personality.c +++ b/eh_personality.c @@ -486,8 +486,7 @@ BEGIN_PERSONALITY_FUNCTION(__gnustep_objc_personality_v0) return internal_objc_personality(version, actions, exceptionClass, exceptionObject, context, YES); } -// FIXME! -#ifndef __arm__ + BEGIN_PERSONALITY_FUNCTION(__gnustep_objcxx_personality_v0) if (exceptionClass == objc_exception_class) { @@ -497,17 +496,15 @@ BEGIN_PERSONALITY_FUNCTION(__gnustep_objcxx_personality_v0) id *newEx = __cxa_allocate_exception(sizeof(id)); *newEx = ex->object; ex->cxx_exception = objc_init_cxx_exception(newEx); + memcpy(ex->cxx_exception, exceptionObject, sizeof(struct _Unwind_Exception)); ex->cxx_exception->exception_class = cxx_exception_class; ex->cxx_exception->exception_cleanup = cleanup; - ex->cxx_exception->private_1 = exceptionObject->private_1; - ex->cxx_exception->private_2 = exceptionObject->private_2; } exceptionObject = ex->cxx_exception; exceptionClass = cxx_exception_class; } return CALL_PERSONALITY_FUNCTION(__gxx_personality_v0); } -#endif // 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 From 7d1ea63046a1fdc59614fbae9bab650fda70ca3d Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 25 Dec 2015 17:03:50 +0000 Subject: [PATCH 27/42] Silence some valgrind warnings. The ignored field is never used, so it doesn't matter that the comparison to the old value is reading uninitialised memory (unless the compiler is clever enough to spot that this is undefined behaviour and do something evil). --- encoding2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/encoding2.c b/encoding2.c index 5e73245..8e68019 100644 --- a/encoding2.c +++ b/encoding2.c @@ -236,7 +236,7 @@ static const char *sizeof_type(const char *type, size_t *size) { // All pointers look the same to me. *size += sizeof(void*) * 8; - size_t ignored; + size_t ignored = 0; // Skip the definition of the pointeee type. return sizeof_type(type+1, &ignored); } @@ -329,7 +329,7 @@ static const char *alignof_type(const char *type, size_t *align) { *align = max((alignof(void*) * 8), *align); // All pointers look the same to me. - size_t ignored; + size_t ignored = 0; // Skip the definition of the pointeee type. return alignof_type(type+1, &ignored); } From 813a26025db13a11878206f583dfb2c105b5d17a Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sat, 26 Dec 2015 18:10:18 +0100 Subject: [PATCH 28/42] Remove an instruction that should have been deleted with the dtable rewrite. --- objc_msgSend.arm.S | 1 - 1 file changed, 1 deletion(-) diff --git a/objc_msgSend.arm.S b/objc_msgSend.arm.S index 0100dba..bde50f3 100644 --- a/objc_msgSend.arm.S +++ b/objc_msgSend.arm.S @@ -32,7 +32,6 @@ ldr r5, [\sel] // selector->index -> r5 ldr r6, [r4, #SHIFT_OFFSET] // dtable->shift -> r6 - ldr r4, [r4, #DATA_OFFSET] // dtable->data -> r4 teq r6, #8 // If this is a small dtable, jump to the small dtable handlers beq 1f From e8e5f1e254c8f5905a5834cd74d188d65ca39dc2 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 15:36:30 +0100 Subject: [PATCH 29/42] Try calling the IMP directly before calling it indirectly in the block-as-method test. This makes debugging *much* easier! --- Test/BlockImpTest.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Test/BlockImpTest.m b/Test/BlockImpTest.m index 190779a..ce9931a 100644 --- a/Test/BlockImpTest.m +++ b/Test/BlockImpTest.m @@ -33,8 +33,9 @@ int main(void) char *type = block_copyIMPTypeEncoding_np(blk); assert(NULL != type); class_addMethod((objc_getMetaClass("Foo")), @selector(count:), imp, type); + Class cls = objc_getClass("Foo"); + assert(2 == ((int(*)(id,SEL,int))imp)(cls, @selector(count:), 2)); free(type); - assert(2 == [Foo count: 2]); assert(4 == [Foo count: 2]); assert(6 == [Foo count: 2]); assert(imp_getBlock(imp) == (blk)); From e8604511a6c9fd024fa470bd881202eb9bbf707a Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 15:36:53 +0100 Subject: [PATCH 30/42] Fix block metadata address calculation. --- block_to_imp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block_to_imp.c b/block_to_imp.c index f4472b1..e6c771f 100644 --- a/block_to_imp.c +++ b/block_to_imp.c @@ -113,7 +113,7 @@ static struct trampoline_set *alloc_trampolines(char *start, char *end) { metadata->buffers->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 trampoline_buffers)); + 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; From 19331670460f82e0e82eac35db5a9e85e8b05171 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 15:38:03 +0100 Subject: [PATCH 31/42] Copy (retain) blocks when they are used as IMPs. --- block_to_imp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/block_to_imp.c b/block_to_imp.c index e6c771f..1ad3211 100644 --- a/block_to_imp.c +++ b/block_to_imp.c @@ -149,6 +149,7 @@ IMP imp_implementationWithBlock(void *block) // 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) { From a36013f090b1cc9c6684afee28546d8a28c7637e Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 15:38:28 +0100 Subject: [PATCH 32/42] Fix the offsets in the ARM block trampoline. --- block_trampolines.S | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/block_trampolines.S b/block_trampolines.S index 1ea96a8..add0e7f 100644 --- a/block_trampolines.S +++ b/block_trampolines.S @@ -104,14 +104,14 @@ CDECL(__objc_block_trampoline_end_sret): CDECL(__objc_block_trampoline): sub r12, pc, #4096 mov r1, r0 // Move self over _cmd - ldr r0, [r12, #-4] // Load the block pointer over self - ldr pc, [r12] // Jump to the block function + ldr r0, [r12, #-8] // Load the block pointer over self + ldr pc, [r12, #-4] // Jump to the block function CDECL(__objc_block_trampoline_end): CDECL(__objc_block_trampoline_sret): sub r12, pc, #4096 mov r2, r1 // Move self over _cmd - ldr r0, [r12, #-4] // Load the block pointer over self - ldr pc, [r12] // Jump to the block function + ldr r1, [r12, #-8] // Load the block pointer over self + ldr pc, [r12, #-4] // Jump to the block function CDECL(__objc_block_trampoline_end_sret): #else #warning imp_implementationWithBlock() not implemented for your architecture From 197018f090e28ff4b5de0eca10dd88c51cdc33c9 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 15:39:16 +0100 Subject: [PATCH 33/42] Fix objc_msgSend on ARM. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When loading the slot, it’s quite important to load it into the register where you’re going to use it! --- objc_msgSend.arm.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/objc_msgSend.arm.S b/objc_msgSend.arm.S index bde50f3..e6daaff 100644 --- a/objc_msgSend.arm.S +++ b/objc_msgSend.arm.S @@ -48,7 +48,7 @@ 2: // dtable8 uxtb r6, r5 // Low byte of sel id into r5 add r6, r4, r6, lsl #2 // r6 = dtable address + dtable data offset - ldr r4, [r6, #DATA_OFFSET] // Load, adding in the data offset + ldr ip, [r6, #DATA_OFFSET] // Load, adding in the data offset teq ip, #0 // If the slot is nil beq 5f // Go to the slow path and do the forwarding stuff From 034cb08c10fb8ed60cc2cb297f0c4a2bfd6b8992 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 15:49:44 +0100 Subject: [PATCH 34/42] Provide code paths for pre-ARMv6t2 ARM ISAs. --- objc_msgSend.arm.S | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/objc_msgSend.arm.S b/objc_msgSend.arm.S index e6daaff..b30bd83 100644 --- a/objc_msgSend.arm.S +++ b/objc_msgSend.arm.S @@ -1,5 +1,28 @@ .syntax unified .fpu neon +#if ((__ARM_ARCH >= 7) || defined (__ARM_ARCH_6T2__)) +.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 +.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 @@ -38,15 +61,15 @@ teq r6, #0 beq 2f - ubfx r6, r5, #16, #8 // Put byte 3 of the sel id in r6 + 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 - ubfx r6, r5, #8, #8 // Put byte 2 of the sel id in r6 + 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 - uxtb r6, r5 // Low byte of sel id into r5 + 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 From ef458104dd9110fa0fbe4c608104c96dbd2dfbc6 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 16:04:50 +0100 Subject: [PATCH 35/42] Remove some debugging code. --- block_to_imp.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/block_to_imp.c b/block_to_imp.c index 1ad3211..ee4a36b 100644 --- a/block_to_imp.c +++ b/block_to_imp.c @@ -167,9 +167,6 @@ IMP imp_implementationWithBlock(void *block) assert(set->first_free >= -1); h->fnptr = (void(*)(void))b->invoke; h->block = b; - fprintf(stderr, "Putting block %d invoke %p (block %p) at %p (block at %p)\n", i, b->invoke, &b, &h->fnptr, &h->block); - fprintf(stderr, "Returning imp: %p\n", &set->buffers->rx_buffer[i*sizeof(struct block_header)]); - return (IMP)&set->buffers->rx_buffer[i*sizeof(struct block_header)]; } } From bd790df7e37d4642d156be4cb76f23968eb9df84 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 16:05:23 +0100 Subject: [PATCH 36/42] =?UTF-8?q?Disable=20building=20LLVM=20passes=20for?= =?UTF-8?q?=20now.=20=20It=E2=80=99s=20unlikely=20that=20anyone=20actually?= =?UTF-8?q?=20wants=20them=20at=20the=20moment,=20and=20we=20should=20prob?= =?UTF-8?q?ably=20remove=20them=20at=20some=20future=20date.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 83c9363..eb29b99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,7 +126,7 @@ else () add_definitions(-DNO_LEGACY) endif () -find_package(LLVM QUIET) +#find_package(LLVM QUIET) set(DEFAULT_ENABLE_LLVM ${LLVM_FOUND}) if (DEFAULT_ENABLE_LLVM) exec_program(llvm-config From cfb87b0cf91ec18bd9d12a7c400137ebf4b73fad Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 16:09:16 +0100 Subject: [PATCH 37/42] Preserve floating point argument registers across calls out to the slow path. --- objc_msgSend.arm.S | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/objc_msgSend.arm.S b/objc_msgSend.arm.S index b30bd83..7f924c6 100644 --- a/objc_msgSend.arm.S +++ b/objc_msgSend.arm.S @@ -93,6 +93,12 @@ push {\receiver} // &self, _cmd in arguments .save {\receiver} +#ifndef __SOFTFP__ + vpush {q0-q3} + .pad #64 +#endif + + mov r0, sp mov r1, \sel @@ -100,6 +106,9 @@ 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 b 3b From 8583fc7f61fe981e55b1eca8d9cc20253fef3088 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 20:13:49 +0100 Subject: [PATCH 38/42] =?UTF-8?q?Explicitly=20make=20char=20signed=20in=20?= =?UTF-8?q?the=20property=20introspection=20test.=20=20The=20C=20spec=20al?= =?UTF-8?q?lows=20char=20to=20be=20either=20signed=20or=20unsigned.=20=20I?= =?UTF-8?q?f=20it=E2=80=99s=20unsigned=20(as=20it=20is=20on=20ARM),=20then?= =?UTF-8?q?=20the=20test=20will=20fail=20with=20the=20wrong=20encoding.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Test/PropertyIntrospectionTest2_arc.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Test/PropertyIntrospectionTest2_arc.m b/Test/PropertyIntrospectionTest2_arc.m index a49e1c1..3ec266f 100644 --- a/Test/PropertyIntrospectionTest2_arc.m +++ b/Test/PropertyIntrospectionTest2_arc.m @@ -7,7 +7,7 @@ #pragma GCC diagnostic ignored "-Wobjc-property-no-attribute" enum FooManChu { FOO, MAN, CHU }; -struct YorkshireTeaStruct { int pot; char lady; }; +struct YorkshireTeaStruct { int pot; signed char lady; }; typedef struct YorkshireTeaStruct YorkshireTeaStructType; union MoneyUnion { float alone; double down; }; @@ -22,7 +22,7 @@ __attribute__((objc_root_class)) { @public Class isa; - char charDefault; + signed char charDefault; double doubleDefault; enum FooManChu enumDefault; float floatDefault; @@ -54,7 +54,7 @@ __attribute__((objc_root_class)) __weak id idReadonlyWeakNonatomic; id _idOther; } -@property char charDefault; +@property signed char charDefault; @property double doubleDefault; @property enum FooManChu enumDefault; @property float floatDefault; @@ -133,7 +133,7 @@ __attribute__((objc_root_class)) @end @protocol ProtocolTest -@property char charDefault; +@property signed char charDefault; @property double doubleDefault; @property enum FooManChu enumDefault; @property float floatDefault; @@ -173,7 +173,7 @@ __attribute__((objc_root_class)) @interface PropertyProtocolTest { Class isa; - char charDefault; + signed char charDefault; double doubleDefault; enum FooManChu enumDefault; float floatDefault; From 5cf6d6ac131cb6a5af96f7019d9b60cf3829daba Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 25 Dec 2015 21:17:59 +0000 Subject: [PATCH 39/42] Make the message send test actually test thing things that it's supposed to. --- Test/objc_msgSend.m | 97 ++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/Test/objc_msgSend.m b/Test/objc_msgSend.m index 7cb7eca..1d4f372 100644 --- a/Test/objc_msgSend.m +++ b/Test/objc_msgSend.m @@ -97,58 +97,71 @@ __attribute__((objc_root_class)) @end int forwardcalls; -void fwd(int a0, - int a1, - int a2, - int a3, - int a4, - int a5, - int a6, - int a7, - int a8, - int a9, - int a10, - float f0, - float f1, - float f2, - float f3, - float f4, - float f5, - float f6, - float f7, - float f8, - float f9, - float f10) +void fwdMany(id self, + SEL _cmd, + int a0, + int a1, + int a2, + int a3, + int a4, + int a5, + int a6, + int a7, + int a8, + int a9, + int a10, + float f0, + float f1, + float f2, + float f3, + float f4, + float f5, + float f6, + float f7, + float f8, + float f9, + float f10) { forwardcalls++; + assert(self == objc_getClass("Test")); + if (sel_isEqual(_cmd, sel_registerName("manyArgs:::::::::::::::::::::"))) assert(a0 == 0); - assert(a1 == 0); - assert(a2 == 0); - assert(a3 == 0); - assert(a4 == 0); - assert(a5 == 0); - assert(a6 == 0); - assert(a7 == 0); - assert(a8 == 0); - assert(a9 == 0); + assert(a1 == 1); + assert(a2 == 2); + assert(a3 == 3); + assert(a4 == 4); + assert(a5 == 5); + assert(a6 == 6); + assert(a7 == 7); + assert(a8 == 8); + assert(a9 == 9); assert(a10 == 10); assert(f0 == 0); - assert(f1 == 0); - assert(f2 == 0); - assert(f3 == 0); - assert(f4 == 0); - assert(f5 == 0); - assert(f6 == 0); - assert(f7 == 0); - assert(f8 == 0); - assert(f9 == 0); + assert(f1 == 1); + assert(f2 == 2); + assert(f3 == 3); + assert(f4 == 4); + assert(f5 == 5); + assert(f6 == 6); + assert(f7 == 7); + assert(f8 == 8); + assert(f9 == 9); assert(f10 == 10); } +void fwd(void) +{ + forwardcalls++; +} + IMP forward(id o, SEL s) { assert(o == objc_getClass("Test")); - return (IMP)fwd; + if (sel_isEqual(s, sel_registerName("missing"))) + { + return (IMP)fwd; + } + return (IMP)fwdMany; } int main(void) @@ -167,6 +180,7 @@ int main(void) assert((id)0x42 == objc_msgSend(TestCls, @selector(foo))); objc_msgSend(TestCls, @selector(nothing)); objc_msgSend(TestCls, @selector(missing)); + assert(forwardcalls == 1); assert(0 == objc_msgSend(0, @selector(nothing))); id a = objc_msgSend(objc_getClass("Test"), @selector(foo)); assert((id)0x42 == a); @@ -197,6 +211,7 @@ int main(void) assert(0 == [f ldzero]); assert(0 == [f fzero]); [TestCls manyArgs: 0 : 1 : 2 : 3: 4: 5: 6: 7: 8: 9: 10 : 0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10]; + assert(forwardcalls == 2); #ifdef BENCHMARK const int iterations = 1000000000; double times[3]; From 9c7cc6ac25b7f8a18a62dd02b4967667b9e45907 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 22:08:38 +0100 Subject: [PATCH 40/42] Save the floating point registers in the correct place on ARM. Note to self: It is very important, when you have tested a set of changes, to commit those changes and not a misapplied diff of approximately those changes. --- objc_msgSend.arm.S | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/objc_msgSend.arm.S b/objc_msgSend.arm.S index 7f924c6..71155a0 100644 --- a/objc_msgSend.arm.S +++ b/objc_msgSend.arm.S @@ -89,15 +89,13 @@ 5: // Slow lookup push {r0-r4, lr} // Save anything that will be clobbered by the call .save {r0-r4, lr} - - - push {\receiver} // &self, _cmd in arguments - .save {\receiver} #ifndef __SOFTFP__ vpush {q0-q3} .pad #64 #endif + push {\receiver} // &self, _cmd in arguments + .save {\receiver} mov r0, sp mov r1, \sel From 5c6cd3cb7065c7ff5f702ac654620d6adacd1d06 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 22:12:56 +0100 Subject: [PATCH 41/42] Set up the new forwarding hook in the objc_msgSend test. This is probably wrong, as we should be calling forward2 if forward3 is not set, but it makes the test pass for now. --- Test/objc_msgSend.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Test/objc_msgSend.m b/Test/objc_msgSend.m index 1d4f372..4dcb95e 100644 --- a/Test/objc_msgSend.m +++ b/Test/objc_msgSend.m @@ -164,9 +164,18 @@ IMP forward(id o, SEL s) return (IMP)fwdMany; } +static struct objc_slot slot; +struct objc_slot *forwardslot(id o, SEL s) +{ + slot.method = (IMP)fwd; + return &slot; +} + + int main(void) { __objc_msg_forward2 = forward; + __objc_msg_forward3 = forward_slot; TestCls = objc_getClass("Test"); int exceptionThrown = 0; @try { From 4a59b2a986c2c1d7467af12ded3678176d53581a Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sun, 27 Dec 2015 22:12:56 +0100 Subject: [PATCH 42/42] Set up the new forwarding hook in the objc_msgSend test. This is probably wrong, as we should be calling forward2 if forward3 is not set, but it makes the test pass for now. --- Test/objc_msgSend.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Test/objc_msgSend.m b/Test/objc_msgSend.m index 1d4f372..4dcb95e 100644 --- a/Test/objc_msgSend.m +++ b/Test/objc_msgSend.m @@ -164,9 +164,18 @@ IMP forward(id o, SEL s) return (IMP)fwdMany; } +static struct objc_slot slot; +struct objc_slot *forwardslot(id o, SEL s) +{ + slot.method = (IMP)fwd; + return &slot; +} + + int main(void) { __objc_msg_forward2 = forward; + __objc_msg_forward3 = forward_slot; TestCls = objc_getClass("Test"); int exceptionThrown = 0; @try {