diff --git a/ANNOUNCE b/ANNOUNCE index 9d1029b..7bf7b9c 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,23 @@ 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. +- 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. -You may obtain the code for this release from git and use the 1.8 branch: +- 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: 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 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 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 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/Test/BlockImpTest.m b/Test/BlockImpTest.m index 713ebcc..ce9931a 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; @@ -32,12 +33,16 @@ 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)); - 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/Test/CMakeLists.txt b/Test/CMakeLists.txt index 749f671..6425136 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -19,11 +19,13 @@ set(TESTS NestedExceptions.m PropertyAttributeTest.m PropertyIntrospectionTest.m - PropertyIntrospectionTest2.m + PropertyIntrospectionTest2_arc.m ProtocolCreation.m ResurrectInDealloc_arc.m RuntimeTest.m + WeakBlock_arc.m WeakReferences_arc.m + ivar_arc.m objc_msgSend.m msgInterpose.m NilException.m @@ -32,26 +34,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}) diff --git a/Test/PropertyIntrospectionTest2.m b/Test/PropertyIntrospectionTest2_arc.m similarity index 97% rename from Test/PropertyIntrospectionTest2.m rename to Test/PropertyIntrospectionTest2_arc.m index b77721d..3ec266f 100644 --- a/Test/PropertyIntrospectionTest2.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; @@ -43,18 +43,18 @@ __attribute__((objc_root_class)) int intReadonlyGetter; int intReadwrite; int intAssign; - id idDefault; + __unsafe_unretained 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; +@property signed char charDefault; @property double doubleDefault; @property enum FooManChu enumDefault; @property float floatDefault; @@ -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,10 +129,11 @@ __attribute__((objc_root_class)) @synthesize idOther = _idOther; @dynamic idDynamic; @dynamic idDynamicGetterSetter; +- (void)_ARCCompliantRetainRelease {} @end @protocol ProtocolTest -@property char charDefault; +@property signed char charDefault; @property double doubleDefault; @property enum FooManChu enumDefault; @property float floatDefault; @@ -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; @@ -172,7 +173,7 @@ __attribute__((objc_root_class)) @interface PropertyProtocolTest { Class isa; - char charDefault; + signed char charDefault; double doubleDefault; enum FooManChu enumDefault; float floatDefault; @@ -193,15 +194,15 @@ __attribute__((objc_root_class)) int intReadonlyGetter; int intReadwrite; int intAssign; - id idDefault; + __unsafe_unretained 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 @@ -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)} @@ -473,17 +475,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); +static struct YorkshireTeaStruct structDefault2Getter(id self, SEL _cmd) { + struct YorkshireTeaStruct *s; + object_getInstanceVariable(self, "structDefault", (void**)&s); + return *s; } -void structDefault2Setter(id self, SEL _cmd, struct YorkshireTeaStruct* value) { - Ivar ivar = class_getInstanceVariable(objc_getClass("PropertyTest"), "structDefault"); - object_setIvar(self, ivar, (id)value); +void structDefault2Setter(id self, SEL _cmd, struct YorkshireTeaStruct value) { + object_setInstanceVariable(self, "structDefault", &value); } int main(void) @@ -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); 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/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/Test/objc_msgSend.m b/Test/objc_msgSend.m index 6d6803c..4dcb95e 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,87 @@ __attribute__((objc_root_class)) } + nothing { return 0; } @end + +int forwardcalls; +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 == 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 == 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")); + if (sel_isEqual(s, sel_registerName("missing"))) + { + return (IMP)fwd; + } + 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 { @@ -85,6 +189,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); @@ -114,33 +219,38 @@ 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]; + assert(forwardcalls == 2); #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 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) { + 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 block_header)); + 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; } - - 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; + block = Block_copy(block); + // Allocate some trampolines if this is the first time that we need to do this. + if (*setptr == NULL) + { + *setptr = alloc_trampolines(start, end); + } + for (struct trampoline_set *set=*setptr ; set!=NULL ; set=set->next) + { + if (set->first_free != -1) + { + int i = set->first_free; + struct block_header *h = &set->buffers->headers[i]; + struct block_header *next = h->block; + set->first_free = next ? (next - set->buffers->headers) : -1; + assert(set->first_free < HEADERS_PER_PAGE); + assert(set->first_free >= -1); + h->fnptr = (void(*)(void))b->invoke; + h->block = b; + 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 e3ed411..add0e7f 100644 --- a/block_trampolines.S +++ b/block_trampolines.S @@ -32,75 +32,86 @@ 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 - jmp *-32(%rip) # Call the block function + mov -0x1007(%rip), ARG1 # Load the block pointer into the second argument + xchg ARG1, ARG0 # Swap the first and second arguments + jmp *-0x1008(%rip) # Call the block function 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 - jmp *-32(%rip) # Call the block function + mov -0x1007(%rip), ARG2 # Load the block pointer into the second argument + xchg ARG1, ARG2 # Swap the first and second arguments + 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, #-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 r1, [pc, #-16] // Load the block pointer over self - ldr pc, [pc, #-24] // 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 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/dtable.c b/dtable.c index 486916a..b537e9c 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) @@ -81,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, @@ -644,23 +698,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/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/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 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); } 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) diff --git a/ivar.c b/ivar.c index bf4f54f..8504dae 100644 --- a/ivar.c +++ b/ivar.c @@ -1,6 +1,8 @@ #include +#include #include #include "objc/runtime.h" +#include "objc/objc-arc.h" #include "class.h" #include "ivar.h" #include "visibility.h" @@ -142,27 +144,105 @@ 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); - *(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) { 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; } 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) @@ -170,7 +250,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; } 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.arm.S b/objc_msgSend.arm.S index a7c48ac..71155a0 100644 --- a/objc_msgSend.arm.S +++ b/objc_msgSend.arm.S @@ -1,10 +1,28 @@ -#define DTABLE_OFFSET 32 -#define SMALLOBJ_MASK 1 -#define SHIFT_OFFSET 4 -#define DATA_OFFSET 12 -#define SLOT_OFFSET 16 .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 @@ -37,23 +55,23 @@ 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 teq r6, #0 beq 2f - and r6, r5, #0xff0000 - ldr r4, [r4, r6, asr#14] - ldr r4, [r4, #DATA_OFFSET] + 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 - and r6, r5, #0xff00 - ldr r4, [r4, r6, asr#6] - ldr r4, [r4, #DATA_OFFSET] + 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 - and r6, r5, #0xff - ldr ip, [r4, r6, asl#2] + 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 teq ip, #0 // If the slot is nil beq 5f // Go to the slow path and do the forwarding stuff @@ -71,10 +89,14 @@ 5: // Slow lookup push {r0-r4, lr} // Save anything that will be clobbered by the call .save {r0-r4, lr} - +#ifndef __SOFTFP__ + vpush {q0-q3} + .pad #64 +#endif push {\receiver} // &self, _cmd in arguments .save {\receiver} + mov r0, sp mov r1, \sel @@ -82,6 +104,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 diff --git a/objc_msgSend.mips.S b/objc_msgSend.mips.S index 4fb8607..7c4fe35 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 @@ -51,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 - LP $t7, DATA_OFFSET($t4) # Load 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) - LP $t7, DATA_OFFSET($t7) + 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) - LP $t7, DATA_OFFSET($t7) + 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: @@ -163,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 @@ -192,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 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 4be831b..ebe5e5e 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,28 @@ 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 %rax, -8(%rsp) # %rax contains information for variadic calls + mov %rbx, -16(%rsp) # On the fast path, spill into the red zone + mov (\sel), %eax # Load the selector index into %eax + mov SHIFT_OFFSET(%r10), %r11d # Load the shift (dtable size) into r11 + cmpl $8, %r11d # If this is a small dtable, jump to the small dtable handlers je 2f - cmpl $0, %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, %ebx + movq DATA_OFFSET(%r10, %rbx, 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, %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 @@ -259,10 +241,8 @@ 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 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; } 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..b287438 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) /** @@ -64,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; 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)