Permalink
Cannot retrieve contributors at this time
872 lines (751 sloc)
22.5 KB
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************************************************************** | |
memory_view.c - Memory View | |
Copyright (C) 2020 Kenta Murata <mrkn@mrkn.jp> | |
**********************************************************************/ | |
#include "internal.h" | |
#include "internal/hash.h" | |
#include "internal/variable.h" | |
#include "ruby/memory_view.h" | |
#include "ruby/util.h" | |
#include "vm_sync.h" | |
#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 | |
#define STRUCT_ALIGNOF(T, result) do { \ | |
(result) = RUBY_ALIGNOF(T); \ | |
} while(0) | |
// Exported Object Registry | |
static st_table *exported_object_table = NULL; | |
VALUE rb_memory_view_exported_object_registry = Qundef; | |
static int | |
exported_object_registry_mark_key_i(st_data_t key, st_data_t value, st_data_t data) | |
{ | |
rb_gc_mark(key); | |
return ST_CONTINUE; | |
} | |
static void | |
exported_object_registry_mark(void *ptr) | |
{ | |
// Don't use RB_VM_LOCK_ENTER here. It is unnecessary during GC. | |
st_foreach(exported_object_table, exported_object_registry_mark_key_i, 0); | |
} | |
static void | |
exported_object_registry_free(void *ptr) | |
{ | |
RB_VM_LOCK_ENTER(); | |
st_clear(exported_object_table); | |
st_free_table(exported_object_table); | |
exported_object_table = NULL; | |
RB_VM_LOCK_LEAVE(); | |
} | |
const rb_data_type_t rb_memory_view_exported_object_registry_data_type = { | |
"memory_view/exported_object_registry", | |
{ | |
exported_object_registry_mark, | |
exported_object_registry_free, | |
0, | |
}, | |
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | |
}; | |
static int | |
exported_object_add_ref(st_data_t *key, st_data_t *val, st_data_t arg, int existing) | |
{ | |
ASSERT_vm_locking(); | |
if (existing) { | |
*val += 1; | |
} | |
else { | |
*val = 1; | |
} | |
return ST_CONTINUE; | |
} | |
static int | |
exported_object_dec_ref(st_data_t *key, st_data_t *val, st_data_t arg, int existing) | |
{ | |
ASSERT_vm_locking(); | |
if (existing) { | |
*val -= 1; | |
if (*val == 0) { | |
return ST_DELETE; | |
} | |
} | |
return ST_CONTINUE; | |
} | |
static void | |
register_exported_object(VALUE obj) | |
{ | |
RB_VM_LOCK_ENTER(); | |
st_update(exported_object_table, (st_data_t)obj, exported_object_add_ref, 0); | |
RB_VM_LOCK_LEAVE(); | |
} | |
static void | |
unregister_exported_object(VALUE obj) | |
{ | |
RB_VM_LOCK_ENTER(); | |
if (exported_object_table) | |
st_update(exported_object_table, (st_data_t)obj, exported_object_dec_ref, 0); | |
RB_VM_LOCK_LEAVE(); | |
} | |
// MemoryView | |
static ID id_memory_view; | |
static const rb_data_type_t memory_view_entry_data_type = { | |
"memory_view/entry", | |
{ | |
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_lookup(klass, id_memory_view, Qnil); | |
if (! NIL_P(entry_obj)) { | |
rb_warning("Duplicated registration of memory view to %"PRIsVALUE, klass); | |
return false; | |
} | |
else { | |
entry_obj = TypedData_Wrap_Struct(0, &memory_view_entry_data_type, (void *)entry); | |
rb_ivar_set(klass, id_memory_view, entry_obj); | |
return true; | |
} | |
} | |
/* Examine whether the given memory view has row-major order strides. */ | |
bool | |
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 false; | |
n *= shape[i]; | |
} | |
return true; | |
} | |
/* Examine whether the given memory view has column-major order strides. */ | |
bool | |
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 false; | |
n *= shape[i]; | |
} | |
return true; | |
} | |
/* Initialize strides array to represent the specified contiguous array. */ | |
void | |
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) | |
{ | |
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 */ | |
bool | |
rb_memory_view_init_as_byte_array(rb_memory_view_t *view, VALUE obj, void *data, const ssize_t len, const bool readonly) | |
{ | |
view->obj = obj; | |
view->data = data; | |
view->byte_size = len; | |
view->readonly = readonly; | |
view->format = NULL; | |
view->item_size = 1; | |
view->item_desc.components = NULL; | |
view->item_desc.length = 0; | |
view->ndim = 1; | |
view->shape = NULL; | |
view->strides = NULL; | |
view->sub_offsets = NULL; | |
view->private_data = NULL; | |
return true; | |
} | |
#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, | |
size_t *n_members, const char **err) | |
{ | |
if (format == NULL) return 1; | |
VALUE error = Qnil; | |
ssize_t total = 0; | |
size_t len = 0; | |
bool alignment = false; | |
ssize_t max_alignment_size = 0; | |
const char *p = format; | |
if (*p == '|') { // alignment 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': | |
case 'v': | |
case 'V': | |
little_endian_p = true; | |
break; | |
case 'g': | |
case 'G': | |
case 'n': | |
case 'N': | |
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); | |
ssize_t 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 void | |
switch_endianness(uint8_t *buf, ssize_t len) | |
{ | |
RUBY_ASSERT(buf != NULL); | |
RUBY_ASSERT(len >= 0); | |
uint8_t *p = buf; | |
uint8_t *q = buf + len - 1; | |
while (q - p > 0) { | |
uint8_t t = *p; | |
*p = *q; | |
*q = t; | |
++p; | |
--q; | |
} | |
} | |
static inline VALUE | |
extract_item_member(const uint8_t *ptr, const rb_memory_view_item_component_t *member, const size_t i) | |
{ | |
RUBY_ASSERT(ptr != NULL); | |
RUBY_ASSERT(member != NULL); | |
RUBY_ASSERT(i < member->repeat); | |
#ifdef WORDS_BIGENDIAN | |
const bool native_endian_p = !member->little_endian_p; | |
#else | |
const bool native_endian_p = member->little_endian_p; | |
#endif | |
const uint8_t *p = ptr + member->offset + i * member->size; | |
if (member->format == 'c') { | |
return INT2FIX(*(char *)p); | |
} | |
else if (member->format == 'C') { | |
return INT2FIX(*(unsigned char *)p); | |
} | |
union { | |
short s; | |
unsigned short us; | |
int i; | |
unsigned int ui; | |
long l; | |
unsigned long ul; | |
LONG_LONG ll; | |
unsigned LONG_LONG ull; | |
int16_t i16; | |
uint16_t u16; | |
int32_t i32; | |
uint32_t u32; | |
int64_t i64; | |
uint64_t u64; | |
intptr_t iptr; | |
uintptr_t uptr; | |
float f; | |
double d; | |
} val; | |
if (!native_endian_p) { | |
MEMCPY(&val, p, uint8_t, member->size); | |
switch_endianness((uint8_t *)&val, member->size); | |
} | |
else { | |
MEMCPY(&val, p, uint8_t, member->size); | |
} | |
switch (member->format) { | |
case 's': | |
if (member->native_size_p) { | |
return INT2FIX(val.s); | |
} | |
else { | |
return INT2FIX(val.i16); | |
} | |
case 'S': | |
case 'n': | |
case 'v': | |
if (member->native_size_p) { | |
return UINT2NUM(val.us); | |
} | |
else { | |
return INT2FIX(val.u16); | |
} | |
case 'i': | |
return INT2NUM(val.i); | |
case 'I': | |
return UINT2NUM(val.ui); | |
case 'l': | |
if (member->native_size_p) { | |
return LONG2NUM(val.l); | |
} | |
else { | |
return LONG2NUM(val.i32); | |
} | |
case 'L': | |
case 'N': | |
case 'V': | |
if (member->native_size_p) { | |
return ULONG2NUM(val.ul); | |
} | |
else { | |
return ULONG2NUM(val.u32); | |
} | |
case 'f': | |
case 'e': | |
case 'g': | |
return DBL2NUM(val.f); | |
case 'q': | |
if (member->native_size_p) { | |
return LL2NUM(val.ll); | |
} | |
else { | |
#if SIZEOF_INT64_T == SIZEOF_LONG | |
return LONG2NUM(val.i64); | |
#else | |
return LL2NUM(val.i64); | |
#endif | |
} | |
case 'Q': | |
if (member->native_size_p) { | |
return ULL2NUM(val.ull); | |
} | |
else { | |
#if SIZEOF_UINT64_T == SIZEOF_LONG | |
return ULONG2NUM(val.u64); | |
#else | |
return ULL2NUM(val.u64); | |
#endif | |
} | |
case 'd': | |
case 'E': | |
case 'G': | |
return DBL2NUM(val.d); | |
case 'j': | |
return INTPTR2NUM(val.iptr); | |
case 'J': | |
return UINTPTR2NUM(val.uptr); | |
default: | |
UNREACHABLE_RETURN(Qnil); | |
} | |
} | |
/* Return a value of the extracted member. */ | |
VALUE | |
rb_memory_view_extract_item_member(const void *ptr, const rb_memory_view_item_component_t *member, const size_t i) | |
{ | |
if (ptr == NULL) return Qnil; | |
if (member == NULL) return Qnil; | |
if (i >= member->repeat) return Qnil; | |
return extract_item_member(ptr, member, i); | |
} | |
/* Return a value that consists of item members. | |
* When an item is a single member, the return value is a single value. | |
* When an item consists of multiple members, an array will be returned. */ | |
VALUE | |
rb_memory_view_extract_item_members(const void *ptr, const rb_memory_view_item_component_t *members, const size_t n_members) | |
{ | |
if (ptr == NULL) return Qnil; | |
if (members == NULL) return Qnil; | |
if (n_members == 0) return Qnil; | |
if (n_members == 1 && members[0].repeat == 1) { | |
return rb_memory_view_extract_item_member(ptr, members, 0); | |
} | |
size_t i, j; | |
VALUE item = rb_ary_new(); | |
for (i = 0; i < n_members; ++i) { | |
for (j = 0; j < members[i].repeat; ++j) { | |
VALUE v = extract_item_member(ptr, &members[i], j); | |
rb_ary_push(item, v); | |
} | |
} | |
return item; | |
} | |
void | |
rb_memory_view_prepare_item_desc(rb_memory_view_t *view) | |
{ | |
if (view->item_desc.components == NULL) { | |
const char *err; | |
rb_memory_view_item_component_t **p_components = | |
(rb_memory_view_item_component_t **)&view->item_desc.components; | |
ssize_t n = rb_memory_view_parse_item_format(view->format, p_components, &view->item_desc.length, &err); | |
if (n < 0) { | |
rb_raise(rb_eRuntimeError, | |
"Unable to parse item format at %"PRIdSIZE" in \"%s\"", | |
(err - view->format), view->format); | |
} | |
} | |
} | |
/* Return a value that consists of item members in the given memory view. */ | |
VALUE | |
rb_memory_view_get_item(rb_memory_view_t *view, const ssize_t *indices) | |
{ | |
void *ptr = rb_memory_view_get_item_pointer(view, indices); | |
if (view->format == NULL) { | |
return INT2FIX(*(uint8_t *)ptr); | |
} | |
if (view->item_desc.components == NULL) { | |
rb_memory_view_prepare_item_desc(view); | |
} | |
return rb_memory_view_extract_item_members(ptr, view->item_desc.components, view->item_desc.length); | |
} | |
static const rb_memory_view_entry_t * | |
lookup_memory_view_entry(VALUE klass) | |
{ | |
VALUE entry_obj = rb_ivar_lookup(klass, id_memory_view, Qnil); | |
while (NIL_P(entry_obj)) { | |
klass = rb_class_get_superclass(klass); | |
if (klass == rb_cBasicObject || klass == rb_cObject) | |
return NULL; | |
entry_obj = rb_ivar_lookup(klass, id_memory_view, Qnil); | |
} | |
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. */ | |
bool | |
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 false; | |
} | |
/* Obtain a memory view from obj, and substitute the information to view. */ | |
bool | |
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) { | |
if (!(*entry->available_p_func)(obj)) { | |
return false; | |
} | |
bool rv = (*entry->get_func)(obj, view, flags); | |
if (rv) { | |
view->_memory_view_entry = entry; | |
register_exported_object(view->obj); | |
} | |
return rv; | |
} | |
else | |
return false; | |
} | |
/* Release the memory view obtained from obj. */ | |
bool | |
rb_memory_view_release(rb_memory_view_t* view) | |
{ | |
const rb_memory_view_entry_t *entry = view->_memory_view_entry; | |
if (entry) { | |
bool rv = true; | |
if (entry->release_func) { | |
rv = (*entry->release_func)(view->obj, view); | |
} | |
if (rv) { | |
unregister_exported_object(view->obj); | |
view->obj = Qnil; | |
if (view->item_desc.components) { | |
xfree((void *)view->item_desc.components); | |
} | |
} | |
return rv; | |
} | |
else | |
return false; | |
} | |
void | |
Init_MemoryView(void) | |
{ | |
exported_object_table = rb_init_identtable(); | |
// exported_object_table is referred through rb_memory_view_exported_object_registry | |
// in -test-/memory_view extension. | |
VALUE obj = TypedData_Wrap_Struct( | |
0, &rb_memory_view_exported_object_registry_data_type, | |
exported_object_table); | |
rb_gc_register_mark_object(obj); | |
rb_memory_view_exported_object_registry = obj; | |
id_memory_view = rb_intern_const("__memory_view__"); | |
} |