Implement block forwarding

This commit is contained in:
Ariel Abreu 2021-04-30 09:16:35 -04:00
parent 8e68fa7bf5
commit 1e120e065d
No known key found for this signature in database
GPG Key ID: BB20848279B910AC
8 changed files with 241 additions and 3 deletions

View File

@ -95,6 +95,8 @@ LStretContinue:
.fnend
#error TODO: Implement __CF_forwarding_prep_b for ARM
#elif __i386__
.text
@ -175,6 +177,28 @@ __CF_forwarding_prep_1:
LStretSuccess:
ret $4 // return value in stret pointer; return
.text
.global __CF_forwarding_prep_b
.align 4, 0x90
// NOTE: i have not checked this implemention (it's kind of hacked together based on the other two `forwarding_prep`s)
// i tried to test it, but blocks in general are broken on i386 at the moment (actually, the root issue is that setjmp is broken on i386)
__CF_forwarding_prep_b:
push %ebp
mov %esp, %ebp // create a stack frame
sub $0x04, %esp // reserve space for args pointer
and $-16, %esp // align stack
lea 8(%ebp), %eax // load marg_list, skipping frame pointer and return address
mov %eax, (%esp) // pass marg_list as first argument
call ___block_forwarding__ // call through
mov %ebp, %esp // restore stack
pop %ebp // pop stack frame
ret // return
#elif __x86_64__
.section __TEXT,__text,regular,pure_instructions
@ -259,6 +283,46 @@ Lfail:
.cfi_endproc
.globl __CF_forwarding_prep_b
.align 4, 0x90
__CF_forwarding_prep_b:
.cfi_startproc
.cfi_personality 155, ___objc_personality_v0 // not sure if this personality is correct for this function
push %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register rbp
// Copy args from regs into a stack var
subq $0xd0, %rsp
movq %rax, 0xb0(%rsp)
movapd %xmm7, 0xa0(%rsp)
movapd %xmm6, 0x90(%rsp)
movapd %xmm5, 0x80(%rsp)
movapd %xmm4, 0x70(%rsp)
movapd %xmm3, 0x60(%rsp)
movapd %xmm2, 0x50(%rsp)
movapd %xmm1, 0x40(%rsp)
movapd %xmm0, 0x30(%rsp)
movq %r9, 0x28(%rsp)
movq %r8, 0x20(%rsp)
movq %rcx, 0x18(%rsp)
movq %rdx, 0x10(%rsp)
movq %rsi, 8(%rsp)
movq %rdi, (%rsp)
// call into the actual forwarder
movq %rsp, %rdi
call ___block_forwarding__
movq %rbp, %rsp
pop %rbp
ret
.cfi_endproc
#else
#error Missing forwarding prep handlers for this arch https://code.google.com/p/apportable/issues/detail?id=619
#endif

View File

