From 4cca8bac6bbdab02eba11e6527f793f2e9a5e75d Mon Sep 17 00:00:00 2001 From: Tomasz Dąbrowski Date: Wed, 16 Nov 2016 17:43:55 +0100 Subject: inline structures data type for mruby (MRB_TT_INLINE) (fix #3237) Inline structures have no instance variables, no finalizer, and offer as much space as possible in RBASIC object. This means 24 bytes on 64-bit platforms and 12 bytes on 32-bit platforms. mruby-inline-struct gem is only provided for testing. --- include/mruby/inline.h | 47 +++++++++ include/mruby/value.h | 3 +- mrbgems/default.gembox | 3 + mrbgems/mruby-inline-struct/mrbgem.rake | 5 + mrbgems/mruby-inline-struct/test/inline.c | 83 ++++++++++++++++ mrbgems/mruby-inline-struct/test/inline.rb | 151 +++++++++++++++++++++++++++++ src/class.c | 20 ++++ src/etc.c | 1 + src/kernel.c | 4 + src/object.c | 4 +- 10 files changed, 318 insertions(+), 3 deletions(-) create mode 100644 include/mruby/inline.h create mode 100644 mrbgems/mruby-inline-struct/mrbgem.rake create mode 100644 mrbgems/mruby-inline-struct/test/inline.c create mode 100644 mrbgems/mruby-inline-struct/test/inline.rb diff --git a/include/mruby/inline.h b/include/mruby/inline.h new file mode 100644 index 000000000..e773aa118 --- /dev/null +++ b/include/mruby/inline.h @@ -0,0 +1,47 @@ +/* +** mruby/inline.h - Inline structures +** +** See Copyright Notice in mruby.h +*/ + +#ifndef MRUBY_INLINE_H +#define MRUBY_INLINE_H + +#include "common.h" +#include + +/** + * Inline structures that fit in RVALUE + * + * They cannot have finalizer, and cannot have instance variables. + */ +MRB_BEGIN_DECL + +#define INLINE_DATA_SIZE (sizeof(void*) * 3) + +struct RInline { + MRB_OBJECT_HEADER; + char inline_data[INLINE_DATA_SIZE]; +}; + +#define RINLINE(obj) ((struct RInline*)(mrb_ptr(obj))) +#define INLINE_PTR(obj) (RINLINE(obj)->inline_data) + +MRB_INLINE mrb_int mrb_inline_size() +{ + return INLINE_DATA_SIZE; +} + +MRB_INLINE void* mrb_inline_ptr(mrb_value object) +{ + return INLINE_PTR(object); +} + +MRB_INLINE void mrb_inline_copy(mrb_value dest, mrb_value src) +{ + memcpy(INLINE_PTR(dest), INLINE_PTR(src), INLINE_DATA_SIZE); +} + +MRB_END_DECL + +#endif /* MRUBY_INLINE_H */ diff --git a/include/mruby/value.h b/include/mruby/value.h index 4330b9441..eb3f931e1 100644 --- a/include/mruby/value.h +++ b/include/mruby/value.h @@ -116,7 +116,8 @@ enum mrb_vtype { MRB_TT_ENV, /* 20 */ MRB_TT_DATA, /* 21 */ MRB_TT_FIBER, /* 22 */ - MRB_TT_MAXDEFINE /* 23 */ + MRB_TT_INLINE, /* 23 */ + MRB_TT_MAXDEFINE /* 24 */ }; #include diff --git a/mrbgems/default.gembox b/mrbgems/default.gembox index dab7230aa..e3bd114f3 100644 --- a/mrbgems/default.gembox +++ b/mrbgems/default.gembox @@ -74,6 +74,9 @@ MRuby::GemBox.new do |conf| # Use class/module extension conf.gem :core => "mruby-class-ext" + # Use inline struct + conf.gem :core => "mruby-inline-struct" + # Use mruby-compiler to build other mrbgems conf.gem :core => "mruby-compiler" end diff --git a/mrbgems/mruby-inline-struct/mrbgem.rake b/mrbgems/mruby-inline-struct/mrbgem.rake new file mode 100644 index 000000000..91ad9f44b --- /dev/null +++ b/mrbgems/mruby-inline-struct/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('mruby-inline-struct') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' + spec.summary = 'inline structure' +end diff --git a/mrbgems/mruby-inline-struct/test/inline.c b/mrbgems/mruby-inline-struct/test/inline.c new file mode 100644 index 000000000..903c08aca --- /dev/null +++ b/mrbgems/mruby-inline-struct/test/inline.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +static mrb_value +inline_test_initialize(mrb_state *mrb, mrb_value self) +{ + char *string = mrb_inline_ptr(self); + mrb_int size = mrb_inline_size(); + mrb_value object; + mrb_get_args(mrb, "o", &object); + + if (mrb_float_p(object)) + { + snprintf(string, size, "float(%.3f)", mrb_float(object)); + } + else if (mrb_fixnum_p(object)) + { + snprintf(string, size, "fixnum(%d)", mrb_fixnum(object)); + } + else if (mrb_string_p(object)) + { + snprintf(string, size, "string(%s)", mrb_string_value_cstr(mrb, &object)); + } + + string[size - 1] = 0; // force NULL at the end + return self; +} + +static mrb_value +inline_test_to_s(mrb_state *mrb, mrb_value self) +{ + return mrb_str_new_cstr(mrb, mrb_inline_ptr(self)); +} + +static mrb_value +inline_test_length(mrb_state *mrb, mrb_value self) +{ + return mrb_fixnum_value(mrb_inline_size()); +} + +static mrb_value +inline_test_test_receive(mrb_state *mrb, mrb_value self) +{ + mrb_value object; + mrb_get_args(mrb, "o", &object); + if (mrb_obj_class(mrb, object) != mrb_class_get(mrb, "InlineStructTest")) + { + mrb_raisef(mrb, E_TYPE_ERROR, "Expected InlineStructTest"); + } + return mrb_bool_value(((char*)mrb_inline_ptr(object))[0] == 's'); +} + +static mrb_value +inline_test_test_receive_direct(mrb_state *mrb, mrb_value self) +{ + char *ptr; + mrb_get_args(mrb, "I", &ptr); + return mrb_bool_value(ptr[0] == 's'); +} + +static mrb_value +inline_test_mutate(mrb_state *mrb, mrb_value self) +{ + char *ptr = mrb_inline_ptr(self); + memcpy(ptr, "mutate", 6); + return mrb_nil_value(); +} + +void mrb_mruby_inline_struct_gem_test(mrb_state *mrb) +{ + struct RClass *cls; + + cls = mrb_define_class(mrb, "InlineStructTest", mrb->object_class); + MRB_SET_INSTANCE_TT(cls, MRB_TT_INLINE); + mrb_define_method(mrb, cls, "initialize", inline_test_initialize, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, cls, "to_s", inline_test_to_s, MRB_ARGS_NONE()); + mrb_define_method(mrb, cls, "mutate", inline_test_mutate, MRB_ARGS_NONE()); + mrb_define_class_method(mrb, cls, "length", inline_test_length, MRB_ARGS_NONE()); + mrb_define_class_method(mrb, cls, "test_receive", inline_test_test_receive, MRB_ARGS_REQ(1)); + mrb_define_class_method(mrb, cls, "test_receive_direct", inline_test_test_receive_direct, MRB_ARGS_REQ(1)); +} diff --git a/mrbgems/mruby-inline-struct/test/inline.rb b/mrbgems/mruby-inline-struct/test/inline.rb new file mode 100644 index 000000000..495859232 --- /dev/null +++ b/mrbgems/mruby-inline-struct/test/inline.rb @@ -0,0 +1,151 @@ +## +# InlineStruct Test + +class InlineStructTest + def extra_method + :ok + end + + def test_ivar_set + @var = :ivar + end + + def test_ivar_get + @vat + end +end + +assert('InlineStructTest#dup') do + obj = InlineStructTest.new(1) + assert_equal obj.to_s, 'fixnum(1)' + assert_equal obj.dup.to_s, 'fixnum(1)' +end + +assert('InlineStructTest#clone') do + obj = InlineStructTest.new(1) + assert_equal obj.to_s, 'fixnum(1)' + assert_equal obj.clone.to_s, 'fixnum(1)' +end + +assert('InlineStruct#object_id') do + obj1 = InlineStructTest.new(1) + obj2 = InlineStructTest.new(1) + assert_not_equal obj1, obj2 + assert_not_equal obj1.object_id, obj2.object_id + assert_not_equal obj1.object_id, obj1.dup.object_id + assert_not_equal obj1.object_id, obj1.clone.object_id +end + +assert('InlineStructTest#mutate (dup)') do + obj1 = InlineStructTest.new("foo") + assert_equal obj1.to_s, "string(foo)" + obj2 = obj1.dup + assert_equal obj2.to_s, "string(foo)" + obj1.mutate + assert_equal obj1.to_s, "mutate(foo)" + assert_equal obj2.to_s, "string(foo)" +end + +assert('InlineStructTest#mutate (clone)') do + obj1 = InlineStructTest.new("foo") + assert_equal obj1.to_s, "string(foo)" + obj2 = obj1.clone + assert_equal obj2.to_s, "string(foo)" + obj1.mutate + assert_equal obj1.to_s, "mutate(foo)" + assert_equal obj2.to_s, "string(foo)" +end + +assert('InlineStructTest#test_receive(string)') do + assert_equal InlineStructTest.test_receive(InlineStructTest.new('a')), true +end + +assert('InlineStructTest#test_receive(float)') do + assert_equal InlineStructTest.test_receive(InlineStructTest.new(1.25)), false +end + +assert('InlineStructTest#test_receive(invalid object)') do + assert_raise(TypeError) do + InlineStructTest.test_receive([]) + end +end + +assert('InlineStructTest#test_receive(string)') do + assert_equal InlineStructTest.test_receive_direct(InlineStructTest.new('a')), true +end + +assert('InlineStructTest#test_receive(float)') do + assert_equal InlineStructTest.test_receive_direct(InlineStructTest.new(1.25)), false +end + +assert('InlineStructTest#test_receive(invalid object)') do + assert_raise(TypeError) do + InlineStructTest.test_receive_direct([]) + end +end + +assert('InlineStructTest#extra_method') do + assert_equal InlineStructTest.new(1).extra_method, :ok +end + +assert('InlineStructTest instance variable') do + obj = InlineStructTest.new(1) + assert_raise(ArgumentError) do + obj.test_ivar_set + end + assert_equal obj.test_ivar_get, nil +end + +# 64-bit mode +if InlineStructTest.length == 24 + assert('InlineStructTest length [64 bit]') do + assert_equal InlineStructTest.length, 3 * 8 + end + + assert('InlineStructTest w/float [64 bit]') do + obj = InlineStructTest.new(1.25) + assert_equal obj.to_s, "float(1.250)" + end + + assert('InlineStructTest w/fixnum [64 bit]') do + obj = InlineStructTest.new(42) + assert_equal obj.to_s, "fixnum(42)" + end + + assert('InlineStructTest w/string [64 bit]') do + obj = InlineStructTest.new("hello") + assert_equal obj.to_s, "string(hello)" + end + + assert('InlineStructTest w/long string [64 bit]') do + obj = InlineStructTest.new("this won't fit in 3 * 8 bytes available for the structure") + assert_equal obj.to_s, "string(this won't fit i" + end +end + +# 32-bit mode +if InlineStructTest.length == 12 + assert('InlineStructTest length [32 bit]') do + assert_equal InlineStructTest.length, 3 * 4 + end + + assert('InlineStructTest w/float [32 bit]') do + obj = InlineStructTest.new(1.25) + assert_equal obj.to_s, "float(1.250" + end + + assert('InlineStructTest w/fixnum [32 bit]') do + obj = InlineStructTest.new(42) + assert_equal obj.to_s, "fixnum(42)" + end + + assert('InlineStructTest w/string [32 bit]') do + obj = InlineStructTest.new("hello") + assert_equal obj.to_s, "string(hell" + end + + assert('InlineStructTest w/long string [32 bit]') do + obj = InlineStructTest.new("this won't fit in 3 * 4 bytes available for the structure") + assert_equal obj.to_s, "string(this" + end +end diff --git a/src/class.c b/src/class.c index 4fc81689b..53354d02a 100644 --- a/src/class.c +++ b/src/class.c @@ -14,6 +14,7 @@ #include #include #include +#include KHASH_DEFINE(mt, mrb_sym, struct RProc*, TRUE, kh_int_hash_func, kh_int_hash_equal) @@ -491,6 +492,7 @@ to_sym(mrb_state *mrb, mrb_value ss) b: Boolean [mrb_bool] n: Symbol [mrb_sym] d: Data [void*,mrb_data_type const] 2nd argument will be used to check data type so it won't be modified + I: Inline struct [void*] &: Block [mrb_value] *: rest argument [mrb_value*,mrb_int] Receive the rest of the arguments as an array. |: optional Next argument of '|' and later are optional. @@ -702,6 +704,24 @@ mrb_get_args(mrb_state *mrb, const char *format, ...) } } break; + case 'I': + { + void* *p; + mrb_value ss; + + p = va_arg(ap, void**); + if (i < argc) { + ss = ARGV[arg_i]; + if (mrb_type(ss) != MRB_TT_INLINE) + { + mrb_raisef(mrb, E_TYPE_ERROR, "%S is not inline struct", ss); + } + *p = mrb_inline_ptr(ss); + arg_i++; + i++; + } + } + break; case 'f': { mrb_float *p; diff --git a/src/etc.c b/src/etc.c index 183e2f070..c89549da6 100644 --- a/src/etc.c +++ b/src/etc.c @@ -139,6 +139,7 @@ mrb_obj_id(mrb_value obj) case MRB_TT_EXCEPTION: case MRB_TT_FILE: case MRB_TT_DATA: + case MRB_TT_INLINE: default: return MakeID(mrb_ptr(obj)); } diff --git a/src/kernel.c b/src/kernel.c index df237cd46..4a4b6b414 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -11,6 +11,7 @@ #include #include #include +#include typedef enum { NOEX_PUBLIC = 0x00, @@ -301,6 +302,9 @@ init_copy(mrb_state *mrb, mrb_value dest, mrb_value obj) case MRB_TT_EXCEPTION: mrb_iv_copy(mrb, dest, obj); break; + case MRB_TT_INLINE: + mrb_inline_copy(dest, obj); + break; default: break; diff --git a/src/object.c b/src/object.c index bb1a4ebc4..af66d93d0 100644 --- a/src/object.c +++ b/src/object.c @@ -348,7 +348,7 @@ mrb_check_convert_type(mrb_state *mrb, mrb_value val, enum mrb_vtype type, const { mrb_value v; - if (mrb_type(val) == type && type != MRB_TT_DATA) return val; + if (mrb_type(val) == type && type != MRB_TT_DATA && type != MRB_TT_INLINE) return val; v = convert_type(mrb, val, tname, method, FALSE); if (mrb_nil_p(v) || mrb_type(v) != type) return mrb_nil_value(); return v; @@ -390,7 +390,7 @@ mrb_check_type(mrb_state *mrb, mrb_value x, enum mrb_vtype t) enum mrb_vtype xt; xt = mrb_type(x); - if ((xt != t) || (xt == MRB_TT_DATA)) { + if ((xt != t) || (xt == MRB_TT_DATA) || (xt == MRB_TT_INLINE)) { while (type->type < MRB_TT_MAXDEFINE) { if (type->type == t) { const char *etype; -- cgit v1.2.3