diff --git a/csharp/msgpack.tests/CompiledPackerTests.cs b/csharp/msgpack.tests/CompiledPackerTests.cs new file mode 100644 index 00000000..8e5a5161 --- /dev/null +++ b/csharp/msgpack.tests/CompiledPackerTests.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using TestA_Class = msgpack.tests.ObjectPackerTests.TestA_Class; +using TestB_Class = msgpack.tests.ObjectPackerTests.TestB_Class; + +namespace msgpack.tests +{ + [TestFixture] + public class CompiledPackerTests + { + [Test] + public void TestA () + { + TestA_Class obj0 = new TestA_Class (); + TestA_Class obj1 = CompiledPacker.Unpack (CompiledPacker.Pack (obj0)); + obj0.Check (obj1); + } + + [Test] + public void TestB () + { + TestB_Class obj0 = TestB_Class.Create (); + TestB_Class obj1 = CompiledPacker.Unpack (CompiledPacker.Pack (obj0)); + obj0.Check (obj1); + } + } +} diff --git a/csharp/msgpack.tests/ObjectPackerTests.cs b/csharp/msgpack.tests/ObjectPackerTests.cs index b52e87c9..70ec9e08 100644 --- a/csharp/msgpack.tests/ObjectPackerTests.cs +++ b/csharp/msgpack.tests/ObjectPackerTests.cs @@ -7,12 +7,6 @@ namespace msgpack.tests [TestFixture] public class ObjectPackerTests { - public static void Main () - { - ObjectPackerTests tests = new ObjectPackerTests (); - tests.TestA (); - } - [Test] public void TestA () { @@ -26,16 +20,12 @@ namespace msgpack.tests public void TestB () { ObjectPacker packer = new ObjectPacker (); - Dictionary dic = new Dictionary (); - Random rnd = new Random (); - int size = rnd.Next () & 0xff; - for (int i = 0; i < size; i ++) - dic[rnd.Next()] = rnd.Next (); - Dictionary dic_ = packer.Unpack> (packer.Pack (dic)); - Assert.AreEqual (dic, dic_); + TestB_Class obj0 = TestB_Class.Create (); + TestB_Class obj1 = packer.Unpack (packer.Pack (obj0)); + obj0.Check (obj1); } - class TestA_Class + public class TestA_Class { public bool a; public byte b; @@ -91,5 +81,36 @@ namespace msgpack.tests Assert.AreEqual (this.m, other.m); } } + + public class TestB_Class + { + public TestA_Class x; + public TestB_Class nested; + public int[] list; + + public static TestB_Class Create () + { + return Create (0); + } + + static TestB_Class Create (int level) + { + TestB_Class obj = new TestB_Class (); + obj.x = new TestA_Class (); + if (level < 10) + obj.nested = Create (level + 1); + if ((level % 2) == 0) + obj.list = new int[] {level, 0, level, int.MaxValue, level}; + return obj; + } + + public void Check (TestB_Class other) + { + x.Check (other.x); + if (nested != null) + nested.Check (other.nested); + Assert.AreEqual (list, other.list); + } + } } } diff --git a/csharp/msgpack.tests/msgpack.tests.csproj b/csharp/msgpack.tests/msgpack.tests.csproj index 15ceda98..1991021c 100644 --- a/csharp/msgpack.tests/msgpack.tests.csproj +++ b/csharp/msgpack.tests/msgpack.tests.csproj @@ -44,6 +44,7 @@ + diff --git a/csharp/msgpack/CompiledPacker.cs b/csharp/msgpack/CompiledPacker.cs new file mode 100644 index 00000000..7bb6b622 --- /dev/null +++ b/csharp/msgpack/CompiledPacker.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using msgpack.Compiler; + +namespace msgpack +{ + public class CompiledPacker + { + public static byte[] Pack (object o) + { + using (MemoryStream ms = new MemoryStream ()) { + Pack (ms, o); + return ms.ToArray (); + } + } + + public static void Pack (Stream strm, object o) + { + PackerCompiler.GetPackMethod (o.GetType ()) (new MsgPackWriter (strm), o); + } + + public static byte[] Pack (T o) + { + using (MemoryStream ms = new MemoryStream ()) { + Pack (ms, o); + return ms.ToArray (); + } + } + + public static void Pack (Stream strm, T o) + { + PackerCompiler.GetPackMethod () (new MsgPackWriter (strm), o); + } + + public static T Unpack (byte[] buf) + { + return Unpack (buf, 0, buf.Length); + } + + public static T Unpack (byte[] buf, int offset, int size) + { + using (MemoryStream ms = new MemoryStream (buf, offset, size)) { + return Unpack (ms); + } + } + + public static T Unpack (Stream strm) + { + return PackerCompiler.GetUnpackMethod () (new MsgPackReader (strm)); + } + + public static object Unpack (Type t, byte[] buf) + { + return Unpack (t, buf, 0, buf.Length); + } + + public static object Unpack (Type t, byte[] buf, int offset, int size) + { + using (MemoryStream ms = new MemoryStream (buf, offset, size)) { + return Unpack (t, ms); + } + } + + public static object Unpack (Type t, Stream strm) + { + throw new NotImplementedException (); + } + } +} diff --git a/csharp/msgpack/Compiler/EmitExtensions.cs b/csharp/msgpack/Compiler/EmitExtensions.cs new file mode 100644 index 00000000..ae51e9a8 --- /dev/null +++ b/csharp/msgpack/Compiler/EmitExtensions.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using System.Reflection.Emit; + +namespace msgpack.Compiler +{ + public static class EmitExtensions + { + public static void EmitLd (this ILGenerator il, Variable v) + { + switch (v.VarType) { + case VariableType.Arg: + EmitLdarg (il, v); + break; + case VariableType.Local: + EmitLdloc (il, v); + break; + default: + throw new ArgumentException (); + } + } + + public static void EmitLd (this ILGenerator il, params Variable[] list) + { + for (int i = 0; i < list.Length; i ++) + EmitLd (il, list[i]); + } + + public static void EmitLdarg (this ILGenerator il, Variable v) + { + if (v.VarType != VariableType.Arg) + throw new ArgumentException (); + + switch (v.Index) { + case 0: il.Emit (OpCodes.Ldarg_0); return; + case 1: il.Emit (OpCodes.Ldarg_1); return; + case 2: il.Emit (OpCodes.Ldarg_2); return; + case 3: il.Emit (OpCodes.Ldarg_3); return; + } + if (v.Index <= byte.MaxValue) { + il.Emit (OpCodes.Ldarg_S, (byte)v.Index); + } else if (v.Index <= short.MaxValue) { + il.Emit (OpCodes.Ldarg, v.Index); + } else { + throw new FormatException (); + } + } + + public static void EmitLdloc (this ILGenerator il, Variable v) + { + if (v.VarType != VariableType.Local) + throw new ArgumentException (); + + switch (v.Index) { + case 0: il.Emit (OpCodes.Ldloc_0); return; + case 1: il.Emit (OpCodes.Ldloc_1); return; + case 2: il.Emit (OpCodes.Ldloc_2); return; + case 3: il.Emit (OpCodes.Ldloc_3); return; + } + if (v.Index <= byte.MaxValue) { + il.Emit (OpCodes.Ldloc_S, (byte)v.Index); + } else if (v.Index <= short.MaxValue) { + il.Emit (OpCodes.Ldloc, v.Index); + } else { + throw new FormatException (); + } + } + + public static void EmitSt (this ILGenerator il, Variable v) + { + switch (v.VarType) { + case VariableType.Arg: + EmitStarg (il, v); + break; + case VariableType.Local: + EmitStloc (il, v); + break; + default: + throw new ArgumentException (); + } + } + + public static void EmitStarg (this ILGenerator il, Variable v) + { + if (v.VarType != VariableType.Arg) + throw new ArgumentException (); + + if (v.Index <= byte.MaxValue) { + il.Emit (OpCodes.Starg_S, (byte)v.Index); + } else if (v.Index <= short.MaxValue) { + il.Emit (OpCodes.Starg, v.Index); + } else { + throw new FormatException (); + } + } + + public static void EmitStloc (this ILGenerator il, Variable v) + { + if (v.VarType != VariableType.Local) + throw new ArgumentException (); + + switch (v.Index) { + case 0: il.Emit (OpCodes.Stloc_0); return; + case 1: il.Emit (OpCodes.Stloc_1); return; + case 2: il.Emit (OpCodes.Stloc_2); return; + case 3: il.Emit (OpCodes.Stloc_3); return; + } + if (v.Index <= byte.MaxValue) { + il.Emit (OpCodes.Stloc_S, (byte)v.Index); + } else if (v.Index <= short.MaxValue) { + il.Emit (OpCodes.Stloc, v.Index); + } else { + throw new FormatException (); + } + } + + public static void EmitLdc (this ILGenerator il, int v) + { + switch (v) { + case 0: il.Emit (OpCodes.Ldc_I4_0); return; + case 1: il.Emit (OpCodes.Ldc_I4_1); return; + case 2: il.Emit (OpCodes.Ldc_I4_2); return; + case 3: il.Emit (OpCodes.Ldc_I4_3); return; + case 4: il.Emit (OpCodes.Ldc_I4_4); return; + case 5: il.Emit (OpCodes.Ldc_I4_5); return; + case 6: il.Emit (OpCodes.Ldc_I4_6); return; + case 7: il.Emit (OpCodes.Ldc_I4_7); return; + case 8: il.Emit (OpCodes.Ldc_I4_8); return; + case -1: il.Emit (OpCodes.Ldc_I4_M1); return; + } + if (v <= sbyte.MaxValue && v >= sbyte.MinValue) { + il.Emit (OpCodes.Ldc_I4_S, (sbyte)v); + } else { + il.Emit (OpCodes.Ldc_I4, v); + } + } + + public static void EmitLd_False (this ILGenerator il) + { + il.Emit (OpCodes.Ldc_I4_1); + } + + public static void EmitLd_True (this ILGenerator il) + { + il.Emit (OpCodes.Ldc_I4_1); + } + + public static void EmitLdstr (this ILGenerator il, string v) + { + il.Emit (OpCodes.Ldstr, v); + } + + public static void EmitLdMember (this ILGenerator il, MemberInfo m) + { + if (m.MemberType == MemberTypes.Field) { + il.Emit (OpCodes.Ldfld, (FieldInfo)m); + } else if (m.MemberType == MemberTypes.Property) { + il.Emit (OpCodes.Callvirt, ((PropertyInfo)m).GetGetMethod (true)); + } else { + throw new ArgumentException (); + } + } + + public static void EmitStMember (this ILGenerator il, MemberInfo m) + { + if (m.MemberType == MemberTypes.Field) { + il.Emit (OpCodes.Stfld, (FieldInfo)m); + } else if (m.MemberType == MemberTypes.Property) { + il.Emit (OpCodes.Callvirt, ((PropertyInfo)m).GetSetMethod (true)); + } else { + throw new ArgumentException (); + } + } + } +} diff --git a/csharp/msgpack/Compiler/PackerCompiler.cs b/csharp/msgpack/Compiler/PackerCompiler.cs new file mode 100644 index 00000000..07767177 --- /dev/null +++ b/csharp/msgpack/Compiler/PackerCompiler.cs @@ -0,0 +1,643 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; +using System.Runtime.Serialization; + +namespace msgpack.Compiler +{ + public static class PackerCompiler + { + static Dictionary CompiledPackMethods; + static Dictionary PackDelegates; + static Dictionary> PackWrapperDelegates; + + static Dictionary CompiledUnpackMethods; + static Dictionary UnpackDelegates; + static Dictionary> UnpackFieldMapping; + + static Dictionary UserDefinedPackMethods; + + static PackerCompiler () + { + CompiledPackMethods = new Dictionary (); + PackDelegates = new Dictionary (); + PackWrapperDelegates = new Dictionary> (); + UserDefinedPackMethods = new Dictionary (); + + CompiledUnpackMethods = new Dictionary (); + UnpackDelegates = new Dictionary (); + UnpackFieldMapping = new Dictionary> (); + + RegisterDefaultUnpackMethods (); + RegisterUserDefinedMethods (); + } + + #region Pack Frontend / Compiler + public static Action GetPackMethod () + { + Delegate d; + lock (CompiledPackMethods) { + PackDelegates.TryGetValue (typeof (T), out d); + } + if (d == null) + CreatePackMethod (typeof (T)); + lock (CompiledPackMethods) { + d = PackDelegates[typeof (T)]; + } + return (Action)d; + } + + public static Action GetPackMethod (Type t) + { + Action d; + lock (CompiledPackMethods) { + PackWrapperDelegates.TryGetValue (t, out d); + } + if (d == null) + CreatePackMethod (t); + lock (CompiledPackMethods) { + d = PackWrapperDelegates[t]; + } + return d; + } + + static DynamicMethod CreatePackMethod (Type t) + { + DynamicMethod methodBuilder; + lock (CompiledPackMethods) { + if (CompiledPackMethods.TryGetValue (t, out methodBuilder)) { + return CompiledPackMethods[t]; + } else { + methodBuilder = CreateDynamicMethod (typeof (void), new Type[]{typeof (MsgPackWriter), t}); + CompiledPackMethods.Add (t, methodBuilder); + CreatePackMethod (t, methodBuilder); + PackDelegates.Add (t, methodBuilder.CreateDelegate (typeof (Action<,>).MakeGenericType (typeof (MsgPackWriter), t))); + Action wrapper = CreatePackWrapper (t, methodBuilder); + PackWrapperDelegates.Add (t, wrapper); + } + } + return methodBuilder; + } + + static void CreatePackMethod (Type t, DynamicMethod methodBuilder) + { + if (t == null || methodBuilder == null) + throw new ArgumentNullException (); + if (t.IsPrimitive || t.IsInterface) + throw new NotSupportedException (); + + ILGenerator il = methodBuilder.GetILGenerator(); + + Variable arg_writer = Variable.CreateArg (0); + Variable arg_obj = Variable.CreateArg (1); + Variable local_i = Variable.CreateLocal (il.DeclareLocal (typeof (int))); + + if (!t.IsValueType) { // null check + Label notNullLabel = il.DefineLabel (); + il.EmitLd (arg_obj); + il.Emit (OpCodes.Brtrue_S, notNullLabel); + il.EmitLd (arg_writer); + il.Emit (OpCodes.Call, typeof(MsgPackWriter).GetMethod("WriteNil", new Type[0])); + il.Emit (OpCodes.Ret); + il.MarkLabel (notNullLabel); + } + + MethodInfo udm; + lock (UserDefinedPackMethods) { + UserDefinedPackMethods.TryGetValue (t, out udm); + } + if (udm != null) { + il.EmitLd (arg_writer, arg_obj); + il.Emit (OpCodes.Call, udm); + goto FinallyProcess; + } + + if (t.IsArray) { + EmitArrayWriteProcess (il, t, arg_writer, arg_obj, local_i); + goto FinallyProcess; + } + + // MsgPackWriter.WriteMapHeader + MemberInfo[] members = LookupMembers (t); + il.EmitLd (arg_writer); + il.EmitLdc (members.Length); + il.Emit (OpCodes.Callvirt, typeof (MsgPackWriter).GetMethod("WriteMapHeader", new Type[]{typeof (int)})); + + for (int i = 0; i < members.Length; i ++) { + MemberInfo m = members[i]; + Type mt = m.GetMemberType (); + + // write field-name + il.EmitLd (arg_writer); + il.EmitLdstr (FormatMemberName (m)); + il.EmitLd_True (); + il.Emit (OpCodes.Call, typeof (MsgPackWriter).GetMethod("Write", new Type[]{typeof (string), typeof (bool)})); + + // write value + CreatePackMethod_EmitMemberValue (il, arg_writer, arg_obj, m, null, mt, t, methodBuilder); + } + +FinallyProcess: + il.Emit (OpCodes.Ret); + } + + static void EmitArrayWriteProcess (ILGenerator il, Type t, Variable var_writer, Variable var_obj, Variable var_loop) + { + Type et = t.GetElementType (); + il.EmitLd (var_writer, var_obj); + il.Emit (OpCodes.Ldlen); + il.Emit (OpCodes.Call, typeof(MsgPackWriter).GetMethod("WriteArrayHeader", new Type[]{ typeof(int) })); + + Label beginLabel = il.DefineLabel (); + Label exprLabel = il.DefineLabel (); + + // for-loop: init loop counter + il.EmitLdc (0); + il.EmitSt (var_loop); + + // jump + il.Emit (OpCodes.Br_S, exprLabel); + + // mark begin-label + il.MarkLabel (beginLabel); + + // write element + CreatePackMethod_EmitMemberValue (il, var_writer, var_obj, null, var_loop, et, null, null); + + // increment loop-counter + il.EmitLd (var_loop); + il.Emit (OpCodes.Ldc_I4_1); + il.Emit (OpCodes.Add); + il.EmitSt (var_loop); + + // mark expression label + il.MarkLabel (exprLabel); + + // expression + il.EmitLd (var_loop, var_obj); + il.Emit (OpCodes.Ldlen); + il.Emit (OpCodes.Blt_S, beginLabel); + } + + /// (optional) + /// (optional) + static void CreatePackMethod_EmitMemberValue (ILGenerator il, Variable var_writer, Variable var_obj, MemberInfo m, Variable elementIdx, Type type, Type currentType, DynamicMethod currentMethod) + { + MethodInfo mi; + il.EmitLd (var_writer, var_obj); + if (m != null) + il.EmitLdMember (m); + if (elementIdx != null) { + il.EmitLd (elementIdx); + il.Emit (OpCodes.Ldelem, type); + } + if (type.IsPrimitive) { + mi = typeof(MsgPackWriter).GetMethod("Write", new Type[]{type}); + } else { + if (currentType == type) { + mi = currentMethod; + } else { + lock (UserDefinedPackMethods) { + UserDefinedPackMethods.TryGetValue (type, out mi); + } + if (mi == null) + mi = CreatePackMethod (type); + } + } + il.Emit (OpCodes.Call, mi); + } + + static Action CreatePackWrapper (Type t, DynamicMethod packMethod) + { + DynamicMethod dm = CreateDynamicMethod (typeof (void), new Type[] {typeof (MsgPackWriter), typeof (object)}); + ILGenerator il = dm.GetILGenerator(); + il.EmitLd (Variable.CreateArg (0), Variable.CreateArg (1)); + il.Emit (OpCodes.Castclass, t); + il.Emit (OpCodes.Call, packMethod); + il.Emit (OpCodes.Ret); + return (Action)dm.CreateDelegate (typeof (Action)); + } + #endregion + + #region Unpack Frontend / Compiler + public static Func GetUnpackMethod () + { + Delegate d; + lock (CompiledUnpackMethods) { + UnpackDelegates.TryGetValue (typeof (T), out d); + } + if (d == null) + CreateUnpackMethod (typeof (T)); + lock (CompiledUnpackMethods) { + d = UnpackDelegates[typeof (T)]; + } + return (Func)d; + } + + static MethodInfo CreateUnpackMethod (Type t) + { + MethodInfo methodBuilder; + lock (CompiledUnpackMethods) { + if (CompiledUnpackMethods.TryGetValue (t, out methodBuilder)) { + return methodBuilder; + } else { + methodBuilder = CreateDynamicMethod (t, new Type[]{typeof (MsgPackReader)}); + CompiledUnpackMethods.Add (t, methodBuilder); + UnpackFieldMapping.Add (t, new Dictionary ()); + if (t.IsArray) { + CreateUnpackArrayMethod (t, t.GetElementType (), (DynamicMethod)methodBuilder); + } else { + CreateUnpackMethod (t, (DynamicMethod)methodBuilder); + } + UnpackDelegates.Add (t, ((DynamicMethod)methodBuilder).CreateDelegate (typeof (Func<,>).MakeGenericType (typeof (MsgPackReader), t))); + } + } + return methodBuilder; + } + + static void CreateUnpackMethod (Type t, DynamicMethod methodBuilder) + { + ILGenerator il = methodBuilder.GetILGenerator (); + MethodInfo failedMethod = typeof (PackerCompiler).GetMethod ("UnpackFailed", BindingFlags.Static | BindingFlags.NonPublic); + MemberInfo[] members = LookupMembers (t); + Dictionary member_mapping = UnpackFieldMapping[t]; + for (int i = 0; i < members.Length; i ++) + member_mapping.Add (FormatMemberName (members[i]), i); + + Variable msgpackReader = Variable.CreateArg (0); + Variable obj = Variable.CreateLocal (il.DeclareLocal (t)); + Variable num_of_fields = Variable.CreateLocal (il.DeclareLocal (typeof (int))); + Variable loop_idx = Variable.CreateLocal (il.DeclareLocal (typeof (int))); + Variable mapping = Variable.CreateLocal (il.DeclareLocal (typeof (Dictionary))); + Variable switch_idx = Variable.CreateLocal (il.DeclareLocal (typeof (int))); + Variable type = Variable.CreateLocal (il.DeclareLocal (typeof (Type))); + + // if (!MsgPackReader.Read()) UnpackFailed (); + // if (MsgPackReader.Type == TypePrefixes.Nil) return null; + // if (!MsgPackReader.IsMap ()) UnpackFailed (); + EmitReadAndTypeCheck (il, msgpackReader, typeof (MsgPackReader).GetMethod ("IsMap"), failedMethod, true); + + // type = typeof (T) + il.Emit (OpCodes.Ldtoken, t); + il.Emit (OpCodes.Call, typeof(Type).GetMethod ("GetTypeFromHandle")); + il.EmitSt (type); + + // mapping = LookupMemberMapping (typeof (T)) + il.EmitLd (type); + il.Emit (OpCodes.Call, typeof (PackerCompiler).GetMethod ("LookupMemberMapping", BindingFlags.Static | BindingFlags.NonPublic)); + il.EmitSt (mapping); + + // object o = FormatterServices.GetUninitializedObject (Type); + il.EmitLd (type); + il.Emit (OpCodes.Call, typeof (FormatterServices).GetMethod ("GetUninitializedObject")); + il.Emit (OpCodes.Castclass, t); + il.EmitSt (obj); + + // num_of_fields = (int)reader.Length + il.EmitLd (msgpackReader); + il.Emit (OpCodes.Call, typeof (MsgPackReader).GetProperty ("Length").GetGetMethod ()); + il.EmitSt (num_of_fields); + + // Loop labels + Label lblLoopStart = il.DefineLabel (); + Label lblLoopExpr = il.DefineLabel (); + + // i = 0; + il.EmitLdc (0); + il.EmitSt (loop_idx); + il.Emit (OpCodes.Br, lblLoopExpr); + il.MarkLabel (lblLoopStart); + + /* process */ + // if (!MsgPackReader.Read() || !MsgPackReader.IsRaw()) UnpackFailed(); + EmitReadAndTypeCheck (il, msgpackReader, typeof (MsgPackReader).GetMethod ("IsRaw"), failedMethod, false); + + // MsgPackReader.ReadRawString () + // if (!Dictionary.TryGetValue (,)) UnpackFailed(); + Label lbl3 = il.DefineLabel (); + il.EmitLd (mapping); + il.EmitLd (msgpackReader); + il.Emit (OpCodes.Call, typeof (MsgPackReader).GetMethod ("ReadRawString", new Type[0])); + il.Emit (OpCodes.Ldloca_S, (short)switch_idx.Index); + il.Emit (OpCodes.Call, typeof (Dictionary).GetMethod ("TryGetValue")); + il.Emit (OpCodes.Brtrue, lbl3); + il.Emit (OpCodes.Call, failedMethod); + il.MarkLabel (lbl3); + + // switch + Label[] switchCases = new Label[members.Length]; + for (int i = 0; i < switchCases.Length; i ++) + switchCases[i] = il.DefineLabel (); + Label switchCaseEndLabel = il.DefineLabel (); + il.EmitLd (switch_idx); + il.Emit (OpCodes.Switch, switchCases); + il.Emit (OpCodes.Call, failedMethod); + + for (int i = 0; i < switchCases.Length; i ++) { + il.MarkLabel (switchCases[i]); + MemberInfo mi = members[i]; + Type mt = mi.GetMemberType (); + MethodInfo unpack_method; + + il.EmitLd (obj); + if (!CompiledUnpackMethods.TryGetValue (mt, out unpack_method)) { + unpack_method = CreateUnpackMethod (mt); + } + il.EmitLd (msgpackReader); + il.Emit (OpCodes.Call, unpack_method); + il.EmitStMember (mi); + il.Emit (OpCodes.Br, switchCaseEndLabel); + } + il.MarkLabel (switchCaseEndLabel); + + // i ++ + il.EmitLd (loop_idx); + il.EmitLdc (1); + il.Emit (OpCodes.Add); + il.EmitSt (loop_idx); + + // i < num_of_fields; + il.MarkLabel (lblLoopExpr); + il.EmitLd (loop_idx); + il.EmitLd (num_of_fields); + il.Emit (OpCodes.Blt, lblLoopStart); + + // return + il.EmitLd (obj); + il.Emit (OpCodes.Ret); + } + + static void CreateUnpackArrayMethod (Type arrayType, Type elementType, DynamicMethod methodBuilder) + { + ILGenerator il = methodBuilder.GetILGenerator (); + MethodInfo failedMethod = typeof (PackerCompiler).GetMethod ("UnpackFailed", BindingFlags.Static | BindingFlags.NonPublic); + + Variable msgpackReader = Variable.CreateArg (0); + Variable obj = Variable.CreateLocal (il.DeclareLocal (arrayType)); + Variable num_of_elements = Variable.CreateLocal (il.DeclareLocal (typeof (int))); + Variable loop_idx = Variable.CreateLocal (il.DeclareLocal (typeof (int))); + Variable type = Variable.CreateLocal (il.DeclareLocal (typeof (Type))); + + // if (!MsgPackReader.Read() || !MsgPackReader.IsArray ()) UnpackFailed (); + EmitReadAndTypeCheck (il, msgpackReader, typeof (MsgPackReader).GetMethod ("IsArray"), failedMethod, true); + + // type = typeof (T) + il.Emit (OpCodes.Ldtoken, elementType); + il.Emit (OpCodes.Call, typeof(Type).GetMethod ("GetTypeFromHandle")); + il.EmitSt (type); + + // num_of_elements = (int)reader.Length + il.EmitLd (msgpackReader); + il.Emit (OpCodes.Call, typeof (MsgPackReader).GetProperty ("Length").GetGetMethod ()); + il.EmitSt (num_of_elements); + + // object o = Array.CreateInstance (Type, Length); + il.EmitLd (type); + il.EmitLd (num_of_elements); + il.Emit (OpCodes.Call, typeof (Array).GetMethod ("CreateInstance", new Type[] {typeof (Type), typeof (int)})); + il.Emit (OpCodes.Castclass, arrayType); + il.EmitSt (obj); + + // Unpack element method + MethodInfo unpack_method; + lock (CompiledUnpackMethods) { + if (!CompiledUnpackMethods.TryGetValue (elementType, out unpack_method)) { + unpack_method = CreateUnpackMethod (elementType); + } + } + + // Loop labels + Label lblLoopStart = il.DefineLabel (); + Label lblLoopExpr = il.DefineLabel (); + + // i = 0; + il.EmitLdc (0); + il.EmitSt (loop_idx); + il.Emit (OpCodes.Br, lblLoopExpr); + il.MarkLabel (lblLoopStart); + + /* process */ + il.EmitLd (obj, loop_idx); + il.EmitLd (msgpackReader); + il.Emit (OpCodes.Call, unpack_method); + il.Emit (OpCodes.Stelem, elementType); + + // i ++ + il.EmitLd (loop_idx); + il.EmitLdc (1); + il.Emit (OpCodes.Add); + il.EmitSt (loop_idx); + + // i < num_of_fields; + il.MarkLabel (lblLoopExpr); + il.EmitLd (loop_idx); + il.EmitLd (num_of_elements); + il.Emit (OpCodes.Blt, lblLoopStart); + + // return + il.EmitLd (obj); + il.Emit (OpCodes.Ret); + } + + static void EmitReadAndTypeCheck (ILGenerator il, Variable msgpackReader, MethodInfo typeCheckMethod, MethodInfo failedMethod, bool nullCheckAndReturn) + { + Label lblFailed = il.DefineLabel (); + Label lblNullReturn = il.DefineLabel (); + Label lblPassed = il.DefineLabel (); + il.EmitLd (msgpackReader); + il.Emit (OpCodes.Call, typeof (MsgPackReader).GetMethod ("Read")); + il.Emit (OpCodes.Brfalse_S, lblFailed); + if (nullCheckAndReturn) { + il.EmitLd (msgpackReader); + il.Emit (OpCodes.Call, typeof (MsgPackReader).GetProperty ("Type").GetGetMethod ()); + il.EmitLdc ((int)TypePrefixes.Nil); + il.Emit (OpCodes.Beq_S, lblNullReturn); + } + il.EmitLd (msgpackReader); + il.Emit (OpCodes.Call, typeCheckMethod); + il.Emit (OpCodes.Brtrue_S, lblPassed); + if (nullCheckAndReturn) { + il.MarkLabel (lblNullReturn); + il.Emit (OpCodes.Ldnull); + il.Emit (OpCodes.Ret); + } + il.MarkLabel (lblFailed); + il.Emit (OpCodes.Call, failedMethod); + il.MarkLabel (lblPassed); + } + + /// Exception Helper + static void UnpackFailed () + { + throw new FormatException (); + } + + static Dictionary LookupMemberMapping (Type t) + { + lock (CompiledUnpackMethods) { + return UnpackFieldMapping[t]; + } + } + + static void RegisterDefaultUnpackMethods () + { + BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic; + MethodInfo mi = typeof (PackerCompiler).GetMethod ("Unpack_Signed", flags); + CompiledUnpackMethods.Add (typeof (sbyte), mi); + CompiledUnpackMethods.Add (typeof (short), mi); + CompiledUnpackMethods.Add (typeof (int), mi); + + mi = typeof (PackerCompiler).GetMethod ("Unpack_Signed64", flags); + CompiledUnpackMethods.Add (typeof (long), mi); + + mi = typeof (PackerCompiler).GetMethod ("Unpack_Unsigned", flags); + CompiledUnpackMethods.Add (typeof (byte), mi); + CompiledUnpackMethods.Add (typeof (ushort), mi); + CompiledUnpackMethods.Add (typeof (char), mi); + CompiledUnpackMethods.Add (typeof (uint), mi); + + mi = typeof (PackerCompiler).GetMethod ("Unpack_Unsigned64", flags); + CompiledUnpackMethods.Add (typeof (ulong), mi); + + mi = typeof (PackerCompiler).GetMethod ("Unpack_Boolean", flags); + CompiledUnpackMethods.Add (typeof (bool), mi); + + mi = typeof (PackerCompiler).GetMethod ("Unpack_Float", flags); + CompiledUnpackMethods.Add (typeof (float), mi); + + mi = typeof (PackerCompiler).GetMethod ("Unpack_Double", flags); + CompiledUnpackMethods.Add (typeof (double), mi); + + mi = typeof (PackerCompiler).GetMethod ("Unpack_String", flags); + CompiledUnpackMethods.Add (typeof (string), mi); + } + + static int Unpack_Signed (MsgPackReader reader) + { + if (!reader.Read () || !reader.IsSigned ()) + UnpackFailed (); + return reader.ValueSigned; + } + + static long Unpack_Signed64 (MsgPackReader reader) + { + if (!reader.Read ()) + UnpackFailed (); + if (reader.IsSigned ()) + return reader.ValueSigned; + if (reader.IsSigned64 ()) + return reader.ValueSigned64; + UnpackFailed (); + return 0; // unused + } + + static uint Unpack_Unsigned (MsgPackReader reader) + { + if (!reader.Read () || !reader.IsUnsigned ()) + UnpackFailed (); + return reader.ValueUnsigned; + } + + static ulong Unpack_Unsigned64 (MsgPackReader reader) + { + if (!reader.Read ()) + UnpackFailed (); + if (reader.IsUnsigned ()) + return reader.ValueUnsigned; + if (reader.IsUnsigned64 ()) + return reader.ValueUnsigned64; + UnpackFailed (); + return 0; // unused + } + + static bool Unpack_Boolean (MsgPackReader reader) + { + if (!reader.Read () || !reader.IsBoolean ()) + UnpackFailed (); + return reader.ValueBoolean; + } + + static float Unpack_Float (MsgPackReader reader) + { + if (!reader.Read () || reader.Type != TypePrefixes.Float) + UnpackFailed (); + return reader.ValueFloat; + } + + static double Unpack_Double (MsgPackReader reader) + { + if (!reader.Read () || reader.Type != TypePrefixes.Double) + UnpackFailed (); + return reader.ValueDouble; + } + + static string Unpack_String (MsgPackReader reader) + { + if (!reader.Read () || !reader.IsRaw ()) + UnpackFailed (); + return reader.ReadRawString (); + } + #endregion + + #region User Defined Packer/Unpackers + static void RegisterUserDefinedMethods () + { + BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic; + Type type = typeof (PackerCompiler); + object[][] list = new object[][] { + new object[] {typeof (string), "PackString"} + }; + for (int i = 0; i < list.Length; i ++) + UserDefinedPackMethods.Add ((Type)list[i][0], type.GetMethod ((string)list[i][1], flags)); + } + + static void PackString (MsgPackWriter writer, string x) + { + writer.Write (x); + } + #endregion + + #region Misc + static int _dynamicMethodIdx = 0; + static DynamicMethod CreateDynamicMethod (Type returnType, Type[] parameterTypes) + { + string name = "_" + Interlocked.Increment (ref _dynamicMethodIdx).ToString (); + return new DynamicMethod (name, returnType, parameterTypes, true); + } + + static MemberInfo[] LookupMembers (Type t) + { + BindingFlags baseFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + List list = new List (); + list.AddRange (t.GetFields (baseFlags)); + //list.AddRange (t.GetProperties (baseFlags)); // TODO: + return list.ToArray (); + } + + static string FormatMemberName (MemberInfo m) + { + if (m.MemberType != MemberTypes.Field) + return m.Name; + + int pos; + string name = m.Name; + if (name[0] == '<' && (pos = name.IndexOf ('>')) > 1) + name = name.Substring (1, pos - 1); // Auto-Property (\<.+\>) + return name; + } + + static Type GetMemberType (this MemberInfo mi) + { + if (mi.MemberType == MemberTypes.Field) + return ((FieldInfo)mi).FieldType; + if (mi.MemberType == MemberTypes.Property) + return ((PropertyInfo)mi).PropertyType; + throw new ArgumentException (); + } + #endregion + } +} diff --git a/csharp/msgpack/Compiler/Variable.cs b/csharp/msgpack/Compiler/Variable.cs new file mode 100644 index 00000000..bac34340 --- /dev/null +++ b/csharp/msgpack/Compiler/Variable.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection.Emit; + +namespace msgpack.Compiler +{ + public class Variable + { + Variable (VariableType type, int index) + { + this.VarType = type; + this.Index = index; + } + + public static Variable CreateLocal (LocalBuilder local) + { + return new Variable (VariableType.Local, local.LocalIndex); + } + + public static Variable CreateArg (int idx) + { + return new Variable (VariableType.Arg, idx); + } + + public VariableType VarType { get; set; } + public int Index { get; set; } + } +} diff --git a/csharp/msgpack/Compiler/VariableType.cs b/csharp/msgpack/Compiler/VariableType.cs new file mode 100644 index 00000000..9a0dbceb --- /dev/null +++ b/csharp/msgpack/Compiler/VariableType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace msgpack.Compiler +{ + public enum VariableType + { + Local, + Arg + } +} diff --git a/csharp/msgpack/MsgPackReader.cs b/csharp/msgpack/MsgPackReader.cs index 1a338e7f..06a37e06 100644 --- a/csharp/msgpack/MsgPackReader.cs +++ b/csharp/msgpack/MsgPackReader.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text; namespace msgpack { @@ -9,6 +10,10 @@ namespace msgpack byte[] _tmp0 = new byte[8]; byte[] _tmp1 = new byte[8]; + Encoding _encoding = Encoding.UTF8; + Decoder _decoder = Encoding.UTF8.GetDecoder (); + byte[] _buf = new byte[64]; + public MsgPackReader (Stream strm) { _strm = strm; @@ -213,5 +218,25 @@ namespace msgpack { return _strm.Read (buf, offset, count); } + + public string ReadRawString () + { + return ReadRawString (_buf); + } + + public unsafe string ReadRawString (byte[] buf) + { + if (this.Length < buf.Length) { + if (ReadValueRaw (buf, 0, (int)this.Length) != this.Length) + throw new FormatException (); + return _encoding.GetString (buf, 0, (int)this.Length); + } + + // Poor implementation + byte[] tmp = new byte[(int)this.Length]; + if (ReadValueRaw (tmp, 0, tmp.Length) != tmp.Length) + throw new FormatException (); + return _encoding.GetString (tmp); + } } } diff --git a/csharp/msgpack/MsgPackWriter.cs b/csharp/msgpack/MsgPackWriter.cs index abda9c01..78e347b9 100644 --- a/csharp/msgpack/MsgPackWriter.cs +++ b/csharp/msgpack/MsgPackWriter.cs @@ -8,7 +8,9 @@ namespace msgpack { Stream _strm; Encoding _encoding = Encoding.UTF8; + Encoder _encoder = Encoding.UTF8.GetEncoder (); byte[] _tmp = new byte[9]; + byte[] _buf = new byte[64]; public MsgPackWriter (Stream strm) { @@ -238,9 +240,55 @@ namespace msgpack } } - public void Write (String x) + public void Write (string x) { - Write (_encoding.GetBytes (x)); + Write (x, false); + } + + public void Write (string x, bool highProbAscii) + { + Write (x, _buf, highProbAscii); + } + + public void Write (string x, byte[] buf) + { + Write (x, buf, false); + } + + public unsafe void Write (string x, byte[] buf, bool highProbAscii) + { + Encoder encoder = _encoder; + fixed (char *pstr = x) + fixed (byte *pbuf = buf) { + if (highProbAscii && x.Length <= buf.Length) { + bool isAsciiFullCompatible = true; + for (int i = 0; i < x.Length; i ++) { + int v = (int)pstr[i]; + if (v > 0x7f) { + isAsciiFullCompatible = false; + break; + } + buf[i] = (byte)v; + } + if (isAsciiFullCompatible) { + WriteRawHeader (x.Length); + _strm.Write (buf, 0, x.Length); + return; + } + } + + WriteRawHeader (encoder.GetByteCount (pstr, x.Length, true)); + int str_len = x.Length; + char *p = pstr; + int convertedChars, bytesUsed; + bool completed = true; + while (str_len > 0 || !completed) { + encoder.Convert (p, str_len, pbuf, buf.Length, false, out convertedChars, out bytesUsed, out completed); + _strm.Write (buf, 0, bytesUsed); + str_len -= convertedChars; + p += convertedChars; + } + } } } } diff --git a/csharp/msgpack/ObjectPacker.cs b/csharp/msgpack/ObjectPacker.cs index c3529959..0dabd21c 100644 --- a/csharp/msgpack/ObjectPacker.cs +++ b/csharp/msgpack/ObjectPacker.cs @@ -83,7 +83,7 @@ namespace msgpack ReflectionCacheEntry entry = ReflectionCache.Lookup (t); writer.WriteMapHeader (entry.FieldMap.Count); foreach (KeyValuePair pair in entry.FieldMap) { - writer.Write (pair.Key); + writer.Write (pair.Key, _buf); object v = pair.Value.GetValue (o); if (pair.Value.FieldType.IsInterface && v != null) { writer.WriteArrayHeader (2); @@ -145,8 +145,10 @@ namespace msgpack return unpacker (this, reader); if (t.IsArray) { - if (!reader.Read () || !reader.IsArray ()) + if (!reader.Read () || (!reader.IsArray () && reader.Type != TypePrefixes.Nil)) throw new FormatException (); + if (reader.Type == TypePrefixes.Nil) + return null; Type et = t.GetElementType (); Array ary = Array.CreateInstance (et, (int)reader.Length); for (int i = 0; i < ary.Length; i ++) diff --git a/csharp/msgpack/msgpack.csproj b/csharp/msgpack/msgpack.csproj index 1a778ae8..2ecda117 100644 --- a/csharp/msgpack/msgpack.csproj +++ b/csharp/msgpack/msgpack.csproj @@ -1,4 +1,4 @@ - + Debug @@ -22,6 +22,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -30,6 +31,7 @@ TRACE prompt 4 + true @@ -38,6 +40,11 @@ + + + + +