summaryrefslogtreecommitdiff
path: root/memory_view.c
diff options
context:
space:
mode:
Diffstat (limited to 'memory_view.c')
-rw-r--r--memory_view.c506
1 files changed, 506 insertions, 0 deletions
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 <mrkn@mrkn.jp>
+
+**********************************************************************/
+
+#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__");
+}