diff options
78 files changed, 2886 insertions, 718 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 376c068..fc48721 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,8 @@ name: CI on: [push, pull_request] +permissions: + contents: read + jobs: system-libffi: # Run on latest MRI with explicit selection of system or builtin libffi @@ -29,7 +32,7 @@ jobs: env: # work around misconfiguration of libffi on MacOS with homebrew PKG_CONFIG_PATH: ${{ env.PKG_CONFIG_PATH }}:/usr/local/opt/libffi/lib/pkgconfig - - run: bundle exec rake test + - run: bundle exec rake test FFI_TEST_GC=true - run: bundle exec rake types_conf && git --no-pager diff specs: @@ -39,12 +42,10 @@ jobs: fail-fast: false matrix: os: [ ubuntu, macos, windows ] - ruby: [ 2.3, 2.4, 2.5, 2.6, 2.7, ruby-head, truffleruby-head, jruby-head ] + ruby: [ 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, ruby-head, truffleruby-head, jruby-head ] exclude: - os: windows ruby: truffleruby-head - - os: windows - ruby: 2.3 # compilation fails runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v2 @@ -59,8 +60,7 @@ jobs: - run: bundle exec rake libffi - run: bundle exec rake compile - - run: bundle exec rake test - continue-on-error: ${{ matrix.ruby == 'jruby-head' }} + - run: bundle exec rake test FFI_TEST_GC=true - run: bundle exec rake bench:all if: ${{ matrix.ruby != 'truffleruby-head' && matrix.ruby != 'jruby-head' }} @@ -12,14 +12,17 @@ build *.so *.[oa] core -lib/ffi/types.conf -lib/ffi_c.bundle -lib/ffi_c.so +/lib/ffi/types.conf +/lib/ffi_c.bundle +/lib/ffi_c.so vendor .bundle Gemfile.lock types_log *.gem embed-test.rb.log -spec/ffi/embed-test/ext/Makefile -spec/ffi/embed-test/ext/embed_test.bundle +/spec/ffi/embed-test/ext/Makefile +/spec/ffi/embed-test/ext/embed_test.bundle +/ext/ffi_c/Makefile +/ext/ffi_c/extconf.h +/ext/ffi_c/mkmf.log diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 518ff08..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -dist: trusty -group: beta -language: generic -git: - submodules: false -matrix: - include: - - name: powerpc - before_install: | - docker run --rm --privileged multiarch/qemu-user-static:register --reset && - docker build --rm -t ffi-powerpc -f spec/env/Dockerfile.powerpc . - script: | - docker run --rm -t -v `pwd`:/ffi ffi-powerpc - - name: armhf - before_install: | - docker run --rm --privileged multiarch/qemu-user-static:register --reset && - docker build --rm -t ffi-armhf -f spec/env/Dockerfile.armhf . - script: | - docker run --rm -t -v `pwd`:/ffi ffi-armhf @@ -1,4 +1,4 @@ -# Ruby-FFI https://github.com/ffi/ffi/wiki [![Build Status](https://travis-ci.com/ffi/ffi.svg?branch=master)](https://travis-ci.com/ffi/ffi) [![Build status Windows](https://ci.appveyor.com/api/projects/status/r8wxn1sd4s794gg1/branch/master?svg=true)](https://ci.appveyor.com/project/larskanis/ffi-aofqa/branch/master) +# Ruby-FFI https://github.com/ffi/ffi/wiki ## Description @@ -15,6 +15,7 @@ using Ruby-FFI](https://github.com/ffi/ffi/wiki/why-use-ffi). * C structs (also nested), enums and global variables * Callbacks from C to Ruby * Automatic garbage collection of native memory +* Usable in Ractor ## Synopsis @@ -62,7 +63,7 @@ On JRuby and TruffleRuby, there are no requirements to install the FFI gem, and From rubygems: [sudo] gem install ffi - + From a Gemfile using git or GitHub gem 'ffi', github: 'ffi/ffi', submodules: true @@ -31,8 +31,7 @@ CLEAN.include 'spec/ffi/fixtures/*.o' CLEAN.include 'spec/ffi/embed-test/ext/*.{o,def}' CLEAN.include 'spec/ffi/embed-test/ext/Makefile' CLEAN.include "pkg/ffi-*-{mingw32,java}" -CLEAN.include 'lib/1.*' -CLEAN.include 'lib/2.*' +CLEAN.include 'lib/{2,3}.*' # clean all shipped files, that are not in git CLEAN.include( diff --git a/ext/ffi_c/AbstractMemory.c b/ext/ffi_c/AbstractMemory.c index 1a7fcde..49da32e 100644 --- a/ext/ffi_c/AbstractMemory.c +++ b/ext/ffi_c/AbstractMemory.c @@ -55,21 +55,30 @@ # define RB_OBJ_STRING(obj) StringValueCStr(obj) #endif +static size_t memsize(const void *data); static inline char* memory_address(VALUE self); VALUE rbffi_AbstractMemoryClass = Qnil; static VALUE NullPointerErrorClass = Qnil; static ID id_to_ptr = 0, id_plus = 0, id_call = 0; -static VALUE -memory_allocate(VALUE klass) -{ - AbstractMemory* memory; - VALUE obj; - obj = Data_Make_Struct(klass, AbstractMemory, NULL, -1, memory); - memory->flags = MEM_RD | MEM_WR; +const rb_data_type_t rbffi_abstract_memory_data_type = { /* extern */ + .wrap_struct_name = "FFI::AbstractMemory", + .function = { + .dmark = NULL, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = memsize, + }, + // 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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; - return obj; +static size_t +memsize(const void *data) +{ + return sizeof(AbstractMemory); } + #define VAL(x, swap) (unlikely(((memory->flags & MEM_SWAP) != 0)) ? swap((x)) : (x)) #define NUM_OP(name, type, toNative, fromNative, swap) \ @@ -87,7 +96,7 @@ static VALUE \ memory_put_##name(VALUE self, VALUE offset, VALUE value) \ { \ AbstractMemory* memory; \ - Data_Get_Struct(self, AbstractMemory, memory); \ + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, memory); \ memory_op_put_##name(memory, NUM2LONG(offset), value); \ return self; \ } \ @@ -96,7 +105,7 @@ static VALUE \ memory_write_##name(VALUE self, VALUE value) \ { \ AbstractMemory* memory; \ - Data_Get_Struct(self, AbstractMemory, memory); \ + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, memory); \ memory_op_put_##name(memory, 0, value); \ return self; \ } \ @@ -115,7 +124,7 @@ static VALUE \ memory_get_##name(VALUE self, VALUE offset) \ { \ AbstractMemory* memory; \ - Data_Get_Struct(self, AbstractMemory, memory); \ + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, memory); \ return memory_op_get_##name(memory, NUM2LONG(offset)); \ } \ static VALUE memory_read_##name(VALUE self); \ @@ -123,7 +132,7 @@ static VALUE \ memory_read_##name(VALUE self) \ { \ AbstractMemory* memory; \ - Data_Get_Struct(self, AbstractMemory, memory); \ + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, memory); \ return memory_op_get_##name(memory, 0); \ } \ static MemoryOp memory_op_##name = { memory_op_get_##name, memory_op_put_##name }; \ @@ -141,7 +150,7 @@ memory_put_array_of_##name(VALUE self, VALUE offset, VALUE ary) \ if (likely(count > 0)) checkWrite(memory); \ checkBounds(memory, off, count * sizeof(type)); \ for (i = 0; i < count; i++) { \ - type tmp = (type) VAL(toNative(RARRAY_PTR(ary)[i]), swap); \ + type tmp = (type) VAL(toNative(RARRAY_AREF(ary, i)), swap); \ memcpy(memory->address + off + (i * sizeof(type)), &tmp, sizeof(tmp)); \ } \ return self; \ @@ -307,6 +316,7 @@ static VALUE memory_clear(VALUE self) { AbstractMemory* ptr = MEMORY(self); + checkWrite(ptr); memset(ptr->address, 0, ptr->size); return self; } @@ -321,7 +331,7 @@ memory_size(VALUE self) { AbstractMemory* ptr; - Data_Get_Struct(self, AbstractMemory, ptr); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); return LONG2NUM(ptr->size); } @@ -344,8 +354,8 @@ memory_get(VALUE self, VALUE type_name, VALUE offset) nType = rbffi_Type_Lookup(type_name); if(NIL_P(nType)) goto undefined_type; - Data_Get_Struct(self, AbstractMemory, ptr); - Data_Get_Struct(nType, Type, type); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); + TypedData_Get_Struct(nType, Type, &rbffi_type_data_type, type); MemoryOp *op = get_memory_op(type); if(op == NULL) goto undefined_type; @@ -376,8 +386,8 @@ memory_put(VALUE self, VALUE type_name, VALUE offset, VALUE value) nType = rbffi_Type_Lookup(type_name); if(NIL_P(nType)) goto undefined_type; - Data_Get_Struct(self, AbstractMemory, ptr); - Data_Get_Struct(nType, Type, type); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); + TypedData_Get_Struct(nType, Type, &rbffi_type_data_type, type); MemoryOp *op = get_memory_op(type); if(op == NULL) goto undefined_type; @@ -442,7 +452,7 @@ memory_get_array_of_string(int argc, VALUE* argv, VALUE self) count = (countnum == Qnil ? 0 : NUM2INT(countnum)); retVal = rb_ary_new2(count); - Data_Get_Struct(self, AbstractMemory, ptr); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); checkRead(ptr); if (countnum != Qnil) { @@ -633,7 +643,7 @@ memory_type_size(VALUE self) { AbstractMemory* ptr; - Data_Get_Struct(self, AbstractMemory, ptr); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); return INT2NUM(ptr->typeSize); } @@ -651,7 +661,7 @@ memory_aref(VALUE self, VALUE idx) AbstractMemory* ptr; VALUE rbOffset = Qnil; - Data_Get_Struct(self, AbstractMemory, ptr); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); rbOffset = ULONG2NUM(NUM2ULONG(idx) * ptr->typeSize); @@ -661,7 +671,9 @@ memory_aref(VALUE self, VALUE idx) static inline char* memory_address(VALUE obj) { - return ((AbstractMemory *) DATA_PTR(obj))->address; + AbstractMemory *mem; + TypedData_Get_Struct(obj, AbstractMemory, &rbffi_abstract_memory_data_type, mem); + return mem->address; } static VALUE @@ -669,24 +681,33 @@ memory_copy_from(VALUE self, VALUE rbsrc, VALUE rblen) { AbstractMemory* dst; - Data_Get_Struct(self, AbstractMemory, dst); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, dst); - memcpy(dst->address, rbffi_AbstractMemory_Cast(rbsrc, rbffi_AbstractMemoryClass)->address, NUM2INT(rblen)); + memcpy(dst->address, rbffi_AbstractMemory_Cast(rbsrc, &rbffi_abstract_memory_data_type)->address, NUM2INT(rblen)); return self; } -AbstractMemory* -rbffi_AbstractMemory_Cast(VALUE obj, VALUE klass) +/* + * call-seq: + * res.freeze + * + * Freeze the AbstractMemory object and unset the writable flag. + */ +static VALUE +memory_freeze(VALUE self) { - if (rb_obj_is_kind_of(obj, klass)) { - AbstractMemory* memory; - Data_Get_Struct(obj, AbstractMemory, memory); - return memory; - } + AbstractMemory* ptr = MEMORY(self); + ptr->flags &= ~MEM_WR; + return rb_call_super(0, NULL); +} - rb_raise(rb_eArgError, "Invalid Memory object"); - return NULL; +AbstractMemory* +rbffi_AbstractMemory_Cast(VALUE obj, const rb_data_type_t *data_type) +{ + AbstractMemory* memory; + TypedData_Get_Struct(obj, AbstractMemory, data_type, memory); + return memory; } void @@ -781,7 +802,7 @@ rbffi_AbstractMemory_Init(VALUE moduleFFI) * Document-variable: FFI::AbstractMemory */ rb_global_variable(&rbffi_AbstractMemoryClass); - rb_define_alloc_func(classMemory, memory_allocate); + rb_undef_alloc_func(classMemory); NullPointerErrorClass = rb_define_class_under(moduleFFI, "NullPointerError", rb_eRuntimeError); /* Document-variable: NullPointerError */ @@ -1096,6 +1117,7 @@ rbffi_AbstractMemory_Init(VALUE moduleFFI) rb_define_method(classMemory, "type_size", memory_type_size, 0); rb_define_method(classMemory, "[]", memory_aref, 1); rb_define_method(classMemory, "__copy_from__", memory_copy_from, 2); + rb_define_method(classMemory, "freeze", memory_freeze, 0 ); id_to_ptr = rb_intern("to_ptr"); id_call = rb_intern("call"); diff --git a/ext/ffi_c/AbstractMemory.h b/ext/ffi_c/AbstractMemory.h index 1119288..5973bac 100644 --- a/ext/ffi_c/AbstractMemory.h +++ b/ext/ffi_c/AbstractMemory.h @@ -86,12 +86,13 @@ struct AbstractMemory_ { }; +extern const rb_data_type_t rbffi_abstract_memory_data_type; extern VALUE rbffi_AbstractMemoryClass; extern MemoryOps rbffi_AbstractMemoryOps; extern void rbffi_AbstractMemory_Init(VALUE ffiModule); -extern AbstractMemory* rbffi_AbstractMemory_Cast(VALUE obj, VALUE klass); +extern AbstractMemory* rbffi_AbstractMemory_Cast(VALUE obj, const rb_data_type_t *data_type); extern void rbffi_AbstractMemory_Error(AbstractMemory *, int op); @@ -161,7 +162,7 @@ get_memory_op(Type* type) } } -#define MEMORY(obj) rbffi_AbstractMemory_Cast((obj), rbffi_AbstractMemoryClass) +#define MEMORY(obj) rbffi_AbstractMemory_Cast((obj), &rbffi_abstract_memory_data_type) #define MEMORY_PTR(obj) MEMORY((obj))->address #define MEMORY_LEN(obj) MEMORY((obj))->size diff --git a/ext/ffi_c/ArrayType.c b/ext/ffi_c/ArrayType.c index bfd666a..b1cbcea 100644 --- a/ext/ffi_c/ArrayType.c +++ b/ext/ffi_c/ArrayType.c @@ -29,12 +29,30 @@ #include <ruby.h> #include <ffi.h> +#include "compat.h" #include "ArrayType.h" static VALUE array_type_s_allocate(VALUE klass); static VALUE array_type_initialize(VALUE self, VALUE rbComponentType, VALUE rbLength); -static void array_type_mark(ArrayType *); -static void array_type_free(ArrayType *); +static void array_type_mark(void *); +static void array_type_compact(void *); +static void array_type_free(void *); +static size_t array_type_memsize(const void *); + +const rb_data_type_t rbffi_array_type_data_type = { /* extern */ + .wrap_struct_name = "FFI::ArrayType", + .function = { + .dmark = array_type_mark, + .dfree = array_type_free, + .dsize = array_type_memsize, + ffi_compact_callback( array_type_compact ) + }, + .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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + VALUE rbffi_ArrayTypeClass = Qnil; @@ -44,32 +62,50 @@ array_type_s_allocate(VALUE klass) ArrayType* array; VALUE obj; - obj = Data_Make_Struct(klass, ArrayType, array_type_mark, array_type_free, array); + obj = TypedData_Make_Struct(klass, ArrayType, &rbffi_array_type_data_type, array); array->base.nativeType = NATIVE_ARRAY; array->base.ffiType = xcalloc(1, sizeof(*array->base.ffiType)); array->base.ffiType->type = FFI_TYPE_STRUCT; array->base.ffiType->size = 0; array->base.ffiType->alignment = 0; - array->rbComponentType = Qnil; + RB_OBJ_WRITE(obj, &array->rbComponentType, Qnil); return obj; } static void -array_type_mark(ArrayType *array) +array_type_mark(void *data) +{ + ArrayType *array = (ArrayType *)data; + rb_gc_mark_movable(array->rbComponentType); +} + +static void +array_type_compact(void *data) { - rb_gc_mark(array->rbComponentType); + ArrayType *array = (ArrayType *)data; + ffi_gc_location(array->rbComponentType); } static void -array_type_free(ArrayType *array) +array_type_free(void *data) { + ArrayType *array = (ArrayType *)data; xfree(array->base.ffiType); xfree(array->ffiTypes); xfree(array); } +static size_t +array_type_memsize(const void *data) +{ + const ArrayType *array = (const ArrayType *)data; + size_t memsize = sizeof(ArrayType); + memsize += array->length * sizeof(*array->ffiTypes); + memsize += sizeof(*array->base.ffiType); + return memsize; +} /* * call-seq: initialize(component_type, length) @@ -84,12 +120,12 @@ array_type_initialize(VALUE self, VALUE rbComponentType, VALUE rbLength) ArrayType* array; int i; - Data_Get_Struct(self, ArrayType, array); + TypedData_Get_Struct(self, ArrayType, &rbffi_array_type_data_type, array); array->length = NUM2UINT(rbLength); - array->rbComponentType = rbComponentType; - Data_Get_Struct(rbComponentType, Type, array->componentType); - + RB_OBJ_WRITE(self, &array->rbComponentType, rbComponentType); + TypedData_Get_Struct(rbComponentType, Type, &rbffi_type_data_type, array->componentType); + array->ffiTypes = xcalloc(array->length + 1, sizeof(*array->ffiTypes)); array->base.ffiType->elements = array->ffiTypes; array->base.ffiType->size = array->componentType->ffiType->size * array->length; @@ -112,7 +148,7 @@ array_type_length(VALUE self) { ArrayType* array; - Data_Get_Struct(self, ArrayType, array); + TypedData_Get_Struct(self, ArrayType, &rbffi_array_type_data_type, array); return UINT2NUM(array->length); } @@ -127,7 +163,7 @@ array_type_element_type(VALUE self) { ArrayType* array; - Data_Get_Struct(self, ArrayType, array); + TypedData_Get_Struct(self, ArrayType, &rbffi_array_type_data_type, array); return array->rbComponentType; } diff --git a/ext/ffi_c/ArrayType.h b/ext/ffi_c/ArrayType.h index 356ffb1..9b1eba0 100644 --- a/ext/ffi_c/ArrayType.h +++ b/ext/ffi_c/ArrayType.h @@ -48,6 +48,7 @@ typedef struct ArrayType_ { } ArrayType; extern void rbffi_ArrayType_Init(VALUE moduleFFI); +extern const rb_data_type_t rbffi_array_type_data_type; extern VALUE rbffi_ArrayTypeClass; diff --git a/ext/ffi_c/Buffer.c b/ext/ffi_c/Buffer.c index b5f39a4..78339f3 100644 --- a/ext/ffi_c/Buffer.c +++ b/ext/ffi_c/Buffer.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2008-2010 Wayne Meissner * Copyright (C) 2009 Aman Gupta <aman@tmm1.net> - * + * * Copyright (c) 2008-2013, Ruby FFI project contributors * All rights reserved. * @@ -39,7 +39,7 @@ #define BUFFER_EMBED_MAXLEN (8) typedef struct Buffer { AbstractMemory memory; - + union { VALUE rbParent; /* link to parent buffer */ char* storage; /* start of malloc area */ @@ -49,9 +49,40 @@ typedef struct Buffer { static VALUE buffer_allocate(VALUE klass); static VALUE buffer_initialize(int argc, VALUE* argv, VALUE self); -static void buffer_release(Buffer* ptr); -static void buffer_mark(Buffer* ptr); +static void buffer_release(void *data); +static void buffer_mark(void *data); +static void buffer_compact(void *data); static VALUE buffer_free(VALUE self); +static size_t allocated_buffer_memsize(const void *data); +static size_t buffer_memsize(const void *data); + +static const rb_data_type_t buffer_data_type = { + .wrap_struct_name = "FFI::Buffer", + .function = { + .dmark = buffer_mark, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = buffer_memsize, + ffi_compact_callback( buffer_compact ) + }, + .parent = &rbffi_abstract_memory_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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + +static const rb_data_type_t allocated_buffer_data_type = { + .wrap_struct_name = "FFI::Buffer(allocated)", + .function = { + .dmark = NULL, + .dfree = buffer_release, + .dsize = allocated_buffer_memsize, + }, + .parent = &buffer_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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + static VALUE BufferClass = Qnil; @@ -61,21 +92,22 @@ buffer_allocate(VALUE klass) Buffer* buffer; VALUE obj; - obj = Data_Make_Struct(klass, Buffer, NULL, buffer_release, buffer); - buffer->data.rbParent = Qnil; + obj = TypedData_Make_Struct(klass, Buffer, &allocated_buffer_data_type, buffer); + RB_OBJ_WRITE(obj, &buffer->data.rbParent, Qnil); buffer->memory.flags = MEM_RD | MEM_WR; return obj; } static void -buffer_release(Buffer* ptr) +buffer_release(void *data) { + Buffer *ptr = (Buffer *)data; if ((ptr->memory.flags & MEM_EMBED) == 0 && ptr->data.storage != NULL) { xfree(ptr->data.storage); ptr->data.storage = NULL; } - + xfree(ptr); } @@ -95,7 +127,7 @@ buffer_initialize(int argc, VALUE* argv, VALUE self) Buffer* p; int nargs; - Data_Get_Struct(self, Buffer, p); + TypedData_Get_Struct(self, Buffer, &buffer_data_type, p); nargs = rb_scan_args(argc, argv, "12", &rbSize, &rbCount, &rbClear); p->memory.typeSize = rbffi_type_size(rbSize); @@ -110,11 +142,11 @@ buffer_initialize(int argc, VALUE* argv, VALUE self) /* ensure the memory is aligned on at least a 8 byte boundary */ p->memory.address = (void *) (((uintptr_t) p->data.storage + 0x7) & (uintptr_t) ~0x7ULL); - + if (p->memory.size > 0 && (nargs < 3 || RTEST(rbClear))) { memset(p->memory.address, 0, p->memory.size); } - + } else { p->memory.flags |= MEM_EMBED; p->memory.address = (void *) &p->data.embed[0]; @@ -137,9 +169,9 @@ buffer_initialize_copy(VALUE self, VALUE other) { AbstractMemory* src; Buffer* dst; - - Data_Get_Struct(self, Buffer, dst); - src = rbffi_AbstractMemory_Cast(other, BufferClass); + + TypedData_Get_Struct(self, Buffer, &buffer_data_type, dst); + src = rbffi_AbstractMemory_Cast(other, &buffer_data_type); if ((dst->memory.flags & MEM_EMBED) == 0 && dst->data.storage != NULL) { xfree(dst->data.storage); } @@ -148,11 +180,11 @@ buffer_initialize_copy(VALUE self, VALUE other) rb_raise(rb_eNoMemError, "failed to allocate memory size=%lu bytes", src->size); return Qnil; } - + dst->memory.address = (void *) (((uintptr_t) dst->data.storage + 0x7) & (uintptr_t) ~0x7ULL); dst->memory.size = src->size; dst->memory.typeSize = src->typeSize; - + /* finally, copy the actual buffer contents */ memcpy(dst->memory.address, src->address, src->size); @@ -171,16 +203,16 @@ slice(VALUE self, long offset, long len) Buffer* ptr; Buffer* result; VALUE obj = Qnil; - - Data_Get_Struct(self, Buffer, ptr); + + TypedData_Get_Struct(self, Buffer, &buffer_data_type, ptr); checkBounds(&ptr->memory, offset, len); - obj = Data_Make_Struct(BufferClass, Buffer, buffer_mark, -1, result); + obj = TypedData_Make_Struct(BufferClass, Buffer, &buffer_data_type, result); result->memory.address = ptr->memory.address + offset; result->memory.size = len; result->memory.flags = ptr->memory.flags; result->memory.typeSize = ptr->memory.typeSize; - result->data.rbParent = self; + RB_OBJ_WRITE(obj, &result->data.rbParent, self); return obj; } @@ -197,7 +229,7 @@ buffer_plus(VALUE self, VALUE rbOffset) Buffer* ptr; long offset = NUM2LONG(rbOffset); - Data_Get_Struct(self, Buffer, ptr); + TypedData_Get_Struct(self, Buffer, &buffer_data_type, ptr); return slice(self, offset, ptr->memory.size - offset); } @@ -226,10 +258,10 @@ buffer_inspect(VALUE self) char tmp[100]; Buffer* ptr; - Data_Get_Struct(self, Buffer, ptr); + TypedData_Get_Struct(self, Buffer, &buffer_data_type, ptr); snprintf(tmp, sizeof(tmp), "#<FFI:Buffer:%p address=%p size=%ld>", ptr, ptr->memory.address, ptr->memory.size); - + return rb_str_new2(tmp); } @@ -255,7 +287,7 @@ buffer_order(int argc, VALUE* argv, VALUE self) { Buffer* ptr; - Data_Get_Struct(self, Buffer, ptr); + TypedData_Get_Struct(self, Buffer, &buffer_data_type, ptr); if (argc == 0) { int order = (ptr->memory.flags & MEM_SWAP) == 0 ? BYTE_ORDER : SWAPPED_ORDER; return order == BIG_ENDIAN ? ID2SYM(rb_intern("big")) : ID2SYM(rb_intern("little")); @@ -279,7 +311,7 @@ buffer_order(int argc, VALUE* argv, VALUE self) Buffer* p2; VALUE retval = slice(self, 0, ptr->memory.size); - Data_Get_Struct(retval, Buffer, p2); + TypedData_Get_Struct(retval, Buffer, &buffer_data_type, p2); p2->memory.flags |= MEM_SWAP; return retval; } @@ -294,7 +326,7 @@ buffer_free(VALUE self) { Buffer* ptr; - Data_Get_Struct(self, Buffer, ptr); + TypedData_Get_Struct(self, Buffer, &buffer_data_type, ptr); if ((ptr->memory.flags & MEM_EMBED) == 0 && ptr->data.storage != NULL) { xfree(ptr->data.storage); ptr->data.storage = NULL; @@ -304,9 +336,34 @@ buffer_free(VALUE self) } static void -buffer_mark(Buffer* ptr) +buffer_mark(void *data) +{ + Buffer *ptr = (Buffer *)data; + rb_gc_mark_movable(ptr->data.rbParent); +} + +static void +buffer_compact(void *data) +{ + Buffer *ptr = (Buffer *)data; + ffi_gc_location(ptr->data.rbParent); +} + +static size_t +buffer_memsize(const void *data) { - rb_gc_mark(ptr->data.rbParent); + return sizeof(Buffer); +} + +static size_t +allocated_buffer_memsize(const void *data) +{ + const Buffer *ptr = (const Buffer *)data; + size_t memsize = sizeof(Buffer); + if ((ptr->memory.flags & MEM_EMBED) == 0 && ptr->data.storage != NULL) { + memsize += ptr->memory.size; + } + return memsize; } void @@ -348,7 +405,7 @@ rbffi_Buffer_Init(VALUE moduleFFI) rb_define_alias(rb_singleton_class(BufferClass), "new_in", "alloc_in"); rb_define_alias(rb_singleton_class(BufferClass), "new_out", "alloc_out"); rb_define_alias(rb_singleton_class(BufferClass), "new_inout", "alloc_inout"); - + rb_define_method(BufferClass, "initialize", buffer_initialize, -1); rb_define_method(BufferClass, "initialize_copy", buffer_initialize_copy, 1); rb_define_method(BufferClass, "order", buffer_order, -1); diff --git a/ext/ffi_c/Call.c b/ext/ffi_c/Call.c index bd6c277..ccb711c 100644 --- a/ext/ffi_c/Call.c +++ b/ext/ffi_c/Call.c @@ -420,11 +420,15 @@ getPointer(VALUE value, int type) { if (likely(type == T_DATA && rb_obj_is_kind_of(value, rbffi_AbstractMemoryClass))) { - return ((AbstractMemory *) DATA_PTR(value))->address; + AbstractMemory *mem; + TypedData_Get_Struct(value, AbstractMemory, &rbffi_abstract_memory_data_type, mem); + return mem->address; } else if (type == T_DATA && rb_obj_is_kind_of(value, rbffi_StructClass)) { - AbstractMemory* memory = ((Struct *) DATA_PTR(value))->pointer; + Struct* s; + TypedData_Get_Struct(value, Struct, &rbffi_struct_data_type, s); + AbstractMemory* memory = s->pointer; return memory != NULL ? memory->address : NULL; } else if (type == T_STRING) { @@ -439,7 +443,9 @@ getPointer(VALUE value, int type) VALUE ptr = rb_funcall2(value, id_to_ptr, 0, NULL); if (rb_obj_is_kind_of(ptr, rbffi_AbstractMemoryClass) && TYPE(ptr) == T_DATA) { - return ((AbstractMemory *) DATA_PTR(ptr))->address; + AbstractMemory *mem; + TypedData_Get_Struct(ptr, AbstractMemory, &rbffi_abstract_memory_data_type, mem); + return mem->address; } rb_raise(rb_eArgError, "to_ptr returned an invalid pointer"); } @@ -466,14 +472,16 @@ callback_param(VALUE proc, VALUE cbInfo) /* Handle Function pointers here */ if (rb_obj_is_kind_of(proc, rbffi_FunctionClass)) { AbstractMemory* ptr; - Data_Get_Struct(proc, AbstractMemory, ptr); + TypedData_Get_Struct(proc, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); return ptr->address; } callback = rbffi_Function_ForProc(cbInfo, proc); RB_GC_GUARD(callback); - return ((AbstractMemory *) DATA_PTR(callback))->address; + AbstractMemory *mem; + TypedData_Get_Struct(callback, AbstractMemory, &rbffi_abstract_memory_data_type, mem); + return mem->address; } diff --git a/ext/ffi_c/DynamicLibrary.c b/ext/ffi_c/DynamicLibrary.c index 78b3de6..9096e74 100644 --- a/ext/ffi_c/DynamicLibrary.c +++ b/ext/ffi_c/DynamicLibrary.c @@ -50,17 +50,45 @@ typedef struct LibrarySymbol_ { Pointer base; - VALUE library; VALUE name; } LibrarySymbol; -static VALUE library_initialize(VALUE self, VALUE libname, VALUE libflags); -static void library_free(Library* lib); +static VALUE library_initialize(VALUE self, VALUE libname, VALUE libflags); +static void library_free(void *); +static size_t library_memsize(const void *); static VALUE symbol_allocate(VALUE klass); static VALUE symbol_new(VALUE library, void* address, VALUE name); -static void symbol_mark(LibrarySymbol* sym); +static void symbol_mark(void *data); +static void symbol_compact(void *data); +static size_t symbol_memsize(const void *data); + +static const rb_data_type_t rbffi_library_data_type = { + .wrap_struct_name = "FFI::DynamicLibrary", + .function = { + .dmark = NULL, + .dfree = library_free, + .dsize = library_memsize, + }, + // 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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + +static const rb_data_type_t library_symbol_data_type = { + .wrap_struct_name = "FFI::DynamicLibrary::Symbol", + .function = { + .dmark = symbol_mark, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = symbol_memsize, + ffi_compact_callback( symbol_compact ) + }, + .parent = &rbffi_pointer_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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; static VALUE LibraryClass = Qnil, SymbolClass = Qnil; @@ -80,7 +108,7 @@ static VALUE library_allocate(VALUE klass) { Library* library; - return Data_Make_Struct(klass, Library, NULL, library_free, library); + return TypedData_Make_Struct(klass, Library, &rbffi_library_data_type, library); } /* @@ -113,9 +141,9 @@ library_initialize(VALUE self, VALUE libname, VALUE libflags) Check_Type(libflags, T_FIXNUM); - Data_Get_Struct(self, Library, library); + TypedData_Get_Struct(self, Library, &rbffi_library_data_type, library); flags = libflags != Qnil ? NUM2UINT(libflags) : 0; - + library->handle = dl_open(libname != Qnil ? StringValueCStr(libname) : NULL, flags); if (library->handle == NULL) { char errmsg[1024]; @@ -133,7 +161,9 @@ library_initialize(VALUE self, VALUE libname, VALUE libflags) library->handle = RTLD_DEFAULT; } #endif - rb_iv_set(self, "@name", libname != Qnil ? libname : rb_str_new2("[current process]")); + rb_iv_set(self, "@name", libname != Qnil ? rb_str_new_frozen(libname) : rb_str_new2("[current process]")); + + rb_obj_freeze(self); return self; } @@ -144,9 +174,9 @@ library_dlsym(VALUE self, VALUE name) void* address = NULL; Check_Type(name, T_STRING); - Data_Get_Struct(self, Library, library); + TypedData_Get_Struct(self, Library, &rbffi_library_data_type, library); address = dl_sym(library->handle, StringValueCStr(name)); - + return address != NULL ? symbol_new(self, address, name) : Qnil; } @@ -163,8 +193,10 @@ library_dlerror(VALUE self) } static void -library_free(Library* library) +library_free(void *data) { + Library *library = (Library*)data; + /* dlclose() on MacOS tends to segfault - avoid it */ #ifndef __APPLE__ if (library->handle != NULL) { @@ -174,6 +206,12 @@ library_free(Library* library) xfree(library); } +static size_t +library_memsize(const void *data) +{ + return sizeof(Library); +} + #if (defined(_WIN32) || defined(__WIN32__)) && !defined(__CYGWIN__) static void* dl_open(const char* name, int flags) @@ -189,8 +227,19 @@ dl_open(const char* name, int flags) static void dl_error(char* buf, int size) { - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), - 0, buf, size, NULL); + // Get the last error code + DWORD error = GetLastError(); + + // Get the associated message + LPSTR message = NULL; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, error, 0, (LPSTR)&message, 0, NULL); + + // Update the passed in buffer + snprintf(buf, size, "Failed with error %d: %s", error, message); + + // Free the allocated message + LocalFree(message); } #endif @@ -198,10 +247,9 @@ static VALUE symbol_allocate(VALUE klass) { LibrarySymbol* sym; - VALUE obj = Data_Make_Struct(klass, LibrarySymbol, NULL, -1, sym); - sym->name = Qnil; - sym->library = Qnil; - sym->base.rbParent = Qnil; + VALUE obj = TypedData_Make_Struct(klass, LibrarySymbol, &library_symbol_data_type, sym); + RB_OBJ_WRITE(obj, &sym->base.rbParent, Qnil); + RB_OBJ_WRITE(obj, &sym->name, Qnil); return obj; } @@ -224,23 +272,39 @@ static VALUE symbol_new(VALUE library, void* address, VALUE name) { LibrarySymbol* sym; - VALUE obj = Data_Make_Struct(SymbolClass, LibrarySymbol, symbol_mark, -1, sym); + VALUE obj = TypedData_Make_Struct(SymbolClass, LibrarySymbol, &library_symbol_data_type, sym); sym->base.memory.address = address; sym->base.memory.size = LONG_MAX; sym->base.memory.typeSize = 1; sym->base.memory.flags = MEM_RD | MEM_WR; - sym->library = library; - sym->name = name; + RB_OBJ_WRITE(obj, &sym->base.rbParent, library); + RB_OBJ_WRITE(obj, &sym->name, rb_str_new_frozen(name)); + rb_obj_freeze(obj); return obj; } static void -symbol_mark(LibrarySymbol* sym) +symbol_mark(void *data) +{ + LibrarySymbol *sym = (LibrarySymbol *)data; + rb_gc_mark_movable(sym->base.rbParent); + rb_gc_mark_movable(sym->name); +} + +static void +symbol_compact(void *data) { - rb_gc_mark(sym->library); - rb_gc_mark(sym->name); + LibrarySymbol *sym = (LibrarySymbol *)data; + ffi_gc_location(sym->base.rbParent); + ffi_gc_location(sym->name); +} + +static size_t +symbol_memsize(const void *data) +{ + return sizeof(LibrarySymbol); } /* @@ -254,8 +318,8 @@ symbol_inspect(VALUE self) LibrarySymbol* sym; char buf[256]; - Data_Get_Struct(self, LibrarySymbol, sym); - snprintf(buf, sizeof(buf), "#<FFI::Library::Symbol name=%s address=%p>", + TypedData_Get_Struct(self, LibrarySymbol, &library_symbol_data_type, sym); + snprintf(buf, sizeof(buf), "#<FFI::DynamicLibrary::Symbol name=%s address=%p>", StringValueCStr(sym->name), sym->base.memory.address); return rb_str_new2(buf); } @@ -331,4 +395,3 @@ rbffi_DynamicLibrary_Init(VALUE moduleFFI) #undef DEF } - diff --git a/ext/ffi_c/Function.c b/ext/ffi_c/Function.c index 1a57591..7810056 100644 --- a/ext/ffi_c/Function.c +++ b/ext/ffi_c/Function.c @@ -42,6 +42,10 @@ #include <ruby.h> #include <ruby/thread.h> +#if HAVE_RB_EXT_RACTOR_SAFE +#include <ruby/ractor.h> +#endif + #include <ffi.h> #if defined(HAVE_NATIVETHREAD) && !defined(_WIN32) #include <pthread.h> @@ -65,6 +69,9 @@ #include "MethodHandle.h" #include "Function.h" +#define DEFER_ASYNC_CALLBACK 1 + +struct async_cb_dispatcher; typedef struct Function_ { Pointer base; FunctionType* info; @@ -73,10 +80,15 @@ typedef struct Function_ { Closure* closure; VALUE rbProc; VALUE rbFunctionInfo; +#if defined(DEFER_ASYNC_CALLBACK) + struct async_cb_dispatcher *dispatcher; +#endif } Function; -static void function_mark(Function *); -static void function_free(Function *); +static void function_mark(void *data); +static void function_compact(void *data); +static void function_free(void *data); +static size_t function_memsize(const void *data); static VALUE function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc); static void callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data); static bool callback_prep(void* ctx, void* code, Closure* closure, char* errmsg, size_t errmsgsize); @@ -84,9 +96,6 @@ static void* callback_with_gvl(void* data); static VALUE invoke_callback(VALUE data); static VALUE save_callback_exception(VALUE data, VALUE exc); -#define DEFER_ASYNC_CALLBACK 1 - - #if defined(DEFER_ASYNC_CALLBACK) static VALUE async_cb_event(void *); static VALUE async_cb_call(void *); @@ -95,11 +104,21 @@ static VALUE async_cb_call(void *); extern int ruby_thread_has_gvl_p(void); extern int ruby_native_thread_p(void); -VALUE rbffi_FunctionClass = Qnil; +static const rb_data_type_t function_data_type = { + .wrap_struct_name = "FFI::Function", + .function = { + .dmark = function_mark, + .dfree = function_free, + .dsize = function_memsize, + ffi_compact_callback( function_compact ) + }, + .parent = &rbffi_pointer_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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; -#if defined(DEFER_ASYNC_CALLBACK) -static VALUE async_cb_thread = Qnil; -#endif +VALUE rbffi_FunctionClass = Qnil; static ID id_call = 0, id_to_native = 0, id_from_native = 0, id_cbtable = 0, id_cb_ref = 0; @@ -110,7 +129,10 @@ struct gvl_callback { bool done; rbffi_frame_t *frame; #if defined(DEFER_ASYNC_CALLBACK) + struct async_cb_dispatcher *dispatcher; struct gvl_callback* next; + + /* Signal when the callback has finished and retval is set */ # ifndef _WIN32 pthread_cond_t async_cond; pthread_mutex_t async_mutex; @@ -122,16 +144,74 @@ struct gvl_callback { #if defined(DEFER_ASYNC_CALLBACK) -static struct gvl_callback* async_cb_list = NULL; +struct async_cb_dispatcher { + /* the Ractor-local dispatcher thread */ + VALUE thread; + + /* single linked list of pending callbacks */ + struct gvl_callback* async_cb_list; + + /* Signal new entries in async_cb_list */ # ifndef _WIN32 - static pthread_mutex_t async_cb_mutex = PTHREAD_MUTEX_INITIALIZER; - static pthread_cond_t async_cb_cond = PTHREAD_COND_INITIALIZER; + pthread_mutex_t async_cb_mutex; + pthread_cond_t async_cb_cond; # else - static HANDLE async_cb_cond; - static CRITICAL_SECTION async_cb_lock; + HANDLE async_cb_cond; + CRITICAL_SECTION async_cb_lock; # endif -#endif +}; + +#if HAVE_RB_EXT_RACTOR_SAFE +static void +async_cb_dispatcher_mark(void *ptr) +{ + struct async_cb_dispatcher *ctx = (struct async_cb_dispatcher *)ptr; + rb_gc_mark(ctx->thread); +} + +static void +async_cb_dispatcher_free(void *ptr) +{ + struct async_cb_dispatcher *ctx = (struct async_cb_dispatcher *)ptr; + xfree(ctx); +} + +struct rb_ractor_local_storage_type async_cb_dispatcher_key_type = { + async_cb_dispatcher_mark, + async_cb_dispatcher_free, +}; + +static rb_ractor_local_key_t async_cb_dispatcher_key; + +static struct async_cb_dispatcher * +async_cb_dispatcher_get(void) +{ + struct async_cb_dispatcher *ctx = (struct async_cb_dispatcher *)rb_ractor_local_storage_ptr(async_cb_dispatcher_key); + return ctx; +} + +static void +async_cb_dispatcher_set(struct async_cb_dispatcher *ctx) +{ + rb_ractor_local_storage_ptr_set(async_cb_dispatcher_key, ctx); +} +#else +// for ruby 2.x +static struct async_cb_dispatcher *async_cb_dispatcher = NULL; +static struct async_cb_dispatcher * +async_cb_dispatcher_get(void) +{ + return async_cb_dispatcher; +} + +static void +async_cb_dispatcher_set(struct async_cb_dispatcher *ctx) +{ + async_cb_dispatcher = ctx; +} +#endif +#endif static VALUE function_allocate(VALUE klass) @@ -139,28 +219,39 @@ function_allocate(VALUE klass) Function *fn; VALUE obj; - obj = Data_Make_Struct(klass, Function, function_mark, function_free, fn); + obj = TypedData_Make_Struct(klass, Function, &function_data_type, fn); fn->base.memory.flags = MEM_RD; - fn->base.rbParent = Qnil; - fn->rbProc = Qnil; - fn->rbFunctionInfo = Qnil; + RB_OBJ_WRITE(obj, &fn->base.rbParent, Qnil); + RB_OBJ_WRITE(obj, &fn->rbProc, Qnil); + RB_OBJ_WRITE(obj, &fn->rbFunctionInfo, Qnil); fn->autorelease = true; return obj; } static void -function_mark(Function *fn) +function_mark(void *data) { - rb_gc_mark(fn->base.rbParent); - rb_gc_mark(fn->rbProc); - rb_gc_mark(fn->rbFunctionInfo); + Function *fn = (Function *)data; + rb_gc_mark_movable(fn->base.rbParent); + rb_gc_mark_movable(fn->rbProc); + rb_gc_mark_movable(fn->rbFunctionInfo); } static void -function_free(Function *fn) +function_compact(void *data) { + Function *fn = (Function *)data; + ffi_gc_location(fn->base.rbParent); + ffi_gc_location(fn->rbProc); + ffi_gc_location(fn->rbFunctionInfo); +} + +static void +function_free(void *data) +{ + Function *fn = (Function *)data; if (fn->methodHandle != NULL) { rbffi_MethodHandle_Free(fn->methodHandle); } @@ -172,6 +263,20 @@ function_free(Function *fn) xfree(fn); } +static size_t +function_memsize(const void *data) +{ + const Function *fn = (const Function *)data; + size_t memsize = sizeof(Function); + + // Would be nice to better account for MethodHandle and Closure too. + if (fn->closure) { + memsize += sizeof(Closure); + } + + return memsize; +} + /* * @param [Type, Symbol] return_type return type for the function * @param [Array<Type, Symbol>] param_types array of parameters types @@ -254,7 +359,7 @@ rbffi_Function_ForProc(VALUE rbFunctionInfo, VALUE proc) /* If the first callback reference has the same function function signature, use it */ if (cbref != Qnil && CLASS_OF(cbref) == rbffi_FunctionClass) { Function* fp; - Data_Get_Struct(cbref, Function, fp); + TypedData_Get_Struct(cbref, Function, &function_data_type, fp); if (fp->rbFunctionInfo == rbFunctionInfo) { return cbref; } @@ -287,9 +392,7 @@ static void after_fork_callback(void) { /* Ensure that a new dispatcher thread is started in a forked process */ - async_cb_thread = Qnil; - pthread_mutex_init(&async_cb_mutex, NULL); - pthread_cond_init(&async_cb_cond, NULL); + async_cb_dispatcher_set(NULL); } #endif @@ -298,17 +401,17 @@ function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc) { Function* fn = NULL; - Data_Get_Struct(self, Function, fn); + TypedData_Get_Struct(self, Function, &function_data_type, fn); - fn->rbFunctionInfo = rbFunctionInfo; + RB_OBJ_WRITE(self, &fn->rbFunctionInfo, rbFunctionInfo); - Data_Get_Struct(fn->rbFunctionInfo, FunctionType, fn->info); + TypedData_Get_Struct(fn->rbFunctionInfo, FunctionType, &rbffi_fntype_data_type, fn->info); if (rb_obj_is_kind_of(rbProc, rbffi_PointerClass)) { Pointer* orig; - Data_Get_Struct(rbProc, Pointer, orig); + TypedData_Get_Struct(rbProc, Pointer, &rbffi_pointer_data_type, orig); fn->base.memory = orig->memory; - fn->base.rbParent = rbProc; + RB_OBJ_WRITE(self, &fn->base.rbParent, rbProc); } else if (rb_obj_is_kind_of(rbProc, rb_cProc) || rb_respond_to(rbProc, id_call)) { if (fn->info->closurePool == NULL) { @@ -319,17 +422,30 @@ function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc) } #if defined(DEFER_ASYNC_CALLBACK) - if (async_cb_thread == Qnil) { + { + struct async_cb_dispatcher *ctx = async_cb_dispatcher_get(); + if (ctx == NULL) { + ctx = (struct async_cb_dispatcher*)ALLOC(struct async_cb_dispatcher); + ctx->async_cb_list = NULL; #if !defined(_WIN32) - if( pthread_atfork(NULL, NULL, after_fork_callback) ){ - rb_warn("FFI: unable to register fork callback"); - } + pthread_mutex_init(&ctx->async_cb_mutex, NULL); + pthread_cond_init(&ctx->async_cb_cond, NULL); + if( pthread_atfork(NULL, NULL, after_fork_callback) ){ + rb_warn("FFI: unable to register fork callback"); + } +#else + InitializeCriticalSection(&ctx->async_cb_lock); + ctx->async_cb_cond = CreateEvent(NULL, FALSE, FALSE, NULL); #endif + ctx->thread = rb_thread_create(async_cb_event, ctx); - async_cb_thread = rb_thread_create(async_cb_event, NULL); - /* Name thread, for better debugging */ - rb_funcall(async_cb_thread, rb_intern("name="), 1, rb_str_new2("FFI Callback Dispatcher")); + /* Name thread, for better debugging */ + rb_funcall(ctx->thread, rb_intern("name="), 1, rb_str_new2("FFI Callback Dispatcher")); + + async_cb_dispatcher_set(ctx); + } + fn->dispatcher = ctx; } #endif @@ -344,7 +460,7 @@ function_init(VALUE self, VALUE rbFunctionInfo, VALUE rbProc) rb_obj_classname(rbProc)); } - fn->rbProc = rbProc; + RB_OBJ_WRITE(self, &fn->rbProc, rbProc); return self; } @@ -360,7 +476,7 @@ function_call(int argc, VALUE* argv, VALUE self) { Function* fn; - Data_Get_Struct(self, Function, fn); + TypedData_Get_Struct(self, Function, &function_data_type, fn); return (*fn->info->invoke)(argc, argv, fn->base.memory.address, fn->info); } @@ -376,9 +492,9 @@ static VALUE function_attach(VALUE self, VALUE module, VALUE name) { Function* fn; - char var[1024]; - Data_Get_Struct(self, Function, fn); + StringValue(name); + TypedData_Get_Struct(self, Function, &function_data_type, fn); if (fn->info->parameterCount == -1) { rb_raise(rb_eRuntimeError, "cannot attach variadic functions"); @@ -394,12 +510,6 @@ function_attach(VALUE self, VALUE module, VALUE name) fn->methodHandle = rbffi_MethodHandle_Alloc(fn->info, fn->base.memory.address); } - /* - * Stash the Function in a module variable so it does not get garbage collected - */ - snprintf(var, sizeof(var), "@@%s", StringValueCStr(name)); - rb_cv_set(module, var, self); - rb_define_singleton_method(module, StringValueCStr(name), rbffi_MethodHandle_CodeAddress(fn->methodHandle), -1); @@ -421,7 +531,8 @@ function_set_autorelease(VALUE self, VALUE autorelease) { Function* fn; - Data_Get_Struct(self, Function, fn); + rb_check_frozen(self); + TypedData_Get_Struct(self, Function, &function_data_type, fn); fn->autorelease = RTEST(autorelease); @@ -433,11 +544,21 @@ function_autorelease_p(VALUE self) { Function* fn; - Data_Get_Struct(self, Function, fn); + TypedData_Get_Struct(self, Function, &function_data_type, fn); return fn->autorelease ? Qtrue : Qfalse; } +static VALUE +function_type(VALUE self) +{ + Function* fn; + + TypedData_Get_Struct(self, Function, &function_data_type, fn); + + return fn->rbFunctionInfo; +} + /* * call-seq: free * @return [self] @@ -448,7 +569,7 @@ function_release(VALUE self) { Function* fn; - Data_Get_Struct(self, Function, fn); + TypedData_Get_Struct(self, Function, &function_data_type, fn); if (fn->closure == NULL) { rb_raise(rb_eRuntimeError, "cannot free function which was not allocated"); @@ -463,6 +584,7 @@ function_release(VALUE self) static void callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data) { + Function* fn; struct gvl_callback cb = { 0 }; cb.closure = (Closure *) user_data; @@ -470,6 +592,7 @@ callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data) cb.parameters = parameters; cb.done = false; cb.frame = rbffi_frame_current(); + fn = (Function *) cb.closure->info; if (cb.frame != NULL) cb.frame->exc = Qnil; @@ -482,18 +605,19 @@ callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data) #if defined(DEFER_ASYNC_CALLBACK) && !defined(_WIN32) } else { bool empty = false; + struct async_cb_dispatcher *ctx = fn->dispatcher; pthread_mutex_init(&cb.async_mutex, NULL); pthread_cond_init(&cb.async_cond, NULL); - /* Now signal the async callback thread */ - pthread_mutex_lock(&async_cb_mutex); - empty = async_cb_list == NULL; - cb.next = async_cb_list; - async_cb_list = &cb; + /* Now signal the async callback dispatcher thread */ + pthread_mutex_lock(&ctx->async_cb_mutex); + empty = ctx->async_cb_list == NULL; + cb.next = ctx->async_cb_list; + ctx->async_cb_list = &cb; - pthread_cond_signal(&async_cb_cond); - pthread_mutex_unlock(&async_cb_mutex); + pthread_cond_signal(&ctx->async_cb_cond); + pthread_mutex_unlock(&ctx->async_cb_mutex); /* Wait for the thread executing the ruby callback to signal it is done */ pthread_mutex_lock(&cb.async_mutex); @@ -507,17 +631,18 @@ callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data) #elif defined(DEFER_ASYNC_CALLBACK) && defined(_WIN32) } else { bool empty = false; + struct async_cb_dispatcher *ctx = fn->dispatcher; cb.async_event = CreateEvent(NULL, FALSE, FALSE, NULL); - /* Now signal the async callback thread */ - EnterCriticalSection(&async_cb_lock); - empty = async_cb_list == NULL; - cb.next = async_cb_list; - async_cb_list = &cb; - LeaveCriticalSection(&async_cb_lock); + /* Now signal the async callback dispatcher thread */ + EnterCriticalSection(&ctx->async_cb_lock); + empty = ctx->async_cb_list == NULL; + cb.next = ctx->async_cb_list; + ctx->async_cb_list = &cb; + LeaveCriticalSection(&ctx->async_cb_lock); - SetEvent(async_cb_cond); + SetEvent(ctx->async_cb_cond); /* Wait for the thread executing the ruby callback to signal it is done */ WaitForSingleObject(cb.async_event, INFINITE); @@ -528,6 +653,7 @@ callback_invoke(ffi_cif* cif, void* retval, void** parameters, void* user_data) #if defined(DEFER_ASYNC_CALLBACK) struct async_wait { + struct async_cb_dispatcher *dispatcher; void* cb; bool stop; }; @@ -536,9 +662,10 @@ static void * async_cb_wait(void *); static void async_cb_stop(void *); static VALUE -async_cb_event(void* unused) +async_cb_event(void* ptr) { - struct async_wait w = { 0 }; + struct async_cb_dispatcher *ctx = (struct async_cb_dispatcher *)ptr; + struct async_wait w = { ctx }; w.stop = false; while (!w.stop) { @@ -559,23 +686,24 @@ static void * async_cb_wait(void *data) { struct async_wait* w = (struct async_wait *) data; + struct async_cb_dispatcher *ctx = w->dispatcher; w->cb = NULL; - EnterCriticalSection(&async_cb_lock); + EnterCriticalSection(&ctx->async_cb_lock); - while (!w->stop && async_cb_list == NULL) { - LeaveCriticalSection(&async_cb_lock); - WaitForSingleObject(async_cb_cond, INFINITE); - EnterCriticalSection(&async_cb_lock); + while (!w->stop && ctx->async_cb_list == NULL) { + LeaveCriticalSection(&ctx->async_cb_lock); + WaitForSingleObject(ctx->async_cb_cond, INFINITE); + EnterCriticalSection(&ctx->async_cb_lock); } - if (async_cb_list != NULL) { - w->cb = async_cb_list; - async_cb_list = async_cb_list->next; + if (ctx->async_cb_list != NULL) { + w->cb = ctx->async_cb_list; + ctx->async_cb_list = ctx->async_cb_list->next; } - LeaveCriticalSection(&async_cb_lock); + LeaveCriticalSection(&ctx->async_cb_lock); return NULL; } @@ -584,11 +712,12 @@ static void async_cb_stop(void *data) { struct async_wait* w = (struct async_wait *) data; + struct async_cb_dispatcher *ctx = w->dispatcher; - EnterCriticalSection(&async_cb_lock); + EnterCriticalSection(&ctx->async_cb_lock); w->stop = true; - LeaveCriticalSection(&async_cb_lock); - SetEvent(async_cb_cond); + LeaveCriticalSection(&ctx->async_cb_lock); + SetEvent(ctx->async_cb_cond); } #else @@ -596,21 +725,22 @@ static void * async_cb_wait(void *data) { struct async_wait* w = (struct async_wait *) data; + struct async_cb_dispatcher *ctx = w->dispatcher; w->cb = NULL; - pthread_mutex_lock(&async_cb_mutex); + pthread_mutex_lock(&ctx->async_cb_mutex); - while (!w->stop && async_cb_list == NULL) { - pthread_cond_wait(&async_cb_cond, &async_cb_mutex); + while (!w->stop && ctx->async_cb_list == NULL) { + pthread_cond_wait(&ctx->async_cb_cond, &ctx->async_cb_mutex); } - if (async_cb_list != NULL) { - w->cb = async_cb_list; - async_cb_list = async_cb_list->next; + if (ctx->async_cb_list != NULL) { + w->cb = ctx->async_cb_list; + ctx->async_cb_list = ctx->async_cb_list->next; } - pthread_mutex_unlock(&async_cb_mutex); + pthread_mutex_unlock(&ctx->async_cb_mutex); return NULL; } @@ -619,11 +749,12 @@ static void async_cb_stop(void *data) { struct async_wait* w = (struct async_wait *) data; + struct async_cb_dispatcher *ctx = w->dispatcher; - pthread_mutex_lock(&async_cb_mutex); + pthread_mutex_lock(&ctx->async_cb_mutex); w->stop = true; - pthread_cond_signal(&async_cb_cond); - pthread_mutex_unlock(&async_cb_mutex); + pthread_cond_signal(&ctx->async_cb_cond); + pthread_mutex_unlock(&ctx->async_cb_mutex); } #endif @@ -796,7 +927,9 @@ invoke_callback(VALUE data) break; case NATIVE_POINTER: if (TYPE(rbReturnValue) == T_DATA && rb_obj_is_kind_of(rbReturnValue, rbffi_PointerClass)) { - *((void **) retval) = ((AbstractMemory *) DATA_PTR(rbReturnValue))->address; + AbstractMemory* memory; + TypedData_Get_Struct(rbReturnValue, AbstractMemory, &rbffi_abstract_memory_data_type, memory); + *((void **) retval) = memory->address; } else { /* Default to returning NULL if not a value pointer object. handles nil case as well */ *((void **) retval) = NULL; @@ -809,15 +942,20 @@ invoke_callback(VALUE data) case NATIVE_FUNCTION: if (TYPE(rbReturnValue) == T_DATA && rb_obj_is_kind_of(rbReturnValue, rbffi_PointerClass)) { + AbstractMemory* memory; + TypedData_Get_Struct(rbReturnValue, AbstractMemory, &rbffi_abstract_memory_data_type, memory); - *((void **) retval) = ((AbstractMemory *) DATA_PTR(rbReturnValue))->address; + *((void **) retval) = memory->address; } else if (rb_obj_is_kind_of(rbReturnValue, rb_cProc) || rb_respond_to(rbReturnValue, id_call)) { VALUE function; function = rbffi_Function_ForProc(rbReturnType, rbReturnValue); - *((void **) retval) = ((AbstractMemory *) DATA_PTR(function))->address; + AbstractMemory* memory; + TypedData_Get_Struct(function, AbstractMemory, &rbffi_abstract_memory_data_type, memory); + + *((void **) retval) = memory->address; } else { *((void **) retval) = NULL; } @@ -825,7 +963,9 @@ invoke_callback(VALUE data) case NATIVE_STRUCT: if (TYPE(rbReturnValue) == T_DATA && rb_obj_is_kind_of(rbReturnValue, rbffi_StructClass)) { - AbstractMemory* memory = ((Struct *) DATA_PTR(rbReturnValue))->pointer; + Struct* s; + TypedData_Get_Struct(rbReturnValue, Struct, &rbffi_struct_data_type, s); + AbstractMemory* memory = s->pointer; if (memory->address != NULL) { memcpy(retval, memory->address, returnType->ffiType->size); @@ -891,6 +1031,7 @@ rbffi_Function_Init(VALUE moduleFFI) rb_define_method(rbffi_FunctionClass, "attach", function_attach, 2); rb_define_method(rbffi_FunctionClass, "free", function_release, 0); rb_define_method(rbffi_FunctionClass, "autorelease=", function_set_autorelease, 1); + rb_define_private_method(rbffi_FunctionClass, "type", function_type, 0); /* * call-seq: autorelease * @return [Boolean] @@ -910,8 +1051,7 @@ rbffi_Function_Init(VALUE moduleFFI) id_cb_ref = rb_intern("@__ffi_callback__"); id_to_native = rb_intern("to_native"); id_from_native = rb_intern("from_native"); -#if defined(_WIN32) - InitializeCriticalSection(&async_cb_lock); - async_cb_cond = CreateEvent(NULL, FALSE, FALSE, NULL); +#if defined(DEFER_ASYNC_CALLBACK) && defined(HAVE_RB_EXT_RACTOR_SAFE) + async_cb_dispatcher_key = rb_ractor_local_storage_ptr_newkey(&async_cb_dispatcher_key_type); #endif } diff --git a/ext/ffi_c/Function.h b/ext/ffi_c/Function.h index 406b4d8..89b22ec 100644 --- a/ext/ffi_c/Function.h +++ b/ext/ffi_c/Function.h @@ -68,6 +68,7 @@ struct FunctionType_ { bool hasStruct; }; +extern const rb_data_type_t rbffi_fntype_data_type; extern VALUE rbffi_FunctionTypeClass, rbffi_FunctionClass; void rbffi_Function_Init(VALUE moduleFFI); diff --git a/ext/ffi_c/FunctionInfo.c b/ext/ffi_c/FunctionInfo.c index 64e9874..b5150d8 100644 --- a/ext/ffi_c/FunctionInfo.c +++ b/ext/ffi_c/FunctionInfo.c @@ -51,8 +51,24 @@ static VALUE fntype_allocate(VALUE klass); static VALUE fntype_initialize(int argc, VALUE* argv, VALUE self); -static void fntype_mark(FunctionType*); -static void fntype_free(FunctionType *); +static void fntype_mark(void *); +static void fntype_compact(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 = fntype_memsize, + ffi_compact_callback( fntype_compact ) + }, + .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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; VALUE rbffi_FunctionTypeClass = Qnil; @@ -60,13 +76,13 @@ static VALUE fntype_allocate(VALUE klass) { FunctionType* fnInfo; - VALUE obj = Data_Make_Struct(klass, FunctionType, fntype_mark, fntype_free, fnInfo); + VALUE obj = TypedData_Make_Struct(klass, FunctionType, &rbffi_fntype_data_type, fnInfo); 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; @@ -74,19 +90,37 @@ fntype_allocate(VALUE klass) } static void -fntype_mark(FunctionType* fnInfo) +fntype_mark(void *data) { - rb_gc_mark(fnInfo->rbReturnType); - rb_gc_mark(fnInfo->rbParameterTypes); - rb_gc_mark(fnInfo->rbEnums); + FunctionType *fnInfo = (FunctionType *)data; + rb_gc_mark_movable(fnInfo->rbReturnType); + rb_gc_mark_movable(fnInfo->rbParameterTypes); + rb_gc_mark_movable(fnInfo->rbEnums); if (fnInfo->callbackCount > 0 && fnInfo->callbackParameters != NULL) { - rb_gc_mark_locations(&fnInfo->callbackParameters[0], &fnInfo->callbackParameters[fnInfo->callbackCount]); + for (size_t index = 0; index < fnInfo->callbackCount; index++) { + rb_gc_mark_movable(fnInfo->callbackParameters[index]); + } + } +} + +static void +fntype_compact(void *data) +{ + FunctionType *fnInfo = (FunctionType *)data; + ffi_gc_location(fnInfo->rbReturnType); + ffi_gc_location(fnInfo->rbParameterTypes); + ffi_gc_location(fnInfo->rbEnums); + if (fnInfo->callbackCount > 0 && fnInfo->callbackParameters != NULL) { + for (size_t index = 0; index < fnInfo->callbackCount; index++) { + ffi_gc_location(fnInfo->callbackParameters[index]); + } } } static void -fntype_free(FunctionType* fnInfo) +fntype_free(void *data) { + FunctionType *fnInfo = (FunctionType *)data; xfree(fnInfo->parameterTypes); xfree(fnInfo->ffiParameterTypes); xfree(fnInfo->nativeParameterTypes); @@ -97,6 +131,23 @@ fntype_free(FunctionType* fnInfo) 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 @@ -129,13 +180,13 @@ fntype_initialize(int argc, VALUE* argv, VALUE self) Check_Type(rbParamTypes, T_ARRAY); - Data_Get_Struct(self, FunctionType, fnInfo); + TypedData_Get_Struct(self, FunctionType, &rbffi_fntype_data_type, fnInfo); fnInfo->parameterCount = (int) RARRAY_LEN(rbParamTypes); 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; @@ -150,7 +201,8 @@ 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); + fnInfo->callbackCount++; } if (rb_obj_is_kind_of(type, rbffi_StructByValueClass)) { @@ -158,12 +210,12 @@ fntype_initialize(int argc, VALUE* argv, VALUE self) } rb_ary_push(fnInfo->rbParameterTypes, type); - Data_Get_Struct(type, Type, fnInfo->parameterTypes[i]); + TypedData_Get_Struct(type, Type, &rbffi_type_data_type, fnInfo->parameterTypes[i]); fnInfo->ffiParameterTypes[i] = fnInfo->parameterTypes[i]->ffiType; 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)); @@ -173,7 +225,7 @@ fntype_initialize(int argc, VALUE* argv, VALUE self) fnInfo->hasStruct = true; } - Data_Get_Struct(fnInfo->rbReturnType, Type, fnInfo->returnType); + TypedData_Get_Struct(fnInfo->rbReturnType, Type, &rbffi_type_data_type, fnInfo->returnType); fnInfo->ffiReturnType = fnInfo->returnType->ffiType; #if defined(X86_WIN32) @@ -199,20 +251,22 @@ fntype_initialize(int argc, VALUE* argv, VALUE self) fnInfo->invoke = rbffi_GetInvoker(fnInfo); + rb_obj_freeze(fnInfo->rbParameterTypes); + rb_obj_freeze(self); return self; } /* - * call-seq: result_type + * call-seq: return_type * @return [Type] * Get the return type of the function type */ static VALUE -fntype_result_type(VALUE self) +fntype_return_type(VALUE self) { FunctionType* ft; - Data_Get_Struct(self, FunctionType, ft); + TypedData_Get_Struct(self, FunctionType, &rbffi_fntype_data_type, ft); return ft->rbReturnType; } @@ -227,7 +281,7 @@ fntype_param_types(VALUE self) { FunctionType* ft; - Data_Get_Struct(self, FunctionType, ft); + TypedData_Get_Struct(self, FunctionType, &rbffi_fntype_data_type, ft); return rb_ary_dup(ft->rbParameterTypes); } @@ -259,7 +313,7 @@ rbffi_FunctionInfo_Init(VALUE moduleFFI) rb_define_alloc_func(rbffi_FunctionTypeClass, fntype_allocate); rb_define_method(rbffi_FunctionTypeClass, "initialize", fntype_initialize, -1); - rb_define_method(rbffi_FunctionTypeClass, "result_type", fntype_result_type, 0); + rb_define_method(rbffi_FunctionTypeClass, "return_type", fntype_return_type, 0); rb_define_method(rbffi_FunctionTypeClass, "param_types", fntype_param_types, 0); } diff --git a/ext/ffi_c/LastError.c b/ext/ffi_c/LastError.c index 6beecef..f4da301 100644 --- a/ext/ffi_c/LastError.c +++ b/ext/ffi_c/LastError.c @@ -91,30 +91,49 @@ thread_data_free(void *ptr) } #else +static size_t +thread_data_memsize(const void *data) { + return sizeof(ThreadData); +} + +static const rb_data_type_t thread_data_data_type = { + .wrap_struct_name = "FFI::ThreadData", + .function = { + .dmark = NULL, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = thread_data_memsize, + }, + // 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 +}; + static ID id_thread_data; static ThreadData* thread_data_init(void) { - ThreadData* td; + ThreadData *td; VALUE obj; - obj = Data_Make_Struct(rb_cObject, ThreadData, NULL, -1, td); + obj = TypedData_Make_Struct(rb_cObject, ThreadData, &thread_data_data_type, td); rb_thread_local_aset(rb_thread_current(), id_thread_data, obj); return td; } static inline ThreadData* -thread_data_get() +thread_data_get(void) { VALUE obj = rb_thread_local_aref(rb_thread_current(), id_thread_data); - if (obj != Qnil && TYPE(obj) == T_DATA) { - return (ThreadData *) DATA_PTR(obj); + if (NIL_P(obj)) { + return thread_data_init(); } - return thread_data_init(); + ThreadData *td; + TypedData_Get_Struct(obj, ThreadData, &thread_data_data_type, td); + return td; } #endif @@ -154,7 +173,6 @@ get_last_winapi_error(VALUE self) static VALUE set_last_error(VALUE self, VALUE error) { - #ifdef _WIN32 SetLastError(NUM2INT(error)); #else diff --git a/ext/ffi_c/MappedType.c b/ext/ffi_c/MappedType.c index d1a4189..2e506f2 100644 --- a/ext/ffi_c/MappedType.c +++ b/ext/ffi_c/MappedType.c @@ -31,6 +31,7 @@ #include <ffi.h> #include "rbffi.h" +#include "compat.h" #include "Type.h" #include "MappedType.h" @@ -38,24 +39,41 @@ static VALUE mapped_allocate(VALUE); static VALUE mapped_initialize(VALUE, VALUE); -static void mapped_mark(MappedType *); +static void mapped_mark(void *); +static void mapped_compact(void *); +static size_t mapped_memsize(const void *); static ID id_native_type, id_to_native, id_from_native; VALUE rbffi_MappedTypeClass = Qnil; +static const rb_data_type_t mapped_type_data_type = { + .wrap_struct_name = "FFI::Type::Mapped", + .function = { + .dmark = mapped_mark, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = mapped_memsize, + ffi_compact_callback( mapped_compact ) + }, + .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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + + static VALUE mapped_allocate(VALUE klass) { MappedType* m; - VALUE obj = Data_Make_Struct(klass, MappedType, mapped_mark, -1, m); + VALUE obj = TypedData_Make_Struct(klass, MappedType, &mapped_type_data_type, m); - m->rbConverter = Qnil; - m->rbType = Qnil; + RB_OBJ_WRITE(obj, &m->rbConverter, Qnil); + RB_OBJ_WRITE(obj, &m->rbType, Qnil); m->type = NULL; m->base.nativeType = NATIVE_MAPPED; m->base.ffiType = &ffi_type_void; - + return obj; } @@ -69,7 +87,7 @@ static VALUE mapped_initialize(VALUE self, VALUE rbConverter) { MappedType* m = NULL; - + if (!rb_respond_to(rbConverter, id_native_type)) { rb_raise(rb_eNoMethodError, "native_type method not implemented"); } @@ -81,25 +99,42 @@ mapped_initialize(VALUE self, VALUE rbConverter) if (!rb_respond_to(rbConverter, id_from_native)) { rb_raise(rb_eNoMethodError, "from_native method not implemented"); } - - Data_Get_Struct(self, MappedType, m); - m->rbType = rb_funcall2(rbConverter, id_native_type, 0, NULL); + + TypedData_Get_Struct(self, MappedType, &mapped_type_data_type, m); + RB_OBJ_WRITE(self, &m->rbType, rb_funcall2(rbConverter, id_native_type, 0, NULL)); if (!(rb_obj_is_kind_of(m->rbType, rbffi_TypeClass))) { rb_raise(rb_eTypeError, "native_type did not return instance of FFI::Type"); } - m->rbConverter = rbConverter; - Data_Get_Struct(m->rbType, Type, m->type); + RB_OBJ_WRITE(self, &m->rbConverter, rbConverter); + TypedData_Get_Struct(m->rbType, Type, &rbffi_type_data_type, m->type); m->base.ffiType = m->type->ffiType; - + + rb_obj_freeze(self); + return self; } static void -mapped_mark(MappedType* m) +mapped_mark(void* data) +{ + MappedType* m = (MappedType*)data; + rb_gc_mark_movable(m->rbType); + rb_gc_mark_movable(m->rbConverter); +} + +static void +mapped_compact(void* data) { - rb_gc_mark(m->rbType); - rb_gc_mark(m->rbConverter); + MappedType* m = (MappedType*)data; + ffi_gc_location(m->rbType); + ffi_gc_location(m->rbConverter); +} + +static size_t +mapped_memsize(const void *data) +{ + return sizeof(MappedType); } /* @@ -111,7 +146,7 @@ static VALUE mapped_native_type(VALUE self) { MappedType*m = NULL; - Data_Get_Struct(self, MappedType, m); + TypedData_Get_Struct(self, MappedType, &mapped_type_data_type, m); return m->rbType; } @@ -124,9 +159,8 @@ static VALUE mapped_to_native(int argc, VALUE* argv, VALUE self) { MappedType*m = NULL; - - Data_Get_Struct(self, MappedType, m); - + TypedData_Get_Struct(self, MappedType, &mapped_type_data_type, m); + return rb_funcall2(m->rbConverter, id_to_native, argc, argv); } @@ -138,20 +172,28 @@ static VALUE mapped_from_native(int argc, VALUE* argv, VALUE self) { MappedType*m = NULL; - - Data_Get_Struct(self, MappedType, m); + TypedData_Get_Struct(self, MappedType, &mapped_type_data_type, m); return rb_funcall2(m->rbConverter, id_from_native, argc, argv); } +static VALUE +mapped_converter(VALUE self) +{ + MappedType*m = NULL; + TypedData_Get_Struct(self, MappedType, &mapped_type_data_type, m); + + return m->rbConverter; +} + void rbffi_MappedType_Init(VALUE moduleFFI) { - /* + /* * Document-class: FFI::Type::Mapped < FFI::Type */ rbffi_MappedTypeClass = rb_define_class_under(rbffi_TypeClass, "Mapped", rbffi_TypeClass); - + rb_global_variable(&rbffi_MappedTypeClass); id_native_type = rb_intern("native_type"); @@ -164,5 +206,6 @@ rbffi_MappedType_Init(VALUE moduleFFI) rb_define_method(rbffi_MappedTypeClass, "native_type", mapped_native_type, 0); rb_define_method(rbffi_MappedTypeClass, "to_native", mapped_to_native, -1); rb_define_method(rbffi_MappedTypeClass, "from_native", mapped_from_native, -1); + rb_define_method(rbffi_MappedTypeClass, "converter", mapped_converter, 0); } diff --git a/ext/ffi_c/MappedType.h b/ext/ffi_c/MappedType.h index 4b26cc1..9f6f9ee 100644 --- a/ext/ffi_c/MappedType.h +++ b/ext/ffi_c/MappedType.h @@ -43,14 +43,12 @@ typedef struct MappedType_ { Type* type; VALUE rbConverter; VALUE rbType; - } MappedType; void rbffi_MappedType_Init(VALUE moduleFFI); extern VALUE rbffi_MappedTypeClass; - #ifdef __cplusplus } #endif diff --git a/ext/ffi_c/MemoryPointer.c b/ext/ffi_c/MemoryPointer.c index 1a64f2e..a60168e 100644 --- a/ext/ffi_c/MemoryPointer.c +++ b/ext/ffi_c/MemoryPointer.c @@ -39,13 +39,14 @@ static VALUE memptr_allocate(VALUE klass); -static void memptr_release(Pointer* ptr); +static void memptr_release(void *data); +static size_t memptr_memsize(const void *data); static VALUE memptr_malloc(VALUE self, long size, long count, bool clear); static VALUE memptr_free(VALUE self); VALUE rbffi_MemoryPointerClass; -#define MEMPTR(obj) ((MemoryPointer *) rbffi_AbstractMemory_Cast(obj, rbffi_MemoryPointerClass)) +#define MEMPTR(obj) ((MemoryPointer *) rbffi_AbstractMemory_Cast(obj, &memory_pointer_data_type)) VALUE rbffi_MemoryPointer_NewInstance(long size, long count, bool clear) @@ -53,12 +54,25 @@ rbffi_MemoryPointer_NewInstance(long size, long count, bool clear) return memptr_malloc(memptr_allocate(rbffi_MemoryPointerClass), size, count, clear); } +static const rb_data_type_t memory_pointer_data_type = { + .wrap_struct_name = "FFI::MemoryPointer", + .function = { + .dmark = NULL, + .dfree = memptr_release, + .dsize = memptr_memsize, + }, + .parent = &rbffi_pointer_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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + static VALUE memptr_allocate(VALUE klass) { Pointer* p; - VALUE obj = Data_Make_Struct(klass, Pointer, NULL, memptr_release, p); - p->rbParent = Qnil; + VALUE obj = TypedData_Make_Struct(klass, Pointer, &memory_pointer_data_type, p); + RB_OBJ_WRITE(obj, &p->rbParent, Qnil); p->memory.flags = MEM_RD | MEM_WR; return obj; @@ -94,7 +108,7 @@ memptr_malloc(VALUE self, long size, long count, bool clear) Pointer* p; unsigned long msize; - Data_Get_Struct(self, Pointer, p); + TypedData_Get_Struct(self, Pointer, &memory_pointer_data_type, p); msize = size * count; @@ -122,7 +136,8 @@ memptr_free(VALUE self) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + rb_check_frozen(self); + TypedData_Get_Struct(self, Pointer, &memory_pointer_data_type, ptr); if (ptr->allocated) { if (ptr->storage != NULL) { @@ -136,8 +151,9 @@ memptr_free(VALUE self) } static void -memptr_release(Pointer* ptr) +memptr_release(void *data) { + Pointer *ptr = (Pointer *)data; if (ptr->autorelease && ptr->allocated && ptr->storage != NULL) { xfree(ptr->storage); ptr->storage = NULL; @@ -145,6 +161,17 @@ memptr_release(Pointer* ptr) xfree(ptr); } +static size_t +memptr_memsize(const void *data) +{ + const Pointer *ptr = (const Pointer *)data; + size_t memsize = sizeof(Pointer); + if (ptr->allocated) { + memsize += ptr->memory.size; + } + return memsize; +} + /* * call-seq: from_string(s) * @param [String] s string diff --git a/ext/ffi_c/MethodHandle.c b/ext/ffi_c/MethodHandle.c index d047e10..83f7b77 100644 --- a/ext/ffi_c/MethodHandle.c +++ b/ext/ffi_c/MethodHandle.c @@ -77,7 +77,9 @@ static bool prep_trampoline(void* ctx, void* code, Closure* closure, char* errmsg, size_t errmsgsize); static long trampoline_size(void); -#if defined(__x86_64__) && (defined(__linux__) || defined(__APPLE__)) +#if defined(__x86_64__) && \ + (defined(__linux__) || defined(__APPLE__)) && \ + !USE_FFI_ALLOC # define CUSTOM_TRAMPOLINE 1 #endif diff --git a/ext/ffi_c/Pointer.c b/ext/ffi_c/Pointer.c index 153fff1..dae853a 100644 --- a/ext/ffi_c/Pointer.c +++ b/ext/ffi_c/Pointer.c @@ -33,16 +33,33 @@ #include <ruby.h> #include "rbffi.h" #include "rbffi_endian.h" +#include "compat.h" #include "AbstractMemory.h" #include "Pointer.h" -#define POINTER(obj) rbffi_AbstractMemory_Cast((obj), rbffi_PointerClass) +#define POINTER(obj) rbffi_AbstractMemory_Cast((obj), &rbffi_pointer_data_type) VALUE rbffi_PointerClass = Qnil; VALUE rbffi_NullPointerSingleton = Qnil; -static void ptr_release(Pointer* ptr); -static void ptr_mark(Pointer* ptr); +static void ptr_release(void *data); +static void ptr_mark(void *data); +static void ptr_compact(void *data); +static size_t ptr_memsize(const void *data); + +const rb_data_type_t rbffi_pointer_data_type = { /* extern */ + .wrap_struct_name = "FFI::Pointer", + .function = { + .dmark = ptr_mark, + .dfree = ptr_release, + .dsize = ptr_memsize, + ffi_compact_callback( ptr_compact ) + }, + .parent = &rbffi_abstract_memory_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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; VALUE rbffi_Pointer_NewInstance(void* addr) @@ -54,12 +71,12 @@ rbffi_Pointer_NewInstance(void* addr) return rbffi_NullPointerSingleton; } - obj = Data_Make_Struct(rbffi_PointerClass, Pointer, NULL, -1, p); + obj = TypedData_Make_Struct(rbffi_PointerClass, Pointer, &rbffi_pointer_data_type, p); p->memory.address = addr; p->memory.size = LONG_MAX; p->memory.flags = (addr == NULL) ? 0 : (MEM_RD | MEM_WR); p->memory.typeSize = 1; - p->rbParent = Qnil; + RB_OBJ_WRITE(obj, &p->rbParent, Qnil); return obj; } @@ -70,8 +87,8 @@ ptr_allocate(VALUE klass) Pointer* p; VALUE obj; - obj = Data_Make_Struct(klass, Pointer, ptr_mark, ptr_release, p); - p->rbParent = Qnil; + obj = TypedData_Make_Struct(klass, Pointer, &rbffi_pointer_data_type, p); + RB_OBJ_WRITE(obj, &p->rbParent, Qnil); p->memory.flags = MEM_RD | MEM_WR; return obj; @@ -95,7 +112,7 @@ ptr_initialize(int argc, VALUE* argv, VALUE self) VALUE rbType = Qnil, rbAddress = Qnil; int typeSize = 1; - Data_Get_Struct(self, Pointer, p); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, p); switch (rb_scan_args(argc, argv, "11", &rbType, &rbAddress)) { case 1: @@ -112,7 +129,7 @@ ptr_initialize(int argc, VALUE* argv, VALUE self) switch (TYPE(rbAddress)) { case T_FIXNUM: case T_BIGNUM: - p->memory.address = (void*) (uintptr_t) NUM2LL(rbAddress); + p->memory.address = (void*) (uintptr_t) NUM2ULL(rbAddress); p->memory.size = LONG_MAX; if (p->memory.address == NULL) { p->memory.flags = 0; @@ -123,8 +140,8 @@ ptr_initialize(int argc, VALUE* argv, VALUE self) if (rb_obj_is_kind_of(rbAddress, rbffi_PointerClass)) { Pointer* orig; - p->rbParent = rbAddress; - Data_Get_Struct(rbAddress, Pointer, orig); + RB_OBJ_WRITE(self, &p->rbParent, rbAddress); + TypedData_Get_Struct(rbAddress, Pointer, &rbffi_pointer_data_type, orig); p->memory = orig->memory; } else { rb_raise(rb_eTypeError, "wrong argument type, expected Integer or FFI::Pointer"); @@ -153,7 +170,7 @@ ptr_initialize_copy(VALUE self, VALUE other) AbstractMemory* src; Pointer* dst; - Data_Get_Struct(self, Pointer, dst); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, dst); src = POINTER(other); if (src->size == LONG_MAX) { rb_raise(rb_eRuntimeError, "cannot duplicate unbounded memory area"); @@ -195,16 +212,16 @@ slice(VALUE self, long offset, long size) Pointer* p; VALUE retval; - Data_Get_Struct(self, AbstractMemory, ptr); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); checkBounds(ptr, offset, size == LONG_MAX ? 1 : size); - retval = Data_Make_Struct(rbffi_PointerClass, Pointer, ptr_mark, -1, p); + retval = TypedData_Make_Struct(rbffi_PointerClass, Pointer, &rbffi_pointer_data_type, p); p->memory.address = ptr->address + offset; p->memory.size = size; p->memory.flags = ptr->flags; p->memory.typeSize = ptr->typeSize; - p->rbParent = self; + RB_OBJ_WRITE(retval, &p->rbParent, self); return retval; } @@ -222,7 +239,7 @@ ptr_plus(VALUE self, VALUE offset) AbstractMemory* ptr; long off = NUM2LONG(offset); - Data_Get_Struct(self, AbstractMemory, ptr); + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, ptr); return slice(self, off, ptr->size == LONG_MAX ? LONG_MAX : ptr->size - off); } @@ -252,7 +269,7 @@ ptr_inspect(VALUE self) char buf[100]; Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); if (ptr->memory.size != LONG_MAX) { snprintf(buf, sizeof(buf), "#<%s address=%p size=%lu>", @@ -275,7 +292,7 @@ ptr_null_p(VALUE self) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); return ptr->memory.address == NULL ? Qtrue : Qfalse; } @@ -291,7 +308,7 @@ ptr_equals(VALUE self, VALUE other) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); if (NIL_P(other)) { return ptr->memory.address == NULL ? Qtrue : Qfalse; @@ -310,7 +327,7 @@ ptr_address(VALUE self) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); return ULL2NUM((uintptr_t) ptr->memory.address); } @@ -335,7 +352,7 @@ ptr_order(int argc, VALUE* argv, VALUE self) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); if (argc == 0) { int order = (ptr->memory.flags & MEM_SWAP) == 0 ? BYTE_ORDER : SWAPPED_ORDER; return order == BIG_ENDIAN ? ID2SYM(rb_intern("big")) : ID2SYM(rb_intern("little")); @@ -361,7 +378,7 @@ ptr_order(int argc, VALUE* argv, VALUE self) Pointer* p2; VALUE retval = slice(self, 0, ptr->memory.size); - Data_Get_Struct(retval, Pointer, p2); + TypedData_Get_Struct(retval, Pointer, &rbffi_pointer_data_type, p2); p2->memory.flags |= MEM_SWAP; return retval; } @@ -381,7 +398,8 @@ ptr_free(VALUE self) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + rb_check_frozen(self); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); if (ptr->allocated) { if (ptr->storage != NULL) { @@ -404,7 +422,7 @@ ptr_type_size(VALUE self) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); return INT2NUM(ptr->memory.typeSize); } @@ -420,7 +438,8 @@ ptr_autorelease(VALUE self, VALUE autorelease) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + rb_check_frozen(self); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); ptr->autorelease = autorelease == Qtrue; return autorelease; @@ -436,15 +455,16 @@ ptr_autorelease_p(VALUE self) { Pointer* ptr; - Data_Get_Struct(self, Pointer, ptr); + TypedData_Get_Struct(self, Pointer, &rbffi_pointer_data_type, ptr); return ptr->autorelease ? Qtrue : Qfalse; } static void -ptr_release(Pointer* ptr) +ptr_release(void *data) { + Pointer *ptr = (Pointer *)data; if (ptr->autorelease && ptr->allocated && ptr->storage != NULL) { xfree(ptr->storage); ptr->storage = NULL; @@ -453,9 +473,28 @@ ptr_release(Pointer* ptr) } static void -ptr_mark(Pointer* ptr) +ptr_mark(void *data) { - rb_gc_mark(ptr->rbParent); + Pointer *ptr = (Pointer *)data; + rb_gc_mark_movable(ptr->rbParent); +} + +static void +ptr_compact(void *data) +{ + Pointer *ptr = (Pointer *)data; + ffi_gc_location(ptr->rbParent); +} + +static size_t +ptr_memsize(const void *data) +{ + const Pointer *ptr = (const Pointer *)data; + size_t memsize = sizeof(Pointer); + if (ptr->allocated) { + memsize += ptr->memory.size; + } + return memsize; } void diff --git a/ext/ffi_c/Pointer.h b/ext/ffi_c/Pointer.h index b3d6c85..0dfd8b2 100644 --- a/ext/ffi_c/Pointer.h +++ b/ext/ffi_c/Pointer.h @@ -40,6 +40,7 @@ extern "C" { extern void rbffi_Pointer_Init(VALUE moduleFFI); extern VALUE rbffi_Pointer_NewInstance(void* addr); +extern const rb_data_type_t rbffi_pointer_data_type; extern VALUE rbffi_PointerClass; extern VALUE rbffi_NullPointerSingleton; diff --git a/ext/ffi_c/Struct.c b/ext/ffi_c/Struct.c index 92731c8..0fdba71 100644 --- a/ext/ffi_c/Struct.c +++ b/ext/ffi_c/Struct.c @@ -61,13 +61,29 @@ typedef struct InlineArray_ { } InlineArray; -static void struct_mark(Struct *); -static void struct_free(Struct *); +static void struct_mark(void *data); +static void struct_compact(void *data); +static void struct_free(void *data); +static size_t struct_memsize(const void *); static VALUE struct_class_layout(VALUE klass); -static void struct_malloc(Struct* s); -static void inline_array_mark(InlineArray *); -static void store_reference_value(StructField* f, Struct* s, VALUE value); - +static void struct_malloc(VALUE self, Struct* s); +static void inline_array_mark(void *); +static void inline_array_compact(void *); +static size_t inline_array_memsize(const void *); +static void store_reference_value(VALUE self, StructField* f, Struct* s, VALUE value); + +const rb_data_type_t rbffi_struct_data_type = { /* extern */ + .wrap_struct_name = "FFI::Struct", + .function = { + .dmark = struct_mark, + .dfree = struct_free, + .dsize = struct_memsize, + ffi_compact_callback( struct_compact ) + }, + // 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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; VALUE rbffi_StructClass = Qnil; VALUE rbffi_StructInlineArrayClass = Qnil; @@ -79,17 +95,19 @@ static ID id_get = 0, id_put = 0, id_to_ptr = 0, id_to_s = 0, id_layout = 0; static inline char* memory_address(VALUE self) { - return ((AbstractMemory *)DATA_PTR((self)))->address; + AbstractMemory *mem; + TypedData_Get_Struct(self, AbstractMemory, &rbffi_abstract_memory_data_type, mem); + return mem->address; } static VALUE struct_allocate(VALUE klass) { Struct* s; - VALUE obj = Data_Make_Struct(klass, Struct, struct_mark, struct_free, s); + VALUE obj = TypedData_Make_Struct(klass, Struct, &rbffi_struct_data_type, s); - s->rbPointer = Qnil; - s->rbLayout = Qnil; + RB_OBJ_WRITE(obj, &s->rbPointer, Qnil); + RB_OBJ_WRITE(obj, &s->rbLayout, Qnil); return obj; } @@ -108,28 +126,29 @@ struct_initialize(int argc, VALUE* argv, VALUE self) VALUE rbPointer = Qnil, rest = Qnil, klass = CLASS_OF(self); int nargs; - Data_Get_Struct(self, Struct, s); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); nargs = rb_scan_args(argc, argv, "01*", &rbPointer, &rest); /* Call up into ruby code to adjust the layout */ if (nargs > 1) { - s->rbLayout = rb_funcall2(CLASS_OF(self), id_layout, (int) RARRAY_LEN(rest), RARRAY_PTR(rest)); + VALUE rbLayout = rb_apply(CLASS_OF(self), id_layout, rest); + RB_OBJ_WRITE(self, &s->rbLayout, rbLayout); } else { - s->rbLayout = struct_class_layout(klass); + RB_OBJ_WRITE(self, &s->rbLayout, struct_class_layout(klass)); } if (!rb_obj_is_kind_of(s->rbLayout, rbffi_StructLayoutClass)) { rb_raise(rb_eRuntimeError, "Invalid Struct layout"); } - Data_Get_Struct(s->rbLayout, StructLayout, s->layout); + TypedData_Get_Struct(s->rbLayout, StructLayout, &rbffi_struct_layout_data_type, s->layout); if (rbPointer != Qnil) { s->pointer = MEMORY(rbPointer); - s->rbPointer = rbPointer; + RB_OBJ_WRITE(self, &s->rbPointer, rbPointer); } else { - struct_malloc(s); + struct_malloc(self, s); } return self; @@ -146,13 +165,13 @@ struct_initialize_copy(VALUE self, VALUE other) Struct* src; Struct* dst; - Data_Get_Struct(self, Struct, dst); - Data_Get_Struct(other, Struct, src); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, dst); + TypedData_Get_Struct(other, Struct, &rbffi_struct_data_type, src); if (dst == src) { return self; } - dst->rbLayout = src->rbLayout; + RB_OBJ_WRITE(self, &dst->rbLayout, src->rbLayout); dst->layout = src->layout; /* @@ -161,17 +180,20 @@ struct_initialize_copy(VALUE self, VALUE other) * be longer than just this struct. */ if (src->pointer->address != NULL) { - dst->rbPointer = rbffi_MemoryPointer_NewInstance(1, src->layout->size, false); + RB_OBJ_WRITE(self, &dst->rbPointer, rbffi_MemoryPointer_NewInstance(1, src->layout->size, false)); dst->pointer = MEMORY(dst->rbPointer); memcpy(dst->pointer->address, src->pointer->address, src->layout->size); } else { - dst->rbPointer = src->rbPointer; + RB_OBJ_WRITE(self, &dst->rbPointer, src->rbPointer); dst->pointer = src->pointer; } if (src->layout->referenceFieldCount > 0) { dst->rbReferences = ALLOC_N(VALUE, dst->layout->referenceFieldCount); memcpy(dst->rbReferences, src->rbReferences, dst->layout->referenceFieldCount * sizeof(VALUE)); + for (size_t index = 0; index < dst->layout->referenceFieldCount; index++) { + RB_OBJ_WRITTEN(self, Qundef, &dst->rbReferences[index]); + } } return self; @@ -196,14 +218,15 @@ struct_class_layout(VALUE klass) static StructLayout* struct_layout(VALUE self) { - Struct* s = (Struct *) DATA_PTR(self); + Struct* s; + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); if (s->layout != NULL) { return s->layout; } if (s->layout == NULL) { - s->rbLayout = struct_class_layout(CLASS_OF(self)); - Data_Get_Struct(s->rbLayout, StructLayout, s->layout); + RB_OBJ_WRITE(self, &s->rbLayout, struct_class_layout(CLASS_OF(self))); + TypedData_Get_Struct(s->rbLayout, StructLayout, &rbffi_struct_layout_data_type, s->layout); } return s->layout; @@ -213,52 +236,74 @@ static Struct* struct_validate(VALUE self) { Struct* s; - Data_Get_Struct(self, Struct, s); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); if (struct_layout(self) == NULL) { rb_raise(rb_eRuntimeError, "struct layout == null"); } if (s->pointer == NULL) { - struct_malloc(s); + struct_malloc(self, s); } return s; } static void -struct_malloc(Struct* s) +struct_malloc(VALUE self, Struct* s) { if (s->rbPointer == Qnil) { - s->rbPointer = rbffi_MemoryPointer_NewInstance(s->layout->size, 1, true); - + RB_OBJ_WRITE(self, &s->rbPointer, rbffi_MemoryPointer_NewInstance(s->layout->size, 1, true)); } else if (!rb_obj_is_kind_of(s->rbPointer, rbffi_AbstractMemoryClass)) { rb_raise(rb_eRuntimeError, "invalid pointer in struct"); } - s->pointer = (AbstractMemory *) DATA_PTR(s->rbPointer); + TypedData_Get_Struct(s->rbPointer, AbstractMemory, &rbffi_abstract_memory_data_type, s->pointer); +} + +static void +struct_mark(void *data) +{ + Struct *s = (Struct *)data; + rb_gc_mark_movable(s->rbPointer); + rb_gc_mark_movable(s->rbLayout); + if (s->rbReferences != NULL) { + for (size_t index = 0; index < s->layout->referenceFieldCount; index++) { + rb_gc_mark_movable(s->rbReferences[index]); + } + } } static void -struct_mark(Struct *s) +struct_compact(void *data) { - rb_gc_mark(s->rbPointer); - rb_gc_mark(s->rbLayout); + Struct *s = (Struct *)data; + ffi_gc_location(s->rbPointer); + ffi_gc_location(s->rbLayout); if (s->rbReferences != NULL) { - rb_gc_mark_locations(&s->rbReferences[0], &s->rbReferences[s->layout->referenceFieldCount]); + for (size_t index = 0; index < s->layout->referenceFieldCount; index++) { + ffi_gc_location(s->rbReferences[index]); + } } } static void -struct_free(Struct* s) +struct_free(void *data) { + Struct *s = (Struct *)data; xfree(s->rbReferences); xfree(s); } +static size_t +struct_memsize(const void *data) +{ + const Struct *s = (const Struct *)data; + return sizeof(Struct) + (s->layout->referenceFieldCount * sizeof(VALUE)); +} static void -store_reference_value(StructField* f, Struct* s, VALUE value) +store_reference_value(VALUE self, StructField* f, Struct* s, VALUE value) { if (unlikely(f->referenceIndex == -1)) { rb_raise(rb_eRuntimeError, "put_reference_value called for non-reference type"); @@ -268,11 +313,11 @@ store_reference_value(StructField* f, Struct* s, VALUE value) int i; s->rbReferences = ALLOC_N(VALUE, s->layout->referenceFieldCount); for (i = 0; i < s->layout->referenceFieldCount; ++i) { - s->rbReferences[i] = Qnil; + RB_OBJ_WRITE(self, &s->rbReferences[i], Qnil); } } - s->rbReferences[f->referenceIndex] = value; + RB_OBJ_WRITE(self, &s->rbReferences[f->referenceIndex], value); } @@ -290,8 +335,8 @@ struct_field(Struct* s, VALUE fieldName) rb_raise(rb_eArgError, "No such field '%s'", StringValueCStr(str)); } /* Write the retrieved coder to the cache */ - p_ce->fieldName = fieldName; - p_ce->field = (StructField *) DATA_PTR(rbField); + RB_OBJ_WRITE(s->rbLayout, &p_ce->fieldName, fieldName); + TypedData_Get_Struct(rbField, StructField, &rbffi_struct_field_data_type, p_ce->field); } return p_ce->field; @@ -311,10 +356,7 @@ struct_aref(VALUE self, VALUE fieldName) s = struct_validate(self); f = struct_field(s, fieldName); - if (f->get != NULL) { - return (*f->get)(f, s); - - } else if (f->memoryOp != NULL) { + if (f->memoryOp != NULL) { return (*f->memoryOp->get)(s->pointer, f->offset); } else { @@ -337,13 +379,11 @@ struct_aset(VALUE self, VALUE fieldName, VALUE value) Struct* s; StructField* f; + rb_check_frozen(self); s = struct_validate(self); f = struct_field(s, fieldName); - if (f->put != NULL) { - (*f->put)(f, s, value); - - } else if (f->memoryOp != NULL) { + if (f->memoryOp != NULL) { (*f->memoryOp->put)(s->pointer, f->offset, value); @@ -357,7 +397,7 @@ struct_aset(VALUE self, VALUE fieldName, VALUE value) } if (f->referenceRequired) { - store_reference_value(f, s, value); + store_reference_value(self, f, s, value); } return value; @@ -376,6 +416,7 @@ struct_set_pointer(VALUE self, VALUE pointer) StructLayout* layout; AbstractMemory* memory; + rb_check_frozen(self); if (!rb_obj_is_kind_of(pointer, rbffi_AbstractMemoryClass)) { rb_raise(rb_eTypeError, "wrong argument type %s (expected Pointer or Buffer)", rb_obj_classname(pointer)); @@ -383,8 +424,8 @@ struct_set_pointer(VALUE self, VALUE pointer) } - Data_Get_Struct(self, Struct, s); - Data_Get_Struct(pointer, AbstractMemory, memory); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); + TypedData_Get_Struct(pointer, AbstractMemory, &rbffi_abstract_memory_data_type, memory); layout = struct_layout(self); if ((int) layout->base.ffiType->size > memory->size) { @@ -393,7 +434,7 @@ struct_set_pointer(VALUE self, VALUE pointer) } s->pointer = MEMORY(pointer); - s->rbPointer = pointer; + RB_OBJ_WRITE(self, &s->rbPointer, pointer); rb_ivar_set(self, id_pointer_ivar, pointer); return self; @@ -409,7 +450,7 @@ struct_get_pointer(VALUE self) { Struct* s; - Data_Get_Struct(self, Struct, s); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); return s->rbPointer; } @@ -424,15 +465,16 @@ static VALUE struct_set_layout(VALUE self, VALUE layout) { Struct* s; - Data_Get_Struct(self, Struct, s); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); + rb_check_frozen(self); if (!rb_obj_is_kind_of(layout, rbffi_StructLayoutClass)) { rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)", rb_obj_classname(layout), rb_class2name(rbffi_StructLayoutClass)); return Qnil; } - Data_Get_Struct(layout, StructLayout, s->layout); + TypedData_Get_Struct(layout, StructLayout, &rbffi_struct_layout_data_type, s->layout); rb_ivar_set(self, id_layout_ivar, layout); return self; @@ -448,7 +490,7 @@ struct_get_layout(VALUE self) { Struct* s; - Data_Get_Struct(self, Struct, s); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); return s->rbLayout; } @@ -463,7 +505,7 @@ struct_null_p(VALUE self) { Struct* s; - Data_Get_Struct(self, Struct, s); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); return s->pointer->address == NULL ? Qtrue : Qfalse; } @@ -476,7 +518,7 @@ struct_order(int argc, VALUE* argv, VALUE self) { Struct* s; - Data_Get_Struct(self, Struct, s); + TypedData_Get_Struct(self, Struct, &rbffi_struct_data_type, s); if (argc == 0) { return rb_funcall(s->rbPointer, rb_intern("order"), 0); @@ -489,24 +531,52 @@ struct_order(int argc, VALUE* argv, VALUE self) } } +static const rb_data_type_t inline_array_data_type = { + .wrap_struct_name = "FFI::Struct::InlineArray", + .function = { + .dmark = inline_array_mark, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = inline_array_memsize, + ffi_compact_callback( inline_array_compact ) + }, + // 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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + static VALUE inline_array_allocate(VALUE klass) { InlineArray* array; VALUE obj; - obj = Data_Make_Struct(klass, InlineArray, inline_array_mark, -1, array); - array->rbField = Qnil; - array->rbMemory = Qnil; + obj = TypedData_Make_Struct(klass, InlineArray, &inline_array_data_type, array); + RB_OBJ_WRITE(obj, &array->rbMemory, Qnil); + RB_OBJ_WRITE(obj, &array->rbField, Qnil); return obj; } static void -inline_array_mark(InlineArray* array) +inline_array_mark(void *data) +{ + InlineArray *array = (InlineArray *)data; + rb_gc_mark_movable(array->rbField); + rb_gc_mark_movable(array->rbMemory); +} + +static void +inline_array_compact(void *data) +{ + InlineArray *array = (InlineArray *)data; + ffi_gc_location(array->rbField); + ffi_gc_location(array->rbMemory); +} + +static size_t +inline_array_memsize(const void *data) { - rb_gc_mark(array->rbField); - rb_gc_mark(array->rbMemory); + return sizeof(InlineArray); } /* @@ -521,14 +591,14 @@ inline_array_initialize(VALUE self, VALUE rbMemory, VALUE rbField) { InlineArray* array; - Data_Get_Struct(self, InlineArray, array); - array->rbMemory = rbMemory; - array->rbField = rbField; + TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array); + RB_OBJ_WRITE(self, &array->rbMemory, rbMemory); + RB_OBJ_WRITE(self, &array->rbField, rbField); - Data_Get_Struct(rbMemory, AbstractMemory, array->memory); - Data_Get_Struct(rbField, StructField, array->field); - Data_Get_Struct(array->field->rbType, ArrayType, array->arrayType); - Data_Get_Struct(array->arrayType->rbComponentType, Type, array->componentType); + TypedData_Get_Struct(rbMemory, AbstractMemory, &rbffi_abstract_memory_data_type, array->memory); + TypedData_Get_Struct(rbField, StructField, &rbffi_struct_field_data_type, array->field); + TypedData_Get_Struct(array->field->rbType, ArrayType, &rbffi_array_type_data_type, array->arrayType); + TypedData_Get_Struct(array->arrayType->rbComponentType, Type, &rbffi_type_data_type, array->componentType); array->op = get_memory_op(array->componentType); if (array->op == NULL && array->componentType->nativeType == NATIVE_MAPPED) { @@ -550,7 +620,7 @@ inline_array_size(VALUE self) { InlineArray* array; - Data_Get_Struct(self, InlineArray, array); + TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array); return UINT2NUM(((ArrayType *) array->field->type)->length); } @@ -575,7 +645,7 @@ inline_array_aref(VALUE self, VALUE rbIndex) { InlineArray* array; - Data_Get_Struct(self, InlineArray, array); + TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array); if (array->op != NULL) { VALUE rbNativeValue = array->op->get(array->memory, @@ -611,7 +681,8 @@ inline_array_aset(VALUE self, VALUE rbIndex, VALUE rbValue) { InlineArray* array; - Data_Get_Struct(self, InlineArray, array); + rb_check_frozen(self); + TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array); if (array->op != NULL) { if (unlikely(array->componentType->nativeType == NATIVE_MAPPED)) { @@ -633,7 +704,7 @@ inline_array_aset(VALUE self, VALUE rbIndex, VALUE rbValue) checkWrite(array->memory); checkBounds(array->memory, offset, array->componentType->ffiType->size); - Data_Get_Struct(rbValue, Struct, s); + TypedData_Get_Struct(rbValue, Struct, &rbffi_struct_data_type, s); checkRead(s->pointer); checkBounds(s->pointer, 0, array->componentType->ffiType->size); @@ -641,7 +712,7 @@ inline_array_aset(VALUE self, VALUE rbIndex, VALUE rbValue) } else { ArrayType* arrayType; - Data_Get_Struct(array->field->rbType, ArrayType, arrayType); + TypedData_Get_Struct(array->field->rbType, ArrayType, &rbffi_array_type_data_type, arrayType); rb_raise(rb_eArgError, "set not supported for %s", rb_obj_classname(arrayType->rbComponentType)); return Qnil; @@ -661,7 +732,7 @@ inline_array_each(VALUE self) int i; - Data_Get_Struct(self, InlineArray, array); + TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array); for (i = 0; i < array->length; ++i) { rb_yield(inline_array_aref(self, INT2FIX(i))); @@ -682,7 +753,7 @@ inline_array_to_a(VALUE self) VALUE obj; int i; - Data_Get_Struct(self, InlineArray, array); + TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array); obj = rb_ary_new2(array->length); @@ -705,7 +776,7 @@ inline_array_to_s(VALUE self) InlineArray* array; VALUE argv[2]; - Data_Get_Struct(self, InlineArray, array); + TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array); if (array->componentType->nativeType != NATIVE_INT8 && array->componentType->nativeType != NATIVE_UINT8) { VALUE dummy = Qnil; @@ -728,7 +799,7 @@ inline_array_to_ptr(VALUE self) { InlineArray* array; - Data_Get_Struct(self, InlineArray, array); + TypedData_Get_Struct(self, InlineArray, &inline_array_data_type, array); return rb_funcall(array->rbMemory, rb_intern("slice"), 2, UINT2NUM(array->field->offset), UINT2NUM(array->arrayType->base.ffiType->size)); diff --git a/ext/ffi_c/Struct.h b/ext/ffi_c/Struct.h index eb6edf2..b86607d 100644 --- a/ext/ffi_c/Struct.h +++ b/ext/ffi_c/Struct.h @@ -42,6 +42,9 @@ extern "C" { extern void rbffi_Struct_Init(VALUE ffiModule); extern void rbffi_StructLayout_Init(VALUE ffiModule); + extern const rb_data_type_t rbffi_struct_layout_data_type; + extern const rb_data_type_t rbffi_struct_field_data_type; + typedef struct StructField_ StructField; typedef struct StructLayout_ StructLayout; typedef struct Struct_ Struct; @@ -56,9 +59,6 @@ extern "C" { VALUE rbType; VALUE rbName; - VALUE (*get)(StructField* field, Struct* s); - void (*put)(StructField* field, Struct* s, VALUE value); - MemoryOp* memoryOp; }; @@ -75,11 +75,12 @@ extern "C" { * This avoids full ruby hash lookups for repeated lookups. */ #define FIELD_CACHE_LOOKUP(this, sym) ( &(this)->cache_row[((sym) >> 8) & 0xff] ) + #define FIELD_CACHE_ROWS 0x100 struct field_cache_entry { VALUE fieldName; StructField *field; - } cache_row[0x100]; + } cache_row[FIELD_CACHE_ROWS]; /** The number of reference tracking fields in this struct */ int referenceFieldCount; @@ -98,6 +99,8 @@ extern "C" { VALUE rbPointer; }; + extern const rb_data_type_t rbffi_struct_data_type; + extern const rb_data_type_t rbffi_struct_field_data_type; extern VALUE rbffi_StructClass, rbffi_StructLayoutClass; extern VALUE rbffi_StructLayoutFieldClass, rbffi_StructLayoutFunctionFieldClass; extern VALUE rbffi_StructLayoutArrayFieldClass; diff --git a/ext/ffi_c/StructByValue.c b/ext/ffi_c/StructByValue.c index a3255f4..df03684 100644 --- a/ext/ffi_c/StructByValue.c +++ b/ext/ffi_c/StructByValue.c @@ -49,20 +49,36 @@ static VALUE sbv_allocate(VALUE); static VALUE sbv_initialize(VALUE, VALUE); -static void sbv_mark(StructByValue *); -static void sbv_free(StructByValue *); +static void sbv_mark(void *); +static void sbv_compact(void *); +static void sbv_free(void *); +static size_t sbv_memsize(const void *); VALUE rbffi_StructByValueClass = Qnil; +static const rb_data_type_t sbv_type_data_type = { + .wrap_struct_name = "FFI::StructByValue", + .function = { + .dmark = sbv_mark, + .dfree = sbv_free, + .dsize = sbv_memsize, + ffi_compact_callback( sbv_compact ) + }, + .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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + static VALUE sbv_allocate(VALUE klass) { StructByValue* sbv; - VALUE obj = Data_Make_Struct(klass, StructByValue, sbv_mark, sbv_free, sbv); + VALUE obj = TypedData_Make_Struct(klass, StructByValue, &sbv_type_data_type, sbv); - sbv->rbStructClass = Qnil; - sbv->rbStructLayout = Qnil; + RB_OBJ_WRITE(obj, &sbv->rbStructClass, Qnil); + RB_OBJ_WRITE(obj, &sbv->rbStructLayout, Qnil); sbv->base.nativeType = NATIVE_STRUCT; sbv->base.ffiType = xcalloc(1, sizeof(*sbv->base.ffiType)); @@ -85,38 +101,54 @@ sbv_initialize(VALUE self, VALUE rbStructClass) rb_raise(rb_eTypeError, "wrong type in @layout ivar (expected FFI::StructLayout)"); } - Data_Get_Struct(rbLayout, StructLayout, layout); - Data_Get_Struct(self, StructByValue, sbv); - sbv->rbStructClass = rbStructClass; - sbv->rbStructLayout = rbLayout; + TypedData_Get_Struct(rbLayout, StructLayout, &rbffi_struct_layout_data_type, layout); + TypedData_Get_Struct(self, StructByValue, &sbv_type_data_type, sbv); + RB_OBJ_WRITE(self, &sbv->rbStructClass, rbStructClass); + RB_OBJ_WRITE(self, &sbv->rbStructLayout, rbLayout); /* We can just use everything from the ffi_type directly */ *sbv->base.ffiType = *layout->base.ffiType; - + return self; } static void -sbv_mark(StructByValue *sbv) +sbv_mark(void *data) +{ + StructByValue *sbv = (StructByValue *)data; + rb_gc_mark_movable(sbv->rbStructClass); + rb_gc_mark_movable(sbv->rbStructLayout); +} + +static void +sbv_compact(void *data) { - rb_gc_mark(sbv->rbStructClass); - rb_gc_mark(sbv->rbStructLayout); + StructByValue *sbv = (StructByValue *)data; + ffi_gc_location(sbv->rbStructClass); + ffi_gc_location(sbv->rbStructLayout); } static void -sbv_free(StructByValue *sbv) +sbv_free(void *data) { + StructByValue *sbv = (StructByValue *)data; xfree(sbv->base.ffiType); xfree(sbv); } +static size_t +sbv_memsize(const void *data) +{ + const StructByValue *sbv = (const StructByValue *)data; + return sizeof(StructByValue) + sizeof(*sbv->base.ffiType); +} static VALUE sbv_layout(VALUE self) { StructByValue* sbv; - Data_Get_Struct(self, StructByValue, sbv); + TypedData_Get_Struct(self, StructByValue, &sbv_type_data_type, sbv); return sbv->rbStructLayout; } @@ -125,7 +157,7 @@ sbv_struct_class(VALUE self) { StructByValue* sbv; - Data_Get_Struct(self, StructByValue, sbv); + TypedData_Get_Struct(self, StructByValue, &sbv_type_data_type, sbv); return sbv->rbStructClass; } diff --git a/ext/ffi_c/StructLayout.c b/ext/ffi_c/StructLayout.c index d318b8c..a56d48f 100644 --- a/ext/ffi_c/StructLayout.c +++ b/ext/ffi_c/StructLayout.c @@ -51,9 +51,13 @@ #define FFI_ALIGN(v, a) (((((size_t) (v))-1) | ((a)-1))+1) -static void struct_layout_mark(StructLayout *); -static void struct_layout_free(StructLayout *); -static void struct_field_mark(StructField* ); +static void struct_layout_mark(void *); +static void struct_layout_compact(void *); +static void struct_layout_free(void *); +static size_t struct_layout_memsize(const void *); +static void struct_field_mark(void *); +static void struct_field_compact(void *); +static size_t struct_field_memsize(const void *); VALUE rbffi_StructLayoutFieldClass = Qnil; VALUE rbffi_StructLayoutNumberFieldClass = Qnil, rbffi_StructLayoutPointerFieldClass = Qnil; @@ -62,6 +66,33 @@ VALUE rbffi_StructLayoutFunctionFieldClass = Qnil, rbffi_StructLayoutArrayFieldC VALUE rbffi_StructLayoutClass = Qnil; +const rb_data_type_t rbffi_struct_layout_data_type = { /* extern */ + .wrap_struct_name = "FFI::StructLayout", + .function = { + .dmark = struct_layout_mark, + .dfree = struct_layout_free, + .dsize = struct_layout_memsize, + ffi_compact_callback( struct_layout_compact ) + }, + .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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + +const rb_data_type_t rbffi_struct_field_data_type = { /* extern */ + .wrap_struct_name = "FFI::StructField", + .function = { + .dmark = struct_field_mark, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = struct_field_memsize, + ffi_compact_callback( struct_field_compact ) + }, + .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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; static VALUE struct_field_allocate(VALUE klass) @@ -69,18 +100,33 @@ struct_field_allocate(VALUE klass) StructField* field; VALUE obj; - obj = Data_Make_Struct(klass, StructField, struct_field_mark, -1, field); - field->rbType = Qnil; - field->rbName = Qnil; + obj = TypedData_Make_Struct(klass, StructField, &rbffi_struct_field_data_type, field); + RB_OBJ_WRITE(obj, &field->rbType, Qnil); + RB_OBJ_WRITE(obj, &field->rbName, Qnil); return obj; } static void -struct_field_mark(StructField* f) +struct_field_mark(void *data) { - rb_gc_mark(f->rbType); - rb_gc_mark(f->rbName); + StructField *f = (StructField *)data; + rb_gc_mark_movable(f->rbType); + rb_gc_mark_movable(f->rbName); +} + +static void +struct_field_compact(void *data) +{ + StructField *f = (StructField *)data; + ffi_gc_location(f->rbType); + ffi_gc_location(f->rbName); +} + +static size_t +struct_field_memsize(const void *data) +{ + return sizeof(StructField); } /* @@ -98,7 +144,7 @@ struct_field_initialize(int argc, VALUE* argv, VALUE self) StructField* field; int nargs; - Data_Get_Struct(self, StructField, field); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, field); nargs = rb_scan_args(argc, argv, "3", &rbName, &rbOffset, &rbType); @@ -115,9 +161,9 @@ struct_field_initialize(int argc, VALUE* argv, VALUE self) } field->offset = NUM2UINT(rbOffset); - field->rbName = (TYPE(rbName) == T_SYMBOL) ? rbName : rb_str_intern(rbName); - field->rbType = rbType; - Data_Get_Struct(field->rbType, Type, field->type); + RB_OBJ_WRITE(self, &field->rbName, (TYPE(rbName) == T_SYMBOL) ? rbName : rb_str_intern(rbName)); + RB_OBJ_WRITE(self, &field->rbType, rbType); + TypedData_Get_Struct(field->rbType, Type, &rbffi_type_data_type, field->type); field->memoryOp = get_memory_op(field->type); field->referenceIndex = -1; @@ -135,6 +181,8 @@ struct_field_initialize(int argc, VALUE* argv, VALUE self) break; } + rb_obj_freeze(self); + return self; } @@ -147,7 +195,7 @@ static VALUE struct_field_offset(VALUE self) { StructField* field; - Data_Get_Struct(self, StructField, field); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, field); return UINT2NUM(field->offset); } @@ -160,7 +208,7 @@ static VALUE struct_field_size(VALUE self) { StructField* field; - Data_Get_Struct(self, StructField, field); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, field); return UINT2NUM(field->type->ffiType->size); } @@ -173,7 +221,7 @@ static VALUE struct_field_alignment(VALUE self) { StructField* field; - Data_Get_Struct(self, StructField, field); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, field); return UINT2NUM(field->type->ffiType->alignment); } @@ -186,7 +234,7 @@ static VALUE struct_field_type(VALUE self) { StructField* field; - Data_Get_Struct(self, StructField, field); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, field); return field->rbType; } @@ -200,7 +248,7 @@ static VALUE struct_field_name(VALUE self) { StructField* field; - Data_Get_Struct(self, StructField, field); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, field); return field->rbName; } @@ -215,7 +263,7 @@ struct_field_get(VALUE self, VALUE pointer) { StructField* f; - Data_Get_Struct(self, StructField, f); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, f); if (f->memoryOp == NULL) { rb_raise(rb_eArgError, "get not supported for %s", rb_obj_classname(f->rbType)); return Qnil; @@ -236,7 +284,7 @@ struct_field_put(VALUE self, VALUE pointer, VALUE value) { StructField* f; - Data_Get_Struct(self, StructField, f); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, f); if (f->memoryOp == NULL) { rb_raise(rb_eArgError, "put not supported for %s", rb_obj_classname(f->rbType)); return self; @@ -258,7 +306,7 @@ function_field_get(VALUE self, VALUE pointer) { StructField* f; - Data_Get_Struct(self, StructField, f); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, f); return rbffi_Function_NewInstance(f->rbType, (*rbffi_AbstractMemoryOps.pointer->get)(MEMORY(pointer), f->offset)); } @@ -278,7 +326,7 @@ function_field_put(VALUE self, VALUE pointer, VALUE proc) StructField* f; VALUE value = Qnil; - Data_Get_Struct(self, StructField, f); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, f); if (NIL_P(proc) || rb_obj_is_kind_of(proc, rbffi_FunctionClass)) { value = proc; @@ -313,8 +361,8 @@ array_field_get(VALUE self, VALUE pointer) ArrayType* array; VALUE argv[2]; - Data_Get_Struct(self, StructField, f); - Data_Get_Struct(f->rbType, ArrayType, array); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, f); + TypedData_Get_Struct(f->rbType, ArrayType, &rbffi_array_type_data_type, array); argv[0] = pointer; argv[1] = self; @@ -336,9 +384,8 @@ array_field_put(VALUE self, VALUE pointer, VALUE value) StructField* f; ArrayType* array; - - Data_Get_Struct(self, StructField, f); - Data_Get_Struct(f->rbType, ArrayType, array); + TypedData_Get_Struct(self, StructField, &rbffi_struct_field_data_type, f); + TypedData_Get_Struct(f->rbType, ArrayType, &rbffi_array_type_data_type, array); if (isCharArray(array) && rb_obj_is_instance_of(value, rb_cString)) { VALUE argv[2]; @@ -385,12 +432,8 @@ array_field_put(VALUE self, VALUE pointer, VALUE value) VALUE entry = rb_ary_entry(value, i); Struct* s; - if (!rb_obj_is_kind_of(entry, rbffi_StructClass)) { - rb_raise(rb_eTypeError, "array element not an instance of FFI::Struct"); - break; - } + TypedData_Get_Struct(entry, Struct, &rbffi_struct_data_type, s); - Data_Get_Struct(entry, Struct, s); checkRead(s->pointer); checkBounds(s->pointer, 0, array->componentType->ffiType->size); @@ -416,7 +459,7 @@ struct_layout_allocate(VALUE klass) StructLayout* layout; VALUE obj; - obj = Data_Make_Struct(klass, StructLayout, struct_layout_mark, struct_layout_free, layout); + obj = TypedData_Make_Struct(klass, StructLayout, &rbffi_struct_layout_data_type, layout); layout->rbFieldMap = Qnil; layout->rbFieldNames = Qnil; layout->rbFields = Qnil; @@ -443,7 +486,7 @@ struct_layout_initialize(VALUE self, VALUE fields, VALUE size, VALUE align) ffi_type* ltype; int i; - Data_Get_Struct(self, StructLayout, layout); + TypedData_Get_Struct(self, StructLayout, &rbffi_struct_layout_data_type, layout); layout->fieldCount = (int) RARRAY_LEN(fields); layout->rbFieldMap = rb_hash_new(); layout->rbFieldNames = rb_ary_new2(layout->fieldCount); @@ -470,7 +513,7 @@ struct_layout_initialize(VALUE self, VALUE fields, VALUE size, VALUE align) } rbName = rb_funcall2(rbField, rb_intern("name"), 0, NULL); - Data_Get_Struct(rbField, StructField, field); + TypedData_Get_Struct(rbField, StructField, &rbffi_struct_field_data_type, field); layout->fields[i] = field; if (field->type == NULL || field->type->ffiType == NULL) { @@ -497,6 +540,11 @@ struct_layout_initialize(VALUE self, VALUE fields, VALUE size, VALUE align) rb_raise(rb_eRuntimeError, "Struct size is zero"); } + rb_obj_freeze(layout->rbFieldMap); + rb_obj_freeze(layout->rbFields); + rb_obj_freeze(layout->rbFieldNames); + rb_obj_freeze(self); + return self; } @@ -515,7 +563,7 @@ struct_layout_union_bang(VALUE self) ffi_type *t = NULL; int count, i; - Data_Get_Struct(self, StructLayout, layout); + TypedData_Get_Struct(self, StructLayout, &rbffi_struct_layout_data_type, layout); for (i = 0; alignment_types[i] != NULL; ++i) { if (alignment_types[i]->alignment == layout->align) { @@ -545,7 +593,7 @@ struct_layout_aref(VALUE self, VALUE field) { StructLayout* layout; - Data_Get_Struct(self, StructLayout, layout); + TypedData_Get_Struct(self, StructLayout, &rbffi_struct_layout_data_type, layout); return rb_hash_aref(layout->rbFieldMap, field); } @@ -560,7 +608,7 @@ struct_layout_fields(VALUE self) { StructLayout* layout; - Data_Get_Struct(self, StructLayout, layout); + TypedData_Get_Struct(self, StructLayout, &rbffi_struct_layout_data_type, layout); return rb_ary_dup(layout->rbFields); } @@ -575,7 +623,7 @@ struct_layout_members(VALUE self) { StructLayout* layout; - Data_Get_Struct(self, StructLayout, layout); + TypedData_Get_Struct(self, StructLayout, &rbffi_struct_layout_data_type, layout); return rb_ary_dup(layout->rbFieldNames); } @@ -590,26 +638,37 @@ struct_layout_to_a(VALUE self) { StructLayout* layout; - Data_Get_Struct(self, StructLayout, layout); + TypedData_Get_Struct(self, StructLayout, &rbffi_struct_layout_data_type, layout); return rb_ary_dup(layout->rbFields); } static void -struct_layout_mark(StructLayout *layout) +struct_layout_mark(void *data) { - rb_gc_mark(layout->rbFieldMap); - rb_gc_mark(layout->rbFieldNames); - rb_gc_mark(layout->rbFields); - /* Clear the cache, to be safe from changes of fieldName VALUE by GC.compact. - * TODO: Move cache clearing to compactation callback provided by Ruby-2.7+. - */ + StructLayout *layout = (StructLayout *)data; + rb_gc_mark_movable(layout->rbFieldMap); + rb_gc_mark_movable(layout->rbFieldNames); + rb_gc_mark_movable(layout->rbFields); + /* The values stored in layout->cache_row.fieldName are primary stored in layout->rbFieldMap and are marked there */ +} + +static void +struct_layout_compact(void *data) +{ + StructLayout *layout = (StructLayout *)data; + ffi_gc_location(layout->rbFieldMap); + ffi_gc_location(layout->rbFieldNames); + ffi_gc_location(layout->rbFields); + + /* Clear the cache, to be safe from changes of fieldName VALUE by GC.compact */ memset(&layout->cache_row, 0, sizeof(layout->cache_row)); } static void -struct_layout_free(StructLayout *layout) +struct_layout_free(void *data) { + StructLayout *layout = (StructLayout *)data; xfree(layout->ffiTypes); xfree(layout->base.ffiType); xfree(layout->fields); @@ -617,6 +676,16 @@ struct_layout_free(StructLayout *layout) } +static size_t +struct_layout_memsize(const void * data) +{ + const StructLayout *layout = (const StructLayout *)data; + size_t memsize = sizeof(StructLayout); + memsize += layout->fieldCount * (sizeof(StructField *) + sizeof(ffi_type *)); + memsize += sizeof(*layout->base.ffiType); + return memsize; +} + void rbffi_StructLayout_Init(VALUE moduleFFI) { diff --git a/ext/ffi_c/Type.c b/ext/ffi_c/Type.c index 7776bb0..a94c009 100644 --- a/ext/ffi_c/Type.c +++ b/ext/ffi_c/Type.c @@ -14,7 +14,7 @@ * * Neither the name of the Ruby FFI project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -33,36 +33,74 @@ #include <sys/types.h> #include <ruby.h> +#if HAVE_RB_EXT_RACTOR_SAFE +#include <ruby/ractor.h> +#endif #include <ffi.h> #include "rbffi.h" #include "compat.h" #include "Types.h" #include "Type.h" +static size_t type_memsize(const void *); typedef struct BuiltinType_ { Type type; - char* name; + const char* name; } BuiltinType; -static void builtin_type_free(BuiltinType *); +static size_t builtin_type_memsize(const void *); VALUE rbffi_TypeClass = Qnil; static VALUE classBuiltinType = Qnil; static VALUE moduleNativeType = Qnil; -static VALUE typeMap = Qnil, sizeMap = Qnil; -static ID id_find_type = 0, id_type_size = 0, id_size = 0; +static VALUE typeMap = Qnil; +static ID id_type_size = 0, id_size = 0; +#if HAVE_RB_EXT_RACTOR_SAFE +static rb_ractor_local_key_t custom_typedefs_key; +#endif + +const rb_data_type_t rbffi_type_data_type = { /* extern */ + .wrap_struct_name = "FFI::Type", + .function = { + .dmark = NULL, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = type_memsize, + }, + // 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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + +static const rb_data_type_t builtin_type_data_type = { + .wrap_struct_name = "FFI::Type::Builtin", + .function = { + .dmark = NULL, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = builtin_type_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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + +static size_t +type_memsize(const void *data) +{ + return sizeof(Type); +} static VALUE type_allocate(VALUE klass) { Type* type; - VALUE obj = Data_Make_Struct(klass, Type, NULL, -1, type); + VALUE obj = TypedData_Make_Struct(klass, Type, &rbffi_type_data_type, type); type->nativeType = -1; type->ffiType = &ffi_type_void; - + return obj; } @@ -78,18 +116,20 @@ type_initialize(VALUE self, VALUE value) Type* type; Type* other; - Data_Get_Struct(self, Type, type); + TypedData_Get_Struct(self, Type, &rbffi_type_data_type, type); if (FIXNUM_P(value)) { type->nativeType = FIX2INT(value); } else if (rb_obj_is_kind_of(value, rbffi_TypeClass)) { - Data_Get_Struct(value, Type, other); + TypedData_Get_Struct(value, Type, &rbffi_type_data_type, other); type->nativeType = other->nativeType; type->ffiType = other->ffiType; } else { rb_raise(rb_eArgError, "wrong type"); } - + + rb_obj_freeze(self); + return self; } @@ -103,7 +143,7 @@ type_size(VALUE self) { Type *type; - Data_Get_Struct(self, Type, type); + TypedData_Get_Struct(self, Type, &rbffi_type_data_type, type); return INT2FIX(type->ffiType->size); } @@ -118,7 +158,7 @@ type_alignment(VALUE self) { Type *type; - Data_Get_Struct(self, Type, type); + TypedData_Get_Struct(self, Type, &rbffi_type_data_type, type); return INT2FIX(type->ffiType->alignment); } @@ -134,9 +174,9 @@ type_inspect(VALUE self) char buf[100]; Type *type; - Data_Get_Struct(self, Type, type); + TypedData_Get_Struct(self, Type, &rbffi_type_data_type, type); - snprintf(buf, sizeof(buf), "#<%s:%p size=%d alignment=%d>", + snprintf(buf, sizeof(buf), "#<%s::%p size=%d alignment=%d>", rb_obj_classname(self), type, (int) type->ffiType->size, (int) type->ffiType->alignment); return rb_str_new2(buf); @@ -148,20 +188,21 @@ builtin_type_new(VALUE klass, int nativeType, ffi_type* ffiType, const char* nam BuiltinType* type; VALUE obj = Qnil; - obj = Data_Make_Struct(klass, BuiltinType, NULL, builtin_type_free, type); - - type->name = strdup(name); + obj = TypedData_Make_Struct(klass, BuiltinType, &builtin_type_data_type, type); + + type->name = name; type->type.nativeType = nativeType; type->type.ffiType = ffiType; + rb_obj_freeze(obj); + return obj; } -static void -builtin_type_free(BuiltinType *type) +static size_t +builtin_type_memsize(const void *data) { - free(type->name); - xfree(type); + return sizeof(BuiltinType) + sizeof(ffi_type); } /* @@ -175,8 +216,8 @@ builtin_type_inspect(VALUE self) char buf[100]; BuiltinType *type; - Data_Get_Struct(self, BuiltinType, type); - snprintf(buf, sizeof(buf), "#<%s:%s size=%d alignment=%d>", + TypedData_Get_Struct(self, BuiltinType, &builtin_type_data_type, type); + snprintf(buf, sizeof(buf), "#<%s::%s size=%d alignment=%d>", rb_obj_classname(self), type->name, (int) type->type.ffiType->size, type->type.ffiType->alignment); return rb_str_new2(buf); @@ -186,21 +227,21 @@ int rbffi_type_size(VALUE type) { int t = TYPE(type); - + if (t == T_FIXNUM || t == T_BIGNUM) { return NUM2INT(type); - + } else if (t == T_SYMBOL) { /* - * Try looking up directly in the type and size maps + * Try looking up directly in the type map */ VALUE nType; if ((nType = rb_hash_lookup(typeMap, type)) != Qnil) { if (rb_obj_is_kind_of(nType, rbffi_TypeClass)) { Type* type; - Data_Get_Struct(nType, Type, type); + TypedData_Get_Struct(nType, Type, &rbffi_type_data_type, type); return (int) type->ffiType->size; - + } else if (rb_respond_to(nType, id_size)) { return NUM2INT(rb_funcall2(nType, id_size, 0, NULL)); } @@ -208,26 +249,51 @@ rbffi_type_size(VALUE type) /* Not found - call up to the ruby version to resolve */ return NUM2INT(rb_funcall2(rbffi_FFIModule, id_type_size, 1, &type)); - + } else { return NUM2INT(rb_funcall2(type, id_size, 0, NULL)); } } +static VALUE +custom_typedefs(VALUE self) +{ +#if HAVE_RB_EXT_RACTOR_SAFE + VALUE hash = rb_ractor_local_storage_value(custom_typedefs_key); + if (hash == Qnil) { + hash = rb_hash_new(); + rb_ractor_local_storage_value_set(custom_typedefs_key, hash); + } +#else + static VALUE hash = Qundef; + if (hash == Qundef) { + rb_global_variable(&hash); + hash = rb_hash_new(); + } +#endif + return hash; +} + VALUE rbffi_Type_Lookup(VALUE name) { int t = TYPE(name); if (t == T_SYMBOL || t == T_STRING) { /* - * Try looking up directly in the type Map + * Try looking up directly in the type map */ VALUE nType; + VALUE cust = custom_typedefs(Qnil); + + if ((nType = rb_hash_lookup(cust, name)) != Qnil && rb_obj_is_kind_of(nType, rbffi_TypeClass)) { + return nType; + } + if ((nType = rb_hash_lookup(typeMap, name)) != Qnil && rb_obj_is_kind_of(nType, rbffi_TypeClass)) { return nType; } } else if (rb_obj_is_kind_of(name, rbffi_TypeClass)) { - + return name; } @@ -251,13 +317,15 @@ rbffi_Type_Init(VALUE moduleFFI) * Document-constant: FFI::TypeDefs */ rb_define_const(moduleFFI, "TypeDefs", typeMap = rb_hash_new()); - rb_define_const(moduleFFI, "SizeTypes", sizeMap = rb_hash_new()); rb_global_variable(&typeMap); - rb_global_variable(&sizeMap); - id_find_type = rb_intern("find_type"); id_type_size = rb_intern("type_size"); id_size = rb_intern("size"); +#if HAVE_RB_EXT_RACTOR_SAFE + custom_typedefs_key = rb_ractor_local_storage_value_newkey(); +#endif + rb_define_module_function(moduleFFI, "custom_typedefs", custom_typedefs, 0); + /* * Document-class: FFI::Type::Builtin * Class for Built-in types. @@ -297,7 +365,7 @@ rbffi_Type_Init(VALUE moduleFFI) * * BUFFER_OUT * * VARARGS (function takes a variable number of arguments) * - * All these constants are exported to {FFI} module prefixed with "TYPE_". + * All these constants are exported to {FFI} module prefixed with "TYPE_". * They are objets from {FFI::Type::Builtin} class. */ moduleNativeType = rb_define_module_under(moduleFFI, "NativeType"); @@ -318,7 +386,7 @@ rbffi_Type_Init(VALUE moduleFFI) /* Make Type::Builtin non-allocatable */ rb_undef_method(CLASS_OF(classBuiltinType), "new"); rb_define_method(classBuiltinType, "inspect", builtin_type_inspect, 0); - + rb_global_variable(&rbffi_TypeClass); rb_global_variable(&classBuiltinType); diff --git a/ext/ffi_c/Type.h b/ext/ffi_c/Type.h index b81995a..b6de634 100644 --- a/ext/ffi_c/Type.h +++ b/ext/ffi_c/Type.h @@ -44,7 +44,7 @@ extern "C" { typedef struct Type_ Type; #include "Types.h" - + struct Type_ { NativeType nativeType; ffi_type* ffiType; @@ -53,6 +53,8 @@ struct Type_ { extern VALUE rbffi_TypeClass; extern VALUE rbffi_Type_Lookup(VALUE type); +extern const rb_data_type_t rbffi_type_data_type; + #ifdef __cplusplus } #endif diff --git a/ext/ffi_c/Types.c b/ext/ffi_c/Types.c index 77741e0..8695a3b 100644 --- a/ext/ffi_c/Types.c +++ b/ext/ffi_c/Types.c @@ -97,7 +97,7 @@ rbffi_NativeValue_ToRuby(Type* type, VALUE rbType, const void* ptr) AbstractMemory* mem; VALUE rbMemory = rbffi_MemoryPointer_NewInstance(1, sbv->base.ffiType->size, false); - Data_Get_Struct(rbMemory, AbstractMemory, mem); + TypedData_Get_Struct(rbMemory, AbstractMemory, &rbffi_abstract_memory_data_type, mem); memcpy(mem->address, ptr, sbv->base.ffiType->size); RB_GC_GUARD(rbMemory); RB_GC_GUARD(rbType); diff --git a/ext/ffi_c/Variadic.c b/ext/ffi_c/Variadic.c index 8ad38b1..09d7ce8 100644 --- a/ext/ffi_c/Variadic.c +++ b/ext/ffi_c/Variadic.c @@ -36,6 +36,9 @@ #include <stdint.h> #include <stdbool.h> #include <ruby.h> +#if HAVE_RB_EXT_RACTOR_SAFE +#include <ruby/ractor.h> +#endif #include <ffi.h> #include "rbffi.h" @@ -62,35 +65,65 @@ typedef struct VariadicInvoker_ { bool blocking; } VariadicInvoker; - static VALUE variadic_allocate(VALUE klass); static VALUE variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE rbReturnType, VALUE options); -static void variadic_mark(VariadicInvoker *); +static void variadic_mark(void *); +static void variadic_compact(void *); +static size_t variadic_memsize(const void *); static VALUE classVariadicInvoker = Qnil; +static const rb_data_type_t variadic_data_type = { + .wrap_struct_name = "FFI::VariadicInvoker", + .function = { + .dmark = variadic_mark, + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = variadic_memsize, + ffi_compact_callback( variadic_compact ) + }, + // 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 | FFI_RUBY_TYPED_FROZEN_SHAREABLE +}; + static VALUE variadic_allocate(VALUE klass) { VariadicInvoker *invoker; - VALUE obj = Data_Make_Struct(klass, VariadicInvoker, variadic_mark, -1, invoker); + VALUE obj = TypedData_Make_Struct(klass, VariadicInvoker, &variadic_data_type, invoker); - invoker->rbAddress = Qnil; - invoker->rbEnums = Qnil; - invoker->rbReturnType = Qnil; + RB_OBJ_WRITE(obj, &invoker->rbAddress, Qnil); + RB_OBJ_WRITE(obj, &invoker->rbEnums, Qnil); + RB_OBJ_WRITE(obj, &invoker->rbReturnType, Qnil); invoker->blocking = false; return obj; } static void -variadic_mark(VariadicInvoker *invoker) +variadic_mark(void *data) +{ + VariadicInvoker *invoker = (VariadicInvoker *)data; + rb_gc_mark_movable(invoker->rbEnums); + rb_gc_mark_movable(invoker->rbAddress); + rb_gc_mark_movable(invoker->rbReturnType); +} + +static void +variadic_compact(void *data) { - rb_gc_mark(invoker->rbEnums); - rb_gc_mark(invoker->rbAddress); - rb_gc_mark(invoker->rbReturnType); + VariadicInvoker *invoker = (VariadicInvoker *)data; + ffi_gc_location(invoker->rbEnums); + ffi_gc_location(invoker->rbAddress); + ffi_gc_location(invoker->rbReturnType); +} + +static size_t +variadic_memsize(const void *data) +{ + return sizeof(VariadicInvoker); } static VALUE @@ -108,10 +141,10 @@ variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE Check_Type(options, T_HASH); convention = rb_hash_aref(options, ID2SYM(rb_intern("convention"))); - Data_Get_Struct(self, VariadicInvoker, invoker); - invoker->rbEnums = rb_hash_aref(options, ID2SYM(rb_intern("enums"))); - invoker->rbAddress = rbFunction; - invoker->function = rbffi_AbstractMemory_Cast(rbFunction, rbffi_PointerClass)->address; + TypedData_Get_Struct(self, VariadicInvoker, &variadic_data_type, invoker); + RB_OBJ_WRITE(self, &invoker->rbEnums, rb_hash_aref(options, ID2SYM(rb_intern("enums")))); + RB_OBJ_WRITE(self, &invoker->rbAddress, rbFunction); + invoker->function = rbffi_AbstractMemory_Cast(rbFunction, &rbffi_pointer_data_type)->address; invoker->blocking = RTEST(rb_hash_aref(options, ID2SYM(rb_intern("blocking")))); #if defined(X86_WIN32) @@ -122,13 +155,13 @@ variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE invoker->abi = FFI_DEFAULT_ABI; #endif - invoker->rbReturnType = rbffi_Type_Lookup(rbReturnType); + RB_OBJ_WRITE(self, &invoker->rbReturnType, rbffi_Type_Lookup(rbReturnType)); if (!RTEST(invoker->rbReturnType)) { VALUE typeName = rb_funcall2(rbReturnType, rb_intern("inspect"), 0, NULL); rb_raise(rb_eTypeError, "Invalid return type (%s)", RSTRING_PTR(typeName)); } - Data_Get_Struct(rbReturnType, Type, invoker->returnType); + TypedData_Get_Struct(rbReturnType, Type, &rbffi_type_data_type, invoker->returnType); invoker->paramCount = -1; @@ -142,7 +175,7 @@ variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE VALUE typeName = rb_funcall2(entry, rb_intern("inspect"), 0, NULL); rb_raise(rb_eTypeError, "Invalid parameter type (%s)", RSTRING_PTR(typeName)); } - Data_Get_Struct(rbType, Type, type); + TypedData_Get_Struct(rbType, Type, &rbffi_type_data_type, type); if (type->nativeType != NATIVE_VARARGS) { rb_ary_push(fixed, entry); } @@ -150,7 +183,7 @@ variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE /* * @fixed and @type_map are used by the parameter mangling ruby code */ - rb_iv_set(self, "@fixed", fixed); + rb_iv_set(self, "@fixed", rb_obj_freeze(fixed)); rb_iv_set(self, "@type_map", rb_hash_aref(options, ID2SYM(rb_intern("type_map")))); return retval; @@ -176,7 +209,7 @@ variadic_invoke(VALUE self, VALUE parameterTypes, VALUE parameterValues) Check_Type(parameterTypes, T_ARRAY); Check_Type(parameterValues, T_ARRAY); - Data_Get_Struct(self, VariadicInvoker, invoker); + TypedData_Get_Struct(self, VariadicInvoker, &variadic_data_type, invoker); paramCount = (int) RARRAY_LEN(parameterTypes); paramTypes = ALLOCA_N(Type *, paramCount); ffiParamTypes = ALLOCA_N(ffi_type *, paramCount); @@ -192,25 +225,25 @@ variadic_invoke(VALUE self, VALUE parameterTypes, VALUE parameterValues) if (!rb_obj_is_kind_of(rbType, rbffi_TypeClass)) { rb_raise(rb_eTypeError, "wrong type. Expected (FFI::Type)"); } - Data_Get_Struct(rbType, Type, paramTypes[i]); + TypedData_Get_Struct(rbType, Type, &rbffi_type_data_type, paramTypes[i]); switch (paramTypes[i]->nativeType) { case NATIVE_INT8: case NATIVE_INT16: case NATIVE_INT32: rbType = rb_const_get(rbffi_TypeClass, rb_intern("INT32")); - Data_Get_Struct(rbType, Type, paramTypes[i]); + TypedData_Get_Struct(rbType, Type, &rbffi_type_data_type, paramTypes[i]); break; case NATIVE_UINT8: case NATIVE_UINT16: case NATIVE_UINT32: rbType = rb_const_get(rbffi_TypeClass, rb_intern("UINT32")); - Data_Get_Struct(rbType, Type, paramTypes[i]); + TypedData_Get_Struct(rbType, Type, &rbffi_type_data_type, paramTypes[i]); break; case NATIVE_FLOAT32: rbType = rb_const_get(rbffi_TypeClass, rb_intern("DOUBLE")); - Data_Get_Struct(rbType, Type, paramTypes[i]); + TypedData_Get_Struct(rbType, Type, &rbffi_type_data_type, paramTypes[i]); break; case NATIVE_FUNCTION: @@ -288,6 +321,14 @@ variadic_invoke(VALUE self, VALUE parameterTypes, VALUE parameterValues) return rbffi_NativeValue_ToRuby(invoker->returnType, invoker->rbReturnType, retval); } +static VALUE +variadic_return_type(VALUE self) +{ + VariadicInvoker* invoker; + + TypedData_Get_Struct(self, VariadicInvoker, &variadic_data_type, invoker); + return invoker->rbReturnType; +} void rbffi_Variadic_Init(VALUE moduleFFI) @@ -299,5 +340,6 @@ rbffi_Variadic_Init(VALUE moduleFFI) rb_define_method(classVariadicInvoker, "initialize", variadic_initialize, 4); rb_define_method(classVariadicInvoker, "invoke", variadic_invoke, 2); + rb_define_method(classVariadicInvoker, "return_type", variadic_return_type, 0); } diff --git a/ext/ffi_c/compat.h b/ext/ffi_c/compat.h index 3f7bbae..a1be55d 100644 --- a/ext/ffi_c/compat.h +++ b/ext/ffi_c/compat.h @@ -32,26 +32,6 @@ #include <ruby.h> -#ifndef RARRAY_LEN -# define RARRAY_LEN(ary) RARRAY(ary)->len -#endif - -#ifndef RARRAY_PTR -# define RARRAY_PTR(ary) RARRAY(ary)->ptr -#endif - -#ifndef RSTRING_LEN -# define RSTRING_LEN(s) RSTRING(s)->len -#endif - -#ifndef RSTRING_PTR -# define RSTRING_PTR(s) RSTRING(s)->ptr -#endif - -#ifndef NUM2ULL -# define NUM2ULL(x) rb_num2ull((VALUE)x) -#endif - #ifndef roundup # define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) #endif @@ -75,8 +55,28 @@ # define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif -#ifndef RB_GC_GUARD -# define RB_GC_GUARD(x) (x) + + +/* For compatibility with ruby < 2.7 */ +#ifdef HAVE_RB_GC_MARK_MOVABLE +#define ffi_compact_callback(x) .dcompact = (x), +#define ffi_gc_location(x) x = rb_gc_location(x) +#else +#define rb_gc_mark_movable(x) rb_gc_mark(x) +#define ffi_compact_callback(x) +#define ffi_gc_location(x) +#endif + + +/* For compatibility with ruby < 3.0 */ +#ifndef RUBY_TYPED_FROZEN_SHAREABLE +#define FFI_RUBY_TYPED_FROZEN_SHAREABLE 0 +#else +#define FFI_RUBY_TYPED_FROZEN_SHAREABLE RUBY_TYPED_FROZEN_SHAREABLE +#endif + +#ifndef HAVE_RB_EXT_RACTOR_SAFE +#define rb_ractor_make_shareable(self) rb_obj_freeze(self); #endif #endif /* RBFFI_COMPAT_H */ diff --git a/ext/ffi_c/extconf.rb b/ext/ffi_c/extconf.rb index 720fb06..b3eb020 100644..100755 --- a/ext/ffi_c/extconf.rb +++ b/ext/ffi_c/extconf.rb @@ -33,7 +33,7 @@ if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'rbx' $CFLAGS.gsub!(/[\s+]-ansi/, '') $CFLAGS.gsub!(/[\s+]-std=[^\s]+/, '') # solaris 10 needs -c99 for <stdbool.h> - $CFLAGS << " -std=c99" if RbConfig::CONFIG['host_os'] =~ /solaris(!?2\.11)/ + $CFLAGS << " -g -std=c99" if RbConfig::CONFIG['host_os'] =~ /solaris(!?2\.11)/ # Check whether we use system libffi system_libffi = enable_config('system-libffi', :try) @@ -57,10 +57,13 @@ if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'rbx' append_ldflags "-Wl,--exclude-libs,ALL" end + have_func 'rb_gc_mark_movable' # since ruby-2.7 + # Some linux archs need explicit linking to pthread, see https://github.com/ffi/ffi/issues/893 append_ldflags "-pthread" ffi_alloc_default = RbConfig::CONFIG['host_os'] =~ /darwin/i && RbConfig::CONFIG['host'] =~ /arm|aarch64/i + ffi_alloc_default = ffi_alloc_default || RbConfig::CONFIG['host'] =~ /hppa/i if enable_config('libffi-alloc', ffi_alloc_default) $defs << "-DUSE_FFI_ALLOC" end diff --git a/ext/ffi_c/ffi.c b/ext/ffi_c/ffi.c index 22ea3bf..e297f8a 100644 --- a/ext/ffi_c/ffi.c +++ b/ext/ffi_c/ffi.c @@ -60,6 +60,10 @@ static VALUE moduleFFI = Qnil; void Init_ffi_c(void) { + #ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(1); + #endif + /* * Document-module: FFI * diff --git a/ext/ffi_c/libffi b/ext/ffi_c/libffi -Subproject 5c63b463b87d3c06102a4a7f05f395929d9ea79 +Subproject ac598b7f5272d536b75f4b3833a4610cf4cd940 diff --git a/ext/ffi_c/rbffi.h b/ext/ffi_c/rbffi.h index 89b3e32..0e4e91a 100644 --- a/ext/ffi_c/rbffi.h +++ b/ext/ffi_c/rbffi.h @@ -39,7 +39,7 @@ extern "C" { #define MAX_PARAMETERS (32) extern VALUE rbffi_FFIModule; - + extern void rbffi_Type_Init(VALUE ffiModule); extern void rbffi_Buffer_Init(VALUE ffiModule); extern void rbffi_Invoker_Init(VALUE ffiModule); diff --git a/ffi.gemspec b/ffi.gemspec index 0e9d86a..71b5931 100644 --- a/ffi.gemspec +++ b/ffi.gemspec @@ -34,7 +34,7 @@ Gem::Specification.new do |s| s.rdoc_options = %w[--exclude=ext/ffi_c/.*\.o$ --exclude=ffi_c\.(bundle|so)$] s.license = 'BSD-3-Clause' s.require_paths << 'ext/ffi_c' - s.required_ruby_version = '>= 2.3' + s.required_ruby_version = '>= 2.5' s.add_development_dependency 'rake', '~> 13.0' s.add_development_dependency 'rake-compiler', '~> 1.1' s.add_development_dependency 'rake-compiler-dock', '~> 1.0' diff --git a/gen/Rakefile b/gen/Rakefile index f71a1da..65f62c9 100644 --- a/gen/Rakefile +++ b/gen/Rakefile @@ -9,7 +9,7 @@ logfile = File.join(File.dirname(__FILE__), 'log') file types_conf do |task| options = {} - FileUtils.mkdir_p(File.dirname(task.name), { :mode => 0755 }) + FileUtils.mkdir_p(File.dirname(task.name), mode: 0755) File.open(task.name, File::CREAT|File::TRUNC|File::RDWR, 0644) do |f| f.puts FFI::TypesGenerator.generate(options) end diff --git a/lib/ffi/autopointer.rb b/lib/ffi/autopointer.rb index 679d7e6..bbcb6db 100644 --- a/lib/ffi/autopointer.rb +++ b/lib/ffi/autopointer.rb @@ -76,21 +76,21 @@ module FFI # going to be useful if you subclass {AutoPointer}, and override # #release, which by default does nothing. def initialize(ptr, proc=nil, &block) + raise TypeError, "Invalid pointer" if ptr.nil? || !ptr.kind_of?(Pointer) || + ptr.kind_of?(MemoryPointer) || ptr.kind_of?(AutoPointer) super(ptr.type_size, ptr) - raise TypeError, "Invalid pointer" if ptr.nil? || !ptr.kind_of?(Pointer) \ - || ptr.kind_of?(MemoryPointer) || ptr.kind_of?(AutoPointer) @releaser = if proc if not proc.respond_to?(:call) raise RuntimeError.new("proc must be callable") end - CallableReleaser.new(ptr, proc) + Releaser.new(ptr, proc) else - if not self.class.respond_to?(:release) + if not self.class.respond_to?(:release, true) raise RuntimeError.new("no release method defined") end - DefaultReleaser.new(ptr, self.class) + Releaser.new(ptr, self.class.method(:release)) end ObjectSpace.define_finalizer(self, @releaser) @@ -107,6 +107,7 @@ module FFI # @return [Boolean] +autorelease+ # Set +autorelease+ property. See {Pointer Autorelease section at Pointer}. def autorelease=(autorelease) + raise FrozenError.new("can't modify frozen #{self.class}") if frozen? @releaser.autorelease=(autorelease) end @@ -149,23 +150,7 @@ module FFI def call(*args) release(@ptr) if @autorelease && @ptr end - end - - # DefaultReleaser is a {Releaser} used when an {AutoPointer} is defined - # without Proc or Method. In this case, the pointer to release must be of - # a class derived from AutoPointer with a {release} class method. - class DefaultReleaser < Releaser - # @param [Pointer] ptr - # @return [nil] - # Release +ptr+ using the {release} class method of its class. - def release(ptr) - @proc.release(ptr) - end - end - # CallableReleaser is a {Releaser} used when an {AutoPointer} is defined with a - # Proc or a Method. - class CallableReleaser < Releaser # Release +ptr+ by using Proc or Method defined at +ptr+ # {AutoPointer#initialize initialization}. # @@ -182,7 +167,7 @@ module FFI # @return [Type::POINTER] # @raise {RuntimeError} if class does not implement a +#release+ method def self.native_type - if not self.respond_to?(:release) + if not self.respond_to?(:release, true) raise RuntimeError.new("no release method defined for #{self.inspect}") end Type::POINTER diff --git a/lib/ffi/compat.rb b/lib/ffi/compat.rb new file mode 100644 index 0000000..7569013 --- /dev/null +++ b/lib/ffi/compat.rb @@ -0,0 +1,43 @@ +# +# Copyright (C) 2023-2023 Lars Kanis +# +# This file is part of ruby-ffi. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the Ruby FFI project nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +module FFI + if defined?(Ractor.make_shareable) + # This is for FFI internal use only. + def self.make_shareable(obj) + Ractor.make_shareable(obj) + end + else + def self.make_shareable(obj) + obj.freeze + end + end +end diff --git a/lib/ffi/data_converter.rb b/lib/ffi/data_converter.rb index 1527588..7852e7e 100644 --- a/lib/ffi/data_converter.rb +++ b/lib/ffi/data_converter.rb @@ -31,7 +31,7 @@ module FFI # This module is used to extend somes classes and give then a common API. # - # Most of methods defined here must be overriden. + # Most of methods defined here must be overridden. module DataConverter # Get native type. # @@ -41,7 +41,7 @@ module FFI # Get native type from +type+. # # @overload native_type - # @raise {NotImplementedError} This method must be overriden. + # @raise {NotImplementedError} This method must be overridden. def native_type(type = nil) if type @native_type = FFI.find_type(type) diff --git a/lib/ffi/dynamic_library.rb b/lib/ffi/dynamic_library.rb new file mode 100644 index 0000000..b415033 --- /dev/null +++ b/lib/ffi/dynamic_library.rb @@ -0,0 +1,89 @@ +# +# Copyright (C) 2008-2010 Wayne Meissner +# +# This file is part of ruby-ffi. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the Ruby FFI project nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.# + +module FFI + class DynamicLibrary + SEARCH_PATH = %w[/usr/lib /usr/local/lib /opt/local/lib] + if FFI::Platform::ARCH == 'aarch64' && FFI::Platform.mac? + SEARCH_PATH << '/opt/homebrew/lib' + end + + SEARCH_PATH_MESSAGE = "Searched in <system library path>, #{SEARCH_PATH.join(', ')}".freeze + + def self.load_library(name, flags) + if name == FFI::CURRENT_PROCESS + FFI::DynamicLibrary.open(nil, RTLD_LAZY | RTLD_LOCAL) + else + flags ||= RTLD_LAZY | RTLD_LOCAL + + libnames = (name.is_a?(::Array) ? name : [name]) + libnames = libnames.map(&:to_s).map { |n| [n, FFI.map_library_name(n)].uniq }.flatten.compact + errors = [] + + libnames.each do |libname| + lib = try_load(libname, flags, errors) + return lib if lib + + unless libname.start_with?("/") || FFI::Platform.windows? + SEARCH_PATH.each do |prefix| + path = "#{prefix}/#{libname}" + if File.exist?(path) + lib = try_load(path, flags, errors) + return lib if lib + end + end + end + end + + raise LoadError, [*errors, SEARCH_PATH_MESSAGE].join(".\n") + end + end + private_class_method :load_library + + def self.try_load(libname, flags, errors) + begin + lib = FFI::DynamicLibrary.open(libname, flags) + return lib if lib + + # LoadError for C ext & JRuby, RuntimeError for TruffleRuby + rescue LoadError, RuntimeError => ex + if ex.message =~ /(([^ \t()])+\.so([^ \t:()])*):([ \t])*(invalid ELF header|file too short|invalid file format)/ + if File.binread($1) =~ /(?:GROUP|INPUT) *\( *([^ \)]+)/ + return try_load($1, flags, errors) + end + end + + errors << ex + nil + end + end + private_class_method :try_load + end +end diff --git a/lib/ffi/ffi.rb b/lib/ffi/ffi.rb index dfffa8c..bc1b2e6 100644 --- a/lib/ffi/ffi.rb +++ b/lib/ffi/ffi.rb @@ -28,6 +28,7 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +require 'ffi/compat' require 'ffi/platform' require 'ffi/data_converter' require 'ffi/types' @@ -45,3 +46,4 @@ require 'ffi/autopointer' require 'ffi/variadic' require 'ffi/enum' require 'ffi/version' +require 'ffi/function' diff --git a/lib/ffi/function.rb b/lib/ffi/function.rb new file mode 100644 index 0000000..ac4daf0 --- /dev/null +++ b/lib/ffi/function.rb @@ -0,0 +1,71 @@ +# +# Copyright (C) 2008-2010 JRuby project +# +# This file is part of ruby-ffi. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the Ruby FFI project nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +module FFI + class Function + # Only MRI allows function type queries + if private_method_defined?(:type) + # Retrieve the return type of the function + # + # This method returns FFI type returned by the function. + # + # @return [FFI::Type] + def return_type + type.return_type + end + + # Retrieve Array of parameter types + # + # This method returns an Array of FFI types accepted as function parameters. + # + # @return [Array<FFI::Type>] + def param_types + type.param_types + end + end + + # Stash the Function in a module variable so it can be inspected by attached_functions. + # On CRuby it also ensures that it does not get garbage collected. + module RegisterAttach + def attach(mod, name) + funcs = mod.instance_variable_get("@ffi_functions") + unless funcs + funcs = {} + mod.instance_variable_set("@ffi_functions", funcs) + end + funcs[name.to_sym] = self + # Jump to the native attach method of CRuby, JRuby or Tuffleruby + super + end + end + private_constant :RegisterAttach + prepend RegisterAttach + end +end diff --git a/lib/ffi/library.rb b/lib/ffi/library.rb index c2628ab..2556975 100644 --- a/lib/ffi/library.rb +++ b/lib/ffi/library.rb @@ -28,8 +28,10 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.# +require 'ffi/dynamic_library' + module FFI - CURRENT_PROCESS = USE_THIS_PROCESS_AS_LIBRARY = Object.new + CURRENT_PROCESS = USE_THIS_PROCESS_AS_LIBRARY = FFI.make_shareable(Object.new) class LibraryPath < ::Struct.new(:name, :abi_number, :root) PATTERN = /(#{Platform::LIBPREFIX})?(?<name>.*?)(\.|\z)/ @@ -134,62 +136,11 @@ module FFI def ffi_lib(*names) raise LoadError.new("library names list must not be empty") if names.empty? - lib_flags = defined?(@ffi_lib_flags) ? @ffi_lib_flags : FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_LOCAL - ffi_libs = names.map do |name| - - if name == FFI::CURRENT_PROCESS - FFI::DynamicLibrary.open(nil, FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_LOCAL) - - else - libnames = (name.is_a?(::Array) ? name : [ name ]).map(&:to_s).map { |n| [ n, FFI.map_library_name(n) ].uniq }.flatten.compact - lib = nil - errors = {} - - libnames.each do |libname| - begin - orig = libname - lib = FFI::DynamicLibrary.open(libname, lib_flags) - break if lib - - rescue Exception => ex - ldscript = false - if ex.message =~ /(([^ \t()])+\.so([^ \t:()])*):([ \t])*(invalid ELF header|file too short|invalid file format)/ - if File.binread($1) =~ /(?:GROUP|INPUT) *\( *([^ \)]+)/ - libname = $1 - ldscript = true - end - end - - if ldscript - retry - else - # TODO better library lookup logic - unless libname.start_with?("/") || FFI::Platform.windows? - path = ['/usr/lib/','/usr/local/lib/','/opt/local/lib/', '/opt/homebrew/lib/'].find do |pth| - File.exist?(pth + libname) - end - if path - libname = path + libname - retry - end - end - - libr = (orig == libname ? orig : "#{orig} #{libname}") - errors[libr] = ex - end - end - end - - if lib.nil? - raise LoadError.new(errors.values.join(".\n")) - end + lib_flags = defined?(@ffi_lib_flags) && @ffi_lib_flags - # return the found lib - lib - end + @ffi_libs = names.map do |name| + FFI::DynamicLibrary.send(:load_library, name, lib_flags) end - - @ffi_libs = ffi_libs end # Set the calling convention for {#attach_function} and {#callback} @@ -297,7 +248,7 @@ module FFI end raise LoadError unless function - invokers << if arg_types.length > 0 && arg_types[arg_types.length - 1] == FFI::NativeType::VARARGS + invokers << if arg_types[-1] == FFI::NativeType::VARARGS VariadicInvoker.new(function, arg_types, find_type(ret_type), options) else @@ -369,6 +320,7 @@ module FFI # Attach C variable +cname+ to this module. def attach_variable(mname, a1, a2 = nil) cname, type = a2 ? [ a1, a2 ] : [ mname.to_s, a1 ] + mname = mname.to_sym address = nil ffi_libraries.each do |lib| begin @@ -383,9 +335,10 @@ module FFI # If it is a global struct, just attach directly to the pointer s = s = type.new(address) # Assigning twice to suppress unused variable warning self.module_eval <<-code, __FILE__, __LINE__ - @@ffi_gvar_#{mname} = s + @ffi_gsvars = {} unless defined?(@ffi_gsvars) + @ffi_gsvars[#{mname.inspect}] = s def self.#{mname} - @@ffi_gvar_#{mname} + @ffi_gsvars[#{mname.inspect}] end code @@ -397,12 +350,13 @@ module FFI # Attach to this module as mname/mname= # self.module_eval <<-code, __FILE__, __LINE__ - @@ffi_gvar_#{mname} = s + @ffi_gvars = {} unless defined?(@ffi_gvars) + @ffi_gvars[#{mname.inspect}] = s def self.#{mname} - @@ffi_gvar_#{mname}[:gvar] + @ffi_gvars[#{mname.inspect}][:gvar] end def self.#{mname}=(value) - @@ffi_gvar_#{mname}[:gvar] = value + @ffi_gvars[#{mname.inspect}][:gvar] = value end code @@ -627,5 +581,43 @@ module FFI end || FFI.find_type(t) end + + # Retrieve all attached functions and their function signature + # + # This method returns a Hash of method names of attached functions connected by #attach_function and the corresponding function type. + # The function type responds to #return_type and #param_types which return the FFI types of the function signature. + # + # @return [Hash< Symbol => [FFI::Function, FFI::VariadicInvoker] >] + def attached_functions + @ffi_functions || {} + end + + # Retrieve all attached variables and their type + # + # This method returns a Hash of variable names and the corresponding type or variables connected by #attach_variable . + # + # @return [Hash< Symbol => ffi_type >] + def attached_variables + ( + (@ffi_gsvars || {}).map do |name, gvar| + [name, gvar.class] + end + + (@ffi_gvars || {}).map do |name, gvar| + [name, gvar.layout[:gvar].type] + end + ).to_h + end + + # Freeze all definitions of the module + # + # This freezes the module's definitions, so that it can be used in a Ractor. + # No further methods or variables can be attached and no further enums or typedefs can be created in this module afterwards. + def freeze + instance_variables.each do |name| + var = instance_variable_get(name) + FFI.make_shareable(var) + end + nil + end end end diff --git a/lib/ffi/managedstruct.rb b/lib/ffi/managedstruct.rb index b5ec8a3..5d243e5 100644 --- a/lib/ffi/managedstruct.rb +++ b/lib/ffi/managedstruct.rb @@ -75,7 +75,7 @@ module FFI # @overload initialize # A new instance of FFI::ManagedStruct. def initialize(pointer=nil) - raise NoMethodError, "release() not implemented for class #{self}" unless self.class.respond_to? :release + raise NoMethodError, "release() not implemented for class #{self}" unless self.class.respond_to?(:release, true) raise ArgumentError, "Must supply a pointer to memory for the Struct" unless pointer super AutoPointer.new(pointer, self.class.method(:release)) end diff --git a/lib/ffi/platform.rb b/lib/ffi/platform.rb index bf01a27..5ac4dd7 100644 --- a/lib/ffi/platform.rb +++ b/lib/ffi/platform.rb @@ -29,13 +29,15 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.# require 'rbconfig' +require_relative 'compat' + module FFI class PlatformError < LoadError; end # This module defines different constants and class methods to play with # various platforms. module Platform - OS = case RbConfig::CONFIG['host_os'].downcase + OS = FFI.make_shareable(case RbConfig::CONFIG['host_os'].downcase when /linux/ "linux" when /darwin/ @@ -54,13 +56,13 @@ module FFI "windows" else RbConfig::CONFIG['host_os'].downcase - end + end) OSVERSION = RbConfig::CONFIG['host_os'].gsub(/[^\d]/, '').to_i - CPU = RbConfig::CONFIG['host_cpu'] + CPU = FFI.make_shareable(RbConfig::CONFIG['host_cpu']) - ARCH = case CPU.downcase + ARCH = FFI.make_shareable(case CPU.downcase when /amd64|x86_64|x64/ "x86_64" when /i\d86|x86|i86pc/ @@ -81,7 +83,7 @@ module FFI end else RbConfig::CONFIG['host_cpu'] - end + end) private # @param [String) os @@ -105,21 +107,21 @@ module FFI # Add the version for known ABI breaks name_version = "12" if IS_FREEBSD && OSVERSION >= 12 # 64-bit inodes - NAME = "#{ARCH}-#{OS}#{name_version}" - CONF_DIR = File.join(File.dirname(__FILE__), 'platform', NAME) + NAME = FFI.make_shareable("#{ARCH}-#{OS}#{name_version}") + CONF_DIR = FFI.make_shareable(File.join(File.dirname(__FILE__), 'platform', NAME)) public - LIBPREFIX = case OS + LIBPREFIX = FFI.make_shareable(case OS when /windows|msys/ '' when /cygwin/ 'cyg' else 'lib' - end + end) - LIBSUFFIX = case OS + LIBSUFFIX = FFI.make_shareable(case OS when /darwin/ 'dylib' when /linux|bsd|solaris/ @@ -129,9 +131,9 @@ module FFI else # Punt and just assume a sane unix (i.e. anything but AIX) 'so' - end + end) - LIBC = if IS_WINDOWS + LIBC = FFI.make_shareable(if IS_WINDOWS crtname = RbConfig::CONFIG["RUBY_SO_NAME"][/msvc\w+/] || 'ucrtbase' "#{crtname}.dll" elsif IS_GNU @@ -143,7 +145,7 @@ module FFI "msys-2.0.dll" else "#{LIBPREFIX}c.#{LIBSUFFIX}" - end + end) LITTLE_ENDIAN = 1234 unless defined?(LITTLE_ENDIAN) BIG_ENDIAN = 4321 unless defined?(BIG_ENDIAN) diff --git a/lib/ffi/platform/hppa1.1-linux/types.conf b/lib/ffi/platform/hppa1.1-linux/types.conf new file mode 100644 index 0000000..c8a8141 --- /dev/null +++ b/lib/ffi/platform/hppa1.1-linux/types.conf @@ -0,0 +1,178 @@ +rbx.platform.typedef.*__caddr_t = char +rbx.platform.typedef.__blkcnt64_t = long_long +rbx.platform.typedef.__blkcnt_t = long +rbx.platform.typedef.__blksize_t = long +rbx.platform.typedef.__clock_t = long +rbx.platform.typedef.__clockid_t = int +rbx.platform.typedef.__daddr_t = int +rbx.platform.typedef.__dev_t = ulong_long +rbx.platform.typedef.__fd_mask = long +rbx.platform.typedef.__fsblkcnt64_t = ulong_long +rbx.platform.typedef.__fsblkcnt_t = ulong +rbx.platform.typedef.__fsfilcnt64_t = ulong_long +rbx.platform.typedef.__fsfilcnt_t = ulong +rbx.platform.typedef.__fsword_t = int +rbx.platform.typedef.__gid_t = uint +rbx.platform.typedef.__id_t = uint +rbx.platform.typedef.__ino64_t = ulong_long +rbx.platform.typedef.__ino_t = ulong +rbx.platform.typedef.__int16_t = short +rbx.platform.typedef.__int32_t = int +rbx.platform.typedef.__int64_t = long_long +rbx.platform.typedef.__int8_t = char +rbx.platform.typedef.__int_least16_t = short +rbx.platform.typedef.__int_least32_t = int +rbx.platform.typedef.__int_least64_t = long_long +rbx.platform.typedef.__int_least8_t = char +rbx.platform.typedef.__intmax_t = long_long +rbx.platform.typedef.__intptr_t = int +rbx.platform.typedef.__kernel_caddr_t = string +rbx.platform.typedef.__kernel_clock_t = long +rbx.platform.typedef.__kernel_clockid_t = int +rbx.platform.typedef.__kernel_daddr_t = int +rbx.platform.typedef.__kernel_gid16_t = ushort +rbx.platform.typedef.__kernel_gid32_t = uint +rbx.platform.typedef.__kernel_gid_t = uint +rbx.platform.typedef.__kernel_ino64_t = ulong_long +rbx.platform.typedef.__kernel_ino_t = ulong +rbx.platform.typedef.__kernel_ipc_pid_t = ushort +rbx.platform.typedef.__kernel_key_t = int +rbx.platform.typedef.__kernel_loff_t = long_long +rbx.platform.typedef.__kernel_long_t = long +rbx.platform.typedef.__kernel_mode_t = ushort +rbx.platform.typedef.__kernel_mqd_t = int +rbx.platform.typedef.__kernel_off64_t = long_long +rbx.platform.typedef.__kernel_off_t = long +rbx.platform.typedef.__kernel_old_dev_t = uint +rbx.platform.typedef.__kernel_old_gid_t = uint +rbx.platform.typedef.__kernel_old_time_t = long +rbx.platform.typedef.__kernel_old_uid_t = uint +rbx.platform.typedef.__kernel_pid_t = int +rbx.platform.typedef.__kernel_ptrdiff_t = int +rbx.platform.typedef.__kernel_size_t = uint +rbx.platform.typedef.__kernel_ssize_t = int +rbx.platform.typedef.__kernel_suseconds_t = long +rbx.platform.typedef.__kernel_time64_t = long_long +rbx.platform.typedef.__kernel_time_t = long +rbx.platform.typedef.__kernel_timer_t = int +rbx.platform.typedef.__kernel_uid16_t = ushort +rbx.platform.typedef.__kernel_uid32_t = uint +rbx.platform.typedef.__kernel_uid_t = uint +rbx.platform.typedef.__kernel_ulong_t = ulong +rbx.platform.typedef.__key_t = int +rbx.platform.typedef.__loff_t = long_long +rbx.platform.typedef.__mode_t = uint +rbx.platform.typedef.__nlink_t = uint +rbx.platform.typedef.__off64_t = long_long +rbx.platform.typedef.__off_t = long +rbx.platform.typedef.__pid_t = int +rbx.platform.typedef.__priority_which_t = int +rbx.platform.typedef.__quad_t = long_long +rbx.platform.typedef.__rlim64_t = ulong_long +rbx.platform.typedef.__rlim_t = ulong +rbx.platform.typedef.__rlimit_resource_t = int +rbx.platform.typedef.__rusage_who_t = int +rbx.platform.typedef.__sig_atomic_t = int +rbx.platform.typedef.__socklen_t = uint +rbx.platform.typedef.__ssize_t = int +rbx.platform.typedef.__suseconds64_t = long_long +rbx.platform.typedef.__suseconds_t = long +rbx.platform.typedef.__syscall_slong_t = long +rbx.platform.typedef.__syscall_ulong_t = ulong +rbx.platform.typedef.__thrd_t = ulong +rbx.platform.typedef.__time64_t = long_long +rbx.platform.typedef.__time_t = long +rbx.platform.typedef.__timer_t = pointer +rbx.platform.typedef.__tss_t = uint +rbx.platform.typedef.__u_char = uchar +rbx.platform.typedef.__u_int = uint +rbx.platform.typedef.__u_long = ulong +rbx.platform.typedef.__u_quad_t = ulong_long +rbx.platform.typedef.__u_short = ushort +rbx.platform.typedef.__uid_t = uint +rbx.platform.typedef.__uint16_t = ushort +rbx.platform.typedef.__uint32_t = uint +rbx.platform.typedef.__uint64_t = ulong_long +rbx.platform.typedef.__uint8_t = uchar +rbx.platform.typedef.__uint_least16_t = ushort +rbx.platform.typedef.__uint_least32_t = uint +rbx.platform.typedef.__uint_least64_t = ulong_long +rbx.platform.typedef.__uint_least8_t = uchar +rbx.platform.typedef.__uintmax_t = ulong_long +rbx.platform.typedef.__useconds_t = uint +rbx.platform.typedef.blkcnt_t = long_long +rbx.platform.typedef.blksize_t = long +rbx.platform.typedef.clock_t = long +rbx.platform.typedef.clockid_t = int +rbx.platform.typedef.daddr_t = int +rbx.platform.typedef.dev_t = ulong_long +rbx.platform.typedef.fd_mask = long +rbx.platform.typedef.fsblkcnt_t = ulong_long +rbx.platform.typedef.fsfilcnt_t = ulong_long +rbx.platform.typedef.gid_t = uint +rbx.platform.typedef.id_t = uint +rbx.platform.typedef.in_addr_t = uint +rbx.platform.typedef.in_port_t = ushort +rbx.platform.typedef.ino_t = ulong_long +rbx.platform.typedef.int16_t = short +rbx.platform.typedef.int32_t = int +rbx.platform.typedef.int64_t = long_long +rbx.platform.typedef.int8_t = char +rbx.platform.typedef.int_fast16_t = int +rbx.platform.typedef.int_fast32_t = int +rbx.platform.typedef.int_fast64_t = long_long +rbx.platform.typedef.int_fast8_t = char +rbx.platform.typedef.int_least16_t = short +rbx.platform.typedef.int_least32_t = int +rbx.platform.typedef.int_least64_t = long_long +rbx.platform.typedef.int_least8_t = char +rbx.platform.typedef.intmax_t = long_long +rbx.platform.typedef.intptr_t = int +rbx.platform.typedef.key_t = int +rbx.platform.typedef.loff_t = long_long +rbx.platform.typedef.mode_t = uint +rbx.platform.typedef.nlink_t = uint +rbx.platform.typedef.off_t = long_long +rbx.platform.typedef.pid_t = int +rbx.platform.typedef.pthread_key_t = uint +rbx.platform.typedef.pthread_once_t = int +rbx.platform.typedef.pthread_t = ulong +rbx.platform.typedef.ptrdiff_t = int +rbx.platform.typedef.quad_t = long_long +rbx.platform.typedef.register_t = long +rbx.platform.typedef.rlim_t = ulong_long +rbx.platform.typedef.sa_family_t = ushort +rbx.platform.typedef.size_t = uint +rbx.platform.typedef.socklen_t = uint +rbx.platform.typedef.ssize_t = int +rbx.platform.typedef.suseconds_t = long +rbx.platform.typedef.time_t = long +rbx.platform.typedef.timer_t = pointer +rbx.platform.typedef.u_char = uchar +rbx.platform.typedef.u_int = uint +rbx.platform.typedef.u_int16_t = ushort +rbx.platform.typedef.u_int32_t = uint +rbx.platform.typedef.u_int64_t = ulong_long +rbx.platform.typedef.u_int8_t = uchar +rbx.platform.typedef.u_long = ulong +rbx.platform.typedef.u_quad_t = ulong_long +rbx.platform.typedef.u_short = ushort +rbx.platform.typedef.uid_t = uint +rbx.platform.typedef.uint = uint +rbx.platform.typedef.uint16_t = ushort +rbx.platform.typedef.uint32_t = uint +rbx.platform.typedef.uint64_t = ulong_long +rbx.platform.typedef.uint8_t = uchar +rbx.platform.typedef.uint_fast16_t = uint +rbx.platform.typedef.uint_fast32_t = uint +rbx.platform.typedef.uint_fast64_t = ulong_long +rbx.platform.typedef.uint_fast8_t = uchar +rbx.platform.typedef.uint_least16_t = ushort +rbx.platform.typedef.uint_least32_t = uint +rbx.platform.typedef.uint_least64_t = ulong_long +rbx.platform.typedef.uint_least8_t = uchar +rbx.platform.typedef.uintmax_t = ulong_long +rbx.platform.typedef.uintptr_t = uint +rbx.platform.typedef.ulong = ulong +rbx.platform.typedef.ushort = ushort +rbx.platform.typedef.wchar_t = long diff --git a/lib/ffi/platform/hppa2.0-linux/types.conf b/lib/ffi/platform/hppa2.0-linux/types.conf new file mode 100644 index 0000000..c8a8141 --- /dev/null +++ b/lib/ffi/platform/hppa2.0-linux/types.conf @@ -0,0 +1,178 @@ +rbx.platform.typedef.*__caddr_t = char +rbx.platform.typedef.__blkcnt64_t = long_long +rbx.platform.typedef.__blkcnt_t = long +rbx.platform.typedef.__blksize_t = long +rbx.platform.typedef.__clock_t = long +rbx.platform.typedef.__clockid_t = int +rbx.platform.typedef.__daddr_t = int +rbx.platform.typedef.__dev_t = ulong_long +rbx.platform.typedef.__fd_mask = long +rbx.platform.typedef.__fsblkcnt64_t = ulong_long +rbx.platform.typedef.__fsblkcnt_t = ulong +rbx.platform.typedef.__fsfilcnt64_t = ulong_long +rbx.platform.typedef.__fsfilcnt_t = ulong +rbx.platform.typedef.__fsword_t = int +rbx.platform.typedef.__gid_t = uint +rbx.platform.typedef.__id_t = uint +rbx.platform.typedef.__ino64_t = ulong_long +rbx.platform.typedef.__ino_t = ulong +rbx.platform.typedef.__int16_t = short +rbx.platform.typedef.__int32_t = int +rbx.platform.typedef.__int64_t = long_long +rbx.platform.typedef.__int8_t = char +rbx.platform.typedef.__int_least16_t = short +rbx.platform.typedef.__int_least32_t = int +rbx.platform.typedef.__int_least64_t = long_long +rbx.platform.typedef.__int_least8_t = char +rbx.platform.typedef.__intmax_t = long_long +rbx.platform.typedef.__intptr_t = int +rbx.platform.typedef.__kernel_caddr_t = string +rbx.platform.typedef.__kernel_clock_t = long +rbx.platform.typedef.__kernel_clockid_t = int +rbx.platform.typedef.__kernel_daddr_t = int +rbx.platform.typedef.__kernel_gid16_t = ushort +rbx.platform.typedef.__kernel_gid32_t = uint +rbx.platform.typedef.__kernel_gid_t = uint +rbx.platform.typedef.__kernel_ino64_t = ulong_long +rbx.platform.typedef.__kernel_ino_t = ulong +rbx.platform.typedef.__kernel_ipc_pid_t = ushort +rbx.platform.typedef.__kernel_key_t = int +rbx.platform.typedef.__kernel_loff_t = long_long +rbx.platform.typedef.__kernel_long_t = long +rbx.platform.typedef.__kernel_mode_t = ushort +rbx.platform.typedef.__kernel_mqd_t = int +rbx.platform.typedef.__kernel_off64_t = long_long +rbx.platform.typedef.__kernel_off_t = long +rbx.platform.typedef.__kernel_old_dev_t = uint +rbx.platform.typedef.__kernel_old_gid_t = uint +rbx.platform.typedef.__kernel_old_time_t = long +rbx.platform.typedef.__kernel_old_uid_t = uint +rbx.platform.typedef.__kernel_pid_t = int +rbx.platform.typedef.__kernel_ptrdiff_t = int +rbx.platform.typedef.__kernel_size_t = uint +rbx.platform.typedef.__kernel_ssize_t = int +rbx.platform.typedef.__kernel_suseconds_t = long +rbx.platform.typedef.__kernel_time64_t = long_long +rbx.platform.typedef.__kernel_time_t = long +rbx.platform.typedef.__kernel_timer_t = int +rbx.platform.typedef.__kernel_uid16_t = ushort +rbx.platform.typedef.__kernel_uid32_t = uint +rbx.platform.typedef.__kernel_uid_t = uint +rbx.platform.typedef.__kernel_ulong_t = ulong +rbx.platform.typedef.__key_t = int +rbx.platform.typedef.__loff_t = long_long +rbx.platform.typedef.__mode_t = uint +rbx.platform.typedef.__nlink_t = uint +rbx.platform.typedef.__off64_t = long_long +rbx.platform.typedef.__off_t = long +rbx.platform.typedef.__pid_t = int +rbx.platform.typedef.__priority_which_t = int +rbx.platform.typedef.__quad_t = long_long +rbx.platform.typedef.__rlim64_t = ulong_long +rbx.platform.typedef.__rlim_t = ulong +rbx.platform.typedef.__rlimit_resource_t = int +rbx.platform.typedef.__rusage_who_t = int +rbx.platform.typedef.__sig_atomic_t = int +rbx.platform.typedef.__socklen_t = uint +rbx.platform.typedef.__ssize_t = int +rbx.platform.typedef.__suseconds64_t = long_long +rbx.platform.typedef.__suseconds_t = long +rbx.platform.typedef.__syscall_slong_t = long +rbx.platform.typedef.__syscall_ulong_t = ulong +rbx.platform.typedef.__thrd_t = ulong +rbx.platform.typedef.__time64_t = long_long +rbx.platform.typedef.__time_t = long +rbx.platform.typedef.__timer_t = pointer +rbx.platform.typedef.__tss_t = uint +rbx.platform.typedef.__u_char = uchar +rbx.platform.typedef.__u_int = uint +rbx.platform.typedef.__u_long = ulong +rbx.platform.typedef.__u_quad_t = ulong_long +rbx.platform.typedef.__u_short = ushort +rbx.platform.typedef.__uid_t = uint +rbx.platform.typedef.__uint16_t = ushort +rbx.platform.typedef.__uint32_t = uint +rbx.platform.typedef.__uint64_t = ulong_long +rbx.platform.typedef.__uint8_t = uchar +rbx.platform.typedef.__uint_least16_t = ushort +rbx.platform.typedef.__uint_least32_t = uint +rbx.platform.typedef.__uint_least64_t = ulong_long +rbx.platform.typedef.__uint_least8_t = uchar +rbx.platform.typedef.__uintmax_t = ulong_long +rbx.platform.typedef.__useconds_t = uint +rbx.platform.typedef.blkcnt_t = long_long +rbx.platform.typedef.blksize_t = long +rbx.platform.typedef.clock_t = long +rbx.platform.typedef.clockid_t = int +rbx.platform.typedef.daddr_t = int +rbx.platform.typedef.dev_t = ulong_long +rbx.platform.typedef.fd_mask = long +rbx.platform.typedef.fsblkcnt_t = ulong_long +rbx.platform.typedef.fsfilcnt_t = ulong_long +rbx.platform.typedef.gid_t = uint +rbx.platform.typedef.id_t = uint +rbx.platform.typedef.in_addr_t = uint +rbx.platform.typedef.in_port_t = ushort +rbx.platform.typedef.ino_t = ulong_long +rbx.platform.typedef.int16_t = short +rbx.platform.typedef.int32_t = int +rbx.platform.typedef.int64_t = long_long +rbx.platform.typedef.int8_t = char +rbx.platform.typedef.int_fast16_t = int +rbx.platform.typedef.int_fast32_t = int +rbx.platform.typedef.int_fast64_t = long_long +rbx.platform.typedef.int_fast8_t = char +rbx.platform.typedef.int_least16_t = short +rbx.platform.typedef.int_least32_t = int +rbx.platform.typedef.int_least64_t = long_long +rbx.platform.typedef.int_least8_t = char +rbx.platform.typedef.intmax_t = long_long +rbx.platform.typedef.intptr_t = int +rbx.platform.typedef.key_t = int +rbx.platform.typedef.loff_t = long_long +rbx.platform.typedef.mode_t = uint +rbx.platform.typedef.nlink_t = uint +rbx.platform.typedef.off_t = long_long +rbx.platform.typedef.pid_t = int +rbx.platform.typedef.pthread_key_t = uint +rbx.platform.typedef.pthread_once_t = int +rbx.platform.typedef.pthread_t = ulong +rbx.platform.typedef.ptrdiff_t = int +rbx.platform.typedef.quad_t = long_long +rbx.platform.typedef.register_t = long +rbx.platform.typedef.rlim_t = ulong_long +rbx.platform.typedef.sa_family_t = ushort +rbx.platform.typedef.size_t = uint +rbx.platform.typedef.socklen_t = uint +rbx.platform.typedef.ssize_t = int +rbx.platform.typedef.suseconds_t = long +rbx.platform.typedef.time_t = long +rbx.platform.typedef.timer_t = pointer +rbx.platform.typedef.u_char = uchar +rbx.platform.typedef.u_int = uint +rbx.platform.typedef.u_int16_t = ushort +rbx.platform.typedef.u_int32_t = uint +rbx.platform.typedef.u_int64_t = ulong_long +rbx.platform.typedef.u_int8_t = uchar +rbx.platform.typedef.u_long = ulong +rbx.platform.typedef.u_quad_t = ulong_long +rbx.platform.typedef.u_short = ushort +rbx.platform.typedef.uid_t = uint +rbx.platform.typedef.uint = uint +rbx.platform.typedef.uint16_t = ushort +rbx.platform.typedef.uint32_t = uint +rbx.platform.typedef.uint64_t = ulong_long +rbx.platform.typedef.uint8_t = uchar +rbx.platform.typedef.uint_fast16_t = uint +rbx.platform.typedef.uint_fast32_t = uint +rbx.platform.typedef.uint_fast64_t = ulong_long +rbx.platform.typedef.uint_fast8_t = uchar +rbx.platform.typedef.uint_least16_t = ushort +rbx.platform.typedef.uint_least32_t = uint +rbx.platform.typedef.uint_least64_t = ulong_long +rbx.platform.typedef.uint_least8_t = uchar +rbx.platform.typedef.uintmax_t = ulong_long +rbx.platform.typedef.uintptr_t = uint +rbx.platform.typedef.ulong = ulong +rbx.platform.typedef.ushort = ushort +rbx.platform.typedef.wchar_t = long diff --git a/lib/ffi/platform/sparc64-linux/types.conf b/lib/ffi/platform/sparcv9-linux/types.conf index 7626bfc..7626bfc 100644 --- a/lib/ffi/platform/sparc64-linux/types.conf +++ b/lib/ffi/platform/sparcv9-linux/types.conf diff --git a/lib/ffi/platform/sw_64-linux/types.conf b/lib/ffi/platform/sw_64-linux/types.conf new file mode 100644 index 0000000..aa8716d --- /dev/null +++ b/lib/ffi/platform/sw_64-linux/types.conf @@ -0,0 +1,141 @@ +rbx.platform.typedef.*__caddr_t = char +rbx.platform.typedef.__blkcnt64_t = long +rbx.platform.typedef.__blkcnt_t = long +rbx.platform.typedef.__blksize_t = int +rbx.platform.typedef.__clock_t = long +rbx.platform.typedef.__clockid_t = int +rbx.platform.typedef.__daddr_t = int +rbx.platform.typedef.__dev_t = ulong +rbx.platform.typedef.__fd_mask = long +rbx.platform.typedef.__fsblkcnt64_t = ulong +rbx.platform.typedef.__fsblkcnt_t = ulong +rbx.platform.typedef.__fsfilcnt64_t = ulong +rbx.platform.typedef.__fsfilcnt_t = ulong +rbx.platform.typedef.__fsword_t = long +rbx.platform.typedef.__gid_t = uint +rbx.platform.typedef.__id_t = uint +rbx.platform.typedef.__ino64_t = ulong +rbx.platform.typedef.__ino_t = ulong +rbx.platform.typedef.__int16_t = short +rbx.platform.typedef.__int32_t = int +rbx.platform.typedef.__int64_t = long +rbx.platform.typedef.__int8_t = char +rbx.platform.typedef.__int_least16_t = short +rbx.platform.typedef.__int_least32_t = int +rbx.platform.typedef.__int_least64_t = long +rbx.platform.typedef.__int_least8_t = char +rbx.platform.typedef.__intmax_t = long +rbx.platform.typedef.__intptr_t = long +rbx.platform.typedef.__key_t = int +rbx.platform.typedef.__loff_t = long +rbx.platform.typedef.__mode_t = uint +rbx.platform.typedef.__nlink_t = uint +rbx.platform.typedef.__off64_t = long +rbx.platform.typedef.__off_t = long +rbx.platform.typedef.__pid_t = int +rbx.platform.typedef.__priority_which_t = int +rbx.platform.typedef.__quad_t = long +rbx.platform.typedef.__rlim64_t = ulong +rbx.platform.typedef.__rlim_t = ulong +rbx.platform.typedef.__rlimit_resource_t = int +rbx.platform.typedef.__rusage_who_t = int +rbx.platform.typedef.__sig_atomic_t = int +rbx.platform.typedef.__socklen_t = uint +rbx.platform.typedef.__ssize_t = long +rbx.platform.typedef.__suseconds_t = long +rbx.platform.typedef.__syscall_slong_t = long +rbx.platform.typedef.__syscall_ulong_t = ulong +rbx.platform.typedef.__time_t = long +rbx.platform.typedef.__timer_t = pointer +rbx.platform.typedef.__u_char = uchar +rbx.platform.typedef.__u_int = uint +rbx.platform.typedef.__u_long = ulong +rbx.platform.typedef.__u_quad_t = ulong +rbx.platform.typedef.__u_short = ushort +rbx.platform.typedef.__uid_t = uint +rbx.platform.typedef.__uint16_t = ushort +rbx.platform.typedef.__uint32_t = uint +rbx.platform.typedef.__uint64_t = ulong +rbx.platform.typedef.__uint8_t = uchar +rbx.platform.typedef.__uint_least16_t = ushort +rbx.platform.typedef.__uint_least32_t = uint +rbx.platform.typedef.__uint_least64_t = ulong +rbx.platform.typedef.__uint_least8_t = uchar +rbx.platform.typedef.__uintmax_t = ulong +rbx.platform.typedef.__useconds_t = uint +rbx.platform.typedef.blkcnt_t = long +rbx.platform.typedef.blksize_t = int +rbx.platform.typedef.clock_t = long +rbx.platform.typedef.clockid_t = int +rbx.platform.typedef.daddr_t = int +rbx.platform.typedef.dev_t = ulong +rbx.platform.typedef.fd_mask = long +rbx.platform.typedef.fsblkcnt_t = ulong +rbx.platform.typedef.fsfilcnt_t = ulong +rbx.platform.typedef.gid_t = uint +rbx.platform.typedef.id_t = uint +rbx.platform.typedef.in_addr_t = uint +rbx.platform.typedef.in_port_t = ushort +rbx.platform.typedef.ino_t = ulong +rbx.platform.typedef.int16_t = short +rbx.platform.typedef.int32_t = int +rbx.platform.typedef.int64_t = long +rbx.platform.typedef.int8_t = char +rbx.platform.typedef.int_fast16_t = long +rbx.platform.typedef.int_fast32_t = long +rbx.platform.typedef.int_fast64_t = long +rbx.platform.typedef.int_fast8_t = char +rbx.platform.typedef.int_least16_t = short +rbx.platform.typedef.int_least32_t = int +rbx.platform.typedef.int_least64_t = long +rbx.platform.typedef.int_least8_t = char +rbx.platform.typedef.intmax_t = long +rbx.platform.typedef.intptr_t = long +rbx.platform.typedef.key_t = int +rbx.platform.typedef.loff_t = long +rbx.platform.typedef.mode_t = uint +rbx.platform.typedef.nlink_t = uint +rbx.platform.typedef.off_t = long +rbx.platform.typedef.pid_t = int +rbx.platform.typedef.pthread_key_t = uint +rbx.platform.typedef.pthread_once_t = int +rbx.platform.typedef.pthread_t = ulong +rbx.platform.typedef.ptrdiff_t = long +rbx.platform.typedef.quad_t = long +rbx.platform.typedef.register_t = long +rbx.platform.typedef.rlim_t = ulong +rbx.platform.typedef.sa_family_t = ushort +rbx.platform.typedef.size_t = ulong +rbx.platform.typedef.socklen_t = uint +rbx.platform.typedef.ssize_t = long +rbx.platform.typedef.suseconds_t = long +rbx.platform.typedef.time_t = long +rbx.platform.typedef.timer_t = pointer +rbx.platform.typedef.u_char = uchar +rbx.platform.typedef.u_int = uint +rbx.platform.typedef.u_int16_t = ushort +rbx.platform.typedef.u_int32_t = uint +rbx.platform.typedef.u_int64_t = ulong_long +rbx.platform.typedef.u_int8_t = uchar +rbx.platform.typedef.u_long = ulong +rbx.platform.typedef.u_quad_t = ulong +rbx.platform.typedef.u_short = ushort +rbx.platform.typedef.uid_t = uint +rbx.platform.typedef.uint = uint +rbx.platform.typedef.uint16_t = ushort +rbx.platform.typedef.uint32_t = uint +rbx.platform.typedef.uint64_t = ulong +rbx.platform.typedef.uint8_t = uchar +rbx.platform.typedef.uint_fast16_t = ulong +rbx.platform.typedef.uint_fast32_t = ulong +rbx.platform.typedef.uint_fast64_t = ulong +rbx.platform.typedef.uint_fast8_t = uchar +rbx.platform.typedef.uint_least16_t = ushort +rbx.platform.typedef.uint_least32_t = uint +rbx.platform.typedef.uint_least64_t = ulong +rbx.platform.typedef.uint_least8_t = uchar +rbx.platform.typedef.uintmax_t = ulong +rbx.platform.typedef.uintptr_t = ulong +rbx.platform.typedef.ulong = ulong +rbx.platform.typedef.ushort = ushort +rbx.platform.typedef.wchar_t = int diff --git a/lib/ffi/struct.rb b/lib/ffi/struct.rb index 7028258..725b9cb 100644 --- a/lib/ffi/struct.rb +++ b/lib/ffi/struct.rb @@ -203,9 +203,10 @@ module FFI # :field3, :string # end def layout(*spec) - warn "[DEPRECATION] Struct layout is already defined for class #{self.inspect}. Redefinition as in #{caller[0]} will be disallowed in ffi-2.0." if defined?(@layout) return @layout if spec.size == 0 + warn "[DEPRECATION] Struct layout is already defined for class #{self.inspect}. Redefinition as in #{caller[0]} will be disallowed in ffi-2.0." if defined?(@layout) + builder = StructLayoutBuilder.new builder.union = self < Union builder.packed = @packed if defined?(@packed) diff --git a/lib/ffi/struct_layout.rb b/lib/ffi/struct_layout.rb index d5a78a7..3fd68cb 100644 --- a/lib/ffi/struct_layout.rb +++ b/lib/ffi/struct_layout.rb @@ -80,8 +80,8 @@ module FFI class Mapped < Field def initialize(name, offset, type, orig_field) - super(name, offset, type) @orig_field = orig_field + super(name, offset, type) end def get(ptr) diff --git a/lib/ffi/struct_layout_builder.rb b/lib/ffi/struct_layout_builder.rb index 4d6a464..d7d26a2 100644 --- a/lib/ffi/struct_layout_builder.rb +++ b/lib/ffi/struct_layout_builder.rb @@ -112,7 +112,7 @@ module FFI Type::FLOAT64, Type::LONGDOUBLE, Type::BOOL, - ] + ].freeze # @param [String, Symbol] name name of the field # @param [Array, DataConverter, Struct, StructLayout::Field, Symbol, Type] type type of the field diff --git a/lib/ffi/types.rb b/lib/ffi/types.rb index 90f50c1..8f5d897 100644 --- a/lib/ffi/types.rb +++ b/lib/ffi/types.rb @@ -33,12 +33,24 @@ # see {file:README} module FFI + unless defined?(self.custom_typedefs) + # Truffleruby and JRuby don't support Ractor so far. + # So they don't need separation between builtin and custom types. + def self.custom_typedefs + TypeDefs + end + writable_typemap = true + end + # @param [Type, DataConverter, Symbol] old type definition used by {FFI.find_type} # @param [Symbol] add new type definition's name to add # @return [Type] # Add a definition type to type definitions. + # + # The type definition is local per Ractor. def self.typedef(old, add) - TypeDefs[add] = self.find_type(old) + tm = custom_typedefs + tm[add] = self.find_type(old) end # (see FFI.typedef) @@ -46,6 +58,14 @@ module FFI typedef old, add end + class << self + private def __typedef(old, add) + TypeDefs[add] = self.find_type(old) + end + + private :custom_typedefs + end + # @param [Type, DataConverter, Symbol] name # @param [Hash] type_map if nil, {FFI::TypeDefs} is used @@ -57,9 +77,12 @@ module FFI if name.is_a?(Type) name - elsif type_map && type_map.has_key?(name) + elsif type_map&.has_key?(name) type_map[name] + elsif (tm=custom_typedefs).has_key?(name) + tm[name] + elsif TypeDefs.has_key?(name) TypeDefs[name] @@ -168,7 +191,7 @@ module FFI end end - typedef(StrPtrConverter, :strptr) + __typedef(StrPtrConverter, :strptr) # @param type +type+ is an instance of class accepted by {FFI.find_type} # @return [Numeric] @@ -184,11 +207,13 @@ module FFI f.each_line { |line| if line.index(prefix) == 0 new_type, orig_type = line.chomp.slice(prefix.length..-1).split(/\s*=\s*/) - typedef(orig_type.to_sym, new_type.to_sym) + __typedef(orig_type.to_sym, new_type.to_sym) end } end - typedef :pointer, :caddr_t + __typedef :pointer, :caddr_t rescue Errno::ENOENT end + + FFI.make_shareable(TypeDefs) unless writable_typemap end diff --git a/lib/ffi/variadic.rb b/lib/ffi/variadic.rb index 743ce7f..ee33409 100644 --- a/lib/ffi/variadic.rb +++ b/lib/ffi/variadic.rb @@ -54,16 +54,27 @@ module FFI invoker = self params = "*args" call = "call" - mod.module_eval <<-code - @@#{mname} = invoker - def self.#{mname}(#{params}) - @@#{mname}.#{call}(#{params}) - end - def #{mname}(#{params}) - @@#{mname}.#{call}(#{params}) - end + mname = mname.to_sym + mod.module_eval <<-code, __FILE__, __LINE__ + @ffi_functions = {} unless defined?(@ffi_functions) + @ffi_functions[#{mname.inspect}] = invoker + + def self.#{mname}(#{params}) + @ffi_functions[#{mname.inspect}].#{call}(#{params}) + end + + define_method(#{mname.inspect}, &method(#{mname.inspect})) code invoker end + + # Retrieve Array of parameter types + # + # This method returns an Array of FFI types accepted as function parameters. + # + # @return [Array<FFI::Type>] + def param_types + [*@fixed, Type::Builtin::VARARGS] + end end end diff --git a/spec/ffi/async_callback_spec.rb b/spec/ffi/async_callback_spec.rb index 51a4886..7bb9f70 100644 --- a/spec/ffi/async_callback_spec.rb +++ b/spec/ffi/async_callback_spec.rb @@ -50,4 +50,27 @@ describe "async callback" do expect(callback_runner_thread.name).to eq("FFI Callback Runner") end + + it "works in Ractor", :ractor do + skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby" + + res = Ractor.new do + v = 0xdeadbeef + correct_ractor = false + correct_thread = false + thread = Thread.current + rac = Ractor.current + cb = Proc.new do |i| + v = i + correct_ractor = rac == Ractor.current + correct_thread = thread != Thread.current + end + LibTest.testAsyncCallback(cb, 0x7fffffff) + + [v, correct_ractor, correct_thread] + end.take + + expect(res).to eq([0x7fffffff, true, true]) + end + end diff --git a/spec/ffi/buffer_spec.rb b/spec/ffi/buffer_spec.rb index 619cb6b..e031f7c 100644 --- a/spec/ffi/buffer_spec.rb +++ b/spec/ffi/buffer_spec.rb @@ -285,3 +285,13 @@ describe "Buffer#initialize" do expect(block_executed).to be true end end + +describe "Buffer#memsize_of" do + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + buf = FFI::Buffer.new 14 + size = ObjectSpace.memsize_of(buf) + expect(size).to be > base_size + end +end diff --git a/spec/ffi/dynamic_library_spec.rb b/spec/ffi/dynamic_library_spec.rb new file mode 100644 index 0000000..088a822 --- /dev/null +++ b/spec/ffi/dynamic_library_spec.rb @@ -0,0 +1,60 @@ +# +# 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::DynamicLibrary do + it "should be shareable for Ractor", :ractor do + libtest = FFI::DynamicLibrary.open(TestLibrary::PATH, + FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL) + + res = Ractor.new(libtest) do |libtest2| + libtest2.find_symbol("testClosureVrV").address + end.take + + expect( res ).to be > 0 + end + + it "load a library in a Ractor", :ractor do + res = Ractor.new do + libtest = FFI::DynamicLibrary.open(TestLibrary::PATH, + FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL) + libtest.find_symbol("testClosureVrV") + end.take + + expect(res.address).to be > 0 + end + + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + libtest = FFI::DynamicLibrary.open(TestLibrary::PATH, + FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL) + size = ObjectSpace.memsize_of(libtest) + expect(size).to be > base_size + end + + describe Symbol do + before do + @libtest = FFI::DynamicLibrary.open( + TestLibrary::PATH, + FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL, + ) + end + + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + symbol = @libtest.find_symbol("gvar_gstruct_set") + size = ObjectSpace.memsize_of(symbol) + expect(size).to be > base_size + end + + it "should be shareable for Ractor", :ractor do + symbol = @libtest.find_symbol("gvar_gstruct_set") + expect(Ractor.shareable?(symbol)).to be true + end + end +end diff --git a/spec/ffi/enum_spec.rb b/spec/ffi/enum_spec.rb index b8c5b57..7a55b07 100644 --- a/spec/ffi/enum_spec.rb +++ b/spec/ffi/enum_spec.rb @@ -26,14 +26,18 @@ module TestEnum3 ffi_lib TestLibrary::PATH enum :enum_type1, [:c1, :c2, :c3, :c4] - enum :enum_type2, [:c5, 42, :c6, :c7, :c8] - enum :enum_type3, [:c9, 42, :c10, :c11, 4242, :c12] - enum :enum_type4, [:c13, 42, :c14, 4242, :c15, 424242, :c16, 42424242] - attach_function :test_tagged_typedef_enum1, [:enum_type1], :enum_type1 + + enum :enum_type2, [:c5, 42, :c6, :c7, :c8] attach_function :test_tagged_typedef_enum2, [:enum_type2], :enum_type2 + + enum :enum_type3, [:c9, 42, :c10, :c11, 4242, :c12] attach_function :test_tagged_typedef_enum3, [:enum_type3], :enum_type3 + + enum :enum_type4, [:c13, 42, :c14, 4242, :c15, 424242, :c16, 42424242] attach_function :test_tagged_typedef_enum4, [:enum_type4], :enum_type4 + + freeze end module TestEnum4 @@ -44,18 +48,22 @@ module TestEnum4 enum :enum_type1, [:c5, 0x42, :c6, :c7, :c8] enum :enum_type2, [:c9, 0x42, :c10, :c11, 0x4242, :c12] enum :enum_type3, [:c13, 0x42, :c14, 0x4242, :c15, 0x42424242, :c16, 0x4242424242424242] - enum FFI::Type::UINT16, :enum_type4, [:c17, 0x42, :c18, :c19, :c20] - enum FFI::Type::UINT32, :enum_type5, [:c21, 0x42, :c22, :c23, 0x4242, :c24] - enum FFI::Type::UINT64, :enum_type6, [:c25, 0x42, :c26, 0x4242, :c27, 0x42424242, :c28, 0x4242424242424242] - enum FFI::Type::UINT64, [:c29, 0x4242424242424242, :c30, :c31, :c32] attach_function :test_untagged_nonint_enum, [:uint8], :uint8 attach_function :test_tagged_nonint_enum1, [:uint16], :uint16 attach_function :test_tagged_nonint_enum2, [:uint32], :uint32 attach_function :test_tagged_nonint_enum3, [:uint64], :uint64 + + enum FFI::Type::UINT16, :enum_type4, [:c17, 0x42, :c18, :c19, :c20] + enum FFI::Type::UINT32, :enum_type5, [:c21, 0x42, :c22, :c23, 0x4242, :c24] + enum FFI::Type::UINT64, :enum_type6, [:c25, 0x42, :c26, 0x4242, :c27, 0x42424242, :c28, 0x4242424242424242] + enum FFI::Type::UINT64, [:c29, 0x4242424242424242, :c30, :c31, :c32] + attach_function :test_tagged_nonint_enum4, :test_tagged_nonint_enum1, [:enum_type4], :enum_type4 attach_function :test_tagged_nonint_enum5, :test_tagged_nonint_enum2, [:enum_type5], :enum_type5 attach_function :test_tagged_nonint_enum6, :test_tagged_nonint_enum3, [:enum_type6], :enum_type6 + + freeze end describe "A library with no enum defined" do @@ -430,4 +438,18 @@ describe "All enums" do end end.to raise_error(ArgumentError, /duplicate/) end + + it "should be usable in Ractor", :ractor do + res = Ractor.new do + [ + TestEnum1.test_untagged_enum(:c1), + TestEnum3.test_tagged_typedef_enum1(:c1), + TestEnum4.test_tagged_nonint_enum4(0x45), + TestEnum3.enum_type(:enum_type1)[0], + TestEnum4.enum_type(:enum_type6)[0x4242424242424242], + TestEnum4.enum_value(:c3) + ] + end.take + expect( res ).to eq( [0, :c1, :c20, :c1, :c28, 2] ) + end end diff --git a/spec/ffi/errno_spec.rb b/spec/ffi/errno_spec.rb index 4170f92..c0207b8 100644 --- a/spec/ffi/errno_spec.rb +++ b/spec/ffi/errno_spec.rb @@ -34,4 +34,12 @@ describe "FFI.errno" do expect(FFI.errno).to eq(0x12345678) end end + + it "works in Ractor", :ractor do + res = Ractor.new do + LibTest.setLastError(0x12345678) + FFI.errno + end.take + expect(res).to eq(0x12345678) + end end diff --git a/spec/ffi/fixtures/ClosureTest.c b/spec/ffi/fixtures/ClosureTest.c index 16f72c4..47273e8 100644 --- a/spec/ffi/fixtures/ClosureTest.c +++ b/spec/ffi/fixtures/ClosureTest.c @@ -16,10 +16,10 @@ double testClosureVrDva(double d, ...) { va_list args; - double (*closure)(void); + typedef double (*closure_fun)(void); va_start(args, d); - closure = va_arg(args, double (*)(void)); + closure_fun closure = va_arg(args, closure_fun); va_end(args); return d + closure(); @@ -27,12 +27,12 @@ double testClosureVrDva(double d, ...) { long testClosureVrILva(int i, long l, ...) { va_list args; - int (*cl1)(int); - long (*cl2)(long); + typedef int (*cl1_fun)(int); + typedef long (*cl2_fun)(long); va_start(args, l); - cl1 = va_arg(args, int (*)(int)); - cl2 = va_arg(args, long (*)(long)); + cl1_fun cl1 = va_arg(args, cl1_fun); + cl2_fun cl2 = va_arg(args, cl2_fun); va_end(args); return cl1(i) + cl2(l); diff --git a/spec/ffi/fixtures/PipeHelperWindows.c b/spec/ffi/fixtures/PipeHelperWindows.c index 0bdbd2e..31b4a2a 100644 --- a/spec/ffi/fixtures/PipeHelperWindows.c +++ b/spec/ffi/fixtures/PipeHelperWindows.c @@ -16,7 +16,7 @@ int pipeHelperCreatePipe(FD_TYPE pipefd[2]) sprintf( name, "\\\\.\\Pipe\\pipeHelper-%u-%i", (unsigned int)GetCurrentProcessId(), pipe_idx++ ); - pipefd[0] = CreateNamedPipe( name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + pipefd[0] = CreateNamedPipeA( name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, // Number of pipes 5, // Out buffer size @@ -26,7 +26,7 @@ int pipeHelperCreatePipe(FD_TYPE pipefd[2]) if(pipefd[0] == INVALID_HANDLE_VALUE) return -1; - pipefd[1] = CreateFile( name, GENERIC_WRITE, 0, NULL, + pipefd[1] = CreateFileA( name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); diff --git a/spec/ffi/fixtures/PointerTest.c b/spec/ffi/fixtures/PointerTest.c index a7f392a..02dbc27 100644 --- a/spec/ffi/fixtures/PointerTest.c +++ b/spec/ffi/fixtures/PointerTest.c @@ -5,7 +5,9 @@ */ #include <sys/types.h> +#ifndef _MSC_VER #include <sys/param.h> +#endif #include <stdint.h> #include <stdio.h> #include <stdlib.h> diff --git a/spec/ffi/fixtures/compile.rb b/spec/ffi/fixtures/compile.rb index f2e831a..2be97cb 100644 --- a/spec/ffi/fixtures/compile.rb +++ b/spec/ffi/fixtures/compile.rb @@ -22,6 +22,8 @@ module TestLibrary "powerpc64" when /ppc|powerpc/ "powerpc" + when /sparcv9|sparc64/ + "sparcv9" when /^arm/ if RbConfig::CONFIG['host_os'] =~ /darwin/ "aarch64" @@ -67,5 +69,5 @@ module TestLibrary lib end - PATH = compile_library(".", "libtest.#{FFI::Platform::LIBSUFFIX}") + PATH = FFI.make_shareable(compile_library(".", "libtest.#{FFI::Platform::LIBSUFFIX}")) end diff --git a/spec/ffi/function_spec.rb b/spec/ffi/function_spec.rb index 5bc2421..77d09d8 100644 --- a/spec/ffi/function_spec.rb +++ b/spec/ffi/function_spec.rb @@ -48,6 +48,30 @@ describe FFI::Function do expect(LibTest.testFunctionAdd(10, 10, function_add)).to eq(20) end + def adder(a, b) + a + b + end + + it "can be made shareable for Ractor", :ractor do + add = FFI::Function.new(:int, [:int, :int], &method(:adder)) + Ractor.make_shareable(add) + + res = Ractor.new(add) do |add2| + LibTest.testFunctionAdd(10, 10, add2) + end.take + + expect( res ).to eq(20) + end + + it "should be usable with Ractor", :ractor do + res = Ractor.new do + function_add = FFI::Function.new(:int, [:int, :int]) { |a, b| a + b } + LibTest.testFunctionAdd(10, 10, function_add) + end.take + + expect( res ).to eq(20) + end + it 'can be used to wrap an existing function pointer' do expect(FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd')).call(10, 10)).to eq(20) end @@ -59,6 +83,16 @@ describe FFI::Function do expect(Foo.add(10, 10)).to eq(20) end + it 'can be attached to two modules' do + module Foo1; end + module Foo2; end + fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd')) + fp.attach(Foo1, 'add') + fp.attach(Foo2, 'add') + expect(Foo1.add(11, 11)).to eq(22) + expect(Foo2.add(12, 12)).to eq(24) + end + it 'can be used to extend an object' do fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd')) foo = Object.new @@ -103,4 +137,12 @@ describe FFI::Function do fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd')) expect { fp.free }.to raise_error RuntimeError end + + it 'has a memsize function', skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + function = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd')) + size = ObjectSpace.memsize_of(function) + expect(size).to be > base_size + end end diff --git a/spec/ffi/function_type_spec.rb b/spec/ffi/function_type_spec.rb new file mode 100644 index 0000000..2f171d8 --- /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.return_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/gc_compact_spec.rb b/spec/ffi/gc_compact_spec.rb new file mode 100644 index 0000000..974d7f8 --- /dev/null +++ b/spec/ffi/gc_compact_spec.rb @@ -0,0 +1,66 @@ +# -*- rspec -*- +# encoding: utf-8 +# +# Tests to verify correct implementation of compaction callbacks in rb_data_type_t definitions. +# +# Compaction callbacks update moved VALUEs. +# In ruby-2.7 they are invoked only while GC.compact or GC.verify_compaction_references. +# Ruby constants are usually moved, but local variables are not. +# +# Effectiveness of the tests below should be verified by commenting the compact callback out like so: +# +# const rb_data_type_t rbffi_struct_layout_data_type = { +# .wrap_struct_name = "FFI::StructLayout", +# .function = { +# .dmark = struct_layout_mark, +# .dfree = struct_layout_free, +# .dsize = struct_layout_memsize, +# # ffi_compact_callback( struct_layout_compact ) +# }, +# .parent = &rbffi_type_data_type, +# .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED +# }; +# +# This should result in a segmentation fault aborting the whole process. +# Therefore the effectiveness of only one test can be verified per rspec run. + +require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) + +describe "GC.compact", if: GC.respond_to?(:compact) do + before :all do + + class St1 < FFI::Struct + layout :i, :int + end + ST1 = St1.new + + class St2 < FFI::Struct + layout :i, :int + end + ST2 = St2.new + ST2[:i] = 6789 + + begin + # Use GC.verify_compaction_references instead of GC.compact . + # This has the advantage that all movable objects are actually moved. + # The downside is that it doubles the heap space of the Ruby process. + # Therefore we call it only once and do several tests afterwards. + GC.verify_compaction_references(toward: :empty, double_heap: true) + rescue NotImplementedError, NoMethodError => err + skip("GC.compact skipped: #{err}") + end + end + + it "should compact FFI::StructLayout without field cache" do + expect( ST1[:i] ).to eq( 0 ) + end + + it "should compact FFI::StructLayout with field cache" do + expect( ST2[:i] ).to eq( 6789 ) + end + + it "should compact FFI::StructLayout::Field" do + l = St1.layout + expect( l.fields.first.type ).to eq( FFI::Type::Builtin::INT32 ) + end +end diff --git a/spec/ffi/library_spec.rb b/spec/ffi/library_spec.rb index ca28974..8691b17 100644 --- a/spec/ffi/library_spec.rb +++ b/spec/ffi/library_spec.rb @@ -5,6 +5,12 @@ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) +module TestEnumValueRactor + extend FFI::Library + enum :something, [:one, :two] + freeze +end + describe "Library" do describe ".enum_value" do m = Module.new do @@ -20,6 +26,14 @@ describe "Library" do it "should return nil for an invalid key" do expect(m.enum_value(:three)).to be nil end + + it "should be queryable in Ractor", :ractor do + res = Ractor.new do + TestEnumValueRactor.enum_value(:one) + end.take + + expect( res ).to eq(0) + end end describe "#ffi_convention" do @@ -88,7 +102,7 @@ describe "Library" do }.to raise_error(LoadError) end - it "interprets INPUT() in loader scripts", unless: FFI::Platform.windows? do + it "interprets INPUT() in linker scripts", unless: FFI::Platform.windows? || FFI::Platform.mac? do path = File.dirname(TestLibrary::PATH) file = File.basename(TestLibrary::PATH) script = File.join(path, "ldscript.so") @@ -184,15 +198,49 @@ describe "Library" do end.getpid).to eq(Process.pid) }.to raise_error(LoadError) end + end - it "attach_function :bool_return_true from [ File.expand_path(#{TestLibrary::PATH.inspect}) ]" do - mod = Module.new do |m| - m.extend FFI::Library - ffi_lib File.expand_path(TestLibrary::PATH) - attach_function :bool_return_true, [ ], :bool - end - expect(mod.bool_return_true).to be true + it "attach_function :bool_return_true from [ File.expand_path(#{TestLibrary::PATH.inspect}) ]" do + mod = Module.new do |m| + m.extend FFI::Library + ffi_lib File.expand_path(TestLibrary::PATH) + attach_function :bool_return_true, [ ], :bool end + expect(mod.bool_return_true).to be true + end + + it "can define a foo! function" do + mod = Module.new do |m| + m.extend FFI::Library + ffi_lib File.expand_path(TestLibrary::PATH) + attach_function :foo!, :bool_return_true, [], :bool + end + expect(mod.foo!).to be true + end + + it "can define a foo? function" do + mod = Module.new do |m| + m.extend FFI::Library + ffi_lib File.expand_path(TestLibrary::PATH) + attach_function :foo?, :bool_return_true, [], :bool + end + expect(mod.foo?).to be true + end + + it "can reveal the function type" do + skip 'this is not yet implemented on JRuby' if RUBY_ENGINE == 'jruby' + skip 'this is not yet implemented on Truffleruby' if RUBY_ENGINE == 'truffleruby' + + mod = Module.new do |m| + m.extend FFI::Library + ffi_lib File.expand_path(TestLibrary::PATH) + attach_function :bool_return_true, [ :string ], :bool + end + + fun = mod.attached_functions + expect(fun.keys).to eq([:bool_return_true]) + expect(fun[:bool_return_true].param_types).to eq([FFI::Type::STRING]) + expect(fun[:bool_return_true].return_type).to eq(FFI::Type::BOOL) end def gvar_lib(name, type) @@ -322,4 +370,35 @@ describe "Library" do expect(val[:data]).to eq(i) end end + + it "can reveal its attached global struct based variables" do + lib = Module.new do |m| + m.extend FFI::Library + ffi_lib TestLibrary::PATH + attach_variable :gvari, "gvar_gstruct", GlobalStruct + end + expect(lib.attached_variables).to eq({ gvari: GlobalStruct }) + end + + it "can reveal its attached global variables" do + lib = Module.new do |m| + m.extend FFI::Library + ffi_lib TestLibrary::PATH + attach_variable "gvaro", "gvar_u32", :uint32 + end + expect(lib.attached_variables).to eq({ gvaro: FFI::Type::UINT32 }) + end + + it "should have shareable constants for Ractor", :ractor do + res = Ractor.new do + [ + FFI::Library::LIBC, + FFI::Library::CURRENT_PROCESS, + FFI::CURRENT_PROCESS, + FFI::USE_THIS_PROCESS_AS_LIBRARY, + ] + end.take + + expect( res.size ).to be > 0 + end end diff --git a/spec/ffi/managed_struct_spec.rb b/spec/ffi/managed_struct_spec.rb index b4e80bb..6299bdc 100644 --- a/spec/ffi/managed_struct_spec.rb +++ b/spec/ffi/managed_struct_spec.rb @@ -46,6 +46,8 @@ describe "Managed Struct" do def self.release(_ptr) @@count += 1 end + private_class_method :release + def self.wait_gc(count) loop = 5 while loop > 0 && @@count < count diff --git a/spec/ffi/memorypointer_spec.rb b/spec/ffi/memorypointer_spec.rb index c8d1b42..f29e2cb 100644 --- a/spec/ffi/memorypointer_spec.rb +++ b/spec/ffi/memorypointer_spec.rb @@ -28,6 +28,21 @@ describe "MemoryPointer#total" do expect(MemoryPointer.new(1024).total).to eq 1024 end end +describe "MemoryPointer#clear" do + it "should clear the memory" do + ptr = MemoryPointer.new(:long) + ptr.write_long 1234 + expect(ptr.read_long).to eq(1234) + ptr.clear + expect(ptr.read_long).to eq(0) + end + it "should deny changes when frozen" do + skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby" + skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby" + ptr = MemoryPointer.new(:long).freeze + expect{ ptr.clear }.to raise_error(RuntimeError, /memory write/) + end +end describe "MemoryPointer#read_array_of_long" do it "foo" do ptr = MemoryPointer.new(:long, 1024) @@ -76,3 +91,22 @@ describe "MemoryPointer return value" do expect(Stdio.fclose(fp)).to eq 0 unless fp.nil? or fp.null? end end +describe "#autorelease" do + it "should be true by default" do + expect(MemoryPointer.new(8).autorelease?).to be true + end + + it "should return false when autorelease=(false)" do + ptr = MemoryPointer.new(8) + ptr.autorelease = false + expect(ptr.autorelease?).to be false + ptr.free + end + + it "should deny changes when frozen" do + skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby" + skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby" + ptr = MemoryPointer.new(8).freeze + expect{ ptr.autorelease = false }.to raise_error(FrozenError) + end +end diff --git a/spec/ffi/platform_spec.rb b/spec/ffi/platform_spec.rb index ad23621..8890b07 100644 --- a/spec/ffi/platform_spec.rb +++ b/spec/ffi/platform_spec.rb @@ -134,4 +134,17 @@ describe "FFI::Platform.unix?" do expect(FFI::Platform::BYTE_ORDER).to eq(order) end end + + it "should have shareable constants for Ractor", :ractor do + res = Ractor.new do + [ + FFI::Platform::OS, + FFI::Platform::CPU, + FFI::Platform::ARCH, + FFI::Platform::OS, + ] + end.take + + expect( res.size ).to be > 0 + end end diff --git a/spec/ffi/pointer_spec.rb b/spec/ffi/pointer_spec.rb index b216a16..8716d30 100644 --- a/spec/ffi/pointer_spec.rb +++ b/spec/ffi/pointer_spec.rb @@ -90,6 +90,13 @@ describe "Pointer" do expect(PointerTestLib.ptr_ret_pointer(memory, 0).address).to eq(0xdeadbeef) end + it "#write_pointer frozen object" do + skip "not yet supported on TruffleRuby" if RUBY_ENGINE == "truffleruby" + skip "not yet supported on JRuby" if RUBY_ENGINE == "jruby" + memory = FFI::MemoryPointer.new(:pointer).freeze + expect{ memory.write_pointer(PointerTestLib.ptr_from_address(0xdeadbeef)) }.to raise_error(RuntimeError, /memory write/) + end + it "#read_array_of_pointer" do values = [0x12345678, 0xfeedf00d, 0xdeadbeef] memory = FFI::MemoryPointer.new :pointer, values.size @@ -237,6 +244,14 @@ describe "Pointer" do expect(FFI::Pointer.new(0).slice(0, 10).size_limit?).to be true end end + + describe "#initialise" do + it 'can use adresses with high bit set' do + max_address = 2**FFI::Platform::ADDRESS_SIZE - 1 + pointer = FFI::Pointer.new(:uint8, max_address) + expect(pointer.address).to eq(max_address) + end + end if (RUBY_ENGINE != "truffleruby" && RUBY_ENGINE != "jruby") end describe "AutoPointer" do @@ -249,6 +264,7 @@ describe "AutoPointer" do def self.release @@count += 1 if @@count > 0 end + private_class_method(:release) def self.reset @@count = 0 end @@ -267,10 +283,11 @@ describe "AutoPointer" do end class AutoPointerSubclass < FFI::AutoPointer def self.release(ptr); end + private_class_method(:release) end # see #427 - it "cleanup via default release method", :broken => true do + it "cleanup via default release method", gc_dependent: true do expect(AutoPointerSubclass).to receive(:release).at_least(loop_count-wiggle_room).times AutoPointerTestHelper.reset loop_count.times do @@ -283,7 +300,7 @@ describe "AutoPointer" do end # see #427 - it "cleanup when passed a proc", :broken => true do + it "cleanup when passed a proc", gc_dependent: true do # NOTE: passing a proc is touchy, because it's so easy to create a memory leak. # # specifically, if we made an inline call to @@ -302,7 +319,7 @@ describe "AutoPointer" do end # see #427 - it "cleanup when passed a method", :broken => true do + it "cleanup when passed a method", gc_dependent: true do expect(AutoPointerTestHelper).to receive(:release).at_least(loop_count-wiggle_room).times AutoPointerTestHelper.reset loop_count.times do @@ -319,6 +336,7 @@ describe "AutoPointer" do ffi_lib TestLibrary::PATH class CustomAutoPointer < FFI::AutoPointer def self.release(ptr); end + private_class_method(:release) end attach_function :ptr_from_address, [ FFI::Platform::ADDRESS_SIZE == 32 ? :uint : :ulong_long ], CustomAutoPointer end @@ -341,6 +359,7 @@ describe "AutoPointer" do describe "#autorelease?" do ptr_class = Class.new(FFI::AutoPointer) do def self.release(ptr); end + private_class_method(:release) end it "should be true by default" do @@ -352,11 +371,17 @@ describe "AutoPointer" do ptr.autorelease = false expect(ptr.autorelease?).to be false end + + it "should deny changes when frozen" do + ptr = ptr_class.new(FFI::Pointer.new(0xdeadbeef)).freeze + expect{ ptr.autorelease = false }.to raise_error(FrozenError) + end end describe "#type_size" do ptr_class = Class.new(FFI::AutoPointer) do def self.release(ptr); end + private_class_method(:release) end it "type_size of AutoPointer should match wrapped Pointer" do @@ -373,5 +398,13 @@ describe "AutoPointer" do expect(mptr[1].read_uint).to eq(0xcafebabe) end end + + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + pointer = FFI::Pointer.new(:int, 0xdeadbeef) + size = ObjectSpace.memsize_of(pointer) + expect(size).to be > base_size + end end diff --git a/spec/ffi/rbx/memory_pointer_spec.rb b/spec/ffi/rbx/memory_pointer_spec.rb index 3878973..1270c9b 100644 --- a/spec/ffi/rbx/memory_pointer_spec.rb +++ b/spec/ffi/rbx/memory_pointer_spec.rb @@ -104,6 +104,18 @@ describe "MemoryPointer" do expect(m.read FFI::Type::BOOL).to eq(false) end + it "allows definition of a custom typedef" do + FFI.typedef :uint32, :fubar_t + expect(FFI.find_type(:fubar_t)).to eq(FFI::Type::Builtin::UINT32) + end + + it "allows overwriting of a default typedef" do + FFI.typedef :uint32, :char + expect(FFI.find_type(:char)).to eq(FFI::Type::Builtin::UINT32) + ensure + FFI.typedef FFI::Type::Builtin::CHAR, :char + end + it "allows writing a custom typedef" do FFI.typedef :uint, :fubar_t FFI.typedef :size_t, :fubar2_t @@ -189,4 +201,12 @@ describe "MemoryPointer" do end expect(block_executed).to be true end + + it "has a memsize function", skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + pointer = FFI::MemoryPointer.from_string("FFI is Awesome") + size = ObjectSpace.memsize_of(pointer) + expect(size).to be > base_size + end end diff --git a/spec/ffi/spec_helper.rb b/spec/ffi/spec_helper.rb index bb12050..22d1c47 100644 --- a/spec/ffi/spec_helper.rb +++ b/spec/ffi/spec_helper.rb @@ -5,9 +5,11 @@ require_relative 'fixtures/compile' require 'timeout' +require 'objspace' RSpec.configure do |c| - c.filter_run_excluding :broken => true + c.filter_run_excluding gc_dependent: true unless ENV['FFI_TEST_GC'] == 'true' + c.filter_run_excluding( :ractor ) unless defined?(Ractor) && RUBY_VERSION >= "3.1" end module TestLibrary diff --git a/spec/ffi/struct_by_ref_spec.rb b/spec/ffi/struct_by_ref_spec.rb index 0858423..a775f92 100644 --- a/spec/ffi/struct_by_ref_spec.rb +++ b/spec/ffi/struct_by_ref_spec.rb @@ -39,5 +39,14 @@ describe FFI::Struct, ' by_ref' do expect { @api.struct_test(other_class.new) }.to raise_error(TypeError) end + + it "can reveal the mapped type converter" do + skip 'this is not yet implemented on JRuby' if RUBY_ENGINE == 'jruby' + skip 'this is not yet implemented on Truffleruby' if RUBY_ENGINE == 'truffleruby' + + param_type = @api.attached_functions[:struct_test].param_types[0] + expect(param_type).to be_a(FFI::Type::Mapped) + expect(param_type.converter).to be_a(FFI::StructByReference) + end end diff --git a/spec/ffi/struct_spec.rb b/spec/ffi/struct_spec.rb index fb574d2..ab62da1 100644 --- a/spec/ffi/struct_spec.rb +++ b/spec/ffi/struct_spec.rb @@ -410,6 +410,7 @@ module StructSpecsStructTests s.pointer.put_double(0, 1.0) expect(s.pointer.get_double(0)).to eq(1.0) end + module EnumFields extend FFI::Library TestEnum = enum :test_enum, [:c1, 10, :c2, 20, :c3, 30, :c4, 40] @@ -456,13 +457,24 @@ module StructSpecsStructTests end it "Can have CallbackInfo struct field" do + s = CallbackMember::TestStruct.new + add_proc = lambda { |a, b| a + b } + sub_proc = lambda { |a, b| a - b } + s[:add] = add_proc + s[:sub] = sub_proc + expect(CallbackMember.struct_call_add_cb(s, 40, 2)).to eq(42) + expect(CallbackMember.struct_call_sub_cb(s, 44, 2)).to eq(42) + end + + it "Can use CallbackInfo struct field in Ractor", :ractor do + res = Ractor.new do s = CallbackMember::TestStruct.new - add_proc = lambda { |a, b| a+b } - sub_proc = lambda { |a, b| a-b } + add_proc = lambda { |a, b| a + b } s[:add] = add_proc - s[:sub] = sub_proc - expect(CallbackMember.struct_call_add_cb(s, 40, 2)).to eq(42) - expect(CallbackMember.struct_call_sub_cb(s, 44, 2)).to eq(42) + CallbackMember.struct_call_add_cb(s, 40, 2) + end.take + + expect( res ).to eq(42) end it "Can return its members as a list" do @@ -532,6 +544,34 @@ module StructSpecsStructTests expect(a.members).to eq([:a]) expect(b.members).to eq([:a, :b]) end + + it "can be made shareable for Ractor", :ractor do + a = Class.new(FFI::Struct) do + layout :a, :char + end.new + a[:a] = -34 + Ractor.make_shareable(a) + + res = Ractor.new(a) do |a2| + a2[:a] + end.take + + expect( res ).to eq(-34) + end + + it "should be usable with Ractor", :ractor do + class TestStructRactor < FFI::Struct + layout :i, :int + end + + res = Ractor.new do + s = TestStructRactor.new + s[:i] = 0x14 + LibTest.ptr_ret_int32_t(s, 0) + end.take + + expect( res ).to eq(0x14) + end end end @@ -1027,6 +1067,65 @@ describe "variable-length arrays" do end end +describe "Struct memsize functions", skip: RUBY_ENGINE != "ruby" do + it "has a memsize function" do + base_size = ObjectSpace.memsize_of(Object.new) + + c = Class.new(FFI::Struct) do + layout :b, :bool + end + struct = c.new + size = ObjectSpace.memsize_of(struct) + expect(size).to be > base_size + end + + class SmallCustomStruct < FFI::Struct + layout :pointer, :pointer + end + + class LargerCustomStruct < FFI::Struct + layout :pointer, :pointer, + :c, :char, + :i, :int + end + + it "StructLayout has a memsize function" do + base_size = ObjectSpace.memsize_of(Object.new) + + layout = SmallCustomStruct.layout + size = ObjectSpace.memsize_of(layout) + expect(size).to be > base_size + base_size = size + + layout = LargerCustomStruct.layout + size = ObjectSpace.memsize_of(layout) + expect(size).to be > base_size + end + + it "StructField has a memsize function" do + base_size = ObjectSpace.memsize_of(Object.new) + + layout = SmallCustomStruct.layout + size = ObjectSpace.memsize_of(layout[:pointer]) + expect(size).to be > base_size + end + + it "StructLayout should be shareable with Ractor", :ractor do + kl = Class.new(FFI::Struct) do + layout :ptr, :pointer + end + expect(Ractor.shareable?(kl.layout)).to eq(true) + end + + it "StructField should be shareable with Ractor", :ractor do + kl = Class.new(FFI::Struct) do + layout :ptr, :pointer + end + expect(Ractor.shareable?(kl.layout[:ptr])).to eq(true) + end +end + + describe "Struct order" do before :all do @struct = Class.new(FFI::Struct) do diff --git a/spec/ffi/type_spec.rb b/spec/ffi/type_spec.rb new file mode 100644 index 0000000..e880c83 --- /dev/null +++ b/spec/ffi/type_spec.rb @@ -0,0 +1,76 @@ +# +# 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::Type do + it 'has a memsize function', skip: RUBY_ENGINE != "ruby" do + base_size = ObjectSpace.memsize_of(Object.new) + + size = ObjectSpace.memsize_of(FFI::Type.new(42)) + expect(size).to be > base_size + base_size = size + + converter = Module.new do + extend FFI::DataConverter + + def self.native_type + @native_type_called = true + FFI::Type::INT32 + end + + def self.to_native(val, ctx) + @to_native_called = true + ToNativeMap[val] + end + + def self.from_native(val, ctx) + @from_native_called = true + FromNativeMap[val] + end + end + + size = ObjectSpace.memsize_of(FFI::Type::Mapped.new(converter)) + expect(size).to be > base_size + base_size = size + + # Builtin types are larger as they also have a name and own ffi_type + size = ObjectSpace.memsize_of(FFI::Type::Builtin::CHAR) + expect(size).to be > base_size + end + + it "should be shareable with Ractor", :ractor do + expect(Ractor.shareable?(FFI::Type.new(5))).to eq(true) + end + + describe :Builtin do + it "should be shareable with Ractor", :ractor do + expect(Ractor.shareable?(FFI::Type::INT32)).to eq(true) + end + end + + describe :Mapped do + it "should be shareable with Ractor", :ractor do + converter = Module.new do + extend FFI::DataConverter + + def self.native_type + FFI::Type::INT32 + end + + def self.to_native(val, ctx) + ToNativeMap[val] + end + + def self.from_native(val, ctx) + FromNativeMap[val] + end + end + expect(Ractor.shareable?(converter)).to eq(true) + type = FFI::Type::Mapped.new(converter) + expect(Ractor.shareable?(type)).to eq(true) + end + end +end diff --git a/spec/ffi/variadic_spec.rb b/spec/ffi/variadic_spec.rb index f379ed4..b528c09 100644 --- a/spec/ffi/variadic_spec.rb +++ b/spec/ffi/variadic_spec.rb @@ -23,6 +23,8 @@ describe "Function with variadic arguments" do attach_function :testBlockingClose, [ :pointer ], :void attach_function :testCallbackVrDva, :testClosureVrDva, [ :double, :varargs ], :double attach_function :testCallbackVrILva, :testClosureVrILva, [ :int, :long, :varargs ], :long + + freeze end it "takes enum arguments" do @@ -37,6 +39,15 @@ describe "Function with variadic arguments" do expect(LibTest.pack_varargs2(buf, :c1, "ii", :int, :c3, :int, :c4)).to eq(:c2) end + it "can reveal its return and parameters" do + skip 'this is not yet implemented on JRuby' if RUBY_ENGINE == 'jruby' + skip 'this is not yet implemented on Truffleruby' if RUBY_ENGINE == 'truffleruby' + + fun = LibTest.attached_functions[:testBlockingWRva] + expect(fun.param_types).to eq([FFI::Type::POINTER, FFI::Type::CHAR, FFI::Type::VARARGS]) + expect(fun.return_type).to eq(FFI::Type::INT8) + end + it 'can wrap a blocking function with varargs' do handle = LibTest.testBlockingOpen expect(handle).not_to be_null @@ -87,12 +98,33 @@ describe "Function with variadic arguments" do expect(LibTest.testCallbackVrDva(3.0, :cbVrD, pr)).to be_within(0.0000001).of(45.0) end + it "can be called as instance method" do + kl = Class.new do + include LibTest + def call + pr = proc { 42.0 } + testCallbackVrDva(3.0, :cbVrD, pr) + end + end + expect(kl.new.call).to be_within(0.0000001).of(45.0) + end + it "call variadic with several callback arguments" do pr1 = proc { |i| i + 1 } pr2 = proc { |l| l + 2 } expect(LibTest.testCallbackVrILva(5, 6, :cbVrI, pr1, :cbVrL, pr2)).to eq(14) end + it "should be usable with Ractor", :ractor do + res = Ractor.new do + pr = proc { 42.0 } + LibTest.testCallbackVrDva(3.0, :cbVrD, pr) + end.take + + expect(res).to be_within(0.0000001).of(45.0) + end + + module Varargs PACK_VALUES = { 'c' => [ 0x12 ], |