diff --git a/ext/-test-/memory_view/memory_view.c b/ext/-test-/memory_view/memory_view.c index 53656b145c61af..61d02464b837f8 100644 --- a/ext/-test-/memory_view/memory_view.c +++ b/ext/-test-/memory_view/memory_view.c @@ -275,15 +275,15 @@ mdview_get_memory_view(VALUE obj, rb_memory_view_t *view, int flags) return 0; } - ssize_t i, ndim = RARRAY_LEN(shape_v); + ssize_t ndim = RARRAY_LEN(shape_v); + if (!NIL_P(strides_v) && RARRAY_LEN(strides_v) != ndim) { + rb_raise(rb_eArgError, "strides has an invalid dimension"); + } + ssize_t *shape = ALLOC_N(ssize_t, ndim); - ssize_t *strides = NULL; + ssize_t *strides = ALLOC_N(ssize_t, ndim); + ssize_t i; if (!NIL_P(strides_v)) { - if (RARRAY_LEN(strides_v) != ndim) { - rb_raise(rb_eArgError, "strides has an invalid dimension"); - } - - strides = ALLOC_N(ssize_t, ndim); for (i = 0; i < ndim; ++i) { shape[i] = NUM2SSIZET(RARRAY_AREF(shape_v, i)); strides[i] = NUM2SSIZET(RARRAY_AREF(strides_v, i)); @@ -293,6 +293,12 @@ mdview_get_memory_view(VALUE obj, rb_memory_view_t *view, int flags) for (i = 0; i < ndim; ++i) { shape[i] = NUM2SSIZET(RARRAY_AREF(shape_v, i)); } + + i = ndim - 1; + strides[i] = item_size; + for (; i > 0; --i) { + strides[i-1] = strides[i] * shape[i]; + } } rb_memory_view_init_as_byte_array(view, obj, RSTRING_PTR(buf_v), RSTRING_LEN(buf_v), true); diff --git a/ext/fiddle/conversions.c b/ext/fiddle/conversions.c index fdfc3fcbf2c233..6e0ce36378c461 100644 --- a/ext/fiddle/conversions.c +++ b/ext/fiddle/conversions.c @@ -22,6 +22,18 @@ rb_fiddle_type_ensure(VALUE type) ID long_id; #ifdef TYPE_LONG_LONG ID long_long_id; +#endif +#ifdef TYPE_INT8_T + ID int8_t_id; +#endif +#ifdef TYPE_INT16_T + ID int16_t_id; +#endif +#ifdef TYPE_INT32_T + ID int32_t_id; +#endif +#ifdef TYPE_INT64_T + ID int64_t_id; #endif ID float_id; ID double_id; @@ -40,6 +52,18 @@ rb_fiddle_type_ensure(VALUE type) RUBY_CONST_ID(long_id, "long"); #ifdef TYPE_LONG_LONG RUBY_CONST_ID(long_long_id, "long_long"); +#endif +#ifdef TYPE_INT8_T + RUBY_CONST_ID(int8_t_id, "int8_t"); +#endif +#ifdef TYPE_INT16_T + RUBY_CONST_ID(int16_t_id, "int16_t"); +#endif +#ifdef TYPE_INT32_T + RUBY_CONST_ID(int32_t_id, "int32_t"); +#endif +#ifdef TYPE_INT64_T + RUBY_CONST_ID(int64_t_id, "int64_t"); #endif RUBY_CONST_ID(float_id, "float"); RUBY_CONST_ID(double_id, "double"); @@ -72,6 +96,26 @@ rb_fiddle_type_ensure(VALUE type) else if (type_id == long_long_id) { return INT2NUM(TYPE_LONG_LONG); } +#endif +#ifdef TYPE_INT8_T + else if (type_id == int8_t_id) { + return INT2NUM(TYPE_INT8_T); + } +#endif +#ifdef TYPE_INT16_T + else if (type_id == int16_t_id) { + return INT2NUM(TYPE_INT16_T); + } +#endif +#ifdef TYPE_INT32_T + else if (type_id == int32_t_id) { + return INT2NUM(TYPE_INT32_T); + } +#endif +#ifdef TYPE_INT64_T + else if (type_id == int64_t_id) { + return INT2NUM(TYPE_INT64_T); + } #endif else if (type_id == float_id) { return INT2NUM(TYPE_FLOAT); diff --git a/ext/fiddle/depend b/ext/fiddle/depend index 6087aa856f0276..6afc09dd6785dd 100644 --- a/ext/fiddle/depend +++ b/ext/fiddle/depend @@ -879,6 +879,172 @@ handle.o: conversions.h handle.o: fiddle.h handle.o: function.h handle.o: handle.c +memory_view.o: $(RUBY_EXTCONF_H) +memory_view.o: $(arch_hdrdir)/ruby/config.h +memory_view.o: $(hdrdir)/ruby.h +memory_view.o: $(hdrdir)/ruby/assert.h +memory_view.o: $(hdrdir)/ruby/backward.h +memory_view.o: $(hdrdir)/ruby/backward/2/assume.h +memory_view.o: $(hdrdir)/ruby/backward/2/attributes.h +memory_view.o: $(hdrdir)/ruby/backward/2/bool.h +memory_view.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h +memory_view.o: $(hdrdir)/ruby/backward/2/inttypes.h +memory_view.o: $(hdrdir)/ruby/backward/2/limits.h +memory_view.o: $(hdrdir)/ruby/backward/2/long_long.h +memory_view.o: $(hdrdir)/ruby/backward/2/stdalign.h +memory_view.o: $(hdrdir)/ruby/backward/2/stdarg.h +memory_view.o: $(hdrdir)/ruby/defines.h +memory_view.o: $(hdrdir)/ruby/intern.h +memory_view.o: $(hdrdir)/ruby/internal/anyargs.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/char.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/double.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/int.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/long.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/short.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +memory_view.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +memory_view.o: $(hdrdir)/ruby/internal/assume.h +memory_view.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +memory_view.o: $(hdrdir)/ruby/internal/attr/artificial.h +memory_view.o: $(hdrdir)/ruby/internal/attr/cold.h +memory_view.o: $(hdrdir)/ruby/internal/attr/const.h +memory_view.o: $(hdrdir)/ruby/internal/attr/constexpr.h +memory_view.o: $(hdrdir)/ruby/internal/attr/deprecated.h +memory_view.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +memory_view.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +memory_view.o: $(hdrdir)/ruby/internal/attr/error.h +memory_view.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +memory_view.o: $(hdrdir)/ruby/internal/attr/forceinline.h +memory_view.o: $(hdrdir)/ruby/internal/attr/format.h +memory_view.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +memory_view.o: $(hdrdir)/ruby/internal/attr/noalias.h +memory_view.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +memory_view.o: $(hdrdir)/ruby/internal/attr/noexcept.h +memory_view.o: $(hdrdir)/ruby/internal/attr/noinline.h +memory_view.o: $(hdrdir)/ruby/internal/attr/nonnull.h +memory_view.o: $(hdrdir)/ruby/internal/attr/noreturn.h +memory_view.o: $(hdrdir)/ruby/internal/attr/pure.h +memory_view.o: $(hdrdir)/ruby/internal/attr/restrict.h +memory_view.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +memory_view.o: $(hdrdir)/ruby/internal/attr/warning.h +memory_view.o: $(hdrdir)/ruby/internal/attr/weakref.h +memory_view.o: $(hdrdir)/ruby/internal/cast.h +memory_view.o: $(hdrdir)/ruby/internal/compiler_is.h +memory_view.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +memory_view.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +memory_view.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +memory_view.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +memory_view.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +memory_view.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +memory_view.o: $(hdrdir)/ruby/internal/compiler_since.h +memory_view.o: $(hdrdir)/ruby/internal/config.h +memory_view.o: $(hdrdir)/ruby/internal/constant_p.h +memory_view.o: $(hdrdir)/ruby/internal/core.h +memory_view.o: $(hdrdir)/ruby/internal/core/rarray.h +memory_view.o: $(hdrdir)/ruby/internal/core/rbasic.h +memory_view.o: $(hdrdir)/ruby/internal/core/rbignum.h +memory_view.o: $(hdrdir)/ruby/internal/core/rclass.h +memory_view.o: $(hdrdir)/ruby/internal/core/rdata.h +memory_view.o: $(hdrdir)/ruby/internal/core/rfile.h +memory_view.o: $(hdrdir)/ruby/internal/core/rhash.h +memory_view.o: $(hdrdir)/ruby/internal/core/robject.h +memory_view.o: $(hdrdir)/ruby/internal/core/rregexp.h +memory_view.o: $(hdrdir)/ruby/internal/core/rstring.h +memory_view.o: $(hdrdir)/ruby/internal/core/rstruct.h +memory_view.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +memory_view.o: $(hdrdir)/ruby/internal/ctype.h +memory_view.o: $(hdrdir)/ruby/internal/dllexport.h +memory_view.o: $(hdrdir)/ruby/internal/dosish.h +memory_view.o: $(hdrdir)/ruby/internal/error.h +memory_view.o: $(hdrdir)/ruby/internal/eval.h +memory_view.o: $(hdrdir)/ruby/internal/event.h +memory_view.o: $(hdrdir)/ruby/internal/fl_type.h +memory_view.o: $(hdrdir)/ruby/internal/gc.h +memory_view.o: $(hdrdir)/ruby/internal/glob.h +memory_view.o: $(hdrdir)/ruby/internal/globals.h +memory_view.o: $(hdrdir)/ruby/internal/has/attribute.h +memory_view.o: $(hdrdir)/ruby/internal/has/builtin.h +memory_view.o: $(hdrdir)/ruby/internal/has/c_attribute.h +memory_view.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +memory_view.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +memory_view.o: $(hdrdir)/ruby/internal/has/extension.h +memory_view.o: $(hdrdir)/ruby/internal/has/feature.h +memory_view.o: $(hdrdir)/ruby/internal/has/warning.h +memory_view.o: $(hdrdir)/ruby/internal/intern/array.h +memory_view.o: $(hdrdir)/ruby/internal/intern/bignum.h +memory_view.o: $(hdrdir)/ruby/internal/intern/class.h +memory_view.o: $(hdrdir)/ruby/internal/intern/compar.h +memory_view.o: $(hdrdir)/ruby/internal/intern/complex.h +memory_view.o: $(hdrdir)/ruby/internal/intern/cont.h +memory_view.o: $(hdrdir)/ruby/internal/intern/dir.h +memory_view.o: $(hdrdir)/ruby/internal/intern/enum.h +memory_view.o: $(hdrdir)/ruby/internal/intern/enumerator.h +memory_view.o: $(hdrdir)/ruby/internal/intern/error.h +memory_view.o: $(hdrdir)/ruby/internal/intern/eval.h +memory_view.o: $(hdrdir)/ruby/internal/intern/file.h +memory_view.o: $(hdrdir)/ruby/internal/intern/gc.h +memory_view.o: $(hdrdir)/ruby/internal/intern/hash.h +memory_view.o: $(hdrdir)/ruby/internal/intern/io.h +memory_view.o: $(hdrdir)/ruby/internal/intern/load.h +memory_view.o: $(hdrdir)/ruby/internal/intern/marshal.h +memory_view.o: $(hdrdir)/ruby/internal/intern/numeric.h +memory_view.o: $(hdrdir)/ruby/internal/intern/object.h +memory_view.o: $(hdrdir)/ruby/internal/intern/parse.h +memory_view.o: $(hdrdir)/ruby/internal/intern/proc.h +memory_view.o: $(hdrdir)/ruby/internal/intern/process.h +memory_view.o: $(hdrdir)/ruby/internal/intern/random.h +memory_view.o: $(hdrdir)/ruby/internal/intern/range.h +memory_view.o: $(hdrdir)/ruby/internal/intern/rational.h +memory_view.o: $(hdrdir)/ruby/internal/intern/re.h +memory_view.o: $(hdrdir)/ruby/internal/intern/ruby.h +memory_view.o: $(hdrdir)/ruby/internal/intern/select.h +memory_view.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +memory_view.o: $(hdrdir)/ruby/internal/intern/signal.h +memory_view.o: $(hdrdir)/ruby/internal/intern/sprintf.h +memory_view.o: $(hdrdir)/ruby/internal/intern/string.h +memory_view.o: $(hdrdir)/ruby/internal/intern/struct.h +memory_view.o: $(hdrdir)/ruby/internal/intern/thread.h +memory_view.o: $(hdrdir)/ruby/internal/intern/time.h +memory_view.o: $(hdrdir)/ruby/internal/intern/variable.h +memory_view.o: $(hdrdir)/ruby/internal/intern/vm.h +memory_view.o: $(hdrdir)/ruby/internal/interpreter.h +memory_view.o: $(hdrdir)/ruby/internal/iterator.h +memory_view.o: $(hdrdir)/ruby/internal/memory.h +memory_view.o: $(hdrdir)/ruby/internal/method.h +memory_view.o: $(hdrdir)/ruby/internal/module.h +memory_view.o: $(hdrdir)/ruby/internal/newobj.h +memory_view.o: $(hdrdir)/ruby/internal/rgengc.h +memory_view.o: $(hdrdir)/ruby/internal/scan_args.h +memory_view.o: $(hdrdir)/ruby/internal/special_consts.h +memory_view.o: $(hdrdir)/ruby/internal/static_assert.h +memory_view.o: $(hdrdir)/ruby/internal/stdalign.h +memory_view.o: $(hdrdir)/ruby/internal/stdbool.h +memory_view.o: $(hdrdir)/ruby/internal/symbol.h +memory_view.o: $(hdrdir)/ruby/internal/token_paste.h +memory_view.o: $(hdrdir)/ruby/internal/value.h +memory_view.o: $(hdrdir)/ruby/internal/value_type.h +memory_view.o: $(hdrdir)/ruby/internal/variable.h +memory_view.o: $(hdrdir)/ruby/internal/warning_push.h +memory_view.o: $(hdrdir)/ruby/internal/xmalloc.h +memory_view.o: $(hdrdir)/ruby/memory_view.h +memory_view.o: $(hdrdir)/ruby/missing.h +memory_view.o: $(hdrdir)/ruby/ruby.h +memory_view.o: $(hdrdir)/ruby/st.h +memory_view.o: $(hdrdir)/ruby/subst.h +memory_view.o: closure.h +memory_view.o: conversions.h +memory_view.o: fiddle.h +memory_view.o: function.h +memory_view.o: memory_view.c pinned.o: $(RUBY_EXTCONF_H) pinned.o: $(arch_hdrdir)/ruby/config.h pinned.o: $(hdrdir)/ruby.h @@ -1202,6 +1368,7 @@ pointer.o: $(hdrdir)/ruby/internal/variable.h pointer.o: $(hdrdir)/ruby/internal/warning_push.h pointer.o: $(hdrdir)/ruby/internal/xmalloc.h pointer.o: $(hdrdir)/ruby/io.h +pointer.o: $(hdrdir)/ruby/memory_view.h pointer.o: $(hdrdir)/ruby/missing.h pointer.o: $(hdrdir)/ruby/onigmo.h pointer.o: $(hdrdir)/ruby/oniguruma.h diff --git a/ext/fiddle/extconf.rb b/ext/fiddle/extconf.rb index 9836087455bb5a..7e92d6a2f72636 100644 --- a/ext/fiddle/extconf.rb +++ b/ext/fiddle/extconf.rb @@ -172,14 +172,19 @@ if signed check_signedness(type.downcase, "stddef.h") end + else + check_signedness(type.downcase, "stddef.h") end end +if have_header("ruby/memory_view.h") + have_type("rb_memory_view_t", ["ruby/memory_view.h"]) +end + if libffi $LOCAL_LIBS.prepend("./#{libffi.a} ").strip! # to exts.mk $INCFLAGS.gsub!(/-I#{libffi.dir}/, '-I$(LIBFFI_DIR)') end -$INCFLAGS << " -I$(top_srcdir)" create_makefile 'fiddle' do |conf| if !libffi next conf << "LIBFFI_CLEAN = none\n" diff --git a/ext/fiddle/extlibs b/ext/fiddle/extlibs index b991f3bf4655d3..68dac46a9504b4 100644 --- a/ext/fiddle/extlibs +++ b/ext/fiddle/extlibs @@ -6,7 +6,6 @@ https://ftp.osuosl.org/pub/blfs/conglomeration/libffi/$(pkg).tar.gz \ sha512:980ca30a8d76f963fca722432b1fe5af77d7a4e4d2eac5144fbc5374d4c596609a293440573f4294207e1bdd9fda80ad1e1cafb2ffb543df5a275bc3bd546483 \ # win32/$(pkg)-mswin.patch -p0 - win32/$(pkg)-cygwin.patch -p0 $(pkg)/config.guess -> /tool/config.guess $(pkg)/config.sub -> /tool/config.sub diff --git a/ext/fiddle/fiddle.c b/ext/fiddle/fiddle.c index 546b1dc451b2bc..dd819a298ca3d8 100644 --- a/ext/fiddle/fiddle.c +++ b/ext/fiddle/fiddle.c @@ -7,6 +7,10 @@ VALUE rb_eFiddleError; void Init_fiddle_pointer(void); void Init_fiddle_pinned(void); +#ifdef FIDDLE_MEMORY_VIEW +void Init_fiddle_memory_view(void); +#endif + /* * call-seq: Fiddle.malloc(size) * @@ -204,6 +208,38 @@ Init_fiddle(void) rb_define_const(mFiddle, "TYPE_LONG_LONG", INT2NUM(TYPE_LONG_LONG)); #endif +#ifdef TYPE_INT8_T + /* Document-const: TYPE_INT8_T + * + * C type - int8_t + */ + rb_define_const(mFiddle, "TYPE_INT8_T", INT2NUM(TYPE_INT8_T)); +#endif + +#ifdef TYPE_INT16_T + /* Document-const: TYPE_INT16_T + * + * C type - int16_t + */ + rb_define_const(mFiddle, "TYPE_INT16_T", INT2NUM(TYPE_INT16_T)); +#endif + +#ifdef TYPE_INT32_T + /* Document-const: TYPE_INT32_T + * + * C type - int32_t + */ + rb_define_const(mFiddle, "TYPE_INT32_T", INT2NUM(TYPE_INT32_T)); +#endif + +#ifdef TYPE_INT64_T + /* Document-const: TYPE_INT64_T + * + * C type - int64_t + */ + rb_define_const(mFiddle, "TYPE_INT64_T", INT2NUM(TYPE_INT64_T)); +#endif + /* Document-const: TYPE_FLOAT * * C type - float @@ -298,6 +334,30 @@ Init_fiddle(void) rb_define_const(mFiddle, "ALIGN_LONG_LONG", INT2NUM(ALIGN_LONG_LONG)); #endif + /* Document-const: ALIGN_INT8_T + * + * The alignment size of a int8_t + */ + rb_define_const(mFiddle, "ALIGN_INT8_T", INT2NUM(ALIGN_INT8_T)); + + /* Document-const: ALIGN_INT16_T + * + * The alignment size of a int16_t + */ + rb_define_const(mFiddle, "ALIGN_INT16_T", INT2NUM(ALIGN_INT16_T)); + + /* Document-const: ALIGN_INT32_T + * + * The alignment size of a int32_t + */ + rb_define_const(mFiddle, "ALIGN_INT32_T", INT2NUM(ALIGN_INT32_T)); + + /* Document-const: ALIGN_INT64_T + * + * The alignment size of a int64_t + */ + rb_define_const(mFiddle, "ALIGN_INT64_T", INT2NUM(ALIGN_INT64_T)); + /* Document-const: ALIGN_FLOAT * * The alignment size of a float @@ -388,6 +448,30 @@ Init_fiddle(void) rb_define_const(mFiddle, "SIZEOF_LONG_LONG", INT2NUM(sizeof(LONG_LONG))); #endif + /* Document-const: SIZEOF_INT8_T + * + * size of a int8_t + */ + rb_define_const(mFiddle, "SIZEOF_INT8_T", INT2NUM(sizeof(int8_t))); + + /* Document-const: SIZEOF_INT16_T + * + * size of a int16_t + */ + rb_define_const(mFiddle, "SIZEOF_INT16_T", INT2NUM(sizeof(int16_t))); + + /* Document-const: SIZEOF_INT32_T + * + * size of a int32_t + */ + rb_define_const(mFiddle, "SIZEOF_INT32_T", INT2NUM(sizeof(int32_t))); + + /* Document-const: SIZEOF_INT64_T + * + * size of a int64_t + */ + rb_define_const(mFiddle, "SIZEOF_INT64_T", INT2NUM(sizeof(int64_t))); + /* Document-const: SIZEOF_FLOAT * * size of a float @@ -461,5 +545,9 @@ Init_fiddle(void) Init_fiddle_handle(); Init_fiddle_pointer(); Init_fiddle_pinned(); + +#ifdef FIDDLE_MEMORY_VIEW + Init_fiddle_memory_view(); +#endif } /* vim: set noet sws=4 sw=4: */ diff --git a/ext/fiddle/fiddle.gemspec b/ext/fiddle/fiddle.gemspec index a9514399f0de8b..a47f795f59ad45 100644 --- a/ext/fiddle/fiddle.gemspec +++ b/ext/fiddle/fiddle.gemspec @@ -1,18 +1,13 @@ # frozen_string_literal: true -source_version = ["", "ext/fiddle/"].find do |dir| - begin - break File.open(File.join(__dir__, "#{dir}lib/fiddle/version.rb")) {|f| - f.gets("\n VERSION = ") - f.gets[/\s*"(.+)"/, 1] - } - rescue Errno::ENOENT - end +version_module = Module.new do + version_rb = File.join(__dir__, "lib/fiddle/version.rb") + module_eval(File.read(version_rb), version_rb, __LINE__) end Gem::Specification.new do |spec| spec.name = "fiddle" - spec.version = source_version + spec.version = version_module::Fiddle::VERSION spec.authors = ["Aaron Patterson", "SHIBATA Hiroshi"] spec.email = ["aaron@tenderlovemaking.com", "hsbt@ruby-lang.org"] @@ -39,6 +34,7 @@ Gem::Specification.new do |spec| "ext/fiddle/function.c", "ext/fiddle/function.h", "ext/fiddle/handle.c", + "ext/fiddle/memory_view.c", "ext/fiddle/pinned.c", "ext/fiddle/pointer.c", "ext/fiddle/win32/fficonfig.h", diff --git a/ext/fiddle/fiddle.h b/ext/fiddle/fiddle.h index 46f5a1d737644e..dbad910d07504b 100644 --- a/ext/fiddle/fiddle.h +++ b/ext/fiddle/fiddle.h @@ -58,38 +58,38 @@ # error "CHAR_BIT not supported" #endif -# if SIZEOF_SHORT == 2 -# define ffi_type_ushort ffi_type_uint16 -# define ffi_type_sshort ffi_type_sint16 -# elif SIZEOF_SHORT == 4 -# define ffi_type_ushort ffi_type_uint32 -# define ffi_type_sshort ffi_type_sint32 -# else -# error "short size not supported" -# endif +#if SIZEOF_SHORT == 2 +# define ffi_type_ushort ffi_type_uint16 +# define ffi_type_sshort ffi_type_sint16 +#elif SIZEOF_SHORT == 4 +# define ffi_type_ushort ffi_type_uint32 +# define ffi_type_sshort ffi_type_sint32 +#else +# error "short size not supported" +#endif -# if SIZEOF_INT == 2 -# define ffi_type_uint ffi_type_uint16 -# define ffi_type_sint ffi_type_sint16 -# elif SIZEOF_INT == 4 -# define ffi_type_uint ffi_type_uint32 -# define ffi_type_sint ffi_type_sint32 -# elif SIZEOF_INT == 8 -# define ffi_type_uint ffi_type_uint64 -# define ffi_type_sint ffi_type_sint64 -# else -# error "int size not supported" -# endif +#if SIZEOF_INT == 2 +# define ffi_type_uint ffi_type_uint16 +# define ffi_type_sint ffi_type_sint16 +#elif SIZEOF_INT == 4 +# define ffi_type_uint ffi_type_uint32 +# define ffi_type_sint ffi_type_sint32 +#elif SIZEOF_INT == 8 +# define ffi_type_uint ffi_type_uint64 +# define ffi_type_sint ffi_type_sint64 +#else +# error "int size not supported" +#endif -# if SIZEOF_LONG == 4 -# define ffi_type_ulong ffi_type_uint32 -# define ffi_type_slong ffi_type_sint32 -# elif SIZEOF_LONG == 8 -# define ffi_type_ulong ffi_type_uint64 -# define ffi_type_slong ffi_type_sint64 -# else -# error "long size not supported" -# endif +#if SIZEOF_LONG == 4 +# define ffi_type_ulong ffi_type_uint32 +# define ffi_type_slong ffi_type_sint32 +#elif SIZEOF_LONG == 8 +# define ffi_type_ulong ffi_type_uint64 +# define ffi_type_slong ffi_type_sint64 +#else +# error "long size not supported" +#endif #if HAVE_LONG_LONG # if SIZEOF_LONG_LONG == 8 @@ -118,6 +118,27 @@ #define TYPE_VARIADIC 9 #define TYPE_CONST_STRING 10 +#define TYPE_INT8_T TYPE_CHAR +#if SIZEOF_SHORT == 2 +# define TYPE_INT16_T TYPE_SHORT +#elif SIZEOF_INT == 2 +# define TYPE_INT16_T TYPE_INT +#endif +#if SIZEOF_SHORT == 4 +# define TYPE_INT32_T TYPE_SHORT +#elif SIZEOF_INT == 4 +# define TYPE_INT32_T TYPE_INT +#elif SIZEOF_LONG == 4 +# define TYPE_INT32_T TYPE_LONG +#endif +#if SIZEOF_INT == 8 +# define TYPE_INT64_T TYPE_INT +#elif SIZEOF_LONG == 8 +# define TYPE_INT64_T TYPE_LONG +#elif defined(TYPE_LONG_LONG) +# define TYPE_INT64_T TYPE_LONG_LONG +#endif + #ifndef TYPE_SSIZE_T # if SIZEOF_SIZE_T == SIZEOF_INT # define TYPE_SSIZE_T TYPE_INT @@ -153,8 +174,8 @@ #define ALIGN_OF(type) offsetof(struct {char align_c; type align_x;}, align_x) #define ALIGN_VOIDP ALIGN_OF(void*) -#define ALIGN_SHORT ALIGN_OF(short) #define ALIGN_CHAR ALIGN_OF(char) +#define ALIGN_SHORT ALIGN_OF(short) #define ALIGN_INT ALIGN_OF(int) #define ALIGN_LONG ALIGN_OF(long) #if HAVE_LONG_LONG @@ -163,6 +184,15 @@ #define ALIGN_FLOAT ALIGN_OF(float) #define ALIGN_DOUBLE ALIGN_OF(double) +#define ALIGN_INT8_T ALIGN_OF(int8_t) +#define ALIGN_INT16_T ALIGN_OF(int16_t) +#define ALIGN_INT32_T ALIGN_OF(int32_t) +#define ALIGN_INT64_T ALIGN_OF(int64_t) + +#ifdef HAVE_TYPE_RB_MEMORY_VIEW_T +# define FIDDLE_MEMORY_VIEW +#endif + extern VALUE mFiddle; extern VALUE rb_eFiddleDLError; diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 437dbb624a7cd6..1d82bc8a3e81e0 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -77,18 +77,6 @@ rb_fiddle_new_function(VALUE address, VALUE arg_types, VALUE ret_type) return rb_class_new_instance(3, argv, cFiddleFunction); } -static int -parse_keyword_arg_i(VALUE key, VALUE value, VALUE self) -{ - if (key == ID2SYM(rb_intern("name"))) { - rb_iv_set(self, "@name", value); - } else { - rb_raise(rb_eArgError, "unknown keyword: %"PRIsVALUE, - RB_OBJ_STRING(key)); - } - return ST_CONTINUE; -} - static VALUE normalize_argument_types(const char *name, VALUE arg_types, @@ -134,15 +122,40 @@ static VALUE initialize(int argc, VALUE argv[], VALUE self) { ffi_cif * cif; - VALUE ptr, arg_types, ret_type, abi, kwds; + VALUE ptr, arg_types, ret_type, abi, kwargs; + VALUE name = Qnil; + VALUE need_gvl = Qfalse; int c_ret_type; bool is_variadic = false; ffi_abi c_ffi_abi; void *cfunc; - rb_scan_args(argc, argv, "31:", &ptr, &arg_types, &ret_type, &abi, &kwds); + rb_scan_args(argc, argv, "31:", &ptr, &arg_types, &ret_type, &abi, &kwargs); rb_iv_set(self, "@closure", ptr); + if (!NIL_P(kwargs)) { + enum { + kw_name, + kw_need_gvl, + kw_max_, + }; + static ID kw[kw_max_]; + VALUE args[kw_max_]; + if (!kw[0]) { + kw[kw_name] = rb_intern_const("name"); + kw[kw_need_gvl] = rb_intern_const("need_gvl"); + } + rb_get_kwargs(kwargs, kw, 0, kw_max_, args); + if (args[kw_name] != Qundef) { + name = args[kw_name]; + } + if (args[kw_need_gvl] != Qundef) { + need_gvl = args[kw_need_gvl]; + } + } + rb_iv_set(self, "@name", name); + rb_iv_set(self, "@need_gvl", need_gvl); + ptr = rb_Integer(ptr); cfunc = NUM2PTR(ptr); PTR2NUM(cfunc); @@ -170,8 +183,6 @@ initialize(int argc, VALUE argv[], VALUE self) rb_iv_set(self, "@abi", abi); rb_iv_set(self, "@is_variadic", is_variadic ? Qtrue : Qfalse); - if (!NIL_P(kwds)) rb_hash_foreach(kwds, parse_keyword_arg_i, self); - TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif); cif->arg_types = NULL; @@ -205,6 +216,7 @@ function_call(int argc, VALUE argv[], VALUE self) VALUE arg_types; VALUE cPointer; VALUE is_variadic; + VALUE need_gvl; int n_arg_types; int n_fixed_args = 0; int n_call_args = 0; @@ -218,6 +230,7 @@ function_call(int argc, VALUE argv[], VALUE self) arg_types = rb_iv_get(self, "@argument_types"); cPointer = rb_const_get(mFiddle, rb_intern("Pointer")); is_variadic = rb_iv_get(self, "@is_variadic"); + need_gvl = rb_iv_get(self, "@need_gvl"); n_arg_types = RARRAY_LENINT(arg_types); n_fixed_args = n_arg_types; @@ -355,7 +368,12 @@ function_call(int argc, VALUE argv[], VALUE self) args.values[i_call] = NULL; args.fn = (void(*)(void))NUM2PTR(cfunc); - (void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0); + if (RTEST(need_gvl)) { + ffi_call(args.cif, args.fn, &(args.retval), args.values); + } + else { + (void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0); + } rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno)); #if defined(_WIN32) @@ -433,6 +451,10 @@ Init_fiddle_function(void) * Caller must ensure the underlying function is called in a * thread-safe manner if running in a multi-threaded process. * + * Note that it is not thread-safe to use this method to + * directly or indirectly call many Ruby C-extension APIs unless + * you don't pass +need_gvl: true+ to Fiddle::Function#new. + * * For an example see Fiddle::Function * */ @@ -440,13 +462,20 @@ Init_fiddle_function(void) /* * Document-method: new - * call-seq: new(ptr, args, ret_type, abi = DEFAULT) + * call-seq: new(ptr, + * args, + * ret_type, + * abi = DEFAULT, + * name: nil, + * need_gvl: false) * * Constructs a Function object. * * +ptr+ is a referenced function, of a Fiddle::Handle * * +args+ is an Array of arguments, passed to the +ptr+ function * * +ret_type+ is the return type of the function * * +abi+ is the ABI of the function + * * +name+ is the name of the function + * * +need_gvl+ is whether GVL is needed to call the function * */ rb_define_method(cFiddleFunction, "initialize", initialize, -1); diff --git a/ext/fiddle/lib/fiddle/cparser.rb b/ext/fiddle/lib/fiddle/cparser.rb index cd0a64fef55332..8a269393c6b231 100644 --- a/ext/fiddle/lib/fiddle/cparser.rb +++ b/ext/fiddle/lib/fiddle/cparser.rb @@ -35,12 +35,37 @@ module CParser def parse_struct_signature(signature, tymap=nil) if signature.is_a?(String) signature = split_arguments(signature, /[,;]/) + elsif signature.is_a?(Hash) + signature = [signature] end mems = [] tys = [] signature.each{|msig| - msig = compact(msig) + msig = compact(msig) if msig.is_a?(String) case msig + when Hash + msig.each do |struct_name, struct_signature| + struct_name = struct_name.to_s if struct_name.is_a?(Symbol) + struct_name = compact(struct_name) + struct_count = nil + if struct_name =~ /^([\w\*\s]+)\[(\d+)\]$/ + struct_count = $2.to_i + struct_name = $1 + end + if struct_signature.respond_to?(:entity_class) + struct_type = struct_signature + else + parsed_struct = parse_struct_signature(struct_signature, tymap) + struct_type = CStructBuilder.create(CStruct, *parsed_struct) + end + if struct_count + ty = [struct_type, struct_count] + else + ty = struct_type + end + mems.push([struct_name, struct_type.members]) + tys.push(ty) + end when /^[\w\*\s]+[\*\s](\w+)$/ mems.push($1) tys.push(parse_ctype(msig, tymap)) @@ -128,50 +153,90 @@ def parse_ctype(ty, tymap=nil) return [parse_ctype(ty[0], tymap), ty[1]] when 'void' return TYPE_VOID - when /^(?:(?:signed\s+)?long\s+long(?:\s+int\s+)?|int64_t)(?:\s+\w+)?$/ - if( defined?(TYPE_LONG_LONG) ) - return TYPE_LONG_LONG - else + when /\A(?:(?:signed\s+)?long\s+long(?:\s+int\s+)?|int64_t)(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_LONG_LONG) raise(RuntimeError, "unsupported type: #{ty}") end - when /^(?:unsigned\s+long\s+long(?:\s+int\s+)?|uint64_t)(?:\s+\w+)?$/ - if( defined?(TYPE_LONG_LONG) ) - return -TYPE_LONG_LONG - else + return TYPE_LONG_LONG + when /\A(?:unsigned\s+long\s+long(?:\s+int\s+)?|uint64_t)(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_LONG_LONG) raise(RuntimeError, "unsupported type: #{ty}") end - when /^(?:signed\s+)?long(?:\s+int\s+)?(?:\s+\w+)?$/ + return -TYPE_LONG_LONG + when /\A(?:signed\s+)?long(?:\s+int\s+)?(?:\s+\w+)?\z/ return TYPE_LONG - when /^unsigned\s+long(?:\s+int\s+)?(?:\s+\w+)?$/ + when /\Aunsigned\s+long(?:\s+int\s+)?(?:\s+\w+)?\z/ return -TYPE_LONG - when /^(?:signed\s+)?int(?:\s+\w+)?$/ + when /\A(?:signed\s+)?int(?:\s+\w+)?\z/ return TYPE_INT - when /^(?:unsigned\s+int|uint)(?:\s+\w+)?$/ + when /\A(?:unsigned\s+int|uint)(?:\s+\w+)?\z/ return -TYPE_INT - when /^(?:signed\s+)?short(?:\s+int\s+)?(?:\s+\w+)?$/ + when /\A(?:signed\s+)?short(?:\s+int\s+)?(?:\s+\w+)?\z/ return TYPE_SHORT - when /^unsigned\s+short(?:\s+int\s+)?(?:\s+\w+)?$/ + when /\Aunsigned\s+short(?:\s+int\s+)?(?:\s+\w+)?\z/ return -TYPE_SHORT - when /^(?:signed\s+)?char(?:\s+\w+)?$/ + when /\A(?:signed\s+)?char(?:\s+\w+)?\z/ return TYPE_CHAR - when /^unsigned\s+char(?:\s+\w+)?$/ + when /\Aunsigned\s+char(?:\s+\w+)?\z/ return -TYPE_CHAR - when /^float(?:\s+\w+)?$/ + when /\Aint8_t(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_INT8_T) + raise(RuntimeError, "unsupported type: #{ty}") + end + return TYPE_INT8_T + when /\Auint8_t(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_INT8_T) + raise(RuntimeError, "unsupported type: #{ty}") + end + return -TYPE_INT8_T + when /\Aint16_t(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_INT16_T) + raise(RuntimeError, "unsupported type: #{ty}") + end + return TYPE_INT16_T + when /\Auint16_t(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_INT16_T) + raise(RuntimeError, "unsupported type: #{ty}") + end + return -TYPE_INT16_T + when /\Aint32_t(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_INT32_T) + raise(RuntimeError, "unsupported type: #{ty}") + end + return TYPE_INT32_T + when /\Auint32_t(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_INT32_T) + raise(RuntimeError, "unsupported type: #{ty}") + end + return -TYPE_INT32_T + when /\Aint64_t(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_INT64_T) + raise(RuntimeError, "unsupported type: #{ty}") + end + return TYPE_INT64_T + when /\Auint64_t(?:\s+\w+)?\z/ + unless Fiddle.const_defined?(:TYPE_INT64_T) + raise(RuntimeError, "unsupported type: #{ty}") + end + return -TYPE_INT64_T + when /\Afloat(?:\s+\w+)?\z/ return TYPE_FLOAT - when /^double(?:\s+\w+)?$/ + when /\Adouble(?:\s+\w+)?\z/ return TYPE_DOUBLE - when /^size_t(?:\s+\w+)?$/ + when /\Asize_t(?:\s+\w+)?\z/ return TYPE_SIZE_T - when /^ssize_t(?:\s+\w+)?$/ + when /\Assize_t(?:\s+\w+)?\z/ return TYPE_SSIZE_T - when /^ptrdiff_t(?:\s+\w+)?$/ + when /\Aptrdiff_t(?:\s+\w+)?\z/ return TYPE_PTRDIFF_T - when /^intptr_t(?:\s+\w+)?$/ + when /\Aintptr_t(?:\s+\w+)?\z/ return TYPE_INTPTR_T - when /^uintptr_t(?:\s+\w+)?$/ + when /\Auintptr_t(?:\s+\w+)?\z/ return TYPE_UINTPTR_T when /\*/, /\[[\s\d]*\]/ return TYPE_VOIDP + when "..." + return TYPE_VARIADIC else ty = ty.split(' ', 2)[0] if( tymap[ty] ) @@ -186,7 +251,7 @@ def parse_ctype(ty, tymap=nil) def split_arguments(arguments, sep=',') return [] if arguments.strip == 'void' - arguments.scan(/([\w\*\s]+\(\*\w*\)\(.*?\)|[\w\*\s\[\]]+)(?:#{sep}\s*|$)/).collect {|m| m[0]} + arguments.scan(/([\w\*\s]+\(\*\w*\)\(.*?\)|[\w\*\s\[\]]+|\.\.\.)(?:#{sep}\s*|\z)/).collect {|m| m[0]} end def compact(signature) diff --git a/ext/fiddle/lib/fiddle/function.rb b/ext/fiddle/lib/fiddle/function.rb index dd5e04e4170ac4..0f9913adebb599 100644 --- a/ext/fiddle/lib/fiddle/function.rb +++ b/ext/fiddle/lib/fiddle/function.rb @@ -10,6 +10,11 @@ class Function # The name of this function attr_reader :name + # Whether GVL is needed to call this function + def need_gvl? + @need_gvl + end + # The integer memory location of this function def to_i ptr.to_i diff --git a/ext/fiddle/lib/fiddle/struct.rb b/ext/fiddle/lib/fiddle/struct.rb index 259903d25cd132..a766eba83b7905 100644 --- a/ext/fiddle/lib/fiddle/struct.rb +++ b/ext/fiddle/lib/fiddle/struct.rb @@ -4,15 +4,72 @@ require 'fiddle/pack' module Fiddle - # C struct shell + # A base class for objects representing a C structure class CStruct + include Enumerable + # accessor to Fiddle::CStructEntity def CStruct.entity_class CStructEntity end + + def each + return enum_for(__function__) unless block_given? + + self.class.members.each do |name,| + yield(self[name]) + end + end + + def each_pair + return enum_for(__function__) unless block_given? + + self.class.members.each do |name,| + yield(name, self[name]) + end + end + + def to_h + hash = {} + each_pair do |name, value| + hash[name] = unstruct(value) + end + hash + end + + def replace(another) + if another.nil? + self.class.members.each do |name,| + self[name] = nil + end + elsif another.respond_to?(:each_pair) + another.each_pair do |name, value| + self[name] = value + end + else + another.each do |name, value| + self[name] = value + end + end + self + end + + private + def unstruct(value) + case value + when CStruct + value.to_h + when Array + value.collect do |v| + unstruct(v) + end + else + value + end + end end - # C union shell + # A base class for objects representing a C union class CUnion # accessor to Fiddle::CUnionEntity def CUnion.entity_class @@ -27,10 +84,14 @@ class StructArray < Array def initialize(ptr, type, initial_values) @ptr = ptr @type = type - @align = PackInfo::ALIGN_MAP[type] - @size = Fiddle::PackInfo::SIZE_MAP[type] - @pack_format = Fiddle::PackInfo::PACK_MAP[type] - super(initial_values.collect { |v| unsigned_value(v, type) }) + @is_struct = @type.respond_to?(:entity_class) + if @is_struct + super(initial_values) + else + @size = Fiddle::PackInfo::SIZE_MAP[type] + @pack_format = Fiddle::PackInfo::PACK_MAP[type] + super(initial_values.collect { |v| unsigned_value(v, type) }) + end end def to_ptr @@ -42,8 +103,12 @@ def []=(index, value) raise IndexError, 'index %d outside of array bounds 0...%d' % [index, size] end - to_ptr[index * @size, @size] = [value].pack(@pack_format) - super(index, value) + if @is_struct + self[index].replace(value) + else + to_ptr[index * @size, @size] = [value].pack(@pack_format) + super(index, value) + end end end @@ -62,7 +127,7 @@ module CStructBuilder # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an # easy-to-use manner. # - # Example: + # Examples: # # require 'fiddle/struct' # require 'fiddle/cparser' @@ -73,6 +138,17 @@ module CStructBuilder # # MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members) # + # MyStruct.malloc(Fiddle::RUBY_FREE) do |obj| + # ... + # end + # + # obj = MyStruct.malloc(Fiddle::RUBY_FREE) + # begin + # ... + # ensure + # obj.call_free + # end + # # obj = MyStruct.malloc # begin # ... @@ -82,45 +158,78 @@ module CStructBuilder # def create(klass, types, members) new_class = Class.new(klass){ - define_method(:initialize){|addr| - @entity = klass.entity_class.new(addr, types) + define_method(:initialize){|addr, func = nil| + if addr.is_a?(self.class.entity_class) + @entity = addr + else + @entity = self.class.entity_class.new(addr, types, func) + end @entity.assign_names(members) } define_method(:[]) { |*args| @entity.send(:[], *args) } define_method(:[]=) { |*args| @entity.send(:[]=, *args) } define_method(:to_ptr){ @entity } define_method(:to_i){ @entity.to_i } + define_singleton_method(:types) { types } + define_singleton_method(:members) { members } members.each{|name| + name = name[0] if name.is_a?(Array) # name is a nested struct + next if method_defined?(name) define_method(name){ @entity[name] } define_method(name + "="){|val| @entity[name] = val } } - } - size = klass.entity_class.size(types) - new_class.module_eval(<<-EOS, __FILE__, __LINE__+1) - def new_class.size() - #{size} - end - def new_class.malloc() - addr = Fiddle.malloc(#{size}) - new(addr) + entity_class = klass.entity_class + alignment = entity_class.alignment(types) + size = entity_class.size(types) + define_singleton_method(:alignment) { alignment } + define_singleton_method(:size) { size } + define_singleton_method(:malloc) do |func=nil, &block| + if block + entity_class.malloc(types, func, size) do |entity| + block.call(new(entity)) + end + else + new(entity_class.malloc(types, func, size)) + end end - EOS + } return new_class end module_function :create end - # A C struct wrapper + # A pointer to a C structure class CStructEntity < Fiddle::Pointer include PackInfo include ValueUtil + def CStructEntity.alignment(types) + max = 1 + types.each do |type, count = 1| + if type.respond_to?(:entity_class) + n = type.alignment + else + n = ALIGN_MAP[type] + end + max = n if n > max + end + max + end + # Allocates a C struct with the +types+ provided. # # See Fiddle::Pointer.malloc for memory management issues. - def CStructEntity.malloc(types, func = nil) - addr = Fiddle.malloc(CStructEntity.size(types)) - CStructEntity.new(addr, types, func) + def CStructEntity.malloc(types, func = nil, size = size(types), &block) + if block_given? + super(size, func) do |struct| + struct.set_ctypes types + yield struct + end + else + struct = super(size, func) + struct.set_ctypes types + struct + end end # Returns the offset for the packed sizes for the given +types+. @@ -136,9 +245,15 @@ def CStructEntity.size(types) max_align = types.map { |type, count = 1| last_offset = offset - align = PackInfo::ALIGN_MAP[type] + if type.respond_to?(:entity_class) + align = type.alignment + type_size = type.size + else + align = PackInfo::ALIGN_MAP[type] + type_size = PackInfo::SIZE_MAP[type] + end offset = PackInfo.align(last_offset, align) + - (PackInfo::SIZE_MAP[type] * count) + (type_size * count) align }.max @@ -152,13 +267,37 @@ def CStructEntity.size(types) # # See also Fiddle::Pointer.new def initialize(addr, types, func = nil) + if func && addr.is_a?(Pointer) && addr.free + raise ArgumentError, 'free function specified on both underlying struct Pointer and when creating a CStructEntity - who do you want to free this?' + end set_ctypes(types) super(addr, @size, func) end # Set the names of the +members+ in this C struct def assign_names(members) - @members = members + @members = [] + @nested_structs = {} + members.each_with_index do |member, index| + if member.is_a?(Array) # nested struct + member_name = member[0] + struct_type, struct_count = @ctypes[index] + if struct_count.nil? + struct = struct_type.new(to_i + @offset[index]) + else + structs = struct_count.times.map do |i| + struct_type.new(to_i + @offset[index] + i * struct_type.size) + end + struct = StructArray.new(to_i + @offset[index], + struct_type, + structs) + end + @nested_structs[member_name] = struct + else + member_name = member + end + @members << member_name + end end # Calculates the offsets and sizes for the given +types+ in the struct. @@ -169,12 +308,18 @@ def set_ctypes(types) max_align = types.map { |type, count = 1| orig_offset = offset - align = ALIGN_MAP[type] + if type.respond_to?(:entity_class) + align = type.alignment + type_size = type.size + else + align = ALIGN_MAP[type] + type_size = SIZE_MAP[type] + end offset = PackInfo.align(orig_offset, align) @offset << offset - offset += (SIZE_MAP[type] * count) + offset += (type_size * count) align }.max @@ -203,7 +348,13 @@ def [](*args) end ty = @ctypes[idx] if( ty.is_a?(Array) ) - r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) + if ty.first.respond_to?(:entity_class) + return @nested_structs[name] + else + r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) + end + elsif ty.respond_to?(:entity_class) + return @nested_structs[name] else r = super(@offset[idx], SIZE_MAP[ty.abs]) end @@ -243,6 +394,24 @@ def [](*args) def []=(*args) return super(*args) if args.size > 2 name, val = *args + name = name.to_s if name.is_a?(Symbol) + nested_struct = @nested_structs[name] + if nested_struct + if nested_struct.is_a?(StructArray) + if val.nil? + nested_struct.each do |s| + s.replace(nil) + end + else + val.each_with_index do |v, i| + nested_struct[i] = v + end + end + else + nested_struct.replace(val) + end + return val + end idx = @members.index(name) if( idx.nil? ) raise(ArgumentError, "no such member: #{name}") @@ -261,23 +430,16 @@ def []=(*args) end end + undef_method :size= def to_s() # :nodoc: super(@size) end end - # A C union wrapper + # A pointer to a C union class CUnionEntity < CStructEntity include PackInfo - # Allocates a C union the +types+ provided. - # - # See Fiddle::Pointer.malloc for memory management issues. - def CUnionEntity.malloc(types, func=nil) - addr = Fiddle.malloc(CUnionEntity.size(types)) - CUnionEntity.new(addr, types, func) - end - # Returns the size needed for the union with the given +types+. # # Fiddle::CUnionEntity.size( @@ -287,7 +449,11 @@ def CUnionEntity.malloc(types, func=nil) # Fiddle::TYPE_VOIDP ]) #=> 8 def CUnionEntity.size(types) types.map { |type, count = 1| - PackInfo::SIZE_MAP[type] * count + if type.respond_to?(:entity_class) + type.size * count + else + PackInfo::SIZE_MAP[type] * count + end }.max end @@ -300,4 +466,3 @@ def set_ctypes(types) end end end - diff --git a/ext/fiddle/lib/fiddle/version.rb b/ext/fiddle/lib/fiddle/version.rb index 40d8300d2f82e7..d8aef1a71bd643 100644 --- a/ext/fiddle/lib/fiddle/version.rb +++ b/ext/fiddle/lib/fiddle/version.rb @@ -1,3 +1,3 @@ module Fiddle - VERSION = "1.0.2" + VERSION = "1.0.4" end diff --git a/ext/fiddle/memory_view.c b/ext/fiddle/memory_view.c new file mode 100644 index 00000000000000..819a8bd95ded68 --- /dev/null +++ b/ext/fiddle/memory_view.c @@ -0,0 +1,254 @@ +#include +#include + +#ifdef HAVE_RUBY_MEMORY_VIEW_H +# include +#endif + +#if SIZEOF_INTPTR_T == SIZEOF_LONG_LONG +# define INTPTR2NUM LL2NUM +# define UINTPTR2NUM ULL2NUM +#elif SIZEOF_INTPTR_T == SIZEOF_LONG +# define INTPTR2NUM LONG2NUM +# define UINTPTR2NUM ULONG2NUM +#else +# define INTPTR2NUM INT2NUM +# define UINTPTR2NUM UINT2NUM +#endif + +#include + +#ifdef FIDDLE_MEMORY_VIEW +VALUE rb_cMemoryView = Qnil; + +struct memview_data { + rb_memory_view_t view; + rb_memory_view_item_component_t *members; + size_t n_members; +}; + +static void +fiddle_memview_mark(void *ptr) +{ + const struct memview_data *data = ptr; + rb_gc_mark(data->view.obj); +} + +static void +fiddle_memview_free(void *ptr) +{ + struct memview_data *data = ptr; + rb_memory_view_release(&data->view); + if (data->members) + xfree(data->members); + xfree(ptr); +} + +static size_t +fiddle_memview_memsize(const void *ptr) +{ + const struct memview_data *data = ptr; + return sizeof(*data) + sizeof(rb_memory_view_item_component_t)*data->n_members + (size_t)data->view.len; +} + +static const rb_data_type_t fiddle_memview_data_type = { + "fiddle/memory_view", + {fiddle_memview_mark, fiddle_memview_free, fiddle_memview_memsize,}, +}; + +static VALUE +rb_fiddle_memview_s_allocate(VALUE klass) +{ + struct memview_data *data; + VALUE obj = TypedData_Make_Struct(klass, struct memview_data, &fiddle_memview_data_type, data); + data->view.obj = Qnil; + data->members = NULL; + data->n_members = 0; + return obj; +} + +static VALUE +rb_fiddle_memview_initialize(VALUE obj, VALUE target) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (!rb_memory_view_get(target, &data->view, 0)) { + rb_raise(rb_eArgError, "Unable to get a memory view from %+"PRIsVALUE, target); + } + + return Qnil; +} + +static VALUE +rb_fiddle_memview_get_obj(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + return data->view.obj; +} + +static VALUE +rb_fiddle_memview_get_length(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + return SSIZET2NUM(data->view.len); +} + +static VALUE +rb_fiddle_memview_get_readonly(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + return data->view.readonly ? Qtrue : Qfalse; +} + +static VALUE +rb_fiddle_memview_get_format(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + return data->view.format == NULL ? Qnil : rb_str_new_cstr(data->view.format); +} + +static VALUE +rb_fiddle_memview_get_item_size(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + return SSIZET2NUM(data->view.item_size); +} + +static VALUE +rb_fiddle_memview_get_ndim(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + return SSIZET2NUM(data->view.ndim); +} + +static VALUE +rb_fiddle_memview_get_shape(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + if (data->view.shape == NULL) return Qnil; + + const ssize_t ndim = data->view.ndim; + VALUE shape = rb_ary_new_capa(ndim); + ssize_t i; + for (i = 0; i < ndim; ++i) { + rb_ary_push(shape, SSIZET2NUM(data->view.shape[i])); + } + return shape; +} + +static VALUE +rb_fiddle_memview_get_strides(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + if (data->view.strides == NULL) return Qnil; + + const ssize_t ndim = data->view.ndim; + VALUE strides = rb_ary_new_capa(ndim); + ssize_t i; + for (i = 0; i < ndim; ++i) { + rb_ary_push(strides, SSIZET2NUM(data->view.strides[i])); + } + return strides; +} + +static VALUE +rb_fiddle_memview_get_sub_offsets(VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + if (data->view.sub_offsets == NULL) return Qnil; + + const ssize_t ndim = data->view.ndim; + VALUE sub_offsets = rb_ary_new_capa(ndim); + ssize_t i; + for (i = 0; i < ndim; ++i) { + rb_ary_push(sub_offsets, SSIZET2NUM(data->view.sub_offsets[i])); + } + return sub_offsets; +} + +static VALUE +rb_fiddle_memview_aref(int argc, VALUE *argv, VALUE obj) +{ + struct memview_data *data; + TypedData_Get_Struct(obj, struct memview_data, &fiddle_memview_data_type, data); + + if (NIL_P(data->view.obj)) return Qnil; + + const ssize_t ndim = data->view.ndim; + if (argc != ndim) { + rb_raise(rb_eIndexError, "wrong number of index (%d for %"PRIdSIZE")", argc, ndim); + } + + VALUE indices_v = 0; + ssize_t *indices = ALLOCV_N(ssize_t, indices_v, ndim); + + ssize_t i; + for (i = 0; i < ndim; ++i) { + ssize_t x = NUM2SSIZET(argv[i]); + indices[i] = x; + } + + uint8_t *ptr = rb_memory_view_get_item_pointer(&data->view, indices); + ALLOCV_END(indices_v); + + if (data->view.format == NULL) { + return INT2FIX(*ptr); + } + + if (!data->members) { + const char *err; + if (rb_memory_view_parse_item_format(data->view.format, &data->members, &data->n_members, &err) < 0) { + rb_raise(rb_eRuntimeError, "Unable to recognize item format at %"PRIdSIZE" in \"%s\"", + err - data->view.format, data->view.format); + } + } + + return rb_memory_view_extract_item_members(ptr, data->members, data->n_members); +} + +void +Init_fiddle_memory_view(void) +{ + rb_cMemoryView = rb_define_class_under(mFiddle, "MemoryView", rb_cObject); + rb_define_alloc_func(rb_cMemoryView, rb_fiddle_memview_s_allocate); + rb_define_method(rb_cMemoryView, "initialize", rb_fiddle_memview_initialize, 1); + rb_define_method(rb_cMemoryView, "obj", rb_fiddle_memview_get_obj, 0); + rb_define_method(rb_cMemoryView, "length", rb_fiddle_memview_get_length, 0); + rb_define_method(rb_cMemoryView, "readonly?", rb_fiddle_memview_get_readonly, 0); + rb_define_method(rb_cMemoryView, "format", rb_fiddle_memview_get_format, 0); + rb_define_method(rb_cMemoryView, "item_size", rb_fiddle_memview_get_item_size, 0); + rb_define_method(rb_cMemoryView, "ndim", rb_fiddle_memview_get_ndim, 0); + rb_define_method(rb_cMemoryView, "shape", rb_fiddle_memview_get_shape, 0); + rb_define_method(rb_cMemoryView, "strides", rb_fiddle_memview_get_strides, 0); + rb_define_method(rb_cMemoryView, "sub_offsets", rb_fiddle_memview_get_sub_offsets, 0); + rb_define_method(rb_cMemoryView, "[]", rb_fiddle_memview_aref, -1); +} + +#endif /* FIDDLE_MEMORY_VIEW */ diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index b531befd6e6f8c..ca1d59ff5fa9c1 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -2,8 +2,14 @@ * $Id$ */ +#include #include #include + +#ifdef HAVE_RUBY_MEMORY_VIEW_H +# include +#endif + #include #include @@ -24,6 +30,7 @@ struct ptr_data { void *ptr; long size; freefunc_t free; + bool freed; VALUE wrap[2]; }; @@ -57,14 +64,19 @@ fiddle_ptr_mark(void *ptr) } static void -fiddle_ptr_free(void *ptr) +fiddle_ptr_free_ptr(void *ptr) { struct ptr_data *data = ptr; - if (data->ptr) { - if (data->free) { - (*(data->free))(data->ptr); - } + if (data->ptr && data->free && !data->freed) { + data->freed = true; + (*(data->free))(data->ptr); } +} + +static void +fiddle_ptr_free(void *ptr) +{ + fiddle_ptr_free_ptr(ptr); xfree(ptr); } @@ -80,6 +92,38 @@ static const rb_data_type_t fiddle_ptr_data_type = { {fiddle_ptr_mark, fiddle_ptr_free, fiddle_ptr_memsize,}, }; +#ifdef FIDDLE_MEMORY_VIEW +static struct ptr_data * +fiddle_ptr_check_memory_view(VALUE obj) +{ + struct ptr_data *data; + TypedData_Get_Struct(obj, struct ptr_data, &fiddle_ptr_data_type, data); + if (data->ptr == NULL || data->size == 0) return NULL; + return data; +} + +static int +fiddle_ptr_memory_view_available_p(VALUE obj) +{ + return fiddle_ptr_check_memory_view(obj) != NULL; +} + +static int +fiddle_ptr_get_memory_view(VALUE obj, rb_memory_view_t *view, int flags) +{ + struct ptr_data *data = fiddle_ptr_check_memory_view(obj); + rb_memory_view_init_as_byte_array(view, obj, data->ptr, data->size, true); + + return 1; +} + +static const rb_memory_view_entry_t fiddle_ptr_memory_view_entry = { + fiddle_ptr_get_memory_view, + NULL, + fiddle_ptr_memory_view_available_p +}; +#endif + static VALUE rb_fiddle_ptr_new2(VALUE klass, void *ptr, long size, freefunc_t func) { @@ -89,6 +133,7 @@ rb_fiddle_ptr_new2(VALUE klass, void *ptr, long size, freefunc_t func) val = TypedData_Make_Struct(klass, struct ptr_data, &fiddle_ptr_data_type, data); data->ptr = ptr; data->free = func; + data->freed = false; data->size = size; return val; @@ -101,13 +146,13 @@ rb_fiddle_ptr_new(void *ptr, long size, freefunc_t func) } static VALUE -rb_fiddle_ptr_malloc(long size, freefunc_t func) +rb_fiddle_ptr_malloc(VALUE klass, long size, freefunc_t func) { void *ptr; ptr = ruby_xmalloc((size_t)size); memset(ptr,0,(size_t)size); - return rb_fiddle_ptr_new(ptr, size, func); + return rb_fiddle_ptr_new2(klass, ptr, size, func); } static void * @@ -140,6 +185,7 @@ rb_fiddle_ptr_s_allocate(VALUE klass) data->ptr = 0; data->size = 0; data->free = 0; + data->freed = false; return obj; } @@ -191,17 +237,31 @@ rb_fiddle_ptr_initialize(int argc, VALUE argv[], VALUE self) return Qnil; } +static VALUE +rb_fiddle_ptr_call_free(VALUE self); + /* * call-seq: * Fiddle::Pointer.malloc(size, freefunc = nil) => fiddle pointer instance + * Fiddle::Pointer.malloc(size, freefunc) { |pointer| ... } => ... * * == Examples * + * # Automatically freeing the pointer when the block is exited - recommended + * Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) do |pointer| + * ... + * end + * + * # Manually freeing but relying on the garbage collector otherwise + * pointer = Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) + * ... + * pointer.call_free + * * # Relying on the garbage collector - may lead to unlimited memory allocated before freeing any, but safe * pointer = Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) * ... * - * # Manual freeing + * # Only manually freeing * pointer = Fiddle::Pointer.malloc(size) * begin * ... @@ -214,13 +274,16 @@ rb_fiddle_ptr_initialize(int argc, VALUE argv[], VALUE self) * ... * * Allocate +size+ bytes of memory and associate it with an optional - * +freefunc+ that will be called when the pointer is garbage collected. - * +freefunc+ must be an address pointing to a function or an instance of - * +Fiddle::Function+. Using +freefunc+ may lead to unlimited memory being - * allocated before any is freed as the native memory the pointer references - * does not contribute to triggering the Ruby garbage collector. Consider - * manually freeing the memory as illustrated above. You cannot combine - * the techniques as this may lead to a double-free. + * +freefunc+. + * + * If a block is supplied, the pointer will be yielded to the block instead of + * being returned, and the return value of the block will be returned. A + * +freefunc+ must be supplied if a block is. + * + * If a +freefunc+ is supplied it will be called once, when the pointer is + * garbage collected or when the block is left if a block is supplied or + * when the user calls +call_free+, whichever happens first. +freefunc+ must be + * an address pointing to a function or an instance of +Fiddle::Function+. */ static VALUE rb_fiddle_ptr_s_malloc(int argc, VALUE argv[], VALUE klass) @@ -242,10 +305,17 @@ rb_fiddle_ptr_s_malloc(int argc, VALUE argv[], VALUE klass) rb_bug("rb_fiddle_ptr_s_malloc"); } - obj = rb_fiddle_ptr_malloc(s,f); + obj = rb_fiddle_ptr_malloc(klass, s,f); if (wrap) RPTR_DATA(obj)->wrap[1] = wrap; - return obj; + if (rb_block_given_p()) { + if (!f) { + rb_raise(rb_eArgError, "a free function must be supplied to Fiddle::Pointer.malloc when it is called with a block"); + } + return rb_ensure(rb_yield, obj, rb_fiddle_ptr_call_free, obj); + } else { + return obj; + } } /* @@ -370,6 +440,34 @@ rb_fiddle_ptr_free_get(VALUE self) return rb_fiddle_new_function(address, arg_types, ret_type); } +/* + * call-seq: call_free => nil + * + * Call the free function for this pointer. Calling more than once will do + * nothing. Does nothing if there is no free function attached. + */ +static VALUE +rb_fiddle_ptr_call_free(VALUE self) +{ + struct ptr_data *pdata; + TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, pdata); + fiddle_ptr_free_ptr(pdata); + return Qnil; +} + +/* + * call-seq: freed? => bool + * + * Returns if the free function for this pointer has been called. + */ +static VALUE +rb_fiddle_ptr_freed_p(VALUE self) +{ + struct ptr_data *pdata; + TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, pdata); + return pdata->freed ? Qtrue : Qfalse; +} + /* * call-seq: * @@ -711,6 +809,8 @@ Init_fiddle_pointer(void) rb_define_method(rb_cPointer, "initialize", rb_fiddle_ptr_initialize, -1); rb_define_method(rb_cPointer, "free=", rb_fiddle_ptr_free_set, 1); rb_define_method(rb_cPointer, "free", rb_fiddle_ptr_free_get, 0); + rb_define_method(rb_cPointer, "call_free", rb_fiddle_ptr_call_free, 0); + rb_define_method(rb_cPointer, "freed?", rb_fiddle_ptr_freed_p, 0); rb_define_method(rb_cPointer, "to_i", rb_fiddle_ptr_to_i, 0); rb_define_method(rb_cPointer, "to_int", rb_fiddle_ptr_to_i, 0); rb_define_method(rb_cPointer, "to_value", rb_fiddle_ptr_to_value, 0); @@ -732,6 +832,10 @@ Init_fiddle_pointer(void) rb_define_method(rb_cPointer, "size", rb_fiddle_ptr_size_get, 0); rb_define_method(rb_cPointer, "size=", rb_fiddle_ptr_size_set, 1); +#ifdef FIDDLE_MEMORY_VIEW + rb_memory_view_register(rb_cPointer, &fiddle_ptr_memory_view_entry); +#endif + /* Document-const: NULL * * A NULL pointer diff --git a/ext/fiddle/win32/libffi-3.2.1-cygwin.patch b/ext/fiddle/win32/libffi-3.2.1-cygwin.patch deleted file mode 100644 index 743be274f4ef59..00000000000000 --- a/ext/fiddle/win32/libffi-3.2.1-cygwin.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- libffi-3.2.1/src/closures.c 2014-11-08 21:47:24.000000000 +0900 -+++ libffi-3.2.1/src/closures.c 2020-05-26 19:09:26.334088215 +0900 -@@ -26,7 +26,7 @@ - DEALINGS IN THE SOFTWARE. - ----------------------------------------------------------------------- */ - --#if defined __linux__ && !defined _GNU_SOURCE -+#if (defined __linux__ || defined __CYGWIN__) && !defined _GNU_SOURCE - #define _GNU_SOURCE 1 - #endif - diff --git a/test/fiddle/test_c_struct_entry.rb b/test/fiddle/test_c_struct_entry.rb index 33bfee6218c17f..9fd16d71014c9d 100644 --- a/test/fiddle/test_c_struct_entry.rb +++ b/test/fiddle/test_c_struct_entry.rb @@ -43,35 +43,123 @@ def test_class_size_with_count end def test_set_ctypes - union = CStructEntity.malloc [TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE - union.assign_names %w[int long] + CStructEntity.malloc([TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE) do |struct| + struct.assign_names %w[int long] - # this test is roundabout because the stored ctypes are not accessible - union['long'] = 1 - union['int'] = 2 + # this test is roundabout because the stored ctypes are not accessible + struct['long'] = 1 + struct['int'] = 2 - assert_equal 1, union['long'] - assert_equal 2, union['int'] + assert_equal 1, struct['long'] + assert_equal 2, struct['int'] + end end def test_aref_pointer_array - team = CStructEntity.malloc([[TYPE_VOIDP, 2]], Fiddle::RUBY_FREE) - team.assign_names(["names"]) - alice = Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) - alice[0, 6] = "Alice\0" - bob = Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE) - bob[0, 4] = "Bob\0" - team["names"] = [alice, bob] - assert_equal(["Alice", "Bob"], team["names"].map(&:to_s)) + CStructEntity.malloc([[TYPE_VOIDP, 2]], Fiddle::RUBY_FREE) do |team| + team.assign_names(["names"]) + Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice| + alice[0, 6] = "Alice\0" + Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE) do |bob| + bob[0, 4] = "Bob\0" + team["names"] = [alice, bob] + assert_equal(["Alice", "Bob"], team["names"].map(&:to_s)) + end + end + end end def test_aref_pointer - user = CStructEntity.malloc([TYPE_VOIDP], Fiddle::RUBY_FREE) - user.assign_names(["name"]) - alice = Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) - alice[0, 6] = "Alice\0" - user["name"] = alice - assert_equal("Alice", user["name"].to_s) + CStructEntity.malloc([TYPE_VOIDP], Fiddle::RUBY_FREE) do |user| + user.assign_names(["name"]) + Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice| + alice[0, 6] = "Alice\0" + user["name"] = alice + assert_equal("Alice", user["name"].to_s) + end + end + end + + def test_new_double_free + types = [TYPE_INT] + Pointer.malloc(CStructEntity.size(types), Fiddle::RUBY_FREE) do |pointer| + assert_raise ArgumentError do + CStructEntity.new(pointer, types, Fiddle::RUBY_FREE) + end + end + end + + def test_malloc_block + escaped_struct = nil + returned = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct| + assert_equal Fiddle::SIZEOF_INT, struct.size + assert_equal Fiddle::RUBY_FREE, struct.free.to_i + escaped_struct = struct + :returned + end + assert_equal :returned, returned + assert escaped_struct.freed? + end + + def test_malloc_block_no_free + assert_raise ArgumentError do + CStructEntity.malloc([TYPE_INT]) { |struct| } + end + end + + def test_free + struct = CStructEntity.malloc([TYPE_INT]) + begin + assert_nil struct.free + ensure + Fiddle.free struct + end + end + + def test_free_with_func + struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) + refute struct.freed? + struct.call_free + assert struct.freed? + struct.call_free # you can safely run it again + assert struct.freed? + GC.start # you can safely run the GC routine + assert struct.freed? + end + + def test_free_with_no_func + struct = CStructEntity.malloc([TYPE_INT]) + refute struct.freed? + struct.call_free + refute struct.freed? + struct.call_free # you can safely run it again + refute struct.freed? + end + + def test_freed? + struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) + refute struct.freed? + struct.call_free + assert struct.freed? + end + + def test_null? + struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) + refute struct.null? + end + + def test_size + CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct| + assert_equal Fiddle::SIZEOF_INT, struct.size + end + end + + def test_size= + CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct| + assert_raise NoMethodError do + struct.size = 1 + end + end end end end if defined?(Fiddle) diff --git a/test/fiddle/test_c_union_entity.rb b/test/fiddle/test_c_union_entity.rb index 93100847334bf9..e0a3757562540f 100644 --- a/test/fiddle/test_c_union_entity.rb +++ b/test/fiddle/test_c_union_entity.rb @@ -21,15 +21,16 @@ def test_class_size_with_count end def test_set_ctypes - union = CUnionEntity.malloc [TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE - union.assign_names %w[int long] + CUnionEntity.malloc([TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE) do |union| + union.assign_names %w[int long] - # this test is roundabout because the stored ctypes are not accessible - union['long'] = 1 - assert_equal 1, union['long'] + # this test is roundabout because the stored ctypes are not accessible + union['long'] = 1 + assert_equal 1, union['long'] - union['int'] = 1 - assert_equal 1, union['int'] + union['int'] = 1 + assert_equal 1, union['int'] + end end end end if defined?(Fiddle) diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb index 5d9ac3c8151c17..ef8cec5daa909d 100644 --- a/test/fiddle/test_cparser.rb +++ b/test/fiddle/test_cparser.rb @@ -2,6 +2,7 @@ begin require_relative 'helper' require 'fiddle/cparser' + require 'fiddle/import' rescue LoadError end @@ -68,6 +69,19 @@ def test_undefined_ctype_with_type_alias assert_equal(-TYPE_LONG, parse_ctype('DWORD', {"DWORD" => "unsigned long"})) end + def expand_struct_types(types) + types.collect do |type| + case type + when Class + [expand_struct_types(type.types)] + when Array + [expand_struct_types([type[0]])[0][0], type[1]] + else + type + end + end + end + def test_struct_basic assert_equal [[TYPE_INT, TYPE_CHAR], ['i', 'c']], parse_struct_signature(['int i', 'char c']) end @@ -76,6 +90,93 @@ def test_struct_array assert_equal [[[TYPE_CHAR,80],[TYPE_INT,5]], ['buffer','x']], parse_struct_signature(['char buffer[80]', 'int[5] x']) end + def test_struct_nested_struct + types, members = parse_struct_signature([ + 'int x', + {inner: ['int i', 'char c']}, + ]) + assert_equal([[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]], + ['x', ['inner', ['i', 'c']]]], + [expand_struct_types(types), + members]) + end + + def test_struct_nested_defined_struct + inner = Fiddle::Importer.struct(['int i', 'char c']) + assert_equal([[TYPE_INT, inner], + ['x', ['inner', ['i', 'c']]]], + parse_struct_signature([ + 'int x', + {inner: inner}, + ])) + end + + def test_struct_double_nested_struct + types, members = parse_struct_signature([ + 'int x', + { + outer: [ + 'int y', + {inner: ['int i', 'char c']}, + ], + }, + ]) + assert_equal([[TYPE_INT, [[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]]]], + ['x', ['outer', ['y', ['inner', ['i', 'c']]]]]], + [expand_struct_types(types), + members]) + end + + def test_struct_nested_struct_array + types, members = parse_struct_signature([ + 'int x', + { + 'inner[2]' => [ + 'int i', + 'char c', + ], + }, + ]) + assert_equal([[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]], + ['x', ['inner', ['i', 'c']]]], + [expand_struct_types(types), + members]) + end + + def test_struct_double_nested_struct_inner_array + types, members = parse_struct_signature(outer: [ + 'int x', + { + 'inner[2]' => [ + 'int i', + 'char c', + ], + }, + ]) + assert_equal([[[[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]]]], + [['outer', ['x', ['inner', ['i', 'c']]]]]], + [expand_struct_types(types), + members]) + end + + def test_struct_double_nested_struct_outer_array + types, members = parse_struct_signature([ + 'int x', + { + 'outer[2]' => { + inner: [ + 'int i', + 'char c', + ], + }, + }, + ]) + assert_equal([[TYPE_INT, [[[[TYPE_INT, TYPE_CHAR]]], 2]], + ['x', ['outer', [['inner', ['i', 'c']]]]]], + [expand_struct_types(types), + members]) + end + def test_struct_array_str assert_equal [[[TYPE_CHAR,80],[TYPE_INT,5]], ['buffer','x']], parse_struct_signature('char buffer[80], int[5] x') end @@ -179,6 +280,18 @@ def test_signature_function_pointer assert_equal [TYPE_VOIDP, TYPE_INT, TYPE_INT], args end + def test_signature_variadic_arguments + unless Fiddle.const_defined?("TYPE_VARIADIC") + skip "libffi doesn't support variadic arguments" + end + assert_equal([ + "printf", + TYPE_INT, + [TYPE_VOIDP, TYPE_VARIADIC], + ], + parse_signature('int printf(const char *format, ...)')) + end + def test_signature_return_pointer func, ret, args = parse_signature('void* malloc(size_t)') assert_equal 'malloc', func diff --git a/test/fiddle/test_function.rb b/test/fiddle/test_function.rb index 0bb66f9b8decc3..742615a56bac2f 100644 --- a/test/fiddle/test_function.rb +++ b/test/fiddle/test_function.rb @@ -21,6 +21,17 @@ def test_name assert_equal 'sin', func.name end + def test_need_gvl? + libruby = Fiddle.dlopen(nil) + rb_str_dup = Function.new(libruby['rb_str_dup'], + [:voidp], + :voidp, + need_gvl: true) + assert(rb_str_dup.need_gvl?) + assert_equal('Hello', + Fiddle.dlunwrap(rb_str_dup.call(Fiddle.dlwrap('Hello')))) + end + def test_argument_errors assert_raise(TypeError) do Function.new(@libm['sin'], TYPE_DOUBLE, TYPE_DOUBLE) @@ -111,8 +122,7 @@ def test_nogvl_poll n1 = f.call(nil, 0, msec) n2 = th.value t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - delta = EnvUtil.apply_timeout_scale(180) - assert_in_delta(msec, t1 - t0, delta, 'slept amount of time') + assert_in_delta(msec, t1 - t0, 180, 'slept amount of time') assert_equal(0, n1, perror("poll(2) in main-thread")) assert_equal(0, n2, perror("poll(2) in sub-thread")) end diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 4afd8e5562df5b..afa8df9e008246 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -36,6 +36,29 @@ module LIBC "char c", "unsigned char buff[7]", ] + StructNestedStruct = struct [ + { + "vertices[2]" => { + position: ["float x", "float y", "float z"], + texcoord: ["float u", "float v"] + }, + object: ["int id", "void *user_data"], + }, + "int id" + ] + UnionNestedStruct = union [ + { + keyboard: [ + 'unsigned int state', + 'char key' + ], + mouse: [ + 'unsigned int button', + 'unsigned short x', + 'unsigned short y' + ] + } + ] CallCallback = bind("void call_callback(void*, void*)"){ | ptr1, ptr2| f = Function.new(ptr1.to_i, [TYPE_VOIDP], TYPE_VOID) @@ -56,22 +79,18 @@ def test_ensure_call_dlload def test_struct_memory_access() # check memory operations performed directly on struct - my_struct = Fiddle::Importer.struct(['int id']).malloc - begin + Fiddle::Importer.struct(['int id']).malloc(Fiddle::RUBY_FREE) do |my_struct| my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT assert_equal 0x01010101, my_struct.id my_struct.id = 0 assert_equal "\x00".b * Fiddle::SIZEOF_INT, my_struct[0, Fiddle::SIZEOF_INT] - ensure - Fiddle.free my_struct.to_ptr end end def test_struct_ptr_array_subscript_multiarg() # check memory operations performed on struct#to_ptr - struct = Fiddle::Importer.struct([ 'int x' ]).malloc - begin + Fiddle::Importer.struct([ 'int x' ]).malloc(Fiddle::RUBY_FREE) do |struct| ptr = struct.to_ptr struct.x = 0x02020202 @@ -79,35 +98,25 @@ def test_struct_ptr_array_subscript_multiarg() ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT assert_equal 0x01010101, struct.x - ensure - Fiddle.free struct.to_ptr end end def test_malloc() - s1 = LIBC::Timeval.malloc() - begin - s2 = LIBC::Timeval.malloc() - begin + LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s1| + LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s2| refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i) - ensure - Fiddle.free s2.to_ptr end - ensure - Fiddle.free s1.to_ptr end end def test_sizeof() assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*")) assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct)) - my_struct = LIBC::MyStruct.malloc() - begin + LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |my_struct| assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(my_struct)) - ensure - Fiddle.free my_struct.to_ptr end assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG) + assert_equal(LIBC::StructNestedStruct.size(), LIBC.sizeof(LIBC::StructNestedStruct)) end Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(.*)/) do @@ -158,8 +167,7 @@ def test_value() end def test_struct_array_assignment() - instance = Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc - begin + Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc(Fiddle::RUBY_FREE) do |instance| instance.stages[0] = 1024 instance.stages[1] = 10 instance.stages[2] = 100 @@ -170,39 +178,279 @@ def test_struct_array_assignment() instance.to_ptr[0, 3 * Fiddle::SIZEOF_INT] assert_raise(IndexError) { instance.stages[-1] = 5 } assert_raise(IndexError) { instance.stages[3] = 5 } - ensure - Fiddle.free instance.to_ptr + end + end + + def test_nested_struct_reusing_other_structs() + position_struct = Fiddle::Importer.struct(['float x', 'float y', 'float z']) + texcoord_struct = Fiddle::Importer.struct(['float u', 'float v']) + vertex_struct = Fiddle::Importer.struct(position: position_struct, texcoord: texcoord_struct) + mesh_struct = Fiddle::Importer.struct([ + { + "vertices[2]" => vertex_struct, + object: [ + "int id", + "void *user_data", + ], + }, + "int id", + ]) + assert_equal LIBC::StructNestedStruct.size, mesh_struct.size + + + keyboard_event_struct = Fiddle::Importer.struct(['unsigned int state', 'char key']) + mouse_event_struct = Fiddle::Importer.struct(['unsigned int button', 'unsigned short x', 'unsigned short y']) + event_union = Fiddle::Importer.union([{ keboard: keyboard_event_struct, mouse: mouse_event_struct}]) + assert_equal LIBC::UnionNestedStruct.size, event_union.size + end + + def test_nested_struct_alignment_is_not_its_size() + inner = Fiddle::Importer.struct(['int x', 'int y', 'int z', 'int w']) + outer = Fiddle::Importer.struct(['char a', { 'nested' => inner }, 'char b']) + outer.malloc(Fiddle::RUBY_FREE) do |instance| + offset = instance.to_ptr.instance_variable_get(:"@offset") + assert_equal Fiddle::SIZEOF_INT * 5, offset.last + assert_equal Fiddle::SIZEOF_INT * 6, outer.size + assert_equal instance.to_ptr.size, outer.size + end + end + + def test_struct_nested_struct_members() + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + Fiddle::Pointer.malloc(24, Fiddle::RUBY_FREE) do |user_data| + s.vertices[0].position.x = 1 + s.vertices[0].position.y = 2 + s.vertices[0].position.z = 3 + s.vertices[0].texcoord.u = 4 + s.vertices[0].texcoord.v = 5 + s.vertices[1].position.x = 6 + s.vertices[1].position.y = 7 + s.vertices[1].position.z = 8 + s.vertices[1].texcoord.u = 9 + s.vertices[1].texcoord.v = 10 + s.object.id = 100 + s.object.user_data = user_data + s.id = 101 + assert_equal({ + "vertices" => [ + { + "position" => { + "x" => 1, + "y" => 2, + "z" => 3, + }, + "texcoord" => { + "u" => 4, + "v" => 5, + }, + }, + { + "position" => { + "x" => 6, + "y" => 7, + "z" => 8, + }, + "texcoord" => { + "u" => 9, + "v" => 10, + }, + }, + ], + "object" => { + "id" => 100, + "user_data" => user_data, + }, + "id" => 101, + }, + s.to_h) + end + end + end + + def test_union_nested_struct_members() + LIBC::UnionNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + s.keyboard.state = 100 + s.keyboard.key = 101 + assert_equal(100, s.mouse.button) + refute_equal( 0, s.mouse.x) + end + end + + def test_struct_nested_struct_replace_array_element() + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + s.vertices[0].position.x = 5 + + vertex_struct = Fiddle::Importer.struct [{ + position: ["float x", "float y", "float z"], + texcoord: ["float u", "float v"] + }] + vertex_struct.malloc(Fiddle::RUBY_FREE) do |vertex| + vertex.position.x = 100 + s.vertices[0] = vertex + + # make sure element was copied by value, but things like memory address + # should not be changed + assert_equal(100, s.vertices[0].position.x) + refute_equal(vertex.object_id, s.vertices[0].object_id) + refute_equal(vertex.to_ptr, s.vertices[0].to_ptr) + end + end + end + + def test_struct_nested_struct_replace_array_element_nil() + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + s.vertices[0].position.x = 5 + s.vertices[0] = nil + assert_equal({ + "position" => { + "x" => 0.0, + "y" => 0.0, + "z" => 0.0, + }, + "texcoord" => { + "u" => 0.0, + "v" => 0.0, + }, + }, + s.vertices[0].to_h) + end + end + + def test_struct_nested_struct_replace_array_element_hash() + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + s.vertices[0] = { + position: { + x: 10, + y: 100, + } + } + assert_equal({ + "position" => { + "x" => 10.0, + "y" => 100.0, + "z" => 0.0, + }, + "texcoord" => { + "u" => 0.0, + "v" => 0.0, + }, + }, + s.vertices[0].to_h) + end + end + + def test_struct_nested_struct_replace_entire_array() + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + vertex_struct = Fiddle::Importer.struct [{ + position: ["float x", "float y", "float z"], + texcoord: ["float u", "float v"] + }] + + vertex_struct.malloc(Fiddle::RUBY_FREE) do |same0| + vertex_struct.malloc(Fiddle::RUBY_FREE) do |same1| + same = [same0, same1] + same[0].position.x = 1; same[1].position.x = 6 + same[0].position.y = 2; same[1].position.y = 7 + same[0].position.z = 3; same[1].position.z = 8 + same[0].texcoord.u = 4; same[1].texcoord.u = 9 + same[0].texcoord.v = 5; same[1].texcoord.v = 10 + s.vertices = same + assert_equal([ + { + "position" => { + "x" => 1.0, + "y" => 2.0, + "z" => 3.0, + }, + "texcoord" => { + "u" => 4.0, + "v" => 5.0, + }, + }, + { + "position" => { + "x" => 6.0, + "y" => 7.0, + "z" => 8.0, + }, + "texcoord" => { + "u" => 9.0, + "v" => 10.0, + }, + } + ], + s.vertices.collect(&:to_h)) + end + end + end + end + + def test_struct_nested_struct_replace_entire_array_with_different_struct() + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + different_struct_same_size = Fiddle::Importer.struct [{ + a: ['float i', 'float j', 'float k'], + b: ['float l', 'float m'] + }] + + different_struct_same_size.malloc(Fiddle::RUBY_FREE) do |different0| + different_struct_same_size.malloc(Fiddle::RUBY_FREE) do |different1| + different = [different0, different1] + different[0].a.i = 11; different[1].a.i = 16 + different[0].a.j = 12; different[1].a.j = 17 + different[0].a.k = 13; different[1].a.k = 18 + different[0].b.l = 14; different[1].b.l = 19 + different[0].b.m = 15; different[1].b.m = 20 + s.vertices[0][0, s.vertices[0].class.size] = different[0].to_ptr + s.vertices[1][0, s.vertices[1].class.size] = different[1].to_ptr + assert_equal([ + { + "position" => { + "x" => 11.0, + "y" => 12.0, + "z" => 13.0, + }, + "texcoord" => { + "u" => 14.0, + "v" => 15.0, + }, + }, + { + "position" => { + "x" => 16.0, + "y" => 17.0, + "z" => 18.0, + }, + "texcoord" => { + "u" => 19.0, + "v" => 20.0, + }, + } + ], + s.vertices.collect(&:to_h)) + end + end end end def test_struct() - s = LIBC::MyStruct.malloc() - begin + LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |s| s.num = [0,1,2,3,4] s.c = ?a.ord s.buff = "012345\377" assert_equal([0,1,2,3,4], s.num) assert_equal(?a.ord, s.c) assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,?\377.ord], s.buff) - ensure - Fiddle.free s.to_ptr end end def test_gettimeofday() if( defined?(LIBC.gettimeofday) ) - timeval = LIBC::Timeval.malloc() - begin - timezone = LIBC::Timezone.malloc() - begin + LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |timeval| + LIBC::Timezone.malloc(Fiddle::RUBY_FREE) do |timezone| LIBC.gettimeofday(timeval, timezone) - ensure - Fiddle.free timezone.to_ptr end cur = Time.now() assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i) - ensure - Fiddle.free timeval.to_ptr end end end @@ -227,9 +475,5 @@ def test_atof r = LIBC.atof("12.34") assert_includes(12.00..13.00, r) end - - def test_no_message_with_debug - assert_in_out_err(%w[--debug --disable=gems -rfiddle/import], 'p Fiddle::Importer', ['Fiddle::Importer']) - end end end if defined?(Fiddle) diff --git a/test/fiddle/test_memory_view.rb b/test/fiddle/test_memory_view.rb new file mode 100644 index 00000000000000..3c310c2d684ad1 --- /dev/null +++ b/test/fiddle/test_memory_view.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true +begin + require_relative 'helper' +rescue LoadError +end + +begin + require '-test-/memory_view' +rescue LoadError +end + +module Fiddle + class TestMemoryView < TestCase + def setup + skip "MemoryView is unavailable" unless defined? Fiddle::MemoryView + end + + def test_null_ptr + assert_raise(ArgumentError) do + MemoryView.new(Fiddle::NULL) + end + end + + def test_memory_view_from_unsupported_obj + obj = Object.new + assert_raise(ArgumentError) do + MemoryView.new(obj) + end + end + + def test_memory_view_from_pointer + str = Marshal.load(Marshal.dump("hello world")) + ptr = Pointer[str] + mview = MemoryView.new(ptr) + assert_same(ptr, mview.obj) + assert_equal(str.length, mview.length) + assert_equal(true, mview.readonly?) + assert_equal(nil, mview.format) + assert_equal(1, mview.item_size) + assert_equal(1, mview.ndim) + assert_equal(nil, mview.shape) + assert_equal(nil, mview.strides) + assert_equal(nil, mview.sub_offsets) + + codes = str.codepoints + assert_equal(codes, (0...str.length).map {|i| mview[i] }) + end + + def test_memory_view_multi_dimensional + skip "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils + + buf = [ 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12 ].pack("l!*") + shape = [3, 4] + md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, nil) + mview = Fiddle::MemoryView.new(md) + assert_equal(buf.length, mview.length) + assert_equal("l!", mview.format) + assert_equal(Fiddle::SIZEOF_LONG, mview.item_size) + assert_equal(2, mview.ndim) + assert_equal(shape, mview.shape) + assert_equal([Fiddle::SIZEOF_LONG*4, Fiddle::SIZEOF_LONG], mview.strides) + assert_equal(nil, mview.sub_offsets) + assert_equal(1, mview[0, 0]) + assert_equal(4, mview[0, 3]) + assert_equal(6, mview[1, 1]) + assert_equal(10, mview[2, 1]) + end + + def test_memory_view_multi_dimensional_with_strides + skip "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils + + buf = [ 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16 ].pack("l!*") + shape = [2, 8] + strides = [4*Fiddle::SIZEOF_LONG*2, Fiddle::SIZEOF_LONG*2] + md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, strides) + mview = Fiddle::MemoryView.new(md) + assert_equal("l!", mview.format) + assert_equal(Fiddle::SIZEOF_LONG, mview.item_size) + assert_equal(buf.length, mview.length) + assert_equal(2, mview.ndim) + assert_equal(shape, mview.shape) + assert_equal(strides, mview.strides) + assert_equal(nil, mview.sub_offsets) + assert_equal(1, mview[0, 0]) + assert_equal(5, mview[0, 2]) + assert_equal(9, mview[1, 0]) + assert_equal(15, mview[1, 3]) + end + + def test_memory_view_multi_dimensional_with_multiple_members + skip "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils + + buf = [ 1, 2, 3, 4, 5, 6, 7, 8, + -1, -2, -3, -4, -5, -6, -7, -8].pack("s*") + shape = [2, 4] + strides = [4*Fiddle::SIZEOF_SHORT*2, Fiddle::SIZEOF_SHORT*2] + md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides) + mview = Fiddle::MemoryView.new(md) + assert_equal("ss", mview.format) + assert_equal(Fiddle::SIZEOF_SHORT*2, mview.item_size) + assert_equal(buf.length, mview.length) + assert_equal(2, mview.ndim) + assert_equal(shape, mview.shape) + assert_equal(strides, mview.strides) + assert_equal(nil, mview.sub_offsets) + assert_equal([1, 2], mview[0, 0]) + assert_equal([5, 6], mview[0, 2]) + assert_equal([-1, -2], mview[1, 0]) + assert_equal([-7, -8], mview[1, 3]) + end + end +end diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb index c69e4f7142d79f..e685fea5dc3e2a 100644 --- a/test/fiddle/test_pointer.rb +++ b/test/fiddle/test_pointer.rb @@ -32,6 +32,31 @@ def test_malloc_free_func assert_equal free.to_i, ptr.free.to_i end + def test_malloc_block + escaped_ptr = nil + returned = Pointer.malloc(10, Fiddle::RUBY_FREE) do |ptr| + assert_equal 10, ptr.size + assert_equal Fiddle::RUBY_FREE, ptr.free.to_i + escaped_ptr = ptr + :returned + end + assert_equal :returned, returned + assert escaped_ptr.freed? + end + + def test_malloc_block_no_free + assert_raise ArgumentError do + Pointer.malloc(10) { |ptr| } + end + end + + def test_malloc_subclass + subclass = Class.new(Pointer) + subclass.malloc(10, Fiddle::RUBY_FREE) do |ptr| + assert ptr.is_a?(subclass) + end + end + def test_to_str str = Marshal.load(Marshal.dump("hello world")) ptr = Pointer[str] @@ -84,17 +109,18 @@ def test_to_ptr_string end def test_to_ptr_io - buf = Pointer.malloc(10, Fiddle::RUBY_FREE) - File.open(__FILE__, 'r') do |f| - ptr = Pointer.to_ptr f - fread = Function.new(@libc['fread'], - [TYPE_VOIDP, TYPE_INT, TYPE_INT, TYPE_VOIDP], - TYPE_INT) - fread.call(buf.to_i, Fiddle::SIZEOF_CHAR, buf.size - 1, ptr.to_i) - end - - File.open(__FILE__, 'r') do |f| - assert_equal f.read(9), buf.to_s + Pointer.malloc(10, Fiddle::RUBY_FREE) do |buf| + File.open(__FILE__, 'r') do |f| + ptr = Pointer.to_ptr f + fread = Function.new(@libc['fread'], + [TYPE_VOIDP, TYPE_INT, TYPE_INT, TYPE_VOIDP], + TYPE_INT) + fread.call(buf.to_i, Fiddle::SIZEOF_CHAR, buf.size - 1, ptr.to_i) + end + + File.open(__FILE__, 'r') do |f| + assert_equal f.read(9), buf.to_s + end end end @@ -170,27 +196,48 @@ def test_free= assert_equal free.ptr, ptr.free.ptr end + def test_free_with_func + ptr = Pointer.malloc(4, Fiddle::RUBY_FREE) + refute ptr.freed? + ptr.call_free + assert ptr.freed? + ptr.call_free # you can safely run it again + assert ptr.freed? + GC.start # you can safely run the GC routine + assert ptr.freed? + end + + def test_free_with_no_func + ptr = Pointer.malloc(4) + refute ptr.freed? + ptr.call_free + refute ptr.freed? + ptr.call_free # you can safely run it again + refute ptr.freed? + end + + def test_freed? + ptr = Pointer.malloc(4, Fiddle::RUBY_FREE) + refute ptr.freed? + ptr.call_free + assert ptr.freed? + end + def test_null? ptr = Pointer.new(0) assert ptr.null? end def test_size - ptr = Pointer.malloc(4) - begin + Pointer.malloc(4, Fiddle::RUBY_FREE) do |ptr| assert_equal 4, ptr.size - ensure - Fiddle.free ptr end end def test_size= - ptr = Pointer.malloc(4) - begin + Pointer.malloc(4, Fiddle::RUBY_FREE) do |ptr| ptr.size = 10 assert_equal 10, ptr.size - ensure - Fiddle.free ptr end end