Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/yusefnapora/nu
Browse files Browse the repository at this point in the history
  • Loading branch information
timburks committed Mar 24, 2011
2 parents faf9995 + a78ca67 commit 39455c4
Show file tree
Hide file tree
Showing 3 changed files with 319 additions and 3 deletions.
53 changes: 53 additions & 0 deletions nu/cblocks.nu
@@ -0,0 +1,53 @@
;; @file cblocks.nu
;; @discussion Macros for creating C/Objective-C blocks from Nu
;;
;;
;; @copyright Copyright (c) 2007 Tim Burks, Neon Design Technology, Inc.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.

;; bridgedblock returns a NuBridgedBlock object that references a NuBlock and a corresponding C Block.
;; These can be obtained by sending the nuBlock and cBlock messages, respectively. Use this when you
;; want to be able to call the block from Nu or pass it to an objective C method. If you only care
;; about the C block, you can use the 'cblock' macro instead.
;;

(macro bridgedblock (ret params *body)
(progn
; Bail if blocks aren't enabled in the framework
(try ((NuBridgedBlock class))
(catch (execption) (throw* "NuException" "This build of Nu does not support C blocks.")))

(function __get_type_signature (identifier)
(((NuBridgedFunction functionWithName:"signature_for_identifier" signature:"@@@") identifier
(NuSymbolTable sharedSymbolTable))))
(set __sig (__get_type_signature (list ret)))
(set __blockparams ())
(set __paramlist params)
(until (eq __paramlist nil)
(set __type (car __paramlist))
(if (eq (cdr __paramlist) nil)
(throw* "NuMatchException"
"cblock parameter list must contain an even number of elements in the form \"(type) name\""))
(set __param (car (cdr __paramlist)))
(set __paramlist (cdr (cdr __paramlist)))
(set __sig (__sig stringByAppendingString:
(__get_type_signature __type)))
(set __blockparams (append __blockparams (list __param))))
;(puts "Signature: #{__sig}")
;(puts "Block params: #{__blockparams}")
`(((NuBridgedBlock alloc) initWithNuBlock:
(do ,__blockparams ,*body) signature:,__sig))))


(macro cblock (ret params *body) `((bridgedblock ,ret ,params ,*body) cBlock))
38 changes: 38 additions & 0 deletions objc/NuBridge.h
Expand Up @@ -103,3 +103,41 @@ But in practice, this has not been much of a problem.
+ (id) constantWithName:(NSString *) name signature:(NSString *) signature;

@end

#ifdef __BLOCKS__

/*!
@class NuBridgedBlock
@abstract Generates a C block that wraps a nu block
@discussion This class makes a C block that wraps a nu block using a supplied
Objective-C-style function signature. This works by copying a dummy c block and
then writing over its function pointer with a libFFI-generated closure function.
*/
@interface NuBridgedBlock : NSObject
{
NuBlock *nuBlock;

id cBlock;
}

/*! Returns a C block that wraps the supplied nu block using the supplied
Objective-C-style function signature.
*/
+(id)cBlockWithNuBlock:(NuBlock*)nb signature:(NSString*)sig;

/*! Initializes a NuBridgedBlock object using a NuBlock and an Objective-C-style
function signature. A C block is generated during the initialization.
*/
-(id)initWithNuBlock:(NuBlock*)nb signature:(NSString*)sig;

/*! Returns the NuBlock associated with the NuBridgedBlock object.
*/
-(NuBlock*)nuBlock;

/*! Returns the C block generated by the NuBridgedBlock object.
*/
-(id)cBlock;

@end
#endif //__BLOCKS__

231 changes: 228 additions & 3 deletions objc/NuBridge.m
Expand Up @@ -1024,9 +1024,30 @@ id nu_calling_objc_method_handler(id target, Method_t m, NSMutableArray *args)
id result = [NSNull null];

// dynamically construct the method call

//the method_***** functions seems to count c blocks twice, i.e. they separate
//the @ and ?. Using an NSMethodSignature seems to be an easy way around it.
//However, it appears to have some flaws as it causes 'nuke test' to fail
#define USE_SIG 1

#if USE_SIG
NSMethodSignature *sig = [target methodSignatureForSelector:s];
int argument_count = [sig numberOfArguments];
BOOL zeroArguments = NO;
if (argument_count == 0)
{
// - [NSMethodSignature numberOfArguments] returns 0 if there are no arguments, but we expect 2 (cmd and self).
// If we get zero, we use method_getNumberOfArguments() here, and method_getArgumentType() below.
// This works around Apple's bug in the method_*** functions, but allows 'nuke test' to pass
argument_count = method_getNumberOfArguments(m);
zeroArguments = YES;
}
#else
int argument_count = method_getNumberOfArguments(m);
if ( [args count] != argument_count-2) {
raise_argc_exception(s, argument_count-2, [args count]);
#endif
if ( [args count] != argument_count-2) {

raise_argc_exception(s, argument_count-2, [args count]);
}
else {
bool success = false;
Expand All @@ -1040,8 +1061,17 @@ id nu_calling_objc_method_handler(id target, Method_t m, NSMutableArray *args)
int *argument_needs_retained = (int *) malloc (argument_count * sizeof(int));
int i;
for (i = 0; i < argument_count; i++) {
#if USE_SIG
if (zeroArguments) {
method_getArgumentType(m, i, &arg_type_buffer[0], BUFSIZE);
} else {
strncpy(&arg_type_buffer[0], [sig getArgumentTypeAtIndex:i], BUFSIZE);
}
#else
method_getArgumentType(m, i, &arg_type_buffer[0], BUFSIZE);
argument_types[i] = ffi_type_for_objc_type(&arg_type_buffer[0]);
#endif

argument_types[i] = ffi_type_for_objc_type(&arg_type_buffer[0]);
argument_values[i] = value_buffer_for_objc_type(&arg_type_buffer[0]);
if (i == 0)
*((id *) argument_values[i]) = target;
Expand Down Expand Up @@ -1454,11 +1484,13 @@ + (id) constantWithName:(NSString *) name signature:(NSString *) signature

@end


static NuSymbol *oneway_symbol, *in_symbol, *out_symbol, *inout_symbol, *bycopy_symbol, *byref_symbol, *const_symbol,
*void_symbol, *star_symbol, *id_symbol, *voidstar_symbol, *idstar_symbol, *int_symbol, *long_symbol, *NSComparisonResult_symbol,
*BOOL_symbol, *double_symbol, *float_symbol, *NSRect_symbol, *NSPoint_symbol, *NSSize_symbol, *NSRange_symbol,
*SEL_symbol, *Class_symbol;


static void prepare_symbols(NuSymbolTable *symbolTable)
{
oneway_symbol = [symbolTable symbolWithCString:"oneway"];
Expand Down Expand Up @@ -1782,3 +1814,196 @@ id help_add_method_to_class(Class classToExtend, id cdr, NSMutableDictionary *co
return nil;
}
}

#ifdef __BLOCKS__

static id make_cblock (NuBlock *nuBlock, NSString *signature);
static void objc_calling_nu_block_handler(ffi_cif* cif, void* returnvalue, void** args, void* userdata);
char **generate_block_userdata(NuBlock *nuBlock, const char *signature);
void *construct_block_handler(NuBlock *block, const char *signature);

@implementation NuBridgedBlock

+(id)cBlockWithNuBlock:(NuBlock*)nb signature:(NSString*)sig
{
return [[[[self alloc] initWithNuBlock:nb signature:sig] autorelease] cBlock];
}

-(id)initWithNuBlock:(NuBlock*)nb signature:(NSString*)sig
{
nuBlock = [nb retain];
cBlock = make_cblock(nb,sig);

return self;
}

-(NuBlock*)nuBlock
{return [[nuBlock retain] autorelease];}

-(id)cBlock
{return [[cBlock retain] autorelease];}

-(void)dealloc
{
[nuBlock release];
[cBlock release];
[super dealloc];
}

@end
//the caller gets ownership of the block
static id make_cblock (NuBlock *nuBlock, NSString *signature)
{
void *funcptr = construct_block_handler(nuBlock, [signature UTF8String]);

int i = 0xFFFF;
void(^cBlock)(void)=[^(void){printf("%i",i);} copy];

#ifdef __x86_64__
/* this is what happens when a block is called on x86 64
mov %rax,-0x18(%rbp) //the pointer to the block object is in rax
mov -0x18(%rbp),%rax
mov 0x10(%rax),%rax //the pointer to the block function is at +0x10 into the block object
mov -0x18(%rbp),%rdi //the first argument (this examples has no others) is always the pointer to the block object
callq *%rax
*/
//2*(sizeof(void*)) = 0x10
*((void **)(id)cBlock + 2) = (void *)funcptr;
#else
/* this is what happens when a block is called on x86 32
mov %eax,-0x14(%ebp) //the pointer to the block object is in eax
mov -0x14(%ebp),%eax
mov 0xc(%eax),%eax //the pointer to the block function is at +0xc into the block object
mov %eax,%edx
mov -0x14(%ebp),%eax //the first argument (this examples has no others) is always the pointer to the block object
mov %eax,(%esp)
call *%edx
*/
//3*(sizeof(void*)) = 0xc
*((void **)(id)cBlock + 3) = (void *)funcptr;
#endif
return cBlock;
}

static void objc_calling_nu_block_handler(ffi_cif* cif, void* returnvalue, void** args, void* userdata)
{
int argc = cif->nargs - 1;
//void *ptr = (void*)args[0] //don't need this first parameter
// see objc_calling_nu_method_handler

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NuBlock *block = ((NuBlock **)userdata)[1];
//NSLog(@"----------------------------------------");
//NSLog(@"calling block %@", [block stringValue]);
id arguments = [[NuCell alloc] init];
id cursor = arguments;
int i;
for (i = 0; i < argc; i++) {
NuCell *nextCell = [[NuCell alloc] init];
[cursor setCdr:nextCell];
[nextCell release];
cursor = [cursor cdr];
id value = get_nu_value_from_objc_value(args[i+1], ((char **)userdata)[i+2]);
[cursor setCar:value];
}
//NSLog(@"in nu method handler, using arguments %@", [arguments stringValue]);
id result = [block evalWithArguments:[arguments cdr] context:nil];
//NSLog(@"in nu method handler, putting result %@ in %x with type %s", [result stringValue], (size_t) returnvalue, ((char **)userdata)[0]);
char *resultType = (((char **)userdata)[0])+1;// skip the first character, it's a flag
set_objc_value_from_nu_value(returnvalue, result, resultType);
[arguments release];
if (pool) {
if (resultType[0] == '@')
[*((id *)returnvalue) retain];
[pool release];
if (resultType[0] == '@')
[*((id *)returnvalue) autorelease];
}
}


char **generate_block_userdata(NuBlock *nuBlock, const char *signature)
{
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:signature];
const char *return_type_string = [methodSignature methodReturnType];
int argument_count = [methodSignature numberOfArguments];
char **userdata = (char **) malloc ((argument_count+3) * sizeof(char*));
userdata[0] = (char *) malloc (2 + strlen(return_type_string));

//assume blocks never return retained results
sprintf(userdata[0], " %s", return_type_string);

//so first element is return type, second is nuBlock
userdata[1] = (char *) nuBlock;
[nuBlock retain];
int i;
for (i = 0; i < argument_count; i++) {
const char *argument_type_string = [methodSignature getArgumentTypeAtIndex:i];
userdata[i+2] = strdup(argument_type_string);
}
userdata[argument_count+2] = NULL;

#if 0
NSLog(@"Userdata for block: %@, signature: %s", [nuBlock stringValue], signature);
for (int i = 0; i < argument_count+2; i++)
{ if (i != 1)
NSLog(@"userdata[%i] = %s",i,userdata[i]); }
#endif
return userdata;
}


void *construct_block_handler(NuBlock *block, const char *signature)
{
char **userdata = generate_block_userdata(block, signature);

int argument_count = 0;
while (userdata[argument_count] != 0) argument_count++;
argument_count-=1; //unlike a method call, c blocks have one, not two hidden args (see comments in make_cblock()
#if 0
NSLog(@"using libffi to construct handler for nu block with %d arguments and signature %s", argument_count, signature);
#endif



ffi_type **argument_types = (ffi_type **) malloc ((argument_count+1) * sizeof(ffi_type *));
ffi_type *result_type = ffi_type_for_objc_type(userdata[0]+1);

argument_types[0] = ffi_type_for_objc_type("^?");

for (int i = 1; i < argument_count; i++)
argument_types[i] = ffi_type_for_objc_type(userdata[i+1]);
argument_types[argument_count] = NULL;
ffi_cif *cif = (ffi_cif *)malloc(sizeof(ffi_cif));
if (cif == NULL) {
NSLog(@"unable to prepare closure for signature %s (could not allocate memory for cif structure)", signature);
return NULL;
}
int status = ffi_prep_cif(cif, FFI_DEFAULT_ABI, argument_count, result_type, argument_types);
if (status != FFI_OK) {
NSLog(@"unable to prepare closure for signature %s (ffi_prep_cif failed)", signature);
return NULL;
}
ffi_closure *closure = (ffi_closure *)mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (closure == (ffi_closure *) -1) {
NSLog(@"unable to prepare closure for signature %s (mmap failed with error %d)", signature, errno);
return NULL;
}
if (closure == NULL) {
NSLog(@"unable to prepare closure for signature %s (could not allocate memory for closure)", signature);
return NULL;
}
if (ffi_prep_closure(closure, cif, objc_calling_nu_block_handler, userdata) != FFI_OK) {
NSLog(@"unable to prepare closure for signature %s (ffi_prep_closure failed)", signature);
return NULL;
}
if (mprotect(closure, sizeof(closure), PROT_READ | PROT_EXEC) == -1) {
NSLog(@"unable to prepare closure for signature %s (mprotect failed with error %d)", signature, errno);
return NULL;
}
return (void*)closure;
}

#endif //__BLOCKS__

0 comments on commit 39455c4

Please sign in to comment.