summaryrefslogtreecommitdiff
path: root/io_buffer.c
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@oriontransfer.co.nz>2021-07-02 22:41:16 +1200
committerSamuel Williams <samuel.williams@oriontransfer.co.nz>2021-11-10 19:21:05 +1300
commit4b8903421828cb9d4de139180563ae8d8f04e1ab (patch)
tree21a0d02ba22afad6a4ce1c042acf6d74767dc5a2 /io_buffer.c
parent56b90cf94465ce347a3d9a779363c78ce3deb180 (diff)
IO::Buffer for scheduler interface.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/4621
Diffstat (limited to 'io_buffer.c')
-rw-r--r--io_buffer.c1024
1 files changed, 1024 insertions, 0 deletions
diff --git a/io_buffer.c b/io_buffer.c
new file mode 100644
index 0000000000..3455713f8d
--- /dev/null
+++ b/io_buffer.c
@@ -0,0 +1,1024 @@
+/**********************************************************************
+
+ io_buffer.c
+
+ Copyright (C) 2021 Samuel Grant Dawson Williams
+
+**********************************************************************/
+
+#include "ruby/io.h"
+#include "ruby/io/buffer.h"
+
+#include "internal/string.h"
+#include "internal/bits.h"
+
+VALUE rb_cIOBuffer;
+size_t RUBY_IO_BUFFER_PAGE_SIZE;
+
+#ifdef _WIN32
+#else
+#include <unistd.h>
+#include <sys/mman.h>
+#endif
+
+struct rb_io_buffer {
+ void *base;
+ size_t size;
+ enum rb_io_buffer_flags flags;
+
+#if defined(_WIN32)
+ HANDLE mapping;
+#endif
+
+ VALUE source;
+};
+
+static inline void* io_buffer_map_memory(size_t size)
+{
+#if defined(_WIN32)
+ void * base = VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE);
+
+ if (!base) {
+ rb_sys_fail("io_buffer_map_memory:VirtualAlloc");
+ }
+#else
+ void * base = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
+
+ if (base == MAP_FAILED) {
+ rb_sys_fail("io_buffer_map_memory:mmap");
+ }
+#endif
+
+ return base;
+}
+
+static
+void io_buffer_map_file(struct rb_io_buffer *data, int descriptor, size_t size, off_t offset, enum rb_io_buffer_flags flags)
+{
+#if defined(_WIN32)
+ HANDLE file = (HANDLE)_get_osfhandle(descriptor);
+ if (!file) rb_sys_fail("io_buffer_map_descriptor:_get_osfhandle");
+
+ DWORD protect = PAGE_READONLY, access = FILE_MAP_READ;
+
+ if (flags & RB_IO_BUFFER_IMMUTABLE) {
+ data->flags |= RB_IO_BUFFER_IMMUTABLE;
+ } else {
+ protect = PAGE_READWRITE;
+ access = FILE_MAP_WRITE;
+ }
+
+ HANDLE mapping = CreateFileMapping(file, NULL, protect, 0, 0, NULL);
+ if (!mapping) rb_sys_fail("io_buffer_map_descriptor:CreateFileMapping");
+
+ if (flags & RB_IO_BUFFER_PRIVATE) {
+ access |= FILE_MAP_COPY;
+ data->flags |= RB_IO_BUFFER_PRIVATE;
+ }
+
+ void *base = MapViewOfFile(mapping, access, (DWORD)(offset >> 32), (DWORD)(offset & 0xFFFFFFFF), size);
+
+ if (!base) {
+ CloseHandle(mapping);
+ rb_sys_fail("io_buffer_map_file:MapViewOfFile");
+ }
+
+ data->mapping = mapping;
+#else
+ int protect = PROT_READ, access = 0;
+
+ if (flags & RB_IO_BUFFER_IMMUTABLE) {
+ data->flags |= RB_IO_BUFFER_IMMUTABLE;
+ } else {
+ protect |= PROT_WRITE;
+ }
+
+ if (flags & RB_IO_BUFFER_PRIVATE) {
+ data->flags |= RB_IO_BUFFER_PRIVATE;
+ } else {
+ access |= MAP_SHARED;
+ }
+
+ void *base = mmap(NULL, size, protect, access, descriptor, offset);
+
+ if (base == MAP_FAILED) {
+ rb_sys_fail("io_buffer_map_file:mmap");
+ }
+#endif
+
+ data->base = base;
+ data->size = size;
+
+ data->flags |= RB_IO_BUFFER_MAPPED;
+}
+
+static inline void io_buffer_unmap(void* base, size_t size)
+{
+#ifdef _WIN32
+ VirtualFree(base, 0, MEM_RELEASE);
+#else
+ munmap(base, size);
+#endif
+}
+
+static void io_buffer_initialize(struct rb_io_buffer *data, void *base, size_t size, enum rb_io_buffer_flags flags, VALUE source)
+{
+ data->flags = flags;
+ data->size = size;
+
+ if (base) {
+ data->base = base;
+ } else {
+ if (data->flags & RB_IO_BUFFER_INTERNAL) {
+ data->base = calloc(data->size, 1);
+ } else if (data->flags & RB_IO_BUFFER_MAPPED) {
+ data->base = io_buffer_map_memory(data->size);
+ }
+ }
+
+ if (!data->base) {
+ rb_raise(rb_eRuntimeError, "Could not allocate buffer!");
+ }
+
+ data->source = source;
+}
+
+static int io_buffer_free(struct rb_io_buffer *data)
+{
+ if (data->base) {
+ if (data->flags & RB_IO_BUFFER_INTERNAL) {
+ free(data->base);
+ }
+
+ if (data->flags & RB_IO_BUFFER_MAPPED) {
+ io_buffer_unmap(data->base, data->size);
+ }
+
+ data->base = NULL;
+
+#if defined(_WIN32)
+ if (data->mapping) {
+ CloseHandle(data->mapping);
+ data->mapping = NULL;
+ }
+#endif
+
+ return 1;
+ }
+
+ return 0;
+}
+
+void rb_io_buffer_type_mark(void *_data)
+{
+ struct rb_io_buffer *data = _data;
+ rb_gc_mark(data->source);
+}
+
+void rb_io_buffer_type_free(void *_data)
+{
+ struct rb_io_buffer *data = _data;
+
+ io_buffer_free(data);
+
+ free(data);
+}
+
+size_t rb_io_buffer_type_size(const void *_data)
+{
+ const struct rb_io_buffer *data = _data;
+ size_t total = sizeof(struct rb_io_buffer);
+
+ if (data->flags) {
+ total += data->size;
+ }
+
+ return total;
+}
+
+static const rb_data_type_t rb_io_buffer_type = {
+ .wrap_struct_name = "IO::Buffer",
+ .function = {
+ .dmark = rb_io_buffer_type_mark,
+ .dfree = rb_io_buffer_type_free,
+ .dsize = rb_io_buffer_type_size,
+ },
+ .data = NULL,
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+VALUE rb_io_buffer_type_allocate(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ VALUE instance = TypedData_Make_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ data->base = NULL;
+ data->size = 0;
+ data->flags = 0;
+ data->source = Qnil;
+
+ return instance;
+}
+
+VALUE rb_io_buffer_new(void *base, size_t size, enum rb_io_buffer_flags flags)
+{
+ VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer);
+
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ io_buffer_initialize(data, base, size, 0, Qnil);
+
+ return instance;
+}
+
+VALUE rb_io_buffer_map(VALUE io, size_t size, off_t offset, enum rb_io_buffer_flags flags)
+{
+ VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer);
+
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ int descriptor = rb_io_descriptor(io);
+
+ io_buffer_map_file(data, descriptor, size, offset, flags);
+
+ return instance;
+}
+
+static
+VALUE io_buffer_map(int argc, VALUE *argv, VALUE klass)
+{
+ if (argc < 1 || argc > 4) {
+ rb_error_arity(argc, 2, 4);
+ }
+
+ VALUE io = argv[0];
+
+ size_t size;
+ if (argc >= 2) {
+ size = RB_NUM2SIZE(argv[1]);
+ } else {
+ size = rb_file_size(io);
+ }
+
+ off_t offset = 0;
+ if (argc >= 3) {
+ offset = NUM2OFFT(argv[2]);
+ }
+
+ enum rb_io_buffer_flags flags = RB_IO_BUFFER_IMMUTABLE;
+ if (argc >= 4) {
+ flags = RB_NUM2UINT(argv[3]);
+ }
+
+ return rb_io_buffer_map(io, size, offset, flags);
+}
+
+VALUE rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self)
+{
+ if (argc < 1 || argc > 2) {
+ rb_error_arity(argc, 1, 2);
+ }
+
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ size_t size = RB_NUM2SIZE(argv[0]);
+
+ enum rb_io_buffer_flags flags = 0;
+ if (argc >= 2) {
+ flags = RB_NUM2UINT(argv[1]);
+ } else {
+ if (size > RUBY_IO_BUFFER_PAGE_SIZE) {
+ flags |= RB_IO_BUFFER_MAPPED;
+ } else {
+ flags |= RB_IO_BUFFER_INTERNAL;
+ }
+ }
+
+ io_buffer_initialize(data, NULL, size, flags, Qnil);
+
+ return self;
+}
+
+static int io_buffer_validate_slice(VALUE source, void *base, size_t size)
+{
+ const void *source_base = NULL;
+ size_t source_size = 0;
+
+ if (RB_TYPE_P(source, T_STRING)) {
+ RSTRING_GETMEM(source, source_base, source_size);
+ } else {
+ rb_io_buffer_get_immutable(source, &source_base, &source_size);
+ }
+
+ // Source is invalid:
+ if (source_base == NULL) return 0;
+
+ // Base is out of range:
+ if (base < source_base) return 0;
+
+ const void *source_end = (char*)source_base + source_size;
+ const void *end = (char*)base + size;
+
+ // End is out of range:
+ if (end > source_end) return 0;
+
+ // It seems okay:
+ return 1;
+}
+
+static int io_buffer_validate(struct rb_io_buffer *data)
+{
+ if (data->source != Qnil) {
+ // Only slices incur this overhead, unfortunately... better safe than sorry!
+ return io_buffer_validate_slice(data->source, data->base, data->size);
+ } else {
+ return 1;
+ }
+}
+
+VALUE rb_io_buffer_to_s(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ VALUE result = rb_str_new_cstr("#<");
+
+ rb_str_append(result, rb_class_name(CLASS_OF(self)));
+ rb_str_catf(result, " %p+%ld", data->base, data->size);
+
+ if (data->flags & RB_IO_BUFFER_INTERNAL) {
+ rb_str_cat2(result, " INTERNAL");
+ }
+
+ if (data->flags & RB_IO_BUFFER_MAPPED) {
+ rb_str_cat2(result, " MAPPED");
+ }
+
+ if (data->flags & RB_IO_BUFFER_LOCKED) {
+ rb_str_cat2(result, " LOCKED");
+ }
+
+ if (data->flags & RB_IO_BUFFER_IMMUTABLE) {
+ rb_str_cat2(result, " IMMUTABLE");
+ }
+
+ if (data->source != Qnil) {
+ rb_str_cat2(result, " SLICE");
+ }
+
+ if (!io_buffer_validate(data)) {
+ rb_str_cat2(result, " INVALID");
+ }
+
+ return rb_str_cat2(result, ">");
+
+}
+
+static VALUE io_buffer_hexdump(VALUE string, size_t width, char *base, size_t size)
+{
+ char *text = alloca(width+1);
+ text[width] = '\0';
+
+ for (size_t offset = 0; offset < size; offset += width) {
+ memset(text, '\0', width);
+ rb_str_catf(string, "\n0x%08zx ", offset);
+
+ for (size_t i = 0; i < width; i += 1) {
+ if (offset+i < size) {
+ unsigned char value = ((unsigned char*)base)[offset+i];
+
+ if (value < 127 && isprint(value)) {
+ text[i] = (char)value;
+ } else {
+ text[i] = '.';
+ }
+
+ rb_str_catf(string, " %02x", value);
+ } else {
+ rb_str_cat2(string, " ");
+ }
+ }
+
+ rb_str_catf(string, " %s", text);
+ }
+
+ rb_str_cat2(string, "\n");
+
+ return string;
+}
+
+VALUE rb_io_buffer_inspect(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ VALUE result = rb_io_buffer_to_s(self);
+
+ if (io_buffer_validate(data)) {
+ io_buffer_hexdump(result, 16, data->base, data->size);
+ }
+
+ return result;
+}
+
+VALUE rb_io_buffer_size(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ return SIZET2NUM(data->size);
+}
+
+static VALUE rb_io_buffer_external_p(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ return data->flags & (RB_IO_BUFFER_INTERNAL | RB_IO_BUFFER_MAPPED) ? Qfalse : Qtrue;
+}
+
+static VALUE rb_io_buffer_internal_p(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ return data->flags & RB_IO_BUFFER_INTERNAL ? Qtrue : Qfalse;
+}
+
+static VALUE rb_io_buffer_mapped_p(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ return data->flags & RB_IO_BUFFER_MAPPED ? Qtrue : Qfalse;
+}
+
+static VALUE rb_io_buffer_locked_p(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ return data->flags & RB_IO_BUFFER_LOCKED ? Qtrue : Qfalse;
+}
+
+static VALUE rb_io_buffer_immutable_p(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ return data->flags & RB_IO_BUFFER_IMMUTABLE ? Qtrue : Qfalse;
+}
+
+VALUE rb_io_buffer_lock(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ if (data->flags & RB_IO_BUFFER_LOCKED) {
+ rb_raise(rb_eRuntimeError, "Buffer already locked!");
+ }
+
+ data->flags |= RB_IO_BUFFER_LOCKED;
+
+ return self;
+}
+
+VALUE rb_io_buffer_unlock(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ if (!(data->flags & RB_IO_BUFFER_LOCKED)) {
+ rb_raise(rb_eRuntimeError, "Buffer not locked!");
+ }
+
+ data->flags &= ~RB_IO_BUFFER_LOCKED;
+
+ return self;
+}
+
+VALUE rb_io_buffer_locked(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ if (data->flags & RB_IO_BUFFER_LOCKED) {
+ rb_raise(rb_eRuntimeError, "Buffer already locked!");
+ }
+
+ data->flags |= RB_IO_BUFFER_LOCKED;
+
+ VALUE result = rb_yield(self);
+
+ data->flags &= ~RB_IO_BUFFER_LOCKED;
+
+ return result;
+}
+
+VALUE rb_io_buffer_free(VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ if (data->flags & RB_IO_BUFFER_LOCKED) {
+ rb_raise(rb_eRuntimeError, "Buffer is locked!");
+ }
+
+ io_buffer_free(data);
+
+ return self;
+}
+
+static inline void rb_io_buffer_validate(struct rb_io_buffer *data, size_t offset, size_t length)
+{
+ if (offset + length > data->size) {
+ rb_raise(rb_eRuntimeError, "Specified offset + length exceeds source size!");
+ }
+}
+
+VALUE rb_io_buffer_slice(VALUE self, VALUE _offset, VALUE _length)
+{
+ // TODO fail on negative offets/lengths.
+ size_t offset = NUM2SIZET(_offset);
+ size_t length = NUM2SIZET(_length);
+
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ rb_io_buffer_validate(data, offset, length);
+
+ VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self));
+ struct rb_io_buffer *slice = NULL;
+ TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, slice);
+
+ slice->base = (char*)data->base + offset;
+ slice->size = length;
+
+ // The source should be the root buffer:
+ if (data->source != Qnil)
+ slice->source = data->source;
+ else
+ slice->source = self;
+
+ return instance;
+}
+
+VALUE rb_io_buffer_to_str(int argc, VALUE *argv, VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ size_t offset = 0;
+ size_t length = data->size;
+
+ if (argc == 0) {
+ // Defaults.
+ } else if (argc == 1) {
+ offset = NUM2SIZET(argv[0]);
+ length = data->size - offset;
+ } else if (argc == 2) {
+ offset = NUM2SIZET(argv[0]);
+ length = NUM2SIZET(argv[1]);
+ } else {
+ rb_error_arity(argc, 0, 2);
+ }
+
+ rb_io_buffer_validate(data, offset, length);
+
+ return rb_usascii_str_new((char*)data->base + offset, length);
+}
+
+void rb_io_buffer_get_mutable(VALUE self, void **base, size_t *size)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ if (data->flags & RB_IO_BUFFER_IMMUTABLE) {
+ rb_raise(rb_eRuntimeError, "Buffer is immutable!");
+ }
+
+ if (!io_buffer_validate(data)) {
+ rb_raise(rb_eRuntimeError, "Buffer has been invalidated!");
+ }
+
+ if (data && data->base) {
+ *base = data->base;
+ *size = data->size;
+
+ return;
+ }
+
+ rb_raise(rb_eRuntimeError, "Buffer is not allocated!");
+}
+
+void rb_io_buffer_get_immutable(VALUE self, const void **base, size_t *size)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ if (!io_buffer_validate(data)) {
+ rb_raise(rb_eRuntimeError, "Buffer has been invalidated!");
+ }
+
+ if (data && data->base) {
+ *base = data->base;
+ *size = data->size;
+
+ return;
+ }
+
+ rb_raise(rb_eRuntimeError, "Buffer is not allocated!");
+}
+
+size_t rb_io_buffer_copy(VALUE self, VALUE source, size_t offset)
+{
+ const void *source_base = NULL;
+ size_t source_size = 0;
+
+ if (RB_TYPE_P(source, T_STRING)) {
+ RSTRING_GETMEM(source, source_base, source_size);
+ } else {
+ rb_io_buffer_get_immutable(source, &source_base, &source_size);
+ }
+
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ rb_io_buffer_validate(data, offset, source_size);
+
+ memcpy((char*)data->base + offset, source_base, source_size);
+
+ return source_size;
+}
+
+static VALUE io_buffer_copy(VALUE self, VALUE source, VALUE offset)
+{
+ size_t size = rb_io_buffer_copy(self, source, NUM2SIZET(offset));
+
+ return RB_SIZE2NUM(size);
+}
+
+static int io_buffer_external_p(enum rb_io_buffer_flags flags)
+{
+ return !(flags & (RB_IO_BUFFER_INTERNAL | RB_IO_BUFFER_MAPPED));
+}
+
+void rb_io_buffer_resize(VALUE self, size_t size, size_t preserve)
+{
+ struct rb_io_buffer *data = NULL, updated;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ if (preserve > data->size) {
+ rb_raise(rb_eRuntimeError, "Preservation size bigger than buffer size!");
+ }
+
+ if (preserve > size) {
+ rb_raise(rb_eRuntimeError, "Preservation size bigger than destination size!");
+ }
+
+ if (data->flags & RB_IO_BUFFER_LOCKED) {
+ rb_raise(rb_eRuntimeError, "Cannot resize locked buffer!");
+ }
+
+ // By virtue of this passing, we don't need to do any further validation on the buffer:
+ if (io_buffer_external_p(data->flags)) {
+ rb_raise(rb_eRuntimeError, "Cannot resize external buffer!");
+ }
+
+ io_buffer_initialize(&updated, NULL, size, data->flags, data->source);
+
+ if (data->base && preserve > 0) {
+ memcpy(updated.base, data->base, preserve);
+ }
+
+ io_buffer_free(data);
+ *data = updated;
+}
+
+static
+VALUE rb_io_buffer_compare(VALUE self, VALUE other)
+{
+ const void *ptr1, *ptr2;
+ size_t size1, size2;
+
+ rb_io_buffer_get_immutable(self, &ptr1, &size1);
+ rb_io_buffer_get_immutable(other, &ptr2, &size2);
+
+ if (size1 < size2) {
+ return RB_INT2NUM(-1);
+ }
+
+ if (size1 > size2) {
+ return RB_INT2NUM(1);
+ }
+
+ return RB_INT2NUM(memcmp(ptr1, ptr2, size1));
+}
+
+static VALUE io_buffer_resize(VALUE self, VALUE size, VALUE preserve)
+{
+ rb_io_buffer_resize(self, NUM2SIZET(size), NUM2SIZET(preserve));
+
+ return self;
+}
+
+static void io_buffer_validate_type(size_t size, size_t offset) {
+ if (offset > size) {
+ rb_raise(rb_eRuntimeError, "Type extends beyond end of buffer!");
+ }
+}
+
+// Lower case: little endian.
+// Upper case: big endian (network endian).
+//
+// :U8 | unsigned 8-bit integer.
+// :S8 | signed 8-bit integer.
+//
+// :u16, :U16 | unsigned 16-bit integer.
+// :s16, :S16 | signed 16-bit integer.
+//
+// :u32, :U32 | unsigned 32-bit integer.
+// :s32, :S32 | signed 32-bit integer.
+//
+// :u64, :U64 | unsigned 64-bit integer.
+// :s64, :S64 | signed 64-bit integer.
+//
+// :f32, :F32 | 32-bit floating point number.
+// :f64, :F64 | 64-bit floating point number.
+
+#define ruby_swap8(value) value
+
+union swapf32 {
+ uint32_t integral;
+ float value;
+};
+
+static float ruby_swapf32(float value)
+{
+ union swapf32 swap = {.value = value};
+ swap.integral = ruby_swap32(swap.integral);
+ return swap.value;
+}
+
+union swapf64 {
+ uint64_t integral;
+ double value;
+};
+
+static double ruby_swapf64(double value)
+{
+ union swapf64 swap = {.value = value};
+ swap.integral = ruby_swap64(swap.integral);
+ return swap.value;
+}
+
+#define DECLAIR_TYPE(name, type, endian, wrap, unwrap, swap) \
+static ID RB_IO_BUFFER_TYPE_##name; \
+\
+static VALUE io_buffer_read_##name(const void* base, size_t size, size_t *offset) \
+{ \
+ io_buffer_validate_type(size, *offset + sizeof(type)); \
+ type value; \
+ memcpy(&value, (char*)base + *offset, sizeof(type)); \
+ if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \
+ *offset += sizeof(type); \
+ return wrap(value); \
+} \
+\
+static void io_buffer_write_##name(const void* base, size_t size, size_t *offset, VALUE _value) \
+{ \
+ io_buffer_validate_type(size, *offset + sizeof(type)); \
+ type value = unwrap(_value); \
+ if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \
+ memcpy((char*)base + *offset, &value, sizeof(type)); \
+ *offset += sizeof(type); \
+}
+
+DECLAIR_TYPE(U8, uint8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap8)
+DECLAIR_TYPE(S8, int8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap8)
+
+DECLAIR_TYPE(u16, uint16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
+DECLAIR_TYPE(U16, uint16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
+DECLAIR_TYPE(s16, int16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
+DECLAIR_TYPE(S16, int16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
+
+DECLAIR_TYPE(u32, uint32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
+DECLAIR_TYPE(U32, uint32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
+DECLAIR_TYPE(s32, int32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
+DECLAIR_TYPE(S32, int32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
+
+DECLAIR_TYPE(u64, uint64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_ULONG2NUM, RB_NUM2ULONG, ruby_swap64)
+DECLAIR_TYPE(U64, uint64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_ULONG2NUM, RB_NUM2ULONG, ruby_swap64)
+DECLAIR_TYPE(s64, int64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_LONG2NUM, RB_NUM2LONG, ruby_swap64)
+DECLAIR_TYPE(S64, int64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_LONG2NUM, RB_NUM2LONG, ruby_swap64)
+
+DECLAIR_TYPE(f32, float, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
+DECLAIR_TYPE(F32, float, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
+DECLAIR_TYPE(f64, double, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
+DECLAIR_TYPE(F64, double, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
+#undef DECLAIR_TYPE
+
+VALUE rb_io_buffer_get(const void* base, size_t size, ID type, size_t offset)
+{
+#define READ_TYPE(name) if (type == RB_IO_BUFFER_TYPE_##name) return io_buffer_read_##name(base, size, &offset);
+ READ_TYPE(U8)
+ READ_TYPE(S8)
+
+ READ_TYPE(u16)
+ READ_TYPE(U16)
+ READ_TYPE(s16)
+ READ_TYPE(S16)
+
+ READ_TYPE(u32)
+ READ_TYPE(U32)
+ READ_TYPE(s32)
+ READ_TYPE(S32)
+
+ READ_TYPE(u64)
+ READ_TYPE(U64)
+ READ_TYPE(s64)
+ READ_TYPE(S64)
+
+ READ_TYPE(f32)
+ READ_TYPE(F32)
+ READ_TYPE(f64)
+ READ_TYPE(F64)
+#undef READ_TYPE
+
+ rb_raise(rb_eArgError, "Invalid type name!");
+}
+
+static VALUE io_buffer_get(VALUE self, VALUE type, VALUE _offset)
+{
+ const void *base;
+ size_t size;
+ size_t offset = NUM2SIZET(_offset);
+
+ rb_io_buffer_get_immutable(self, &base, &size);
+
+ return rb_io_buffer_get(base, size, RB_SYM2ID(type), offset);
+}
+
+void rb_io_buffer_set(const void* base, size_t size, ID type, size_t offset, VALUE value)
+{
+#define WRITE_TYPE(name) if (type == RB_IO_BUFFER_TYPE_##name) {io_buffer_write_##name(base, size, &offset, value); return;}
+ WRITE_TYPE(U8)
+ WRITE_TYPE(S8)
+
+ WRITE_TYPE(u16)
+ WRITE_TYPE(U16)
+ WRITE_TYPE(s16)
+ WRITE_TYPE(S16)
+
+ WRITE_TYPE(u32)
+ WRITE_TYPE(U32)
+ WRITE_TYPE(s32)
+ WRITE_TYPE(S32)
+
+ WRITE_TYPE(u64)
+ WRITE_TYPE(U64)
+ WRITE_TYPE(s64)
+ WRITE_TYPE(S64)
+
+ WRITE_TYPE(f32)
+ WRITE_TYPE(F32)
+ WRITE_TYPE(f64)
+ WRITE_TYPE(F64)
+#undef WRITE_TYPE
+
+ rb_raise(rb_eArgError, "Invalid type name!");
+}
+
+static VALUE io_buffer_set(VALUE self, VALUE type, VALUE _offset, VALUE value)
+{
+ void *base;
+ size_t size;
+ size_t offset = NUM2SIZET(_offset);
+
+ rb_io_buffer_get_mutable(self, &base, &size);
+
+ rb_io_buffer_set(base, size, RB_SYM2ID(type), offset, value);
+
+ return SIZET2NUM(offset);
+}
+
+void rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length)
+{
+ void *base;
+ size_t size;
+
+ rb_io_buffer_get_mutable(self, &base, &size);
+
+ if (offset + length > size) {
+ rb_raise(rb_eRuntimeError, "Offset + length out of bounds!");
+ }
+
+ memset((char*)base + offset, value, length);
+}
+
+static
+VALUE io_buffer_clear(int argc, VALUE *argv, VALUE self)
+{
+ struct rb_io_buffer *data = NULL;
+ TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data);
+
+ if (argc > 3) {
+ rb_error_arity(argc, 0, 3);
+ }
+
+ uint8_t value = 0;
+ if (argc >= 1) {
+ value = NUM2UINT(argv[0]);
+ }
+
+ size_t offset = 0;
+ if (argc >= 2) {
+ offset = NUM2SIZET(argv[1]);
+ }
+
+ size_t length = data->size;
+ if (argc >= 3) {
+ length = NUM2SIZET(argv[2]);
+ }
+
+ rb_io_buffer_clear(self, value, offset, length);
+
+ return self;
+}
+
+void Init_IO_Buffer(void)
+{
+ rb_cIOBuffer = rb_define_class_under(rb_cIO, "Buffer", rb_cObject);
+
+ rb_define_alloc_func(rb_cIOBuffer, rb_io_buffer_type_allocate);
+
+#ifdef _WIN32
+ SYSTEM_INFO info;
+ GetSystemInfo(&info);
+ RUBY_IO_BUFFER_PAGE_SIZE = info.dwPageSize;
+#else /* not WIN32 */
+ RUBY_IO_BUFFER_PAGE_SIZE = sysconf(_SC_PAGESIZE);
+#endif
+
+ // Efficient sicing of mapped buffers:
+ rb_define_const(rb_cIOBuffer, "PAGE_SIZE", SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE));
+
+ rb_define_singleton_method(rb_cIOBuffer, "map", io_buffer_map, -1);
+
+ // General use:
+ rb_define_method(rb_cIOBuffer, "initialize", rb_io_buffer_initialize, -1);
+ rb_define_method(rb_cIOBuffer, "inspect", rb_io_buffer_inspect, 0);
+ rb_define_method(rb_cIOBuffer, "to_s", rb_io_buffer_to_s, 0);
+ rb_define_method(rb_cIOBuffer, "size", rb_io_buffer_size, 0);
+
+ // Flags:
+ rb_define_const(rb_cIOBuffer, "EXTERNAL", RB_INT2NUM(RB_IO_BUFFER_EXTERNAL));
+ rb_define_const(rb_cIOBuffer, "INTERNAL", RB_INT2NUM(RB_IO_BUFFER_INTERNAL));
+ rb_define_const(rb_cIOBuffer, "MAPPED", RB_INT2NUM(RB_IO_BUFFER_MAPPED));
+ rb_define_const(rb_cIOBuffer, "LOCKED", RB_INT2NUM(RB_IO_BUFFER_LOCKED));
+ rb_define_const(rb_cIOBuffer, "PRIVATE", RB_INT2NUM(RB_IO_BUFFER_PRIVATE));
+ rb_define_const(rb_cIOBuffer, "IMMUTABLE", RB_INT2NUM(RB_IO_BUFFER_IMMUTABLE));
+
+ // Endian:
+ rb_define_const(rb_cIOBuffer, "LITTLE_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_LITTLE_ENDIAN));
+ rb_define_const(rb_cIOBuffer, "BIG_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_BIG_ENDIAN));
+ rb_define_const(rb_cIOBuffer, "HOST_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_HOST_ENDIAN));
+ rb_define_const(rb_cIOBuffer, "NETWORK_ENDIAN", RB_INT2NUM(RB_IO_BUFFER_NETWORK_ENDIAN));
+
+ rb_define_method(rb_cIOBuffer, "external?", rb_io_buffer_external_p, 0);
+ rb_define_method(rb_cIOBuffer, "internal?", rb_io_buffer_internal_p, 0);
+ rb_define_method(rb_cIOBuffer, "mapped?", rb_io_buffer_mapped_p, 0);
+ rb_define_method(rb_cIOBuffer, "locked?", rb_io_buffer_locked_p, 0);
+ rb_define_method(rb_cIOBuffer, "immutable?", rb_io_buffer_immutable_p, 0);
+
+ // Locking to prevent changes while using pointer:
+ // rb_define_method(rb_cIOBuffer, "lock", rb_io_buffer_lock, 0);
+ // rb_define_method(rb_cIOBuffer, "unlock", rb_io_buffer_unlock, 0);
+ rb_define_method(rb_cIOBuffer, "locked", rb_io_buffer_locked, 0);
+
+ // Manipulation:
+ rb_define_method(rb_cIOBuffer, "slice", rb_io_buffer_slice, 2);
+ rb_define_method(rb_cIOBuffer, "to_str", rb_io_buffer_to_str, -1);
+ rb_define_method(rb_cIOBuffer, "copy", io_buffer_copy, 2);
+ rb_define_method(rb_cIOBuffer, "<=>", rb_io_buffer_compare, 1);
+ rb_define_method(rb_cIOBuffer, "resize", io_buffer_resize, 2);
+ rb_define_method(rb_cIOBuffer, "clear", io_buffer_clear, -1);
+ rb_define_method(rb_cIOBuffer, "free", rb_io_buffer_free, 0);
+
+ rb_include_module(rb_cIOBuffer, rb_mComparable);
+
+#define DEFINE_TYPE(name) RB_IO_BUFFER_TYPE_##name = rb_intern_const(#name)
+ DEFINE_TYPE(U8); DEFINE_TYPE(S8);
+ DEFINE_TYPE(u16); DEFINE_TYPE(U16); DEFINE_TYPE(s16); DEFINE_TYPE(S16);
+ DEFINE_TYPE(u32); DEFINE_TYPE(U32); DEFINE_TYPE(s32); DEFINE_TYPE(S32);
+ DEFINE_TYPE(u64); DEFINE_TYPE(U64); DEFINE_TYPE(s64); DEFINE_TYPE(S64);
+ DEFINE_TYPE(f32); DEFINE_TYPE(F32); DEFINE_TYPE(f64); DEFINE_TYPE(F64);
+#undef DEFINE_TYPE
+
+ // Data access:
+ rb_define_method(rb_cIOBuffer, "get", io_buffer_get, 2);
+ rb_define_method(rb_cIOBuffer, "set", io_buffer_set, 3);
+}