diff --git a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll index 21aac9e253..ace54bb2c6 100644 --- a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll +++ b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c76d63dbc0c7c64e52f8ac2c5f8d15645956737befdcccac5ca09c5b86240ed3 -size 150528 +oid sha256:63c65b2d24cd6ae487ea6c9d957343dba83c7992a0a2831d20f0f4da5b78dadc +size 147456 diff --git a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll.hash b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll.hash index 9832b1c9f7..da40182b23 100644 --- a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll.hash +++ b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.dll.hash @@ -1 +1 @@ -C76D63DBC0C7C64E52F8AC2C5F8D15645956737BEFDCCCAC5CA09C5B86240ED3 \ No newline at end of file +63C65B2D24CD6AE487EA6C9D957343DBA83C7992A0A2831D20F0F4DA5B78DADC \ No newline at end of file diff --git a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.pdb b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.pdb index bea31f4e1d..f5d5cf9d20 100644 --- a/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.pdb +++ b/deps/AssemblyProcessor/net10.0/Stride.Core.AssemblyProcessor.pdb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d24c0c9a848e934363ba72dd325057f3f0f89737b7e0403896abaca860e1981 -size 411136 +oid sha256:c21b7719e470059842e4525453a248a3dcf033f75c4cc61a50382684c478a33f +size 407040 diff --git a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll index 9761425c8c..8cad1bab2b 100644 --- a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll +++ b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd9b63a4a434e57ede5727114221868fe77d252fff4c14c3342ba4e74ad3905d -size 158208 +oid sha256:6c5bc9da7e5a867c953c362c3419ad3c9554df115baa61baec10ed2c3856b752 +size 155136 diff --git a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll.hash b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll.hash index 1f1345ecea..e9d3f2dc84 100644 --- a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll.hash +++ b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.dll.hash @@ -1 +1 @@ -DD9B63A4A434E57EDE5727114221868FE77D252FFF4C14C3342BA4E74AD3905D \ No newline at end of file +6C5BC9DA7E5A867C953C362C3419AD3C9554DF115BAA61BAEC10ED2C3856B752 \ No newline at end of file diff --git a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.pdb b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.pdb index 7019b2d210..7cb404ba5f 100644 --- a/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.pdb +++ b/deps/AssemblyProcessor/netstandard2.0/Stride.Core.AssemblyProcessor.pdb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bee19bb3a32f1397b17d0e587ec0c65d88cd9dc5ba09253220df43802a93e806 +oid sha256:27df1b2f1b7d6beb16f173e0039a4d0810171d87a4f160c7d3756912dbb4c290 size 550400 diff --git a/sources/core/Stride.Core.AssemblyProcessor/CecilArraySerializerFactory.cs b/sources/core/Stride.Core.AssemblyProcessor/CecilArraySerializerFactory.cs deleted file mode 100644 index 2b37026ff3..0000000000 --- a/sources/core/Stride.Core.AssemblyProcessor/CecilArraySerializerFactory.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -using Mono.Cecil; - -namespace Stride.Core.AssemblyProcessor; - -/// -/// Generates array serializer type from a given array type. -/// -public class CecilArraySerializerFactory : ICecilSerializerFactory -{ - private readonly TypeReference genericArraySerializerType; - - public CecilArraySerializerFactory(TypeReference genericArraySerializerType) - { - this.genericArraySerializerType = genericArraySerializerType; - } - - public TypeReference? GetSerializer(TypeReference objectType) - { - if (objectType.IsArray) - { - return genericArraySerializerType.MakeGenericType(((ArrayType)objectType).ElementType); - } - - return null; - } -} diff --git a/sources/core/Stride.Core.AssemblyProcessor/CecilEnumSerializerFactory.cs b/sources/core/Stride.Core.AssemblyProcessor/CecilEnumSerializerFactory.cs deleted file mode 100644 index 8cd4928d98..0000000000 --- a/sources/core/Stride.Core.AssemblyProcessor/CecilEnumSerializerFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -using Mono.Cecil; - -namespace Stride.Core.AssemblyProcessor; - -/// -/// Generates enum serializer type from a given enum type. -/// -public class CecilEnumSerializerFactory : ICecilSerializerFactory -{ - private readonly TypeReference genericEnumSerializerType; - - public CecilEnumSerializerFactory(TypeReference genericEnumSerializerType) - { - this.genericEnumSerializerType = genericEnumSerializerType; - } - - public TypeReference? GetSerializer(TypeReference objectType) - { - var resolvedObjectType = objectType.Resolve(); - if (resolvedObjectType?.IsEnum == true) - { - return genericEnumSerializerType.MakeGenericType(objectType); - } - - return null; - } -} diff --git a/sources/core/Stride.Core.AssemblyProcessor/CecilGenericSerializerFactory.cs b/sources/core/Stride.Core.AssemblyProcessor/CecilGenericSerializerFactory.cs deleted file mode 100644 index 71ab3533d3..0000000000 --- a/sources/core/Stride.Core.AssemblyProcessor/CecilGenericSerializerFactory.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -using Mono.Cecil; - -namespace Stride.Core.AssemblyProcessor; - -/// -/// Gives the specified serializer type constructed with generic arguments of the serialized type. -/// As an example, this instance with will give from . -/// -public class CecilGenericSerializerFactory : ICecilSerializerFactory -{ - private readonly Type genericSerializableType; - private readonly bool checkInterfaces; - - protected Func CreateSerializer { get; set; } - - private CecilGenericSerializerFactory(Type genericSerializableType, Func createSerializer) - { - this.genericSerializableType = genericSerializableType; - CreateSerializer = createSerializer; - checkInterfaces = genericSerializableType.IsInterface; - } - - public CecilGenericSerializerFactory(Type genericSerializableType, TypeReference genericSerializerType) - : this(genericSerializableType, type => genericSerializerType.MakeGenericType([.. ((GenericInstanceType)type).GenericArguments])) - { - if (genericSerializerType == null) - throw new ArgumentNullException(nameof(genericSerializerType)); - if (genericSerializableType == null) - throw new ArgumentNullException(nameof(genericSerializableType)); - } - - #region IDataSerializerFactory Members - - public virtual TypeReference? GetSerializer(TypeReference objectType) - { - // Check if objectType matches genericSerializableType. - // Note: Not perfectly valid but hopefully it should be fast enough. - if (objectType.IsGenericInstance && checkInterfaces) - { - if (objectType.GetElementType().Resolve().Interfaces.Any(x => x.InterfaceType.IsGenericInstance && x.InterfaceType.GetElementType().FullName == genericSerializableType.FullName)) - return CreateSerializer(objectType); - } - if (objectType.IsGenericInstance && objectType.GetElementType().FullName == genericSerializableType.FullName) - return CreateSerializer(objectType); - - return null; - } - - #endregion -} diff --git a/sources/core/Stride.Core.AssemblyProcessor/CecilSerializerDependency.cs b/sources/core/Stride.Core.AssemblyProcessor/CecilSerializerDependency.cs deleted file mode 100644 index db25748be2..0000000000 --- a/sources/core/Stride.Core.AssemblyProcessor/CecilSerializerDependency.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -using Mono.Cecil; - -namespace Stride.Core.AssemblyProcessor; - -/// -/// Enumerates required subtypes the given serializer will use internally. -/// -public class CecilSerializerDependency : ICecilSerializerDependency -{ - readonly string genericSerializerTypeFullName; - readonly TypeReference? genericSerializableType; - - /// - /// Initializes a new instance of the class. - /// It will enumerates T1, T2 from genericSerializerType{T1, T2}. - /// - /// Type of the generic serializer. - public CecilSerializerDependency(string genericSerializerTypeFullName) - { - this.genericSerializerTypeFullName = genericSerializerTypeFullName ?? throw new ArgumentNullException(nameof(genericSerializerTypeFullName)); - } - - /// - /// Initializes a new instance of the class. - /// It will enumerates genericSerializableType{T1, T2} from genericSerializerType{T1, T2}. - /// - /// Type of the generic serializer. - /// Type of the generic serializable. - public CecilSerializerDependency(string genericSerializerTypeFullName, TypeReference genericSerializableType) - { - this.genericSerializerTypeFullName = genericSerializerTypeFullName ?? throw new ArgumentNullException(nameof(genericSerializerTypeFullName)); - this.genericSerializableType = genericSerializableType ?? throw new ArgumentNullException(nameof(genericSerializableType)); - } - - public IEnumerable EnumerateSubTypesFromSerializer(TypeReference serializerType) - { - // Check if serializer type name matches - if (serializerType.IsGenericInstance && serializerType.GetElementType().FullName == genericSerializerTypeFullName) - { - if (genericSerializableType != null) - { - // Transforms genericSerializerType{T1, T2} into genericSerializableType{T1, T2} - return Enumerable.Repeat(genericSerializableType.MakeGenericType([.. ((GenericInstanceType)serializerType).GenericArguments]), 1); - } - else - { - // Transforms genericSerializerType{T1, T2} into T1, T2 - return ((GenericInstanceType)serializerType).GenericArguments; - } - } - - return []; - } -} diff --git a/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerDependency.cs b/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerDependency.cs deleted file mode 100644 index 8a39826cd0..0000000000 --- a/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerDependency.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -using Mono.Cecil; - -namespace Stride.Core.AssemblyProcessor; - -/// -/// Enumerates required subtypes the given serializer will use internally. -/// This is useful for generation of serialization assembly, when AOT is performed (all generic serializers must be available). -/// -public interface ICecilSerializerDependency -{ - /// - /// Enumerates the types this serializer requires. - /// - /// Type of the serializer. - /// - IEnumerable EnumerateSubTypesFromSerializer(TypeReference serializerType); -} diff --git a/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerFactory.cs b/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerFactory.cs deleted file mode 100644 index 5c6ced14b9..0000000000 --- a/sources/core/Stride.Core.AssemblyProcessor/ICecilSerializerFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -using Mono.Cecil; - -namespace Stride.Core.AssemblyProcessor; - -/// -/// Gives the required generic serializer for a given type. -/// This is useful for generation of serialization assembly, when AOT is performed (all generic serializers must be available). -/// -public interface ICecilSerializerFactory -{ - /// - /// Gets the serializer type from a given object type. - /// - /// Type of the object to serialize. - /// - TypeReference? GetSerializer(TypeReference objectType); -} diff --git a/sources/core/Stride.Core.AssemblyProcessor/ILBuilder.cs b/sources/core/Stride.Core.AssemblyProcessor/ILBuilder.cs new file mode 100644 index 0000000000..c3c76e543c --- /dev/null +++ b/sources/core/Stride.Core.AssemblyProcessor/ILBuilder.cs @@ -0,0 +1,206 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Stride.Core.AssemblyProcessor; + +/// +/// A thin fluent wrapper over Cecil's IL emission that reduces verbosity. +/// Provides automatic and composite patterns +/// like typeof(T) emission. +/// +internal class ILBuilder +{ + private readonly MethodBody body; + private readonly ModuleDefinition module; + + public ILBuilder(MethodBody body, ModuleDefinition module) + { + this.body = body; + this.module = module; + } + + /// + /// The underlying method body, for direct access when needed (e.g. adding variables). + /// + public MethodBody Body => body; + + /// + /// The module used for importing references. + /// + public ModuleDefinition Module => module; + + // ── Core Emit overloads ────────────────────────────────────────── + + public ILBuilder Emit(OpCode opcode) + { + body.Instructions.Add(Instruction.Create(opcode)); + return this; + } + + public ILBuilder Emit(OpCode opcode, int value) + { + body.Instructions.Add(Instruction.Create(opcode, value)); + return this; + } + + public ILBuilder Emit(OpCode opcode, byte value) + { + body.Instructions.Add(Instruction.Create(opcode, value)); + return this; + } + + public ILBuilder Emit(OpCode opcode, string value) + { + body.Instructions.Add(Instruction.Create(opcode, value)); + return this; + } + + public ILBuilder Emit(OpCode opcode, TypeReference type) + { + body.Instructions.Add(Instruction.Create(opcode, type)); + return this; + } + + public ILBuilder Emit(OpCode opcode, MethodReference method) + { + body.Instructions.Add(Instruction.Create(opcode, method)); + return this; + } + + public ILBuilder Emit(OpCode opcode, FieldReference field) + { + body.Instructions.Add(Instruction.Create(opcode, field)); + return this; + } + + public ILBuilder Emit(OpCode opcode, VariableDefinition variable) + { + body.Instructions.Add(Instruction.Create(opcode, variable)); + return this; + } + + public ILBuilder Emit(OpCode opcode, ParameterDefinition parameter) + { + body.Instructions.Add(Instruction.Create(opcode, parameter)); + return this; + } + + public ILBuilder Emit(OpCode opcode, Instruction target) + { + body.Instructions.Add(Instruction.Create(opcode, target)); + return this; + } + + public ILBuilder Emit(OpCode opcode, CallSite callSite) + { + body.Instructions.Add(Instruction.Create(opcode, callSite)); + return this; + } + + /// + /// Appends a pre-created instruction to the method body. + /// + public ILBuilder Append(Instruction instruction) + { + body.Instructions.Add(instruction); + return this; + } + + // ── Import helpers ─────────────────────────────────────────────── + + /// + /// Imports a into the current module. + /// Shorthand for module.ImportReference(type). + /// + public TypeReference Import(TypeReference type) => module.ImportReference(type); + + /// + /// Imports a into the current module. + /// Shorthand for module.ImportReference(method). + /// + public MethodReference Import(MethodReference method) => module.ImportReference(method); + + /// + /// Imports a into the current module. + /// Shorthand for module.ImportReference(field). + /// + public FieldReference Import(FieldReference field) => module.ImportReference(field); + + // ── Composite patterns ─────────────────────────────────────────── + + /// + /// Emits typeof(T): ldtoken type; call GetTypeFromHandle. + /// + public ILBuilder EmitTypeof(TypeReference type, MethodReference getTypeFromHandle) + { + Emit(OpCodes.Ldtoken, type); + Emit(OpCodes.Call, getTypeFromHandle); + return this; + } + + /// + /// Emits typeof(T).GetTypeInfo().Assembly. + /// + public ILBuilder EmitTypeofAssembly(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeInfo, MethodReference getAssembly) + { + EmitTypeof(type, getTypeFromHandle); + Emit(OpCodes.Call, getTypeInfo); + Emit(OpCodes.Callvirt, getAssembly); + return this; + } + + /// + /// Emits typeof(T).GetTypeInfo().Module. + /// + public ILBuilder EmitTypeofModule(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeInfo, MethodReference getModule) + { + EmitTypeof(type, getTypeFromHandle); + Emit(OpCodes.Call, getTypeInfo); + Emit(OpCodes.Callvirt, getModule); + return this; + } + + /// + /// Emits typeof(T).TypeHandle: ldtoken type; call GetTypeFromHandle; callvirt get_TypeHandle. + /// + public ILBuilder EmitTypeHandle(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeHandle) + { + EmitTypeof(type, getTypeFromHandle); + Emit(OpCodes.Callvirt, getTypeHandle); + return this; + } + + // ── Variable helpers ───────────────────────────────────────────── + + /// + /// Adds a local variable to the method body and returns it. + /// Sets to . + /// + public VariableDefinition AddLocal(TypeReference type) + { + var variable = new VariableDefinition(type); + body.Variables.Add(variable); + body.InitLocals = true; + return variable; + } + + // ── Label support ──────────────────────────────────────────────── + + /// + /// Creates a that can be used as a branch target, + /// without appending it to the body. Call to place it. + /// + public static Instruction DefineLabel() => Instruction.Create(OpCodes.Nop); + + /// + /// Appends a previously defined label instruction to the body, marking the current position. + /// + public ILBuilder MarkLabel(Instruction label) + { + body.Instructions.Add(label); + return this; + } +} diff --git a/sources/core/Stride.Core.AssemblyProcessor/InteropProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/InteropProcessor.cs index ebaa6a79ad..45ccd8fb14 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/InteropProcessor.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/InteropProcessor.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -103,7 +103,7 @@ private void CreateModuleInit(MethodDefinition method) } /// - /// Creates the write method with the following signature: + /// Creates the write method with the following signature: /// /// public static unsafe void* Write<T>(void* pDest, ref T data) where T : struct /// @@ -114,7 +114,7 @@ private void CreateWriteMethod(MethodDefinition method) method.Body.Instructions.Clear(); method.Body.InitLocals = true; - var gen = method.Body.GetILProcessor(); + var il = new ILBuilder(method.Body, assembly.MainModule); var paramT = method.GenericParameters[0]; // Preparing locals // local(0) int @@ -123,34 +123,28 @@ private void CreateWriteMethod(MethodDefinition method) method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT)))); // Push (0) pDest for memcpy - gen.Emit(OpCodes.Ldarg_0); - - // fixed (void* pinnedData = &data[offset]) - gen.Emit(OpCodes.Ldarg_1); - gen.Emit(OpCodes.Stloc_1); - - // Push (1) pinnedData for memcpy - gen.Emit(OpCodes.Ldloc_1); - - // totalSize = sizeof(T) - gen.Emit(OpCodes.Sizeof, paramT); - gen.Emit(OpCodes.Conv_I4); - gen.Emit(OpCodes.Stloc_0); - - // Push (2) totalSize - gen.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldarg_0) + // fixed (void* pinnedData = &data[offset]) + .Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Stloc_1) + // Push (1) pinnedData for memcpy + .Emit(OpCodes.Ldloc_1) + // totalSize = sizeof(T) + .Emit(OpCodes.Sizeof, paramT) + .Emit(OpCodes.Conv_I4) + .Emit(OpCodes.Stloc_0) + // Push (2) totalSize + .Emit(OpCodes.Ldloc_0); // Emit cpblk - EmitCpblk(method, gen); + EmitCpblk(il); // Return pDest + totalSize - gen.Emit(OpCodes.Ldloc_0); - gen.Emit(OpCodes.Conv_I); - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Add); - - // Ret - gen.Emit(OpCodes.Ret); + il.Emit(OpCodes.Ldloc_0) + .Emit(OpCodes.Conv_I) + .Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Add) + .Emit(OpCodes.Ret); } private void ReplacePinStatement(MethodDefinition method, ILProcessor ilProcessor, Instruction fixedtoPatch) @@ -348,12 +342,9 @@ private void CreateCastMethod(MethodDefinition method) method.Body.Instructions.Clear(); method.Body.InitLocals = true; - var gen = method.Body.GetILProcessor(); - - gen.Emit(OpCodes.Ldarg_0); - - // Ret - gen.Emit(OpCodes.Ret); + var il = new ILBuilder(method.Body, assembly.MainModule); + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ret); } /// @@ -368,12 +359,9 @@ private void CreateCastArrayMethod(MethodDefinition method) method.Body.Instructions.Clear(); method.Body.InitLocals = true; - var gen = method.Body.GetILProcessor(); - - gen.Emit(OpCodes.Ldarg_0); - - // Ret - gen.Emit(OpCodes.Ret); + var il = new ILBuilder(method.Body, assembly.MainModule); + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ret); } private void ReplaceFixedArrayStatement(MethodDefinition method, ILProcessor ilProcessor, Instruction fixedtoPatch) @@ -431,7 +419,7 @@ private void CreateWriteRangeMethod(MethodDefinition method) method.Body.Instructions.Clear(); method.Body.InitLocals = true; - var gen = method.Body.GetILProcessor(); + var il = new ILBuilder(method.Body, assembly.MainModule); var paramT = method.GenericParameters[0]; // Preparing locals // local(0) int @@ -440,38 +428,32 @@ private void CreateWriteRangeMethod(MethodDefinition method) method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT)))); // Push (0) pDest for memcpy - gen.Emit(OpCodes.Ldarg_0); - - // fixed (void* pinnedData = &data[offset]) - gen.Emit(OpCodes.Ldarg_1); - gen.Emit(OpCodes.Ldarg_2); - gen.Emit(OpCodes.Ldelema, paramT); - gen.Emit(OpCodes.Stloc_1); - - // Push (1) pinnedData for memcpy - gen.Emit(OpCodes.Ldloc_1); - - // totalSize = sizeof(T) * count - gen.Emit(OpCodes.Sizeof, paramT); - gen.Emit(OpCodes.Conv_I4); - gen.Emit(OpCodes.Ldarg_3); - gen.Emit(OpCodes.Mul); - gen.Emit(OpCodes.Stloc_0); - - // Push (2) totalSize - gen.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldarg_0) + // fixed (void* pinnedData = &data[offset]) + .Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Ldarg_2) + .Emit(OpCodes.Ldelema, paramT) + .Emit(OpCodes.Stloc_1) + // Push (1) pinnedData for memcpy + .Emit(OpCodes.Ldloc_1) + // totalSize = sizeof(T) * count + .Emit(OpCodes.Sizeof, paramT) + .Emit(OpCodes.Conv_I4) + .Emit(OpCodes.Ldarg_3) + .Emit(OpCodes.Mul) + .Emit(OpCodes.Stloc_0) + // Push (2) totalSize + .Emit(OpCodes.Ldloc_0); // Emit cpblk - EmitCpblk(method, gen); + EmitCpblk(il); // Return pDest + totalSize - gen.Emit(OpCodes.Ldloc_0); - gen.Emit(OpCodes.Conv_I); - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Add); - - // Ret - gen.Emit(OpCodes.Ret); + il.Emit(OpCodes.Ldloc_0) + .Emit(OpCodes.Conv_I) + .Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Add) + .Emit(OpCodes.Ret); } /// @@ -486,45 +468,38 @@ private void CreateReadMethod(MethodDefinition method) method.Body.Instructions.Clear(); method.Body.InitLocals = true; - var gen = method.Body.GetILProcessor(); + var il = new ILBuilder(method.Body, assembly.MainModule); var paramT = method.GenericParameters[0]; // Preparing locals // local(0) int method.Body.Variables.Add(new VariableDefinition(intType)); // local(1) T* - method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT)))); // fixed (void* pinnedData = &data[offset]) - gen.Emit(OpCodes.Ldarg_1); - gen.Emit(OpCodes.Stloc_1); - - // Push (0) pinnedData for memcpy - gen.Emit(OpCodes.Ldloc_1); - - // Push (1) pSrc for memcpy - gen.Emit(OpCodes.Ldarg_0); - - // totalSize = sizeof(T) - gen.Emit(OpCodes.Sizeof, paramT); - gen.Emit(OpCodes.Conv_I4); - gen.Emit(OpCodes.Stloc_0); - - // Push (2) totalSize - gen.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Stloc_1) + // Push (0) pinnedData for memcpy + .Emit(OpCodes.Ldloc_1) + // Push (1) pSrc for memcpy + .Emit(OpCodes.Ldarg_0) + // totalSize = sizeof(T) + .Emit(OpCodes.Sizeof, paramT) + .Emit(OpCodes.Conv_I4) + .Emit(OpCodes.Stloc_0) + // Push (2) totalSize + .Emit(OpCodes.Ldloc_0); // Emit cpblk - EmitCpblk(method, gen); + EmitCpblk(il); // Return pDest + totalSize - gen.Emit(OpCodes.Ldloc_0); - gen.Emit(OpCodes.Conv_I); - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Add); - - // Ret - gen.Emit(OpCodes.Ret); + il.Emit(OpCodes.Ldloc_0) + .Emit(OpCodes.Conv_I) + .Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Add) + .Emit(OpCodes.Ret); } /// @@ -539,12 +514,10 @@ private void CreateReadRawMethod(MethodDefinition method) method.Body.Instructions.Clear(); method.Body.InitLocals = true; - var gen = method.Body.GetILProcessor(); - var paramT = method.GenericParameters[0]; + var il = new ILBuilder(method.Body, assembly.MainModule); // Push (1) pSrc for memcpy - gen.Emit(OpCodes.Cpobj); - + il.Emit(OpCodes.Cpobj); } /// @@ -559,7 +532,7 @@ private void CreateReadRangeMethod(MethodDefinition method) method.Body.Instructions.Clear(); method.Body.InitLocals = true; - var gen = method.Body.GetILProcessor(); + var il = new ILBuilder(method.Body, assembly.MainModule); var paramT = method.GenericParameters[0]; // Preparing locals // local(0) int @@ -568,39 +541,33 @@ private void CreateReadRangeMethod(MethodDefinition method) method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT)))); // fixed (void* pinnedData = &data[offset]) - gen.Emit(OpCodes.Ldarg_1); - gen.Emit(OpCodes.Ldarg_2); - gen.Emit(OpCodes.Ldelema, paramT); - gen.Emit(OpCodes.Stloc_1); - - // Push (0) pinnedData for memcpy - gen.Emit(OpCodes.Ldloc_1); - - // Push (1) pDest for memcpy - gen.Emit(OpCodes.Ldarg_0); - - // totalSize = sizeof(T) * count - gen.Emit(OpCodes.Sizeof, paramT); - gen.Emit(OpCodes.Conv_I4); - gen.Emit(OpCodes.Ldarg_3); - gen.Emit(OpCodes.Conv_I4); - gen.Emit(OpCodes.Mul); - gen.Emit(OpCodes.Stloc_0); - - // Push (2) totalSize - gen.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Ldarg_2) + .Emit(OpCodes.Ldelema, paramT) + .Emit(OpCodes.Stloc_1) + // Push (0) pinnedData for memcpy + .Emit(OpCodes.Ldloc_1) + // Push (1) pDest for memcpy + .Emit(OpCodes.Ldarg_0) + // totalSize = sizeof(T) * count + .Emit(OpCodes.Sizeof, paramT) + .Emit(OpCodes.Conv_I4) + .Emit(OpCodes.Ldarg_3) + .Emit(OpCodes.Conv_I4) + .Emit(OpCodes.Mul) + .Emit(OpCodes.Stloc_0) + // Push (2) totalSize + .Emit(OpCodes.Ldloc_0); // Emit cpblk - EmitCpblk(method, gen); + EmitCpblk(il); // Return pDest + totalSize - gen.Emit(OpCodes.Ldloc_0); - gen.Emit(OpCodes.Conv_I); - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Add); - - // Ret - gen.Emit(OpCodes.Ret); + il.Emit(OpCodes.Ldloc_0) + .Emit(OpCodes.Conv_I) + .Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Add) + .Emit(OpCodes.Ret); } /// @@ -614,16 +581,13 @@ private void CreateMemcpy(MethodDefinition methodCopyStruct) { methodCopyStruct.Body.Instructions.Clear(); - var gen = methodCopyStruct.Body.GetILProcessor(); - - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Ldarg_1); - gen.Emit(OpCodes.Ldarg_2); - gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1 - gen.Emit(OpCodes.Cpblk); - - // Ret - gen.Emit(OpCodes.Ret); + var il = new ILBuilder(methodCopyStruct.Body, assembly.MainModule); + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Ldarg_2) + .Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1 + .Emit(OpCodes.Cpblk) + .Emit(OpCodes.Ret); } /// @@ -637,32 +601,27 @@ private void CreateMemset(MethodDefinition methodSetStruct) { methodSetStruct.Body.Instructions.Clear(); - var gen = methodSetStruct.Body.GetILProcessor(); - - gen.Emit(OpCodes.Ldarg_0); - gen.Emit(OpCodes.Ldarg_1); - gen.Emit(OpCodes.Ldarg_2); - gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1 - gen.Emit(OpCodes.Initblk); - - // Ret - gen.Emit(OpCodes.Ret); + var il = new ILBuilder(methodSetStruct.Body, assembly.MainModule); + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Ldarg_2) + .Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1 + .Emit(OpCodes.Initblk) + .Emit(OpCodes.Ret); } /// /// Emits the cpblk method, supporting x86 and x64 platform. /// - /// The method. - /// The gen. - private void EmitCpblk(MethodDefinition method, ILProcessor gen) + /// The IL builder. + private static void EmitCpblk(ILBuilder il) { - var cpblk = gen.Create(OpCodes.Cpblk); - //gen.Emit(OpCodes.Sizeof, voidPointerType); - //gen.Emit(OpCodes.Ldc_I4_8); - //gen.Emit(OpCodes.Bne_Un_S, cpblk); - gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1 - gen.Append(cpblk); - + var cpblk = Instruction.Create(OpCodes.Cpblk); + //il.Emit(OpCodes.Sizeof, voidPointerType); + //il.Emit(OpCodes.Ldc_I4_8); + //il.Emit(OpCodes.Bne_Un_S, cpblk); + il.Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1 + .Append(cpblk); } private List GetSharpDXAttributes(MethodDefinition method) diff --git a/sources/core/Stride.Core.AssemblyProcessor/ProfileSerializerProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/ProfileSerializerProcessor.cs index a802087ca2..d2020cf076 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/ProfileSerializerProcessor.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/ProfileSerializerProcessor.cs @@ -22,7 +22,7 @@ public void ProcessSerializers(CecilSerializerContext context) // For each profile, try to instantiate all types existing in default profile foreach (var type in defaultProfile.SerializableTypes) { - context.GenerateSerializer(type.Key, false, profile.Key); + context.ResolveSerializer(type.Key, false, profile.Key); } } } diff --git a/sources/core/Stride.Core.AssemblyProcessor/ResolveGenericsVisitor.cs b/sources/core/Stride.Core.AssemblyProcessor/ResolveGenericsVisitor.cs index 151fc45a64..635b1c0fb9 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/ResolveGenericsVisitor.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/ResolveGenericsVisitor.cs @@ -65,6 +65,21 @@ public static TypeReference Process(TypeReference context, TypeReference type) return result; } + /// + /// Creates a visitor that maps generic parameters from to . + /// Returns null if the source has no generic parameters. + /// + public static ResolveGenericsVisitor? FromMapping(IGenericParameterProvider source, IGenericParameterProvider target) + { + if (source.GenericParameters.Count == 0) + return null; + + var mapping = new Dictionary(); + for (int i = 0; i < source.GenericParameters.Count; i++) + mapping[source.GenericParameters[i]] = target.GenericParameters[i]; + return new ResolveGenericsVisitor(mapping); + } + public override TypeReference Visit(GenericParameter type) { TypeReference typeParent = type; diff --git a/sources/core/Stride.Core.AssemblyProcessor/ComplexSerializerRegistry.cs b/sources/core/Stride.Core.AssemblyProcessor/SerializationHelpers.cs similarity index 55% rename from sources/core/Stride.Core.AssemblyProcessor/ComplexSerializerRegistry.cs rename to sources/core/Stride.Core.AssemblyProcessor/SerializationHelpers.cs index cb0c3de7aa..9827fc7ae6 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/ComplexSerializerRegistry.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/SerializationHelpers.cs @@ -1,144 +1,38 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Runtime.Versioning; -using Stride.Core.AssemblyProcessor.Serializers; using System.Runtime.CompilerServices; -using System.Text; using Mono.Cecil; namespace Stride.Core.AssemblyProcessor; -internal class ComplexSerializerRegistry +/// +/// Static utility methods for analyzing serializable types and generating names. +/// +internal static class SerializationHelpers { - private static readonly HashSet forbiddenKeywords; - private static readonly HashSet ignoredMembers; - - static ComplexSerializerRegistry() - { - ignoredMembers = []; - - forbiddenKeywords = - [ "obj", "stream", "mode", - "abstract", "event", "new", "struct", - "as", "explicit", "null", "switch", - "base", "extern", "object", "this", - "bool", "false", "operator", "throw", - "break", "finally", "out", "true", - "byte", "fixed", "override", "try", - "case", "float", "params", "typeof", - "catch", "for", "private", "uint", - "char", "foreach", "protected", "ulong", - "checked", "goto", "public", "unchecked", - "class", "if", "readonly", "unsafe", - "const", "implicit", "ref", "ushort", - "continue", "in", "return", "using", - "decimal", "int", "sbyte", "virtual", - "default", "interface", "sealed", "volatile", - "delegate", "internal", "short", "void", - "do", "is", "sizeof", "while", - "double", "lock", "stackalloc", - "else", "long", "static", - "enum", "namespace", "string" ]; - } - - public List ReferencedAssemblySerializerFactoryTypes { get; } = []; - - public CecilSerializerContext Context { get; } - - //private List serializerFactories = new List(); - - public string? TargetFramework { get; } - - public string ClassName { get; } - - public AssemblyDefinition Assembly { get; } - - public List SerializerDependencies { get; } = []; - - public List SerializerFactories { get; } = []; - - public ComplexSerializerRegistry(PlatformType platform, AssemblyDefinition assembly, TextWriter log) - { - Assembly = assembly; - ClassName = Utilities.BuildValidClassName(assembly.Name.Name) + "SerializerFactory"; - - // Register referenced assemblies serializer factory, so that we can call them recursively - foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences) - { - try - { - var referencedAssembly = assembly.MainModule.AssemblyResolver.Resolve(referencedAssemblyName); - - var assemblySerializerFactoryType = GetSerializerFactoryType(referencedAssembly); - if (assemblySerializerFactoryType != null) - ReferencedAssemblySerializerFactoryTypes.Add(assemblySerializerFactoryType); - } - catch (AssemblyResolutionException) - { - continue; - } - } - - // Find target framework and replicate it for serializer assembly. - var targetFrameworkAttribute = assembly.CustomAttributes - .FirstOrDefault(x => x.AttributeType.FullName == typeof(TargetFrameworkAttribute).FullName); - if (targetFrameworkAttribute != null) - { - TargetFramework = "\"" + (string)targetFrameworkAttribute.ConstructorArguments[0].Value + "\""; - var frameworkDisplayNameField = targetFrameworkAttribute.Properties.FirstOrDefault(x => x.Name == "FrameworkDisplayName"); - if (frameworkDisplayNameField.Name != null) - { - TargetFramework += ", FrameworkDisplayName=\"" + (string)frameworkDisplayNameField.Argument.Value + "\""; - } - } - - // Prepare serializer processors - Context = new CecilSerializerContext(platform, assembly, log); - var processors = new List - { - // Import list of serializer registered by referenced assemblies - new ReferencedAssemblySerializerProcessor(), - - // Generate serializers for types tagged as serializable - new CecilComplexClassSerializerProcessor(), - - // Generate serializers for PropertyKey and ParameterKey - new PropertyKeySerializerProcessor(), - - // Update Engine (with AnimationData) - new UpdateEngineProcessor(), - - // Profile serializers - new ProfileSerializerProcessor(), - - // Data contract aliases - new DataContractAliasProcessor() - }; - - // Apply each processor - foreach (var processor in processors) - processor.ProcessSerializers(Context); - } - - private static TypeDefinition? GetSerializerFactoryType(AssemblyDefinition referencedAssembly) - { - var assemblySerializerFactoryAttribute = - referencedAssembly.CustomAttributes.FirstOrDefault( - x => - x.AttributeType.FullName == - "Stride.Core.Serialization.AssemblySerializerFactoryAttribute"); - - // No serializer factory? - if (assemblySerializerFactoryAttribute == null) - return null; - - var typeReference = (TypeReference)assemblySerializerFactoryAttribute.Fields.Single(x => x.Name == "Type").Argument.Value; - if (typeReference == null) - return null; - - return typeReference.Resolve(); - } + private static readonly HashSet forbiddenKeywords = + [ "obj", "stream", "mode", + "abstract", "event", "new", "struct", + "as", "explicit", "null", "switch", + "base", "extern", "object", "this", + "bool", "false", "operator", "throw", + "break", "finally", "out", "true", + "byte", "fixed", "override", "try", + "case", "float", "params", "typeof", + "catch", "for", "private", "uint", + "char", "foreach", "protected", "ulong", + "checked", "goto", "public", "unchecked", + "class", "if", "readonly", "unsafe", + "const", "implicit", "ref", "ushort", + "continue", "in", "return", "using", + "decimal", "int", "sbyte", "virtual", + "default", "interface", "sealed", "volatile", + "delegate", "internal", "short", "void", + "do", "is", "sizeof", "while", + "double", "lock", "stackalloc", + "else", "long", "static", + "enum", "namespace", "string" ]; private static string TypeNameWithoutGenericEnding(TypeReference type) { @@ -175,88 +69,17 @@ public static string SerializerTypeName(TypeReference type, bool appendGenerics, return typeName; } - public static string GetSerializerInstantiateMethodName(TypeReference serializerType, bool appendGenerics) - { - return "Instantiate_" + SerializerTypeName(serializerType, appendGenerics, false); - } - - /// - /// Generates the generic constraints in a code form. - /// - /// The type. - /// - public static string GenerateGenericConstraints(TypeReference type) - { - if (!type.HasGenericParameters) - return string.Empty; - - var result = new StringBuilder(); - foreach (var genericParameter in type.GenericParameters) - { - // If no constraints, skip it - var hasContraints = genericParameter.HasReferenceTypeConstraint || genericParameter.HasNotNullableValueTypeConstraint || genericParameter.Constraints.Count > 0 || genericParameter.HasDefaultConstructorConstraint; - if (!hasContraints) - { - continue; - } - - bool hasFirstContraint = false; - - result.AppendFormat(" where {0}: ", genericParameter.Name); - - // Where class/struct constraint must be before any other constraint - if (genericParameter.HasReferenceTypeConstraint) - { - result.AppendFormat("class"); - hasFirstContraint = true; - } - else if (genericParameter.HasNotNullableValueTypeConstraint) - { - result.AppendFormat("struct"); - hasFirstContraint = true; - } - - foreach (var genericParameterConstraint in genericParameter.Constraints) - { - // Skip value type constraint - if (genericParameterConstraint.ConstraintType.FullName != typeof(ValueType).FullName) - { - if (hasFirstContraint) - { - result.Append(", "); - } - - result.AppendFormat("{0}", genericParameterConstraint.ConstraintType.ConvertCSharp()); - result.AppendLine(); - - hasFirstContraint = true; - } - } - - // New constraint must be last - if (!genericParameter.HasNotNullableValueTypeConstraint && genericParameter.HasDefaultConstructorConstraint) - { - if (hasFirstContraint) - { - result.Append(", "); - } - - result.AppendFormat("new()"); - result.AppendLine(); - } - } - - return result.ToString(); - } - - public static void IgnoreMember(IMemberDefinition memberInfo) + public static string CreateMemberVariableName(IMemberDefinition memberInfo) { - ignoredMembers.Add(memberInfo); + var memberVariableName = char.ToLowerInvariant(memberInfo.Name[0]) + memberInfo.Name[1..]; + if (forbiddenKeywords.Contains(memberVariableName)) + memberVariableName += "_"; + return memberVariableName; } - public static IEnumerable GetSerializableItems(TypeReference type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null) + public static IEnumerable GetSerializableItems(TypeReference type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null, HashSet? ignoredMembers = null) { - foreach (var serializableItemOriginal in GetSerializableItems(type.Resolve(), serializeFields, flagsOverride)) + foreach (var serializableItemOriginal in GetSerializableItems(type.Resolve(), serializeFields, flagsOverride, ignoredMembers)) { var serializableItem = serializableItemOriginal; @@ -270,7 +93,7 @@ public static IEnumerable GetSerializableItems(TypeReference t } } - public static IEnumerable GetSerializableItems(TypeDefinition type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null) + public static IEnumerable GetSerializableItems(TypeDefinition type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null, HashSet? ignoredMembers = null) { ComplexTypeSerializerFlags flags; @@ -282,7 +105,7 @@ public static IEnumerable GetSerializableItems(TypeDefinition if (field.IsStatic) continue; - if (ignoredMembers.Contains(field)) + if (ignoredMembers?.Contains(field) == true) continue; if (field.IsPublic || (field.IsAssembly && field.CustomAttributes.Any(a => a.AttributeType.FullName == "Stride.Core.DataMemberAttribute"))) @@ -315,7 +138,7 @@ public static IEnumerable GetSerializableItems(TypeDefinition } // Ignore blacklisted properties - if (ignoredMembers.Contains(property)) + if (ignoredMembers?.Contains(property) == true) continue; properties.Add(property); @@ -385,7 +208,7 @@ public static IEnumerable GetSerializableItems(TypeDefinition } } - static bool IsAccessibleThroughAccessModifiers(PropertyDefinition property) + private static bool IsAccessibleThroughAccessModifiers(PropertyDefinition property) { var get = property.GetMethod; var set = property.SetMethod; @@ -445,14 +268,6 @@ private static bool IsReadOnlyTypeSerializable(TypeReference type) && !((type.MetadataType == MetadataType.ValueType || type.MetadataType == MetadataType.Class) && type.Resolve().IsValueType); } - public static string CreateMemberVariableName(IMemberDefinition memberInfo) - { - var memberVariableName = char.ToLowerInvariant(memberInfo.Name[0]) + memberInfo.Name[1..]; - if (forbiddenKeywords.Contains(memberVariableName)) - memberVariableName += "_"; - return memberVariableName; - } - public struct SerializableItem { public bool HasFixedAttribute; diff --git a/sources/core/Stride.Core.AssemblyProcessor/SerializationProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/SerializationProcessor.cs index e308694fd4..6d7be94a96 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/SerializationProcessor.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/SerializationProcessor.cs @@ -7,6 +7,7 @@ using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; +using Stride.Core.AssemblyProcessor.Serializers; using Stride.Core.Serialization; using Stride.Core.Storage; using CustomAttributeNamedArgument = Mono.Cecil.CustomAttributeNamedArgument; @@ -16,23 +17,31 @@ namespace Stride.Core.AssemblyProcessor; -internal class SerializationProcessor : IAssemblyDefinitionProcessor +/// +/// Bundles per-descriptor state used during serializer code generation. +/// +internal struct SerializerCodegenContext { - public delegate void RegisterSourceCode(string code, string? name = null); + public TypeDefinition Type; + public TypeDefinition SerializerType; + public TypeReference[] GenericParameters; + public TypeReference TypeWithGenerics; + public SerializationHelpers.SerializableItem[] SerializableItems; + public Dictionary SerializableItemInfos; + public Dictionary LocalsByTypes; + public TypeReference ParentType; + public FieldDefinition ParentSerializerField; + public ModuleDefinition Module; +} +internal class SerializationProcessor : IAssemblyDefinitionProcessor +{ public bool Process(AssemblyProcessorContext context) { - var registry = new ComplexSerializerRegistry(context.Platform, context.Assembly, context.Log); - - // Register default serialization profile (to help AOT generic instantiation of serializers) - RegisterDefaultSerializationProfile(context.AssemblyResolver, context.Assembly, registry, context.Log); - - // Generate serializer code - // Create the serializer code generator - //var serializerGenerator = new ComplexSerializerCodeGenerator(registry); - //sourceCodeRegisterAction(serializerGenerator.TransformText(), "DataSerializers"); + var serializerContext = new CecilSerializerContext(context.Platform, context.Assembly, context.Log); - GenerateSerializerCode(registry, out var serializationHash); + // Generate serializer code using Cecil and ILBuilder + GenerateSerializerCode(serializerContext, out var serializationHash); context.SerializationHash = serializationHash; @@ -40,75 +49,138 @@ public bool Process(AssemblyProcessorContext context) } /// - /// Generates serializer code using Cecil. - /// Note: we might want something more fluent? (probably lot of work to get the system working, but would make changes easier to do -- not sure if worth it considering it didn't change much recently) + /// Generates serializer code using Cecil and for readable IL emission. /// - /// - private static void GenerateSerializerCode(ComplexSerializerRegistry registry, out ObjectId serializationHash) + private static void GenerateSerializerCode(CecilSerializerContext serializerContext, out ObjectId serializationHash) { var hash = new ObjectIdBuilder(); // First, hash global binary format version, in case it gets bumped hash.Write(DataSerializer.BinaryFormatVersion); - var assembly = registry.Assembly; + var assembly = serializerContext.Assembly; + var module = assembly.MainModule; var strideCoreModule = assembly.GetStrideCoreModule(); - var dataSerializerTypeRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializer`1")); + // Generate serializer classes for each pending type + GenerateSerializerTypes(serializerContext, module, strideCoreModule, hash); + + // Generate the factory type with attributes and module initializer + GenerateSerializerFactory(serializerContext, assembly, module, strideCoreModule); + + serializationHash = hash.ComputeHash(); + } + + /// + /// Creates the for a serializer from its , + /// adds it to the module, and wires it into the . + /// + private static TypeDefinition CreateSerializerTypeDefinition( + SerializerDescriptor descriptor, + ModuleDefinition module, + ModuleDefinition strideCoreModule) + { + var type = descriptor.DataType; + var serializerType = new TypeDefinition("Stride.Core.DataSerializers", descriptor.SerializerClassName, + TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Sealed | + TypeAttributes.BeforeFieldInit | + (descriptor.IsPublic ? TypeAttributes.Public : TypeAttributes.NotPublic)); + + // Clone generic parameters from the data type + if (type.HasGenericParameters) + { + foreach (var genericParameter in type.GenericParameters) + { + var newGenericParameter = new GenericParameter(genericParameter.Name, serializerType) + { + Attributes = genericParameter.Attributes + }; + + foreach (var constraint in genericParameter.Constraints) + newGenericParameter.Constraints.Add(constraint); + + serializerType.GenericParameters.Add(newGenericParameter); + } + } + + // Setup base class + var baseSerializerName = descriptor.UseClassDataSerializer + ? "Stride.Core.Serialization.ClassDataSerializer`1" + : "Stride.Core.Serialization.DataSerializer`1"; + var classDataSerializerType = strideCoreModule.GetType(baseSerializerName); + var parentType = module.ImportReference(classDataSerializerType) + .MakeGenericType(type.MakeGenericType(serializerType.GenericParameters.ToArray())); + serializerType.BaseType = parentType; + + module.Types.Add(serializerType); + + // Update the SerializableTypeInfo to point to the real TypeDefinition + descriptor.SerializableTypeInfo.SerializerType = serializerType; + + return serializerType; + } + + /// + /// Generates serializer classes (constructor, Initialize, Serialize methods) for each pending type. + /// + private static void GenerateSerializerTypes( + CecilSerializerContext serializerContext, + ModuleDefinition module, + ModuleDefinition strideCoreModule, + ObjectIdBuilder hash) + { + var dataSerializerTypeRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializer`1")); var serializerSelectorType = strideCoreModule.GetType("Stride.Core.Serialization.SerializerSelector"); - var serializerSelectorTypeRef = assembly.MainModule.ImportReference(serializerSelectorType); - var serializerSelectorGetSerializerRef = assembly.MainModule.ImportReference(serializerSelectorType.Methods.Single(x => x.Name == "GetSerializer" && x.Parameters.Count == 0 && x.GenericParameters.Count == 1)); - var memberSerializerCreateRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.MemberSerializer`1").Methods.Single(x => x.Name == "Create")); + var serializerSelectorTypeRef = module.ImportReference(serializerSelectorType); + var serializerSelectorGetSerializerRef = module.ImportReference(serializerSelectorType.Methods.Single(x => x.Name == "GetSerializer" && x.Parameters.Count == 0 && x.GenericParameters.Count == 1)); + var memberSerializerCreateRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.MemberSerializer`1").Methods.Single(x => x.Name == "Create")); var dataSerializerSerializeMethod = dataSerializerTypeRef.Resolve().Methods.Single(x => x.Name == "Serialize" && (x.Attributes & MethodAttributes.Abstract) != 0); - var dataSerializerSerializeMethodRef = assembly.MainModule.ImportReference(dataSerializerSerializeMethod); + var dataSerializerSerializeMethodRef = module.ImportReference(dataSerializerSerializeMethod); - // Generate serializer code for each type (we generate code similar to ComplexClassSerializerGenerator.tt, see this file for reference) - foreach (var complexType in registry.Context.ComplexTypes) + foreach (var descriptor in serializerContext.PendingSerializers) { - var type = complexType.Key; - var serializerType = (TypeDefinition)complexType.Value.SerializerType; + var type = descriptor.DataType; + var serializerType = CreateSerializerTypeDefinition(descriptor, module, strideCoreModule); var genericParameters = serializerType.GenericParameters.ToArray(); var typeWithGenerics = type.MakeGenericType(genericParameters); // Hash hash.Write(typeWithGenerics.FullName); - TypeReference parentType = null; - FieldDefinition parentSerializerField = null; - if (complexType.Value.ComplexSerializerProcessParentType != null) + var ctx = new SerializerCodegenContext + { + Type = type, + SerializerType = serializerType, + GenericParameters = genericParameters, + TypeWithGenerics = typeWithGenerics, + SerializableItems = descriptor.SerializableItems, + SerializableItemInfos = new Dictionary(TypeReferenceEqualityComparer.Default), + LocalsByTypes = new Dictionary(TypeReferenceEqualityComparer.Default), + Module = module, + }; + + if (descriptor.SerializedParentType != null) { - parentType = complexType.Value.ComplexSerializerProcessParentType; - serializerType.Fields.Add(parentSerializerField = new FieldDefinition("parentSerializer", Mono.Cecil.FieldAttributes.Private, dataSerializerTypeRef.MakeGenericType(parentType))); + ctx.ParentType = descriptor.SerializedParentType; + ctx.ParentSerializerField = new FieldDefinition("parentSerializer", Mono.Cecil.FieldAttributes.Private, dataSerializerTypeRef.MakeGenericType(ctx.ParentType)); + serializerType.Fields.Add(ctx.ParentSerializerField); hash.Write("parent"); } - var serializableItems = ComplexSerializerRegistry.GetSerializableItems(type, true).ToArray(); - var serializableItemInfos = new Dictionary(TypeReferenceEqualityComparer.Default); - var localsByTypes = new Dictionary(TypeReferenceEqualityComparer.Default); + var genericResolver = ResolveGenericsVisitor.FromMapping(type, serializerType); - ResolveGenericsVisitor genericResolver = null; - if (type.HasGenericParameters) + foreach (var serializableItem in ctx.SerializableItems) { - var genericMapping = new Dictionary(); - for (int i = 0; i < type.GenericParameters.Count; i++) - { - genericMapping[type.GenericParameters[i]] = serializerType.GenericParameters[i]; - } - genericResolver = new ResolveGenericsVisitor(genericMapping); - } - - foreach (var serializableItem in serializableItems) - { - if (serializableItemInfos.ContainsKey(serializableItem.Type)) + if (ctx.SerializableItemInfos.ContainsKey(serializableItem.Type)) continue; var serializableItemType = serializableItem.Type; if (genericResolver != null) serializableItemType = genericResolver.VisitDynamic(serializableItemType); var fieldDefinition = new FieldDefinition($"{Utilities.BuildValidClassName(serializableItemType.FullName)}Serializer", Mono.Cecil.FieldAttributes.Private, dataSerializerTypeRef.MakeGenericType(serializableItemType)); - serializableItemInfos.Add(serializableItem.Type, (fieldDefinition, serializableItemType)); + ctx.SerializableItemInfos.Add(serializableItem.Type, (fieldDefinition, serializableItemType)); serializerType.Fields.Add(fieldDefinition); hash.Write(serializableItem.Type.FullName); @@ -116,464 +188,523 @@ private static void GenerateSerializerCode(ComplexSerializerRegistry registry, o hash.Write(serializableItem.AssignBack); } - // Add constructor (call parent constructor) + // Generates: public TypeSerializer() : base() { } var ctor = new MethodDefinition(".ctor", MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.HideBySig | - MethodAttributes.Public, assembly.MainModule.TypeSystem.Void); - ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(serializerType.BaseType.Resolve().GetEmptyConstructor(true)).MakeGeneric(typeWithGenerics))); - ctor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + MethodAttributes.Public, module.TypeSystem.Void); + var ctorIL = new ILBuilder(ctor.Body, module); + ctorIL.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Call, ctorIL.Import(serializerType.BaseType.Resolve().GetEmptyConstructor(true)).MakeGeneric(typeWithGenerics)) + .Emit(OpCodes.Ret); serializerType.Methods.Add(ctor); - // Add Initialize method - var initialize = new MethodDefinition("Initialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, assembly.MainModule.TypeSystem.Void); + // Generates: + // public override void Initialize(SerializerSelector serializerSelector) + // { + // parentSerializer = serializerSelector.GetSerializer(); // if has parent + // field1Serializer = MemberSerializer.Create(serializerSelector, true); + // field2Serializer = MemberSerializer.Create(serializerSelector, true); + // ... + // } + var initialize = new MethodDefinition("Initialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, module.TypeSystem.Void); initialize.Parameters.Add(new ParameterDefinition("serializerSelector", ParameterAttributes.None, serializerSelectorTypeRef)); - if (complexType.Value.ComplexSerializerProcessParentType != null) + var initIL = new ILBuilder(initialize.Body, module); + if (ctx.ParentType != null) { - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, serializerSelectorGetSerializerRef.MakeGenericMethod(parentType))); - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, parentSerializerField.MakeGeneric(genericParameters))); + // this.parentSerializer = serializerSelector.GetSerializer(); + initIL.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Callvirt, serializerSelectorGetSerializerRef.MakeGenericMethod(ctx.ParentType)) + .Emit(OpCodes.Stfld, ctx.ParentSerializerField.MakeGeneric(genericParameters)); } - foreach (var serializableItem in serializableItemInfos) + foreach (var serializableItem in ctx.SerializableItemInfos) { - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_1)); - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Call, memberSerializerCreateRef.MakeGeneric(serializableItem.Value.Type))); - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, serializableItem.Value.SerializerField.MakeGeneric(genericParameters))); + // this.fieldSerializer = MemberSerializer.Create(serializerSelector, true); + initIL.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Ldc_I4_1) + .Emit(OpCodes.Call, memberSerializerCreateRef.MakeGeneric(serializableItem.Value.Type)) + .Emit(OpCodes.Stfld, serializableItem.Value.SerializerField.MakeGeneric(genericParameters)); } - initialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + initIL.Emit(OpCodes.Ret); serializerType.Methods.Add(initialize); // Add Serialize method - var serialize = new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, assembly.MainModule.TypeSystem.Void); - serialize.Parameters.Add(new ParameterDefinition("obj", ParameterAttributes.None, typeWithGenerics.MakeByReferenceType())); - // Copy other parameters from parent method - for (int i = 1; i < dataSerializerSerializeMethod.Parameters.Count; ++i) - { - var parentParameter = dataSerializerSerializeMethod.Parameters[i]; - serialize.Parameters.Add(new ParameterDefinition(parentParameter.Name, ParameterAttributes.None, assembly.MainModule.ImportReference(parentParameter.ParameterType))); - } + GenerateSerializeMethod(ctx, dataSerializerSerializeMethod, dataSerializerSerializeMethodRef); + } + } - if (complexType.Value.ComplexSerializerProcessParentType != null) - { - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, parentSerializerField.MakeGeneric(genericParameters))); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_3)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(parentType))); - } + /// + /// Generates the Serialize method for a complex serializer type. + /// + /// + /// Generates code equivalent to: + /// + /// public override void Serialize(ref T obj, ArchiveMode mode, SerializationStream stream) + /// { + /// parentSerializer.Serialize(ref obj, mode, stream); // if has parent + /// if (mode == ArchiveMode.Serialize) + /// { + /// // For each member (field or property): + /// var tmp = obj.Member; + /// memberSerializer.Serialize(ref tmp, mode, stream); + /// } + /// else + /// { + /// // For each member: + /// var tmp = default(MemberType); + /// memberSerializer.Serialize(ref tmp, mode, stream); + /// obj.Member = tmp; // assign back + /// } + /// } + /// + /// For fields with AssignBack, the field address is used directly (no temp variable). + /// + private static void GenerateSerializeMethod( + SerializerCodegenContext ctx, + MethodDefinition dataSerializerSerializeMethod, + MethodReference dataSerializerSerializeMethodRef) + { + var module = ctx.Module; + var serialize = new MethodDefinition("Serialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, module.TypeSystem.Void); + serialize.Parameters.Add(new ParameterDefinition("obj", ParameterAttributes.None, ctx.TypeWithGenerics.MakeByReferenceType())); + // Copy other parameters from parent method + for (int i = 1; i < dataSerializerSerializeMethod.Parameters.Count; ++i) + { + var parentParameter = dataSerializerSerializeMethod.Parameters[i]; + serialize.Parameters.Add(new ParameterDefinition(parentParameter.Name, ParameterAttributes.None, module.ImportReference(parentParameter.ParameterType))); + } + + var il = new ILBuilder(serialize.Body, module); + + // this.parentSerializer.Serialize(ref obj, mode, stream); + if (ctx.ParentType != null) + { + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldfld, ctx.ParentSerializerField.MakeGeneric(ctx.GenericParameters)) + .Emit(OpCodes.Ldarg_1) + .Emit(OpCodes.Ldarg_2) + .Emit(OpCodes.Ldarg_3) + .Emit(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(ctx.ParentType)); + } + + if (ctx.SerializableItems.Length > 0) + { + var deserializeLabel = ILBuilder.DefineLabel(); + var endLabel = ILBuilder.DefineLabel(); - if (serializableItems.Length > 0) + // Iterate over ArchiveMode + for (int i = 0; i < 2; ++i) { - var blockStartInstructions = new[] { Instruction.Create(OpCodes.Nop), Instruction.Create(OpCodes.Nop) }; - // Iterate over ArchiveMode - for (int i = 0; i < 2; ++i) + var archiveMode = i == 0 ? ArchiveMode.Serialize : ArchiveMode.Deserialize; + + // Check mode + if (archiveMode == ArchiveMode.Serialize) + { + il.Emit(OpCodes.Ldarg_2) + .Emit(OpCodes.Ldc_I4, (int)archiveMode) + .Emit(OpCodes.Ceq) + .Emit(OpCodes.Brfalse, deserializeLabel); + } + else { - var archiveMode = i == 0 ? ArchiveMode.Serialize : ArchiveMode.Deserialize; + il.MarkLabel(deserializeLabel); + } - // Check mode - if (archiveMode == ArchiveMode.Serialize) - { - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4, (int) archiveMode)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ceq)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, blockStartInstructions[0])); - } - else + foreach (var serializableItem in ctx.SerializableItems) + { + if (serializableItem.HasFixedAttribute) { - serialize.Body.Instructions.Add(blockStartInstructions[0]); + throw new NotImplementedException("FixedBuffer attribute is not supported."); } - foreach (var serializableItem in serializableItems) + var memberAssignBack = serializableItem.AssignBack; + var memberVariableName = (serializableItem.MemberInfo is PropertyDefinition || !memberAssignBack) ? SerializationHelpers.CreateMemberVariableName(serializableItem.MemberInfo) : null; + var serializableItemInfo = ctx.SerializableItemInfos[serializableItem.Type]; + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldfld, serializableItemInfo.SerializerField.MakeGeneric(ctx.GenericParameters)); + + var fieldReference = serializableItem.MemberInfo is FieldReference ? il.Import((FieldReference)serializableItem.MemberInfo).MakeGeneric(ctx.GenericParameters) : null; + + if (memberVariableName != null) { - if (serializableItem.HasFixedAttribute) + // Properties (and non-assignback fields) need a temp variable: + // var tmp = obj.Member; // serialize path + // var tmp = default(MemberType); // deserialize path + if (!ctx.LocalsByTypes.TryGetValue(serializableItemInfo.Type, out var tempLocal)) { - throw new NotImplementedException("FixedBuffer attribute is not supported."); + tempLocal = il.AddLocal(serializableItemInfo.Type); + ctx.LocalsByTypes.Add(serializableItemInfo.Type, tempLocal); } - var memberAssignBack = serializableItem.AssignBack; - var memberVariableName = (serializableItem.MemberInfo is PropertyDefinition || !memberAssignBack) ? ComplexSerializerRegistry.CreateMemberVariableName(serializableItem.MemberInfo) : null; - var serializableItemInfo = serializableItemInfos[serializableItem.Type]; - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, serializableItemInfo.SerializerField.MakeGeneric(genericParameters))); - - var fieldReference = serializableItem.MemberInfo is FieldReference ? assembly.MainModule.ImportReference((FieldReference)serializableItem.MemberInfo).MakeGeneric(genericParameters) : null; - - if (memberVariableName != null) + if (!(archiveMode == ArchiveMode.Deserialize && memberAssignBack)) { - // Use a temporary variable - if (!localsByTypes.TryGetValue(serializableItemInfo.Type, out var tempLocal)) - { - tempLocal = new VariableDefinition(serializableItemInfo.Type); - localsByTypes.Add(serializableItemInfo.Type, tempLocal); - serialize.Body.Variables.Add(tempLocal); - serialize.Body.InitLocals = true; - } - - if (!(archiveMode == ArchiveMode.Deserialize && memberAssignBack)) - { - // obj.Member - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); - if (!type.IsValueType) - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref)); - - if (serializableItem.MemberInfo is PropertyDefinition property) - { - var getMethod = property.Resolve().GetMethod; - serialize.Body.Instructions.Add(Instruction.Create(getMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, assembly.MainModule.ImportReference(getMethod).MakeGeneric(genericParameters))); - } - else if (serializableItem.MemberInfo is FieldDefinition) - { - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldfld, fieldReference)); - } - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stloc, tempLocal)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloca, tempLocal)); - } - else - { - // default(T) - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloca, tempLocal)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Dup)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Initobj, serializableItemInfo.Type)); - } + // var tmp = obj.Member; + il.Emit(OpCodes.Ldarg_1); + if (!ctx.Type.IsValueType) + il.Emit(OpCodes.Ldind_Ref); + + EmitLoadMember(il, serializableItem, fieldReference, ctx.GenericParameters); + il.Emit(OpCodes.Stloc, tempLocal) + .Emit(OpCodes.Ldloca, tempLocal); } else { - // Use object directly - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); - if (!type.IsValueType) - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldflda, fieldReference)); - } - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_2)); - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_3)); - - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(serializableItemInfo.Type))); - - if (archiveMode == ArchiveMode.Deserialize && memberVariableName != null && memberAssignBack) - { - // Need to copy back to object - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1)); - if (!type.IsValueType) - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldind_Ref)); - - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ldloc, localsByTypes[serializableItemInfo.Type])); - - if (serializableItem.MemberInfo is PropertyDefinition property) - { - var setMethod = property.Resolve().SetMethod; - serialize.Body.Instructions.Add(Instruction.Create(setMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, assembly.MainModule.ImportReference(setMethod).MakeGeneric(genericParameters))); - } - else if (serializableItem.MemberInfo is FieldDefinition) - { - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Stfld, fieldReference)); - } + // var tmp = default(MemberType); + il.Emit(OpCodes.Ldloca, tempLocal) + .Emit(OpCodes.Dup) + .Emit(OpCodes.Initobj, serializableItemInfo.Type); } } + else + { + // Field with AssignBack: pass field address directly — &obj.field + il.Emit(OpCodes.Ldarg_1); + if (!ctx.Type.IsValueType) + il.Emit(OpCodes.Ldind_Ref); + il.Emit(OpCodes.Ldflda, fieldReference); + } + // memberSerializer.Serialize(ref tmp, mode, stream); + il.Emit(OpCodes.Ldarg_2) + .Emit(OpCodes.Ldarg_3) + .Emit(OpCodes.Callvirt, dataSerializerSerializeMethodRef.MakeGeneric(serializableItemInfo.Type)); - if (archiveMode == ArchiveMode.Serialize) + if (archiveMode == ArchiveMode.Deserialize && memberVariableName != null && memberAssignBack) { - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Br, blockStartInstructions[1])); + // obj.Member = tmp; + il.Emit(OpCodes.Ldarg_1); + if (!ctx.Type.IsValueType) + il.Emit(OpCodes.Ldind_Ref); + + il.Emit(OpCodes.Ldloc, ctx.LocalsByTypes[serializableItemInfo.Type]); + EmitStoreMember(il, serializableItem, fieldReference, ctx.GenericParameters); } } - serialize.Body.Instructions.Add(blockStartInstructions[1]); + if (archiveMode == ArchiveMode.Serialize) + { + il.Emit(OpCodes.Br, endLabel); + } } - serialize.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - serializerType.Methods.Add(serialize); - //assembly.MainModule.Types.Add(serializerType); + il.MarkLabel(endLabel); } + il.Emit(OpCodes.Ret); + ctx.SerializerType.Methods.Add(serialize); + } - var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly); - var reflectionAssembly = CecilExtensions.FindReflectionAssembly(assembly); + private static void EmitLoadMember(ILBuilder il, SerializationHelpers.SerializableItem item, FieldReference fieldReference, TypeReference[] genericParameters) + { + if (item.MemberInfo is PropertyDefinition property) + { + var getMethod = property.Resolve().GetMethod; + il.Emit(getMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, il.Import(getMethod).MakeGeneric(genericParameters)); + } + else if (item.MemberInfo is FieldDefinition) + { + il.Emit(OpCodes.Ldfld, fieldReference); + } + } - // String - var stringType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(string).FullName); - var stringTypeRef = assembly.MainModule.ImportReference(stringType); - // Type - var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName); - var typeTypeRef = assembly.MainModule.ImportReference(typeType); - var getTypeFromHandleMethod = typeType.Methods.First(x => x.Name == nameof(Type.GetTypeFromHandle)); - var getTokenInfoExMethod = reflectionAssembly.MainModule.GetTypeResolved("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == nameof(IntrospectionExtensions.GetTypeInfo)); - var typeInfoType = reflectionAssembly.MainModule.GetTypeResolved(typeof(TypeInfo).FullName); - // Note: TypeInfo.Assembly/Module could be on the type itself or on its parent MemberInfo depending on runtime - var getTypeInfoAssembly = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties).First(x => x.Name == nameof(TypeInfo.Assembly)).GetMethod; - var getTypeInfoModule = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties).First(x => x.Name == nameof(TypeInfo.Module)).GetMethod; - var typeHandleProperty = typeType.Properties.First(x => x.Name == nameof(Type.TypeHandle)); - var getTypeHandleMethodRef = assembly.MainModule.ImportReference(typeHandleProperty.GetMethod); + private static void EmitStoreMember(ILBuilder il, SerializationHelpers.SerializableItem item, FieldReference fieldReference, TypeReference[] genericParameters) + { + if (item.MemberInfo is PropertyDefinition property) + { + var setMethod = property.Resolve().SetMethod; + il.Emit(setMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, il.Import(setMethod).MakeGeneric(genericParameters)); + } + else if (item.MemberInfo is FieldDefinition) + { + il.Emit(OpCodes.Stfld, fieldReference); + } + } + + /// + /// Generates the serializer factory type with s + /// and the module initializer that registers all serializers at runtime. + /// + private static void GenerateSerializerFactory( + CecilSerializerContext serializerContext, + AssemblyDefinition assembly, + ModuleDefinition module, + ModuleDefinition strideCoreModule) + { + var typeTypeRef = module.ImportReference(CecilExtensions.FindCorlibAssembly(assembly).MainModule.GetTypeResolved(typeof(Type).FullName)); - // Generate code + // Create factory type var serializerFactoryType = new TypeDefinition("Stride.Core.DataSerializers", Utilities.BuildValidClassName(assembly.Name.Name) + "SerializerFactory", TypeAttributes.BeforeFieldInit | TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract, - assembly.MainModule.TypeSystem.Object); - assembly.MainModule.Types.Add(serializerFactoryType); + module.TypeSystem.Object); + module.Types.Add(serializerFactoryType); - var dataSerializerModeTypeRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGenericMode")); + // Add [DataSerializerGlobal] attributes for each serializable type + EmitDataSerializerGlobalAttributes(serializerContext, module, strideCoreModule, typeTypeRef, serializerFactoryType); - var dataSerializerGlobalAttribute = strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGlobalAttribute"); - var dataSerializerGlobalCtorRef = assembly.MainModule.ImportReference(dataSerializerGlobalAttribute.GetConstructors().Single(x => !x.IsStatic && x.Parameters.Count == 5)); + // Generate the Initialize method (module initializer body) + GenerateInitializeMethod(serializerContext, assembly, module, strideCoreModule, serializerFactoryType); - foreach (var profile in registry.Context.SerializableTypesProfiles) + // Add [AssemblySerializerFactory] attribute to the assembly + var assemblySerializerFactoryAttribute = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerFactoryAttribute"); + assembly.CustomAttributes.Add(new CustomAttribute(module.ImportReference(assemblySerializerFactoryAttribute.GetEmptyConstructor())) { - foreach (var type in profile.Value.SerializableTypes.Where(x => x.Value.Local)) + Fields = { - // Generating: [DataSerializerGlobalAttribute(<#= type.Value.SerializerType != null ? $"typeof({type.Value.SerializerType.ConvertCSharp(false)})" : "null" #>, typeof(<#= type.Key.ConvertCSharp(false) #>), DataSerializerGenericMode.<#= type.Value.Mode.ToString() #>, <#=type.Value.Inherited ? "true" : "false"#>, <#=type.Value.ComplexSerializer ? "true" : "false"#>, Profile = "<#=profile.Key#>")] - serializerFactoryType.CustomAttributes.Add(new CustomAttribute(dataSerializerGlobalCtorRef) - { - ConstructorArguments = - { - new CustomAttributeArgument(typeTypeRef, type.Value.SerializerType != null ? assembly.MainModule.ImportReference(type.Value.SerializerType) : null), - new CustomAttributeArgument(typeTypeRef, assembly.MainModule.ImportReference(type.Key)), - new CustomAttributeArgument(dataSerializerModeTypeRef, type.Value.Mode), - new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.Inherited), - new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.ComplexSerializer), - }, - Properties = - { - new CustomAttributeNamedArgument("Profile", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, profile.Key)) - }, - }); + new CustomAttributeNamedArgument("Type", new CustomAttributeArgument(typeTypeRef, serializerFactoryType)), } - foreach (var type in profile.Value.GenericSerializableTypes.Where(x => x.Value.Local)) + }); + } + + /// + /// Adds [DataSerializerGlobal] attributes to the factory type for each serializable type. + /// + private static void EmitDataSerializerGlobalAttributes( + CecilSerializerContext serializerContext, + ModuleDefinition module, + ModuleDefinition strideCoreModule, + TypeReference typeTypeRef, + TypeDefinition serializerFactoryType) + { + var dataSerializerModeTypeRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGenericMode")); + var dataSerializerGlobalAttribute = strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerGlobalAttribute"); + var dataSerializerGlobalCtorRef = module.ImportReference(dataSerializerGlobalAttribute.GetConstructors().Single(x => !x.IsStatic && x.Parameters.Count == 5)); + + foreach (var profile in serializerContext.SerializableTypesProfiles) + { + // Emit attributes for both concrete and generic serializable types + var allTypes = profile.Value.SerializableTypes.Where(x => x.Value.Local) + .Concat(profile.Value.GenericSerializableTypes.Where(x => x.Value.Local)); + + foreach (var type in allTypes) { - // Generating: [DataSerializerGlobalAttribute(<#= type.Value.SerializerType != null ? $"typeof({type.Value.SerializerType.ConvertCSharp(true)})" : "null" #>, typeof(<#= type.Key.ConvertCSharp(true) #>), DataSerializerGenericMode.<#= type.Value.Mode.ToString() #>, <#=type.Value.Inherited ? "true" : "false"#>, <#=type.Value.ComplexSerializer ? "true" : "false"#>, Profile = "<#=profile.Key#>")] serializerFactoryType.CustomAttributes.Add(new CustomAttribute(dataSerializerGlobalCtorRef) { ConstructorArguments = { - new CustomAttributeArgument(typeTypeRef, type.Value.SerializerType != null ? assembly.MainModule.ImportReference(type.Value.SerializerType) : null), - new CustomAttributeArgument(typeTypeRef, assembly.MainModule.ImportReference(type.Key)), + new CustomAttributeArgument(typeTypeRef, type.Value.SerializerType != null ? module.ImportReference(type.Value.SerializerType) : null), + new CustomAttributeArgument(typeTypeRef, module.ImportReference(type.Key)), new CustomAttributeArgument(dataSerializerModeTypeRef, type.Value.Mode), - new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.Inherited), - new CustomAttributeArgument(assembly.MainModule.TypeSystem.Boolean, type.Value.ComplexSerializer), + new CustomAttributeArgument(module.TypeSystem.Boolean, type.Value.Inherited), + new CustomAttributeArgument(module.TypeSystem.Boolean, type.Value.IsGeneratedSerializer), }, Properties = { - new CustomAttributeNamedArgument("Profile", new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, profile.Key)) + new CustomAttributeNamedArgument("Profile", new CustomAttributeArgument(module.TypeSystem.String, profile.Key)) }, }); } } + } + + /// + /// Generates the Initialize method that serves as the module initializer, + /// registering all serializers with the runtime . + /// + /// + /// Generates code equivalent to: + /// + /// static void Initialize() + /// { + /// var assemblySerializers = new AssemblySerializers(typeof(Factory).GetTypeInfo().Assembly); + /// // Register DataContract aliases + /// assemblySerializers.DataContractAliases.Add(new DataContractAlias("alias", typeof(T), isRemap)); + /// // Register referenced modules + /// assemblySerializers.Modules.Add(typeof(RefFactory).GetTypeInfo().Module); + /// // Register serializer entries per profile + /// var profile = new AssemblySerializersPerProfile(); + /// profile.Add(new AssemblySerializerEntry(objectId, typeof(T), typeof(TSerializer))); + /// assemblySerializers.Profiles["Default"] = profile; + /// DataSerializerFactory.RegisterSerializationAssembly(assemblySerializers); + /// AssemblyRegistry.Register(typeof(Factory).GetTypeInfo().Assembly, new[] { "Engine" }); + /// } + /// + /// + private static void GenerateInitializeMethod( + CecilSerializerContext serializerContext, + AssemblyDefinition assembly, + ModuleDefinition module, + ModuleDefinition strideCoreModule, + TypeDefinition serializerFactoryType) + { + var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly); + var reflectionAssembly = CecilExtensions.FindReflectionAssembly(assembly); // Create Initialize method var initializeMethod = new MethodDefinition("Initialize", MethodAttributes.Assembly | MethodAttributes.HideBySig | MethodAttributes.Static, - assembly.MainModule.TypeSystem.Void); + module.TypeSystem.Void); serializerFactoryType.Methods.Add(initializeMethod); - // Obtain the static constructor of and the return instruction - var moduleConstructor = assembly.OpenModuleConstructor(out var returnInstruction); + var il = new ILBuilder(initializeMethod.Body, module); - // Get the IL processor of the module constructor - var il = moduleConstructor.Body.GetILProcessor(); - - // Create the call to Initialize method - var initializeMethodReference = assembly.MainModule.ImportReference(initializeMethod); - var callInitializeInstruction = il.Create(OpCodes.Call, initializeMethodReference); - - var initializeMethodIL = initializeMethod.Body.GetILProcessor(); + // Resolve and import reflection helpers + var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName); + var getTypeFromHandleRef = il.Import(typeType.Methods.First(x => x.Name == nameof(Type.GetTypeFromHandle))); + var getTypeInfoRef = il.Import(reflectionAssembly.MainModule.GetTypeResolved("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == nameof(IntrospectionExtensions.GetTypeInfo))); + var typeInfoType = reflectionAssembly.MainModule.GetTypeResolved(typeof(TypeInfo).FullName); + // Note: TypeInfo.Assembly/Module could be on the type itself or on its parent MemberInfo depending on runtime + var typeInfoProperties = typeInfoType.Properties.Concat(typeInfoType.BaseType.Resolve().Properties); + var getAssemblyRef = il.Import(typeInfoProperties.First(x => x.Name == nameof(TypeInfo.Assembly)).GetMethod); + var getModuleRef = il.Import(typeInfoProperties.First(x => x.Name == nameof(TypeInfo.Module)).GetMethod); + var getTypeHandleMethodRef = module.ImportReference(typeType.Properties.First(x => x.Name == nameof(Type.TypeHandle)).GetMethod); - // Generating: var assemblySerializers = new AssemblySerializers(typeof(<#=registry.ClassName#>).GetTypeInfo().Assembly); + // Emit: var assemblySerializers = new AssemblySerializers(typeof(Factory).GetTypeInfo().Assembly); var assemblySerializersType = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializers"); + il.EmitTypeofAssembly(serializerFactoryType, getTypeFromHandleRef, getTypeInfoRef, getAssemblyRef) + .Emit(OpCodes.Newobj, il.Import(assemblySerializersType.Methods.Single(x => x.IsConstructor && x.Parameters.Count == 1))); + + EmitDataContractAliases(il, serializerContext, module, assemblySerializersType, getTypeFromHandleRef); + EmitModuleRegistrations(il, serializerContext, module, assemblySerializersType, getTypeFromHandleRef, getTypeInfoRef, getModuleRef); + EmitProfileEntries(il, serializerContext, module, strideCoreModule, mscorlibAssembly, assemblySerializersType, getTypeFromHandleRef, getTypeHandleMethodRef); + + // Emit: DataSerializerFactory.RegisterSerializationAssembly(assemblySerializers); + var dataSerializerFactoryRegisterRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerFactory").Methods.Single(x => x.Name == "RegisterSerializationAssembly" && x.Parameters[0].ParameterType.FullName == assemblySerializersType.FullName)); + il.Emit(OpCodes.Call, dataSerializerFactoryRegisterRef); + + // Emit: AssemblyRegistry.Register(typeof(Factory).GetTypeInfo().Assembly, new[] { "Engine" }); + il.EmitTypeofAssembly(serializerFactoryType, getTypeFromHandleRef, getTypeInfoRef, getAssemblyRef) + .Emit(OpCodes.Ldc_I4_1) + .Emit(OpCodes.Newarr, module.TypeSystem.String) + .Emit(OpCodes.Dup) + .Emit(OpCodes.Ldc_I4_0) + .Emit(OpCodes.Ldstr, Reflection.AssemblyCommonCategories.Engine) + .Emit(OpCodes.Stelem_Ref); + + var assemblyRegistryRegisterMethodRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Reflection.AssemblyRegistry").Methods.Single(x => x.Name == "Register" && x.Parameters[1].ParameterType.IsArray)); + il.Emit(OpCodes.Call, assemblyRegistryRegisterMethodRef) + .Emit(OpCodes.Ret); + + // Wire up module constructor to call Initialize + var moduleConstructor = assembly.OpenModuleConstructor(out var returnInstruction); + var moduleCtorIL = moduleConstructor.Body.GetILProcessor(); + var callInitializeInstruction = moduleCtorIL.Create(OpCodes.Call, module.ImportReference(initializeMethod)); + moduleCtorIL.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction); + } + + /// + /// Emits: assemblySerializers.DataContractAliases.Add(new (alias, typeof(T), isRemap)); for each alias. + /// + private static void EmitDataContractAliases( + ILBuilder il, CecilSerializerContext serializerContext, ModuleDefinition module, + TypeDefinition assemblySerializersType, MethodReference getTypeFromHandleRef) + { + var getAliasesRef = module.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "DataContractAliases").GetMethod); + var addMethod = getAliasesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add"); + var aliasTypeRef = ((GenericInstanceType)getAliasesRef.ReturnType).GenericArguments[0]; + var aliasCtorRef = module.ImportReference(aliasTypeRef.Resolve().GetConstructors().Single()); + var addRef = module.ImportReference(addMethod).MakeGeneric(aliasTypeRef); - var assemblySerializersGetDataContractAliasesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "DataContractAliases").GetMethod); - var assemblySerializersGetDataContractAliasesAdd = assemblySerializersGetDataContractAliasesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add"); - var dataContractAliasTypeRef = ((GenericInstanceType)assemblySerializersGetDataContractAliasesRef.ReturnType).GenericArguments[0]; - var dataContractAliasTypeCtorRef = assembly.MainModule.ImportReference(dataContractAliasTypeRef.Resolve().GetConstructors().Single()); - var assemblySerializersGetDataContractAliasesAddRef = assembly.MainModule.ImportReference(assemblySerializersGetDataContractAliasesAdd).MakeGeneric(dataContractAliasTypeRef); - initializeMethodIL.Emit(OpCodes.Ldtoken, serializerFactoryType); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod)); - initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoAssembly)); - initializeMethodIL.Emit(OpCodes.Newobj, assembly.MainModule.ImportReference(assemblySerializersType.Methods.Single(x => x.IsConstructor && x.Parameters.Count == 1))); - - foreach (var alias in registry.Context.DataContractAliases) + foreach (var alias in serializerContext.DataContractAliases) { - initializeMethodIL.Emit(OpCodes.Dup); - - // Generating: assemblySerializers.DataContractAliases.Add(new AssemblySerializers.DataContractAlias(@"<#= alias.Item1 #>", typeof(<#= alias.Item2.ConvertCSharp(true) #>), <#=alias.Item3 ? "true" : "false"#>)); - initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetDataContractAliasesRef); - initializeMethodIL.Emit(OpCodes.Ldstr, alias.Item1); - initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(alias.Item2)); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); - initializeMethodIL.Emit(alias.Item3 ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - initializeMethodIL.Emit(OpCodes.Newobj, dataContractAliasTypeCtorRef); - initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetDataContractAliasesAddRef); + il.Emit(OpCodes.Dup) + .Emit(OpCodes.Call, getAliasesRef) + .Emit(OpCodes.Ldstr, alias.Item1) + .EmitTypeof(il.Import(alias.Item2), getTypeFromHandleRef) + .Emit(alias.Item3 ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0) + .Emit(OpCodes.Newobj, aliasCtorRef) + .Emit(OpCodes.Call, addRef); } + } - var assemblySerializersGetModulesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Modules").GetMethod); - var assemblySerializersGetModulesAdd = assemblySerializersGetModulesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add"); - var moduleRef = ((GenericInstanceType)assemblySerializersGetModulesRef.ReturnType).GenericArguments[0]; - var assemblySerializersGetModulesAddRef = assembly.MainModule.ImportReference(assemblySerializersGetModulesAdd).MakeGeneric(moduleRef); + /// + /// Emits: assemblySerializers.Modules.Add(typeof(RefFactory).GetTypeInfo().Module); for each referenced assembly. + /// + private static void EmitModuleRegistrations( + ILBuilder il, CecilSerializerContext serializerContext, ModuleDefinition module, + TypeDefinition assemblySerializersType, + MethodReference getTypeFromHandleRef, MethodReference getTypeInfoRef, MethodReference getModuleRef) + { + var getModulesRef = module.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Modules").GetMethod); + var addMethod = getModulesRef.ReturnType.Resolve().Methods.First(x => x.Name == "Add"); + var moduleTypeRef = ((GenericInstanceType)getModulesRef.ReturnType).GenericArguments[0]; + var addRef = module.ImportReference(addMethod).MakeGeneric(moduleTypeRef); - foreach (var referencedAssemblySerializerFactoryType in registry.ReferencedAssemblySerializerFactoryTypes) + foreach (var referencedAssemblySerializerFactoryType in serializerContext.ReferencedAssemblySerializerFactoryTypes) { - initializeMethodIL.Emit(OpCodes.Dup); - - // Generating: assemblySerializers.Modules.Add(typeof(<#=referencedAssemblySerializerFactoryType.ConvertCSharp()#>).GetTypeInfo().Module); - initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetModulesRef); - initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(referencedAssemblySerializerFactoryType)); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod)); - initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoModule)); - initializeMethodIL.Emit(OpCodes.Call, assemblySerializersGetModulesAddRef); + il.Emit(OpCodes.Dup) + .Emit(OpCodes.Call, getModulesRef) + .EmitTypeofModule(il.Import(referencedAssemblySerializerFactoryType), getTypeFromHandleRef, getTypeInfoRef, getModuleRef) + .Emit(OpCodes.Call, addRef); } + } - var objectIdCtorRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Storage.ObjectId").GetConstructors().Single(x => x.Parameters.Count == 4)); - var serializerEntryTypeCtorRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerEntry").GetConstructors().Single()); - var assemblySerializersPerProfileType = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializersPerProfile"); - var assemblySerializersPerProfileTypeAddRef = assembly.MainModule.ImportReference(assemblySerializersPerProfileType.BaseType.Resolve().Methods.First(x => x.Name == "Add")).MakeGeneric(serializerEntryTypeCtorRef.DeclaringType); - var assemblySerializersPerProfileTypeCtorRef = assembly.MainModule.ImportReference(assemblySerializersPerProfileType.GetEmptyConstructor()); - var assemblySerializersGetProfilesRef = assembly.MainModule.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Profiles").GetMethod); - var assemblySerializersGetProfilesSetItemRef = assembly.MainModule.ImportReference(assemblySerializersGetProfilesRef.ReturnType.Resolve().Methods.First(x => x.Name == "set_Item")) - .MakeGeneric([.. ((GenericInstanceType)assemblySerializersGetProfilesRef.ReturnType).GenericArguments]); + /// + /// Emits per-profile serializer registration: + /// + /// var profile = new AssemblySerializersPerProfile(); + /// profile.Add(new AssemblySerializerEntry(typeId, typeof(T), typeof(TSerializer))); + /// RuntimeHelpers.RunClassConstructor(typeof(TSerializer).TypeHandle); // if has static ctor + /// assemblySerializers.Profiles["Default"] = profile; + /// + /// + private static void EmitProfileEntries( + ILBuilder il, CecilSerializerContext serializerContext, ModuleDefinition module, + ModuleDefinition strideCoreModule, AssemblyDefinition mscorlibAssembly, + TypeDefinition assemblySerializersType, + MethodReference getTypeFromHandleRef, MethodReference getTypeHandleMethodRef) + { + var objectIdCtorRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Storage.ObjectId").GetConstructors().Single(x => x.Parameters.Count == 4)); + var serializerEntryTypeCtorRef = module.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerEntry").GetConstructors().Single()); + var perProfileType = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializersPerProfile"); + var perProfileAddRef = module.ImportReference(perProfileType.BaseType.Resolve().Methods.First(x => x.Name == "Add")).MakeGeneric(serializerEntryTypeCtorRef.DeclaringType); + var perProfileCtorRef = module.ImportReference(perProfileType.GetEmptyConstructor()); + var getProfilesRef = module.ImportReference(assemblySerializersType.Properties.First(x => x.Name == "Profiles").GetMethod); + var setItemRef = module.ImportReference(getProfilesRef.ReturnType.Resolve().Methods.First(x => x.Name == "set_Item")) + .MakeGeneric([.. ((GenericInstanceType)getProfilesRef.ReturnType).GenericArguments]); var runtimeHelpersType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(RuntimeHelpers).FullName); - var runClassConstructorMethod = assembly.MainModule.ImportReference(runtimeHelpersType.Methods.Single(x => x.IsPublic && x.Name == "RunClassConstructor" && x.Parameters.Count == 1 && x.Parameters[0].ParameterType.FullName == typeof(RuntimeTypeHandle).FullName)); + var runClassConstructorMethod = module.ImportReference(runtimeHelpersType.Methods.Single(x => x.IsPublic && x.Name == "RunClassConstructor" && x.Parameters.Count == 1 && x.Parameters[0].ParameterType.FullName == typeof(RuntimeTypeHandle).FullName)); - foreach (var profile in registry.Context.SerializableTypesProfiles) + foreach (var profile in serializerContext.SerializableTypesProfiles) { - initializeMethodIL.Emit(OpCodes.Dup); - - // Generating: var assemblySerializersProfile = new AssemblySerializersPerProfile(); - // Generating: assemblySerializers.Profiles["<#=profile.Key#>"] = assemblySerializersProfile; - initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersGetProfilesRef); - initializeMethodIL.Emit(OpCodes.Ldstr, profile.Key); - initializeMethodIL.Emit(OpCodes.Newobj, assemblySerializersPerProfileTypeCtorRef); + il.Emit(OpCodes.Dup) + .Emit(OpCodes.Callvirt, getProfilesRef) + .Emit(OpCodes.Ldstr, profile.Key) + .Emit(OpCodes.Newobj, perProfileCtorRef); foreach (var type in profile.Value.SerializableTypes.Where(x => x.Value.Local)) { - // Generating: assemblySerializersProfile.Add(new AssemblySerializerEntry(<#=type.Key.ConvertTypeId()#>, typeof(<#= type.Key.ConvertCSharp() #>), <# if (type.Value.SerializerType != null) { #>typeof(<#= type.Value.SerializerType.ConvertCSharp() #>)<# } else { #>null<# } #>)); - initializeMethodIL.Emit(OpCodes.Dup); + il.Emit(OpCodes.Dup); var typeName = type.Key.ConvertCSharp(false); var typeId = ObjectId.FromBytes(Encoding.UTF8.GetBytes(typeName)); unsafe { - var typeIdHash = (int*)&typeId; + var typeIdHash = (int*)&typeId; - for (int i = 0; i < ObjectId.HashSize / 4; ++i) - initializeMethodIL.Emit(OpCodes.Ldc_I4, typeIdHash[i]); - } - - initializeMethodIL.Emit(OpCodes.Newobj, objectIdCtorRef); + for (int i = 0; i < ObjectId.HashSize / 4; ++i) + il.Emit(OpCodes.Ldc_I4, typeIdHash[i]); + } - initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(type.Key)); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); + il.Emit(OpCodes.Newobj, objectIdCtorRef) + .EmitTypeof(il.Import(type.Key), getTypeFromHandleRef); if (type.Value.SerializerType != null) { - initializeMethodIL.Emit(OpCodes.Ldtoken, assembly.MainModule.ImportReference(type.Value.SerializerType)); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); + il.EmitTypeof(il.Import(type.Value.SerializerType), getTypeFromHandleRef); } else { - initializeMethodIL.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Ldnull); } - initializeMethodIL.Emit(OpCodes.Newobj, serializerEntryTypeCtorRef); - initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersPerProfileTypeAddRef); + il.Emit(OpCodes.Newobj, serializerEntryTypeCtorRef) + .Emit(OpCodes.Callvirt, perProfileAddRef); if (type.Value.SerializerType?.Resolve()?.Methods.Any(x => x.IsConstructor && x.IsStatic) == true) { - // Generating: System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(<#=type.Value.SerializerType.ConvertCSharp()#>).TypeHandle); - initializeMethodIL.Append(Instruction.Create(OpCodes.Ldtoken, type.Value.SerializerType)); - initializeMethodIL.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod))); - initializeMethodIL.Append(Instruction.Create(OpCodes.Callvirt, getTypeHandleMethodRef)); - initializeMethodIL.Append(Instruction.Create(OpCodes.Call, runClassConstructorMethod)); + // RuntimeHelpers.RunClassConstructor(typeof(SerializerType).TypeHandle); + il.EmitTypeHandle(type.Value.SerializerType, getTypeFromHandleRef, getTypeHandleMethodRef) + .Emit(OpCodes.Call, runClassConstructorMethod); } } - initializeMethodIL.Emit(OpCodes.Callvirt, assemblySerializersGetProfilesSetItemRef); + il.Emit(OpCodes.Callvirt, setItemRef); } - - // Generating: DataSerializerFactory.RegisterSerializationAssembly(assemblySerializers); - var dataSerializerFactoryRegisterSerializationAssemblyMethodRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Serialization.DataSerializerFactory").Methods.Single(x => x.Name == "RegisterSerializationAssembly" && x.Parameters[0].ParameterType.FullName == assemblySerializersType.FullName)); - initializeMethodIL.Emit(OpCodes.Call, dataSerializerFactoryRegisterSerializationAssemblyMethodRef); - - // Generating: AssemblyRegistry.Register(typeof(<#=registry.ClassName#>).GetTypeInfo().Assembly, AssemblyCommonCategories.Engine); - initializeMethodIL.Emit(OpCodes.Ldtoken, serializerFactoryType); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod)); - initializeMethodIL.Emit(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod)); - initializeMethodIL.Emit(OpCodes.Callvirt, assembly.MainModule.ImportReference(getTypeInfoAssembly)); - - // create new[] { AssemblyCommonCategories.Engine } - initializeMethodIL.Emit(OpCodes.Ldc_I4_1); - initializeMethodIL.Emit(OpCodes.Newarr, assembly.MainModule.TypeSystem.String); - initializeMethodIL.Emit(OpCodes.Dup); - initializeMethodIL.Emit(OpCodes.Ldc_I4_0); - initializeMethodIL.Emit(OpCodes.Ldstr, Reflection.AssemblyCommonCategories.Engine); - initializeMethodIL.Emit(OpCodes.Stelem_Ref); - - var assemblyRegistryRegisterMethodRef = assembly.MainModule.ImportReference(strideCoreModule.GetType("Stride.Core.Reflection.AssemblyRegistry").Methods.Single(x => x.Name == "Register" && x.Parameters[1].ParameterType.IsArray)); - initializeMethodIL.Emit(OpCodes.Call, assemblyRegistryRegisterMethodRef); - - initializeMethodIL.Emit(OpCodes.Ret); - - // Add AssemblySerializerFactoryAttribute - var assemblySerializerFactoryAttribute = strideCoreModule.GetType("Stride.Core.Serialization.AssemblySerializerFactoryAttribute"); - assembly.CustomAttributes.Add(new CustomAttribute(assembly.MainModule.ImportReference(assemblySerializerFactoryAttribute.GetEmptyConstructor())) - { - Fields = - { - new CustomAttributeNamedArgument("Type", new CustomAttributeArgument(typeTypeRef, serializerFactoryType)), - } - }); - // Insert the call before the end of the method body - il.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction); - - serializationHash = hash.ComputeHash(); } - private static void RegisterDefaultSerializationProfile(IAssemblyResolver assemblyResolver, AssemblyDefinition assembly, ComplexSerializerRegistry registry, System.IO.TextWriter log) - { - var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly); - if (mscorlibAssembly == null) - { - log.WriteLine("Missing mscorlib.dll from assembly {0}", assembly.FullName); - throw new InvalidOperationException("Missing mscorlib.dll from assembly"); - } - - var coreSerializationAssembly = assemblyResolver.Resolve(new AssemblyNameReference("Stride.Core", null)); - - // Register serializer factories (determine which type requires which serializer) - registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(IList<>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ListInterfaceSerializer`1"))); - registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(List<>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ListSerializer`1"))); - registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(KeyValuePair<,>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.KeyValuePairSerializer`2"))); - registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(IDictionary<,>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.DictionaryInterfaceSerializer`2"))); - registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(Dictionary<,>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.DictionarySerializer`2"))); - registry.SerializerFactories.Add(new CecilGenericSerializerFactory(typeof(Nullable<>), coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.NullableSerializer`1"))); - registry.SerializerFactories.Add(new CecilEnumSerializerFactory(coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.EnumSerializer`1"))); - registry.SerializerFactories.Add(new CecilArraySerializerFactory(coreSerializationAssembly.MainModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ArraySerializer`1"))); - - // Iterate over tuple size - for (int i = 1; i <= 4; ++i) - { - registry.SerializerDependencies.Add(new CecilSerializerDependency( - string.Format("System.Tuple`{0}", i), - coreSerializationAssembly.MainModule.GetTypeResolved(string.Format("Stride.Core.Serialization.Serializers.TupleSerializer`{0}", i)))); - - registry.SerializerDependencies.Add(new CecilSerializerDependency(string.Format("Stride.Core.Serialization.Serializers.TupleSerializer`{0}", i))); - } - - // Register serializer dependencies (determine which serializer serializes which sub-type) - registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.ArraySerializer`1")); - registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.KeyValuePairSerializer`2")); - registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.ListSerializer`1")); - registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.ListInterfaceSerializer`1")); - registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.NullableSerializer`1")); - registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.DictionarySerializer`2", - mscorlibAssembly.MainModule.GetTypeResolved(typeof(KeyValuePair<,>).FullName))); - registry.SerializerDependencies.Add(new CecilSerializerDependency("Stride.Core.Serialization.Serializers.DictionaryInterfaceSerializer`2", - mscorlibAssembly.MainModule.GetTypeResolved(typeof(KeyValuePair<,>).FullName))); - } } public static class HashExtensions diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilComplexClassSerializerProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilDataContractSerializerProcessor.cs similarity index 86% rename from sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilComplexClassSerializerProcessor.cs rename to sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilDataContractSerializerProcessor.cs index 99d0969aef..22b0fe4e21 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilComplexClassSerializerProcessor.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilDataContractSerializerProcessor.cs @@ -6,14 +6,13 @@ namespace Stride.Core.AssemblyProcessor.Serializers; -class CecilComplexClassSerializerProcessor : ICecilSerializerProcessor +class CecilDataContractSerializerProcessor : ICecilSerializerProcessor { public void ProcessSerializers(CecilSerializerContext context) { foreach (var type in context.Assembly.MainModule.GetAllTypes().ToArray()) { - // Force generation of serializers (complex types, etc...) - // Check complex type definitions + // Discover [DataContract] types and resolve their serializers ProcessType(context, type); } } diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilSerializerContext.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilSerializerContext.cs index 920129fa52..227d1f6000 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilSerializerContext.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/CecilSerializerContext.cs @@ -18,10 +18,39 @@ public CecilSerializerContext(PlatformType platform, AssemblyDefinition assembly SerializableTypesProfiles = []; SerializableTypes = new ProfileInfo(); SerializableTypesProfiles.Add("Default", SerializableTypes); - ComplexTypes = []; + PendingSerializers = []; + IgnoredMembers = []; this.log = log; StrideCoreModule = assembly.GetStrideCoreModule(); + + // Discover referenced assemblies' serializer factories + foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences) + { + try + { + var referencedAssembly = assembly.MainModule.AssemblyResolver.Resolve(referencedAssemblyName); + var factoryType = GetSerializerFactoryType(referencedAssembly); + if (factoryType != null) + ReferencedAssemblySerializerFactoryTypes.Add(factoryType); + } + catch (AssemblyResolutionException) + { + } + } + + // Run the processor pipeline + ICecilSerializerProcessor[] processors = + [ + new ReferencedAssemblySerializerProcessor(), + new CecilDataContractSerializerProcessor(), + new PropertyKeySerializerProcessor(), + new UpdateEngineProcessor(), + new ProfileSerializerProcessor(), + new DataContractAliasProcessor(), + ]; + foreach (var processor in processors) + processor.ProcessSerializers(this); } public PlatformType Platform { get; } @@ -29,21 +58,17 @@ public CecilSerializerContext(PlatformType platform, AssemblyDefinition assembly /// /// Gets the assembly being processed. /// - /// - /// The assembly being processed. - /// public AssemblyDefinition Assembly { get; } public ModuleDefinition StrideCoreModule { get; } + public List ReferencedAssemblySerializerFactoryTypes { get; } = []; + public List> DataContractAliases { get; } = []; /// /// Gets the list of serializable type grouped by profile. /// - /// - /// The serializable types profiles. - /// public Dictionary SerializableTypesProfiles { get; } /// @@ -52,158 +77,154 @@ public CecilSerializerContext(PlatformType platform, AssemblyDefinition assembly public ProfileInfo SerializableTypes { get; } /// - /// Gets the list of complex serializers to generate. + /// Gets the list of serializers pending code generation. + /// Populated during collection, consumed by the code generation phase. /// - /// - /// The list of complex serializers to generate. - /// - public Dictionary ComplexTypes { get; } + public List PendingSerializers { get; } + + /// + /// Members that should be excluded from serialization (e.g. members without valid serializers). + /// + public HashSet IgnoredMembers { get; } /// /// Ensure the following type can be serialized. If not, try to register appropriate serializer. /// This method can be recursive. /// - /// The type. - public SerializableTypeInfo GenerateSerializer(TypeReference type, bool force = true, string profile = "Default", bool generic = false) + public SerializableTypeInfo ResolveSerializer(TypeReference type, bool force = true, string profile = "Default", bool generic = false) { var serializableTypes = GetSerializableTypes(profile); // Already handled? if (serializableTypes.TryGetSerializableTypeInfo(type, generic, out var serializableTypeInfo)) return serializableTypeInfo; - - // Try to get one without generic if (generic && serializableTypes.TryGetSerializableTypeInfo(type, false, out serializableTypeInfo)) return serializableTypeInfo; - // TDOO: Array, List, Generic types, etc... (equivalent of previous serializer factories) + // Arrays if (type is ArrayType arrayType) - { - // Only proceed if element type is serializable (and in Default profile, otherwise ElementType is enough) - if (GenerateSerializer(arrayType.ElementType, force, profile) != null) - { - if (profile == "Default") - { - var arraySerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ArraySerializer`1"); - var serializerType = new GenericInstanceType(arraySerializerType); - serializerType.GenericArguments.Add(arrayType.ElementType); - AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true), profile); - return serializableTypeInfo; - } - else - { - // Fallback to default - return GenerateSerializer(type, force, "Default"); - } - } + return ResolveArraySerializer(arrayType, force, profile); - return null; - } - - // Try to match with existing generic serializer (for List, Dictionary, etc...) + // Generic instances (List, Dictionary, etc.) if (type is GenericInstanceType genericInstanceType) { - var elementType = genericInstanceType.ElementType; - SerializableTypeInfo elementSerializableTypeInfo; - if ((elementSerializableTypeInfo = GenerateSerializer(elementType, false, profile, true)) != null) - { - switch (elementSerializableTypeInfo.Mode) - { - case DataSerializerGenericMode.Type: - { - var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType); - serializerType.GenericArguments.Add(type); - - AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile); - break; - } - case DataSerializerGenericMode.TypeAndGenericArguments: - { - var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType); - serializerType.GenericArguments.Add(type); - foreach (var genericArgument in genericInstanceType.GenericArguments) - { - // Generate serializer for each generic argument - //GenerateSerializer(genericArgument); - - serializerType.GenericArguments.Add(genericArgument); - } - - AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile); - break; - } - case DataSerializerGenericMode.GenericArguments: - { - var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType); - foreach (var genericArgument in genericInstanceType.GenericArguments) - { - // Generate serializer for each generic argument - //GenerateSerializer(genericArgument); - - serializerType.GenericArguments.Add(genericArgument); - } - - AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile); - break; - } - default: - throw new NotImplementedException(); - } - - if (elementSerializableTypeInfo.ComplexSerializer) - { - ProcessComplexSerializerMembers(type, serializableTypeInfo); - } + serializableTypeInfo = ResolveGenericSerializer(genericInstanceType, profile); + if (serializableTypeInfo != null) return serializableTypeInfo; - } } - // Check complex type definitions - if (profile == "Default" && (serializableTypeInfo = FindSerializerInfo(type, generic)) != null) + // Check type definitions for serializer info (only in Default profile) + if (profile == "Default") { - return serializableTypeInfo; + serializableTypeInfo = FindSerializerInfo(type, generic); + if (serializableTypeInfo != null) + return serializableTypeInfo; } - // Fallback to default + // Non-Default profiles fall back to Default if (profile != "Default") - return GenerateSerializer(type, force, "Default", generic); + return ResolveSerializer(type, force, "Default", generic); - // Part after that is only if a serializer is absolutely necessary. This is skipped when scanning normal assemblies type that might have nothing to do with serialization. + // Past this point, only proceed if a serializer is absolutely necessary. + // This is skipped when scanning normal assembly types that might have nothing to do with serialization. if (!force) return null; - // Non instantiable type? (object, interfaces, abstract classes) + // Non-instantiable types (object, interfaces, abstract classes) // Serializer can be null since they will be inherited anyway (handled through MemberSerializer) var resolvedType = type.Resolve(); if (resolvedType.IsAbstract || resolvedType.IsInterface || resolvedType.FullName == typeof(object).FullName) { - AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(null, true), profile); + serializableTypeInfo = new SerializableTypeInfo(null, true); + AddSerializableType(type, serializableTypeInfo, profile); return serializableTypeInfo; } return null; } - private void ProcessComplexSerializerMembers(TypeReference type, SerializableTypeInfo serializableTypeInfo, string profile = "Default") + private SerializableTypeInfo ResolveArraySerializer(ArrayType arrayType, bool force, string profile) { - // Process base type (for complex serializers) - // Check if we have any serializable closed base type and collect it to pass it over to GenerateSerializerCode later, - // who'll ensure the generated serializer for this type calls into its base types' serializer + // Only proceed if element type is serializable + if (ResolveSerializer(arrayType.ElementType, force, profile) == null) + return null; + + // Non-Default profiles fall back to Default for array serializer registration + if (profile != "Default") + return ResolveSerializer(arrayType, force, "Default"); + + var arraySerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ArraySerializer`1"); + var serializerType = new GenericInstanceType(arraySerializerType); + serializerType.GenericArguments.Add(arrayType.ElementType); + + var info = new SerializableTypeInfo(serializerType, true); + AddSerializableType(arrayType, info, profile); + return info; + } + + private SerializableTypeInfo ResolveGenericSerializer(GenericInstanceType type, string profile) + { + // Try to match with existing generic serializer (for List, Dictionary, etc.) + var elementInfo = ResolveSerializer(type.ElementType, false, profile, true); + if (elementInfo == null) + return null; + + var serializerType = InstantiateSerializerType(elementInfo.SerializerType, elementInfo.Mode, type, type.GenericArguments); + + var info = new SerializableTypeInfo(serializerType, true) { IsGeneratedSerializer = elementInfo.IsGeneratedSerializer }; + AddSerializableType(type, info, profile); + + if (elementInfo.IsGeneratedSerializer) + CollectSerializerDependencies(type, info); + + return info; + } + + /// + /// Constructs a concrete serializer type from an open generic serializer definition + /// by adding type arguments according to the . + /// + private static GenericInstanceType InstantiateSerializerType( + TypeReference openSerializerType, + DataSerializerGenericMode mode, + TypeReference dataType, + IEnumerable genericArguments) + { + var serializerType = new GenericInstanceType(openSerializerType); + + // Add the data type itself as first arg for Type/TypeAndGenericArguments modes + if (mode is DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments) + serializerType.GenericArguments.Add(dataType); + + // Add the data type's generic arguments for GenericArguments/TypeAndGenericArguments modes + if (mode is DataSerializerGenericMode.GenericArguments or DataSerializerGenericMode.TypeAndGenericArguments) + { + foreach (var arg in genericArguments) + serializerType.GenericArguments.Add(arg); + } + + return serializerType; + } + + private void CollectSerializerDependencies(TypeReference type, SerializableTypeInfo serializableTypeInfo, string profile = "Default", SerializerDescriptor? descriptor = null) + { + // Find the nearest serializable base type so the generated serializer can chain to it for (var baseType = type; (baseType = ResolveGenericsVisitor.Process(baseType, baseType.Resolve().BaseType)) != null;) { if (baseType.ContainsGenericParameter()) continue; // ResolveGenericsVisitor failed, the type it returned is not closed, we can't serialize it - var parentSerializableTypeInfo = GenerateSerializer(baseType, false, profile); - if (parentSerializableTypeInfo?.SerializerType != null) + var parentSerializableTypeInfo = ResolveSerializer(baseType, false, profile); + if (parentSerializableTypeInfo?.SerializerType is not null) { - serializableTypeInfo.ComplexSerializerProcessParentType = baseType; + if (descriptor is not null) + descriptor.SerializedParentType = baseType; break; } } - // Process members - foreach (var serializableItem in ComplexSerializerRegistry.GetSerializableItems(type, true)) + // Resolve serializers for all members, ignoring those without valid serializers + foreach (var serializableItem in SerializationHelpers.GetSerializableItems(type, true, ignoredMembers: IgnoredMembers)) { // Check that all closed types have a proper serializer if (serializableItem.Attributes.Any(x => x.AttributeType.FullName == "Stride.Core.DataMemberCustomSerializerAttribute") @@ -217,9 +238,9 @@ private void ProcessComplexSerializerMembers(TypeReference type, SerializableTyp try { - if (GenerateSerializer(serializableItem.Type, profile: profile) == null) + if (ResolveSerializer(serializableItem.Type, profile: profile) == null) { - ComplexSerializerRegistry.IgnoreMember(serializableItem.MemberInfo); + IgnoredMembers.Add(serializableItem.MemberInfo); if (!isInterface) { log.Write( @@ -232,15 +253,15 @@ private void ProcessComplexSerializerMembers(TypeReference type, SerializableTyp throw new InvalidOperationException($"Could not process serialization for member {serializableItem.MemberInfo}", e); } } + + // Cache final serializable items (after ignored members have been updated) + if (descriptor is not null) + descriptor.SerializableItems = SerializationHelpers.GetSerializableItems(type, true, ignoredMembers: IgnoredMembers).ToArray(); } /// - /// Finds the serializer information. + /// Finds the serializer information by inspecting the type's attributes and inheritance chain. /// - /// The type. - /// If set to true, when using , it will returns the generic version instead of actual type one. - /// - /// Not sure how to process this inherited serializer internal SerializableTypeInfo FindSerializerInfo(TypeReference type, bool generic) { if (type == null || type.FullName == typeof(object).FullName || type.FullName == typeof(ValueType).FullName || type.IsGenericParameter) @@ -248,199 +269,138 @@ internal SerializableTypeInfo FindSerializerInfo(TypeReference type, bool generi var resolvedType = type.Resolve(); - // Nested type - if (resolvedType.IsNested) - { - // Check public/private flags - if (!resolvedType.IsNestedPublic && !resolvedType.IsNestedAssembly) - return null; - } + // Nested type — must be publicly accessible + if (resolvedType.IsNested && !resolvedType.IsNestedPublic && !resolvedType.IsNestedAssembly) + return null; + // Enums if (resolvedType.IsEnum) { - // Enum - // Let's generate a EnumSerializer var enumSerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.EnumSerializer`1"); var serializerType = new GenericInstanceType(enumSerializerType); serializerType.GenericArguments.Add(type); - var serializableTypeInfo = new SerializableTypeInfo(serializerType, true, DataSerializerGenericMode.None); - AddSerializableType(type, serializableTypeInfo); - return serializableTypeInfo; + var info = new SerializableTypeInfo(serializerType, true, DataSerializerGenericMode.None); + AddSerializableType(type, info); + return info; } - // 1. Check if there is a Serializable attribute - // Note: Not anymore since we don't want all system types to have unknown complex serializers. - //if (((resolvedType.Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable) || resolvedType.CustomAttributes.Any(x => x.AttributeType.FullName == typeof(SerializableAttribute).FullName)) - //{ - // serializerInfo.Serializable = true; - // serializerInfo.ComplexSerializer = true; - // serializerInfo.ComplexSerializerName = SerializerTypeName(resolvedType); - // return serializerInfo; - //} - - // 2.1. Check if there is DataSerializerAttribute on this type (if yes, it is serializable, but not a "complex type") - var dataSerializerAttribute = - resolvedType.CustomAttributes.FirstOrDefault( - x => x.AttributeType.FullName == "Stride.Core.Serialization.DataSerializerAttribute"); + // [DataSerializer] attribute — explicit serializer assignment + var dataSerializerAttribute = resolvedType.CustomAttributes + .FirstOrDefault(x => x.AttributeType.FullName == "Stride.Core.Serialization.DataSerializerAttribute"); if (dataSerializerAttribute != null) - { - var modeField = dataSerializerAttribute.Fields.FirstOrDefault(x => x.Name == "Mode"); - var mode = (modeField.Name != null) ? (DataSerializerGenericMode)modeField.Argument.Value : DataSerializerGenericMode.None; - - var dataSerializerType = ((TypeReference)dataSerializerAttribute.ConstructorArguments[0].Value); + return ProcessDataSerializerAttribute(type, dataSerializerAttribute, generic); - // Reading from custom arguments doesn't have its ValueType properly set - dataSerializerType = dataSerializerType.FixupValueType(); + // [DataContract] attribute — collect generated serializer + var dataContractAttribute = resolvedType.CustomAttributes + .FirstOrDefault(x => x.AttributeType.FullName == "Stride.Core.DataContractAttribute"); + if (dataContractAttribute != null) + { + var inherited = dataContractAttribute.Properties + .Where(x => x.Name == "Inherited") + .Select(x => (bool)x.Argument.Value) + .FirstOrDefault(); - if (mode == DataSerializerGenericMode.Type || (mode == DataSerializerGenericMode.TypeAndGenericArguments && type is GenericInstanceType)) - { - var genericSerializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = true }; - AddSerializableType(type, genericSerializableTypeInfo); + var (info, descriptor) = CollectSerializer(type); + info.Inherited = inherited; + CollectSerializerDependencies(type, info, descriptor: descriptor); + return info; + } - var actualSerializerType = new GenericInstanceType(dataSerializerType); + // Check if parent type has Inherited attribute + return FindInheritedSerializerInfo(type, generic); + } - // Add Type as generic arguments - actualSerializerType.GenericArguments.Add(type); + private SerializableTypeInfo ProcessDataSerializerAttribute(TypeReference type, CustomAttribute attribute, bool generic) + { + var modeField = attribute.Fields.FirstOrDefault(x => x.Name == "Mode"); + var mode = (modeField.Name != null) ? (DataSerializerGenericMode)modeField.Argument.Value : DataSerializerGenericMode.None; + var dataSerializerType = ((TypeReference)attribute.ConstructorArguments[0].Value).FixupValueType(); - // If necessary, add generic arguments too - if (mode == DataSerializerGenericMode.TypeAndGenericArguments) - { - foreach (var genericArgument in ((GenericInstanceType)type).GenericArguments) - { - actualSerializerType.GenericArguments.Add(genericArgument); - } - } + if (mode is not (DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments) + || (mode == DataSerializerGenericMode.TypeAndGenericArguments && type is not GenericInstanceType)) + { + // Simple non-generic or GenericArguments mode + var info = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = false }; + AddSerializableType(type, info); + return info; + } - // Special case for GenericMode == DataSerializerGenericMode.Type: we store actual serializer instantiation in SerializerType (alongside the generic version in GenericSerializerType). - var serializableTypeInfo = new SerializableTypeInfo(actualSerializerType, true); - AddSerializableType(type, serializableTypeInfo); + // Type or TypeAndGenericArguments mode — register both generic and concrete versions + var genericInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = true }; + AddSerializableType(type, genericInfo); - if (!generic) - return serializableTypeInfo; + var genericArguments = type is GenericInstanceType git ? git.GenericArguments : []; + var actualSerializerType = InstantiateSerializerType(dataSerializerType, mode, type, genericArguments); - return genericSerializableTypeInfo; - } - else - { - var serializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = false }; - AddSerializableType(type, serializableTypeInfo); - return serializableTypeInfo; - } - } + var concreteInfo = new SerializableTypeInfo(actualSerializerType, true); + AddSerializableType(type, concreteInfo); - // 2.2. Check if SerializableExtendedAttribute is set on this class, or any of its base class with ApplyHierarchy - var serializableExtendedAttribute = - resolvedType.CustomAttributes.FirstOrDefault( - x => x.AttributeType.FullName == "Stride.Core.DataContractAttribute"); - if (dataSerializerAttribute == null && serializableExtendedAttribute != null) - { - // CHeck if ApplyHierarchy is active, otherwise it needs to be the exact type. - var inherited = serializableExtendedAttribute.Properties.Where(x => x.Name == "Inherited") - .Select(x => (bool)x.Argument.Value) - .FirstOrDefault(); + return generic ? genericInfo : concreteInfo; + } - var serializableTypeInfo = CreateComplexSerializer(type); - serializableTypeInfo.Inherited = inherited; + private SerializableTypeInfo FindInheritedSerializerInfo(TypeReference type, bool generic) + { + var parentType = ResolveGenericsVisitor.Process(type, type.Resolve().BaseType); + if (parentType == null) + return null; - // Process members - ProcessComplexSerializerMembers(type, serializableTypeInfo); + // Generate serializer for parent type + var parentInfo = ResolveSerializer(parentType.Resolve(), false, generic: true); + if (parentInfo?.Inherited != true) + return null; - return serializableTypeInfo; + // Parent has a generated serializer — collect one for this type too + if (parentInfo.IsGeneratedSerializer) + { + var (info, descriptor) = CollectSerializer(type); + info.Inherited = true; + CollectSerializerDependencies(type, info, descriptor: descriptor); + return info; } - // Check if parent type contains Inherited attribute - var parentType = ResolveGenericsVisitor.Process(type, type.Resolve().BaseType); - if (parentType != null) + // Parent has a Type/TypeAndGenericArguments mode serializer — inherit it + if (parentInfo.Mode is DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments) { - // Generate serializer for parent type - var parentSerializableInfoType = GenerateSerializer(parentType.Resolve(), false, generic: true); + // Register generic version + var genericInfo = new SerializableTypeInfo(parentInfo.SerializerType, true, parentInfo.Mode); + AddSerializableType(type, genericInfo); - // If Inherited flag is on, we also generate a serializer for this type - if (parentSerializableInfoType?.Inherited == true) + if (!type.HasGenericParameters) { - if (parentSerializableInfoType.ComplexSerializer) - { - var serializableTypeInfo = CreateComplexSerializer(type); - serializableTypeInfo.Inherited = true; - - // Process members - ProcessComplexSerializerMembers(type, serializableTypeInfo); - - return serializableTypeInfo; - } - else if (parentSerializableInfoType.Mode == DataSerializerGenericMode.Type || parentSerializableInfoType.Mode == DataSerializerGenericMode.TypeAndGenericArguments) - { - // Register generic version - var genericSerializableTypeInfo = new SerializableTypeInfo(parentSerializableInfoType.SerializerType, true, parentSerializableInfoType.Mode); - AddSerializableType(type, genericSerializableTypeInfo); + var genericArguments = parentType is GenericInstanceType git ? git.GenericArguments : []; + var actualSerializerType = InstantiateSerializerType(parentInfo.SerializerType, parentInfo.Mode, type, genericArguments); - if (!type.HasGenericParameters) - { - var actualSerializerType = new GenericInstanceType(parentSerializableInfoType.SerializerType); - - // Add Type as generic arguments - actualSerializerType.GenericArguments.Add(type); - - // If necessary, add generic arguments too - if (parentSerializableInfoType.Mode == DataSerializerGenericMode.TypeAndGenericArguments) - { - foreach (var genericArgument in ((GenericInstanceType)parentType).GenericArguments) - { - actualSerializerType.GenericArguments.Add(genericArgument); - } - } - - // Register actual type - var serializableTypeInfo = new SerializableTypeInfo(actualSerializerType, true); - AddSerializableType(type, serializableTypeInfo); - - if (!generic) - return serializableTypeInfo; - } + var concreteInfo = new SerializableTypeInfo(actualSerializerType, true); + AddSerializableType(type, concreteInfo); - return genericSerializableTypeInfo; - } - else - { - throw new InvalidOperationException("Not sure how to process this inherited serializer"); - } + if (!generic) + return concreteInfo; } + + return genericInfo; } - return null; + throw new InvalidOperationException("Not sure how to process this inherited serializer"); } - private SerializableTypeInfo CreateComplexSerializer(TypeReference type) + private (SerializableTypeInfo Info, SerializerDescriptor? Descriptor) CollectSerializer(TypeReference type) { var isLocal = type.Resolve().Module.Assembly == Assembly; - // Create a fake TypeReference (even though it doesn't really exist yet, but at least ConvertCSharp to get its name will work). - TypeReference dataSerializerType; - var className = ComplexSerializerRegistry.SerializerTypeName(type, false, true); + // Create a forward TypeReference for the serializer (the actual TypeDefinition is created later during code generation). + var className = SerializationHelpers.SerializerTypeName(type, false, true); if (type.HasGenericParameters) className += "`" + type.GenericParameters.Count; - if (isLocal && type is TypeDefinition) - { - dataSerializerType = new TypeDefinition("Stride.Core.DataSerializers", className, - TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Sealed | - TypeAttributes.BeforeFieldInit | - (type.HasGenericParameters ? TypeAttributes.Public : TypeAttributes.NotPublic)); - // TODO: Only if not using Roslyn - Assembly.MainModule.Types.Add((TypeDefinition)dataSerializerType); - } - else - { - dataSerializerType = new TypeReference("Stride.Core.DataSerializers", className, type.Module, type.Scope); - } + var dataSerializerType = new TypeReference("Stride.Core.DataSerializers", className, type.Module, isLocal ? Assembly.MainModule : type.Scope); var mode = DataSerializerGenericMode.None; if (type.HasGenericParameters) { mode = DataSerializerGenericMode.GenericArguments; - // Clone generic parameters + // Clone generic parameters onto the forward reference foreach (var genericParameter in type.GenericParameters) { var newGenericParameter = new GenericParameter(genericParameter.Name, dataSerializerType) @@ -448,7 +408,6 @@ private SerializableTypeInfo CreateComplexSerializer(TypeReference type) Attributes = genericParameter.Attributes }; - // Clone type constraints (others will be in Attributes) foreach (var constraint in genericParameter.Constraints) newGenericParameter.Constraints.Add(constraint); @@ -456,31 +415,32 @@ private SerializableTypeInfo CreateComplexSerializer(TypeReference type) } } - if (dataSerializerType is TypeDefinition dataSerializerTypeDefinition) - { - // Setup base class - var resolvedType = type.Resolve(); - var useClassDataSerializer = resolvedType.IsClass && !resolvedType.IsValueType && !resolvedType.IsAbstract && !resolvedType.IsInterface && resolvedType.GetEmptyConstructor() != null; - var classDataSerializerType = Assembly.GetStrideCoreModule().GetType(useClassDataSerializer ? "Stride.Core.Serialization.ClassDataSerializer`1" : "Stride.Core.Serialization.DataSerializer`1"); - var parentType = Assembly.MainModule.ImportReference(classDataSerializerType).MakeGenericType(type.MakeGenericType(dataSerializerType.GenericParameters.ToArray())); - //parentType = ResolveGenericsVisitor.Process(serializerType, type.BaseType); - dataSerializerTypeDefinition.BaseType = parentType; - } - var serializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { - Local = type.Resolve().Module.Assembly == Assembly + Local = isLocal }; AddSerializableType(type, serializableTypeInfo); + SerializerDescriptor? descriptor = null; if (isLocal && type is TypeDefinition definition) { - ComplexTypes.Add(definition, serializableTypeInfo); + var resolvedType = type.Resolve(); + var useClassDataSerializer = resolvedType.IsClass && !resolvedType.IsValueType && !resolvedType.IsAbstract && !resolvedType.IsInterface && resolvedType.GetEmptyConstructor() != null; + + descriptor = new SerializerDescriptor + { + DataType = definition, + SerializerClassName = className, + IsPublic = type.HasGenericParameters, + UseClassDataSerializer = useClassDataSerializer, + SerializableTypeInfo = serializableTypeInfo, + }; + PendingSerializers.Add(descriptor); } - serializableTypeInfo.ComplexSerializer = true; + serializableTypeInfo.IsGeneratedSerializer = true; - return serializableTypeInfo; + return (serializableTypeInfo, descriptor); } public void AddSerializableType(TypeReference dataType, SerializableTypeInfo serializableTypeInfo, string profile = "Default") @@ -498,8 +458,7 @@ public void AddSerializableType(TypeReference dataType, SerializableTypeInfo ser if (profileInfo.TryGetSerializableTypeInfo(dataType, serializableTypeInfo.Mode != DataSerializerGenericMode.None, out var currentValue)) { // TODO: Doesn't work in some generic case - if (//currentValue.SerializerType.ConvertCSharp() != serializableTypeInfo.SerializerType.ConvertCSharp() || - currentValue.Mode != serializableTypeInfo.Mode) + if (currentValue.Mode != serializableTypeInfo.Mode) throw new InvalidOperationException(string.Format("Incompatible serializer found for same type in different assemblies for {0}", dataType.ConvertCSharp())); return; } @@ -516,37 +475,46 @@ public void AddSerializableType(TypeReference dataType, SerializableTypeInfo ser profileInfo.AddSerializableTypeInfo(dataType, serializableTypeInfo); - // Scan and add dependencies (stored in EnumerateGenericInstantiations() functions). - if (serializableTypeInfo.Local && serializableTypeInfo.SerializerType != null) + // Scan and add dependencies (stored in EnumerateGenericInstantiations() methods) + ScanSerializerDependencies(dataType, serializableTypeInfo); + } + + /// + /// Scans a serializer type's EnumerateGenericInstantiations method for ldtoken instructions + /// to discover dependent types that also need serializers. + /// + private void ScanSerializerDependencies(TypeReference dataType, SerializableTypeInfo serializableTypeInfo) + { + if (!serializableTypeInfo.Local || serializableTypeInfo.SerializerType == null) + return; + + var resolvedSerializerType = serializableTypeInfo.SerializerType.Resolve(); + if (resolvedSerializerType == null) + return; + + var enumerateMethod = resolvedSerializerType.Methods.FirstOrDefault(x => x.Name == "EnumerateGenericInstantiations"); + if (enumerateMethod == null) + return; + + // Detect all ldtoken (attributes would have been better, but unfortunately C# doesn't allow generics in attributes) + foreach (var inst in enumerateMethod.Body.Instructions) { - var resolvedSerializerType = serializableTypeInfo.SerializerType.Resolve(); - if (resolvedSerializerType != null) + if (inst.OpCode.Code != Code.Ldtoken) + continue; + + var type = (TypeReference)inst.Operand; + + // Try to "close" generics type with serializer type as a context + var dependentType = ResolveGenericsVisitor.Process(serializableTypeInfo.SerializerType, type); + if (dependentType.ContainsGenericParameter()) + continue; + + // Import type so that it becomes local to the assembly + // (otherwise SerializableTypeInfo.Local will be false and it won't be instantiated) + var importedType = Assembly.MainModule.ImportReference(dependentType); + if (ResolveSerializer(importedType) == null) { - var enumerateGenericInstantiationsMethod = resolvedSerializerType.Methods.FirstOrDefault(x => x.Name == "EnumerateGenericInstantiations"); - if (enumerateGenericInstantiationsMethod != null) - { - // Detect all ldtoken (attributes would have been better, but unfortunately C# doesn't allow generics in attributes) - foreach (var inst in enumerateGenericInstantiationsMethod.Body.Instructions) - { - if (inst.OpCode.Code == Code.Ldtoken) - { - var type = (TypeReference)inst.Operand; - - // Try to "close" generics type with serializer type as a context - var dependentType = ResolveGenericsVisitor.Process(serializableTypeInfo.SerializerType, type); - if (!dependentType.ContainsGenericParameter()) - { - // Import type so that it becomes local to the assembly - // (otherwise SerializableTypeInfo.Local will be false and it won't be instantiated) - var importedType = Assembly.MainModule.ImportReference(dependentType); - if (GenerateSerializer(importedType) == null) - { - throw new InvalidOperationException(string.Format("Could not find serializer for generic dependent type {0} when processing {1}", dependentType, dataType)); - } - } - } - } - } + throw new InvalidOperationException(string.Format("Could not find serializer for generic dependent type {0} when processing {1}", dependentType, dataType)); } } } @@ -578,14 +546,9 @@ internal class SerializableTypeInfo public bool Inherited; /// - /// True if it's a complex serializer. + /// True if the serializer is auto-generated (for [DataContract] types). /// - public bool ComplexSerializer; - - /// - /// Not null if it's a complex serializer and its base class should be serialized too. - /// - public TypeReference? ComplexSerializerProcessParentType; + public bool IsGeneratedSerializer; public SerializableTypeInfo(TypeReference serializerType, bool local, DataSerializerGenericMode mode = DataSerializerGenericMode.None) { @@ -595,6 +558,22 @@ public SerializableTypeInfo(TypeReference serializerType, bool local, DataSerial } } + private static TypeDefinition? GetSerializerFactoryType(AssemblyDefinition referencedAssembly) + { + var assemblySerializerFactoryAttribute = + referencedAssembly.CustomAttributes.FirstOrDefault( + x => x.AttributeType.FullName == "Stride.Core.Serialization.AssemblySerializerFactoryAttribute"); + + if (assemblySerializerFactoryAttribute == null) + return null; + + var typeReference = (TypeReference)assemblySerializerFactoryAttribute.Fields.Single(x => x.Name == "Type").Argument.Value; + if (typeReference == null) + return null; + + return typeReference.Resolve(); + } + public class ProfileInfo { /// diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/PropertyKeySerializerProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/PropertyKeySerializerProcessor.cs index 8ce94b2eeb..d66ce1040f 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/Serializers/PropertyKeySerializerProcessor.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/PropertyKeySerializerProcessor.cs @@ -18,7 +18,7 @@ public void ProcessSerializers(CecilSerializerContext context) if (!member.IsStatic || member.IsPrivate) continue; - if (ComplexSerializerRegistry.IsMemberIgnored(member.CustomAttributes, ComplexTypeSerializerFlags.SerializePublicFields, DataMemberMode.Default)) + if (SerializationHelpers.IsMemberIgnored(member.CustomAttributes, ComplexTypeSerializerFlags.SerializePublicFields, DataMemberMode.Default)) continue; if (member.FieldType.Name == "PropertyKey`1" @@ -27,12 +27,12 @@ public void ProcessSerializers(CecilSerializerContext context) || member.FieldType.Name == "ObjectParameterKey`1" || member.FieldType.Name == "PermutationParameterKey`1") { - context.GenerateSerializer(member.FieldType); + context.ResolveSerializer(member.FieldType); var genericType = (GenericInstanceType)member.FieldType; // Also generate serializer for embedded type - context.GenerateSerializer(genericType.GenericArguments[0]); + context.ResolveSerializer(genericType.GenericArguments[0]); } } } diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/ReferencedAssemblySerializerProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/ReferencedAssemblySerializerProcessor.cs index ca5fd06cb2..a270516bef 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/Serializers/ReferencedAssemblySerializerProcessor.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/ReferencedAssemblySerializerProcessor.cs @@ -25,7 +25,7 @@ private void ProcessDataSerializerGlobalAttributes(CecilSerializerContext contex if (!processedAssemblies.Add(assembly)) return; - // TODO: Add a flag for ComplexSerializer and transmit it properly (it needs different kind of analysis) + // TODO: Add a flag for generated serializers and transmit it properly (it needs different kind of analysis) // Let's recurse over referenced assemblies foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences.ToArray()) @@ -80,8 +80,8 @@ private void ProcessDataSerializerGlobalAttributes(CecilSerializerContext contex if (dataSerializerType == null) { - // TODO: We should avoid calling GenerateSerializer now just to have the dataSerializerType (we should do so only in a second step) - serializableTypeInfo = context.GenerateSerializer(dataType, profile: profile); + // TODO: We should avoid calling ResolveSerializer now just to have the dataSerializerType (we should do so only in a second step) + serializableTypeInfo = context.ResolveSerializer(dataType, profile: profile); if (serializableTypeInfo == null) throw new InvalidOperationException(string.Format("Can't find serializer for type {0}", dataType)); serializableTypeInfo.Local = local; @@ -91,7 +91,7 @@ private void ProcessDataSerializerGlobalAttributes(CecilSerializerContext contex else { // Add it to list of serializable types - serializableTypeInfo = new CecilSerializerContext.SerializableTypeInfo(dataSerializerType, local, mode) { ExistingLocal = local, Inherited = inherited, ComplexSerializer = complexSerializer }; + serializableTypeInfo = new CecilSerializerContext.SerializableTypeInfo(dataSerializerType, local, mode) { ExistingLocal = local, Inherited = inherited, IsGeneratedSerializer = complexSerializer }; context.AddSerializableType(dataType, serializableTypeInfo, profile); } } diff --git a/sources/core/Stride.Core.AssemblyProcessor/Serializers/SerializerDescriptor.cs b/sources/core/Stride.Core.AssemblyProcessor/Serializers/SerializerDescriptor.cs new file mode 100644 index 0000000000..041a389bfe --- /dev/null +++ b/sources/core/Stride.Core.AssemblyProcessor/Serializers/SerializerDescriptor.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Mono.Cecil; + +namespace Stride.Core.AssemblyProcessor.Serializers; + +/// +/// Describes a serializer to be generated. Collected during the discovery phase +/// and consumed by the code generation phase to create the actual . +/// +internal class SerializerDescriptor +{ + /// + /// The data type being serialized. + /// + public required TypeDefinition DataType { get; init; } + + /// + /// The class name for the generated serializer (e.g. "MyTypeSerializer"). + /// + public required string SerializerClassName { get; init; } + + /// + /// Whether the serializer type should be public (true for generic types). + /// + public required bool IsPublic { get; init; } + + /// + /// Whether to use ClassDataSerializer{T} (true) or DataSerializer{T} (false) as the base class. + /// + public required bool UseClassDataSerializer { get; init; } + + /// + /// The serializable type info created during collection, which holds the serializer type reference + /// and metadata used by the registration/factory phase. + /// + public required CecilSerializerContext.SerializableTypeInfo SerializableTypeInfo { get; init; } + + /// + /// Base type whose serializer should be called first (for inheritance chains). + /// Set during . + /// + public TypeReference? SerializedParentType { get; set; } + + /// + /// The serializable fields/properties of . + /// Computed once during collection and reused during code generation. + /// + public SerializationHelpers.SerializableItem[] SerializableItems { get; set; } +} diff --git a/sources/core/Stride.Core.AssemblyProcessor/Stride.Core.AssemblyProcessor.csproj b/sources/core/Stride.Core.AssemblyProcessor/Stride.Core.AssemblyProcessor.csproj index 39be9ff147..0cfa7d9bf1 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/Stride.Core.AssemblyProcessor.csproj +++ b/sources/core/Stride.Core.AssemblyProcessor/Stride.Core.AssemblyProcessor.csproj @@ -72,21 +72,6 @@ Core\ObjectIdBuilder.cs - - True - True - AssemblyScanCodeGenerator.tt - - - ComplexSerializerCodeGenerator.tt - True - True - - - ComplexClassSerializerGenerator.tt - True - True - diff --git a/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.IL.cs b/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.IL.cs index 650ecc9b68..5fb8d780c6 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.IL.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.IL.cs @@ -14,73 +14,45 @@ private void ProcessStrideEngineAssembly(CecilSerializerContext context) var assembly = context.Assembly; // Check "#if IL" directly in the source to easily see what is generated - GenerateUpdateEngineHelperCode(assembly); GenerateUpdatableFieldCode(assembly); new UpdatablePropertyCodeGenerator(assembly).GenerateUpdatablePropertyCode(); new UpdatableListCodeGenerator(assembly).GenerateUpdatablePropertyCode(); } - private static void GenerateUpdateEngineHelperCode(AssemblyDefinition assembly) - { - var updateEngineHelperType = assembly.MainModule.GetType("Stride.Updater.UpdateEngineHelper"); - - // UpdateEngineHelper.ObjectToPtr - var objectToPtr = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "ObjectToPtr")); - objectToPtr.Emit(OpCodes.Ldarg, objectToPtr.Body.Method.Parameters[0]); - objectToPtr.Emit(OpCodes.Conv_I); - objectToPtr.Emit(OpCodes.Ret); - - // UpdateEngineHelper.PtrToObject - // Simpler "ldarg.0 + ret" doesn't work with Xamarin: https://bugzilla.xamarin.com/show_bug.cgi?id=40608 - var ptrToObject = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "PtrToObject")); - ptrToObject.Body.Variables.Add(new VariableDefinition(assembly.MainModule.TypeSystem.Object)); - ptrToObject.Emit(OpCodes.Ldloca_S, (byte)0); - ptrToObject.Emit(OpCodes.Ldarg, ptrToObject.Body.Method.Parameters[0]); - - // Somehow Xamarin forces us to do a roundtrip to an object - ptrToObject.Emit(OpCodes.Stind_I); - ptrToObject.Emit(OpCodes.Ldloc_0); - - ptrToObject.Emit(OpCodes.Ret); - - // UpdateEngineHelper.Unbox - var unbox = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "Unbox")); - unbox.Emit(OpCodes.Ldarg, unbox.Body.Method.Parameters[0]); - unbox.Emit(OpCodes.Unbox, unbox.Body.Method.GenericParameters[0]); - unbox.Emit(OpCodes.Ret); - } - + /// + /// Rewrites UpdatableField methods with raw pointer-based field access. + /// private static void GenerateUpdatableFieldCode(AssemblyDefinition assembly) { var updatableFieldType = assembly.MainModule.GetType("Stride.Updater.UpdatableField"); var updatableFieldGenericType = assembly.MainModule.GetType("Stride.Updater.UpdatableField`1"); - // UpdatableField.GetObject - var getObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "GetObject")); - getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]); - getObject.Emit(OpCodes.Ldind_Ref); - getObject.Emit(OpCodes.Ret); - - // UpdatableField.SetObject - var setObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "SetObject")); - setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]); - setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]); - setObject.Emit(OpCodes.Stind_Ref); - setObject.Emit(OpCodes.Ret); - - // UpdatableField.SetStruct - var setStruct = RewriteBody(updatableFieldGenericType.Methods.First(x => x.Name == "SetStruct")); - setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0]); - setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1]); - setStruct.Emit(OpCodes.Unbox, updatableFieldGenericType.GenericParameters[0]); - setStruct.Emit(OpCodes.Cpobj, updatableFieldGenericType.GenericParameters[0]); - setStruct.Emit(OpCodes.Ret); + // Generates: object GetObject(IntPtr ptr) => *(object*)ptr; + var getObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "GetObject"), assembly); + getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]) + .Emit(OpCodes.Ldind_Ref) + .Emit(OpCodes.Ret); + + // Generates: void SetObject(IntPtr ptr, object value) { *(object*)ptr = value; } + var setObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "SetObject"), assembly); + setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]) + .Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]) + .Emit(OpCodes.Stind_Ref) + .Emit(OpCodes.Ret); + + // Generates: void SetStruct(IntPtr ptr, object value) { *(T*)ptr = (T)value; } + var setStruct = RewriteBody(updatableFieldGenericType.Methods.First(x => x.Name == "SetStruct"), assembly); + setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0]) + .Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1]) + .Emit(OpCodes.Unbox, updatableFieldGenericType.GenericParameters[0]) + .Emit(OpCodes.Cpobj, updatableFieldGenericType.GenericParameters[0]) + .Emit(OpCodes.Ret); } - private static ILProcessor RewriteBody(MethodDefinition method) + private static ILBuilder RewriteBody(MethodDefinition method, AssemblyDefinition assembly) { method.Body = new MethodBody(method); - return method.Body.GetILProcessor(); + return new ILBuilder(method.Body, assembly.MainModule); } /// @@ -96,50 +68,49 @@ public UpdatablePropertyBaseCodeGenerator(AssemblyDefinition assembly) this.assembly = assembly; } - public abstract void EmitGetCode(ILProcessor il, TypeReference type); + public abstract void EmitGetCode(ILBuilder il, TypeReference type); - public virtual void EmitSetCodeBeforeValue(ILProcessor il, TypeReference type) + public virtual void EmitSetCodeBeforeValue(ILBuilder il, TypeReference type) { } - public abstract void EmitSetCodeAfterValue(ILProcessor il, TypeReference type); + public abstract void EmitSetCodeAfterValue(ILBuilder il, TypeReference type); public virtual void GenerateUpdatablePropertyCode() { - // UpdatableProperty.GetStructAndUnbox - var getStructAndUnbox = RewriteBody(declaringType.Methods.First(x => x.Name == "GetStructAndUnbox")); - getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[1]); - //getStructAndUnbox.Emit(OpCodes.Call, assembly.MainModule.ImportReference(unbox).MakeGenericMethod(declaringType.GenericParameters[0])); - getStructAndUnbox.Emit(OpCodes.Unbox, declaringType.GenericParameters[0]); - getStructAndUnbox.Emit(OpCodes.Dup); - getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[0]); + // Generates: void GetStructAndUnbox(IntPtr ptr, object obj) { *(T*)Unbox(obj) = Get(ptr); } + var getStructAndUnbox = RewriteBody(declaringType.Methods.First(x => x.Name == "GetStructAndUnbox"), assembly); + getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[1]) + .Emit(OpCodes.Unbox, declaringType.GenericParameters[0]) + .Emit(OpCodes.Dup) + .Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[0]); EmitGetCode(getStructAndUnbox, declaringType.GenericParameters[0]); - getStructAndUnbox.Emit(OpCodes.Stobj, declaringType.GenericParameters[0]); - getStructAndUnbox.Emit(OpCodes.Ret); + getStructAndUnbox.Emit(OpCodes.Stobj, declaringType.GenericParameters[0]) + .Emit(OpCodes.Ret); - // UpdatableProperty.GetBlittable - var getBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "GetBlittable")); - getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[1]); - getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[0]); + // Generates: void GetBlittable(IntPtr ptr, IntPtr dest) { *(T*)dest = Get(ptr); } + var getBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "GetBlittable"), assembly); + getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[1]) + .Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[0]); EmitGetCode(getBlittable, declaringType.GenericParameters[0]); - getBlittable.Emit(OpCodes.Stobj, declaringType.GenericParameters[0]); - getBlittable.Emit(OpCodes.Ret); + getBlittable.Emit(OpCodes.Stobj, declaringType.GenericParameters[0]) + .Emit(OpCodes.Ret); - // UpdatableProperty.SetStruct - var setStruct = RewriteBody(declaringType.Methods.First(x => x.Name == "SetStruct")); + // Generates: void SetStruct(IntPtr ptr, object value) { Set(ptr, (T)value); } + var setStruct = RewriteBody(declaringType.Methods.First(x => x.Name == "SetStruct"), assembly); setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0]); EmitSetCodeBeforeValue(setStruct, declaringType.GenericParameters[0]); - setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1]); - setStruct.Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]); + setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1]) + .Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]); EmitSetCodeAfterValue(setStruct, declaringType.GenericParameters[0]); setStruct.Emit(OpCodes.Ret); - // UpdatableProperty.SetBlittable - var setBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "SetBlittable")); + // Generates: void SetBlittable(IntPtr ptr, IntPtr src) { Set(ptr, *(T*)src); } + var setBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "SetBlittable"), assembly); setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[0]); EmitSetCodeBeforeValue(setBlittable, declaringType.GenericParameters[0]); - setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[1]); - setBlittable.Emit(OpCodes.Ldobj, declaringType.GenericParameters[0]); + setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[1]) + .Emit(OpCodes.Ldobj, declaringType.GenericParameters[0]); EmitSetCodeAfterValue(setBlittable, declaringType.GenericParameters[0]); setBlittable.Emit(OpCodes.Ret); } @@ -167,16 +138,14 @@ public UpdatablePropertyCodeGenerator(AssemblyDefinition assembly) : base(assemb public override void GenerateUpdatablePropertyCode() { - // For UpdatableProperty, GetObject/SetObject are declared on another type - - // UpdatableProperty.GetObject - var getObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "GetObject")); + // Generates: object GetObject(IntPtr ptr) => Getter(ptr); // via calli + var getObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "GetObject"), assembly); getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]); EmitGetCode(getObject, assembly.MainModule.TypeSystem.Object); getObject.Emit(OpCodes.Ret); - // UpdatableProperty.SetObject - var setObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "SetObject")); + // Generates: void SetObject(IntPtr ptr, object value) { Setter(ptr, value); } // via calli + var setObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "SetObject"), assembly); setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]); EmitSetCodeBeforeValue(setObject, assembly.MainModule.TypeSystem.Object); setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]); @@ -186,48 +155,60 @@ public override void GenerateUpdatablePropertyCode() base.GenerateUpdatablePropertyCode(); } - public override void EmitGetCode(ILProcessor il, TypeReference type) + /// + /// Emits getter call via function pointer (calli). Branches on VirtualDispatchGetter: + /// + /// if (VirtualDispatchGetter) + /// return calli(Getter, (IntPtr)obj); // static dispatcher that does ldvirtftn internally + /// else + /// return calli(Getter, obj); // direct instance call via fn pointer + /// + /// + public override void EmitGetCode(ILBuilder il, TypeReference type) { var calliInstance = Instruction.Create(OpCodes.Calli, new CallSite(type) { HasThis = true }); - // Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expect a IntPtr rather than object + // Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expects IntPtr // (another option would be to use "castclass object" after pushing the IntPtr on the stack) var calliVirtualDispatch = Instruction.Create(OpCodes.Calli, new CallSite(type) { HasThis = false, Parameters = { new ParameterDefinition(assembly.MainModule.TypeSystem.IntPtr) } }); - var postCalli = Instruction.Create(OpCodes.Nop); - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, updatablePropertyGetter); - il.Emit(OpCodes.Ldarg_0); - // For normal calls, we use ldftn and an instance calls - // For virtual and interface calls, we generate a dispatch function that calls ldvirtftn on the actual object, then call the method on the object - // this dispatcher method is static, so the calli has a different signature - // Note: we could later optimize the bool check by having two variant of Get/SetObject - // and two different implementations of both UpdatableProperty and UpdatablePropertyObject - // (not sure if worth it) - il.Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchGetter); - il.Emit(OpCodes.Brfalse, calliInstance); - il.Append(calliVirtualDispatch); - il.Emit(OpCodes.Br, postCalli); - il.Append(calliInstance); - il.Append(postCalli); + var postCalli = ILBuilder.DefineLabel(); + + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldfld, updatablePropertyGetter) + .Emit(OpCodes.Ldarg_0) + // For normal calls, we use ldftn and an instance calli. + // For virtual/interface calls, we generate a static dispatch function (via ) + // that calls ldvirtftn on the actual object — the calli has a different signature since the dispatcher is static. + /// Note: we could later optimize the bool check by having two variants of Get/SetObject + // and two different implementations of both UpdatableProperty<T> and UpdatablePropertyObject<T> + // (not sure if worth it). + .Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchGetter) + .Emit(OpCodes.Brfalse, calliInstance) + .Append(calliVirtualDispatch) + .Emit(OpCodes.Br, postCalli) + .Append(calliInstance) + .MarkLabel(postCalli); } - public override void EmitSetCodeAfterValue(ILProcessor il, TypeReference type) + /// + /// Emits setter call via function pointer (calli), same branching pattern as getter. + /// + public override void EmitSetCodeAfterValue(ILBuilder il, TypeReference type) { var calliInstance = Instruction.Create(OpCodes.Calli, new CallSite(assembly.MainModule.TypeSystem.Void) { HasThis = true, Parameters = { new ParameterDefinition(type) } }); - // Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expect a IntPtr rather than object + // Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expects IntPtr // (another option would be to use "castclass object" after pushing the IntPtr on the stack) var calliVirtualDispatch = Instruction.Create(OpCodes.Calli, new CallSite(assembly.MainModule.TypeSystem.Void) { HasThis = false, Parameters = { new ParameterDefinition(assembly.MainModule.TypeSystem.IntPtr), new ParameterDefinition(type) } }); - var postCalli = Instruction.Create(OpCodes.Nop); - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, updatablePropertySetter); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchSetter); - il.Emit(OpCodes.Brfalse, calliInstance); - il.Append(calliVirtualDispatch); - il.Emit(OpCodes.Br, postCalli); - il.Append(calliInstance); - il.Append(postCalli); + var postCalli = ILBuilder.DefineLabel(); + + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldfld, updatablePropertySetter) + .Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchSetter) + .Emit(OpCodes.Brfalse, calliInstance) + .Append(calliVirtualDispatch) + .Emit(OpCodes.Br, postCalli) + .Append(calliInstance) + .MarkLabel(postCalli); } } @@ -239,21 +220,19 @@ public UpdatableCustomPropertyCodeGenerator(AssemblyDefinition assembly) : base( public override void GenerateUpdatablePropertyCode() { - // For UpdatableCustomAccessor, GetObject/SetObject are declared in the generic type - - // UpdatableProperty.GetObject - var getObject = RewriteBody(declaringType.Methods.First(x => x.Name == "GetObject")); + // Generates: object GetObject(IntPtr ptr) => (object)Get(ptr); + var getObject = RewriteBody(declaringType.Methods.First(x => x.Name == "GetObject"), assembly); getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]); EmitGetCode(getObject, declaringType.GenericParameters[0]); - getObject.Emit(OpCodes.Box, declaringType.GenericParameters[0]); // Required for Windows 10 AOT - getObject.Emit(OpCodes.Ret); + getObject.Emit(OpCodes.Box, declaringType.GenericParameters[0]) // Required for Windows 10 AOT + .Emit(OpCodes.Ret); - // UpdatableProperty.SetObject - var setObject = RewriteBody(declaringType.Methods.First(x => x.Name == "SetObject")); + // Generates: void SetObject(IntPtr ptr, object value) { Set(ptr, (T)value); } + var setObject = RewriteBody(declaringType.Methods.First(x => x.Name == "SetObject"), assembly); setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]); EmitSetCodeBeforeValue(setObject, declaringType.GenericParameters[0]); - setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]); - setObject.Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]); // Required for Windows 10 AOT + setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]) + .Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]); // Required for Windows 10 AOT EmitSetCodeAfterValue(setObject, declaringType.GenericParameters[0]); setObject.Emit(OpCodes.Ret); @@ -261,6 +240,11 @@ public override void GenerateUpdatablePropertyCode() } } + /// + /// Generates list accessor methods that delegate to IList<T>[Index]. + /// Get: return ((IList<T>)obj)[this.Index]; + /// Set: ((IList<T>)obj)[this.Index] = value; + /// class UpdatableListCodeGenerator : UpdatableCustomPropertyCodeGenerator { private readonly FieldDefinition indexField; @@ -282,20 +266,20 @@ public UpdatableListCodeGenerator(AssemblyDefinition assembly) : base(assembly) ilistSetItem = assembly.MainModule.ImportReference(ilistItem.SetMethod).MakeGeneric(declaringType.GenericParameters[0]); } - public override void EmitGetCode(ILProcessor il, TypeReference type) + public override void EmitGetCode(ILBuilder il, TypeReference type) { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, indexField); - il.Emit(OpCodes.Callvirt, ilistGetItem); + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldfld, indexField) + .Emit(OpCodes.Callvirt, ilistGetItem); } - public override void EmitSetCodeBeforeValue(ILProcessor il, TypeReference type) + public override void EmitSetCodeBeforeValue(ILBuilder il, TypeReference type) { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, indexField); + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldfld, indexField); } - public override void EmitSetCodeAfterValue(ILProcessor il, TypeReference type) + public override void EmitSetCodeAfterValue(ILBuilder il, TypeReference type) { il.Emit(OpCodes.Callvirt, ilistSetItem); } diff --git a/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.cs b/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.cs index 23aff266e5..45bdf8a800 100644 --- a/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.cs +++ b/sources/core/Stride.Core.AssemblyProcessor/UpdateEngineProcessor.cs @@ -41,22 +41,37 @@ private MethodDefinition CreateUpdateMethod(AssemblyDefinition assembly) // Obtain the static constructor of and the return instruction var moduleConstructor = assembly.OpenModuleConstructor(out var returnInstruction); - // Get the IL processor of the module constructor - var il = moduleConstructor.Body.GetILProcessor(); + // Get the IL processor of the module constructor (used only for InsertBefore) + var moduleCtorIL = moduleConstructor.Body.GetILProcessor(); // Create the call to Initialize method var initializeMethodReference = assembly.MainModule.ImportReference(mainPrepareMethod); - var callInitializeInstruction = il.Create(OpCodes.Call, initializeMethodReference); - il.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction); + var callInitializeInstruction = moduleCtorIL.Create(OpCodes.Call, initializeMethodReference); + moduleCtorIL.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction); return mainPrepareMethod; } + /// + /// Creates a static dispatcher method for virtual/interface property access via ldvirtftn/calli. + /// + /// + /// Generates code equivalent to: + /// + /// static TReturn Dispatcher_get_Property(object @this /*, params... */) + /// { + /// return @this./* virtual call via ldvirtftn+calli */get_Property(/* params... */); + /// } + /// + /// This is needed because ldftn of a virtual method gives the base slot, not the override. + /// The dispatcher uses ldvirtftn to resolve the actual vtable entry at runtime. + /// private MethodReference CreateDispatcher(AssemblyDefinition assembly, MethodReference method) { var updateEngineType = GetOrCreateUpdateType(assembly, true); + var module = assembly.MainModule; - var dispatcherMethod = new MethodDefinition($"Dispatcher_{method.Name}", MethodAttributes.HideBySig | MethodAttributes.Assembly | MethodAttributes.Static, assembly.MainModule.ImportReference(method.ReturnType)); + var dispatcherMethod = new MethodDefinition($"Dispatcher_{method.Name}", MethodAttributes.HideBySig | MethodAttributes.Assembly | MethodAttributes.Static, module.ImportReference(method.ReturnType)); updateEngineType.Methods.Add(dispatcherMethod); dispatcherMethod.Parameters.Add(new ParameterDefinition("this", ParameterAttributes.None, method.DeclaringType)); @@ -65,19 +80,22 @@ private MethodReference CreateDispatcher(AssemblyDefinition assembly, MethodRefe dispatcherMethod.Parameters.Add(new ParameterDefinition(param.Name, param.Attributes, param.ParameterType)); } - var il = dispatcherMethod.Body.GetILProcessor(); + // Emit: load all args, then resolve virtual method and call via calli + var il = new ILBuilder(dispatcherMethod.Body, module); il.Emit(OpCodes.Ldarg_0); - foreach (var param in dispatcherMethod.Parameters.Skip(1)) // first parameter is "this" + // note: first parameter is "this" + foreach (var param in dispatcherMethod.Parameters.Skip(1)) { il.Emit(OpCodes.Ldarg, param); } - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldvirtftn, method); var callsite = new Mono.Cecil.CallSite(method.ReturnType) { HasThis = true }; foreach (var param in method.Parameters) callsite.Parameters.Add(param); - il.Emit(OpCodes.Calli, callsite); - il.Emit(OpCodes.Ret); + // Emit: @this.ldvirtftn(method) then calli — resolves the actual override at runtime + il.Emit(OpCodes.Ldarg_0) + .Emit(OpCodes.Ldvirtftn, method) + .Emit(OpCodes.Calli, callsite) + .Emit(OpCodes.Ret); return dispatcherMethod; } @@ -88,6 +106,7 @@ public void ProcessSerializers(CecilSerializerContext context) EnumerateReferences(references, context.Assembly); var coreAssembly = CecilExtensions.FindCorlibAssembly(context.Assembly); + var module = context.Assembly.MainModule; // Only process assemblies depending on Stride.Engine if (!references.Any(x => x.Name.Name == "Stride.Engine")) @@ -97,12 +116,12 @@ public void ProcessSerializers(CecilSerializerContext context) var serializationAssemblyName = "Stride.Engine.Serializers"; // Add [InteralsVisibleTo] attribute - var internalsVisibleToAttributeCtor = context.Assembly.MainModule.ImportReference(internalsVisibleToAttribute.GetConstructors().Single()); + var internalsVisibleToAttributeCtor = module.ImportReference(internalsVisibleToAttribute.GetConstructors().Single()); var internalsVisibleAttribute = new CustomAttribute(internalsVisibleToAttributeCtor) { ConstructorArguments = { - new CustomAttributeArgument(context.Assembly.MainModule.ImportReference(context.Assembly.MainModule.TypeSystem.String), serializationAssemblyName) + new CustomAttributeArgument(module.ImportReference(module.TypeSystem.String), serializationAssemblyName) } }; context.Assembly.CustomAttributes.Add(internalsVisibleAttribute); @@ -112,7 +131,7 @@ public void ProcessSerializers(CecilSerializerContext context) var strideEngineAssembly = context.Assembly.Name.Name == "Stride.Engine" ? context.Assembly - : context.Assembly.MainModule.AssemblyResolver.Resolve(new AssemblyNameReference("Stride.Engine", null)); + : module.AssemblyResolver.Resolve(new AssemblyNameReference("Stride.Engine", null)); var strideEngineModule = strideEngineAssembly.MainModule; // Generate IL for Stride.Core @@ -140,14 +159,14 @@ public void ProcessSerializers(CecilSerializerContext context) parameterCollectionResolverInstantiateValueAccessor = parameterCollectionResolver.Methods.First(x => x.Name == "InstantiateValueAccessor"); var registerMemberMethod = strideEngineModule.GetType("Stride.Updater.UpdateEngine").Methods.First(x => x.Name == "RegisterMember"); - updateEngineRegisterMemberMethod = context.Assembly.MainModule.ImportReference(registerMemberMethod); + updateEngineRegisterMemberMethod = module.ImportReference(registerMemberMethod); var registerMemberResolverMethod = strideEngineModule.GetType("Stride.Updater.UpdateEngine").Methods.First(x => x.Name == "RegisterMemberResolver"); //pclVisitor.VisitMethod(registerMemberResolverMethod); - updateEngineRegisterMemberResolverMethod = context.Assembly.MainModule.ImportReference(registerMemberResolverMethod); + updateEngineRegisterMemberResolverMethod = module.ImportReference(registerMemberResolverMethod); var typeType = coreAssembly.MainModule.GetTypeResolved(typeof(Type).FullName); - getTypeFromHandleMethod = context.Assembly.MainModule.ImportReference(typeType.Methods.First(x => x.Name == "GetTypeFromHandle")); + getTypeFromHandleMethod = module.ImportReference(typeType.Methods.First(x => x.Name == "GetTypeFromHandle")); var mainPrepareMethod = CreateUpdateMethod(context.Assembly); @@ -168,7 +187,7 @@ public void ProcessSerializers(CecilSerializerContext context) try { - ProcessType(context, context.Assembly.MainModule.ImportReference(typeDefinition), mainPrepareMethod); + ProcessType(context, module.ImportReference(typeDefinition), mainPrepareMethod); } catch (Exception e) { @@ -176,15 +195,20 @@ public void ProcessSerializers(CecilSerializerContext context) } } - // Force generic instantiations - var il = mainPrepareMethod.Body.GetILProcessor(); + // Force generic instantiations — register resolvers for lists, arrays, and trigger generic update methods + // Generates calls like: + // UpdateEngine.RegisterMemberResolver(new ListUpdateResolver()); + // UpdateEngine.RegisterMemberResolver(new ArrayUpdateResolver()); + // ParameterCollectionResolver.InstantiateValueAccessor(); // iOS AOT only + // UpdateGeneric_TypeName(); // for closed generic types + var il = new ILBuilder(mainPrepareMethod.Body, module); foreach (var serializableType in context.SerializableTypesProfiles.SelectMany(x => x.Value.SerializableTypes).ToArray()) { // Special case: when processing Stride.Engine assembly, we automatically add dependent assemblies types too if (!serializableType.Value.Local && strideEngineAssembly != context.Assembly) continue; - // Try to find if original method definition was generated + // Try to find if original method definition was generated var typeDefinition = serializableType.Key.Resolve(); // If using List, register this type in UpdateEngine @@ -196,8 +220,8 @@ public void ProcessSerializers(CecilSerializerContext context) { //call Updater.UpdateEngine.RegisterMemberResolver(new Updater.ListUpdateResolver()); var elementType = ResolveGenericsVisitor.Process(serializableType.Key, listInterfaceType.GenericArguments[0]); - il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableListUpdateResolverGenericCtor).MakeGeneric(context.Assembly.MainModule.ImportReference(elementType))); - il.Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod); + il.Emit(OpCodes.Newobj, il.Import(updatableListUpdateResolverGenericCtor).MakeGeneric(il.Import(elementType))) + .Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod); } parentTypeDefinition = parentTypeDefinition.BaseType?.Resolve(); @@ -208,15 +232,15 @@ public void ProcessSerializers(CecilSerializerContext context) { //call Updater.UpdateEngine.RegisterMemberResolver(new Updater.ArrayUpdateResolver()); var elementType = ResolveGenericsVisitor.Process(serializableType.Key, arrayType.ElementType); - il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableArrayUpdateResolverGenericCtor).MakeGeneric(context.Assembly.MainModule.ImportReference(elementType))); - il.Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod); + il.Emit(OpCodes.Newobj, il.Import(updatableArrayUpdateResolverGenericCtor).MakeGeneric(il.Import(elementType))) + .Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod); } // Generic instantiation for AOT platforms if (context.Platform == Core.PlatformType.iOS && serializableType.Key.Name == "ValueParameterKey`1") { var keyType = ((GenericInstanceType)serializableType.Key).GenericArguments[0]; - il.Emit(OpCodes.Call, context.Assembly.MainModule.ImportReference(parameterCollectionResolverInstantiateValueAccessor).MakeGenericMethod(context.Assembly.MainModule.ImportReference(keyType))); + il.Emit(OpCodes.Call, il.Import(parameterCollectionResolverInstantiateValueAccessor).MakeGenericMethod(il.Import(keyType))); } if (serializableType.Key is GenericInstanceType genericInstanceType) @@ -233,9 +257,9 @@ public void ProcessSerializers(CecilSerializerContext context) if (updateMethod != null) { // Emit call to update engine setup method with generic arguments of current type - il.Emit(OpCodes.Call, context.Assembly.MainModule.ImportReference(updateMethod) + il.Emit(OpCodes.Call, il.Import(updateMethod) .MakeGenericMethod(genericInstanceType.GenericArguments - .Select(context.Assembly.MainModule.ImportReference) + .Select(x => module.ImportReference(x)) .ToArray())); } } @@ -244,6 +268,11 @@ public void ProcessSerializers(CecilSerializerContext context) il.Emit(OpCodes.Ret); } + /// + /// Registers all updatable fields and properties of a type with the update engine. + /// For generic types, creates a separate UpdateGeneric_TypeName<T>() method + /// that can be instantiated per closed generic type. + /// public void ProcessType(CecilSerializerContext context, TypeReference type, MethodDefinition updateMainMethod) { var typeDefinition = type.Resolve(); @@ -252,14 +281,14 @@ public void ProcessType(CecilSerializerContext context, TypeReference type, Meth if (typeDefinition.IsEnum) return; + var module = context.Assembly.MainModule; var updateCurrentMethod = updateMainMethod; ResolveGenericsVisitor replaceGenericsVisitor = null; if (typeDefinition.HasGenericParameters) { // Make a prepare method for just this object since it might need multiple instantiation - updateCurrentMethod = new MethodDefinition(ComputeUpdateMethodName(typeDefinition), MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static, context.Assembly.MainModule.TypeSystem.Void); - var genericsMapping = new Dictionary(); + updateCurrentMethod = new MethodDefinition(ComputeUpdateMethodName(typeDefinition), MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void); foreach (var genericParameter in typeDefinition.GenericParameters) { var genericParameterCopy = new GenericParameter(genericParameter.Name, updateCurrentMethod) @@ -267,139 +296,180 @@ public void ProcessType(CecilSerializerContext context, TypeReference type, Meth Attributes = genericParameter.Attributes, }; foreach (var constraint in genericParameter.Constraints) - genericParameterCopy.Constraints.Add(new GenericParameterConstraint(context.Assembly.MainModule.ImportReference(constraint.ConstraintType))); + genericParameterCopy.Constraints.Add(new GenericParameterConstraint(module.ImportReference(constraint.ConstraintType))); updateCurrentMethod.GenericParameters.Add(genericParameterCopy); - - genericsMapping[genericParameter] = genericParameterCopy; } - replaceGenericsVisitor = new ResolveGenericsVisitor(genericsMapping); + replaceGenericsVisitor = ResolveGenericsVisitor.FromMapping(typeDefinition, updateCurrentMethod); updateMainMethod.DeclaringType.Methods.Add(updateCurrentMethod); } - var il = updateCurrentMethod.Body.GetILProcessor(); + var il = new ILBuilder(updateCurrentMethod.Body, module); var typeIsValueType = type.IsResolvedValueType(); var emptyObjectField = typeIsValueType ? null : updateMainMethod.DeclaringType.Fields.FirstOrDefault(x => x.Name == "emptyObject"); VariableDefinition emptyStruct = null; // Note: forcing fields and properties to be processed in all cases - foreach (var serializableItem in ComplexSerializerRegistry.GetSerializableItems(type, true, ComplexTypeSerializerFlags.SerializePublicFields | ComplexTypeSerializerFlags.SerializePublicProperties | ComplexTypeSerializerFlags.Updatable)) + foreach (var serializableItem in SerializationHelpers.GetSerializableItems(type, true, ComplexTypeSerializerFlags.SerializePublicFields | ComplexTypeSerializerFlags.SerializePublicProperties | ComplexTypeSerializerFlags.Updatable, context.IgnoredMembers)) { if (serializableItem.MemberInfo is FieldReference fieldReference) - { - var field = fieldReference.Resolve(); - - // First time it is needed, let's create empty object in the class (var emptyObject = new object()) or empty local struct in the method - if (typeIsValueType) - { - if (emptyStruct == null) - { - emptyStruct = new VariableDefinition(type); - updateMainMethod.Body.Variables.Add(emptyStruct); - } - } - else - { - if (emptyObjectField == null) - { - emptyObjectField = new FieldDefinition("emptyObject", FieldAttributes.Static | FieldAttributes.Private, context.Assembly.MainModule.TypeSystem.Object); - - // Create static ctor that will initialize this object - var staticConstructor = new MethodDefinition(".cctor", - MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, - context.Assembly.MainModule.TypeSystem.Void); - var staticConstructorIL = staticConstructor.Body.GetILProcessor(); - staticConstructorIL.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(emptyObjectField.FieldType.Resolve().GetConstructors().Single(x => !x.IsStatic && !x.HasParameters))); - staticConstructorIL.Emit(OpCodes.Stsfld, emptyObjectField); - staticConstructorIL.Emit(OpCodes.Ret); - - updateMainMethod.DeclaringType.Fields.Add(emptyObjectField); - updateMainMethod.DeclaringType.Methods.Add(staticConstructor); - } - } - - il.Emit(OpCodes.Ldtoken, type); - il.Emit(OpCodes.Call, getTypeFromHandleMethod); - il.Emit(OpCodes.Ldstr, field.Name); - - if (typeIsValueType) - il.Emit(OpCodes.Ldloca, emptyStruct); - else - il.Emit(OpCodes.Ldsfld, emptyObjectField); - il.Emit(OpCodes.Ldflda, context.Assembly.MainModule.ImportReference(fieldReference)); - il.Emit(OpCodes.Conv_I); - if (typeIsValueType) - il.Emit(OpCodes.Ldloca, emptyStruct); - else - il.Emit(OpCodes.Ldsfld, emptyObjectField); - il.Emit(OpCodes.Conv_I); - il.Emit(OpCodes.Sub); - il.Emit(OpCodes.Conv_I4); - - var fieldType = context.Assembly.MainModule.ImportReference(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(field.FieldType) : field.FieldType); - il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableFieldGenericCtor).MakeGeneric(fieldType)); - il.Emit(OpCodes.Call, updateEngineRegisterMemberMethod); - } + EmitFieldRegistration(il, fieldReference, type, typeIsValueType, replaceGenericsVisitor, module, updateMainMethod, ref emptyObjectField, ref emptyStruct); if (serializableItem.MemberInfo is PropertyReference propertyReference) - { - var property = propertyReference.Resolve(); + EmitPropertyRegistration(il, propertyReference, type, replaceGenericsVisitor, context.Assembly, updateCurrentMethod); + } - var propertyGetMethod = context.Assembly.MainModule.ImportReference(property.GetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray()); + if (updateCurrentMethod != updateMainMethod) + { + // If we have a local method, close it + il.Emit(OpCodes.Ret); - il.Emit(OpCodes.Ldtoken, type); - il.Emit(OpCodes.Call, getTypeFromHandleMethod); - il.Emit(OpCodes.Ldstr, property.Name); + // Also call it from main method if it was a closed generic instantiation + if (type is GenericInstanceType genericInstanceType) + { + var mainIL = new ILBuilder(updateMainMethod.Body, module); + mainIL.Emit(OpCodes.Call, updateCurrentMethod.MakeGeneric(genericInstanceType.GenericArguments.Select(module.ImportReference).ToArray())); + } + } + } - // If it's a virtual or interface call, we need to create a dispatcher using ldvirtftn - if (property.GetMethod.IsVirtual) - propertyGetMethod = CreateDispatcher(context.Assembly, propertyGetMethod); + /// + /// Emits field registration with the update engine using pointer offset computation. + /// + /// + /// Generates code equivalent to: + /// + /// // For reference types: + /// UpdateEngine.RegisterMember(typeof(T), "fieldName", + /// new UpdatableField<FieldType>(&emptyObject.field - &emptyObject)); + /// // For value types: + /// UpdateEngine.RegisterMember(typeof(T), "fieldName", + /// new UpdatableField<FieldType>(&emptyStruct.field - &emptyStruct)); + /// + /// + private void EmitFieldRegistration( + ILBuilder il, FieldReference fieldReference, TypeReference type, bool typeIsValueType, + ResolveGenericsVisitor replaceGenericsVisitor, ModuleDefinition module, + MethodDefinition updateMainMethod, ref FieldDefinition emptyObjectField, ref VariableDefinition emptyStruct) + { + var field = fieldReference.Resolve(); - il.Emit(OpCodes.Ldftn, propertyGetMethod); + // We need a dummy instance to compute field offsets via pointer math: &empty.field - &empty + // For reference types: a static field initialized in .cctor (var emptyObject = new object()) + // For value types: a local variable (T emptyStruct) + if (typeIsValueType) + { + if (emptyStruct == null) + { + emptyStruct = new VariableDefinition(type); + updateMainMethod.Body.Variables.Add(emptyStruct); + } + } + else + { + if (emptyObjectField == null) + { + emptyObjectField = new FieldDefinition("emptyObject", FieldAttributes.Static | FieldAttributes.Private, module.TypeSystem.Object); + + // Create static ctor that will initialize this object + var staticConstructor = new MethodDefinition(".cctor", + MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + module.TypeSystem.Void); + var staticCtorIL = new ILBuilder(staticConstructor.Body, module); + staticCtorIL.Emit(OpCodes.Newobj, module.ImportReference(emptyObjectField.FieldType.Resolve().GetConstructors().Single(x => !x.IsStatic && !x.HasParameters))) + .Emit(OpCodes.Stsfld, emptyObjectField) + .Emit(OpCodes.Ret); + + updateMainMethod.DeclaringType.Fields.Add(emptyObjectField); + updateMainMethod.DeclaringType.Methods.Add(staticConstructor); + } + } - // Set whether getter method uses a VirtualDispatch (static call) or instance call - il.Emit(property.GetMethod.IsVirtual ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + // Emit: typeof(T), "fieldName" + il.EmitTypeof(type, getTypeFromHandleMethod) + .Emit(OpCodes.Ldstr, field.Name); + + // Emit: (int)(&empty.field - &empty) — computes field byte offset + if (typeIsValueType) + il.Emit(OpCodes.Ldloca, emptyStruct); + else + il.Emit(OpCodes.Ldsfld, emptyObjectField); + il.Emit(OpCodes.Ldflda, il.Import(fieldReference)) + .Emit(OpCodes.Conv_I); + if (typeIsValueType) + il.Emit(OpCodes.Ldloca, emptyStruct); + else + il.Emit(OpCodes.Ldsfld, emptyObjectField); + il.Emit(OpCodes.Conv_I) + .Emit(OpCodes.Sub) + .Emit(OpCodes.Conv_I4); + + // Emit: new UpdatableField(offset) + var fieldType = il.Import(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(field.FieldType) : field.FieldType); + il.Emit(OpCodes.Newobj, il.Import(updatableFieldGenericCtor).MakeGeneric(fieldType)) + // Emit: UpdateEngine.RegisterMember(typeof(T), "fieldName", updatableField); + .Emit(OpCodes.Call, updateEngineRegisterMemberMethod); + } - // Only uses setter if it exists and it's public - if (property.SetMethod?.IsPublic == true) - { - var propertySetMethod = context.Assembly.MainModule.ImportReference(property.SetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray()); - if (property.SetMethod.IsVirtual) - propertySetMethod = CreateDispatcher(context.Assembly, propertySetMethod); - il.Emit(OpCodes.Ldftn, propertySetMethod); - } - else - { - // 0 (native int) - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Conv_I); - } + /// + /// Emits property registration with the update engine using function pointers. + /// + /// + /// Generates code equivalent to: + /// + /// UpdateEngine.RegisterMember(typeof(T), "PropertyName", + /// new UpdatableProperty<PropType>( + /// ldftn(get_Property), // or ldftn(Dispatcher_get_Property) for virtual + /// isGetterVirtual, + /// ldftn(set_Property), // or IntPtr.Zero if no public setter + /// isSetterVirtual)); + /// + /// Virtual/interface properties use a dispatcher trampoline (see ). + /// + private void EmitPropertyRegistration( + ILBuilder il, PropertyReference propertyReference, TypeReference type, + ResolveGenericsVisitor replaceGenericsVisitor, AssemblyDefinition assembly, + MethodDefinition updateCurrentMethod) + { + var property = propertyReference.Resolve(); - // Set whether setter method uses a VirtualDispatch (static call) or instance call - il.Emit((property.SetMethod?.IsVirtual ?? false) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + var propertyGetMethod = il.Import(property.GetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray()); - var propertyType = context.Assembly.MainModule.ImportReference(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(property.PropertyType) : property.PropertyType); + // Emit: typeof(T), "PropertyName" + il.EmitTypeof(type, getTypeFromHandleMethod) + .Emit(OpCodes.Ldstr, property.Name); - var updatablePropertyInflatedCtor = GetOrCreateUpdatablePropertyCtor(context.Assembly, propertyType); + // Emit: ldftn(get_Property) — for virtual, wrap in a dispatcher that uses ldvirtftn+calli + if (property.GetMethod.IsVirtual) + propertyGetMethod = CreateDispatcher(assembly, propertyGetMethod); + il.Emit(OpCodes.Ldftn, propertyGetMethod) + // Set whether setter method uses a VirtualDispatch (static call) or instance call + .Emit(property.GetMethod.IsVirtual ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Newobj, updatablePropertyInflatedCtor); - il.Emit(OpCodes.Call, updateEngineRegisterMemberMethod); - } + // Emit: ldftn(set_Property) or IntPtr.Zero if no public setter + if (property.SetMethod?.IsPublic == true) + { + var propertySetMethod = il.Import(property.SetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray()); + if (property.SetMethod.IsVirtual) + propertySetMethod = CreateDispatcher(assembly, propertySetMethod); + il.Emit(OpCodes.Ldftn, propertySetMethod); } - - if (updateCurrentMethod != updateMainMethod) + else { - // If we have a local method, close it - il.Emit(OpCodes.Ret); - - // Also call it from main method if it was a closed generic instantiation - if (type is GenericInstanceType genericInstanceType) - { - il = updateMainMethod.Body.GetILProcessor(); - il.Emit(OpCodes.Call, updateCurrentMethod.MakeGeneric(genericInstanceType.GenericArguments.Select(context.Assembly.MainModule.ImportReference).ToArray())); - } + il.Emit(OpCodes.Ldc_I4_0) + .Emit(OpCodes.Conv_I); } + + // Set whether setter method uses a VirtualDispatch (static call) or instance call + il.Emit((property.SetMethod?.IsVirtual ?? false) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + + // Emit: new UpdatableProperty(getter, isGetterVirtual, setter, isSetterVirtual) + var propertyType = il.Import(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(property.PropertyType) : property.PropertyType); + var updatablePropertyInflatedCtor = GetOrCreateUpdatablePropertyCtor(assembly, propertyType); + il.Emit(OpCodes.Newobj, updatablePropertyInflatedCtor) + // Emit: UpdateEngine.RegisterMember(typeof(T), "PropertyName", updatableProperty); + .Emit(OpCodes.Call, updateEngineRegisterMemberMethod); } private static string ComputeUpdateMethodName(TypeDefinition typeDefinition) diff --git a/sources/engine/Stride.Engine/Updater/UpdateEngineHelper.cs b/sources/engine/Stride.Engine/Updater/UpdateEngineHelper.cs index 1c34362d98..e3b7a9af01 100644 --- a/sources/engine/Stride.Engine/Updater/UpdateEngineHelper.cs +++ b/sources/engine/Stride.Engine/Updater/UpdateEngineHelper.cs @@ -26,79 +26,18 @@ ldarga.s o => ((nint*)Unsafe.AsPointer(ref o))[0]; [MethodImpl(MethodImplOptions.AggressiveInlining), Obsolete("Do not use.", DiagnosticId = "STRIDE2000")] public static unsafe T PointerToObject(nint address) where T : class - #if false - ldarga.s address - conv.u - nop // call !!0& Unsafe::AsRef(void*) /* ldarg.0; ret */ - ldobj !!T - ret - #endif => Unsafe.AsRef(&address); /// Copies the value out of the pinned box. [MethodImpl(MethodImplOptions.AggressiveInlining), Obsolete("Do not use.", DiagnosticId = "STRIDE2000")] public static unsafe T PointerToStruct(nint address) where T : struct - #if false - ldarga.s address - conv.u - nop // call !!0& Unsafe::AsRef(void*) /* ldarg.0; ret */ - ldind.ref - unbox.any !!T - ret - #endif => (T)Unsafe.AsRef(&address); /// Obtains a reference to the object at the specified address, /// then unboxes the value of type and /// returns the controlled-mutability managed pointer. [MethodImpl(MethodImplOptions.AggressiveInlining), Obsolete("Do not use.", DiagnosticId = "STRIDE2000")] public static unsafe ref T RefBoxedStruct(nint address) where T : struct - #if false - ldarga.s address - conv.u - nop // call !!0& Unsafe::AsRef(void*) /* ldarg.0; ret */ - ldobj System.Object - unbox !!T - ret - #endif => ref Unsafe.Unbox(PointerToObject(address)); - [Obsolete("Use ObjectToPointer instead.")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IntPtr ObjectToPtr(object obj) - { -#if IL - ldarg obj - conv.i - ret -#endif - throw new NotImplementedException(); - } - - [Obsolete("Use PointerToObject instead.")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T PtrToObject(IntPtr obj) where T : class - { -#if IL - object convObj; // TEMP XAMARIN AOT FIX -- DOES NOT WORK FOR VALUE TYPE PROPERTIES - ldarg obj - stloc convObj // TEMP XAMARIN AOT FIX -- DOES NOT WORK FOR VALUE TYPE PROPERTIES - ldloc convObj // TEMP XAMARIN AOT FIX -- DOES NOT WORK FOR VALUE TYPE PROPERTIES - ret -#endif - throw new NotImplementedException(); - } - - [Obsolete("Use Unsafe.Unbox instead.")] - [MethodImpl(MethodImplOptions.NoInlining)] // Needed for Xamarin AOT - public static IntPtr Unbox(object obj) - { -#if IL - ldarg obj - unbox !!T - ret -#endif - throw new NotImplementedException(); - } - private static int ComputeArrayFirstElementOffset() { var testArray = new int[1];