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); }
/* * * 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)); }
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 }