DeserializeDelegate <T> GenerateDeserializer(List <SchemaMember> members)
        {
            var bufferArg    = Parameter(typeof(byte[]), "buffer");
            var refOffsetArg = Parameter(typeof(int).MakeByRefType(), "offset");
            var refValueArg  = Parameter(typeof(T).MakeByRefType(), "value");

            var block  = new List <Expression>();
            var locals = new List <ParameterExpression>();

            //
            // 1. Read existing values into locals (Why? See explanation at the end of the file)
            for (var i = 0; i < members.Count; i++)
            {
                var member = members[i].Member;

                // Read the data into a new local variable
                var tempStore = Variable(member.MemberType, member.Name + "_local");
                locals.Add(tempStore);

                // Init the local with the current value
                block.Add(Assign(tempStore, MakeMemberAccess(refValueArg, member.MemberInfo)));
            }

            //
            // 2. Deserialize using local variable (faster and more robust than working with field/prop directly)
            for (var i = 0; i < members.Count; i++)
            {
                var member    = members[i].Member;
                var tempStore = locals[i];

                var formatter         = _ceras.GetReferenceFormatter(member.MemberType);
                var deserializeMethod = formatter.GetType().GetMethod(nameof(IFormatter <int> .Deserialize));
                Debug.Assert(deserializeMethod != null, "Can't find deserialize method on formatter " + formatter.GetType().FullName);

                // Deserialize the data into the local
                var tempReadCall = Call(Constant(formatter), deserializeMethod, bufferArg, refOffsetArg, tempStore);
                block.Add(tempReadCall);
            }

            //
            // 3. Write back values in one batch
            for (int i = 0; i < members.Count; i++)
            {
                var sMember   = members[i];
                var member    = members[i].Member;
                var tempStore = locals[i];
                var type      = member.MemberType;


                if (member.MemberInfo is FieldInfo fieldInfo && fieldInfo.IsInitOnly)
                {
                    // Readonly field
                    DynamicFormatterHelpers.EmitReadonlyWriteBack(type, sMember.ReadonlyFieldHandling, fieldInfo, refValueArg, tempStore, block);
                }
Exemple #2
0
        /*
         *
         * internal static SerializeDelegate<T> GenerateSerializer2(CerasSerializer ceras, Schema schema, bool isSchemaFormatter, bool isStatic)
         * {
         *      var members = schema.Members;
         *      var refBufferArg = Parameter(typeof(byte[]).MakeByRefType(), "buffer");
         *      var refOffsetArg = Parameter(typeof(int).MakeByRefType(), "offset");
         *      var valueArg = Parameter(typeof(T), "value");
         *
         *      if (isStatic)
         *              valueArg = null;
         *
         *
         *      var body = new List<Expression>();
         *      var locals = new List<ParameterExpression>();
         *
         *      ParameterExpression startPos = null, size = null;
         *      if (isSchemaFormatter)
         *      {
         *              locals.Add(startPos = Variable(typeof(int), "startPos"));
         *              locals.Add(size = Variable(typeof(int), "size"));
         *      }
         *
         *
         *      Dictionary<Type, IFormatter> typeToFormatter = new Dictionary<Type, IFormatter>();
         *      foreach (var m in members.Where(m => !m.IsSkip).DistinctBy(m => m.MemberType))
         *              typeToFormatter.Add(m.MemberType, ceras.GetReferenceFormatter(m.MemberType));
         *
         *      Dictionary<Type, ConstantExpression> typeToFormatterExp = typeToFormatter.ToDictionary(x => x.Key, x => Constant(x.Value));
         *
         *
         *      //
         *      // Create tuple to hold all the formatters
         *      Type[] formatterTypes = typeToFormatter.Values.Select(f => f.GetType()).ToArray();
         *      var create = ReflectionHelper.ResolveMethod(typeof(Tuple), "Create", formatterTypes);
         *      var formatterContainer = create.Invoke(null, typeToFormatter.Values.Cast<object>().ToArray());
         *
         *      // Pass this as arg
         *      var tupleType = formatterContainer.GetType();
         *      var formattersArg = Parameter(tupleType, "formatterContainer");
         *      var newTypeToFormatter = new Dictionary<Type, (Expression getExp, object actualValue)>();
         *      foreach (var kvp in typeToFormatter)
         *      {
         *              var t = kvp.Key;
         *              var formatterInstance = kvp.Value;
         *              var prop = tupleType.GetProperties().First(p => p.PropertyType.FindClosedArg(typeof(IFormatter<>)) == t);
         *              newTypeToFormatter[t] = (MakeMemberAccess(formattersArg, prop), formatterInstance);
         *      }
         *      //
         *      //
         *
         *
         *      // Serialize all members
         *      foreach (var member in members)
         *      {
         *              if (member.IsSkip)
         *                      continue;
         *
         *              // Get the formatter and its Serialize method
         *              var formatterE = newTypeToFormatter[member.MemberType];
         *              var formatter = formatterE.actualValue;
         *              var serializeMethod = formatter.GetType().GetMethod(nameof(IFormatter<int>.Serialize));
         *              Debug.Assert(serializeMethod != null, "Can't find serialize method on formatter " + formatter.GetType().FullName);
         *
         *              // Access the field that we want to serialize
         *              var fieldExp = MakeMemberAccess(valueArg, member.MemberInfo);
         *
         *              // Call "Serialize"
         *              if (!isSchemaFormatter)
         *              {
         *                      var serializeCall = Call(formatterE.getExp, serializeMethod, refBufferArg, refOffsetArg, fieldExp);
         *                      body.Add(serializeCall);
         *              }
         *
         *      }
         *
         *      var serializeBlock = Block(variables: locals, expressions: body);
         *
         *
         *      if (isStatic)
         *              valueArg = Parameter(typeof(T), "value");
         *
         *
         *      var serialize2Type = typeof(SerializeDelegate2<,>);
         *      var innerDelegateType = serialize2Type.MakeGenericType(tupleType, typeof(T));
         *
         *      var lambda = Lambda(delegateType: innerDelegateType,
         *                                              body: serializeBlock,
         *                                              parameters: new ParameterExpression[]
         *                                              {
         *                                                      formattersArg,
         *                                                      refBufferArg, refOffsetArg, valueArg
         *                                              });
         *
         *      var innerDelegate = lambda.Compile();
         *
         *      var lambdaMethodInfo = innerDelegate.Method;
         *
         *      var dynAsm = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("abc"), AssemblyBuilderAccess.RunAndSave);
         *      var dynMod = dynAsm.DefineDynamicModule("module");
         *      var dynType = dynMod.DefineType("type", TypeAttributes.Public);
         *      var dynMethod = dynType.DefineMethod("doSerialize", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[]
         *      {
         *              tupleType,
         *              typeof(byte[]).MakeByRefType(),
         *              typeof(int).MakeByRefType(),
         *              typeof(T)
         *      });
         *
         *      lambda.CompileToMethod(dynMethod);
         *
         *      var createdType = dynType.CreateType();
         *      var generatedMethodInfo = dynType.GetMethod(dynMethod.Name, dynMethod.GetParameters().Select(p => p.ParameterType).ToArray());
         *
         *      // Now bind to the created tuple!
         *      //var finalDelegate = Delegate.CreateDelegate(typeof(SerializeDelegate<T>), formatterContainer, lambdaMethodInfo);
         *      var finalDelegate = generatedMethodInfo.CreateDelegate(typeof(SerializeDelegate<T>), formatterContainer);
         *
         *      return (SerializeDelegate<T>)finalDelegate;
         * }
         *
         */


        internal static Expression <DeserializeDelegate <T> > GenerateDeserializer(CerasSerializer ceras, Schema schema, bool isSchemaFormatter, bool isStatic)
        {
            bool verifySizes = isSchemaFormatter && ceras.Config.VersionTolerance.VerifySizes;
            var  members     = schema.Members;
            var  typeConfig  = ceras.Config.GetTypeConfig(schema.Type, isStatic);
            var  tc          = typeConfig.TypeConstruction;

            bool constructObject = tc.HasDataArguments;             // Are we responsible for instantiating an object?
            HashSet <ParameterExpression> usedVariables = null;

            var bufferArg    = Parameter(typeof(byte[]), "buffer");
            var refOffsetArg = Parameter(typeof(int).MakeByRefType(), "offset");
            var refValueArg  = Parameter(typeof(T).MakeByRefType(), "value");

            if (isStatic)
            {
                refValueArg = null;
            }

            var body   = new List <Expression>();
            var locals = new List <ParameterExpression>(schema.Members.Count);

            var onAfterDeserialize = GetOnAfterDeserialize(schema.Type);

            ParameterExpression blockSize = null, offsetStart = null;

            if (isSchemaFormatter)
            {
                locals.Add(blockSize   = Variable(typeof(int), "blockSize"));
                locals.Add(offsetStart = Variable(typeof(int), "offsetStart"));
            }


            // MemberInfo -> Variable()
            Dictionary <MemberInfo, ParameterExpression> memberInfoToLocal = new Dictionary <MemberInfo, ParameterExpression>();

            foreach (var m in members)
            {
                if (m.IsSkip)
                {
                    continue;
                }

                var local = Variable(m.MemberType, m.MemberName + "_local");
                locals.Add(local);
                memberInfoToLocal.Add(m.MemberInfo, local);
            }

            // Type -> Constant(IFormatter<Type>)
            Dictionary <Type, ConstantExpression> typeToFormatter          = new Dictionary <Type, ConstantExpression>();

            foreach (var m in members.Where(m => !m.IsSkip).DistinctBy(m => m.MemberType))
            {
                typeToFormatter.Add(m.MemberType, Constant(ceras.GetReferenceFormatter(m.MemberType)));
            }


            //
            // 1. Read existing values into locals (Why? See explanation at the end of the file)
            foreach (var m in members)
            {
                if (constructObject)
                {
                    continue;                     // Can't read existing data when there is no object yet...
                }
                if (m.IsSkip)
                {
                    continue;                     // Member doesn't exist
                }
                // Init the local with the current value
                var local = memberInfoToLocal[m.MemberInfo];
                body.Add(Assign(local, MakeMemberAccess(refValueArg, m.MemberInfo)));
            }

            //
            // 2. Deserialize into local (faster and more robust than field/prop directly)
            foreach (var m in members)
            {
                if (isSchemaFormatter)
                {
                    // Read block size
                    // blockSize = ReadSize();
                    var readCall = Call(method: _sizeReadMethod, arg0: bufferArg, arg1: refOffsetArg);
                    body.Add(Assign(blockSize, Convert(readCall, typeof(int))));

                    if (verifySizes)
                    {
                        // Store the offset before reading the member so we can compare it later
                        body.Add(Assign(offsetStart, refOffsetArg));
                    }


                    if (m.IsSkip)
                    {
                        // Skip over the field
                        // offset += blockSize;
                        body.Add(AddAssign(refOffsetArg, blockSize));
                        continue;
                    }
                }

                if (m.IsSkip && !isSchemaFormatter)
                {
                    throw new InvalidOperationException("DynamicFormatter can not skip members in non-schema mode");
                }


                var formatterExp      = typeToFormatter[m.MemberType];
                var formatter         = formatterExp.Value;
                var deserializeMethod = formatter.GetType().GetMethod(nameof(IFormatter <int> .Deserialize));
                Debug.Assert(deserializeMethod != null, "Can't find deserialize method on formatter " + formatter.GetType().FullName);

                var local = memberInfoToLocal[m.MemberInfo];
                body.Add(Call(formatterExp, deserializeMethod, bufferArg, refOffsetArg, local));

                if (isSchemaFormatter && verifySizes)
                {
                    // Compare blockSize with how much we've actually read
                    // if ( offsetStart + blockSize != offset )
                    //     ThrowException();

                    body.Add(IfThen(test: NotEqual(Add(offsetStart, blockSize), refOffsetArg),
                                    ifTrue: Call(instance: null, _offsetMismatchMethod, offsetStart, refOffsetArg, blockSize)));
                }
            }

            //
            // 3. Create object instance (only when actually needed)
            if (constructObject)
            {
                // Create a helper array for the implementing type construction
                var memberParameters = (
                    from m in schema.Members
                    where !m.IsSkip
                    let local = memberInfoToLocal[m.MemberInfo]
                                select new MemberParameterPair {
                    LocalVar = local, Member = m.MemberInfo
                }
                    ).ToArray();

                usedVariables = new HashSet <ParameterExpression>();
                tc.EmitConstruction(schema, body, refValueArg, usedVariables, memberParameters);
            }

            //
            // 4. Write back values in one batch
            var orderedMembers = OrderMembersForWriteBack(members);

            foreach (var m in orderedMembers)
            {
                if (m.IsSkip)
                {
                    continue;
                }

                var local = memberInfoToLocal[m.MemberInfo];
                var type  = m.MemberType;

                if (usedVariables != null && usedVariables.Contains(local))
                {
                    // Member was already used in the constructor / factory method, no need to write it again
                    continue;
                }

                if (m.IsSkip)
                {
                    continue;
                }

                if (m.MemberInfo is FieldInfo fieldInfo)
                {
                    if (fieldInfo.IsInitOnly)
                    {
                        // Readonly field
                        var memberConfig = typeConfig.Members.First(x => x.Member == m.MemberInfo);
                        var rh           = memberConfig.ComputeReadonlyHandling();
                        DynamicFormatterHelpers.EmitReadonlyWriteBack(type, rh, fieldInfo, refValueArg, local, body);
                    }
                    else
                    {
                        // Normal assignment
                        body.Add(Assign(left: Field(refValueArg, fieldInfo),
                                        right: local));
                    }
                }
                else
                {
                    // Context
                    var p = (PropertyInfo)m.MemberInfo;

                    var setMethod = p.GetSetMethod(true);
                    body.Add(Call(instance: refValueArg, setMethod, local));
                }
            }


            // Call "OnAfterDeserialize"
            if (onAfterDeserialize != null)
            {
                body.Add(Call(refValueArg, onAfterDeserialize));
            }


            var bodyBlock = Block(variables: locals, expressions: body);


            if (isStatic)
            {
                refValueArg = Parameter(typeof(T).MakeByRefType(), "value");
            }

            return(Lambda <DeserializeDelegate <T> >(bodyBlock, bufferArg, refOffsetArg, refValueArg));
        }
Exemple #3
0
        DeserializeDelegate <T> GenerateDeserializer(Schema schema)
        {
            var members    = schema.Members;
            var typeConfig = _ceras.Config.GetTypeConfig(schema.Type);
            var tc         = typeConfig.TypeConstruction;

            bool constructObject = tc.HasDataArguments;             // Are we responsible for instantiating an object?
            HashSet <ParameterExpression> usedVariables = null;

            var bufferArg    = Parameter(typeof(byte[]), "buffer");
            var refOffsetArg = Parameter(typeof(int).MakeByRefType(), "offset");
            var refValueArg  = Parameter(typeof(T).MakeByRefType(), "value");

            var body   = new List <Expression>();
            var locals = new List <ParameterExpression>(schema.Members.Count);

            //
            // 1. Read existing values into locals (Why? See explanation at the end of the file)
            for (var i = 0; i < members.Count; i++)
            {
                var member = members[i];

                // Read the data into a new local variable
                var tempStore = Variable(member.MemberType, member.MemberName + "_local");
                locals.Add(tempStore);

                if (constructObject)
                {
                    continue;                     // Can't read existing data when
                }
                // Init the local with the current value
                body.Add(Assign(tempStore, MakeMemberAccess(refValueArg, member.MemberInfo)));
            }

            //
            // 2. Deserialize using local variable (faster and more robust than working with field/prop directly)
            for (var i = 0; i < members.Count; i++)
            {
                var member    = members[i];
                var tempStore = locals[i];

                var formatter         = _ceras.GetReferenceFormatter(member.MemberType);
                var deserializeMethod = formatter.GetType().GetMethod(nameof(IFormatter <int> .Deserialize));
                Debug.Assert(deserializeMethod != null, "Can't find deserialize method on formatter " + formatter.GetType().FullName);

                // Deserialize the data into the local
                // todo: fully unpack known formatters as well. Maybe let matching formatters implement an interface that can return some sort of "Expression GetDirectCall(bufferArg, offsetArg, localStore)"
                var tempReadCall = Call(Constant(formatter), deserializeMethod, bufferArg, refOffsetArg, tempStore);
                body.Add(tempReadCall);
            }

            //
            // 3. Create object instance (only when actually needed)
            if (constructObject)
            {
                // Create a helper array for the implementing type construction
                var memberParameters = schema.Members.Zip(locals, (m, l) => new MemberParameterPair {
                    Member = m.MemberInfo, LocalVar = l
                }).ToArray();

                usedVariables = new HashSet <ParameterExpression>();
                tc.EmitConstruction(schema, body, refValueArg, usedVariables, memberParameters);
            }

            //
            // 4. Write back values in one batch
            for (int i = 0; i < members.Count; i++)
            {
                var member    = members[i];
                var tempStore = locals[i];
                var type      = member.MemberType;

                if (usedVariables != null && usedVariables.Contains(tempStore))
                {
                    // Member was already used in the constructor / factory method, no need to write it again
                    continue;
                }

                if (member.MemberInfo is FieldInfo fieldInfo)
                {
                    if (fieldInfo.IsInitOnly)
                    {
                        // Readonly field
                        var memberConfig = typeConfig.Members.First(m => m.Member == member.MemberInfo);
                        var rh           = memberConfig.ComputeReadonlyHandling();
                        DynamicFormatterHelpers.EmitReadonlyWriteBack(type, rh, fieldInfo, refValueArg, tempStore, body);
                    }
                    else
                    {
                        // Normal assignment
                        body.Add(Assign(left: Field(refValueArg, fieldInfo),
                                        right: tempStore));
                    }
                }
                else
                {
                    // Context
                    var p = (PropertyInfo)member.MemberInfo;

                    var setMethod = p.GetSetMethod(true);
                    body.Add(Call(instance: refValueArg, setMethod, tempStore));
                }
            }


            var bodyBlock = Block(variables: locals, expressions: body);

#if FAST_EXP
            return(Expression.Lambda <DeserializeDelegate <T> >(serializeBlock, bufferArg, refOffsetArg, refValueArg).CompileFast(true));
#else
            return(Lambda <DeserializeDelegate <T> >(bodyBlock, bufferArg, refOffsetArg, refValueArg).Compile());
#endif
        }