summaryrefslogtreecommitdiff
path: root/ext/fiddle/function.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/fiddle/function.c')
-rw-r--r--ext/fiddle/function.c430
1 files changed, 352 insertions, 78 deletions
diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c
index 6e8909991a..274d181d17 100644
--- a/ext/fiddle/function.c
+++ b/ext/fiddle/function.c
@@ -1,13 +1,41 @@
#include <fiddle.h>
+#include <ruby/thread.h>
+
+#include <stdbool.h>
+
+#ifdef PRIsVALUE
+# define RB_OBJ_CLASSNAME(obj) rb_obj_class(obj)
+# define RB_OBJ_STRING(obj) (obj)
+#else
+# define PRIsVALUE "s"
+# define RB_OBJ_CLASSNAME(obj) rb_obj_classname(obj)
+# define RB_OBJ_STRING(obj) StringValueCStr(obj)
+#endif
VALUE cFiddleFunction;
+#define MAX_ARGS (SIZE_MAX / (sizeof(void *) + sizeof(fiddle_generic)) - 1)
+
+#define Check_Max_Args(name, len) \
+ Check_Max_Args_(name, len, "")
+#define Check_Max_Args_Long(name, len) \
+ Check_Max_Args_(name, len, "l")
+#define Check_Max_Args_(name, len, fmt) \
+ do { \
+ if ((size_t)(len) >= MAX_ARGS) { \
+ rb_raise(rb_eTypeError, \
+ "%s is so large " \
+ "that it can cause integer overflow (%"fmt"d)", \
+ (name), (len)); \
+ } \
+ } while (0)
+
static void
deallocate(void *p)
{
- ffi_cif *ptr = p;
- if (ptr->arg_types) xfree(ptr->arg_types);
- xfree(ptr);
+ ffi_cif *cif = p;
+ if (cif->arg_types) xfree(cif->arg_types);
+ xfree(cif);
}
static size_t
@@ -16,12 +44,11 @@ function_memsize(const void *p)
/* const */ffi_cif *ptr = (ffi_cif *)p;
size_t size = 0;
- if (ptr) {
- size += sizeof(*ptr);
+ size += sizeof(*ptr);
#if !defined(FFI_NO_RAW_API) || !FFI_NO_RAW_API
- size += ffi_raw_size(ptr);
+ size += ffi_raw_size(ptr);
#endif
- }
+
return size;
}
@@ -38,101 +65,332 @@ allocate(VALUE klass)
return TypedData_Make_Struct(klass, ffi_cif, &function_data_type, cif);
}
+VALUE
+rb_fiddle_new_function(VALUE address, VALUE arg_types, VALUE ret_type)
+{
+ VALUE argv[3];
+
+ argv[0] = address;
+ argv[1] = arg_types;
+ argv[2] = ret_type;
+
+ return rb_class_new_instance(3, argv, cFiddleFunction);
+}
+
static VALUE
-initialize(int argc, VALUE argv[], VALUE self)
+normalize_argument_types(const char *name,
+ VALUE arg_types,
+ bool *is_variadic)
{
- ffi_cif * cif;
- ffi_type **arg_types;
- ffi_status result;
- VALUE ptr, args, ret_type, abi;
+ VALUE normalized_arg_types;
int i;
+ int n_arg_types;
+ *is_variadic = false;
+
+ Check_Type(arg_types, T_ARRAY);
+ n_arg_types = RARRAY_LENINT(arg_types);
+ Check_Max_Args(name, n_arg_types);
+
+ normalized_arg_types = rb_ary_new_capa(n_arg_types);
+ for (i = 0; i < n_arg_types; i++) {
+ VALUE arg_type = RARRAY_AREF(arg_types, i);
+ int c_arg_type;
+ arg_type = rb_fiddle_type_ensure(arg_type);
+ c_arg_type = NUM2INT(arg_type);
+ if (c_arg_type == TYPE_VARIADIC) {
+ if (i != n_arg_types - 1) {
+ rb_raise(rb_eArgError,
+ "Fiddle::TYPE_VARIADIC must be the last argument type: "
+ "%"PRIsVALUE,
+ arg_types);
+ }
+ *is_variadic = true;
+ break;
+ }
+ else {
+ (void)INT2FFI_TYPE(c_arg_type); /* raise */
+ }
+ rb_ary_push(normalized_arg_types, INT2FIX(c_arg_type));
+ }
- rb_scan_args(argc, argv, "31", &ptr, &args, &ret_type, &abi);
- if(NIL_P(abi)) abi = INT2NUM(FFI_DEFAULT_ABI);
+ /* freeze to prevent inconsistency at calling #to_int later */
+ OBJ_FREEZE(normalized_arg_types);
+ return normalized_arg_types;
+}
- Check_Type(args, T_ARRAY);
+static VALUE
+initialize(int argc, VALUE argv[], VALUE self)
+{
+ ffi_cif * cif;
+ VALUE ptr, arg_types, ret_type, abi, kwargs;
+ VALUE name = Qnil;
+ VALUE need_gvl = Qfalse;
+ int c_ret_type;
+ bool is_variadic = false;
+ ffi_abi c_ffi_abi;
+ void *cfunc;
+
+ rb_scan_args(argc, argv, "31:", &ptr, &arg_types, &ret_type, &abi, &kwargs);
+ rb_iv_set(self, "@closure", ptr);
+
+ if (!NIL_P(kwargs)) {
+ enum {
+ kw_name,
+ kw_need_gvl,
+ kw_max_,
+ };
+ static ID kw[kw_max_];
+ VALUE args[kw_max_];
+ if (!kw[0]) {
+ kw[kw_name] = rb_intern_const("name");
+ kw[kw_need_gvl] = rb_intern_const("need_gvl");
+ }
+ rb_get_kwargs(kwargs, kw, 0, kw_max_, args);
+ if (args[kw_name] != Qundef) {
+ name = args[kw_name];
+ }
+ if (args[kw_need_gvl] != Qundef) {
+ need_gvl = args[kw_need_gvl];
+ }
+ }
+ rb_iv_set(self, "@name", name);
+ rb_iv_set(self, "@need_gvl", need_gvl);
+
+ ptr = rb_Integer(ptr);
+ cfunc = NUM2PTR(ptr);
+ PTR2NUM(cfunc);
+ c_ffi_abi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi);
+ abi = INT2FIX(c_ffi_abi);
+ ret_type = rb_fiddle_type_ensure(ret_type);
+ c_ret_type = NUM2INT(ret_type);
+ (void)INT2FFI_TYPE(c_ret_type); /* raise */
+ ret_type = INT2FIX(c_ret_type);
+
+ arg_types = normalize_argument_types("argument types",
+ arg_types,
+ &is_variadic);
+#ifndef HAVE_FFI_PREP_CIF_VAR
+ if (is_variadic) {
+ rb_raise(rb_eNotImpError,
+ "ffi_prep_cif_var() is required in libffi "
+ "for variadic arguments");
+ }
+#endif
rb_iv_set(self, "@ptr", ptr);
- rb_iv_set(self, "@args", args);
+ 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;
- arg_types = xcalloc(RARRAY_LEN(args) + 1, sizeof(ffi_type *));
+ return self;
+}
- for (i = 0; i < RARRAY_LEN(args); i++) {
- int type = NUM2INT(RARRAY_PTR(args)[i]);
- arg_types[i] = INT2FFI_TYPE(type);
- }
- arg_types[RARRAY_LEN(args)] = NULL;
+struct nogvl_ffi_call_args {
+ ffi_cif *cif;
+ void (*fn)(void);
+ void **values;
+ fiddle_generic retval;
+};
- result = ffi_prep_cif (
- cif,
- NUM2INT(abi),
- RARRAY_LENINT(args),
- INT2FFI_TYPE(NUM2INT(ret_type)),
- arg_types);
+static void *
+nogvl_ffi_call(void *ptr)
+{
+ struct nogvl_ffi_call_args *args = ptr;
- if (result)
- rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
+ ffi_call(args->cif, args->fn, &args->retval, args->values);
- return self;
+ return NULL;
}
static VALUE
function_call(int argc, VALUE argv[], VALUE self)
{
- ffi_cif * cif;
- fiddle_generic retval;
+ struct nogvl_ffi_call_args args = { 0 };
fiddle_generic *generic_args;
- void **values;
- VALUE cfunc, types, cPointer;
+ 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;
+ VALUE alloc_buffer = 0;
cfunc = rb_iv_get(self, "@ptr");
- types = rb_iv_get(self, "@args");
+ abi = rb_iv_get(self, "@abi");
+ arg_types = rb_iv_get(self, "@argument_types");
cPointer = rb_const_get(mFiddle, rb_intern("Pointer"));
-
- if(argc != RARRAY_LENINT(types)) {
- rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
- argc, RARRAY_LENINT(types));
+ is_variadic = rb_iv_get(self, "@is_variadic");
+ need_gvl = rb_iv_get(self, "@need_gvl");
+
+ 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);
+ }
+ if (((argc - n_arg_types) % 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);
}
+ else {
+ if (argc != n_arg_types) {
+ rb_error_arity(argc, n_arg_types, n_arg_types);
+ }
+ n_call_args = n_arg_types;
+ }
+ Check_Max_Args("the number of arguments", n_call_args);
- TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif);
-
- values = xcalloc((size_t)argc + 1, (size_t)sizeof(void *));
- generic_args = xcalloc((size_t)argc, (size_t)sizeof(fiddle_generic));
+ TypedData_Get_Struct(self, ffi_cif, &function_data_type, args.cif);
- for (i = 0; i < argc; i++) {
- VALUE type = RARRAY_PTR(types)[i];
- VALUE src = argv[i];
+ if (is_variadic && args.cif->arg_types) {
+ xfree(args.cif->arg_types);
+ args.cif->arg_types = NULL;
+ }
- if(NUM2INT(type) == TYPE_VOIDP) {
- if(NIL_P(src)) {
- src = INT2NUM(0);
- } else if(cPointer != CLASS_OF(src)) {
- src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
- }
- src = rb_Integer(src);
- }
+ if (!args.cif->arg_types) {
+ VALUE fixed_arg_types = arg_types;
+ VALUE return_type;
+ int c_return_type;
+ ffi_type *ffi_return_type;
+ 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] = NULL;
+
+ if (is_variadic) {
+#ifdef HAVE_FFI_PREP_CIF_VAR
+ result = ffi_prep_cif_var(args.cif,
+ FIX2INT(abi),
+ n_fixed_args,
+ n_call_args,
+ ffi_return_type,
+ ffi_arg_types);
+#else
+ /* This code is never used because ffi_prep_cif_var()
+ * availability check is done in #initialize. */
+ result = FFI_BAD_TYPEDEF;
+#endif
+ }
+ else {
+ result = ffi_prep_cif(args.cif,
+ FIX2INT(abi),
+ n_call_args,
+ ffi_return_type,
+ ffi_arg_types);
+ }
+ if (result != FFI_OK) {
+ xfree(ffi_arg_types);
+ args.cif->arg_types = NULL;
+ rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
+ }
+ }
- VALUE2GENERIC(NUM2INT(type), src, &generic_args[i]);
- values[i] = (void *)&generic_args[i];
+ 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;
+ 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++;
+ }
+ src = argv[i];
+
+ if (c_arg_type == TYPE_VOIDP) {
+ if (NIL_P(src)) {
+ src = INT2FIX(0);
+ }
+ else if (cPointer != CLASS_OF(src)) {
+ src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
+ if (NIL_P(converted_args)) {
+ converted_args = rb_ary_new();
+ }
+ rb_ary_push(converted_args, 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();
+ }
+ rb_ary_push(converted_args, src);
+ }
+ args.values[i_call] = (void *)&generic_args[i_call];
}
- values[argc] = NULL;
+ args.values[i_call] = NULL;
+ args.fn = (void(*)(void))NUM2PTR(cfunc);
- ffi_call(cif, NUM2PTR(rb_Integer(cfunc)), &retval, values);
+ if (RTEST(need_gvl)) {
+ ffi_call(args.cif, args.fn, &(args.retval), args.values);
+ }
+ else {
+ (void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0);
+ }
- rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno));
+ {
+ int errno_keep = errno;
#if defined(_WIN32)
- rb_funcall(mFiddle, rb_intern("win32_last_error="), 1, INT2NUM(errno));
+ 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));
#endif
+ rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno_keep));
+ }
- xfree(values);
- xfree(generic_args);
+ ALLOCV_END(alloc_buffer);
- return GENERIC2VALUE(rb_iv_get(self, "@return_type"), retval);
+ return GENERIC2VALUE(rb_iv_get(self, "@return_type"), args.retval);
}
void
@@ -149,25 +407,28 @@ Init_fiddle_function(void)
*
* === 'strcpy'
*
- * @libc = DL.dlopen "/lib/libc.so.6"
- * => #<DL::Handle:0x00000001d7a8d8>
- * f = Fiddle::Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
- * => #<Fiddle::Function:0x00000001d8ee00>
+ * @libc = Fiddle.dlopen "/lib/libc.so.6"
+ * #=> #<Fiddle::Handle:0x00000001d7a8d8>
+ * f = Fiddle::Function.new(
+ * @libc['strcpy'],
+ * [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP],
+ * Fiddle::TYPE_VOIDP)
+ * #=> #<Fiddle::Function:0x00000001d8ee00>
* buff = "000"
- * => "000"
+ * #=> "000"
* str = f.call(buff, "123")
- * => #<DL::CPtr:0x00000001d0c380 ptr=0x000000018a21b8 size=0 free=0x00000000000000>
+ * #=> #<Fiddle::Pointer:0x00000001d0c380 ptr=0x000000018a21b8 size=0 free=0x00000000000000>
* str.to_s
* => "123"
*
* === ABI check
*
- * @libc = DL.dlopen "/lib/libc.so.6"
- * => #<DL::Handle:0x00000001d7a8d8>
+ * @libc = Fiddle.dlopen "/lib/libc.so.6"
+ * #=> #<Fiddle::Handle:0x00000001d7a8d8>
* f = Fiddle::Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
- * => #<Fiddle::Function:0x00000001d8ee00>
+ * #=> #<Fiddle::Function:0x00000001d8ee00>
* f.abi == Fiddle::Function::DEFAULT
- * => true
+ * #=> true
*/
cFiddleFunction = rb_define_class_under(mFiddle, "Function", rb_cObject);
@@ -179,7 +440,7 @@ Init_fiddle_function(void)
*/
rb_define_const(cFiddleFunction, "DEFAULT", INT2NUM(FFI_DEFAULT_ABI));
-#ifdef FFI_STDCALL
+#ifdef HAVE_CONST_FFI_STDCALL
/*
* Document-const: STDCALL
*
@@ -194,7 +455,13 @@ Init_fiddle_function(void)
/*
* Document-method: call
*
- * Calls the constructed Function, with +args+
+ * Calls the constructed Function, with +args+.
+ * Caller must ensure the underlying function is called in a
+ * thread-safe manner if running in a multi-threaded process.
+ *
+ * Note that it is not thread-safe to use this method to
+ * directly or indirectly call many Ruby C-extension APIs unless
+ * you don't pass +need_gvl: true+ to Fiddle::Function#new.
*
* For an example see Fiddle::Function
*
@@ -203,13 +470,20 @@ Init_fiddle_function(void)
/*
* Document-method: new
- * call-seq: new(ptr, *args, ret_type, abi = DEFAULT)
+ * call-seq: new(ptr,
+ * args,
+ * ret_type,
+ * abi = DEFAULT,
+ * name: nil,
+ * need_gvl: false)
*
* Constructs a Function object.
- * * +ptr+ is a referenced function, of a DL::Handle
+ * * +ptr+ is a referenced function, of a Fiddle::Handle
* * +args+ is an Array of arguments, passed to the +ptr+ function
* * +ret_type+ is the return type of the function
* * +abi+ is the ABI of the function
+ * * +name+ is the name of the function
+ * * +need_gvl+ is whether GVL is needed to call the function
*
*/
rb_define_method(cFiddleFunction, "initialize", initialize, -1);