I'm working with some trampoline functions for use with higher-level calling in C/Objective-C, a slight twist on the way Apple does it.
If you're familiar at all with the way Objective-C IMP
works, it's basically a function pointer where the first two arguments are the receiver of a message and the name of the message selector, such as void(*)(id obj, SEL sel, ...)
. More recent versions of the runtime allow method implementations to be synthesized at run time using C blocks, like void(^)(id obj, ...)
. These blocks don't have the selector; the runtime creates a trampoline that overwrites the selector with the receiver, the receiver with the block pointer, and then moves on executing it.
I want to do something vaguely similar which involves not having either of the first two arguments, so that the arguments to this block are the exact same as the arguments of the traditional method send, plus the block pointer for execution purposes, i.e., void(*)(Block *, ...)
. This requires only copying in the block pointer, and I suppose getting rid of an argument.
__a1a2_tramphead_argonly:
popl %eax
andl $0xFFFFFFF8, %eax
subl $0x1000, %eax
movl 4(%esp), %ecx // self -> ecx
movl %ecx, 8(%esp) // ecx -> _cmd
movl (%eax), %ecx // blockPtr -> ecx
movl %ecx, 4(%esp) // ecx -> self
jmp *12(%ecx) // tail to block->invoke
Here's the assembly I have on ARM:
__a1a2_tramphead_argonly:
// calculate the trampoline's index (512 entries, 8 bytes each)
#ifdef _ARM_ARCH_7
// PC bias is only 4, no need to correct with 8-byte trampolines
ubfx r1, r1, #3, #9
#else
sub r1, r1, #8 // correct PC bias
lsl r1, r1, #20
lsr r1, r1, #23
#endif
// load block pointer from trampoline's data
adr r12, __a1a2_tramphead_argonly // text page
sub r12, r12, #4096 // data page precedes text page
ldr r12, [r12, r1, LSL #3] // load block pointer from data + index*8
// shuffle parameters
mov r1, r0 // _cmd = self
mov r0, r12 // self = block pointer
// tail call block->invoke
ldr pc, [r12, #12]
Similar code exists for the x86_64; the code above is thus far directly from Apple. For personal knowledge, I'm wondering where to start with excising an argument, so that the first argument (what used to be the reciever) is the block literal, the second is the first real argument, and so on.
I'm incredibly noobish at ASM, so any help is greatly appreciated. Everything I've tried has blown up in increasingly interesting ways. Thanks in advance.
The iOS ABI effectively incorporates the AAPCS and only defines the differences, so you'll want to start with http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0042d/index.html first. Then read Apple's iOS ABI Function Call Guide (which I think you need a paid iOS Dev Center membership to access).
Summarizing the rules, to call an ObjC IMP:
So, if you're only looking at arguments with up to 2 params, none of them floating point/int64_t/struct, to remove the self and _cmd arguments is just a matter of shuffling R0-R4:
mov r0, r2
mov r1, r3
Or, to write a function that takes two params and crams the self and _cmd in before forwarding to an IMP, it's just this:
mov r3, r1
mov r2, r0
ldr r1, [address of _cmd]
ldr r0, [address of self]
In the case of Apple's block trampoline, what they're doing is transforming a call to [foo performBlockOnSelf:block] into, effectively, [block foo]. As you say, the block pointer ends up in r0 (the usual self position) and the target parameter foo ends up in r1 (the usual _cmd position). If blocks were really IMPs, of course, this would be nonsense, because foo isn't a SEL, but they're not, so it's not a problem.
From your statement "I want to do something vaguely similar which involves not having either of the first two arguments, so that the arguments to this block are the exact same as the arguments of the traditional method send," I'm not entirely clear which of two things you're trying to do:
Define a "delegate" object (in C# terms), basically a block with its target baked in at construction time. In this case, you're going to want to look up both r0 (the block pointer) and r1 (the target) from some table of delegates, instead of just the block pointer. But you're not going to have any compiler help setting up that table--which means you can set it up and access it in pure C and it'll be just as convenient and building a custom assembly trampoline. (You could even do it through ObjC dictionaries, with some loss of performance that may not matter in practice.)
Turn a regular message into a block, which involves getting everything stored so that when Apple's trampoline code tries to call the block it ends up with the traditional method send parameters instead of the block parameters. If this is your goal, it's simpler and much safer to just use a block wrapper around the message instead of trying to convert the messages into blocks, and I doubt there would be either efficiency or flexibility costs that matter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With