ruby-changes:14691
From: tenderlove <ko1@a...>
Date: Wed, 3 Feb 2010 10:25:17 +0900 (JST)
Subject: [ruby-changes:14691] Ruby:r26545 (trunk): Wed Feb 3 10:12:09 2010 Aaron Patterson <tenderlove@r...>
tenderlove 2010-02-03 10:23:48 +0900 (Wed, 03 Feb 2010) New Revision: 26545 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=26545 Log: Wed Feb 3 10:12:09 2010 Aaron Patterson <tenderlove@r...> * ext/dl/function.c: DL::Function now uses libffi * ext/dl/cfunc.c (rb_dl_set_last_error): set to non static so errors can be exposed. * ext/dl/closure.c: DL::Closure will now be used in place of ext/dl/callback/*. * ext/dl/dl.c: legacy callbacks removed in favor of libffi * ext/dl/dl_converions.(c,h): used for converting ruby types to FFI types. * ext/dl/callback/*: replaced by libffi callbacks. * ext/dl/lib/dl/callback.rb: Converting internal callbacks to use DL::Closure * ext/dl/lib/dl/closure.rb: Ruby parts of the new DL::Closure object * ext/dl/lib/dl/import.rb: More conversion to use DL::Closure object * ext/dl/lib/dl/value.rb (ruby2ffi): adding private method for DL::CPtr to ffi value conversion. Added files: trunk/ext/dl/closure.c trunk/ext/dl/dl_conversions.c trunk/ext/dl/dl_conversions.h trunk/ext/dl/function.c trunk/ext/dl/lib/dl/closure.rb trunk/test/dl/test_closure.rb Removed files: trunk/ext/dl/callback/depend trunk/ext/dl/callback/extconf.rb trunk/ext/dl/callback/mkcallback.rb Modified files: trunk/ChangeLog trunk/ext/dl/cfunc.c trunk/ext/dl/dl.c trunk/ext/dl/dl.h trunk/ext/dl/extconf.rb trunk/ext/dl/lib/dl/callback.rb trunk/ext/dl/lib/dl/func.rb trunk/ext/dl/lib/dl/import.rb trunk/ext/dl/lib/dl/value.rb trunk/test/dl/test_base.rb trunk/test/dl/test_dl2.rb trunk/test/dl/test_func.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 26544) +++ ChangeLog (revision 26545) @@ -1,3 +1,30 @@ +Wed Feb 3 10:12:09 2010 Aaron Patterson <tenderlove@r...> + + * ext/dl/function.c: DL::Function now uses libffi + + * ext/dl/cfunc.c (rb_dl_set_last_error): set to non static so errors + can be exposed. + + * ext/dl/closure.c: DL::Closure will now be used in place of + ext/dl/callback/*. + + * ext/dl/dl.c: legacy callbacks removed in favor of libffi + + * ext/dl/dl_converions.(c,h): used for converting ruby types to FFI + types. + + * ext/dl/callback/*: replaced by libffi callbacks. + + * ext/dl/lib/dl/callback.rb: Converting internal callbacks to use + DL::Closure + + * ext/dl/lib/dl/closure.rb: Ruby parts of the new DL::Closure object + + * ext/dl/lib/dl/import.rb: More conversion to use DL::Closure object + + * ext/dl/lib/dl/value.rb (ruby2ffi): adding private method for + DL::CPtr to ffi value conversion. + Tue Feb 2 18:15:12 2010 Nobuyoshi Nakada <nobu@r...> * ext/socket/socket.c: turn on do_not_reverse_lookup by default, Index: ext/dl/cfunc.c =================================================================== --- ext/dl/cfunc.c (revision 26544) +++ ext/dl/cfunc.c (revision 26545) @@ -16,7 +16,7 @@ return rb_thread_local_aref(rb_thread_current(), id_last_error); } -static VALUE +VALUE rb_dl_set_last_error(VALUE self, VALUE val) { rb_thread_local_aset(rb_thread_current(), id_last_error, val); @@ -33,7 +33,7 @@ return rb_thread_local_aref(rb_thread_current(), id_win32_last_error); } -static VALUE +VALUE rb_dl_set_win32_last_error(VALUE self, VALUE val) { rb_thread_local_aset(rb_thread_current(), id_win32_last_error, val); Index: ext/dl/dl.c =================================================================== --- ext/dl/dl.c (revision 26544) +++ ext/dl/dl.c (revision 26545) @@ -77,19 +77,6 @@ return PTR2NUM((void*)val); } -static void -rb_dl_init_callbacks(VALUE dl) -{ - static const char cb[] = "dl/callback.so"; - - rb_autoload(dl, rb_intern_const("CdeclCallbackAddrs"), cb); - rb_autoload(dl, rb_intern_const("CdeclCallbackProcs"), cb); -#ifdef FUNC_STDCALL - rb_autoload(dl, rb_intern_const("StdcallCallbackAddrs"), cb); - rb_autoload(dl, rb_intern_const("StdcallCallbackProcs"), cb); -#endif -} - void Init_dl(void) { @@ -107,8 +94,6 @@ rb_define_const(rb_mDL, "MAX_CALLBACK", INT2NUM(MAX_CALLBACK)); rb_define_const(rb_mDL, "DLSTACK_SIZE", INT2NUM(DLSTACK_SIZE)); - rb_dl_init_callbacks(rb_mDL); - rb_define_const(rb_mDL, "RTLD_GLOBAL", INT2NUM(RTLD_GLOBAL)); rb_define_const(rb_mDL, "RTLD_LAZY", INT2NUM(RTLD_LAZY)); rb_define_const(rb_mDL, "RTLD_NOW", INT2NUM(RTLD_NOW)); @@ -162,4 +147,6 @@ Init_dlhandle(); Init_dlcfunc(); Init_dlptr(); + Init_dlfunction(); + Init_dlclosure(); } Index: ext/dl/dl.h =================================================================== --- ext/dl/dl.h (revision 26544) +++ ext/dl/dl.h (revision 26545) @@ -3,6 +3,12 @@ #include <ruby.h> +#ifdef USE_HEADER_HACKS +#include <ffi/ffi.h> +#else +#include <ffi.h> +#endif + #if !defined(FUNC_CDECL) # define FUNC_CDECL(x) x #endif @@ -221,4 +227,9 @@ VALUE rb_dlptr_new2(VALUE klass, void *ptr, long size, freefunc_t func); VALUE rb_dlptr_malloc(long size, freefunc_t func); +VALUE rb_dl_set_last_error(VALUE self, VALUE val); +#if defined(HAVE_WINDOWS_H) +VALUE rb_dl_set_win32_last_error(VALUE self, VALUE val); #endif + +#endif Index: ext/dl/function.c =================================================================== --- ext/dl/function.c (revision 0) +++ ext/dl/function.c (revision 26545) @@ -0,0 +1,233 @@ +/* -*- C -*- + * $Id$ + */ + +#include <ruby.h> +#include <errno.h> +#include "dl.h" +#include <dl_conversions.h> + +VALUE rb_cDLFunction; + +typedef union +{ + unsigned char uchar; // ffi_type_uchar + signed char schar; // ffi_type_schar + unsigned short ushort; // ffi_type_sshort + signed short sshort; // ffi_type_ushort + unsigned int uint; // ffi_type_uint + signed int sint; // ffi_type_sint + unsigned long ulong; // ffi_type_ulong + signed long slong; // ffi_type_slong + float ffloat; // ffi_type_float + double ddouble; // ffi_type_double +#if HAVE_LONG_LONG + unsigned LONG_LONG long_long; // ffi_type_uint64 +#endif + void * pointer; // ffi_type_pointer +} dl_generic; + +static void +dlfunction_free(ffi_cif *ptr) +{ + if(ptr->arg_types) xfree(ptr->arg_types); + xfree(ptr); +} + +static size_t +dlfunction_memsize(ffi_cif *ptr) +{ + size_t size = 0; + if(ptr) { + size += sizeof(*ptr); + size += ffi_raw_size(ptr); + } + return size; +} + +const rb_data_type_t dlfunction_data_type = { + "dl/function", + 0, dlfunction_free, dlfunction_memsize, +}; + +static VALUE +rb_dlfunc_allocate(VALUE klass) +{ + ffi_cif * cif; + + return TypedData_Make_Struct(klass, ffi_cif, &dlfunction_data_type, cif); +} + +static VALUE +rb_dlfunction_native_init(VALUE self, VALUE args, VALUE ret_type, VALUE abi) +{ + ffi_cif * cif; + ffi_type **arg_types; + + TypedData_Get_Struct(self, ffi_cif, &dlfunction_data_type, cif); + + arg_types = xcalloc(RARRAY_LEN(args) + 1, sizeof(ffi_type *)); + + int i; + for(i = 0; i < RARRAY_LEN(args); i++) { + int type = NUM2INT(RARRAY_PTR(args)[i]); + arg_types[i] = DL2FFI_TYPE(type); + } + arg_types[RARRAY_LEN(args)] = NULL; + + ffi_status result = ffi_prep_cif( + cif, + NUM2INT(abi), + RARRAY_LEN(args), + DL2FFI_TYPE(NUM2INT(ret_type)), + arg_types); + + if(result) + rb_raise(rb_eRuntimeError, "error creating CIF %d", result); + + return self; +} + +static void +dl2generic(int dl_type, VALUE src, dl_generic * dst) +{ + int signed_p = 1; + + if(dl_type < 0) { + dl_type = -1 * dl_type; + signed_p = 0; + } + + switch(dl_type) { + case DLTYPE_VOID: + break; + case DLTYPE_VOIDP: + dst->pointer = NUM2PTR(rb_Integer(src)); + break; + case DLTYPE_CHAR: + case DLTYPE_SHORT: + case DLTYPE_INT: + dst->sint = NUM2INT(src); + break; + case DLTYPE_LONG: + if(signed_p) + dst->slong = NUM2LONG(src); + else + dst->ulong = NUM2LONG(src); + break; +#if HAVE_LONG_LONG + case DLTYPE_LONG_LONG: + dst->long_long = rb_big2ull(src); + break; +#endif + case DLTYPE_FLOAT: + dst->ffloat = NUM2DBL(src); + break; + case DLTYPE_DOUBLE: + dst->ddouble = NUM2DBL(src); + break; + default: + rb_raise(rb_eRuntimeError, "unknown type %d", dl_type); + } +} + +static VALUE +unwrap_ffi(VALUE rettype, dl_generic retval) +{ + int signed_p = 1; + int dl_type = NUM2INT(rettype); + + if(dl_type < 0) { + dl_type = -1 * dl_type; + signed_p = 0; + } + + switch(dl_type) { + case DLTYPE_VOID: + return Qnil; + case DLTYPE_VOIDP: + return rb_dlptr_new((void *)retval.pointer, 0, NULL); + case DLTYPE_CHAR: + case DLTYPE_SHORT: + case DLTYPE_INT: + return INT2NUM(retval.sint); + case DLTYPE_LONG: + if(signed_p) return LONG2NUM(retval.slong); + return LONG2NUM(retval.ulong); +#if HAVE_LONG_LONG + case DLTYPE_LONG_LONG: + return rb_ll2inum(retval.long_long); + break; +#endif + case DLTYPE_FLOAT: + return rb_float_new(retval.ffloat); + case DLTYPE_DOUBLE: + return rb_float_new(retval.ddouble); + default: + rb_raise(rb_eRuntimeError, "unknown type %d", dl_type); + } +} + +static VALUE +rb_dlfunction_call(int argc, VALUE argv[], VALUE self) +{ + ffi_cif * cif; + dl_generic retval; + dl_generic *generic_args; + void **values; + void * fun_ptr; + + TypedData_Get_Struct(self, ffi_cif, &dlfunction_data_type, cif); + + values = xcalloc((size_t)argc + 1, (size_t)sizeof(void *)); + generic_args = xcalloc((size_t)argc, (size_t)sizeof(dl_generic)); + + VALUE cfunc = rb_iv_get(self, "@cfunc"); + VALUE types = rb_iv_get(self, "@args"); + + int i; + for(i = 0; i < argc; i++) { + VALUE dl_type = RARRAY_PTR(types)[i]; + VALUE src = rb_funcall(self, + rb_intern("ruby2ffi"), + 2, + argv[i], + dl_type + ); + + dl2generic(NUM2INT(dl_type), src, &generic_args[i]); + values[i] = (void *)&generic_args[i]; + } + values[argc] = NULL; + + ffi_call(cif, NUM2PTR(rb_Integer(cfunc)), &retval, values); + + rb_dl_set_last_error(self, INT2NUM(errno)); +#if defined(HAVE_WINDOWS_H) + rb_dl_set_win32_last_error(self, INT2NUM(GetLastError())); +#endif + + xfree(values); + xfree(generic_args); + + return unwrap_ffi(rb_funcall(cfunc, rb_intern("ctype"), 0), retval); +} + +void +Init_dlfunction(void) +{ + rb_cDLFunction = rb_define_class_under(rb_mDL, "Function", rb_cObject); + + rb_define_const(rb_cDLFunction, "DEFAULT", INT2NUM(FFI_DEFAULT_ABI)); + +#ifdef FFI_STDCALL + rb_define_const(rb_cDLFunction, "STDCALL", INT2NUM(FFI_STDCALL)); +#endif + + rb_define_alloc_func(rb_cDLFunction, rb_dlfunc_allocate); + + rb_define_private_method(rb_cDLFunction, "native_call", rb_dlfunction_call, -1); + + rb_define_private_method(rb_cDLFunction, "native_init", rb_dlfunction_native_init, 3); +} +/* vim: set noet sw=4 sts=4 */ Index: ext/dl/lib/dl/import.rb =================================================================== --- ext/dl/lib/dl/import.rb (revision 26544) +++ ext/dl/lib/dl/import.rb (revision 26545) @@ -1,4 +1,5 @@ require 'dl' +require 'dl/closure' require 'dl/func.rb' require 'dl/struct.rb' require 'dl/cparser.rb' @@ -211,9 +212,11 @@ end def bind_function(name, ctype, argtype, call_type = nil, &block) - f = Function.new(CFunc.new(0, ctype, name, call_type || :cdecl), argtype) - f.bind(&block) - f + closure = Class.new(DL::Closure) { + define_method(:call, block) + }.new(ctype, argtype) + + Function.new(closure, argtype) end def create_temp_function(name, ctype, argtype, call_type = nil) Index: ext/dl/lib/dl/func.rb =================================================================== --- ext/dl/lib/dl/func.rb (revision 26544) +++ ext/dl/lib/dl/func.rb (revision 26545) @@ -1,4 +1,5 @@ require 'dl' +require 'dl/closure' require 'dl/callback' require 'dl/stack' require 'dl/value' @@ -9,18 +10,17 @@ include DL include ValueUtil - def initialize(cfunc, argtypes, &proc) - @cfunc = cfunc - @stack = Stack.new(argtypes.collect{|ty| ty.abs}) - if( @cfunc.ctype < 0 ) - @cfunc.ctype = @cfunc.ctype.abs - @unsigned = true + def initialize cfunc, argtypes, abi = DEFAULT, &block + if block_given? + @cfunc = Class.new(DL::Closure) { + define_method(:call, block) + }.new(cfunc.ctype, argtypes) else - @unsigned = false + @cfunc = cfunc end - if( proc ) - bind(&proc) - end + + @args = argtypes + native_init(@args.reject { |x| x == TYPE_VOID }, cfunc.ctype, abi) end def to_i() @@ -32,11 +32,10 @@ end def call(*args, &block) - funcs = [] - args = wrap_args(args, @stack.types, funcs, &block) - r = @cfunc.call(@stack.pack(args)) - funcs.each{|f| f.unbind_at_call()} - return wrap_result(r) + if block_given? + args.find { |a| DL::Function === a }.bind_at_call(&block) + end + native_call(*args) end def wrap_result(r) @@ -52,33 +51,16 @@ end def bind(&block) - if( !block ) - raise(RuntimeError, "block must be given.") - end - if( @cfunc.ptr == 0 ) - cb = Proc.new{|*args| - ary = @stack.unpack(args) - @stack.types.each_with_index{|ty, idx| - case ty - when TYPE_VOIDP - ary[idx] = CPtr.new(ary[idx]) - end - } - r = block.call(*ary) - wrap_arg(r, @cfunc.ctype, []) - } - case @cfunc.calltype - when :cdecl - @cfunc.ptr = set_cdecl_callback(@cfunc.ctype, @stack.size, &cb) - when :stdcall - @cfunc.ptr = set_stdcall_callback(@cfunc.ctype, @stack.size, &cb) - else - raise(RuntimeError, "unsupported calltype: #{@cfunc.calltype}") + @cfunc = Class.new(DL::Closure) { + def initialize ctype, args, block + super(ctype, args) + @block = block end - if( @cfunc.ptr == 0 ) - raise(RuntimeException, "can't bind C function.") + + def call *args + @block.call(*args) end - end + }.new(@cfunc.ctype, @args, block) end def unbind() Index: ext/dl/lib/dl/callback.rb =================================================================== --- ext/dl/lib/dl/callback.rb (revision 26544) +++ ext/dl/lib/dl/callback.rb (revision 26545) @@ -1,26 +1,21 @@ require 'dl' +require 'dl/closure' require 'thread' module DL SEM = Mutex.new - def set_callback_internal(proc_entry, addr_entry, argc, ty, &cbp) + CdeclCallbackProcs = {} + CdeclCallbackAddrs = {} + + def set_callback_internal(proc_entry, addr_entry, argc, ty, abi = DL::Function::DEFAULT, &cbp) if( argc < 0 ) raise(ArgumentError, "arity should not be less than 0.") end - addr = nil - SEM.synchronize{ - ary = proc_entry[ty] - (0...MAX_CALLBACK).each{|n| - idx = (n * DLSTACK_SIZE) + argc - if( ary[idx].nil? ) - ary[idx] = cbp - addr = addr_entry[ty][idx] - break - end - } - } - addr + + closure = DL::Closure::BlockCaller.new(ty, [TYPE_VOIDP] * argc, abi, &cbp) + proc_entry[closure.to_i] = closure + closure.to_i end def set_cdecl_callback(ty, argc, &cbp) @@ -28,32 +23,14 @@ end def set_stdcall_callback(ty, argc, &cbp) - set_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, argc, ty, &cbp) + set_callback_internal(StdcallCallbackProcs, StdcallCallbackAddrs, argc, ty, DL::Function::STDCALL, &cbp) end def remove_callback_internal(proc_entry, addr_entry, addr, ctype = nil) - index = nil - if( ctype ) - addr_entry[ctype].each_with_index{|xaddr, idx| - if( xaddr == addr ) - index = idx - end - } - else - addr_entry.each{|ty,entry| - entry.each_with_index{|xaddr, idx| - if( xaddr == addr ) - index = idx - end - } - } - end - if( index and proc_entry[ctype][index] ) - proc_entry[ctype][index] = nil - return true - else - return false - end + addr = addr.to_i + return false unless proc_entry.key?(addr) + proc_entry.delete(addr) + true end def remove_cdecl_callback(addr, ctype = nil) Index: ext/dl/lib/dl/closure.rb =================================================================== --- ext/dl/lib/dl/closure.rb (revision 0) +++ ext/dl/lib/dl/closure.rb (revision 26545) @@ -0,0 +1,19 @@ +require 'dl' + +module DL + class Closure + attr_reader :ctype + attr_reader :args + + class BlockCaller < DL::Closure + def initialize ctype, args, abi = DL::Function::DEFAULT, &block + super(ctype, args, abi) + @block = block + end + + def call *args + @block.call(*args) + end + end + end +end Index: ext/dl/lib/dl/value.rb =================================================================== --- ext/dl/lib/dl/value.rb (revision 26544) +++ ext/dl/lib/dl/value.rb (revision 26545) @@ -36,16 +36,20 @@ end end - def wrap_args(args, tys, funcs, &block) - result = [] - tys ||= [] - args.each_with_index{|arg, idx| - result.push(wrap_arg(arg, tys[idx], funcs, &block)) - } - result + def ruby2ffi arg, type + return arg unless type == TYPE_VOIDP + case arg + when nil + 0 + when CPtr + arg.to_i + else + CPtr[arg].to_i + end end + private :ruby2ffi - def wrap_arg(arg, ty, funcs, &block) + def wrap_arg(arg, ty, funcs = [], &block) funcs ||= [] case arg when nil Index: ext/dl/dl_conversions.c =================================================================== --- ext/dl/dl_conversions.c (revision 0) +++ ext/dl/dl_conversions.c (revision 26545) @@ -0,0 +1,38 @@ +#include <dl_conversions.h> + +ffi_type * rb_dl_type_to_ffi_type(int dl_type) +{ + int signed_p = 1; + + if(dl_type < 0) { + dl_type = -1 * dl_type; + signed_p = 0; + } + + switch(dl_type) { + case DLTYPE_VOID: + return &ffi_type_void; + case DLTYPE_VOIDP: + return &ffi_type_pointer; + case DLTYPE_CHAR: + return signed_p ? &ffi_type_schar : &ffi_type_uchar; + case DLTYPE_SHORT: + return signed_p ? &ffi_type_sshort : &ffi_type_ushort; + case DLTYPE_INT: + return signed_p ? &ffi_type_sint : &ffi_type_uint; + case DLTYPE_LONG: + return signed_p ? &ffi_type_slong : &ffi_type_ulong; +#if HAVE_LONG_LONG + case DLTYPE_LONG_LONG: + return &ffi_type_uint64; + break; +#endif + case DLTYPE_FLOAT: + return &ffi_type_float; + case DLTYPE_DOUBLE: + return &ffi_type_double; + default: + rb_raise(rb_eRuntimeError, "unknown type %d", dl_type); + } + return &ffi_type_pointer; +} Index: ext/dl/extconf.rb =================================================================== --- ext/dl/extconf.rb (revision 26544) +++ ext/dl/extconf.rb (revision 26545) @@ -8,8 +8,30 @@ ["dl.h", "$(HDRDIR)"], ] +if pkg_config("libffi") + # libffi closure api must be switched depending on the version + if system("pkg-config --atleast-version=3.0.9 libffi") + $defs.push(format('-DUSE_NEW_CLOSURE_API')) + end +else + dir_config('ffi', '/usr/include', '/usr/lib') +end + +unless have_header('ffi.h') + if have_header('ffi/ffi.h') + $defs.push(format('-DUSE_HEADER_HACKS')) + else + abort "ffi is missing" + end +end + +unless have_library('ffi') + abort "ffi is missing" +end + check = true if( have_header("dlfcn.h") ) + have_library("dl") check &&= have_func("dlopen") check &&= have_func("dlclose") Index: ext/dl/callback/depend =================================================================== --- ext/dl/callback/depend (revision 26544) +++ ext/dl/callback/depend (revision 26545) @@ -1,15 +0,0 @@ -src: callback.c \ - callback-0.c callback-1.c callback-2.c \ - callback-3.c callback-4.c callback-5.c \ - callback-6.c callback-7.c callback-8.c - -$(OBJS): $(hdrdir)/ruby.h - -callback-0.c callback-1.c callback-2.c \ -callback-3.c callback-4.c callback-5.c \ -callback-6.c callback-7.c callback-8.c \ - : callback.c - -callback.c: $(srcdir)/mkcallback.rb $(srcdir)/../dl.h - @echo "generating callback.c" - @$(RUBY) $(srcdir)/mkcallback.rb -output=callback $(srcdir)/../dl.h Index: ext/dl/callback/mkcallback.rb =================================================================== --- ext/dl/callback/mkcallback.rb (revision 26544) +++ ext/dl/callback/mkcallback.rb (revision 26545) @@ -1,238 +0,0 @@ -#!ruby -s -$output ||= "callback" -$out = open("#{$output}.c", "w") - -$dl_h = ARGV[0] || "dl.h" - -# import DLSTACK_SIZE, DLSTACK_ARGS and so on -File.open($dl_h){|f| - pre = "" - f.each{|line| - line.chop! - if( line[-1] == ?\\ ) - line.chop! - line.concat(" ") - pre += line - next - end - if( pre.size > 0 ) - line = pre + line - pre = "" - end - case line - when /#define\s+DLSTACK_SIZE\s+\(?(\d+)\)?/ - DLSTACK_SIZE = $1.to_i - when /#define\s+DLSTACK_ARGS\s+(.+)/ - DLSTACK_ARGS = $1.to_i - when /#define\s+DLTYPE_([A-Z_]+)\s+\(?(\d+)\)?/ - eval("#{$1} = #{$2}") - when /#define\s+MAX_DLTYPE\s+\(?(\d+)\)?/ - MAX_DLTYPE = $1.to_i - when /#define\s+MAX_CALLBACK\s+\(?(\d+)\)?/ - MAX_CALLBACK = $1.to_i - end - } -} - -CDECL = "cdecl" -STDCALL = "stdcall" - -CALLTYPES = [CDECL, STDCALL] - -DLTYPE = { - VOID => { - :name => 'void', - :type => 'void', - :conv => nil, - }, - CHAR => { - :name => 'char', - :type => 'char', - :conv => 'NUM2CHR(%s)' - }, - SHORT => { - :name => 'short', - :type => 'short', - :conv => 'NUM2INT(%s)', - }, - INT => { - :name => 'int', - :type => 'int', - :conv => 'NUM2INT(%s)', - }, - LONG => { - :name => 'long', - :type => 'long', - :conv => 'NUM2LONG(%s)', - }, - LONG_LONG => { - :name => 'long_long', - :type => 'LONG_LONG', - :conv => 'NUM2LL(%s)', - }, - FLOAT => { - :name => 'float', - :type => 'float', - :conv => '(float)RFLOAT_VALUE(%s)', - }, - DOUBLE => { - :name => 'double', - :type => 'double', - :conv => 'RFLOAT_VALUE(%s)', - }, - VOIDP => { - :name => 'ptr', - :type => 'void *', - :conv => 'NUM2PTR(%s)', - }, -} - - -def func_name(ty, argc, n, calltype) - "rb_dl_callback_#{DLTYPE[ty][:name]}_#{argc}_#{n}_#{calltype}" -end - -$out << (<<EOS) -#include "ruby.h" - -VALUE rb_DLCdeclCallbackAddrs, rb_DLCdeclCallbackProcs; -#ifdef FUNC_STDCALL -VALUE rb_DLStdcallCallbackAddrs, rb_DLStdcallCallbackProcs; -#endif -/*static void *cdecl_callbacks[MAX_DLTYPE][MAX_CALLBACK];*/ -#ifdef FUNC_STDCALL -/*static void *stdcall_callbacks[MAX_DLTYPE][MAX_CALLBACK];*/ -#endif -ID rb_dl_cb_call; -EOS - -def foreach_proc_entry - for calltype in CALLTYPES - case calltype - when CDECL - proc_entry = "rb_DLCdeclCallbackProcs" - when STDCALL - proc_entry = "rb_DLStdcallCallbackProcs" - else - raise "unknown calltype: #{calltype}" - end - yield calltype, proc_entry - end -end - -def gencallback(ty, calltype, proc_entry, argc, n) - <<-EOS -#{calltype == STDCALL ? "\n#ifdef FUNC_STDCALL" : ""} -static #{DLTYPE[ty][:type]} -FUNC_#{calltype.upcase}(#{func_name(ty,argc,n,calltype)})(#{(0...argc).collect{|i| "DLSTACK_TYPE stack" + i.to_s}.join(", ")}) -{ - VALUE ret, cb#{argc > 0 ? ", args[#{argc}]" : ""}; -#{ - (0...argc).collect{|i| - " args[%d] = LONG2NUM(stack%d);" % [i,i] - }.join("\n") -} - cb = rb_ary_entry(rb_ary_entry(#{proc_entry}, #{ty}), #{(n * DLSTACK_SIZE) + argc}); - ret = rb_funcall2(cb, rb_dl_cb_call, #{argc}, #{argc > 0 ? 'args' : 'NULL'}); - return #{DLTYPE[ty][:conv] ? DLTYPE[ty][:conv] % "ret" : ""}; -} -#{calltype == STDCALL ? "#endif\n" : ""} - EOS -end - -def gen_push_proc_ary(ty, aryname) - sprintf(" rb_ary_push(#{aryname}, rb_ary_new3(%d,%s));", - MAX_CALLBACK * DLSTACK_SIZE, - (0...MAX_CALLBACK).collect{ - (0...DLSTACK_SIZE).collect{ "Qnil" }.join(",") - }.join(",")) -end - -def gen_push_addr_ary(ty, aryname, calltype) - sprintf(" rb_ary_push(#{aryname}, rb_ary_new3(%d,%s));", - MAX_CALLBACK * DLSTACK_SIZE, - (0...MAX_CALLBACK).collect{|i| - (0...DLSTACK_SIZE).collect{|argc| - "PTR2NUM(%s)" % func_name(ty,argc,i,calltype) - }.join(",") - }.join(",")) -end - -def gen_callback_file(ty) - filename = "#{$output}-#{ty}.c" - initname = "rb_dl_init_callbacks_#{ty}" - body = <<-EOS -#include "dl.h" - -extern VALUE rb_DLCdeclCallbackAddrs, rb_DLCdeclCallbackProcs; -#ifdef FUNC_STDCALL -extern VALUE rb_DLStdcallCallbackAddrs, rb_DLStdcallCallbackProcs; -#endif -extern ID rb_dl_cb_call; - EOS - yield body - body << <<-EOS -void -#{initname}() -{ -#{gen_push_proc_ary(ty, "rb_DLCdeclCallbackProcs")} -#{gen_push_addr_ary(ty, "rb_DLCdeclCallbackAddrs", CDECL)} -#ifdef FUNC_STDCALL -#{gen_push_proc_ary(ty, "rb_DLStdcallCallbackProcs")} -#{gen_push_addr_ary(ty, "rb_DLStdcallCallbackAddrs", STDCALL)} -#endif -} - EOS - [filename, initname, body] -end - -callbacks = [] -for ty in 0...MAX_DLTYPE - filename, initname, body = gen_callback_file(ty) {|f| - foreach_proc_entry do |calltype, proc_entry| - for argc in 0...DLSTACK_SIZE - for n in 0...MAX_CALLBACK - f << gencallback(ty, calltype, proc_entry, argc, n) - end - end - end - } - $out << "void #{initname}();\n" - callbacks << [filename, body] -end - -$out << (<<EOS) -void -Init_callback(void) -{ - VALUE tmp; - VALUE rb_mDL = rb_path2class("DL"); - - rb_dl_cb_call = rb_intern("call"); - - tmp = rb_DLCdeclCallbackProcs = rb_ary_new(); - rb_define_const(rb_mDL, "CdeclCallbackProcs", tmp); - - tmp = rb_DLCdeclCallbackAddrs = rb_ary_new(); - rb_define_const(rb_mDL, "CdeclCallbackAddrs", tmp); - -#ifdef FUNC_STDCALL - tmp = rb_DLStdcallCallbackProcs = rb_ary_new(); - rb_define_const(rb_mDL, "StdcallCallbackProcs", tmp); - - tmp = rb_DLStdcallCallbackAddrs = rb_ary_new(); - rb_define_const(rb_mDL, "StdcallCallbackAddrs", tmp); -#endif - -#{ - (0...MAX_DLTYPE).collect{|ty| - " rb_dl_init_callbacks_#{ty}();" - }.join("\n") -} -} -EOS -$out.close - -for filename, body in callbacks - open(filename, "wb") {|f| f.puts body} -end Index: ext/dl/callback/extconf.rb =================================================================== --- ext/dl/callback/extconf.rb (revision 26544) +++ ext/dl/callback/extconf.rb (revision 26545) @@ -1,14 +0,0 @@ -require 'mkmf' - -if compiled?("dl") - callbacks = (0..8).map{|i| "callback-#{i}"}.unshift("callback") - callback_srcs = callbacks.map{|basename| "#{basename}.c"} - callback_objs = callbacks.map{|basename| "#{basename}.o"} - - $distcleanfiles << '$(SRCS)' - $srcs = callback_srcs - $objs = callback_objs - $INCFLAGS << " -I$(srcdir)/.." - - create_makefile("dl/callback") -end Index: ext/dl/closure.c =================================================================== --- ext/dl/closure.c (revision 0) +++ ext/dl/closure.c (revision 26545) @@ -0,0 +1,228 @@ +/* -*- C -*- + * $Id$ + */ + +#include <ruby.h> +#include "dl.h" +#include <sys/mman.h> +#include <dl_conversions.h> + +VALUE rb_cDLClosure; + +typedef struct { + void * code; + ffi_closure *pcl; + ffi_cif * cif; + int argc; + ffi_type **argv; +} dl_closure; + +static void +dlclosure_free(void * ptr) +{ + dl_closure * cls = (dl_closure *)ptr; +#ifdef USE_NEW_CLOSURE_API + ffi_closure_free(cls->pcl); +#else + munmap(cls->pcl, sizeof(cls->pcl)); +#endif + xfree(cls->cif); + if(cls->argv) xfree(cls->argv); + xfree(cls); +} + +static size_t +dlclosure_memsize(const void * ptr) +{ + dl_closure * cls = (dl_closure *)ptr; + + size_t size = 0; + if(ptr) { + size += sizeof(*cls); + size += ffi_raw_size(cls->cif); + size += sizeof(*cls->argv); + size += sizeof(ffi_closure); + } + return size; +} + +const rb_data_type_t dlclosure_data_type = { + "dl/closure", + 0, dlclosure_free, dlclosure_memsize, +}; + +void +dlc_callback(ffi_cif *cif, void *resp, void **args, void *ctx) +{ + VALUE self = (VALUE)ctx; + VALUE rbargs = rb_iv_get(self, "@args"); + VALUE ctype = rb_iv_get(self, "@ctype"); + int argc = RARRAY_LEN(rbargs); + VALUE *params = xcalloc(argc, sizeof(VALUE *)); + + int i; + for(i = 0; i < argc; i++) { + int dl_type = NUM2INT(RARRAY_PTR(rbargs)[i]); + switch(dl_type) { + case DLTYPE_VOID: + argc = 0; + break; + case DLTYPE_INT: + params[i] = INT2NUM(*(int *)args[i]); + break; + case DLTYPE_VOIDP: + params[i] = rb_dlptr_new(*(void **)args[i], 0, NULL); + break; + case DLTYPE_LONG: + params[i] = LONG2NUM(*(long *)args[i]); + break; + case DLTYPE_CHAR: + params[i] = INT2NUM(*(char *)args[i]); + break; + case DLTYPE_DOUBLE: + params[i] = rb_float_new(*(double *)args[i]); + break; + case DLTYPE_FLOAT: + params[i] = rb_float_new(*(float *)args[i]); + break; +#if HAVE_LONG_LONG + case DLTYPE_LONG_LONG: + params[i] = rb_ull2inum(*(unsigned LONG_LONG *)args[i]); + break; +#endif + default: + rb_raise(rb_eRuntimeError, "closure args: %d", dl_type); + } + } + + VALUE ret = rb_funcall2(self, rb_intern("call"), argc, params); + + int dl_type = NUM2INT(ctype); + switch(dl_type) { + case DLTYPE_VOID: + break; + case DLTYPE_LONG: + *(long *)resp = NUM2LONG(ret); + break; + case DLTYPE_CHAR: + *(char *)resp = NUM2INT(ret); + break; + case DLTYPE_VOIDP: + *(void **)resp = NUM2PTR(ret); + break; + case DLTYPE_INT: + *(int *)resp = NUM2INT(ret); + break; + case DLTYPE_DOUBLE: + *(double *)resp = NUM2DBL(ret); + break; + case DLTYPE_FLOAT: + *(float *)resp = NUM2DBL(ret); + break; +#if HAVE_LONG_LONG + case DLTYPE_LONG_LONG: + *(unsigned LONG_LONG *)resp = rb_big2ull(ret); + break; +#endif + default: + rb_raise(rb_eRuntimeError, "closure retval: %d", dl_type); + } + xfree(params); +} + +static VALUE +rb_dlclosure_allocate(VALUE klass) +{ + dl_closure * closure; + + VALUE i = TypedData_Make_Struct(klass, dl_closure, + &dlclosure_data_type, closure); + +#ifdef USE_NEW_CLOSURE_API + closure->pcl = ffi_closure_alloc(sizeof(ffi_closure), &closure->code); +#else + closure->pcl = mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); +#endif + closure->cif = xmalloc(sizeof(ffi_cif)); + + return i; +} + +static VALUE +rb_dlclosure_init(int rbargc, VALUE argv[], VALUE self) +{ + VALUE ret; + VALUE args; + VALUE abi; + + dl_closure * cl; + ffi_cif * cif; + ffi_closure *pcl; + + if(2 == rb_scan_args(rbargc, argv, "21", &ret, &args, &abi)) + abi = INT2NUM(FFI_DEFAULT_ABI); + + int i; + int argc = RARRAY_LEN(args); + + TypedData_Get_Struct(self, dl_closure, &dlclosure_data_type, cl); + + cl->argv = (ffi_type **)xcalloc(argc + 1, sizeof(ffi_type *)); + + for(i = 0; i < argc; i++) { + int dltype = NUM2INT(RARRAY_PTR(args)[i]); + cl->argv[i] = DL2FFI_TYPE(dltype); + } + cl->argv[argc] = NULL; + + rb_iv_set(self, "@ctype", ret); + rb_iv_set(self, "@args", args); + + cif = cl->cif; + pcl = cl->pcl; + + ffi_status result = ffi_prep_cif(cif, NUM2INT(abi), argc, + DL2FFI_TYPE(NUM2INT(ret)), + cl->argv); + + if(FFI_OK != result) + rb_raise(rb_eRuntimeError, "error prepping CIF %d", result); + +#ifdef USE_NEW_CLOSURE_API + result = ffi_prep_closure_loc(pcl, cif, dlc_callback, + (void *)self, cl->code); +#else + result = ffi_prep_closure(pcl, cif, dlc_callback, (void *)self); + cl->code = (void *)pcl; + mprotect(pcl, sizeof(pcl), PROT_READ | PROT_EXEC); +#endif + + if(FFI_OK != result) + rb_raise(rb_eRuntimeError, "error prepping closure %d", result); + + return self; +} + +static VALUE +rb_dlclosure_to_i(VALUE self) +{ + dl_closure * cl; + + TypedData_Get_Struct(self, dl_closure, &dlclosure_data_type, cl); + + void * code = cl->code; + + return PTR2NUM(code); +} + +void +Init_dlclosure(void) +{ + rb_cDLClosure = rb_define_class_under(rb_mDL, "Closure", rb_cObject); + rb_define_alloc_func(rb_cDLClosure, rb_dlclosure_allocate); + + rb_define_method(rb_cDLClosure, "initialize", rb_dlclosure_init, -1); + rb_define_method(rb_cDLClosure, "to_i", rb_dlclosure_to_i, 0); +} +/* vim: set noet sw=4 sts=4 */ Index: ext/dl/dl_conversions.h =================================================================== --- ext/dl/dl_conversions.h (revision 0) +++ ext/dl/dl_conversions.h (revision 26545) @@ -0,0 +1,10 @@ +#ifndef DL_CONVERSIONS +#define DL_CONVERSIONS + +#include <dl.h> + +#define DL2FFI_TYPE(a) rb_dl_type_to_ffi_type(a) + +ffi_type * rb_dl_type_to_ffi_type(int dl_type); + +#endif Index: test/dl/test_dl2.rb =================================================================== --- test/dl/test_dl2.rb (revision 26544) +++ test/dl/test_dl2.rb (revision 26545) @@ -88,14 +88,16 @@ assert_in_delta(-0.1, x) end - def test_sin() + def test_sin pi_2 = Math::PI/2 - cfunc = CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin') - x = cfunc.call([pi_2].pack("d").unpack("l!*")) + cfunc = Function.new(CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin'), + [TYPE_DOUBLE]) + x = cfunc.call(pi_2) assert_equal(Math.sin(pi_2), x) - cfunc = CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin') - x = cfunc.call([-pi_2].pack("d").unpack("l!*")) + cfunc = Function.new(CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin'), + [TYPE_DOUBLE]) + x = cfunc.call(-pi_2) assert_equal(Math.sin(-pi_2), x) end Index: test/dl/test_base.rb =================================================================== --- test/dl/test_base.rb (revision 26544) +++ test/dl/test_base.rb (revision 26545) @@ -74,7 +74,7 @@ end def assert_zero(actual) - assert(actual == 0) + assert_equal(0, actual) end def assert_negative(actual) Index: test/dl/test_func.rb =================================================================== --- test/dl/test_func.rb (revision 26544) +++ test/dl/test_func.rb (revision 26545) @@ -15,6 +15,24 @@ assert_equal cfunc.to_i, f.to_i end + def test_random + f = Function.new(CFunc.new(@libc['srand'], TYPE_VOID, 'srand'), + [-TYPE_LONG]) + assert_nil f.call(10) + end + + def test_sinf + f = Function.new(CFunc.new(@libm['sinf'], TYPE_FLOAT, 'sinf'), + [TYPE_FLOAT]) + assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001 + end + + def test_sin + f = Function.new(CFunc.new(@libm['sin'], TYPE_DOUBLE, 'sin'), + [TYPE_DOUBLE]) + assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001 + end + def test_strcpy() f = Function.new(CFunc.new(@libc['strcpy'], TYPE_VOIDP, 'strcpy'), [TYPE_VOIDP, TYPE_VOIDP]) Index: test/dl/test_closure.rb =================================================================== --- test/dl/test_closure.rb (revision 0) +++ test/dl/test_closure.rb (revision 26545) @@ -0,0 +1,130 @@ +require_relative 'test_base' +require 'dl/func' +require 'dl/closure' + +module DL + class TestClosure < Test::Unit::TestCase + class Returner < DL::Closure + attr_accessor :called + attr_accessor :called_with + def call *args + @called = true + @called_with = args + a = args.first + DL::CPtr === a ? a.to_i : a + end + end + + if defined?(TYPE_LONG_LONG) + def test_long_long + type = TYPE_LONG_LONG + addr = Returner.new(type, [type]) do |num| + called = true + called_with = num + end + func = DL::Function.new(addr, [type]) + assert_equal(9223372036854775807, func.call(9223372036854775807)) + end + end + + def test_with_abi + called = false + addr = DL::Closure::BlockCaller.new( + TYPE_INT, + [TYPE_INT], + DL::Function::DEFAULT + ) do |num| + called = true + num + end + func = DL::Function.new(addr, [TYPE_INT]) + func.call(50) + assert called + end + + def test_block_caller + called = false + called_with = nil + addr = DL::Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |num| + called = true + called_with = num + end + func = DL::Function.new(addr, [TYPE_INT]) + func.call(50) + assert called, 'function was called' + assert_equal 50, called_with + end + + def test_multival + adder = Class.new(DL::Closure) { + def call a, b + a + b + end + }.new(TYPE_INT, [TYPE_INT, TYPE_INT]) + + assert_equal [TYPE_INT, TYPE_INT], adder.args + func = DL::Function.new(adder, adder.args) + assert_equal 70, func.call(50, 20) + end + + def test_call + closure = Class.new(DL::Closure) { + attr_accessor :called_with + def call num + @called_with = num + end + }.new(TYPE_INT, [TYPE_INT]) + + func = DL::Function.new(closure, [TYPE_INT]) + func.call(50) + + assert_equal 50, closure.called_with + end + + def test_return_value + closure = Returner.new(TYPE_INT, [TYPE_INT]) + + func = DL::Function.new(closure, [TYPE_INT]) + assert_equal 50, func.call(50) + end + + def test_float + closure = Returner.new(TYPE_FLOAT, [TYPE_FLOAT]) + func = DL::Function.new(closure, [TYPE_FLOAT]) + assert_equal 2.0, func.call(2.0) + end + + def test_char + closure = Returner.new(TYPE_CHAR, [TYPE_CHAR]) + func = DL::Function.new(closure, [TYPE_CHAR]) + assert_equal 60, func.call(60) + end + + def test_long + closure = Returner.new(TYPE_LONG, [TYPE_LONG]) + func = DL::Function.new(closure, [TYPE_LONG]) + assert_equal 60, func.call(60) + end + + def test_double + closure = Returner.new(TYPE_DOUBLE, [TYPE_DOUBLE]) + func = DL::Function.new(closure, [TYPE_DOUBLE]) + assert_equal 60, func.call(60) + end + + def test_voidp + closure = Returner.new(TYPE_VOIDP, [TYPE_VOIDP]) + func = DL::Function.new(closure, [TYPE_VOIDP]) + + voidp = CPtr['foo'] + assert_equal voidp, func.call(voidp) + end + + def test_void + closure = Returner.new(TYPE_VOID, [TYPE_VOID]) + func = DL::Function.new(closure, [TYPE_VOID]) + func.call() + assert closure.called + end + end +end -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/