1 /**********************************************************************
3 memory_view.c - Memory View
5 Copyright (C) 2020 Kenta Murata <mrkn@mrkn.jp>
7 **********************************************************************/
10 #include "internal/hash.h"
11 #include "internal/variable.h"
12 #include "ruby/memory_view.h"
13 #include "ruby/util.h"
16 #if SIZEOF_INTPTR_T == SIZEOF_LONG_LONG
17 # define INTPTR2NUM LL2NUM
18 # define UINTPTR2NUM ULL2NUM
19 #elif SIZEOF_INTPTR_T == SIZEOF_LONG
20 # define INTPTR2NUM LONG2NUM
21 # define UINTPTR2NUM ULONG2NUM
23 # define INTPTR2NUM INT2NUM
24 # define UINTPTR2NUM UINT2NUM
28 #define STRUCT_ALIGNOF(T, result) do { \
29 (result) = RUBY_ALIGNOF(T); \
32 // Exported Object Registry
34 static st_table
*exported_object_table
= NULL
;
35 VALUE rb_memory_view_exported_object_registry
= Qundef
;
38 exported_object_registry_mark_key_i(st_data_t key
, st_data_t value
, st_data_t data
)
45 exported_object_registry_mark(void *ptr
)
47 // Don't use RB_VM_LOCK_ENTER here. It is unnecessary during GC.
48 st_foreach(exported_object_table
, exported_object_registry_mark_key_i
, 0);
52 exported_object_registry_free(void *ptr
)
55 st_clear(exported_object_table
);
56 st_free_table(exported_object_table
);
57 exported_object_table
= NULL
;
61 const rb_data_type_t rb_memory_view_exported_object_registry_data_type
= {
62 "memory_view/exported_object_registry",
64 exported_object_registry_mark
,
65 exported_object_registry_free
,
68 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
72 exported_object_add_ref(st_data_t
*key
, st_data_t
*val
, st_data_t arg
, int existing
)
86 exported_object_dec_ref(st_data_t
*key
, st_data_t
*val
, st_data_t arg
, int existing
)
100 register_exported_object(VALUE obj
)
103 st_update(exported_object_table
, (st_data_t
)obj
, exported_object_add_ref
, 0);
108 unregister_exported_object(VALUE obj
)
111 if (exported_object_table
)
112 st_update(exported_object_table
, (st_data_t
)obj
, exported_object_dec_ref
, 0);
118 static ID id_memory_view
;
120 static const rb_data_type_t memory_view_entry_data_type
= {
127 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
130 /* Register memory view functions for the given class */
132 rb_memory_view_register(VALUE klass
, const rb_memory_view_entry_t
*entry
)
134 Check_Type(klass
, T_CLASS
);
135 VALUE entry_obj
= rb_ivar_lookup(klass
, id_memory_view
, Qnil
);
136 if (! NIL_P(entry_obj
)) {
137 rb_warning("Duplicated registration of memory view to %"PRIsVALUE
, klass
);
141 entry_obj
= TypedData_Wrap_Struct(0, &memory_view_entry_data_type
, (void *)entry
);
142 rb_ivar_set(klass
, id_memory_view
, entry_obj
);
147 /* Examine whether the given memory view has row-major order strides. */
149 rb_memory_view_is_row_major_contiguous(const rb_memory_view_t
*view
)
151 const ssize_t ndim
= view
->ndim
;
152 const ssize_t
*shape
= view
->shape
;
153 const ssize_t
*strides
= view
->strides
;
154 ssize_t n
= view
->item_size
;
156 for (i
= ndim
- 1; i
>= 0; --i
) {
157 if (strides
[i
] != n
) return false;
163 /* Examine whether the given memory view has column-major order strides. */
165 rb_memory_view_is_column_major_contiguous(const rb_memory_view_t
*view
)
167 const ssize_t ndim
= view
->ndim
;
168 const ssize_t
*shape
= view
->shape
;
169 const ssize_t
*strides
= view
->strides
;
170 ssize_t n
= view
->item_size
;
172 for (i
= 0; i
< ndim
; ++i
) {
173 if (strides
[i
] != n
) return false;
179 /* Initialize strides array to represent the specified contiguous array. */
181 rb_memory_view_fill_contiguous_strides(const ssize_t ndim
, const ssize_t item_size
, const ssize_t
*const shape
, const bool row_major_p
, ssize_t
*const strides
)
183 ssize_t i
, n
= item_size
;
185 for (i
= ndim
- 1; i
>= 0; --i
) {
190 else { // column-major
191 for (i
= 0; i
< ndim
; ++i
) {
198 /* Initialize view to expose a simple byte array */
200 rb_memory_view_init_as_byte_array(rb_memory_view_t
*view
, VALUE obj
, void *data
, const ssize_t len
, const bool readonly
)
204 view
->byte_size
= len
;
205 view
->readonly
= readonly
;
208 view
->item_desc
.components
= NULL
;
209 view
->item_desc
.length
= 0;
212 view
->strides
= NULL
;
213 view
->sub_offsets
= NULL
;
214 view
->private_data
= NULL
;
219 #ifdef HAVE_TRUE_LONG_LONG
220 static const char native_types
[] = "sSiIlLqQjJ";
222 static const char native_types
[] = "sSiIlLjJ";
224 static const char endianness_types
[] = "sSiIlLqQjJ";
233 get_format_size(const char *format
, bool *native_p
, ssize_t
*alignment
, endianness_t
*endianness
, ssize_t
*count
, const char **next_format
, VALUE
*error
)
235 RUBY_ASSERT(format
!= NULL
);
236 RUBY_ASSERT(native_p
!= NULL
);
237 RUBY_ASSERT(endianness
!= NULL
);
238 RUBY_ASSERT(count
!= NULL
);
239 RUBY_ASSERT(next_format
!= NULL
);
242 *endianness
= ENDIANNESS_NATIVE
;
245 const int type_char
= *format
;
252 if (strchr(native_types
, type_char
)) {
258 *error
= rb_exc_new_str(rb_eArgError
,
259 rb_sprintf("Unable to specify native size for '%c'", type_char
));
267 if (!strchr(endianness_types
, type_char
)) {
269 *error
= rb_exc_new_str(rb_eArgError
,
270 rb_sprintf("Unable to specify endianness for '%c'", type_char
));
274 if (*endianness
!= ENDIANNESS_NATIVE
) {
275 *error
= rb_exc_new_cstr(rb_eArgError
, "Unable to use both '<' and '>' multiple times");
278 *endianness
= (format
[i
] == '<') ? ENDIANNESS_LITTLE
: ENDIANNESS_BIG
;
291 if ('0' <= ch
&& ch
<= '9') {
293 while ('0' <= (ch
= format
[i
]) && ch
<= '9') {
294 n
= 10*n
+ ruby_digit36_to_number_table
[ch
];
300 *next_format
= &format
[i
];
306 case 'c': // signed char
307 case 'C': // unsigned char
310 case 's': // s for int16_t, s! for signed short
311 case 'S': // S for uint16_t, S! for unsigned short
313 STRUCT_ALIGNOF(short, *alignment
);
314 return sizeof(short);
318 case 'n': // n for big-endian 16bit unsigned integer
319 case 'v': // v for little-endian 16bit unsigned integer
320 STRUCT_ALIGNOF(int16_t, *alignment
);
323 case 'i': // i and i! for signed int
324 case 'I': // I and I! for unsigned int
325 STRUCT_ALIGNOF(int, *alignment
);
328 case 'l': // l for int32_t, l! for signed long
329 case 'L': // L for uint32_t, L! for unsigned long
331 STRUCT_ALIGNOF(long, *alignment
);
336 case 'N': // N for big-endian 32bit unsigned integer
337 case 'V': // V for little-endian 32bit unsigned integer
338 STRUCT_ALIGNOF(int32_t, *alignment
);
341 case 'f': // f for native float
342 case 'e': // e for little-endian float
343 case 'g': // g for big-endian float
344 STRUCT_ALIGNOF(float, *alignment
);
345 return sizeof(float);
347 case 'q': // q for int64_t, q! for signed long long
348 case 'Q': // Q for uint64_t, Q! for unsigned long long
350 STRUCT_ALIGNOF(LONG_LONG
, *alignment
);
351 return sizeof(LONG_LONG
);
353 STRUCT_ALIGNOF(int64_t, *alignment
);
356 case 'd': // d for native double
357 case 'E': // E for little-endian double
358 case 'G': // G for big-endian double
359 STRUCT_ALIGNOF(double, *alignment
);
360 return sizeof(double);
362 case 'j': // j for intptr_t
363 case 'J': // J for uintptr_t
364 STRUCT_ALIGNOF(intptr_t, *alignment
);
365 return sizeof(intptr_t);
370 *error
= rb_exc_new_str(rb_eArgError
, rb_sprintf("Invalid type character '%c'", type_char
));
376 static inline ssize_t
377 calculate_padding(ssize_t total
, ssize_t alignment_size
)
379 if (alignment_size
> 1) {
380 ssize_t res
= total
% alignment_size
;
382 return alignment_size
- res
;
389 rb_memory_view_parse_item_format(const char *format
,
390 rb_memory_view_item_component_t
**members
,
391 size_t *n_members
, const char **err
)
393 if (format
== NULL
) return 1;
398 bool alignment
= false;
399 ssize_t max_alignment_size
= 0;
401 const char *p
= format
;
402 if (*p
== '|') { // alignment specifier
412 while (ISSPACE(*p
)) ++p
;
416 bool native_size_p
= false;
417 ssize_t alignment_size
= 0;
418 endianness_t endianness
= ENDIANNESS_NATIVE
;
420 const ssize_t size
= get_format_size(p
, &native_size_p
, &alignment_size
, &endianness
, &count
, &p
, &error
);
425 if (max_alignment_size
< alignment_size
) {
426 max_alignment_size
= alignment_size
;
429 const ssize_t padding
= alignment
? calculate_padding(total
, alignment_size
) : 0;
430 total
+= padding
+ size
* count
;
437 // adjust total size with the alignment size of the largest element
438 if (alignment
&& max_alignment_size
> 0) {
439 const ssize_t padding
= calculate_padding(total
, max_alignment_size
);
443 if (members
&& n_members
) {
444 rb_memory_view_item_component_t
*buf
= ALLOC_N(rb_memory_view_item_component_t
, len
);
446 ssize_t i
= 0, offset
= 0;
447 const char *p
= format
;
449 const int type_char
= *p
;
452 ssize_t alignment_size
= 0;
453 endianness_t endianness
= ENDIANNESS_NATIVE
;
455 const ssize_t size
= get_format_size(p
, &native_size_p
, &alignment_size
, &endianness
, &count
, &p
, NULL
);
457 const ssize_t padding
= alignment
? calculate_padding(offset
, alignment_size
) : 0;
460 if (type_char
!= 'x') {
461 #ifdef WORDS_BIGENDIAN
462 bool little_endian_p
= (endianness
== ENDIANNESS_LITTLE
);
464 bool little_endian_p
= (endianness
!= ENDIANNESS_BIG
);
472 little_endian_p
= true;
478 little_endian_p
= false;
484 buf
[i
++] = (rb_memory_view_item_component_t
){
486 .native_size_p
= native_size_p
,
487 .little_endian_p
= little_endian_p
,
494 offset
+= size
* count
;
504 /* Return the item size. */
506 rb_memory_view_item_size_from_format(const char *format
, const char **err
)
508 return rb_memory_view_parse_item_format(format
, NULL
, NULL
, err
);
511 /* Return the pointer to the item located by the given indices. */
513 rb_memory_view_get_item_pointer(rb_memory_view_t
*view
, const ssize_t
*indices
)
515 uint8_t *ptr
= view
->data
;
517 if (view
->ndim
== 1) {
518 ssize_t stride
= view
->strides
!= NULL
? view
->strides
[0] : view
->item_size
;
519 return ptr
+ indices
[0] * stride
;
522 assert(view
->shape
!= NULL
);
525 if (view
->strides
== NULL
) {
526 // row-major contiguous array
527 ssize_t stride
= view
->item_size
;
528 for (i
= 0; i
< view
->ndim
; ++i
) {
529 stride
*= view
->shape
[i
];
531 for (i
= 0; i
< view
->ndim
; ++i
) {
532 stride
/= view
->shape
[i
];
533 ptr
+= indices
[i
] * stride
;
536 else if (view
->sub_offsets
== NULL
) {
537 // flat strided array
538 for (i
= 0; i
< view
->ndim
; ++i
) {
539 ptr
+= indices
[i
] * view
->strides
[i
];
543 // indirect strided array
544 for (i
= 0; i
< view
->ndim
; ++i
) {
545 ptr
+= indices
[i
] * view
->strides
[i
];
546 if (view
->sub_offsets
[i
] >= 0) {
547 ptr
= *(uint8_t **)ptr
+ view
->sub_offsets
[i
];
556 switch_endianness(uint8_t *buf
, ssize_t len
)
558 RUBY_ASSERT(buf
!= NULL
);
559 RUBY_ASSERT(len
>= 0);
562 uint8_t *q
= buf
+ len
- 1;
574 extract_item_member(const uint8_t *ptr
, const rb_memory_view_item_component_t
*member
, const size_t i
)
576 RUBY_ASSERT(ptr
!= NULL
);
577 RUBY_ASSERT(member
!= NULL
);
578 RUBY_ASSERT(i
< member
->repeat
);
580 #ifdef WORDS_BIGENDIAN
581 const bool native_endian_p
= !member
->little_endian_p
;
583 const bool native_endian_p
= member
->little_endian_p
;
586 const uint8_t *p
= ptr
+ member
->offset
+ i
* member
->size
;
588 if (member
->format
== 'c') {
589 return INT2FIX(*(char *)p
);
591 else if (member
->format
== 'C') {
592 return INT2FIX(*(unsigned char *)p
);
603 unsigned LONG_LONG ull
;
616 if (!native_endian_p
) {
617 MEMCPY(&val
, p
, uint8_t, member
->size
);
618 switch_endianness((uint8_t *)&val
, member
->size
);
621 MEMCPY(&val
, p
, uint8_t, member
->size
);
624 switch (member
->format
) {
626 if (member
->native_size_p
) {
627 return INT2FIX(val
.s
);
630 return INT2FIX(val
.i16
);
636 if (member
->native_size_p
) {
637 return UINT2NUM(val
.us
);
640 return INT2FIX(val
.u16
);
644 return INT2NUM(val
.i
);
647 return UINT2NUM(val
.ui
);
650 if (member
->native_size_p
) {
651 return LONG2NUM(val
.l
);
654 return LONG2NUM(val
.i32
);
660 if (member
->native_size_p
) {
661 return ULONG2NUM(val
.ul
);
664 return ULONG2NUM(val
.u32
);
670 return DBL2NUM(val
.f
);
673 if (member
->native_size_p
) {
674 return LL2NUM(val
.ll
);
677 #if SIZEOF_INT64_T == SIZEOF_LONG
678 return LONG2NUM(val
.i64
);
680 return LL2NUM(val
.i64
);
685 if (member
->native_size_p
) {
686 return ULL2NUM(val
.ull
);
689 #if SIZEOF_UINT64_T == SIZEOF_LONG
690 return ULONG2NUM(val
.u64
);
692 return ULL2NUM(val
.u64
);
699 return DBL2NUM(val
.d
);
702 return INTPTR2NUM(val
.iptr
);
705 return UINTPTR2NUM(val
.uptr
);
708 UNREACHABLE_RETURN(Qnil
);
712 /* Return a value of the extracted member. */
714 rb_memory_view_extract_item_member(const void *ptr
, const rb_memory_view_item_component_t
*member
, const size_t i
)
716 if (ptr
== NULL
) return Qnil
;
717 if (member
== NULL
) return Qnil
;
718 if (i
>= member
->repeat
) return Qnil
;
720 return extract_item_member(ptr
, member
, i
);
723 /* Return a value that consists of item members.
724 * When an item is a single member, the return value is a single value.
725 * When an item consists of multiple members, an array will be returned. */
727 rb_memory_view_extract_item_members(const void *ptr
, const rb_memory_view_item_component_t
*members
, const size_t n_members
)
729 if (ptr
== NULL
) return Qnil
;
730 if (members
== NULL
) return Qnil
;
731 if (n_members
== 0) return Qnil
;
733 if (n_members
== 1 && members
[0].repeat
== 1) {
734 return rb_memory_view_extract_item_member(ptr
, members
, 0);
738 VALUE item
= rb_ary_new();
739 for (i
= 0; i
< n_members
; ++i
) {
740 for (j
= 0; j
< members
[i
].repeat
; ++j
) {
741 VALUE v
= extract_item_member(ptr
, &members
[i
], j
);
742 rb_ary_push(item
, v
);
750 rb_memory_view_prepare_item_desc(rb_memory_view_t
*view
)
752 if (view
->item_desc
.components
== NULL
) {
754 rb_memory_view_item_component_t
**p_components
=
755 (rb_memory_view_item_component_t
**)&view
->item_desc
.components
;
756 ssize_t n
= rb_memory_view_parse_item_format(view
->format
, p_components
, &view
->item_desc
.length
, &err
);
758 rb_raise(rb_eRuntimeError
,
759 "Unable to parse item format at %"PRIdSIZE
" in \"%s\"",
760 (err
- view
->format
), view
->format
);
765 /* Return a value that consists of item members in the given memory view. */
767 rb_memory_view_get_item(rb_memory_view_t
*view
, const ssize_t
*indices
)
769 void *ptr
= rb_memory_view_get_item_pointer(view
, indices
);
771 if (view
->format
== NULL
) {
772 return INT2FIX(*(uint8_t *)ptr
);
775 if (view
->item_desc
.components
== NULL
) {
776 rb_memory_view_prepare_item_desc(view
);
779 return rb_memory_view_extract_item_members(ptr
, view
->item_desc
.components
, view
->item_desc
.length
);
782 static const rb_memory_view_entry_t
*
783 lookup_memory_view_entry(VALUE klass
)
785 VALUE entry_obj
= rb_ivar_lookup(klass
, id_memory_view
, Qnil
);
786 while (NIL_P(entry_obj
)) {
787 klass
= rb_class_get_superclass(klass
);
789 if (klass
== rb_cBasicObject
|| klass
== rb_cObject
)
792 entry_obj
= rb_ivar_lookup(klass
, id_memory_view
, Qnil
);
795 if (! rb_typeddata_is_kind_of(entry_obj
, &memory_view_entry_data_type
))
798 return (const rb_memory_view_entry_t
*)RTYPEDDATA_DATA(entry_obj
);
801 /* Examine whether the given object supports memory view. */
803 rb_memory_view_available_p(VALUE obj
)
805 VALUE klass
= CLASS_OF(obj
);
806 const rb_memory_view_entry_t
*entry
= lookup_memory_view_entry(klass
);
808 return (* entry
->available_p_func
)(obj
);
813 /* Obtain a memory view from obj, and substitute the information to view. */
815 rb_memory_view_get(VALUE obj
, rb_memory_view_t
* view
, int flags
)
817 VALUE klass
= CLASS_OF(obj
);
818 const rb_memory_view_entry_t
*entry
= lookup_memory_view_entry(klass
);
820 if (!(*entry
->available_p_func
)(obj
)) {
824 bool rv
= (*entry
->get_func
)(obj
, view
, flags
);
826 view
->_memory_view_entry
= entry
;
827 register_exported_object(view
->obj
);
835 /* Release the memory view obtained from obj. */
837 rb_memory_view_release(rb_memory_view_t
* view
)
839 const rb_memory_view_entry_t
*entry
= view
->_memory_view_entry
;
842 if (entry
->release_func
) {
843 rv
= (*entry
->release_func
)(view
->obj
, view
);
846 unregister_exported_object(view
->obj
);
848 if (view
->item_desc
.components
) {
849 xfree((void *)view
->item_desc
.components
);
859 Init_MemoryView(void)
861 exported_object_table
= rb_init_identtable();
863 // exported_object_table is referred through rb_memory_view_exported_object_registry
864 // in -test-/memory_view extension.
865 VALUE obj
= TypedData_Wrap_Struct(
866 0, &rb_memory_view_exported_object_registry_data_type
,
867 exported_object_table
);
868 rb_gc_register_mark_object(obj
);
869 rb_memory_view_exported_object_registry
= obj
;
871 id_memory_view
= rb_intern_const("__memory_view__");