From 43ee8b9819211ad6cdc293430004a343254ef1fc Mon Sep 17 00:00:00 2001 From: theraven Date: Sun, 9 Oct 2011 19:51:54 +0000 Subject: [PATCH] Add initial support for imp_implementationWithBlock() and friends. Currently only works on x86 and x86-64 and doesn't work with sret functions. --- Makefile | 18 +++-- Test/BlockImpTest.m | 26 +++++++ block_to_imp.c | 125 ++++++++++++++++++++++++++++++ block_trampolines.S | 49 ++++++++++++ blocks_runtime.h | 181 +++++++++++++++++++++++++++++++++++++++++++ blocks_runtime.m | 183 +------------------------------------------- loader.c | 2 + objc/runtime.h | 19 +++++ 8 files changed, 416 insertions(+), 187 deletions(-) create mode 100644 Test/BlockImpTest.m create mode 100644 block_to_imp.c create mode 100644 block_trampolines.S create mode 100644 blocks_runtime.h diff --git a/Makefile b/Makefile index ab2afb2..b1313f3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .POSIX: -.SUFFIXES: .cc .c .m .o +.SUFFIXES: .cc .c .m .o .S MAJOR_VERSION = 4 MINOR_VERSION = 6 @@ -10,12 +10,12 @@ VERSION = $(MAJOR_VERSION).$(MINOR_VERSION).$(SUBMINOR_VERSION) CFLAGS += -std=gnu99 -fPIC -fexceptions CXXFLAGS += -fPIC -fexceptions CPPFLAGS += -DTYPE_DEPENDENT_DISPATCH -DGNUSTEP -CPPFLAGS += -D__OBJC_RUNTIME_INTERNAL__=1 -D_XOPEN_SOURCE=500 +CPPFLAGS += -D__OBJC_RUNTIME_INTERNAL__=1 -D_XOPEN_SOURCE=500 -D__BSD_VISIBLE=1 # Suppress warnings about incorrect selectors CPPFLAGS += -DNO_SELECTOR_MISMATCH_WARNINGS # Some helpful flags for debugging. -#CPPFLAGS += -g -O0 -fno-inline +CPPFLAGS += -g -O0 -fno-inline PREFIX?= /usr/local LIB_DIR= ${PREFIX}/lib @@ -32,6 +32,8 @@ OBJECTS = \ arc.o\ associate.o\ blocks_runtime.o\ + block_to_imp.o\ + block_trampolines.o\ caps.o\ category_loader.o\ class_table.o\ @@ -68,18 +70,22 @@ libobjc.a: $(OBJECTS) @echo Linking static Objective-C runtime library... @ld -r -s -o $@ $(OBJECTS) -.cc.o: +.cc.o: Makefile @echo Compiling `basename $<`... @$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ -.c.o: +.c.o: Makefile @echo Compiling `basename $<`... @$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ -.m.o: +.m.o: Makefile @echo Compiling `basename $<`... @$(CC) $(CPPFLAGS) $(CFLAGS) -fobjc-exceptions -c $< -o $@ +.S.o: Makefile + @echo Assembling `basename $<`... + @$(CC) $(CPPFLAGS) -c $< -o $@ + install: all @echo Installing libraries... @install -d $(LIB_DIR) diff --git a/Test/BlockImpTest.m b/Test/BlockImpTest.m new file mode 100644 index 0000000..25b5665 --- /dev/null +++ b/Test/BlockImpTest.m @@ -0,0 +1,26 @@ +#include +#include +#include + +@interface Foo @end +@implementation Foo @end +@interface Foo (Dynamic) ++(int)count: (int)i; +@end + +int main(void) +{ + __block int b = 0; + void* blk = ^(id self, int a) { + b += a; + return b; }; + blk = Block_copy(blk); + IMP imp = imp_implementationWithBlock(blk); + class_addMethod((objc_getMetaClass("Foo")), @selector(count:), imp, "i@:i"); + assert(2 == [Foo count: 2]); + assert(4 == [Foo count: 2]); + assert(6 == [Foo count: 2]); + assert(imp_getBlock(imp) == (blk)); + imp_removeBlock(blk); + return 0; +} diff --git a/block_to_imp.c b/block_to_imp.c new file mode 100644 index 0000000..19803c9 --- /dev/null +++ b/block_to_imp.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include "objc/runtime.h" +#include "objc/blocks_runtime.h" +#include "blocks_runtime.h" +#include "lock.h" +#include "visibility.h" + +#define PAGE_SIZE 4096 + +static void *executeBuffer; +static void *writeBuffer; +static ptrdiff_t offset; +static mutex_t trampoline_lock; +static char *tmpPattern; + +struct wx_buffer +{ + void *w; + void *x; +}; + +PRIVATE void init_trampolines(void) +{ + INIT_LOCK(trampoline_lock); + char *tmp = getenv("TMPDIR"); + if (NULL == tmp) + { + tmp = "/tmp/"; + } + if (0 > asprintf(&tmpPattern, "%s/objc_trampolinesXXXXXXXXXXX", tmp)) + { + abort(); + } +} + +static struct wx_buffer alloc_buffer(size_t size) +{ + LOCK_FOR_SCOPE(&trampoline_lock); + if ((0 == offset) || (offset + size >= PAGE_SIZE)) + { + int fd = mkstemp(tmpPattern); + unlink(tmpPattern); + ftruncate(fd, PAGE_SIZE); + void *w = mmap(NULL, PAGE_SIZE, PROT_WRITE, MAP_SHARED, fd, 0); + executeBuffer = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0); + *((void**)w) = writeBuffer; + writeBuffer = w; + offset = sizeof(void*); + } + struct wx_buffer b = { writeBuffer + offset, executeBuffer + offset }; + offset += size; + return b; +} + +extern void __objc_block_trampoline; +extern void __objc_block_trampoline_end; +extern void __objc_block_trampoline_sret; +extern void __objc_block_trampoline_end_sret; + +IMP imp_implementationWithBlock(void *block) +{ + struct block_literal *b = block; + void *start; + void *end; + + if ((b->flags & BLOCK_USE_SRET) == BLOCK_USE_SRET) + { + start = &__objc_block_trampoline_sret; + end = &__objc_block_trampoline_end_sret; + } + else + { + 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; + return (IMP)&out[2]; +} + +static void* isBlockIMP(void *anIMP) +{ + LOCK(&trampoline_lock); + void *e = executeBuffer; + void *w = writeBuffer; + UNLOCK(&trampoline_lock); + while (e) + { + if ((anIMP > e) && (anIMP < e + PAGE_SIZE)) + { + return ((char*)w) + ((char*)anIMP - (char*)e); + } + e = *(void**)e; + w = *(void**)w; + } + return 0; +} + +void *imp_getBlock(IMP anImp) +{ + if (0 == isBlockIMP((void*)anImp)) { return 0; } + return *(((void**)anImp) - 1); +} +BOOL imp_removeBlock(IMP anImp) +{ + void *w = isBlockIMP((void*)anImp); + if (0 == w) { return NO; } + Block_release(((void**)anImp) - 1); + return YES; +} diff --git a/block_trampolines.S b/block_trampolines.S new file mode 100644 index 0000000..1f7914c --- /dev/null +++ b/block_trampolines.S @@ -0,0 +1,49 @@ +# +# This file defines some trampolines for calling blocks. A block function +# looks like this: +# +# retType blockFn(block*, ...) +# +# An IMP looks like this: +# +# retType imp(id, SEL,...) +# +# The trampoline must find the block pointer and then call the block function +# with the correct first argument, the self pointer moved to the second real +# argument (the first block argument) and the _cmd parameter excised + +.file "block_trampolines.S" +.globl __objc_block_trampoline_sret + .type __objc_block_trampoline_sret, @function +.globl __objc_block_trampoline_end_sret +.globl __objc_block_trampoline + .type __objc_block_trampoline, @function +.globl __objc_block_trampoline_end +#if __x86_64 +__objc_block_trampoline: +next: + mov -14(%rip), %esi # Load the block pointer into the second argument + xchg %edi, %esi # Swap the first and second arguments + jmp *-30(%rip) # Call the block function +__objc_block_trampoline_end: +__objc_block_trampoline_sret: +__objc_block_trampoline_end_sret: +#elif __i386 +__objc_block_trampoline: + call next_line # Store the instruction pointer on the stack +next_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 %ebx, 4(%esp) # Store the block pointer in the first argument + jmp *-13(%eax) # Call the block function +__objc_block_trampoline_end: +__objc_block_trampoline_sret: +__objc_block_trampoline_end_sret: +#else +__objc_block_trampoline: +__objc_block_trampoline_end: +__objc_block_trampoline_sret: +__objc_block_trampoline_end_sret: +#endif diff --git a/blocks_runtime.h b/blocks_runtime.h new file mode 100644 index 0000000..1838c48 --- /dev/null +++ b/blocks_runtime.h @@ -0,0 +1,181 @@ +/** + * Block descriptor flags. + */ +enum block_flags +{ + /** + * The block descriptor contains copy and dispose helpers. + */ + BLOCK_HAS_COPY_DISPOSE = (1 << 25), + /** + * The helpers have C++ code. + */ + BLOCK_HAS_CTOR = (1 << 26), + /** + * Block is stored in global memory and does not need to be copied. + */ + BLOCK_IS_GLOBAL = (1 << 28), + /** + * Block function uses a calling convention that returns a structure via a + * pointer passed in by the caller. + */ + BLOCK_USE_SRET = (1 << 29), + /** + * Block has an Objective-C type encoding. + */ + BLOCK_HAS_SIGNATURE = (1 << 30), + /** + * Mask for the reference count in byref structure's flags field. The low + * 3 bytes are reserved for the reference count, the top byte for the + * flags. + */ + BLOCK_REFCOUNT_MASK = 0x00ffffff +}; + +/** + * Flags used in the final argument to _Block_object_assign() and + * _Block_object_dispose(). These indicate the type of copy or dispose to + * perform. + */ +enum +{ + /** + * The value is of some id-like type, and should be copied as an + * Objective-C object: i.e. by sending -retain or via the GC assign + * functions in GC mode (not yet supported). + */ + BLOCK_FIELD_IS_OBJECT = 3, + /** + * The field is a block. This must be copied by the block copy functions. + */ + BLOCK_FIELD_IS_BLOCK = 7, + /** + * The field is an indirect reference to a variable declared with the + * __block storage qualifier. + */ + BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable + + BLOCK_FIELD_IS_WEAK = 16, // declared __weak + + BLOCK_BYREF_CALLER = 128, // called from byref copy/dispose helpers +}; +#define IS_SET(x, y) ((x & y) == y) + +/** + * Block descriptor that contains copy and dispose operations. + */ +struct block_descriptor_copydispose +{ + /** + * Reserved for future use. Currently always 0. + */ + unsigned long int reserved; + /** Size of the block. */ + unsigned long int size; + /** + * Copy function, generated by the compiler to help copy the block if it + * contains nontrivial copy operations. + */ + void (*copy_helper)(void *dst, void *src); + /** + * Dispose function, generated by the compiler to help copy the block if it + * contains nontrivial destructors. + */ + void (*dispose_helper)(void *src); + /** + * Objective-C type encoding of the block. + */ + const char *encoding; +}; +/** + * Block descriptor that does not contain copy and dispose helper functions. + */ +struct block_descriptor +{ + /** + * Reserved for future use, currently always 0. + */ + unsigned long int reserved; + /** Size of the block. */ + unsigned long int size; + /** + * Objective-C type encoding of the block. + */ + const char *encoding; +}; + +// Helper structure +struct block_literal +{ + /** + * Class pointer. Always initialised to &_NSConcreteStackBlock for blocks + * that are created on the stack or &_NSConcreteGlobalBlock for blocks that + * are created in global storage. + */ + void *isa; + /** + * Flags. See the block_flags enumerated type for possible values. + */ + int flags; + /** + * Reserved - always initialised to 0 by the compiler. Used for the + * reference count in this implementation. + */ + int reserved; + /** + * The function that implements the block. The first argument is this + * structure, the subsequent arguments are the block's explicit parameters. + * If the BLOCK_USE_SRET flag is set, there is an additional hidden + * argument, which is a pointer to the space on the stack allocated to hold + * the return value. + */ + void (*invoke)(void *, ...); + /** + * The block's descriptor. This is either block_descriptor or + * block_descriptor_copydispose, depending on whether the + * BLOCK_HAS_COPY_DISPOSE flag is set. + */ + struct block_descriptor_copydispose *descriptor; + /** + * Block variables are appended to this structure. + */ +}; + +/** + * Structure used for on-stack variables that are referenced by blocks. + */ +struct block_byref_obj +{ + /** + * Class pointer. Currently unused and always NULL. Could be used in the + * future to support introspection. + */ + void *isa; + /** + * The pointer to the structure that contains the real version of the data. + * All accesses go via this pointer. If an on-stack byref structure is + * copied to the heap, then its forwarding pointer should point to the heap + * version. Otherwise it should point to itself. + */ + struct block_byref_obj *forwarding; + /** + * Flags and reference count. + */ + int flags; //refcount; + /** + * Size of this structure. + */ + int size; + /** + * Copy function. + */ + void (*byref_keep)(struct block_byref_obj *dst, const struct block_byref_obj *src); + /** + * Dispose function. + */ + void (*byref_dispose)(struct block_byref_obj *); + /** + * __block-qualified variables are copied here. + */ +}; + diff --git a/blocks_runtime.m b/blocks_runtime.m index 5de4f2a..45cd80b 100644 --- a/blocks_runtime.m +++ b/blocks_runtime.m @@ -26,6 +26,7 @@ #import "objc/blocks_runtime.h" #import "objc/runtime.h" #import "objc/objc-arc.h" +#include "blocks_runtime.h" #include "gc_ops.h" #include "visibility.h" #include @@ -34,188 +35,8 @@ #include #include -static void *_HeapBlockByRef = (void*)1; - -/** - * Block descriptor flags. - */ -enum block_flags -{ - /** - * The block descriptor contains copy and dispose helpers. - */ - BLOCK_HAS_COPY_DISPOSE = (1 << 25), - /** - * The helpers have C++ code. - */ - BLOCK_HAS_CTOR = (1 << 26), - /** - * Block is stored in global memory and does not need to be copied. - */ - BLOCK_IS_GLOBAL = (1 << 28), - /** - * Block function uses a calling convention that returns a structure via a - * pointer passed in by the caller. - */ - BLOCK_USE_SRET = (1 << 29), - /** - * Block has an Objective-C type encoding. - */ - BLOCK_HAS_SIGNATURE = (1 << 30), - /** - * Mask for the reference count in byref structure's flags field. The low - * 3 bytes are reserved for the reference count, the top byte for the - * flags. - */ - BLOCK_REFCOUNT_MASK = 0x00ffffff -}; - -/** - * Flags used in the final argument to _Block_object_assign() and - * _Block_object_dispose(). These indicate the type of copy or dispose to - * perform. - */ -enum -{ - /** - * The value is of some id-like type, and should be copied as an - * Objective-C object: i.e. by sending -retain or via the GC assign - * functions in GC mode (not yet supported). - */ - BLOCK_FIELD_IS_OBJECT = 3, - /** - * The field is a block. This must be copied by the block copy functions. - */ - BLOCK_FIELD_IS_BLOCK = 7, - /** - * The field is an indirect reference to a variable declared with the - * __block storage qualifier. - */ - BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable - - BLOCK_FIELD_IS_WEAK = 16, // declared __weak - - BLOCK_BYREF_CALLER = 128, // called from byref copy/dispose helpers -}; -#define IS_SET(x, y) ((x & y) == y) - -/** - * Block descriptor that contains copy and dispose operations. - */ -struct block_descriptor_copydispose -{ - /** - * Reserved for future use. Currently always 0. - */ - unsigned long int reserved; - /** Size of the block. */ - unsigned long int size; - /** - * Copy function, generated by the compiler to help copy the block if it - * contains nontrivial copy operations. - */ - void (*copy_helper)(void *dst, void *src); - /** - * Dispose function, generated by the compiler to help copy the block if it - * contains nontrivial destructors. - */ - void (*dispose_helper)(void *src); - /** - * Objective-C type encoding of the block. - */ - const char *encoding; -}; -/** - * Block descriptor that does not contain copy and dispose helper functions. - */ -struct block_descriptor -{ - /** - * Reserved for future use, currently always 0. - */ - unsigned long int reserved; - /** Size of the block. */ - unsigned long int size; - /** - * Objective-C type encoding of the block. - */ - const char *encoding; -}; -// Helper structure -struct block_literal -{ - /** - * Class pointer. Always initialised to &_NSConcreteStackBlock for blocks - * that are created on the stack or &_NSConcreteGlobalBlock for blocks that - * are created in global storage. - */ - void *isa; - /** - * Flags. See the block_flags enumerated type for possible values. - */ - int flags; - /** - * Reserved - always initialised to 0 by the compiler. Used for the - * reference count in this implementation. - */ - int reserved; - /** - * The function that implements the block. The first argument is this - * structure, the subsequent arguments are the block's explicit parameters. - * If the BLOCK_USE_SRET flag is set, there is an additional hidden - * argument, which is a pointer to the space on the stack allocated to hold - * the return value. - */ - void (*invoke)(void *, ...); - /** - * The block's descriptor. This is either block_descriptor or - * block_descriptor_copydispose, depending on whether the - * BLOCK_HAS_COPY_DISPOSE flag is set. - */ - struct block_descriptor_copydispose *descriptor; - /** - * Block variables are appended to this structure. - */ -}; - -/** - * Structure used for on-stack variables that are referenced by blocks. - */ -struct block_byref_obj -{ - /** - * Class pointer. Currently unused and always NULL. Could be used in the - * future to support introspection. - */ - void *isa; - /** - * The pointer to the structure that contains the real version of the data. - * All accesses go via this pointer. If an on-stack byref structure is - * copied to the heap, then its forwarding pointer should point to the heap - * version. Otherwise it should point to itself. - */ - struct block_byref_obj *forwarding; - /** - * Flags and reference count. - */ - int flags; //refcount; - /** - * Size of this structure. - */ - int size; - /** - * Copy function. - */ - void (*byref_keep)(struct block_byref_obj *dst, const struct block_byref_obj *src); - /** - * Dispose function. - */ - void (*byref_dispose)(struct block_byref_obj *); - /** - * __block-qualified variables are copied here. - */ -}; +static void *_HeapBlockByRef = (void*)1; /** diff --git a/loader.c b/loader.c index 34545b6..67a8cf5 100644 --- a/loader.c +++ b/loader.c @@ -22,6 +22,7 @@ void init_dispatch_tables(void); void init_gc(void); void init_protocol_table(void); void init_selector_tables(void); +void init_trampolines(void); void objc_send_load_message(Class class); /* Number of threads that are alive. */ @@ -59,6 +60,7 @@ void __objc_exec_class(struct objc_module_abi_8 *module) init_dispatch_tables(); init_alias_table(); init_arc(); + init_trampolines(); first_run = NO; } diff --git a/objc/runtime.h b/objc/runtime.h index 34529cc..df1172c 100644 --- a/objc/runtime.h +++ b/objc/runtime.h @@ -826,6 +826,25 @@ void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPo */ void objc_removeAssociatedObjects(id object); +/** + * Converts a block into an IMP that can be used as a method. The block should + * take an object pointer (self) as its first argument, and then the same + * arguments as the method. + */ +IMP imp_implementationWithBlock(void *block); +/** + * Returns the block that was used in an IMP created by + * imp_implementationWithBlock(). The result of calling this function with any + * other IMP is undefined. + */ +void *imp_getBlock(IMP anImp); +/** + * Removes a block that was converted to an IMP with + * imp_implementationWithBlock(). The result of calling this function with any + * other IMP is undefined. Returns YES on success, NO on failure. + */ +BOOL imp_removeBlock(IMP anImp); + /** * Adds a method to a specific object, This method will not be added to any * other instances of the same class.