Skip to content

Commit

Permalink
Merge pull request #97 from hadashiA/ku/word-boxing
Browse files Browse the repository at this point in the history
Enable MRB_WORD_BOXING
  • Loading branch information
hadashiA authored Oct 22, 2024
2 parents a7cac79 + 43d90c1 commit 90760f2
Show file tree
Hide file tree
Showing 30 changed files with 251 additions and 64 deletions.
3 changes: 3 additions & 0 deletions src/VitalRouter.Unity/Assets/Sandbox/SampleMruby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ async UniTask Start()
{
using var context = MRubyContext.Create(Router.Default, new MyCommands());

var fvalue = context.EvaluateUnsafe("0.12345").RawValue;
UnityEngine.Debug.Log($"!!!!! float {fvalue.TT} IsObject={fvalue.IsObject} F={fvalue.FloatValue:F5}");

context.Load("def hoge(x) = x * 100");
var h = context.Evaluate<int>("hoge(7)");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ static void Free(void* ptr)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void* Malloc(uint size)
{
var allocated = UnsafeUtility.Malloc(size + HeaderSize, sizeof(byte), DefaultAllocator);
var allocated = UnsafeUtility.Malloc(size + HeaderSize, 4, DefaultAllocator);
WriteHeader(allocated, size);
return (byte*)allocated + HeaderSize;
}
Expand Down Expand Up @@ -106,7 +106,7 @@ public class MRubyContext : SafeHandle

public static unsafe MRubyContext Create()
{
NativeMethods.MrbAllocfSet(MRubyAllocator.AllocPersistent);
// NativeMethods.MrbAllocfSet(MRubyAllocator.AllocPersistent);

var ptr = NativeMethods.MrbContextNew();
NativeMethods.MrbCallbacksSet(ptr, MRubyScript.OnCommandCalled, MRubyScript.OnError);
Expand Down
156 changes: 121 additions & 35 deletions src/VitalRouter.Unity/Assets/VitalRouter.MRuby/Runtime/NativeMethods.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using Unity.Collections.LowLevel.Unsafe;

namespace VitalRouter.MRuby
{
Expand Down Expand Up @@ -38,52 +39,39 @@ unsafe struct MrbNString
public int Length;
}

// mruby types

[StructLayout(LayoutKind.Explicit)]
public struct MrbValueUnion
[StructLayout(LayoutKind.Sequential)]
unsafe struct RBasic
{
// Assuming MRB_NO_FLOAT is off, MRB_USE_FLOAT is off.
[FieldOffset(0)]
public double F;

[FieldOffset(0)]
public nint I;

[FieldOffset(0)]
public IntPtr P;

[FieldOffset(0)]
public uint Sym;
IntPtr c;
IntPtr gcnext;
public MrbVtype TT;
}

// NOTE: Assuming MRUBY_BOXING_NO
[StructLayout(LayoutKind.Sequential)]
public struct MrbValue
unsafe struct RInteger
{
MrbValueUnion value;
IntPtr c;
IntPtr gcnext;
public MrbVtype TT;
fixed byte Footer[3];
public nint IntValue;
}

public bool IsNil => TT == MrbVtype.MRB_TT_FALSE && value.I == 0;
public long IntValue => value.I;
public double FlaotValue => value.F;

public unsafe string ToString(MRubyContext context)
{
var nstring = NativeMethods.MrbToString(context.DangerousGetPtr(), this);
return System.Text.Encoding.UTF8.GetString(nstring.Bytes, nstring.Length);
}

public unsafe FixedUtf8String ToFixedUtf8String(MRubyContext context)
{
var nstring = NativeMethods.MrbToString(context.DangerousGetPtr(), this);
return new FixedUtf8String(nstring.Bytes, nstring.Length);
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct RFloat
{
IntPtr c;
IntPtr gcnext;
public MrbVtype TT;
fixed byte Footer[3];
public double FloatValue;
}

// NOTE: Assuming MRUBY_BOXING_NO
// mruby types

// ReSharper disable InconsistentNaming
public enum MrbVtype
public enum MrbVtype : byte
{
MRB_TT_FALSE,
MRB_TT_TRUE,
Expand Down Expand Up @@ -116,6 +104,104 @@ public enum MrbVtype
}
// ReShaper enable InconsistentNaming

// NOTE: Assuming MRB_WORD_BOXING
//
// mrb_value representation:
//
// 64-bit word with inline float:
// nil : ...0000 0000 (all bits are 0)
// false : ...0000 0100 (mrb_fixnum(v) != 0)
// true : ...0000 1100
// undef : ...0001 0100
// symbol: ...SSS1 1100 (use only upper 32-bit as symbol value with MRB_64BIT)
// fixnum: ...IIII III1
// float : ...FFFF FF10 (51 bit significands; require MRB_64BIT)
// object: ...PPPP P000
//
// 32-bit word with inline float:
// nil : ...0000 0000 (all bits are 0)
// false : ...0000 0100 (mrb_fixnum(v) != 0)
// true : ...0000 1100
// undef : ...0001 0100
// symbol: ...SSS1 0100 (symbol occupies 20bits)
// fixnum: ...IIII III1
// float : ...FFFF FF10 (22 bit significands; require MRB_64BIT)
// object: ...PPPP P000
//
[StructLayout(LayoutKind.Sequential)]
public unsafe struct MrbValue
{
static readonly bool Is64bitTarget = IntPtr.Size == 8;

IntPtr ptr;

public MrbVtype TT => this switch
{
{ IsFalse: true } => MrbVtype.MRB_TT_FALSE,
{ IsTrue: true } => MrbVtype.MRB_TT_TRUE,
{ IsUndef: true } => MrbVtype.MRB_TT_UNDEF,
{ IsSymbol: true } => MrbVtype.MRB_TT_SYMBOL,
{ IsFixnum: true } => MrbVtype.MRB_TT_INTEGER,
{ IsFloat: true } => MrbVtype.MRB_TT_FLOAT,
{ IsObject: true} => ((RBasic*)ptr)->TT,
_ => default
};

public bool IsNil => ptr == IntPtr.Zero;
public bool IsFalse => ptr.ToInt64() == 0b0000_0100;
public bool IsTrue => ptr.ToInt64() == 0b0000_1100;
public bool IsUndef => ptr.ToInt64() == 0b0001_0100;
public bool IsSymbol => (ptr.ToInt64() & 0b1_1111) == 0b1_1100;
public bool IsFixnum => (ptr.ToInt64() & 1) == 1;
public bool IsFloat => Is64bitTarget
? (ptr.ToInt64() & 0b11) == 0b10
: IsObject && ((RBasic*)ptr)->TT == MrbVtype.MRB_TT_FLOAT;

public bool IsObject => (ptr.ToInt64() & 0b111) == 0;

public long IntValue
{
get
{
if (IsObject && ((RBasic *)ptr)->TT == MrbVtype.MRB_TT_INTEGER)
{
return ((RInteger*)ptr)->IntValue;
}
return ptr.ToInt64() >> 1;
}
}

public double FloatValue
{
get
{
// Assume that MRB_USE_FLOAT32 is not defined
// Assume that MRB_WORDBOX_NO_FLOAT_TRUNCATE is not defined
if (Is64bitTarget)
{
var fbits = (ptr.ToInt64() & ~3) | 2;
return UnsafeUtility.As<long, double>(ref fbits);
}
else
{
return ((RFloat*)ptr)->FloatValue;
}
}
}

public unsafe string ToString(MRubyContext context)
{
var nstring = NativeMethods.MrbToString(context.DangerousGetPtr(), this);
return System.Text.Encoding.UTF8.GetString(nstring.Bytes, nstring.Length);
}

public unsafe FixedUtf8String ToFixedUtf8String(MRubyContext context)
{
var nstring = NativeMethods.MrbToString(context.DangerousGetPtr(), this);
return new FixedUtf8String(nstring.Bytes, nstring.Length);
}
}

#pragma warning disable CS8500
#pragma warning disable CS8981
static unsafe class NativeMethods
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public long Deserialize(MrbValue mrbValue, MRubyContext context, MrbValueSeriali
{
throw new MRubySerializationException($"value is not a Integer. {mrbValue.TT}");
}
return checked((long)mrbValue.IntValue);
return mrbValue.IntValue;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public float Deserialize(MrbValue mrbValue, MRubyContext context, MrbValueSerial
{
return mrbValue.TT switch
{
MrbVtype.MRB_TT_FLOAT => (float)mrbValue.FlaotValue,
MrbVtype.MRB_TT_FLOAT => (float)mrbValue.FloatValue,
MrbVtype.MRB_TT_INTEGER => mrbValue.IntValue,
_ => throw new MRubySerializationException($"mrb_value cannot deserialize as float: {mrbValue.TT}")
};
Expand All @@ -23,7 +23,7 @@ public double Deserialize(MrbValue mrbValue, MRubyContext context, MrbValueSeria
{
return mrbValue.TT switch
{
MrbVtype.MRB_TT_FLOAT => (float)mrbValue.FlaotValue,
MrbVtype.MRB_TT_FLOAT => (float)mrbValue.FloatValue,
MrbVtype.MRB_TT_INTEGER => mrbValue.IntValue,
_ => throw new MRubySerializationException($"mrb_value cannot deserialize as double: {mrbValue.TT}")
};
Expand All @@ -38,7 +38,7 @@ public decimal Deserialize(MrbValue mrbValue, MRubyContext context, MrbValueSeri
{
return mrbValue.TT switch
{
MrbVtype.MRB_TT_FLOAT => (decimal)mrbValue.FlaotValue,
MrbVtype.MRB_TT_FLOAT => (decimal)mrbValue.FloatValue,
MrbVtype.MRB_TT_INTEGER => mrbValue.IntValue,
_ => throw new MRubySerializationException($"mrb_value cannot deserialize as decimal: {mrbValue.TT}")
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class PrimitiveObjectFormatter : IMrbValueFormatter<object?>
case MrbVtype.MRB_TT_INTEGER:
return value.IntValue;
case MrbVtype.MRB_TT_FLOAT:
return value.FlaotValue;
return value.FloatValue;
case MrbVtype.MRB_TT_SYMBOL:
case MrbVtype.MRB_TT_STRING:
return value.ToString(context);
Expand Down
95 changes: 95 additions & 0 deletions src/VitalRouter.Unity/Assets/VitalRouter.Tests/MrbValueTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#if UNITY_2022_2_OR_NEWER
using NUnit.Framework;
using VitalRouter.MRuby;

namespace VitalRouter.Tests
{
[TestFixture]
public class MrbValueTest
{
[Test]
public void Nil()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe("nil").RawValue;
Assert.That(value.IsNil, Is.True);
}

[Test]
public void False()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe("false").RawValue;
Assert.That(value.IsFalse, Is.EqualTo(true));
Assert.That(value.TT, Is.EqualTo(MrbVtype.MRB_TT_FALSE));
}

[Test]
public void True()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe("true").RawValue;
Assert.That(value.IsTrue, Is.EqualTo(true));
Assert.That(value.TT, Is.EqualTo(MrbVtype.MRB_TT_TRUE));
}

[Test]
public void Symbol()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe(":abc").RawValue;
Assert.That(value.IsSymbol, Is.EqualTo(true));
Assert.That(value.TT, Is.EqualTo(MrbVtype.MRB_TT_SYMBOL));
Assert.That(value.ToString(context), Is.EqualTo("abc"));
}

[Test]
public void String()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe("'aiueo'").RawValue;
Assert.That(value.IsObject, Is.EqualTo(true));
Assert.That(value.TT, Is.EqualTo(MrbVtype.MRB_TT_STRING));
Assert.That(value.ToString(context), Is.EqualTo("aiueo"));
}

[Test]
public void Int64Max()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe("9223372036854775807").RawValue;
Assert.That(value.IntValue, Is.EqualTo(9223372036854775807));
Assert.That(value.IsFixnum, Is.False);
Assert.That(value.TT, Is.EqualTo(MrbVtype.MRB_TT_INTEGER));
}

[Test]
public void Float()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe("0.1234567").RawValue;
Assert.That(value.FloatValue, Is.EqualTo(0.1234567));
Assert.That(value.IsFloat, Is.True);
Assert.That(value.TT, Is.EqualTo(MrbVtype.MRB_TT_FLOAT));
}

[Test]
public void Array()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe("[1]").RawValue;
Assert.That(value.IsObject, Is.True);
Assert.That(value.TT, Is.EqualTo(MrbVtype.MRB_TT_ARRAY));
}

[Test]
public void Hash()
{
using var context = MRubyContext.Create();
var value = context.EvaluateUnsafe("{ a: 1 }").RawValue;
Assert.That(value.IsObject, Is.True);
Assert.That(value.TT, Is.EqualTo(MrbVtype.MRB_TT_HASH));
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/vitalrouter-mruby/build_config.android.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
conf.gembox '../../../vitalrouter'

conf.disable_presym
conf.cc.defines = %w(MRB_NO_BOXING MRB_NO_STDIO MRB_NO_PRESYM)
conf.cc.defines = %w(MRB_WORD_BOXING MRB_NO_STDIO MRB_NO_PRESYM)
conf.cc.flags << '-Os'
end

Expand All @@ -13,6 +13,6 @@
conf.gembox '../../../vitalrouter'

conf.disable_presym
conf.cc.defines = %w(MRB_NO_BOXING MRB_NO_STDIO MRB_NO_PRESYM)
conf.cc.defines = %w(MRB_WORD_BOXING MRB_NO_STDIO MRB_NO_PRESYM)
conf.cc.flags << '-Os'
end
4 changes: 2 additions & 2 deletions src/vitalrouter-mruby/build_config.ios.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
conf.disable_presym

conf.cc do |cc|
cc.defines = %w(MRB_NO_BOXING MRB_NO_STDIO MRB_NO_PRESYM)
cc.defines = %w(MRB_WORD_BOXING MRB_NO_STDIO MRB_NO_PRESYM)
cc.command = 'xcrun'
cc.flags = %W(-sdk iphoneos clang -arch arm64 -isysroot "#{sdk}" -mios-version-min=#{IOS_VERSION_MIN} -g -Os -Wall -Werror-implicit-function-declaration)
end
Expand All @@ -25,7 +25,7 @@
conf.disable_presym

conf.cc do |cc|
cc.defines = %w(MRB_NO_BOXING MRB_NO_STDIO MRB_NO_PRESYM)
cc.defines = %w(MRB_WORD_BOXING MRB_NO_STDIO MRB_NO_PRESYM)
cc.command = 'xcrun'
cc.flags = %W(-sdk iphonesimulator clang -arch x86_64 -isysroot "#{sdk}" -mios-version-min=#{IOS_VERSION_MIN} -g -Os -Wall -Werror-implicit-function-declaration)
end
Expand Down
Loading

0 comments on commit 90760f2

Please sign in to comment.