Add initial support for imp_implementationWithBlock() and friends. Currently
only works on x86 and x86-64 and doesn't work with sret functions.main
parent
db1c9052de
commit
43ee8b9819
@ -0,0 +1,26 @@
|
|||||||
|
#include <objc/runtime.h>
|
||||||
|
#include <objc/blocks_runtime.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
@ -0,0 +1,125 @@
|
|||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -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.
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
Loading…
Reference in New Issue