@ -161,6 +161,7 @@ set(cf_sources
FoundationExceptions.m
NSRunLoopModes.m
NSFileSecurity.m
NSBlockInvocation.m
)
add_separated_framework(CoreFoundation

71
NSBlockInvocation.m Normal file
View File

@ -0,0 +1,71 @@
#import <Foundation/NSInvocation.h>
#import "NSBlockInvocationInternal.h"
@implementation NSBlockInvocation
- (SEL)selector
{
[self doesNotRecognizeSelector: _cmd];
}
- (void)setSelector: (SEL)selector
{
[self doesNotRecognizeSelector: _cmd];
}
- (void)invokeSuper
{
[self doesNotRecognizeSelector: _cmd];
}
- (void)invokeUsingIMP: (IMP)implementation
{
[self doesNotRecognizeSelector: _cmd];
}
@end
struct Block_descriptor_full {
struct Block_descriptor_1 desc1;
struct Block_descriptor_2 desc2;
struct Block_descriptor_3 desc3;
};
struct NSProxyBlockFull {
struct NSProxyBlock block;
struct Block_descriptor_full descs;
char signature[];
};
id __NSMakeSpecialForwardingCaptureBlock(const char* signature, void (^proxyBlock)(NSBlockInvocation*)) {
// dummy block we can use to borrow certain details we need for our custom block
void (^dummyBlock)(void) = ^{
[proxyBlock self];
};
size_t signatureLength = strlen(signature);
struct Block_layout* dummy = (void*)dummyBlock;
struct Block_descriptor_full* dummyDescs = dummy->descriptor;
struct NSProxyBlockFull* custom = calloc(sizeof(struct NSProxyBlockFull) + signatureLength + 1, 1);
custom->block.blockInternal.isa = _NSConcreteMallocBlock;
custom->block.blockInternal.flags = BLOCK_NEEDS_FREE | BLOCK_HAS_SIGNATURE | BLOCK_HAS_COPY_DISPOSE | (1 << 1);
custom->block.blockInternal.invoke = _CF_forwarding_prep_b;
custom->block.blockInternal.descriptor = &custom->descs.desc1;
custom->block.proxy = proxyBlock;
custom->descs.desc1.size = sizeof(struct NSProxyBlock);
custom->descs.desc2.copy = dummyDescs->desc2.copy;
custom->descs.desc2.dispose = dummyDescs->desc2.dispose;
custom->descs.desc3.signature = custom->signature;
custom->descs.desc3.layout = dummyDescs->desc3.layout;
strlcpy(custom->signature, signature, signatureLength + 1);
custom->descs.desc2.copy(custom, dummy);
return custom;
};

View File

@ -0,0 +1,9 @@
#import <Foundation/NSBlockInvocation.h>
#import <Block_private.h>
extern void _CF_forwarding_prep_b();
struct NSProxyBlock {
struct Block_layout blockInternal;
void (^proxy)(NSBlockInvocation*);
};

View File

@ -32,6 +32,8 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#import <objc/runtime.h>
#import <stdio.h>
#import "NSBlockInvocationInternal.h"
#define ALIGN_TO(value, alignment) \
(((value) % (alignment)) ? \
((value) + (alignment) - ((value) % (alignment))) : \
@ -133,3 +135,26 @@ id ___forwarding___(struct objc_sendv_margs *args, void *returnStorage)
return nil;
}
void __block_forwarding__(void* frame) {
id block = *(id**)frame;
Class class = object_getClass(block);
const char *className = class_getName(class);
if (strncmp(className, ZOMBIE_PREFIX, strlen(ZOMBIE_PREFIX)) == 0) {
CFLog(3, CFSTR("*** NSBlockInvocation: invocation of deallocated Block instance %p"), block);
__builtin_trap();
} else {
const char* rawSig = _Block_signature(block);
if (rawSig) {
NSMethodSignature* sig = [NSMethodSignature signatureWithObjCTypes: rawSig];
NSBlockInvocation* invocation = [NSBlockInvocation _invocationWithMethodSignature: sig frame: frame];
invocation.target = nil; // prevent the proxy from accidentally invoking us again
(((struct NSProxyBlock*)block)->proxy)(invocation);
} else {
CFLog(4, CFSTR("*** NSBlockInvocation: Block %p does not have a type signature -- abort"), block);
__builtin_trap();
}
}
};

View File

@ -83,7 +83,7 @@
[_container addObject: object];
}
- (instancetype)initWithMethodSignature:(NSMethodSignature *)sig
- (instancetype)_initWithMethodSignature: (NSMethodSignature*)sig frame: (void*)frame
{
if (sig == nil)
{
@ -104,6 +104,10 @@
_retdata = calloc(retSize + [_signature frameLength], 1);
_frame = _retdata + retSize;
if (frame) {
memcpy(_frame, frame, [_signature frameLength]);
}
if ([sig _stret])
{
// Set up the return value pointer for the objc_msgSend_stret call.
@ -115,6 +119,11 @@
return self;
}
- (instancetype)initWithMethodSignature:(NSMethodSignature *)sig
{
return [self _initWithMethodSignature: sig frame: NULL];
}
- (instancetype)init
{
[self release]; // init should not work on NSInvocation
@ -129,6 +138,12 @@
[super dealloc];
}
+ (instancetype)_invocationWithMethodSignature: (NSMethodSignature*)sig frame: (void*)frame
{
return [[[self alloc] _initWithMethodSignature: sig frame: frame] autorelease];
}
+ (instancetype)invocationWithMethodSignature:(NSMethodSignature *)sig
{
return [[[self alloc] initWithMethodSignature:sig] autorelease];
@ -326,7 +341,7 @@ static BOOL isBlock(id object)
imp = &objc_msgSend;
}
[self invokeUsingIMP: imp];
[self _invokeUsingIMP: imp withFrame: _frame];
}
- (void) invokeWithTarget: (id) target

View File

@ -56,8 +56,46 @@
{
NSMethodType *ms = &_types[_count];
if (nextType[0] == '>' && nextType[1] == '\0') {
// handle the case where we initialize the signature using the extended type description of a block parameter
// (this is so that we don't have to copy the string and null terminate it in `_signatureForBlockAtArgumentIndex:`)
break;
}
currentType = nextType;
nextType = NSGetSizeAndAlignment(currentType, &ms->size, &ms->alignment);
// we need to be able to handle extended type encodings.
// these don't affect the size or alignment of the type, but they are considered part of the type and we need to store them
if (nextType[0] == '"') {
// this describes what kind of object the argument expects.
// this case is simple; no nesting possible
nextType = strchr(nextType + 1, '"');
if (!nextType) {
// no closing quotation mark? invalid type encoding.
[NSException raise: NSInvalidArgumentException format: @"Invalid type encoding: expected closing quotation mark"];
}
++nextType; // skip the closing quotation mark
} else if (nextType[0] == '<') {
// this describes the block signature.
// this case is little more complicated; nesting is possible
size_t nestLevel = 1;
++nextType;
for (; nestLevel > 0 && nextType[0] != '\0'; ++nextType) {
if (nextType[0] == '<') {
++nestLevel;
} else if (nextType[0] == '>') {
--nestLevel;
}
}
if (nestLevel > 0) {
// still missing a closing angle bracket? invalid type encoding.
[NSException raise: NSInvalidArgumentException format: @"Invalid type encoding: expected closing angle bracket"];
}
}
// there might other extended type encodings i haven't encountered, but those two should be the two most important ones
ms->type = calloc(nextType - currentType + 1, 1);
if (UNLIKELY(ms->type == NULL))
{
@ -216,4 +254,14 @@
return _typeString;
}
- (NSMethodSignature*)_signatureForBlockAtArgumentIndex: (NSUInteger)index
{
const char* argType = [self getArgumentTypeAtIndex: index];
argType = strchr(argType, '<');
if (!argType) {
return nil;
}
return [NSMethodSignature signatureWithObjCTypes: argType + 1];
}
@end

View File

@ -1,5 +1,9 @@
#import <objc/message.h>
#import <Block_private.h>
@class NSMethodSignature;
// Needs to be at least as large as:
// - 4 ints (for r0-r3)
// - 2 longs (for rdx/rax) and two doubles (for xmm0/xmm1)
@ -12,8 +16,9 @@ void __invoke__(void *send, void *retdata, marg_list args, size_t len, char rett
extern void _CF_forwarding_prep_0();
extern void _CF_forwarding_prep_1();
@interface NSInvocation (Internal)
+ (instancetype)_invocationWithMethodSignature: (NSMethodSignature*)signature frame: (void*)frame;
- (instancetype)_initWithMethodSignature: (NSMethodSignature*)signature frame: (void*)frame;
- (void) invokeSuper;
- (void) invokeUsingIMP: (IMP) imp;
- (void **) _idxToArg: (NSUInteger) idx;