diff --git a/ext/fiddle/closure.c b/ext/fiddle/closure.c index d526d870..5d936473 100644 --- a/ext/fiddle/closure.c +++ b/ext/fiddle/closure.c @@ -82,11 +82,8 @@ with_gvl_callback(void *ptr) int argc = RARRAY_LENINT(rbargs); VALUE params = rb_ary_tmp_new(argc); VALUE ret; - VALUE cPointer; int i, type; - cPointer = rb_const_get(mFiddle, rb_intern("Pointer")); - for (i = 0; i < argc; i++) { type = NUM2INT(RARRAY_AREF(rbargs, i)); switch (type) { @@ -101,7 +98,7 @@ with_gvl_callback(void *ptr) break; case TYPE_VOIDP: rb_ary_push(params, - rb_funcall(cPointer, rb_intern("[]"), 1, + rb_funcall(rb_cPointer, rb_intern("[]"), 1, PTR2NUM(*(void **)x->args[i]))); break; case TYPE_LONG: diff --git a/ext/fiddle/conversions.c b/ext/fiddle/conversions.c index 298ed977..41cfd8b2 100644 --- a/ext/fiddle/conversions.c +++ b/ext/fiddle/conversions.c @@ -308,18 +308,13 @@ value_to_generic(int type, VALUE src, fiddle_generic *dst) } VALUE -rb_fiddle_generic_to_value(VALUE rettype, fiddle_generic retval) +rb_fiddle_generic_to_value(int type, fiddle_generic retval) { - int type = NUM2INT(rettype); - VALUE cPointer; - - cPointer = rb_const_get(mFiddle, rb_intern("Pointer")); - switch (type) { case TYPE_VOID: return Qnil; case TYPE_VOIDP: - return rb_funcall(cPointer, rb_intern("[]"), 1, + return rb_funcall(rb_cPointer, rb_intern("[]"), 1, PTR2NUM((void *)retval.pointer)); case TYPE_CHAR: return INT2NUM((signed char)retval.fffi_sarg); @@ -377,5 +372,5 @@ rb_fiddle_generic_to_value(VALUE rettype, fiddle_generic retval) VALUE generic_to_value(VALUE rettype, fiddle_generic retval) { - return rb_fiddle_generic_to_value(rettype, retval); + return rb_fiddle_generic_to_value(NUM2INT(rettype), retval); } diff --git a/ext/fiddle/conversions.h b/ext/fiddle/conversions.h index 7a1e928d..fe68b16a 100644 --- a/ext/fiddle/conversions.h +++ b/ext/fiddle/conversions.h @@ -27,7 +27,7 @@ typedef union VALUE rb_fiddle_type_ensure(VALUE type); ffi_type * rb_fiddle_int_to_ffi_type(int type); void rb_fiddle_value_to_generic(int type, VALUE *src, fiddle_generic *dst); -VALUE rb_fiddle_generic_to_value(VALUE rettype, fiddle_generic retval); +VALUE rb_fiddle_generic_to_value(int type, fiddle_generic retval); /* Deprecated. Use rb_fiddle_*() version. */ ffi_type * int_to_ffi_type(int type); diff --git a/ext/fiddle/extconf.rb b/ext/fiddle/extconf.rb index f11afa2c..6f210830 100644 --- a/ext/fiddle/extconf.rb +++ b/ext/fiddle/extconf.rb @@ -231,6 +231,7 @@ def enable_debug_build_flag(flags) end have_func("rb_str_to_interned_str") +have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 create_makefile 'fiddle' do |conf| if !libffi next conf << "LIBFFI_CLEAN = none\n" diff --git a/ext/fiddle/fiddle.h b/ext/fiddle/fiddle.h index 49cc6023..cd0ac902 100644 --- a/ext/fiddle/fiddle.h +++ b/ext/fiddle/fiddle.h @@ -230,12 +230,14 @@ #define ALIGN_INT64_T ALIGN_OF(int64_t) extern VALUE mFiddle; +extern VALUE rb_cPointer; extern VALUE rb_eFiddleDLError; VALUE rb_fiddle_new_function(VALUE address, VALUE arg_types, VALUE ret_type); typedef void (*rb_fiddle_freefunc_t)(void*); VALUE rb_fiddle_ptr_new_wrap(void *ptr, long size, rb_fiddle_freefunc_t func, VALUE wrap0, VALUE wrap1); +void *rb_fiddle_ptr2cptr(VALUE ptr); enum { FIDDLE_DEFAULT_TYPED_DATA_FLAGS = ( diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 3f574034..dc4901bf 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -14,6 +14,13 @@ VALUE cFiddleFunction; +static ID id_ptr, id_abi, id_argument_types, id_return_type, id_is_variadic, + id_need_gvl, id_name, id_closure, id_aref, + id_last_error; +#if defined(_WIN32) +static ID id_win32_last_error, id_win32_last_socket_error; +#endif + #define MAX_ARGS (SIZE_MAX / (sizeof(void *) + sizeof(fiddle_generic)) - 1) #define Check_Max_Args(name, len) \ @@ -30,23 +37,38 @@ VALUE cFiddleFunction; } \ } while (0) +/* Call-path state, converted and validated once in #initialize. + * Deliberately holds no VALUEs so the type needs no dmark/dcompact. */ +typedef struct fiddle_function { + ffi_cif cif; + void (*fn)(void); + int *argument_types; + int n_argument_types; + int return_type; + ffi_abi abi; + bool is_variadic; + bool need_gvl; +} fiddle_function_t; + static void deallocate(void *p) { - ffi_cif *cif = p; - if (cif->arg_types) xfree(cif->arg_types); - xfree(cif); + fiddle_function_t *func = p; + if (func->cif.arg_types) xfree(func->cif.arg_types); + if (func->argument_types) xfree(func->argument_types); + xfree(func); } static size_t function_memsize(const void *p) { - /* const */ffi_cif *ptr = (ffi_cif *)p; + const fiddle_function_t *func = p; size_t size = 0; - size += sizeof(*ptr); + size += sizeof(*func); + size += func->n_argument_types * sizeof(int); #if !defined(FFI_NO_RAW_API) || !FFI_NO_RAW_API - size += ffi_raw_size(ptr); + size += ffi_raw_size((ffi_cif *)&func->cif); #endif return size; @@ -65,9 +87,9 @@ const rb_data_type_t function_data_type = { static VALUE allocate(VALUE klass) { - ffi_cif * cif; + fiddle_function_t *func; - return TypedData_Make_Struct(klass, ffi_cif, &function_data_type, cif); + return TypedData_Make_Struct(klass, fiddle_function_t, &function_data_type, func); } VALUE @@ -126,7 +148,7 @@ normalize_argument_types(const char *name, static VALUE initialize(int argc, VALUE argv[], VALUE self) { - ffi_cif * cif; + fiddle_function_t *func; VALUE ptr, arg_types, ret_type, abi, kwargs; VALUE name = Qnil; VALUE need_gvl = Qfalse; @@ -134,9 +156,10 @@ initialize(int argc, VALUE argv[], VALUE self) bool is_variadic = false; ffi_abi c_ffi_abi; void *cfunc; + int i; rb_scan_args(argc, argv, "31:", &ptr, &arg_types, &ret_type, &abi, &kwargs); - rb_iv_set(self, "@closure", ptr); + rb_ivar_set(self, id_closure, ptr); if (!NIL_P(kwargs)) { enum { @@ -163,8 +186,8 @@ initialize(int argc, VALUE argv[], VALUE self) need_gvl = args[kw_need_gvl]; } } - rb_iv_set(self, "@name", name); - rb_iv_set(self, "@need_gvl", need_gvl); + rb_ivar_set(self, id_name, name); + rb_ivar_set(self, id_need_gvl, need_gvl); ptr = rb_Integer(ptr); cfunc = NUM2PTR(ptr); @@ -187,14 +210,33 @@ initialize(int argc, VALUE argv[], VALUE self) } #endif - rb_iv_set(self, "@ptr", ptr); - rb_iv_set(self, "@argument_types", arg_types); - rb_iv_set(self, "@return_type", ret_type); - rb_iv_set(self, "@abi", abi); - rb_iv_set(self, "@is_variadic", is_variadic ? Qtrue : Qfalse); - - TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif); - cif->arg_types = NULL; + rb_ivar_set(self, id_ptr, ptr); + rb_ivar_set(self, id_argument_types, arg_types); + rb_ivar_set(self, id_return_type, ret_type); + rb_ivar_set(self, id_abi, abi); + rb_ivar_set(self, id_is_variadic, is_variadic ? Qtrue : Qfalse); + + TypedData_Get_Struct(self, fiddle_function_t, &function_data_type, func); + func->fn = (void (*)(void))(VALUE)cfunc; + func->abi = c_ffi_abi; + func->return_type = c_ret_type; + func->is_variadic = is_variadic; + func->need_gvl = RTEST(need_gvl); + + /* allow re-initialization without leaking or double-freeing */ + if (func->argument_types) { + xfree(func->argument_types); + func->argument_types = NULL; + } + func->n_argument_types = RARRAY_LENINT(arg_types); + func->argument_types = ALLOC_N(int, func->n_argument_types); + for (i = 0; i < func->n_argument_types; i++) { + func->argument_types[i] = FIX2INT(RARRAY_AREF(arg_types, i)); + } + if (func->cif.arg_types) { + xfree(func->cif.arg_types); + func->cif.arg_types = NULL; + } return self; } @@ -220,96 +262,93 @@ static VALUE function_call(int argc, VALUE argv[], VALUE self) { struct nogvl_ffi_call_args args = { 0 }; + fiddle_function_t *func; fiddle_generic *generic_args; - VALUE cfunc; - VALUE abi; - VALUE arg_types; - VALUE cPointer; - VALUE is_variadic; - VALUE need_gvl; - int n_arg_types; int n_fixed_args = 0; int n_call_args = 0; int i; int i_call; - VALUE converted_args = Qnil; + int *call_arg_types; + VALUE *converted_args; + int n_converted_args = 0; VALUE alloc_buffer = 0; - cfunc = rb_iv_get(self, "@ptr"); - abi = rb_iv_get(self, "@abi"); - arg_types = rb_iv_get(self, "@argument_types"); - cPointer = rb_const_get(mFiddle, rb_intern("Pointer")); - is_variadic = rb_iv_get(self, "@is_variadic"); - need_gvl = rb_iv_get(self, "@need_gvl"); + TypedData_Get_Struct(self, fiddle_function_t, &function_data_type, func); + args.cif = &func->cif; - n_arg_types = RARRAY_LENINT(arg_types); - n_fixed_args = n_arg_types; - if (RTEST(is_variadic)) { - if (argc < n_arg_types) { - rb_error_arity(argc, n_arg_types, UNLIMITED_ARGUMENTS); + n_fixed_args = func->n_argument_types; + if (func->is_variadic) { + if (argc < n_fixed_args) { + rb_error_arity(argc, n_fixed_args, UNLIMITED_ARGUMENTS); } - if (((argc - n_arg_types) % 2) != 0) { + if (((argc - n_fixed_args) % 2) != 0) { rb_raise(rb_eArgError, "variadic arguments must be type and value pairs: " "%"PRIsVALUE, rb_ary_new_from_values(argc, argv)); } - n_call_args = n_arg_types + ((argc - n_arg_types) / 2); + n_call_args = n_fixed_args + ((argc - n_fixed_args) / 2); } else { - if (argc != n_arg_types) { - rb_error_arity(argc, n_arg_types, n_arg_types); + if (argc != n_fixed_args) { + rb_error_arity(argc, n_fixed_args, n_fixed_args); } - n_call_args = n_arg_types; + n_call_args = n_fixed_args; } Check_Max_Args("the number of arguments", n_call_args); - TypedData_Get_Struct(self, ffi_cif, &function_data_type, args.cif); + generic_args = ALLOCV(alloc_buffer, + sizeof(fiddle_generic) * n_call_args + + sizeof(void *) * (n_call_args + 1) + + sizeof(VALUE) * n_call_args + + (func->is_variadic ? sizeof(int) * n_call_args : 0)); + args.values = (void **)((char *)generic_args + + sizeof(fiddle_generic) * n_call_args); + /* GC-scanned (conservatively) as part of the ALLOCV buffer */ + converted_args = (VALUE *)((char *)args.values + + sizeof(void *) * (n_call_args + 1)); + MEMZERO(converted_args, VALUE, n_call_args); + + if (func->is_variadic) { + call_arg_types = (int *)(converted_args + n_call_args); + MEMCPY(call_arg_types, func->argument_types, int, n_fixed_args); + for (i = n_fixed_args, i_call = n_fixed_args; + i < argc; + i += 2, i_call++) { + VALUE arg_type = rb_fiddle_type_ensure(argv[i]); + int c_arg_type = NUM2INT(arg_type); + (void)INT2FFI_TYPE(c_arg_type); /* raise */ + call_arg_types[i_call] = c_arg_type; + } - if (is_variadic && args.cif->arg_types) { - xfree(args.cif->arg_types); - args.cif->arg_types = NULL; + /* Variadic calls may pass different types each call, so the + * cif must always be re-prepared */ + if (func->cif.arg_types) { + xfree(func->cif.arg_types); + func->cif.arg_types = NULL; + } + } + else { + call_arg_types = func->argument_types; } - if (!args.cif->arg_types) { - VALUE fixed_arg_types = arg_types; - VALUE return_type; - int c_return_type; - ffi_type *ffi_return_type; + if (!func->cif.arg_types) { ffi_type **ffi_arg_types; ffi_status result; - arg_types = rb_ary_dup(fixed_arg_types); - for (i = n_fixed_args; i < argc; i += 2) { - VALUE arg_type = argv[i]; - int c_arg_type; - arg_type = rb_fiddle_type_ensure(arg_type); - c_arg_type = NUM2INT(arg_type); - (void)INT2FFI_TYPE(c_arg_type); /* raise */ - rb_ary_push(arg_types, INT2FIX(c_arg_type)); - } - - return_type = rb_iv_get(self, "@return_type"); - c_return_type = FIX2INT(return_type); - ffi_return_type = INT2FFI_TYPE(c_return_type); - ffi_arg_types = xcalloc(n_call_args + 1, sizeof(ffi_type *)); for (i_call = 0; i_call < n_call_args; i_call++) { - VALUE arg_type; - int c_arg_type; - arg_type = RARRAY_AREF(arg_types, i_call); - c_arg_type = FIX2INT(arg_type); - ffi_arg_types[i_call] = INT2FFI_TYPE(c_arg_type); + ffi_arg_types[i_call] = INT2FFI_TYPE(call_arg_types[i_call]); } ffi_arg_types[i_call] = NULL; - if (is_variadic) { + if (func->is_variadic) { #ifdef HAVE_FFI_PREP_CIF_VAR - result = ffi_prep_cif_var(args.cif, - FIX2INT(abi), + result = ffi_prep_cif_var(&func->cif, + func->abi, n_fixed_args, n_call_args, - ffi_return_type, + INT2FFI_TYPE(func->return_type), ffi_arg_types); #else /* This code is never used because ffi_prep_cif_var() @@ -318,34 +357,25 @@ function_call(int argc, VALUE argv[], VALUE self) #endif } else { - result = ffi_prep_cif(args.cif, - FIX2INT(abi), + result = ffi_prep_cif(&func->cif, + func->abi, n_call_args, - ffi_return_type, + INT2FFI_TYPE(func->return_type), ffi_arg_types); } if (result != FFI_OK) { xfree(ffi_arg_types); - args.cif->arg_types = NULL; + func->cif.arg_types = NULL; rb_raise(rb_eRuntimeError, "error creating CIF %d", result); } } - generic_args = ALLOCV(alloc_buffer, - sizeof(fiddle_generic) * n_call_args + - sizeof(void *) * (n_call_args + 1)); - args.values = (void **)((char *)generic_args + - sizeof(fiddle_generic) * n_call_args); - for (i = 0, i_call = 0; i < argc && i_call < n_call_args; i++, i_call++) { - VALUE arg_type; - int c_arg_type; + int c_arg_type = call_arg_types[i_call]; VALUE original_src; VALUE src; - arg_type = RARRAY_AREF(arg_types, i_call); - c_arg_type = FIX2INT(arg_type); if (i >= n_fixed_args) { i++; } @@ -353,32 +383,34 @@ function_call(int argc, VALUE argv[], VALUE self) if (c_arg_type == TYPE_VOIDP) { if (NIL_P(src)) { - src = INT2FIX(0); + generic_args[i_call].pointer = NULL; } - else if (cPointer != CLASS_OF(src)) { - src = rb_funcall(cPointer, rb_intern("[]"), 1, src); - if (NIL_P(converted_args)) { - converted_args = rb_ary_new(); + else if (RB_TYPE_P(src, T_STRING)) { + /* kept alive and pinned via converted_args */ + generic_args[i_call].pointer = RSTRING_PTR(src); + converted_args[n_converted_args++] = src; + } + else { + if (rb_cPointer != CLASS_OF(src)) { + src = rb_funcall(rb_cPointer, id_aref, 1, src); + converted_args[n_converted_args++] = src; } - rb_ary_push(converted_args, src); + generic_args[i_call].pointer = rb_fiddle_ptr2cptr(src); } - src = rb_Integer(src); } - - original_src = src; - VALUE2GENERIC(c_arg_type, src, &generic_args[i_call]); - if (src != original_src) { - if (NIL_P(converted_args)) { - converted_args = rb_ary_new(); + else { + original_src = src; + VALUE2GENERIC(c_arg_type, src, &generic_args[i_call]); + if (src != original_src) { + converted_args[n_converted_args++] = src; } - rb_ary_push(converted_args, src); } args.values[i_call] = (void *)&generic_args[i_call]; } args.values[i_call] = NULL; - args.fn = (void(*)(void))(VALUE)NUM2PTR(cfunc); + args.fn = func->fn; - if (RTEST(need_gvl)) { + if (func->need_gvl) { ffi_call(args.cif, args.fn, &(args.retval), args.values); } else { @@ -390,17 +422,18 @@ function_call(int argc, VALUE argv[], VALUE self) #if defined(_WIN32) DWORD error = WSAGetLastError(); int socket_error = WSAGetLastError(); - rb_funcall(mFiddle, rb_intern("win32_last_error="), 1, - ULONG2NUM(error)); - rb_funcall(mFiddle, rb_intern("win32_last_socket_error="), 1, - INT2NUM(socket_error)); + rb_thread_local_aset(rb_thread_current(), id_win32_last_error, + ULONG2NUM(error)); + rb_thread_local_aset(rb_thread_current(), id_win32_last_socket_error, + INT2NUM(socket_error)); #endif - rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno_keep)); + rb_thread_local_aset(rb_thread_current(), id_last_error, + INT2NUM(errno_keep)); } ALLOCV_END(alloc_buffer); - return GENERIC2VALUE(rb_iv_get(self, "@return_type"), args.retval); + return GENERIC2VALUE(func->return_type, args.retval); } void @@ -440,6 +473,21 @@ Init_fiddle_function(void) * f.abi == Fiddle::Function::DEFAULT * #=> true */ + id_ptr = rb_intern_const("@ptr"); + id_abi = rb_intern_const("@abi"); + id_argument_types = rb_intern_const("@argument_types"); + id_return_type = rb_intern_const("@return_type"); + id_is_variadic = rb_intern_const("@is_variadic"); + id_need_gvl = rb_intern_const("@need_gvl"); + id_name = rb_intern_const("@name"); + id_closure = rb_intern_const("@closure"); + id_aref = rb_intern_const("[]"); + id_last_error = rb_intern_const("__FIDDLE_LAST_ERROR__"); +#if defined(_WIN32) + id_win32_last_error = rb_intern_const("__FIDDLE_WIN32_LAST_ERROR__"); + id_win32_last_socket_error = rb_intern_const("__FIDDLE_WIN32_LAST_SOCKET_ERROR__"); +#endif + cFiddleFunction = rb_define_class_under(mFiddle, "Function", rb_cObject); /* diff --git a/ext/fiddle/memory_view.c b/ext/fiddle/memory_view.c index 5fd7b9b4..2d8e4412 100644 --- a/ext/fiddle/memory_view.c +++ b/ext/fiddle/memory_view.c @@ -115,6 +115,7 @@ rb_fiddle_memview_initialize(VALUE obj, VALUE target) data->view.obj = Qnil; rb_raise(rb_eArgError, "Unable to get a memory view from %+"PRIsVALUE, target); } + RB_OBJ_WRITTEN(obj, Qundef, data->view.obj); return Qnil; } diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index 10a6cc70..9eddd717 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -34,7 +34,22 @@ struct ptr_data { VALUE wrap[2]; }; -#define RPTR_DATA(obj) ((struct ptr_data *)(DATA_PTR(obj))) +#ifdef RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +#else +# ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE +# define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +# else +# define RUBY_TYPED_EMBEDDABLE 0 +# endif +#endif + +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE +#define RPTR_DATA(obj) ((struct ptr_data *)RTYPEDDATA_GET_DATA(obj)) +#else +#define RPTR_DATA(obj) ((struct ptr_data *)DATA_PTR(obj)) +#endif static inline freefunc_t get_freefunc(VALUE func, volatile VALUE *wrap) @@ -77,14 +92,20 @@ static void fiddle_ptr_free(void *ptr) { fiddle_ptr_free_ptr(ptr); +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE xfree(ptr); +#endif } static size_t fiddle_ptr_memsize(const void *ptr) { const struct ptr_data *data = ptr; - return sizeof(*data) + data->size; + size_t size = data->size; +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + size += sizeof(*data); +#endif + return size; } static const rb_data_type_t fiddle_ptr_data_type = { @@ -94,7 +115,7 @@ static const rb_data_type_t fiddle_ptr_data_type = { .dfree = fiddle_ptr_free, .dsize = fiddle_ptr_memsize, }, - .flags = FIDDLE_DEFAULT_TYPED_DATA_FLAGS, + .flags = FIDDLE_DEFAULT_TYPED_DATA_FLAGS | RUBY_TYPED_EMBEDDABLE, }; #ifdef HAVE_RUBY_MEMORY_VIEW_H @@ -168,7 +189,7 @@ rb_fiddle_ptr_malloc(VALUE klass, long size, freefunc_t func) return rb_fiddle_ptr_new2(klass, ptr, size, func, 0, 0); } -static void * +void * rb_fiddle_ptr2cptr(VALUE val) { struct ptr_data *data; @@ -416,9 +437,11 @@ static VALUE rb_fiddle_ptr_free_set(VALUE self, VALUE val) { struct ptr_data *data; + VALUE wrap = 0; TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, data); - data->free = get_freefunc(val, &data->wrap[1]); + data->free = get_freefunc(val, &wrap); + RB_OBJ_WRITE(self, &data->wrap[1], wrap); return Qnil; } diff --git a/lib/fiddle.rb b/lib/fiddle.rb index f242de87..3caf3129 100644 --- a/lib/fiddle.rb +++ b/lib/fiddle.rb @@ -44,7 +44,6 @@ def self.last_error # Sets the last +Error+ of the current executing +Thread+ to +error+ def self.last_error= error - Thread.current[:__DL2_LAST_ERROR__] = error Thread.current[:__FIDDLE_LAST_ERROR__] = error end