summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJean Boussier <jean.boussier@gmail.com>2023-03-06 09:51:36 +0100
committerJean Boussier <jean.boussier@gmail.com>2023-03-06 10:50:52 +0100
commitfae39d82cbf8acc9ecb52dda12048876f09ca898 (patch)
tree986280e55bd0462dc29ede30cfb54b6f96313459
parente1cf98e4c0d0cb01c6c936a33578288df735090d (diff)
downloadffi-fae39d82cbf8acc9ecb52dda12048876f09ca898.tar.gz
Implement Write Barrier and dsize for FFI::FunctionType
Write barrier protected objects are allowed to be promoted to the old generation, which means they only get marked on major GC. The downside is that the RB_BJ_WRITE macro MUST be used to set references, otherwise the referenced object may be garbaged collected. This commit also implement a `dsize` function so that these instance report a more relevant size in various memory profilers.
-rw-r--r--ext/ffi_c/FunctionInfo.c50
-rw-r--r--spec/ffi/function_type_spec.rb27
-rw-r--r--spec/ffi/spec_helper.rb1
3 files changed, 63 insertions, 15 deletions
diff --git a/ext/ffi_c/FunctionInfo.c b/ext/ffi_c/FunctionInfo.c
index d2c3376..e3391c3 100644
--- a/ext/ffi_c/FunctionInfo.c
+++ b/ext/ffi_c/FunctionInfo.c
@@ -53,16 +53,19 @@ static VALUE fntype_allocate(VALUE klass);
static VALUE fntype_initialize(int argc, VALUE* argv, VALUE self);
static void fntype_mark(void *);
static void fntype_free(void *);
+static size_t fntype_memsize(const void *);
const rb_data_type_t rbffi_fntype_data_type = { /* extern */
- .wrap_struct_name = "FFI::FunctionType",
- .function = {
- .dmark = fntype_mark,
- .dfree = fntype_free,
- .dsize = NULL,
- },
- .parent = &rbffi_type_data_type,
- .flags = RUBY_TYPED_FREE_IMMEDIATELY
+ .wrap_struct_name = "FFI::FunctionType",
+ .function = {
+ .dmark = fntype_mark,
+ .dfree = fntype_free,
+ .dsize = fntype_memsize,
+ },
+ .parent = &rbffi_type_data_type,
+ // IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
+ // macro to update VALUE references, as to trigger write barriers.
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
};
VALUE rbffi_FunctionTypeClass = Qnil;
@@ -75,9 +78,9 @@ fntype_allocate(VALUE klass)
fnInfo->type.ffiType = &ffi_type_pointer;
fnInfo->type.nativeType = NATIVE_FUNCTION;
- fnInfo->rbReturnType = Qnil;
- fnInfo->rbParameterTypes = Qnil;
- fnInfo->rbEnums = Qnil;
+ RB_OBJ_WRITE(obj, &fnInfo->rbReturnType, Qnil);
+ RB_OBJ_WRITE(obj, &fnInfo->rbParameterTypes, Qnil);
+ RB_OBJ_WRITE(obj, &fnInfo->rbEnums, Qnil);
fnInfo->invoke = rbffi_CallFunction;
fnInfo->closurePool = NULL;
@@ -110,6 +113,23 @@ fntype_free(void *data)
xfree(fnInfo);
}
+static size_t
+fntype_memsize(const void *data)
+{
+ const FunctionType *fnInfo = (const FunctionType *)data;
+
+ size_t memsize = sizeof(FunctionType);
+ memsize += fnInfo->callbackCount * sizeof(VALUE);
+
+ memsize += fnInfo->parameterCount * (
+ sizeof(*fnInfo->parameterTypes)
+ + sizeof(ffi_type *)
+ + sizeof(*fnInfo->nativeParameterTypes)
+ );
+
+ return memsize;
+}
+
/*
* call-seq: initialize(return_type, param_types, options={})
* @param [Type, Symbol] return_type return type for the function
@@ -147,8 +167,8 @@ fntype_initialize(int argc, VALUE* argv, VALUE self)
fnInfo->parameterTypes = xcalloc(fnInfo->parameterCount, sizeof(*fnInfo->parameterTypes));
fnInfo->ffiParameterTypes = xcalloc(fnInfo->parameterCount, sizeof(ffi_type *));
fnInfo->nativeParameterTypes = xcalloc(fnInfo->parameterCount, sizeof(*fnInfo->nativeParameterTypes));
- fnInfo->rbParameterTypes = rb_ary_new2(fnInfo->parameterCount);
- fnInfo->rbEnums = rbEnums;
+ RB_OBJ_WRITE(self, &fnInfo->rbParameterTypes, rb_ary_new2(fnInfo->parameterCount));
+ RB_OBJ_WRITE(self, &fnInfo->rbEnums, rbEnums);
fnInfo->blocking = RTEST(rbBlocking);
fnInfo->hasStruct = false;
@@ -163,7 +183,7 @@ fntype_initialize(int argc, VALUE* argv, VALUE self)
if (rb_obj_is_kind_of(type, rbffi_FunctionTypeClass)) {
REALLOC_N(fnInfo->callbackParameters, VALUE, fnInfo->callbackCount + 1);
- fnInfo->callbackParameters[fnInfo->callbackCount++] = type;
+ RB_OBJ_WRITE(self, &fnInfo->callbackParameters[fnInfo->callbackCount++], type);
}
if (rb_obj_is_kind_of(type, rbffi_StructByValueClass)) {
@@ -176,7 +196,7 @@ fntype_initialize(int argc, VALUE* argv, VALUE self)
fnInfo->nativeParameterTypes[i] = fnInfo->parameterTypes[i]->nativeType;
}
- fnInfo->rbReturnType = rbffi_Type_Lookup(rbReturnType);
+ RB_OBJ_WRITE(self, &fnInfo->rbReturnType, rbffi_Type_Lookup(rbReturnType));
if (!RTEST(fnInfo->rbReturnType)) {
VALUE typeName = rb_funcall2(rbReturnType, rb_intern("inspect"), 0, NULL);
rb_raise(rb_eTypeError, "Invalid return type (%s)", RSTRING_PTR(typeName));
diff --git a/spec/ffi/function_type_spec.rb b/spec/ffi/function_type_spec.rb
new file mode 100644
index 0000000..54e1c48
--- /dev/null
+++ b/spec/ffi/function_type_spec.rb
@@ -0,0 +1,27 @@
+#
+# This file is part of ruby-ffi.
+# For licensing, see LICENSE.SPECS
+#
+
+require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
+
+describe "FFI::FunctionType", skip: RUBY_ENGINE != "ruby" do
+ it 'is initialized with return type and a list of parameter types' do
+ function_type = FFI::FunctionType.new(:int, [ :char, :ulong ])
+ expect(function_type.result_type).to be == FFI::Type::Builtin::INT
+ expect(function_type.param_types).to be == [ FFI::Type::Builtin::CHAR, FFI::Type::Builtin::ULONG ]
+ end
+
+ it 'has a memsize function' do
+ base_size = ObjectSpace.memsize_of(Object.new)
+
+ function_type = FFI::FunctionType.new(:int, [])
+ size = ObjectSpace.memsize_of(function_type)
+ expect(size).to be > base_size
+
+ base_size = size
+ function_type = FFI::FunctionType.new(:int, [:char])
+ size = ObjectSpace.memsize_of(function_type)
+ expect(size).to be > base_size
+ end
+end
diff --git a/spec/ffi/spec_helper.rb b/spec/ffi/spec_helper.rb
index bb12050..e9ced3d 100644
--- a/spec/ffi/spec_helper.rb
+++ b/spec/ffi/spec_helper.rb
@@ -5,6 +5,7 @@
require_relative 'fixtures/compile'
require 'timeout'
+require 'objspace'
RSpec.configure do |c|
c.filter_run_excluding :broken => true