Revision: 4495 http://trac.macosforge.org/projects/ruby/changeset/4495 Author: lsansonetti@apple.com Date: 2010-09-07 17:53:10 -0700 (Tue, 07 Sep 2010) Log Message: ----------- support for C-level blocks (note: this requires a not-yet-released BridgeSupport with special annotations for blocks) Modified Paths: -------------- MacRuby/trunk/bs.c MacRuby/trunk/bs.h MacRuby/trunk/compiler.cpp MacRuby/trunk/compiler.h MacRuby/trunk/kernel.c MacRuby/trunk/objc.h MacRuby/trunk/spec/macruby/language/objc_method_spec.rb Modified: MacRuby/trunk/bs.c =================================================================== --- MacRuby/trunk/bs.c 2010-09-04 01:55:20 UTC (rev 4494) +++ MacRuby/trunk/bs.c 2010-09-08 00:53:10 UTC (rev 4495) @@ -319,10 +319,10 @@ bool ok; int nested; - opposite = - *p_src == '{' ? '}' : - *p_src == '(' ? ')' : - *p_src == '[' ? ']' : 0; + opposite = + *p_src == '{' ? '}' : + *p_src == '(' ? ')' : + *p_src == '[' ? ']' : 0; for (i = 0, ok = false, nested = 0; i < src_len - (p_src - src) && !ok; @@ -1100,23 +1100,18 @@ retval = func != NULL ? func->retval : method->retval; } else { - args_count = func != NULL ? func->args_count : method->args_count; + args_count = func != NULL ? func->args_count + : method->args_count; arg = &args[args_count - 1]; } - // Let's get the old type to keep the number of pointers - char *old_type = (retval ? retval->type : arg->type); - int nb_ptr = 0; + // Determine if we deal with a block or a function pointer. + const char *old_type = (retval ? retval->type : arg->type); + const char lambda_type = *old_type == '@' + ? _MR_C_LAMBDA_BLOCK + : _MR_C_LAMBDA_FUNCPTR; - while (old_type && *old_type == '^') { - nb_ptr++; - old_type++; - } - // Decrementing because if we have ^^?, the last ^ (along with ?) - // is going to be replaced by <...>, so we don't want to keep it - nb_ptr--; - - char tmp_type[1026]; // 2 less to fit <, and > + char tmp_type[1025]; // 3 less to fit <, type and > char new_type[1028]; // Function ptr return type @@ -1127,13 +1122,9 @@ } // Clear the final type string memset(new_type, 0, sizeof(new_type)); - // Copy the number of pointers - for (int i = 0; i < nb_ptr; i++) { - strlcat(new_type, "^", sizeof(new_type)); - } // Append the function pointer type - snprintf(new_type + nb_ptr, sizeof(new_type) - nb_ptr, - "%c%s%c", _MR_C_FPTR_B, tmp_type, _MR_C_FPTR_E); + snprintf(new_type, sizeof(new_type), "%c%c%s%c", + _MR_C_LAMBDA_B, lambda_type, tmp_type, _MR_C_LAMBDA_E); // Free the old values if (retval) { Modified: MacRuby/trunk/bs.h =================================================================== --- MacRuby/trunk/bs.h 2010-09-04 01:55:20 UTC (rev 4494) +++ MacRuby/trunk/bs.h 2010-09-08 00:53:10 UTC (rev 4495) @@ -36,10 +36,16 @@ #include <CoreFoundation/CoreFoundation.h> #include <objc/runtime.h> -/* Extend objc/runtime.h and add function pointers tokens */ -#define _MR_C_FPTR_B '<' -#define _MR_C_FPTR_E '>' +/* Extend objc/runtime.h and add function pointers and blocks tokens. + * A pointer to `void foo (int, char)' will be encoded as <^vic>. + * A `void (^)(int, char)' block will be encoded as <@vic>. + */ +#define _MR_C_LAMBDA_B '<' +#define _MR_C_LAMBDA_FUNCPTR '^' +#define _MR_C_LAMBDA_BLOCK '@' +#define _MR_C_LAMBDA_E '>' + /* Attribute and element representations. * See BridgeSupport(5) for more information. */ Modified: MacRuby/trunk/compiler.cpp =================================================================== --- MacRuby/trunk/compiler.cpp 2010-09-04 01:55:20 UTC (rev 4494) +++ MacRuby/trunk/compiler.cpp 2010-09-08 00:53:10 UTC (rev 4495) @@ -267,6 +267,7 @@ rvalToDoubleFunc = get_function("vm_rval_to_double"); rvalToSelFunc = get_function("vm_rval_to_sel"); rvalToCharPtrFunc = get_function("vm_rval_to_charptr"); + initBlockFunc = get_function("vm_init_c_block"); VoidTy = Type::getVoidTy(context); Int1Ty = Type::getInt1Ty(context); @@ -303,6 +304,9 @@ Int32PtrTy = PointerType::getUnqual(Int32Ty); BitTy = Type::getInt1Ty(context); + BlockLiteralTy = module->getTypeByName("struct.ruby_block_literal"); + assert(BlockLiteralTy != NULL); + #if ROXOR_COMPILER_DEBUG level = 0; #endif @@ -5192,8 +5196,114 @@ } Value * +RoxorCompiler::compile_lambda_to_funcptr(const char *type, + Value *val, Value *slot, bool is_block) +{ + GlobalVariable *proc_gvar = + new GlobalVariable(*RoxorCompiler::module, + RubyObjTy, false, GlobalValue::InternalLinkage, + nilVal, ""); + new StoreInst(val, proc_gvar, bb); + + const size_t buf_len = strlen(type + 1) + 1; + assert(buf_len > 1); + char *buf = (char *)malloc(buf_len); + + const char *p = GetFirstType(type + 1, buf, buf_len); + const Type *ret_type = convert_type(buf); + int argc = 0; + + std::vector<std::string> arg_ctypes; + std::vector<const Type *> arg_types; + + if (is_block) { + // The Block ABI specifies that the first argument is a pointer + // to the block literal, which we don't really care about. + arg_types.push_back(PtrTy); + } + + while (*p != _MR_C_LAMBDA_E) { + p = GetFirstType(p, buf, buf_len); + arg_ctypes.push_back(std::string(buf)); + arg_types.push_back(convert_type(buf)); + argc++; + } + FunctionType *ft = FunctionType::get(ret_type, arg_types, + false); + + // ret_type stub(arg1, arg2, ...) + // { + // VALUE *argv = alloc(argc); + // argv[0] = arg1; + // argv[1] = arg2; + // return rb_proc_check_and_call(procval, argc, argv); + // } + Function *f = cast<Function>(module->getOrInsertFunction("", + ft)); + + BasicBlock *oldbb = bb; + bb = BasicBlock::Create(context, "EntryBlock", f); + + Function::arg_iterator arg = f->arg_begin(); + + Value *argv; + if (argc == 0) { + argv = new BitCastInst(compile_const_pointer(NULL), + RubyObjPtrTy, "", bb); + } + else { + argv = new AllocaInst(RubyObjTy, ConstantInt::get(Int32Ty, argc), + "", bb); + int off = 0; + if (is_block) { + // Skip block literal argument. + off++; + arg++; + } + for (int i = 0; i < argc; i++) { + Value *index = ConstantInt::get(Int32Ty, i); + Value *aslot = GetElementPtrInst::Create(argv, index, "", bb); + Value *rval = compile_conversion_to_ruby(arg_ctypes[i].c_str(), + arg_types[i + off], arg++); + new StoreInst(rval, aslot, bb); + } + } + + // VALUE rb_proc_check_and_call( + // VALUE self, int argc, VALUE *argv + // ) + Function *proc_call_f = + cast<Function>(module->getOrInsertFunction( + "rb_proc_check_and_call", + RubyObjTy, + RubyObjTy, Int32Ty, RubyObjPtrTy, NULL)); + + Value *args[] = { + new LoadInst(proc_gvar, "", bb), + ConstantInt::get(Int32Ty, argc), + argv + }; + Value *ret_val = compile_protected_call(proc_call_f, args, + args + 3); + + if (ret_type != VoidTy) { + GetFirstType(type + 1, buf, buf_len); + ret_val = compile_conversion_to_c(buf, ret_val, + new AllocaInst(ret_type, "", bb)); + ReturnInst::Create(context, ret_val, bb); + } + else { + ReturnInst::Create(context, bb); + } + + bb = oldbb; + free(buf); + return new BitCastInst(f, PtrTy, "", bb); +} + +Value * RoxorCompiler::compile_conversion_to_c(const char *type, Value *val, - Value *slot) + Value *slot) { type = SkipTypeModifiers(type); @@ -5327,96 +5437,28 @@ } break; - case _MR_C_FPTR_B: + case _MR_C_LAMBDA_B: { - GlobalVariable *proc_gvar = - new GlobalVariable(*RoxorCompiler::module, - RubyObjTy, false, GlobalValue::InternalLinkage, - nilVal, ""); - new StoreInst(val, proc_gvar, bb); + type++; + const bool is_block = *type == _MR_C_LAMBDA_BLOCK; - const size_t buf_len = strlen(type + 1) + 1; - assert(buf_len > 1); - char *buf = (char *)malloc(buf_len); - - const char *p = GetFirstType(type + 1, buf, buf_len); - const Type *ret_type = convert_type(buf); - int argc = 0; - - std::vector<std::string> arg_ctypes; - std::vector<const Type *> arg_types; - while (*p != _MR_C_FPTR_E) { - p = GetFirstType(p, buf, buf_len); - arg_ctypes.push_back(std::string(buf)); - arg_types.push_back(convert_type(buf)); - argc++; + Value *funcptr = compile_lambda_to_funcptr(type, val, slot, + is_block); + if (!is_block) { + // A pure function pointer, let's pass it. + return funcptr; } - FunctionType *ft = FunctionType::get(ret_type, arg_types, - false); - // ret_type stub(arg1, arg2, ...) - // { - // VALUE *argv = alloc(argc); - // argv[0] = arg1; - // argv[1] = arg2; - // return rb_proc_check_and_call(procval, argc, argv); - // } - Function *f = cast<Function>(module->getOrInsertFunction("", - ft)); - - BasicBlock *oldbb = bb; - bb = BasicBlock::Create(context, "EntryBlock", f); - - Function::arg_iterator arg = f->arg_begin(); - - Value *argv; - if (argc == 0) { - argv = new BitCastInst(compile_const_pointer(NULL), - RubyObjPtrTy, "", bb); - } - else { - argv = new AllocaInst(RubyObjTy, - ConstantInt::get(Int32Ty, argc), "", bb); - for (int i = 0; i < argc; i++) { - Value *index = ConstantInt::get(Int32Ty, i); - Value *aslot = GetElementPtrInst::Create(argv, index, - "", bb); - Value *rval = compile_conversion_to_ruby( - arg_ctypes[i].c_str(), arg_types[i], arg++); - new StoreInst(rval, aslot, bb); - } - } - - // VALUE rb_proc_check_and_call( - // VALUE self, int argc, VALUE *argv - // ) - Function *proc_call_f = - cast<Function>(module->getOrInsertFunction( - "rb_proc_check_and_call", - RubyObjTy, - RubyObjTy, Int32Ty, RubyObjPtrTy, NULL)); - + // A C-level block. We allocate on the stack the literal + // structure following the ABI, initialize it then pass + // a pointer to it. + Value *block_lit = new AllocaInst(BlockLiteralTy, "", bb); Value *args[] = { - new LoadInst(proc_gvar, "", bb), - ConstantInt::get(Int32Ty, argc), - argv + block_lit, + funcptr }; - Value *ret_val = compile_protected_call(proc_call_f, args, - args + 3); - - if (ret_type != VoidTy) { - GetFirstType(type + 1, buf, buf_len); - ret_val = compile_conversion_to_c(buf, ret_val, - new AllocaInst(ret_type, "", bb)); - ReturnInst::Create(context, ret_val, bb); - } - else { - ReturnInst::Create(context, bb); - } - - bb = oldbb; - free(buf); - return new BitCastInst(f, PtrTy, "", bb); + CallInst::Create(initBlockFunc, args, args + 2, "", bb); + return block_lit; } break; @@ -5769,7 +5811,7 @@ } break; - case _MR_C_FPTR_B: + case _MR_C_LAMBDA_B: return PtrTy; case _C_STRUCT_B: Modified: MacRuby/trunk/compiler.h =================================================================== --- MacRuby/trunk/compiler.h 2010-09-04 01:55:20 UTC (rev 4494) +++ MacRuby/trunk/compiler.h 2010-09-08 00:53:10 UTC (rev 4495) @@ -254,6 +254,7 @@ Function *rvalToDoubleFunc; Function *rvalToSelFunc; Function *rvalToCharPtrFunc; + Function *initBlockFunc; Constant *zeroVal; Constant *oneVal; @@ -283,6 +284,7 @@ const Type *IntTy; const PointerType *Int32PtrTy; const Type *BitTy; + const Type *BlockLiteralTy; unsigned dbg_mdkind; @@ -422,6 +424,8 @@ void compile_set_struct(Value *rcv, int field, Value *val); Value *compile_xmalloc(size_t len); + Value *compile_lambda_to_funcptr(const char *type, Value *val, + Value *slot, bool is_block); Value *compile_conversion_to_c(const char *type, Value *val, Value *slot); Value *compile_conversion_to_ruby(const char *type, Modified: MacRuby/trunk/kernel.c =================================================================== --- MacRuby/trunk/kernel.c 2010-09-04 01:55:20 UTC (rev 4494) +++ MacRuby/trunk/kernel.c 2010-09-08 00:53:10 UTC (rev 4495) @@ -1012,3 +1012,34 @@ } return val; } + +// Support for C-level blocks. +// Following the ABI specifications as documented in the +// BlockImplementation.txt file of LLVM. + +struct ruby_block_descriptor { + unsigned long int reserved; + unsigned long int block_size; +}; + +struct ruby_block_literal { + void *isa; + int flags; + int reserved; + void *imp; + struct ruby_block_descriptor *descriptor; +}; + +static struct ruby_block_descriptor ruby_block_descriptor_value = { + 0, sizeof(struct ruby_block_literal) +}; + +PRIMITIVE void +vm_init_c_block(struct ruby_block_literal *b, void *imp) +{ + b->isa = &_NSConcreteStackBlock; + b->flags = (1 << 29); + b->reserved = 0; + b->imp = imp; + b->descriptor = &ruby_block_descriptor_value; +} Modified: MacRuby/trunk/objc.h =================================================================== --- MacRuby/trunk/objc.h 2010-09-04 01:55:20 UTC (rev 4494) +++ MacRuby/trunk/objc.h 2010-09-08 00:53:10 UTC (rev 4495) @@ -160,9 +160,9 @@ case _C_UNION_B: return type + SubtypeUntil (type, _C_UNION_E) + 1; - /* Function pointers */ - case _MR_C_FPTR_B: - return type + SubtypeUntil (type, _MR_C_FPTR_E) + 1; + /* lambdas */ + case _MR_C_LAMBDA_B: + return type + SubtypeUntil (type, _MR_C_LAMBDA_E) + 1; /* basic types */ default: Modified: MacRuby/trunk/spec/macruby/language/objc_method_spec.rb =================================================================== --- MacRuby/trunk/spec/macruby/language/objc_method_spec.rb 2010-09-04 01:55:20 UTC (rev 4494) +++ MacRuby/trunk/spec/macruby/language/objc_method_spec.rb 2010-09-08 00:53:10 UTC (rev 4495) @@ -744,7 +744,7 @@ end describe "A Proc object" do - it "can be used when a BridgeSupport Obj-C method takes a function pointer as an argument" do + it "can be used when an Objective-C method takes a function pointer as an argument" do framework 'Foundation' array = [1, 42, 6, 2, 3] @@ -760,7 +760,7 @@ lambda { array.sortedArrayUsingFunction(too_many_args_proc, context:nil) }.should raise_error(ArgumentError) end - it "can be used when a BridgeSupport C function takes a function pointer as an argument" do + it "can be used when a C function takes a function pointer as an argument" do functionMultiplicatingByTwoViaFctPtr(42, Proc.new { |x| x * 2 }).should == 84 functionMultiplicatingByTwoViaFctPtr(42, ->(x) { x * 2 }).should == 84 @@ -769,6 +769,16 @@ lambda { functionMultiplicatingByTwoViaFctPtr(42, Proc.new { 1 }) }.should raise_error(ArgumentError) lambda { functionMultiplicatingByTwoViaFctPtr(42, Proc.new { |x, y| x * y }) }.should raise_error(ArgumentError) end + + it "can be used when an Objective-C method takes a Block as argument" do + ary = ['zero', 'one', 'two', 'three', 'four'] + res = [] + ary.enumerateObjectsUsingBlock(Proc.new { |obj, idx, stop| + res << obj + stop.assign(true) if idx == 2 + }) + res.should == ['zero', 'one', 'two'] + end end describe "Ignored Obj-C selectors" do