From fae39d82cbf8acc9ecb52dda12048876f09ca898 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 6 Mar 2023 09:51:36 +0100 Subject: 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. --- ext/ffi_c/FunctionInfo.c | 50 +++++++++++++++++++++++++++++------------- spec/ffi/function_type_spec.rb | 27 +++++++++++++++++++++++ spec/ffi/spec_helper.rb | 1 + 3 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 spec/ffi/function_type_spec.rb 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 -- cgit v1.2.1