From be992fd3148d0c07fe45056e9876fa9528f3ba8b Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 15 Jun 2026 01:51:38 -0700 Subject: [PATCH 01/10] Add missing write barrier on fiddle_ptr_free_set --- ext/fiddle/pointer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index 10a6cc70..eac70c91 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -416,9 +416,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; } From 9ecc54ff886e73912e24b44d0900adb39c594d60 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 14:42:39 -0700 Subject: [PATCH 02/10] Pre-intern IDs --- ext/fiddle/function.c | 63 +++++++++++++++++++++++++++------------- ext/fiddle/memory_view.c | 1 + 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 3f574034..7b2a40bd 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_Pointer, id_aref, + id_set_last_error; +#if defined(_WIN32) +static ID id_set_win32_last_error, id_set_win32_last_socket_error; +#endif + #define MAX_ARGS (SIZE_MAX / (sizeof(void *) + sizeof(fiddle_generic)) - 1) #define Check_Max_Args(name, len) \ @@ -136,7 +143,7 @@ initialize(int argc, VALUE argv[], VALUE self) void *cfunc; 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 +170,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,11 +194,11 @@ 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); + 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, ffi_cif, &function_data_type, cif); cif->arg_types = NULL; @@ -235,12 +242,12 @@ function_call(int argc, VALUE argv[], VALUE self) VALUE converted_args = Qnil; 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"); + cfunc = rb_ivar_get(self, id_ptr); + abi = rb_ivar_get(self, id_abi); + arg_types = rb_ivar_get(self, id_argument_types); + cPointer = rb_const_get(mFiddle, id_Pointer); + is_variadic = rb_ivar_get(self, id_is_variadic); + need_gvl = rb_ivar_get(self, id_need_gvl); n_arg_types = RARRAY_LENINT(arg_types); n_fixed_args = n_arg_types; @@ -289,7 +296,7 @@ function_call(int argc, VALUE argv[], VALUE self) rb_ary_push(arg_types, INT2FIX(c_arg_type)); } - return_type = rb_iv_get(self, "@return_type"); + return_type = rb_ivar_get(self, id_return_type); c_return_type = FIX2INT(return_type); ffi_return_type = INT2FFI_TYPE(c_return_type); @@ -356,7 +363,7 @@ function_call(int argc, VALUE argv[], VALUE self) src = INT2FIX(0); } else if (cPointer != CLASS_OF(src)) { - src = rb_funcall(cPointer, rb_intern("[]"), 1, src); + src = rb_funcall(cPointer, id_aref, 1, src); if (NIL_P(converted_args)) { converted_args = rb_ary_new(); } @@ -390,17 +397,17 @@ 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, + rb_funcall(mFiddle, id_set_win32_last_error, 1, ULONG2NUM(error)); - rb_funcall(mFiddle, rb_intern("win32_last_socket_error="), 1, + rb_funcall(mFiddle, id_set_win32_last_socket_error, 1, INT2NUM(socket_error)); #endif - rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno_keep)); + rb_funcall(mFiddle, id_set_last_error, 1, INT2NUM(errno_keep)); } ALLOCV_END(alloc_buffer); - return GENERIC2VALUE(rb_iv_get(self, "@return_type"), args.retval); + return GENERIC2VALUE(rb_ivar_get(self, id_return_type), args.retval); } void @@ -440,6 +447,22 @@ 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_Pointer = rb_intern_const("Pointer"); + id_aref = rb_intern_const("[]"); + id_set_last_error = rb_intern_const("last_error="); +#if defined(_WIN32) + id_set_win32_last_error = rb_intern_const("win32_last_error="); + id_set_win32_last_socket_error = rb_intern_const("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; } From cab89b92b3420d987f6eed13f0bffe539a1bea14 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 15:02:04 -0700 Subject: [PATCH 03/10] Store converted args ALLOCV instead of ARRAY Also try to convert directly to generic_args pointer --- ext/fiddle/fiddle.h | 1 + ext/fiddle/function.c | 35 +++++++++++++++++++---------------- ext/fiddle/pointer.c | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/ext/fiddle/fiddle.h b/ext/fiddle/fiddle.h index 49cc6023..19bc9d9c 100644 --- a/ext/fiddle/fiddle.h +++ b/ext/fiddle/fiddle.h @@ -236,6 +236,7 @@ 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 7b2a40bd..03545d7f 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -239,7 +239,8 @@ function_call(int argc, VALUE argv[], VALUE self) int n_call_args = 0; int i; int i_call; - VALUE converted_args = Qnil; + VALUE *converted_args; + int n_converted_args = 0; VALUE alloc_buffer = 0; cfunc = rb_ivar_get(self, id_ptr); @@ -340,9 +341,14 @@ function_call(int argc, VALUE argv[], VALUE self) generic_args = ALLOCV(alloc_buffer, sizeof(fiddle_generic) * n_call_args + - sizeof(void *) * (n_call_args + 1)); + sizeof(void *) * (n_call_args + 1) + + sizeof(VALUE) * n_call_args); 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); for (i = 0, i_call = 0; i < argc && i_call < n_call_args; @@ -360,25 +366,22 @@ 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, id_aref, 1, src); - if (NIL_P(converted_args)) { - converted_args = rb_ary_new(); + else { + if (cPointer != CLASS_OF(src)) { + src = rb_funcall(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]; } diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index eac70c91..4c497ea0 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -168,7 +168,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; From 77225d17530248d3fa279beff8b26677d007d3f3 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 16:44:05 -0700 Subject: [PATCH 04/10] Allocate Pointer as embedded --- ext/fiddle/extconf.rb | 1 + ext/fiddle/pointer.c | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) 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/pointer.c b/ext/fiddle/pointer.c index 4c497ea0..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 From 596de05527f1aef6a921e685fc5ba46bc26257ed Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 16:47:12 -0700 Subject: [PATCH 05/10] Fast path for T_STRING --- ext/fiddle/function.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 03545d7f..132451de 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -368,6 +368,11 @@ function_call(int argc, VALUE argv[], VALUE self) if (NIL_P(src)) { generic_args[i_call].pointer = NULL; } + 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 (cPointer != CLASS_OF(src)) { src = rb_funcall(cPointer, id_aref, 1, src); From 7d31f4e2990ca5679dc0a6b772b2f69d7a637504 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 16:54:02 -0700 Subject: [PATCH 06/10] Look up fiddle pointer only once --- ext/fiddle/fiddle.h | 1 + ext/fiddle/function.c | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ext/fiddle/fiddle.h b/ext/fiddle/fiddle.h index 19bc9d9c..cd0ac902 100644 --- a/ext/fiddle/fiddle.h +++ b/ext/fiddle/fiddle.h @@ -230,6 +230,7 @@ #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); diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 132451de..368146c1 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -15,7 +15,7 @@ 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_Pointer, id_aref, + id_need_gvl, id_name, id_closure, id_aref, id_set_last_error; #if defined(_WIN32) static ID id_set_win32_last_error, id_set_win32_last_socket_error; @@ -231,7 +231,6 @@ function_call(int argc, VALUE argv[], VALUE self) VALUE cfunc; VALUE abi; VALUE arg_types; - VALUE cPointer; VALUE is_variadic; VALUE need_gvl; int n_arg_types; @@ -246,7 +245,6 @@ function_call(int argc, VALUE argv[], VALUE self) cfunc = rb_ivar_get(self, id_ptr); abi = rb_ivar_get(self, id_abi); arg_types = rb_ivar_get(self, id_argument_types); - cPointer = rb_const_get(mFiddle, id_Pointer); is_variadic = rb_ivar_get(self, id_is_variadic); need_gvl = rb_ivar_get(self, id_need_gvl); @@ -374,8 +372,8 @@ function_call(int argc, VALUE argv[], VALUE self) converted_args[n_converted_args++] = src; } else { - if (cPointer != CLASS_OF(src)) { - src = rb_funcall(cPointer, id_aref, 1, src); + if (rb_cPointer != CLASS_OF(src)) { + src = rb_funcall(rb_cPointer, id_aref, 1, src); converted_args[n_converted_args++] = src; } generic_args[i_call].pointer = rb_fiddle_ptr2cptr(src); From 5259b90a33b0e7e2826f5fe9fe191c56d222cdd5 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 16:54:36 -0700 Subject: [PATCH 07/10] Avoid const lookups to Pointer --- ext/fiddle/closure.c | 5 +---- ext/fiddle/conversions.c | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) 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..91497bb9 100644 --- a/ext/fiddle/conversions.c +++ b/ext/fiddle/conversions.c @@ -311,15 +311,12 @@ VALUE rb_fiddle_generic_to_value(VALUE rettype, 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); From b44bec0418f14630a6e85b96d22ee2161d83035f Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 16:54:48 -0700 Subject: [PATCH 08/10] Set thread locals directly for last_error --- ext/fiddle/function.c | 22 +++++++++++----------- lib/fiddle.rb | 1 - 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 368146c1..6a824b1b 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -16,9 +16,9 @@ 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_set_last_error; + id_last_error; #if defined(_WIN32) -static ID id_set_win32_last_error, id_set_win32_last_socket_error; +static ID id_win32_last_error, id_win32_last_socket_error; #endif #define MAX_ARGS (SIZE_MAX / (sizeof(void *) + sizeof(fiddle_generic)) - 1) @@ -403,12 +403,13 @@ function_call(int argc, VALUE argv[], VALUE self) #if defined(_WIN32) DWORD error = WSAGetLastError(); int socket_error = WSAGetLastError(); - rb_funcall(mFiddle, id_set_win32_last_error, 1, - ULONG2NUM(error)); - rb_funcall(mFiddle, id_set_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, id_set_last_error, 1, INT2NUM(errno_keep)); + rb_thread_local_aset(rb_thread_current(), id_last_error, + INT2NUM(errno_keep)); } ALLOCV_END(alloc_buffer); @@ -461,12 +462,11 @@ Init_fiddle_function(void) id_need_gvl = rb_intern_const("@need_gvl"); id_name = rb_intern_const("@name"); id_closure = rb_intern_const("@closure"); - id_Pointer = rb_intern_const("Pointer"); id_aref = rb_intern_const("[]"); - id_set_last_error = rb_intern_const("last_error="); + id_last_error = rb_intern_const("__FIDDLE_LAST_ERROR__"); #if defined(_WIN32) - id_set_win32_last_error = rb_intern_const("win32_last_error="); - id_set_win32_last_socket_error = rb_intern_const("win32_last_socket_error="); + 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/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 From 29efadc06450aff2dd1ae40192e6915b5a27dac6 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 17:03:50 -0700 Subject: [PATCH 09/10] Move ivars into struct --- ext/fiddle/function.c | 191 +++++++++++++++++++++++------------------- 1 file changed, 105 insertions(+), 86 deletions(-) diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 6a824b1b..a4d78a05 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -37,23 +37,38 @@ static ID id_win32_last_error, id_win32_last_socket_error; } \ } 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; @@ -72,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 @@ -133,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; @@ -141,6 +156,7 @@ 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_ivar_set(self, id_closure, ptr); @@ -200,8 +216,27 @@ initialize(int argc, VALUE argv[], VALUE self) rb_ivar_set(self, id_abi, abi); rb_ivar_set(self, id_is_variadic, is_variadic ? Qtrue : Qfalse); - TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif); - cif->arg_types = NULL; + 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; } @@ -227,95 +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 is_variadic; - VALUE need_gvl; - int n_arg_types; int n_fixed_args = 0; int n_call_args = 0; int i; int i_call; + int *call_arg_types; VALUE *converted_args; int n_converted_args = 0; VALUE alloc_buffer = 0; - cfunc = rb_ivar_get(self, id_ptr); - abi = rb_ivar_get(self, id_abi); - arg_types = rb_ivar_get(self, id_argument_types); - is_variadic = rb_ivar_get(self, id_is_variadic); - need_gvl = rb_ivar_get(self, id_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 (is_variadic && args.cif->arg_types) { - xfree(args.cif->arg_types); - args.cif->arg_types = NULL; + 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; + } + + /* 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_ivar_get(self, id_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() @@ -324,39 +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) + - sizeof(VALUE) * n_call_args); - 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); - 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++; } @@ -389,9 +408,9 @@ function_call(int argc, VALUE argv[], VALUE self) 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 { @@ -414,7 +433,7 @@ function_call(int argc, VALUE argv[], VALUE self) ALLOCV_END(alloc_buffer); - return GENERIC2VALUE(rb_ivar_get(self, id_return_type), args.retval); + return GENERIC2VALUE(INT2FIX(func->return_type), args.retval); } void From 6c9fa80579cf33213047d4bdc9409a88cc2fbd41 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 10 Jul 2026 17:05:35 -0700 Subject: [PATCH 10/10] Make rb_fiddle_generic_to_value take int --- ext/fiddle/conversions.c | 6 ++---- ext/fiddle/conversions.h | 2 +- ext/fiddle/function.c | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ext/fiddle/conversions.c b/ext/fiddle/conversions.c index 91497bb9..41cfd8b2 100644 --- a/ext/fiddle/conversions.c +++ b/ext/fiddle/conversions.c @@ -308,10 +308,8 @@ 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); - switch (type) { case TYPE_VOID: return Qnil; @@ -374,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/function.c b/ext/fiddle/function.c index a4d78a05..dc4901bf 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -433,7 +433,7 @@ function_call(int argc, VALUE argv[], VALUE self) ALLOCV_END(alloc_buffer); - return GENERIC2VALUE(INT2FIX(func->return_type), args.retval); + return GENERIC2VALUE(func->return_type, args.retval); } void