diff --git a/.rubocop.yml b/.rubocop.yml index 6a2e616..c507f9d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,6 +3,8 @@ AllCops: NewCops: enable Naming/MethodName: Enabled: false +Metrics/MethodLength: + Enabled: false Style/IfUnlessModifier: Enabled: false Style/StringLiterals: diff --git a/bin/benchmark b/bin/benchmark new file mode 100755 index 0000000..a74f234 --- /dev/null +++ b/bin/benchmark @@ -0,0 +1,158 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "benchmark" + +SIZE = 1_000_000 +COLS = 12 + +def run_objc_hash + dict = ObjRuby::NSMutableDictionary.new + + (0...SIZE).each do |i| + if (i % 2).zero? + dict.setObject_forKey("foo", i) + end + end + + raise unless dict.count == SIZE / 2 + + (0...SIZE).each do |i| + unless dict.objectForKey(i) + dict.setObject_forKey("bar", i) + end + end + + raise unless dict.count == SIZE +end + +def run_ruby_hash + hash = {} + + (0...SIZE).each do |i| + if (i % 2).zero? + hash[i] = "foo" + end + end + + raise unless hash.size == SIZE / 2 + + (0...SIZE).each do |i| + unless hash[i] + hash[i] = "bar" + end + end + + raise unless hash.size == SIZE +end + +def run_objc_set + set = ObjRuby::NSMutableSet.new + + (0...SIZE).each do |i| + if (i % 2).zero? + set.addObject(i) + end + end + + raise unless set.count == SIZE / 2 + + (0...SIZE).each do |i| + unless set.containsObject(i) + set.addObject(i) + end + end + + raise unless set.count == SIZE +end + +def run_ruby_set + set = Set.new + + (0...SIZE).each do |i| + if (i % 2).zero? + set << i + end + end + + raise unless set.size == SIZE / 2 + + (0...SIZE).each do |i| + unless set.include?(i) + set << i + end + end + + raise unless set.size == SIZE +end + +def run_objc_array + array = ObjRuby::NSMutableArray.new + + (0...SIZE).each do |i| + if (i % 2).zero? + array.addObject("foo") + end + end + + raise unless array.count == SIZE / 2 + + (0...SIZE).each do |i| + unless (i % 2).zero? + array.addObject("bar") + end + end + raise unless array.count == SIZE +end + +def run_ruby_array + array = [] + + (0...SIZE).each do |i| + if (i % 2).zero? + array << "foo" + end + end + + raise unless array.size == SIZE / 2 + + (0...SIZE).each do |i| + unless (i % 2).zero? + array << "bar" + end + end + + raise unless array.size == SIZE +end + +Benchmark.bm(COLS) do |bm| + bm.report("Warmup:") do + require "obj_ruby" + require "obj_ruby/cocoa" + end + + bm.report("NSDictionary:") do + run_objc_hash + end + + bm.report("Hash:") do + run_ruby_hash + end + + bm.report("NSSet:") do + run_objc_set + end + + bm.report("Set:") do + run_ruby_set + end + + bm.report("NSArray:") do + run_objc_array + end + + bm.report("Array:") do + run_ruby_array + end +end diff --git a/ext/obj_ext/RIGSBridgeSupportParser.h b/ext/obj_ext/RIGSBridgeSupportParser.h index 6569708..fe50ac9 100644 --- a/ext/obj_ext/RIGSBridgeSupportParser.h +++ b/ext/obj_ext/RIGSBridgeSupportParser.h @@ -25,6 +25,7 @@ @interface RIGSBridgeSupportParser : NSObject { + NSString *_className; NSString *_methodName; NSString *_functionName; NSString *_protocolName; diff --git a/ext/obj_ext/RIGSBridgeSupportParser.m b/ext/obj_ext/RIGSBridgeSupportParser.m index dc4655f..1ebcf41 100644 --- a/ext/obj_ext/RIGSBridgeSupportParser.m +++ b/ext/obj_ext/RIGSBridgeSupportParser.m @@ -54,24 +54,16 @@ - (void)parser:(NSXMLParser *)parser [self parseFunctionWithName:[attributeDict objectForKey:@"name"]]; } else if ([elementName isEqualToString:@"retval"]) { - [self parseArgWithIndex:-1 - type:[attributeDict objectForKey:@"type64"] - printf:nil - block:nil]; + [self parseArgWithType:[attributeDict objectForKey:@"type64"] + index:@"-1" + printf:nil + block:nil]; } else if ([elementName isEqualToString:@"arg"]) { - if (_methodName) { - [self parseArgWithIndex:[attributeDict objectForKey:@"index"] ? [[attributeDict objectForKey:@"index"] intValue] : _argIndex++ - type:[attributeDict objectForKey:@"type64"] - printf:[attributeDict objectForKey:@"printf_format"] - block:[attributeDict objectForKey:@"function_pointer"]]; - } - else if (_functionName) { - [self parseArgWithIndex:_argIndex++ - type:[attributeDict objectForKey:@"type64"] - printf:[attributeDict objectForKey:@"printf_format"] - block:nil]; - } + [self parseArgWithType:[attributeDict objectForKey:@"type64"] + index:[attributeDict objectForKey:@"index"] + printf:[attributeDict objectForKey:@"printf_format"] + block:[attributeDict objectForKey:@"function_pointer"]]; } } @@ -79,7 +71,10 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { - if ([elementName isEqualToString:@"informal_protocol"]) { + if ([elementName isEqualToString:@"class"]) { + [self finalizeClass]; + } + else if ([elementName isEqualToString:@"informal_protocol"]) { [self finalizeProtocol]; } else if ([elementName isEqualToString:@"method"]) { @@ -88,7 +83,7 @@ - (void)parser:(NSXMLParser *)parser else if ([elementName isEqualToString:@"function"]) { [self finalizeFunction]; } - else if ([elementName isEqualToString:@"arg"] || [elementName isEqualToString:@"retval"]) { + else if ([elementName isEqualToString:@"retval"] || [elementName isEqualToString:@"arg"]) { [self finalizeArg]; } } @@ -139,36 +134,46 @@ - (void)parseProtocolWithName:(NSString*)name _argDepth = 0; } -- (void)parseArgWithIndex:(NSInteger)index type:(NSString*)type printf:(NSString*)printf block:(NSString*)block +- (void)parseArgWithType:(NSString*)type index:(NSString*)index printf:(NSString*)printf block:(NSString*)block { _argDepth++; if (_methodName) { - if ([printf isEqualToString:@"true"]) { - _formatStringIndex = index; + if (_argDepth == 1) { + if ([printf isEqualToString:@"true"]) { + _formatStringIndex = [index integerValue]; + } + if ([block isEqualToString:@"true"] && [type isEqualToString:@"@?"]) { + _blockIndex = [index integerValue]; + _objcTypes = [[type mutableCopy] retain]; + } + if (type) { + rb_objc_register_type_arg_from_objc([_methodName UTF8String], [index intValue], [type UTF8String]); + } } - if ([block isEqualToString:@"true"] && [type isEqualToString:@"@?"]) { - _blockIndex = index; - _objcTypes = [[NSMutableString string] retain]; + else { + if (_objcTypes) { + if ([index isEqualToString:@"-1"]) { + [_objcTypes insertString:type atIndex:0]; + } + else { + [_objcTypes appendString:type]; + } + } } - if (_objcTypes) { - if (index == -1) { + } + else if (_functionName) { + if (_argDepth == 1) { + if ([printf isEqualToString:@"true"]) { + _formatStringIndex = _argIndex; + } + if ([index isEqualToString:@"-1"]) { [_objcTypes insertString:type atIndex:0]; } else { [_objcTypes appendString:type]; } - } - } - else if (_functionName) { - if ([printf isEqualToString:@"true"]) { - _formatStringIndex = index; - } - if (index == -1) { - [_objcTypes insertString:type atIndex:0]; - } - else { - [_objcTypes appendString:type]; + _argIndex++; } } } @@ -252,11 +257,7 @@ - (void)parseEnumWithName:(NSString*)name value:(NSString*)value - (void)parseClassWithName:(NSString*)name { - Class objc_class = NSClassFromString(name); - - if (objc_class) { - rb_objc_register_class_from_objc(objc_class); - } + _className = [name retain]; } - (NSUInteger)parseStructArgCountWithType:(NSString*)type @@ -270,6 +271,18 @@ - (NSUInteger)parseStructArgCountWithType:(NSString*)type return quoteCount / 2; } +- (void)finalizeClass +{ + Class objc_class; + + if ((objc_class = NSClassFromString(_className))) { + rb_objc_register_class_from_objc(objc_class); + } + + [_className release]; + _className = nil; +} + - (void)finalizeProtocol { [_protocolName release]; @@ -306,7 +319,7 @@ - (void)finalizeArg if (_blockIndex != -1) { if (_methodName) { - rb_objc_register_block_from_objc([_methodName UTF8String], _blockIndex, [_objcTypes UTF8String]); + rb_objc_register_block_arg_from_objc([_methodName UTF8String], _blockIndex, [_objcTypes UTF8String]); } _blockIndex = -1; diff --git a/ext/obj_ext/RIGSCore.h b/ext/obj_ext/RIGSCore.h index 2a7e927..8e91f2c 100644 --- a/ext/obj_ext/RIGSCore.h +++ b/ext/obj_ext/RIGSCore.h @@ -40,11 +40,12 @@ VALUE rb_objc_register_instance_method_from_rb(VALUE rb_class, VALUE rb_method); void rb_objc_register_float_from_objc(const char *name, double value); void rb_objc_register_integer_from_objc(const char *name, long long value); void rb_objc_register_struct_from_objc(const char *key, const char *name, const char *args[], size_t argCount); -void rb_objc_register_format_string_from_objc(const char *selector, size_t index); -void rb_objc_register_block_from_objc(const char *selector, size_t index, const char *objcTypes); void rb_objc_register_constant_from_objc(const char *name, const char *type); void rb_objc_register_function_from_objc(const char *name, const char *objcTypes); void rb_objc_register_protocol_from_objc(const char *selector, const char *objcTypes); +void rb_objc_register_format_string_from_objc(const char *selector, size_t index); +void rb_objc_register_type_arg_from_objc(const char *selector, int index, const char *objcTypes); +void rb_objc_register_block_arg_from_objc(const char *selector, size_t index, const char *objcTypes); VALUE rb_objc_import(VALUE rb_self, VALUE rb_name); VALUE rb_objc_new(int rigs_argc, VALUE *rigs_argv, VALUE rb_class); diff --git a/ext/obj_ext/RIGSCore.m b/ext/obj_ext/RIGSCore.m index 045aab0..033c043 100644 --- a/ext/obj_ext/RIGSCore.m +++ b/ext/obj_ext/RIGSCore.m @@ -48,8 +48,20 @@ // Hash table that maps known ObjC functions to objcTypes encoding static NSMapTable *knownFunctions = 0; -// Hash table that maps known ObjC selectors to block objcTypes encoding -static NSMapTable *knownBlocks = 0; +// Hash table that maps known ObjC class selectors with class to objcTypes encoding +static NSMapTable *knownClassMethods = 0; + +// Hash table that maps known ObjC instance selectors with class to objcTypes encoding +static NSMapTable *knownInstanceMethods = 0; + +// Hash table that maps known ObjC selectors with arg position to objcTypes encoding +static NSMapTable *knownTypeArgs = 0; + +// Hash table that maps known ObjC selectors with arg position to block objcTypes encoding +static NSMapTable *knownBlockArgs = 0; + +// Hash table that maps known ObjC selectors to printf arg positions (index+1) +static NSMapTable *knownFormatStrings = 0; // Hash table that maps known ObjC selectors to objcTypes encoding static NSMapTable *knownProtocols = 0; @@ -57,9 +69,6 @@ // Hash table that maps known objcTypes encoding to Ruby proxy method implementations static NSMapTable *knownImplementations = 0; -// Hash table that maps known ObjC selectors to printf arg positions (index+1) -static NSMapTable *knownFormatStrings = 0; - // Hash table that contains loaded Framework bundleIdentifiers static NSHashTable *knownFrameworks = 0; @@ -154,9 +163,8 @@ rb_objc_ffi_type_for_type(const char *type) { ffi_type *inStruct = NULL; - unsigned long inStructHash; int inStructIndex = 0; - long inStructCount = 0; + unsigned long inStructCount = 0; type = rb_objc_skip_type_qualifiers(type); @@ -165,16 +173,15 @@ } if (*type == _C_STRUCT_B) { - inStructHash = rb_objc_hash_struct(type); - type = rb_objc_skip_type_sname(type); - inStructCount = rb_array_len(rb_struct_s_members((VALUE)NSMapGet(knownStructs, (void*)inStructHash))); - + inStructCount = rb_objc_struct_type_arity(type); + inStruct = (ffi_type *)malloc(sizeof(ffi_type)); inStruct->size = 0; inStruct->alignment = 0; inStruct->type = FFI_TYPE_STRUCT; inStruct->elements = malloc((inStructCount + 1) * sizeof(ffi_type *)); - + + type = rb_objc_skip_type_sname(type); while (*type != _C_STRUCT_E) { inStruct->elements[inStructIndex++] = rb_objc_ffi_type_for_type(type); type = rb_objc_skip_typespec(type); @@ -192,10 +199,10 @@ case _C_PTR: return &ffi_type_pointer; case _C_BOOL: - case _C_UCHR: - return &ffi_type_uchar; case _C_CHR: return &ffi_type_schar; + case _C_UCHR: + return &ffi_type_uchar; case _C_SHT: return &ffi_type_sshort; case _C_USHT: @@ -285,6 +292,7 @@ { id val; Class retClass; + Class class; VALUE rb_val; VALUE rb_class; BOOL ret; @@ -305,10 +313,11 @@ object by calling rb_objc_release() */ } retClass = [val classForCoder]; - if (retClass != [val class] && strncmp(object_getClassName(val), "NSConcrete", 10) == 0) { + class = object_getClass(val); + if (retClass != class && strncmp(class_getName(class), "NSConcrete", 10) == 0) { // [NSAttributedString alloc] returns NSConcreteAttributedString // which is where initWithString is defined so we need it defined in Ruby - retClass = [val class]; + retClass = class; } rb_class = (VALUE) NSMapGet(knownClasses, (void *)retClass); @@ -386,12 +395,12 @@ object by calling rb_objc_release() */ switch (TYPE(rb_val)) { case T_DATA: - if (rb_obj_is_kind_of(rb_val, rb_cTime) == Qtrue) { - *(id*)where = rb_objc_date_from_rb(rb_val); - } - else if (rb_iv_get(CLASS_OF(rb_val), "@objc_class") != Qnil) { + if (rb_iv_get(CLASS_OF(rb_val), "@objc_class") != Qnil) { Data_Get_Struct(rb_val, void, *(id*)where); } + else if (rb_obj_is_kind_of(rb_val, rb_cTime) == Qtrue) { + *(id*)where = rb_objc_date_from_rb(rb_val); + } else { ret = NO; } @@ -575,12 +584,7 @@ object by calling rb_objc_release() */ break; } - if (inStruct) { - // skip the component we have just processed - type = rb_objc_skip_typespec(type); - } - - } while (inStruct && *type != _C_STRUCT_E); + } while (inStruct && (type = rb_objc_skip_typespec(type)) && *type != _C_STRUCT_E); if (ret == NO) { /* raise exception - Don't know how to handle this type of argument */ @@ -659,7 +663,6 @@ object by calling rb_objc_release() */ break; case _C_CHR: - // Assume that if YES or NO then it's a BOOLean if (__OBJC_BOOL_IS_BOOL != 1 && *(char *)where == YES) rb_val = Qtrue; else if (__OBJC_BOOL_IS_BOOL != 1 && *(char *)where == NO) @@ -760,15 +763,13 @@ object by calling rb_objc_release() */ // the end of the running Ruby array rb_ary_push(end, rb_val); } - // skip the type of the component we have just processed - type = (char*)rb_objc_skip_typespec(type); } else { // We are not in a C structure so simply return the // Ruby value *rb_val_ptr = rb_val; } - } while (inStruct && *type != _C_STRUCT_E); + } while (inStruct && (type = rb_objc_skip_typespec(type)) && *type != _C_STRUCT_E); if (end != Qnil && NSMapGet(knownStructs, (void*)inStructHash) != NULL) { *rb_val_ptr = rb_struct_alloc((VALUE)NSMapGet(knownStructs, (void*)inStructHash), end); @@ -793,7 +794,7 @@ object by calling rb_objc_release() */ nbArgs = [signature numberOfArguments]; objcTypesIndex = 0; - type = [signature methodReturnType]; + type = [signature methodReturnType]; while (*type) { objcTypes[objcTypesIndex++] = *type++; } @@ -961,10 +962,9 @@ object by calling rb_objc_release() */ } static VALUE -rb_objc_dispatch(id rcv, const char *method, NSMethodSignature *signature, int rigs_argc, VALUE *rigs_argv) +rb_objc_dispatch(id rcv, const char *method, unsigned long hash, const char *types, int rigs_argc, VALUE *rigs_argv) { void *sym; - unsigned long hash; int nbArgs; int nbArgsExtra; int nbArgsAdjust; @@ -982,9 +982,11 @@ object by calling rb_objc_release() */ void *closurePtr; struct rb_objc_block *block; ffi_cif closureCif; + NSMethodSignature *signature; + + signature = [NSMethodSignature signatureWithObjCTypes:types]; if (rcv != nil) { - // TODO: perhaps check [rcv methodForSelector:sel] for IMP nbArgsAdjust = 2; switch(*(signature.methodReturnType)) { #ifndef __aarch64__ @@ -1006,8 +1008,6 @@ object by calling rb_objc_release() */ return Qnil; } - hash = rb_objc_hash(method); - nbArgs = (int)[signature numberOfArguments]; nbArgsExtra = rigs_argc - (nbArgs - nbArgsAdjust); @@ -1058,7 +1058,7 @@ object by calling rb_objc_release() */ for (i=nbArgsAdjust;i 255) return NO; + + knownMethods = class_isMetaClass(class) ? knownClassMethods : knownInstanceMethods; + hash = rb_objc_hash(sel_getName(method_getName(method))); + chash = rb_objc_hash_s(class_getName(class), hash); + data = NSMapGet(knownMethods, (void*)chash); + + if (data) return NO; + + pos = rb_objc_skip_type_qualifiers(pos); + typeIndex = 0; + while((lpos = pos) && (pos = rb_objc_skip_typespec(lpos))) { + if (typeIndex != 1 && + typeIndex != 2 && + (type = NSMapGet(knownTypeArgs, (void*)(hash + (typeIndex == 0 ? -1 : typeIndex - 2))))) { + strlcat(objcTypes, type, strlen(type) + strlen(objcTypes) + 1); + } + else { + strlcat(objcTypes, lpos, pos - lpos + strlen(objcTypes) + 1); + } + pos = rb_objc_skip_type_size(pos); + pos = rb_objc_skip_type_qualifiers(pos); + typeIndex++; + } + + data = malloc(sizeof(char) * (strlen(objcTypes) + 1)); + strcpy(data, objcTypes); + NSMapInsertKnownAbsent(knownMethods, (void*)chash, (void*)data); + + return YES; +} + static unsigned int rb_objc_register_instance_methods(Class objc_class, VALUE rb_class) { @@ -1239,6 +1293,7 @@ object by calling rb_objc_release() */ mthRubyName = rb_objc_sel_to_method(mthSel); if (mthRubyName == NULL) continue; + if (!rb_objc_register_method(objc_class, methods[i])) continue; rb_define_method(rb_class, mthRubyName, rb_objc_send, -1); @@ -1262,16 +1317,14 @@ object by calling rb_objc_release() */ SEL mthSel; char *mthRubyName; char *mthRubyAlias; - Class objc_meta_class; unsigned int cmth_cnt; unsigned int i; Method *methods; VALUE rb_singleton; - objc_meta_class = objc_getMetaClass(class_getName(objc_class)); - /* Define all Ruby Class (singleton) methods for this Class */ - methods = class_copyMethodList(objc_meta_class, &cmth_cnt); + objc_class = object_getClass(objc_class); + methods = class_copyMethodList(objc_class, &cmth_cnt); rb_singleton = rb_singleton_class(rb_class); for (i=0;i