Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various fixes for AOT compilation #110

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
21 changes: 20 additions & 1 deletion src/Ceras.AotGenerator/Generator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//#define ENHANCED_ASSEMBLY_DEBUG_INFO // Having this enabled kills Unity play-in-editor startup performance

using System;

namespace CerasAotFormatterGenerator
Expand Down Expand Up @@ -55,6 +57,7 @@ public static void Generate(string ns, IEnumerable<Assembly> asms, StringBuilder
config = (SerializerConfig)configMethods[0].Invoke(null, null);

var ceras = new CerasSerializer(config);
ceras.IsDuringAOTGeneration = true;


// Start with KnownTypes and user-marked types...
Expand All @@ -72,16 +75,30 @@ public static void Generate(string ns, IEnumerable<Assembly> asms, StringBuilder
// Get first, remove from "to explore" list, and add it to the "done" list.
var t = newTypes.First();

if(t.IsArray)
if (t.IsArray)
{
newTypes.Remove(t);
processedTypes.Add(t);
t = t.GetElementType();

if (processedTypes.Contains(t))
{
continue;
}
}

newTypes.Remove(t);
processedTypes.Add(t);

if (CerasSerializer.IsPrimitiveType(t))
// Skip int, string, Type, ...
continue;

if (t.IsAbstract && asms.Any(x => x.GetTypes().Contains(t)))
{
newTypes.AddRange(asms.SelectMany(x => x.GetTypes()).Where(y => !y.IsAbstract && y.IsSubclassOf(t) && !processedTypes.Contains(y)));
}

if (t.IsAbstract || t.ContainsGenericParameters)
// Can't explore abstract or open generics
continue;
Expand Down Expand Up @@ -144,7 +161,9 @@ public static void RegisterAssemblyResolver()
{
if (_resolverRegistered)
return;
#if ENHANCED_ASSEMBLY_DEBUG_INFO
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
#endif
_resolverRegistered = true;
}

Expand Down
6 changes: 6 additions & 0 deletions src/Ceras.AotGenerator/Helpers/TypeNameHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ public static string ToVariableSafeName(this Type type)
+ "_"
+ string.Join("_", type.GetGenericArguments().Select(t => t.ToFriendlyName(false)).ToArray());
}
else if (type.IsArray)
{
int rank = type.GetArrayRank();
string baseName = name.Split('[')[0];
return $"{baseName}_Array{rank}";
}
else
{
return name;
Expand Down
19 changes: 11 additions & 8 deletions src/Ceras.AotGenerator/SourceGenerator/SourceFormatterGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ static class SourceFormatterGenerator
public static void GenerateAll(string ns, List<Type> targets, Dictionary<Type, Type> aotHint,
CerasSerializer ceras, StringBuilder text)
{
text.AppendLine(@"
// ReSharper disable All

#nullable disable
#pragma warning disable 649
");
text.AppendLine("// ReSharper disable All");
text.AppendLine("");
#if !CSHARP_7_OR_LATER || UNITY_2020_2_OR_NEWER
text.AppendLine("#nullable disable");
#endif
text.AppendLine("#pragma warning disable 649");
text.AppendLine("");
text.AppendLine("using Ceras;");
text.AppendLine("using Ceras.Formatters;");
text.AppendLine("using Ceras.Formatters.AotGenerator;");
text.AppendLine("");
text.AppendLine($"namespace {ns}");
text.AppendLine("{");

var setFormattersHint = aotHint.Keys.Select((t, i) => $"\t\t\t{aotHint[t].ToFriendlyName(true)} var{i} = default;\n\t\t\tconfig.ConfigType<{t.ToFriendlyName(true)}>().CustomFormatter = var{i};");
var setFormattersHint = aotHint.Keys.Select((t, i) => $"\t\t\t{aotHint[t].ToFriendlyName(true)} var{i} = default;{Environment.NewLine}\t\t\tconfig.ConfigType<{t.ToFriendlyName(true)}>().CustomFormatter = var{i};");
var setCustomFormatters = targets.Select(t => $"\t\t\tconfig.ConfigType<{t.ToFriendlyName(true)}>().CustomFormatter = new {t.ToVariableSafeName()}Formatter();");
text.AppendLine(
$@" public static class GeneratedFormatters
Expand All @@ -48,7 +49,9 @@ private static void AotHint(SerializerConfig config)

text.Length -= Environment.NewLine.Length;
text.AppendLine("}");
text.AppendLine("#nullable restore");
#if !CSHARP_7_OR_LATER || UNITY_2020_2_OR_NEWER
text.AppendLine("#nullable restore");
#endif
text.AppendLine("#pragma warning restore 649");
text.AppendLine();
}
Expand Down
2 changes: 2 additions & 0 deletions src/Ceras/CerasSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ public static void ClearGenericCaches()
int _recursionDepth = 0;
RecursionMode _mode = RecursionMode.Idle; // while in one mode we cannot enter the others

internal bool IsDuringAOTGeneration { get; set; } = false;

/// <summary>
/// A "free" property where you can store anything.
/// <para>This is useful for scenarios where callbacks like <see cref="OnBeforeSerializeAttribute"/> are used, because the <see cref="CerasSerializer"/> instance is passed to those methods (assuming they take a <see cref="CerasSerializer"/> instance as an argument). That way you can obtain the <see cref="UserContext"/> inside those methods.</para>
Expand Down
11 changes: 10 additions & 1 deletion src/Ceras/Formatters/CollectionFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,16 @@ public void Deserialize(byte[] buffer, ref int offset, ref TCollection value)


if (value == null)
value = _capacityConstructor((int)itemCount);
{
if (_capacityConstructor != null)
{
value = _capacityConstructor((int)itemCount);
}
else
{
value = Activator.CreateInstance<TCollection>();
}
}
else
{
if (value.Count > 0)
Expand Down
5 changes: 2 additions & 3 deletions src/Ceras/Formatters/ReferenceFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,9 @@ static SerializeDelegate<T> CreateSpecificSerializerDispatcher_Aot(Type type, IF
}

// Can't call directly, need to invoke through reflection so T gets casted up/down correctly.
var args = new object[3];
return (ref byte[] buffer, ref int offset, T value) =>
{
var args = new object[3];
args[0] = buffer;
args[1] = offset;
args[2] = value;
Expand All @@ -559,10 +559,9 @@ static DeserializeDelegate<T> CreateSpecificDeserializerDispatcher_Aot(Type type
// return (ref byte[] buffer, ref int offset, T value) => { f.Serialize(ref buffer, ref offset, value); };
}


var args = new object[3];
return new DeserializeDelegate<T>((byte[] buffer, ref int offset, ref T value) =>
{
var args = new object[3];
args[0] = buffer;
args[1] = offset;
args[2] = value;
Expand Down
3 changes: 2 additions & 1 deletion src/Ceras/Helpers/ReflectionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,8 @@ public static string FriendlyName(this Type type, bool fullName = false)
}
else
{
return fullName ? type.FullName : type.Name;
var name = fullName ? type.FullName : type.Name;
return name.Replace("+", ".");
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/Ceras/Resolvers/ReinterpretFormatterResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public IFormatter GetFormatter(Type type)
if (!ReflectionHelper.IsBlittableType(type))
return null;

if (_ceras.IsDuringAOTGeneration && type.IsGenericType)
return null; // ReinterpretFormatter's unmanaged constraint on T causes the compiler reject generic types, but runtime doesn't enforce it

var formatterType = typeof(ReinterpretFormatter<>).MakeGenericType(type);

return (IFormatter) Activator.CreateInstance(formatterType);
Expand Down
12 changes: 6 additions & 6 deletions src/Ceras/Resolvers/StandardFormatterResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref Nullable<T> value)
}


class KeyValuePairFormatter<TKey, TValue> : IFormatter<KeyValuePair<TKey, TValue>>
public class KeyValuePairFormatter<TKey, TValue> : IFormatter<KeyValuePair<TKey, TValue>>
{
IFormatter<TKey> _keyFormatter;
IFormatter<TValue> _valueFormatter;
Expand All @@ -196,7 +196,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref KeyValuePair<TKey, TV
}


class DateTimeFormatter : IFormatter<DateTime>
public class DateTimeFormatter : IFormatter<DateTime>
{
public void Serialize(ref byte[] buffer, ref int offset, DateTime value)
{
Expand All @@ -211,7 +211,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref DateTime value)
}
}

class DateTimeOffsetFormatter : IFormatter<DateTimeOffset>
public class DateTimeOffsetFormatter : IFormatter<DateTimeOffset>
{
public void Serialize(ref byte[] buffer, ref int offset, DateTimeOffset value)
{
Expand All @@ -228,7 +228,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref DateTimeOffset value)
}
}

class TimeSpanFormatter : IFormatter<TimeSpan>
public class TimeSpanFormatter : IFormatter<TimeSpan>
{
public void Serialize(ref byte[] buffer, ref int offset, TimeSpan value)
{
Expand All @@ -241,7 +241,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref TimeSpan value)
}
}

class BitVector32Formatter : IFormatter<BitVector32>
public class BitVector32Formatter : IFormatter<BitVector32>
{
public void Serialize(ref byte[] buffer, ref int offset, BitVector32 value)
{
Expand All @@ -255,7 +255,7 @@ public void Deserialize(byte[] buffer, ref int offset, ref BitVector32 value)
}
}

class BigIntegerFormatter : IFormatter<BigInteger>
public class BigIntegerFormatter : IFormatter<BigInteger>
{
public void Serialize(ref byte[] buffer, ref int offset, BigInteger value)
{
Expand Down