From 890bc2cdde4097390f3b71dfeaa36dd92ee0afe2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 25 Sep 2020 20:32:02 +0900 Subject: [PATCH] Buffer protocol proposal (#3261) * Add buffer protocol * Modify for some review comments * Per-object buffer availability * Rename to MemoryView from Buffer and make compilable * Support integral repeat count in memory view format * Support 'x' for padding bytes * Add rb_memory_view_parse_item_format * Check type in rb_memory_view_register * Update dependencies in common.mk * Add test of MemoryView * Add test of rb_memory_view_init_as_byte_array * Add native size format test * Add MemoryView test utilities * Add test of rb_memory_view_fill_contiguous_strides * Skip spaces in format string * Support endianness specifiers * Update documentation * Support alignment * Use RUBY_ALIGNOF * Fix format parser to follow the pack format * Support the _ modifier * Parse count specifiers in get_format_size function. * Use STRUCT_ALIGNOF * Fix test * Fix test * Fix total size for the case with tail padding * Fix rb_memory_view_get_item_pointer * Fix rb_memory_view_parse_item_format again --- common.mk | 162 +++++++++ ext/-test-/memory_view/depend | 164 +++++++++ ext/-test-/memory_view/extconf.rb | 3 + ext/-test-/memory_view/memory_view.c | 400 +++++++++++++++++++++ include/ruby/memory_view.h | 136 +++++++ inits.c | 1 + memory_view.c | 506 +++++++++++++++++++++++++++ test/ruby/test_memory_view.rb | 249 +++++++++++++ 8 files changed, 1621 insertions(+) create mode 100644 ext/-test-/memory_view/depend create mode 100644 ext/-test-/memory_view/extconf.rb create mode 100644 ext/-test-/memory_view/memory_view.c create mode 100644 include/ruby/memory_view.h create mode 100644 memory_view.c create mode 100644 test/ruby/test_memory_view.rb diff --git a/common.mk b/common.mk index 8a1864dbd8..cf4c6398ab 100644 --- a/common.mk +++ b/common.mk @@ -107,6 +107,7 @@ COMMONOBJS = array.$(OBJEXT) \ load.$(OBJEXT) \ marshal.$(OBJEXT) \ math.$(OBJEXT) \ + memory_view.$(OBJEXT) \ mjit.$(OBJEXT) \ mjit_compile.$(OBJEXT) \ node.$(OBJEXT) \ @@ -7998,6 +7999,167 @@ math.$(OBJEXT): {$(VPATH)}math.c math.$(OBJEXT): {$(VPATH)}missing.h math.$(OBJEXT): {$(VPATH)}st.h math.$(OBJEXT): {$(VPATH)}subst.h +memory_view.$(OBJEXT): $(hdrdir)/ruby/ruby.h +memory_view.$(OBJEXT): $(top_srcdir)/internal/util.h +memory_view.$(OBJEXT): {$(VPATH)}assert.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/assume.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/attributes.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/bool.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/gcc_version_since.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/inttypes.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/limits.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/long_long.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h +memory_view.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +memory_view.$(OBJEXT): {$(VPATH)}config.h +memory_view.$(OBJEXT): {$(VPATH)}defines.h +memory_view.$(OBJEXT): {$(VPATH)}intern.h +memory_view.$(OBJEXT): {$(VPATH)}internal.h +memory_view.$(OBJEXT): {$(VPATH)}internal/anyargs.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/char.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/double.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/fixnum.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/gid_t.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/int.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/intptr_t.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/long.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/long_long.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/mode_t.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/off_t.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/pid_t.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/short.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/size_t.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/st_data_t.h +memory_view.$(OBJEXT): {$(VPATH)}internal/arithmetic/uid_t.h +memory_view.$(OBJEXT): {$(VPATH)}internal/assume.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/artificial.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/cold.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/const.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/constexpr.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/deprecated.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/diagnose_if.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/enum_extensibility.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/error.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/flag_enum.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/forceinline.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/format.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/maybe_unused.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/noalias.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/noinline.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/nonnull.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/noreturn.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/pure.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/warning.h +memory_view.$(OBJEXT): {$(VPATH)}internal/attr/weakref.h +memory_view.$(OBJEXT): {$(VPATH)}internal/cast.h +memory_view.$(OBJEXT): {$(VPATH)}internal/compiler_is.h +memory_view.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h +memory_view.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h +memory_view.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h +memory_view.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h +memory_view.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h +memory_view.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h +memory_view.$(OBJEXT): {$(VPATH)}internal/compiler_since.h +memory_view.$(OBJEXT): {$(VPATH)}internal/config.h +memory_view.$(OBJEXT): {$(VPATH)}internal/constant_p.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rarray.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rbasic.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rbignum.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rclass.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rdata.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rfile.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rhash.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/robject.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rregexp.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rstring.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rstruct.h +memory_view.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h +memory_view.$(OBJEXT): {$(VPATH)}internal/ctype.h +memory_view.$(OBJEXT): {$(VPATH)}internal/dllexport.h +memory_view.$(OBJEXT): {$(VPATH)}internal/dosish.h +memory_view.$(OBJEXT): {$(VPATH)}internal/error.h +memory_view.$(OBJEXT): {$(VPATH)}internal/eval.h +memory_view.$(OBJEXT): {$(VPATH)}internal/event.h +memory_view.$(OBJEXT): {$(VPATH)}internal/fl_type.h +memory_view.$(OBJEXT): {$(VPATH)}internal/gc.h +memory_view.$(OBJEXT): {$(VPATH)}internal/glob.h +memory_view.$(OBJEXT): {$(VPATH)}internal/globals.h +memory_view.$(OBJEXT): {$(VPATH)}internal/has/attribute.h +memory_view.$(OBJEXT): {$(VPATH)}internal/has/builtin.h +memory_view.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h +memory_view.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h +memory_view.$(OBJEXT): {$(VPATH)}internal/has/declspec_attribute.h +memory_view.$(OBJEXT): {$(VPATH)}internal/has/extension.h +memory_view.$(OBJEXT): {$(VPATH)}internal/has/feature.h +memory_view.$(OBJEXT): {$(VPATH)}internal/has/warning.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/array.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/bignum.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/class.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/compar.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/complex.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/cont.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/dir.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/enum.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/enumerator.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/error.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/eval.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/file.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/gc.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/hash.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/io.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/load.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/marshal.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/numeric.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/object.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/parse.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/proc.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/process.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/random.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/range.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/rational.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/re.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/ruby.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/select.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/select/largesize.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/signal.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/sprintf.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/string.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/struct.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/thread.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/time.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/variable.h +memory_view.$(OBJEXT): {$(VPATH)}internal/intern/vm.h +memory_view.$(OBJEXT): {$(VPATH)}internal/interpreter.h +memory_view.$(OBJEXT): {$(VPATH)}internal/iterator.h +memory_view.$(OBJEXT): {$(VPATH)}internal/memory.h +memory_view.$(OBJEXT): {$(VPATH)}internal/method.h +memory_view.$(OBJEXT): {$(VPATH)}internal/module.h +memory_view.$(OBJEXT): {$(VPATH)}internal/newobj.h +memory_view.$(OBJEXT): {$(VPATH)}internal/rgengc.h +memory_view.$(OBJEXT): {$(VPATH)}internal/scan_args.h +memory_view.$(OBJEXT): {$(VPATH)}internal/special_consts.h +memory_view.$(OBJEXT): {$(VPATH)}internal/static_assert.h +memory_view.$(OBJEXT): {$(VPATH)}internal/stdalign.h +memory_view.$(OBJEXT): {$(VPATH)}internal/stdbool.h +memory_view.$(OBJEXT): {$(VPATH)}internal/symbol.h +memory_view.$(OBJEXT): {$(VPATH)}internal/token_paste.h +memory_view.$(OBJEXT): {$(VPATH)}internal/value.h +memory_view.$(OBJEXT): {$(VPATH)}internal/value_type.h +memory_view.$(OBJEXT): {$(VPATH)}internal/variable.h +memory_view.$(OBJEXT): {$(VPATH)}internal/warning_push.h +memory_view.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +memory_view.$(OBJEXT): {$(VPATH)}memory_view.c +memory_view.$(OBJEXT): {$(VPATH)}memory_view.h +memory_view.$(OBJEXT): {$(VPATH)}missing.h +memory_view.$(OBJEXT): {$(VPATH)}st.h +memory_view.$(OBJEXT): {$(VPATH)}subst.h miniinit.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h miniinit.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h miniinit.$(OBJEXT): $(CCAN_DIR)/list/list.h diff --git a/ext/-test-/memory_view/depend b/ext/-test-/memory_view/depend new file mode 100644 index 0000000000..bcbd98d41f --- /dev/null +++ b/ext/-test-/memory_view/depend @@ -0,0 +1,164 @@ +# AUTOGENERATED DEPENDENCIES START +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: memory_view.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/-test-/memory_view/extconf.rb b/ext/-test-/memory_view/extconf.rb new file mode 100644 index 0000000000..d786b15db9 --- /dev/null +++ b/ext/-test-/memory_view/extconf.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: false +require_relative "../auto_ext.rb" +auto_ext(inc: true) diff --git a/ext/-test-/memory_view/memory_view.c b/ext/-test-/memory_view/memory_view.c new file mode 100644 index 0000000000..33124e2551 --- /dev/null +++ b/ext/-test-/memory_view/memory_view.c @@ -0,0 +1,400 @@ +#include "ruby.h" +#include "ruby/memory_view.h" + +#define STRUCT_ALIGNOF(T, result) do { \ + struct S { char _; T t; }; \ + (result) = (int)offsetof(struct S, t); \ +} while(0) + +static ID id_str; +static VALUE sym_format; +static VALUE sym_native_size_p; +static VALUE sym_offset; +static VALUE sym_size; +static VALUE sym_repeat; +static VALUE sym_obj; +static VALUE sym_len; +static VALUE sym_readonly; +static VALUE sym_format; +static VALUE sym_item_size; +static VALUE sym_ndim; +static VALUE sym_shape; +static VALUE sym_strides; +static VALUE sym_sub_offsets; +static VALUE sym_endianness; +static VALUE sym_little_endian; +static VALUE sym_big_endian; + +static VALUE exported_objects; + +static int +exportable_string_get_memory_view(VALUE obj, rb_memory_view_t *view, int flags) +{ + VALUE str = rb_ivar_get(obj, id_str); + rb_memory_view_init_as_byte_array(view, obj, RSTRING_PTR(str), RSTRING_LEN(str), true); + + VALUE count = rb_hash_lookup2(exported_objects, obj, INT2FIX(0)); + count = rb_funcall(count, '+', 1, INT2FIX(1)); + rb_hash_aset(exported_objects, obj, count); + + return 1; +} + +static int +exportable_string_release_memory_view(VALUE obj, rb_memory_view_t *view) +{ + VALUE count = rb_hash_lookup2(exported_objects, obj, INT2FIX(0)); + if (INT2FIX(1) == count) { + rb_hash_delete(exported_objects, obj); + } + else if (INT2FIX(0) == count) { + rb_raise(rb_eRuntimeError, "Duplicated releasing of a memory view has been occurred for %"PRIsVALUE, obj); + } + else { + count = rb_funcall(count, '-', 1, INT2FIX(1)); + rb_hash_aset(exported_objects, obj, count); + } + + return 1; +} + +static int +exportable_string_memory_view_available_p(VALUE obj) +{ + return Qtrue; +} + +static const rb_memory_view_entry_t exportable_string_memory_view_entry = { + exportable_string_get_memory_view, + exportable_string_release_memory_view, + exportable_string_memory_view_available_p +}; + +static VALUE +memory_view_available_p(VALUE mod, VALUE obj) +{ + return rb_memory_view_available_p(obj) ? Qtrue : Qfalse; +} + +static VALUE +memory_view_register(VALUE mod, VALUE obj) +{ + return rb_memory_view_register(obj, &exportable_string_memory_view_entry) ? Qtrue : Qfalse; +} + +static VALUE +memory_view_item_size_from_format(VALUE mod, VALUE format) +{ + const char *c_str = NULL; + if (!NIL_P(format)) + c_str = StringValueCStr(format); + const char *err = NULL; + ssize_t item_size = rb_memory_view_item_size_from_format(c_str, &err); + if (!err) + return rb_assoc_new(SSIZET2NUM(item_size), Qnil); + else + return rb_assoc_new(SSIZET2NUM(item_size), rb_str_new_cstr(err)); +} + +static VALUE +memory_view_parse_item_format(VALUE mod, VALUE format) +{ + const char *c_str = NULL; + if (!NIL_P(format)) + c_str = StringValueCStr(format); + const char *err = NULL; + + rb_memory_view_item_component_t *members; + ssize_t n_members; + ssize_t item_size = rb_memory_view_parse_item_format(c_str, &members, &n_members, &err); + + VALUE result = rb_ary_new_capa(3); + rb_ary_push(result, SSIZET2NUM(item_size)); + + if (!err) { + VALUE ary = rb_ary_new_capa(n_members); + ssize_t i; + for (i = 0; i < n_members; ++i) { + VALUE member = rb_hash_new(); + rb_hash_aset(member, sym_format, rb_str_new(&members[i].format, 1)); + rb_hash_aset(member, sym_native_size_p, members[i].native_size_p ? Qtrue : Qfalse); + rb_hash_aset(member, sym_endianness, members[i].little_endian_p ? sym_little_endian : sym_big_endian); + rb_hash_aset(member, sym_offset, SSIZET2NUM(members[i].offset)); + rb_hash_aset(member, sym_size, SSIZET2NUM(members[i].size)); + rb_hash_aset(member, sym_repeat, SSIZET2NUM(members[i].repeat)); + rb_ary_push(ary, member); + } + xfree(members); + rb_ary_push(result, ary); + rb_ary_push(result, Qnil); + } + else { + rb_ary_push(result, Qnil); // members + rb_ary_push(result, rb_str_new_cstr(err)); + } + + return result; +} + +static VALUE +memory_view_get_memory_view_info(VALUE mod, VALUE obj) +{ + rb_memory_view_t view; + + if (!rb_memory_view_get(obj, &view, 0)) { + return Qnil; + } + + VALUE hash = rb_hash_new(); + rb_hash_aset(hash, sym_obj, view.obj); + rb_hash_aset(hash, sym_len, SSIZET2NUM(view.len)); + rb_hash_aset(hash, sym_readonly, view.readonly ? Qtrue : Qfalse); + rb_hash_aset(hash, sym_format, view.format ? rb_str_new_cstr(view.format) : Qnil); + rb_hash_aset(hash, sym_item_size, SSIZET2NUM(view.item_size)); + rb_hash_aset(hash, sym_ndim, SSIZET2NUM(view.ndim)); + + if (view.shape) { + VALUE shape = rb_ary_new_capa(view.ndim); + rb_hash_aset(hash, sym_shape, shape); + } + else { + rb_hash_aset(hash, sym_shape, Qnil); + } + + if (view.strides) { + VALUE strides = rb_ary_new_capa(view.ndim); + rb_hash_aset(hash, sym_strides, strides); + } + else { + rb_hash_aset(hash, sym_strides, Qnil); + } + + if (view.sub_offsets) { + VALUE sub_offsets = rb_ary_new_capa(view.ndim); + rb_hash_aset(hash, sym_sub_offsets, sub_offsets); + } + else { + rb_hash_aset(hash, sym_sub_offsets, Qnil); + } + + rb_memory_view_release(&view); + + return hash; +} + +static VALUE +memory_view_fill_contiguous_strides(VALUE mod, VALUE ndim_v, VALUE item_size_v, VALUE shape_v, VALUE row_major_p) +{ + int i, ndim = FIX2INT(ndim_v); + + Check_Type(shape_v, T_ARRAY); + ssize_t *shape = ALLOC_N(ssize_t, ndim); + for (i = 0; i < ndim; ++i) { + shape[i] = NUM2SSIZET(RARRAY_AREF(shape_v, i)); + } + + ssize_t *strides = ALLOC_N(ssize_t, ndim); + rb_memory_view_fill_contiguous_strides(ndim, NUM2SSIZET(item_size_v), shape, RTEST(row_major_p), strides); + + VALUE result = rb_ary_new_capa(ndim); + for (i = 0; i < ndim; ++i) { + rb_ary_push(result, SSIZET2NUM(strides[i])); + } + + xfree(strides); + xfree(shape); + + return result; +} + +static VALUE +expstr_initialize(VALUE obj, VALUE s) +{ + rb_ivar_set(obj, id_str, s); + return Qnil; +} + +static int +mdview_get_memory_view(VALUE obj, rb_memory_view_t *view, int flags) +{ + VALUE buf_v = rb_ivar_get(obj, id_str); + VALUE shape_v = rb_ivar_get(obj, SYM2ID(sym_shape)); + VALUE strides_v = rb_ivar_get(obj, SYM2ID(sym_strides)); + + ssize_t i, ndim = RARRAY_LEN(shape_v); + ssize_t *shape = ALLOC_N(ssize_t, ndim); + ssize_t *strides = NULL; + 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)); + } + } + else { + for (i = 0; i < ndim; ++i) { + shape[i] = NUM2SSIZET(RARRAY_AREF(shape_v, i)); + } + } + + rb_memory_view_init_as_byte_array(view, obj, RSTRING_PTR(buf_v), RSTRING_LEN(buf_v), true); + view->format = "l"; + view->item_size = sizeof(long); + view->ndim = ndim; + view->shape = shape; + view->strides = strides; + + VALUE count = rb_hash_lookup2(exported_objects, obj, INT2FIX(0)); + count = rb_funcall(count, '+', 1, INT2FIX(1)); + rb_hash_aset(exported_objects, obj, count); + + return 1; +} + +static int +mdview_release_memory_view(VALUE obj, rb_memory_view_t *view) +{ + VALUE count = rb_hash_lookup2(exported_objects, obj, INT2FIX(0)); + if (INT2FIX(1) == count) { + rb_hash_delete(exported_objects, obj); + } + else if (INT2FIX(0) == count) { + rb_raise(rb_eRuntimeError, "Duplicated releasing of a memory view has been occurred for %"PRIsVALUE, obj); + } + else { + count = rb_funcall(count, '-', 1, INT2FIX(1)); + rb_hash_aset(exported_objects, obj, count); + } + + return 1; +} + +static int +mdview_memory_view_available_p(VALUE obj) +{ + return true; +} + +static const rb_memory_view_entry_t mdview_memory_view_entry = { + mdview_get_memory_view, + mdview_release_memory_view, + mdview_memory_view_available_p +}; + +static VALUE +mdview_initialize(VALUE obj, VALUE buf, VALUE shape, VALUE strides) +{ + Check_Type(buf, T_STRING); + Check_Type(shape, T_ARRAY); + if (!NIL_P(strides)) Check_Type(strides, T_ARRAY); + + rb_ivar_set(obj, id_str, buf); + rb_ivar_set(obj, SYM2ID(sym_shape), shape); + rb_ivar_set(obj, SYM2ID(sym_strides), strides); + return Qnil; +} + +static VALUE +mdview_aref(VALUE obj, VALUE indices_v) +{ + Check_Type(indices_v, T_ARRAY); + + rb_memory_view_t view; + if (!rb_memory_view_get(obj, &view, 0)) { + rb_raise(rb_eRuntimeError, "rb_memory_view_get: failed"); + } + + if (RARRAY_LEN(indices_v) != view.ndim) { + rb_raise(rb_eKeyError, "Indices has an invalid dimension"); + } + + VALUE buf_indices; + ssize_t *indices = ALLOCV_N(ssize_t, buf_indices, view.ndim); + + ssize_t i; + for (i = 0; i < view.ndim; ++i) { + indices[i] = NUM2SSIZET(RARRAY_AREF(indices_v, i)); + } + + char *ptr = rb_memory_view_get_item_pointer(&view, indices); + ALLOCV_END(buf_indices); + + long x = *(long *)ptr; + VALUE result = LONG2FIX(x); + rb_memory_view_release(&view); + + return result; +} + +void +Init_memory_view(void) +{ + VALUE mMemoryViewTestUtils = rb_define_module("MemoryViewTestUtils"); + + rb_define_module_function(mMemoryViewTestUtils, "available?", memory_view_available_p, 1); + rb_define_module_function(mMemoryViewTestUtils, "register", memory_view_register, 1); + rb_define_module_function(mMemoryViewTestUtils, "item_size_from_format", memory_view_item_size_from_format, 1); + rb_define_module_function(mMemoryViewTestUtils, "parse_item_format", memory_view_parse_item_format, 1); + rb_define_module_function(mMemoryViewTestUtils, "get_memory_view_info", memory_view_get_memory_view_info, 1); + rb_define_module_function(mMemoryViewTestUtils, "fill_contiguous_strides", memory_view_fill_contiguous_strides, 4); + + VALUE cExportableString = rb_define_class_under(mMemoryViewTestUtils, "ExportableString", rb_cObject); + rb_define_method(cExportableString, "initialize", expstr_initialize, 1); + rb_memory_view_register(cExportableString, &exportable_string_memory_view_entry); + + VALUE cMDView = rb_define_class_under(mMemoryViewTestUtils, "MultiDimensionalView", rb_cObject); + rb_define_method(cMDView, "initialize", mdview_initialize, 3); + rb_define_method(cMDView, "[]", mdview_aref, 1); + rb_memory_view_register(cMDView, &mdview_memory_view_entry); + + id_str = rb_intern("__str__"); + sym_format = ID2SYM(rb_intern("format")); + sym_native_size_p = ID2SYM(rb_intern("native_size_p")); + sym_offset = ID2SYM(rb_intern("offset")); + sym_size = ID2SYM(rb_intern("size")); + sym_repeat = ID2SYM(rb_intern("repeat")); + sym_obj = ID2SYM(rb_intern("obj")); + sym_len = ID2SYM(rb_intern("len")); + sym_readonly = ID2SYM(rb_intern("readonly")); + sym_format = ID2SYM(rb_intern("format")); + sym_item_size = ID2SYM(rb_intern("item_size")); + sym_ndim = ID2SYM(rb_intern("ndim")); + sym_shape = ID2SYM(rb_intern("shape")); + sym_strides = ID2SYM(rb_intern("strides")); + sym_sub_offsets = ID2SYM(rb_intern("sub_offsets")); + sym_endianness = ID2SYM(rb_intern("endianness")); + sym_little_endian = ID2SYM(rb_intern("little_endian")); + sym_big_endian = ID2SYM(rb_intern("big_endian")); + +#ifdef WORDS_BIGENDIAN + rb_const_set(mMemoryViewTestUtils, rb_intern("NATIVE_ENDIAN"), sym_big_endian); +#else + rb_const_set(mMemoryViewTestUtils, rb_intern("NATIVE_ENDIAN"), sym_little_endian); +#endif + +#define DEF_ALIGNMENT_CONST(type, TYPE) do { \ + int alignment; \ + STRUCT_ALIGNOF(type, alignment); \ + rb_const_set(mMemoryViewTestUtils, rb_intern(#TYPE "_ALIGNMENT"), INT2FIX(alignment)); \ +} while(0) + + DEF_ALIGNMENT_CONST(short, SHORT); + DEF_ALIGNMENT_CONST(int, INT); + DEF_ALIGNMENT_CONST(long, LONG); + DEF_ALIGNMENT_CONST(LONG_LONG, LONG_LONG); + DEF_ALIGNMENT_CONST(int16_t, INT16); + DEF_ALIGNMENT_CONST(int32_t, INT32); + DEF_ALIGNMENT_CONST(int64_t, INT64); + DEF_ALIGNMENT_CONST(intptr_t, INTPTR); + DEF_ALIGNMENT_CONST(float, FLOAT); + DEF_ALIGNMENT_CONST(double, DOUBLE); + +#undef DEF_ALIGNMENT_CONST + + exported_objects = rb_hash_new(); + rb_gc_register_mark_object(exported_objects); +} diff --git a/include/ruby/memory_view.h b/include/ruby/memory_view.h new file mode 100644 index 0000000000..dc6c971593 --- /dev/null +++ b/include/ruby/memory_view.h @@ -0,0 +1,136 @@ +#ifndef RUBY_MEMORY_VIEW_H +#define RUBY_MEMORY_VIEW_H 1 +/** + * @file + * @author Ruby developers + * @copyright This file is a part of the programming language Ruby. + * Permission is hereby granted, to either redistribute and/or + * modify this file, provided that the conditions mentioned in the + * file COPYING are met. Consult the file for details. + * @brief Memory View. + */ + +#include "ruby/internal/dllexport.h" +#include "ruby/internal/stdbool.h" +#include "ruby/internal/value.h" +#include "ruby/intern.h" + +enum ruby_memory_view_flags { + RUBY_MEMORY_VIEW_SIMPLE = 0, + RUBY_MEMORY_VIEW_WRITABLE = (1<<0), + RUBY_MEMORY_VIEW_FORMAT = (1<<1), + RUBY_MEMORY_VIEW_MULTI_DIMENSIONAL = (1<<2), + RUBY_MEMORY_VIEW_STRIDES = (1<<3) | RUBY_MEMORY_VIEW_MULTI_DIMENSIONAL, + RUBY_MEMORY_VIEW_ROW_MAJOR = (1<<4) | RUBY_MEMORY_VIEW_STRIDES, + RUBY_MEMORY_VIEW_COLUMN_MAJOR = (1<<5) | RUBY_MEMORY_VIEW_STRIDES, + RUBY_MEMORY_VIEW_ANY_CONTIGUOUS = RUBY_MEMORY_VIEW_ROW_MAJOR | RUBY_MEMORY_VIEW_COLUMN_MAJOR, + RUBY_MEMORY_VIEW_INDIRECT = (1<<6) | RUBY_MEMORY_VIEW_STRIDES, +}; + +typedef struct { + char format; + unsigned native_size_p: 1; + unsigned little_endian_p: 1; + size_t offset; + size_t size; + size_t repeat; +} rb_memory_view_item_component_t; + +typedef struct { + /* The original object that have the memory exported via this memory view. + * The consumer of this memory view has the responsibility to call rb_gc_mark + * for preventing this obj collected by GC. */ + VALUE obj; + + /* The pointer to the exported memory. */ + void *data; + + /* The number of bytes in data. */ + ssize_t len; + + /* 1 for readonly memory, 0 for writable memory. */ + int readonly; + + /* A string to describe the format of an element, or NULL for unsigned byte. + * The format string is a sequence the following pack-template specifiers: + * + * c, C, s, s!, S, S!, n, v, i, i!, I, I!, l, l!, + * L, L!, N, V, f, e, g, d, E, G, j, J, x + * + * For example, "dd" for an element that consists of two double values, + * and "CCC" for an element that consists of three bytes, such as + * a RGB color triplet. + * + * Also, the value endianness can be explicitly specified by '<' or '>' + * following a value type specifier. + */ + const char *format; + + /* The number of bytes in each element. + * item_size should equal to rb_memory_view_item_size_from_format(format). */ + ssize_t item_size; + + struct { + /* The array of rb_memory_view_item_component_t that describes the + * item structure. */ + rb_memory_view_item_component_t *components; + + /* The number of components in an item. */ + ssize_t length; + } item_desc; + + /* The number of dimension. */ + int ndim; + + /* ndim size array indicating the number of elements in each dimension. + * This can be NULL when ndim == 1. */ + ssize_t *shape; + + /* ndim size array indicating the number of bytes to skip to go to the + * next element in each dimension. */ + ssize_t *strides; + + /* The offset in each dimension when this memory view exposes a nested array. + * Or, NULL when this memory view exposes a flat array. */ + ssize_t *sub_offsets; + + /* the private data for managing this exported memory */ + void *const private; +} rb_memory_view_t; + +typedef int (* rb_memory_view_get_func_t)(VALUE obj, rb_memory_view_t *view, int flags); +typedef int (* rb_memory_view_release_func_t)(VALUE obj, rb_memory_view_t *view); +typedef int (* rb_memory_view_available_p_func_t)(VALUE obj); + +typedef struct { + rb_memory_view_get_func_t get_func; + rb_memory_view_release_func_t release_func; + rb_memory_view_available_p_func_t available_p_func; +} rb_memory_view_entry_t; + +RBIMPL_SYMBOL_EXPORT_BEGIN() + +/* memory_view.c */ +bool rb_memory_view_register(VALUE klass, const rb_memory_view_entry_t *entry); + +#define rb_memory_view_is_contiguous(view) ( \ + rb_memory_view_is_row_major_contiguous(view) \ + || rb_memory_view_is_column_major_contiguous(view)) + +int rb_memory_view_is_row_major_contiguous(const rb_memory_view_t *view); +int rb_memory_view_is_column_major_contiguous(const rb_memory_view_t *view); +void rb_memory_view_fill_contiguous_strides(const int ndim, const int item_size, const ssize_t *const shape, const int row_major_p, ssize_t *const strides); +int rb_memory_view_init_as_byte_array(rb_memory_view_t *view, VALUE obj, void *data, const ssize_t len, const int readonly); +ssize_t rb_memory_view_parse_item_format(const char *format, + rb_memory_view_item_component_t **members, + ssize_t *n_members, const char **err); +ssize_t rb_memory_view_item_size_from_format(const char *format, const char **err); +void *rb_memory_view_get_item_pointer(rb_memory_view_t *view, const ssize_t *indices); + +int rb_memory_view_available_p(VALUE obj); +int rb_memory_view_get(VALUE obj, rb_memory_view_t* memory_view, int flags); +int rb_memory_view_release(rb_memory_view_t* memory_view); + +RBIMPL_SYMBOL_EXPORT_END() + +#endif /* RUBY_BUFFER_H */ diff --git a/inits.c b/inits.c index b36b162a42..f636748101 100644 --- a/inits.c +++ b/inits.c @@ -70,6 +70,7 @@ rb_call_inits(void) CALL(Cont); CALL(Rational); CALL(Complex); + CALL(MemoryView); CALL(version); CALL(vm_trace); CALL(vm_stack_canary); diff --git a/memory_view.c b/memory_view.c new file mode 100644 index 0000000000..e45cbeb796 --- /dev/null +++ b/memory_view.c @@ -0,0 +1,506 @@ +/********************************************************************** + + memory_view.c - Memory View + + Copyright (C) 2020 Kenta Murata + +**********************************************************************/ + +#include "internal.h" +#include "internal/util.h" +#include "ruby/memory_view.h" + +#define STRUCT_ALIGNOF(T, result) do { \ + struct S { char _; T t; }; \ + (result) = (int)offsetof(struct S, t); \ +} while(0) + +static ID id_memory_view; + +static const rb_data_type_t memory_view_entry_data_type = { + "memory_view", + { + 0, + 0, + 0, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + +/* Register memory view functions for the given class */ +bool +rb_memory_view_register(VALUE klass, const rb_memory_view_entry_t *entry) { + Check_Type(klass, T_CLASS); + VALUE entry_obj = rb_ivar_get(klass, id_memory_view); + if (! NIL_P(entry_obj)) { + rb_warning("Duplicated registration of memory view to %"PRIsVALUE, klass); + return 0; + } + else { + entry_obj = TypedData_Wrap_Struct(0, &memory_view_entry_data_type, (void *)entry); + rb_ivar_set(klass, id_memory_view, entry_obj); + return 1; + } +} + +/* Examine whether the given memory view has row-major order strides. */ +int +rb_memory_view_is_row_major_contiguous(const rb_memory_view_t *view) +{ + const ssize_t ndim = view->ndim; + const ssize_t *shape = view->shape; + const ssize_t *strides = view->strides; + ssize_t n = view->item_size; + ssize_t i; + for (i = ndim - 1; i >= 0; --i) { + if (strides[i] != n) return 0; + n *= shape[i]; + } + return 1; +} + +/* Examine whether the given memory view has column-major order strides. */ +int +rb_memory_view_is_column_major_contiguous(const rb_memory_view_t *view) +{ + const ssize_t ndim = view->ndim; + const ssize_t *shape = view->shape; + const ssize_t *strides = view->strides; + ssize_t n = view->item_size; + ssize_t i; + for (i = 0; i < ndim; ++i) { + if (strides[i] != n) return 0; + n *= shape[i]; + } + return 1; +} + +/* Initialize strides array to represent the specified contiguous array. */ +void +rb_memory_view_fill_contiguous_strides(const int ndim, const int item_size, const ssize_t *const shape, const int row_major_p, ssize_t *const strides) +{ + ssize_t i, n = item_size; + if (row_major_p) { + for (i = ndim - 1; i >= 0; --i) { + strides[i] = n; + n *= shape[i]; + } + } + else { // column-major + for (i = 0; i < ndim; ++i) { + strides[i] = n; + n *= shape[i]; + } + } +} + +/* Initialize view to expose a simple byte array */ +int +rb_memory_view_init_as_byte_array(rb_memory_view_t *view, VALUE obj, void *data, const ssize_t len, const int readonly) +{ + view->obj = obj; + view->data = data; + view->len = len; + view->readonly = readonly; + view->format = NULL; + view->item_size = 1; + view->ndim = 1; + view->shape = NULL; + view->strides = NULL; + view->sub_offsets = NULL; + *((void **)&view->private) = NULL; + + return 1; +} + +#ifdef HAVE_TRUE_LONG_LONG +static const char native_types[] = "sSiIlLqQjJ"; +#else +static const char native_types[] = "sSiIlLjJ"; +#endif +static const char endianness_types[] = "sSiIlLqQjJ"; + +typedef enum { + ENDIANNESS_NATIVE, + ENDIANNESS_LITTLE, + ENDIANNESS_BIG +} endianness_t; + +static ssize_t +get_format_size(const char *format, bool *native_p, ssize_t *alignment, endianness_t *endianness, ssize_t *count, const char **next_format, VALUE *error) +{ + RUBY_ASSERT(format != NULL); + RUBY_ASSERT(native_p != NULL); + RUBY_ASSERT(endianness != NULL); + RUBY_ASSERT(count != NULL); + RUBY_ASSERT(next_format != NULL); + + *native_p = false; + *endianness = ENDIANNESS_NATIVE; + *count = 1; + + const int type_char = *format; + + int i = 1; + while (format[i]) { + switch (format[i]) { + case '!': + case '_': + if (strchr(native_types, type_char)) { + *native_p = true; + ++i; + } + else { + if (error) { + *error = rb_exc_new_str(rb_eArgError, + rb_sprintf("Unable to specify native size for '%c'", type_char)); + } + return -1; + } + continue; + + case '<': + case '>': + if (!strchr(endianness_types, type_char)) { + if (error) { + *error = rb_exc_new_str(rb_eArgError, + rb_sprintf("Unable to specify endianness for '%c'", type_char)); + } + return -1; + } + if (*endianness != ENDIANNESS_NATIVE) { + *error = rb_exc_new_cstr(rb_eArgError, "Unable to use both '<' and '>' multiple times"); + return -1; + } + *endianness = (format[i] == '<') ? ENDIANNESS_LITTLE : ENDIANNESS_BIG; + ++i; + continue; + + default: + break; + } + + break; + } + + // parse count + int ch = format[i]; + if ('0' <= ch && ch <= '9') { + ssize_t n = 0; + while ('0' <= (ch = format[i]) && ch <= '9') { + n = 10*n + ruby_digit36_to_number_table[ch]; + ++i; + } + *count = n; + } + + *next_format = &format[i]; + + switch (type_char) { + case 'x': // padding + return 1; + + case 'c': // signed char + case 'C': // unsigned char + return sizeof(char); + + case 's': // s for int16_t, s! for signed short + case 'S': // S for uint16_t, S! for unsigned short + if (*native_p) { + STRUCT_ALIGNOF(short, *alignment); + return sizeof(short); + } + // fall through + + case 'n': // n for big-endian 16bit unsigned integer + case 'v': // v for little-endian 16bit unsigned integer + STRUCT_ALIGNOF(int16_t, *alignment); + return 2; + + case 'i': // i and i! for signed int + case 'I': // I and I! for unsigned int + STRUCT_ALIGNOF(int, *alignment); + return sizeof(int); + + case 'l': // l for int32_t, l! for signed long + case 'L': // L for uint32_t, L! for unsigned long + if (*native_p) { + STRUCT_ALIGNOF(long, *alignment); + return sizeof(long); + } + // fall through + + case 'N': // N for big-endian 32bit unsigned integer + case 'V': // V for little-endian 32bit unsigned integer + STRUCT_ALIGNOF(int32_t, *alignment); + return 4; + + case 'f': // f for native float + case 'e': // e for little-endian float + case 'g': // g for big-endian float + STRUCT_ALIGNOF(float, *alignment); + return sizeof(float); + + case 'q': // q for int64_t, q! for signed long long + case 'Q': // Q for uint64_t, Q! for unsigned long long + if (*native_p) { + STRUCT_ALIGNOF(LONG_LONG, *alignment); + return sizeof(LONG_LONG); + } + STRUCT_ALIGNOF(int64_t, *alignment); + return 8; + + case 'd': // d for native double + case 'E': // E for little-endian double + case 'G': // G for big-endian double + STRUCT_ALIGNOF(double, *alignment); + return sizeof(double); + + case 'j': // j for intptr_t + case 'J': // J for uintptr_t + STRUCT_ALIGNOF(intptr_t, *alignment); + return sizeof(intptr_t); + + default: + *alignment = -1; + if (error) { + *error = rb_exc_new_str(rb_eArgError, rb_sprintf("Invalid type character '%c'", type_char)); + } + return -1; + } +} + +static inline ssize_t +calculate_padding(ssize_t total, ssize_t alignment_size) { + if (alignment_size > 1) { + ssize_t res = total % alignment_size; + if (res > 0) { + return alignment_size - res; + } + } + return 0; +} + +ssize_t +rb_memory_view_parse_item_format(const char *format, + rb_memory_view_item_component_t **members, + ssize_t *n_members, const char **err) +{ + if (format == NULL) return 1; + + VALUE error = Qnil; + ssize_t total = 0; + ssize_t len = 0; + bool alignment = false; + ssize_t max_alignment_size = 0; + + const char *p = format; + if (*p == '|') { // alginment specifier + alignment = true; + ++format; + ++p; + } + while (*p) { + const char *q = p; + + // ignore spaces + if (ISSPACE(*p)) { + while (ISSPACE(*p)) ++p; + continue; + } + + bool native_size_p = false; + ssize_t alignment_size = 0; + endianness_t endianness = ENDIANNESS_NATIVE; + ssize_t count = 0; + const ssize_t size = get_format_size(p, &native_size_p, &alignment_size, &endianness, &count, &p, &error); + if (size < 0) { + if (err) *err = q; + return -1; + } + if (max_alignment_size < alignment_size) { + max_alignment_size = alignment_size; + } + + const ssize_t padding = alignment ? calculate_padding(total, alignment_size) : 0; + total += padding + size * count; + + if (*q != 'x') { + ++len; + } + } + + // adjust total size with the alignment size of the largest element + if (alignment && max_alignment_size > 0) { + const ssize_t padding = calculate_padding(total, max_alignment_size); + total += padding; + } + + if (members && n_members) { + rb_memory_view_item_component_t *buf = ALLOC_N(rb_memory_view_item_component_t, len); + + ssize_t i = 0, offset = 0; + const char *p = format; + while (*p) { + const int type_char = *p; + + bool native_size_p; + ssize_t alignment_size = 0; + endianness_t endianness = ENDIANNESS_NATIVE; + ssize_t count = 0; + const ssize_t size = get_format_size(p, &native_size_p, &alignment_size, &endianness, &count, &p, NULL); + + const ssize_t padding = alignment ? calculate_padding(offset, alignment_size) : 0; + offset += padding; + + if (type_char != 'x') { +#ifdef WORDS_BIGENDIAN + bool little_endian_p = (endianness == ENDIANNESS_LITTLE); +#else + bool little_endian_p = (endianness != ENDIANNESS_BIG); +#endif + + switch (type_char) { + case 'e': + case 'E': + little_endian_p = true; + break; + case 'g': + case 'G': + little_endian_p = false; + break; + default: + break; + } + + buf[i++] = (rb_memory_view_item_component_t){ + .format = type_char, + .native_size_p = native_size_p, + .little_endian_p = little_endian_p, + .offset = offset, + .size = size, + .repeat = count + }; + } + + offset += size * count; + } + + *members = buf; + *n_members = len; + } + + return total; +} + +/* Return the item size. */ +ssize_t +rb_memory_view_item_size_from_format(const char *format, const char **err) +{ + return rb_memory_view_parse_item_format(format, NULL, NULL, err); +} + +/* Return the pointer to the item located by the given indices. */ +void * +rb_memory_view_get_item_pointer(rb_memory_view_t *view, const ssize_t *indices) +{ + uint8_t *ptr = view->data; + + if (view->ndim == 1) { + ssize_t stride = view->strides != NULL ? view->strides[0] : view->item_size; + return ptr + indices[0] * stride; + } + + assert(view->shape != NULL); + + int i; + if (view->strides == NULL) { + // row-major contiguous array + ssize_t stride = view->item_size; + for (i = 0; i < view->ndim; ++i) { + stride *= view->shape[i]; + } + for (i = 0; i < view->ndim; ++i) { + stride /= view->shape[i]; + ptr += indices[i] * stride; + } + } + else if (view->sub_offsets == NULL) { + // flat strided array + for (i = 0; i < view->ndim; ++i) { + ptr += indices[i] * view->strides[i]; + } + } + else { + // indirect strided array + for (i = 0; i < view->ndim; ++i) { + ptr += indices[i] * view->strides[i]; + if (view->sub_offsets[i] >= 0) { + ptr = *(uint8_t **)ptr + view->sub_offsets[i]; + } + } + } + + return ptr; +} + +static const rb_memory_view_entry_t * +lookup_memory_view_entry(VALUE klass) +{ + VALUE entry_obj = rb_ivar_get(klass, id_memory_view); + while (NIL_P(entry_obj)) { + klass = rb_class_get_superclass(klass); + + if (klass == rb_cBasicObject || klass == rb_cObject) + return NULL; + + entry_obj = rb_ivar_get(klass, id_memory_view); + } + + if (! rb_typeddata_is_kind_of(entry_obj, &memory_view_entry_data_type)) + return NULL; + + return (const rb_memory_view_entry_t *)RTYPEDDATA_DATA(entry_obj); +} + +/* Examine whether the given object supports memory view. */ +int +rb_memory_view_available_p(VALUE obj) +{ + VALUE klass = CLASS_OF(obj); + const rb_memory_view_entry_t *entry = lookup_memory_view_entry(klass); + if (entry) + return (* entry->available_p_func)(obj); + else + return 0; +} + +/* Obtain a memory view from obj, and substitute the information to view. */ +int +rb_memory_view_get(VALUE obj, rb_memory_view_t* view, int flags) +{ + VALUE klass = CLASS_OF(obj); + const rb_memory_view_entry_t *entry = lookup_memory_view_entry(klass); + if (entry) + return (*entry->get_func)(obj, view, flags); + else + return 0; +} + +/* Release the memory view obtained from obj. */ +int +rb_memory_view_release(rb_memory_view_t* view) +{ + VALUE klass = CLASS_OF(view->obj); + const rb_memory_view_entry_t *entry = lookup_memory_view_entry(klass); + if (entry) + return (*entry->release_func)(view->obj, view); + else + return 0; +} + +void +Init_MemoryView(void) +{ + id_memory_view = rb_intern("__memory_view__"); +} diff --git a/test/ruby/test_memory_view.rb b/test/ruby/test_memory_view.rb new file mode 100644 index 0000000000..b9bcae9008 --- /dev/null +++ b/test/ruby/test_memory_view.rb @@ -0,0 +1,249 @@ +require "-test-/memory_view" +require "rbconfig/sizeof" + +class TestMemoryView < Test::Unit::TestCase + NATIVE_ENDIAN = MemoryViewTestUtils::NATIVE_ENDIAN + LITTLE_ENDIAN = :little_endian + BIG_ENDIAN = :big_endian + + %I(SHORT INT INT16 INT32 INT64 INTPTR LONG LONG_LONG FLOAT DOUBLE).each do |type| + name = :"#{type}_ALIGNMENT" + const_set(name, MemoryViewTestUtils.const_get(name)) + end + + def test_rb_memory_view_register_duplicated + assert_warning(/Duplicated registration of memory view to/) do + MemoryViewTestUtils.register(MemoryViewTestUtils::ExportableString) + end + end + + def test_rb_memory_view_register_nonclass + assert_raise(TypeError) do + MemoryViewTestUtils.register(Object.new) + end + end + + def sizeof(type) + RbConfig::SIZEOF[type.to_s] + end + + def test_rb_memory_view_item_size_from_format + [ + [nil, 1], ['c', 1], ['C', 1], + ['n', 2], ['v', 2], + ['l', 4], ['L', 4], ['N', 4], ['V', 4], ['f', 4], ['e', 4], ['g', 4], + ['q', 8], ['Q', 8], ['d', 8], ['E', 8], ['G', 8], + ['s', sizeof(:short)], ['S', sizeof(:short)], ['s!', sizeof(:short)], ['S!', sizeof(:short)], + ['i', sizeof(:int)], ['I', sizeof(:int)], ['i!', sizeof(:int)], ['I!', sizeof(:int)], + ['l!', sizeof(:long)], ['L!', sizeof(:long)], + ['q!', sizeof('long long')], ['Q!', sizeof('long long')], + ['j', sizeof(:intptr_t)], ['J', sizeof(:intptr_t)], + ].each do |format, expected| + actual, err = MemoryViewTestUtils.item_size_from_format(format) + assert_nil(err) + assert_equal(expected, actual, "rb_memory_view_item_size_from_format(#{format || 'NULL'}) == #{expected}") + end + end + + def test_rb_memory_view_item_size_from_format_composed + actual, = MemoryViewTestUtils.item_size_from_format("ccc") + assert_equal(3, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("c3") + assert_equal(3, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("fd") + assert_equal(12, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("fx2d") + assert_equal(14, actual) + end + + def test_rb_memory_view_item_size_from_format_with_spaces + # spaces should be ignored + actual, = MemoryViewTestUtils.item_size_from_format("f x2 d") + assert_equal(14, actual) + end + + def test_rb_memory_view_item_size_from_format_error + assert_equal([-1, "a"], MemoryViewTestUtils.item_size_from_format("ccca")) + assert_equal([-1, "a"], MemoryViewTestUtils.item_size_from_format("ccc4a")) + end + + def test_rb_memory_view_parse_item_format + total_size, members, err = MemoryViewTestUtils.parse_item_format("ccc2f3x2d4q!<") + assert_equal(58, total_size) + assert_nil(err) + assert_equal([ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 1, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 2, size: 1, repeat: 2}, + {format: 'f', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 4, size: 4, repeat: 3}, + {format: 'd', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 18, size: 8, repeat: 4}, + {format: 'q', native_size_p: true, endianness: :little_endian, offset: 50, size: sizeof('long long'), repeat: 1} + ], + members) + end + + def test_rb_memory_view_parse_item_format_with_alignment_signle + [ + ["c", false, NATIVE_ENDIAN, 1, 1, 1], + ["C", false, NATIVE_ENDIAN, 1, 1, 1], + ["s", false, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["S", false, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["s!", true, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["S!", true, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["n", false, NATIVE_ENDIAN, INT16_ALIGNMENT, sizeof(:int16_t), 1], + ["v", false, NATIVE_ENDIAN, INT16_ALIGNMENT, sizeof(:int16_t), 1], + ["i", false, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["I", false, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["i!", true, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["I!", true, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["l", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["L", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["l!", true, NATIVE_ENDIAN, LONG_ALIGNMENT, sizeof(:long), 1], + ["L!", true, NATIVE_ENDIAN, LONG_ALIGNMENT, sizeof(:long), 1], + ["N", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["V", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["f", false, NATIVE_ENDIAN, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["e", false, :little_endian, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["g", false, :big_endian, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["q", false, NATIVE_ENDIAN, INT64_ALIGNMENT, sizeof(:int64_t), 1], + ["Q", false, NATIVE_ENDIAN, INT64_ALIGNMENT, sizeof(:int64_t), 1], + ["q!", true, NATIVE_ENDIAN, LONG_LONG_ALIGNMENT, sizeof("long long"), 1], + ["Q!", true, NATIVE_ENDIAN, LONG_LONG_ALIGNMENT, sizeof("long long"), 1], + ["d", false, NATIVE_ENDIAN, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["E", false, :little_endian, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["G", false, :big_endian, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["j", false, NATIVE_ENDIAN, INTPTR_ALIGNMENT, sizeof(:intptr_t), 1], + ["J", false, NATIVE_ENDIAN, INTPTR_ALIGNMENT, sizeof(:intptr_t), 1], + ].each do |type, native_size_p, endianness, alignment, size, repeat, total_size| + total_size, members, err = MemoryViewTestUtils.parse_item_format("|c#{type}") + assert_nil(err) + + padding_size = alignment - 1 + expected_total_size = 1 + padding_size + size + assert_equal(expected_total_size, total_size) + + expected_result = [ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: type[0], native_size_p: native_size_p, endianness: endianness, offset: alignment, size: size, repeat: repeat}, + ] + assert_equal(expected_result, members) + end + end + + def alignment_padding(total_size, alignment) + res = total_size % alignment + if res > 0 + alignment - res + else + 0 + end + end + + def test_rb_memory_view_parse_item_format_with_alignment_total_size_with_tail_padding + total_size, members, err = MemoryViewTestUtils.parse_item_format("|lqc") + assert_nil(err) + + expected_total_size = sizeof(:int32_t) + expected_total_size += alignment_padding(expected_total_size, INT32_ALIGNMENT) + expected_total_size += sizeof(:int64_t) + expected_total_size += alignment_padding(expected_total_size, INT64_ALIGNMENT) + expected_total_size += 1 + expected_total_size += alignment_padding(expected_total_size, INT64_ALIGNMENT) + assert_equal(expected_total_size, total_size) + end + + def test_rb_memory_view_parse_item_format_with_alignment_compound + total_size, members, err = MemoryViewTestUtils.parse_item_format("|ccc2f3x2d4cq!<") + assert_nil(err) + + expected_total_size = 1 + 1 + 1*2 + expected_total_size += alignment_padding(expected_total_size, FLOAT_ALIGNMENT) + expected_total_size += sizeof(:float)*3 + 1*2 + expected_total_size += alignment_padding(expected_total_size, DOUBLE_ALIGNMENT) + expected_total_size += sizeof(:double)*4 + 1 + expected_total_size += alignment_padding(expected_total_size, LONG_LONG_ALIGNMENT) + expected_total_size += sizeof("long long") + assert_equal(expected_total_size, total_size) + + expected_result = [ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 1, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 2, size: 1, repeat: 2}, + ] + offset = 4 + + res = offset % FLOAT_ALIGNMENT + offset += FLOAT_ALIGNMENT - res if res > 0 + expected_result << {format: 'f', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 4, repeat: 3} + offset += 12 + + offset += 2 # 2x + + res = offset % DOUBLE_ALIGNMENT + offset += DOUBLE_ALIGNMENT - res if res > 0 + expected_result << {format: 'd', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 8, repeat: 4} + offset += 32 + + expected_result << {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 1, repeat: 1} + offset += 1 + + res = offset % LONG_LONG_ALIGNMENT + offset += LONG_LONG_ALIGNMENT - res if res > 0 + expected_result << {format: 'q', native_size_p: true, endianness: :little_endian, offset: offset, size: 8, repeat: 1} + + assert_equal(expected_result, members) + end + + def test_rb_memory_view_init_as_byte_array + # ExportableString's memory view is initialized by rb_memory_view_init_as_byte_array + es = MemoryViewTestUtils::ExportableString.new("ruby") + memory_view_info = MemoryViewTestUtils.get_memory_view_info(es) + assert_equal({ + obj: es, + len: 4, + readonly: true, + format: nil, + item_size: 1, + ndim: 1, + shape: nil, + strides: nil, + sub_offsets: nil + }, + memory_view_info) + end + + def test_rb_memory_view_fill_contiguous_strides + row_major_strides = MemoryViewTestUtils.fill_contiguous_strides(3, 8, [2, 3, 4], true) + assert_equal([96, 32, 8], + row_major_strides) + + column_major_strides = MemoryViewTestUtils.fill_contiguous_strides(3, 8, [2, 3, 4], false) + assert_equal([8, 16, 48], + column_major_strides) + end + + def test_rb_memory_view_get_item_pointer + buf = [ 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12 ].pack("l!*") + shape = [3, 4] + mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, shape, nil) + assert_equal(1, mv[[0, 0]]) + assert_equal(4, mv[[0, 3]]) + assert_equal(6, mv[[1, 1]]) + assert_equal(10, mv[[2, 1]]) + + buf = [ 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16 ].pack("l!*") + shape = [2, 8] + strides = [4*sizeof(:long)*2, sizeof(:long)*2] + mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, shape, strides) + assert_equal(1, mv[[0, 0]]) + assert_equal(5, mv[[0, 2]]) + assert_equal(9, mv[[1, 0]]) + assert_equal(15, mv[[1, 3]]) + end +end