diff --git a/.github/workflows/NativeAotTest.yml b/.github/workflows/NativeAotTest.yml new file mode 100644 index 00000000..35a2daff --- /dev/null +++ b/.github/workflows/NativeAotTest.yml @@ -0,0 +1,63 @@ +name: NativeAot Validation + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + include: + - os: windows-latest + rid: win-x64 + - os: ubuntu-latest + rid: linux-x64 + - os: macos-latest + rid: osx-x64 + + runs-on: ${{ matrix.os }} + env: + AppVeyorBuild: true + steps: + - uses: actions/checkout@v2 + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + working-directory: src + run: dotnet restore + + - name: Build FlatSharp.Compiler + working-directory: src/FlatSharp.Compiler + run: dotnet build -c Release + + - name: Run FlatSharp.Compiler + # You may pin to the exact commit or the version. + # uses: Amadevus/pwsh-script@97a8b211a5922816aa8a69ced41fa32f23477186 + uses: Amadevus/pwsh-script@v2.0.3 + with: + # PowerShell script to execute in Actions-hydrated context + script: | + $fbs = (gci -r src/Tests/CompileTests/NativeAot/*.fbs) -join ";" + dotnet src/FlatSharp.Compiler/bin/Release/net8.0/FlatSharp.Compiler.dll --nullable-warnings false --normalize-field-names true --input "$fbs" -o src/Tests/CompileTests/NativeAot + + - name: Build + working-directory: src/Tests/CompileTests/NativeAot + run: dotnet publish -c Release -r ${{ matrix.rid }} -f net8.0 + + - name: Run + working-directory: src/Tests/CompileTests/NativeAot/bin/Release/net8.0/${{ matrix.rid }}/publish + run: ./NativeAot + + - name: Upload Files + uses: actions/upload-artifact@v3 + with: + name: assembly + path: src/Tests/CompileTests/NativeAot/bin/**/*.* diff --git a/.gitignore b/.gitignore index b33eaac6..cbdb58f4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ /src/Benchmarks/ExperimentalBenchmark/FlatSharp.generated.cs /src/Tests/FlatSharpEndToEndTests_Generated/FlatSharpEndToEndTestsGenerated.xml **/BenchmarkDotNet.Artifacts/** +/src/Tests/CompileTests/NativeAot/FlatSharp.generated.cs diff --git a/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj b/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj index db9cce9a..cbd6b77e 100644 --- a/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj +++ b/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj @@ -14,6 +14,7 @@ enable + true diff --git a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs index 141f1ce7..44a01ae8 100644 --- a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs +++ b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs @@ -119,19 +119,19 @@ public T Parse(TInputBuffer buffer, FlatBufferDeserializationOptio switch (option ?? this.option) { case FlatBufferDeserializationOption.Lazy: - item = buffer.InvokeLazyParse(inner, in parseArgs); + item = inner.ParseLazy(buffer, in parseArgs); break; case FlatBufferDeserializationOption.Greedy: - item = buffer.InvokeGreedyParse(inner, in parseArgs); + item = inner.ParseGreedy(buffer, in parseArgs); break; case FlatBufferDeserializationOption.GreedyMutable: - item = buffer.InvokeGreedyMutableParse(inner, in parseArgs); + item = inner.ParseGreedyMutable(buffer, in parseArgs); break; case FlatBufferDeserializationOption.Progressive: - item = buffer.InvokeProgressiveParse(inner, in parseArgs); + item = inner.ParseProgressive(buffer, in parseArgs); break; default: diff --git a/src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs b/src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs index 8dc550fc..11692f89 100644 --- a/src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs @@ -133,20 +133,4 @@ public ReadOnlyMemory GetReadOnlyMemory() { return this.memory; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeLazyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseLazy(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeProgressiveParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseProgressive(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeGreedyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseGreedy(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeGreedyMutableParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseGreedyMutable(this, in arguments); } diff --git a/src/FlatSharp.Runtime/IO/ArraySegmentInputBuffer.cs b/src/FlatSharp.Runtime/IO/ArraySegmentInputBuffer.cs index 5a2fe07f..e3de0e77 100644 --- a/src/FlatSharp.Runtime/IO/ArraySegmentInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/ArraySegmentInputBuffer.cs @@ -134,22 +134,6 @@ public ReadOnlyMemory GetReadOnlyMemory() return this.pointer.segment; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeLazyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseLazy(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeProgressiveParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseProgressive(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeGreedyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseGreedy(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeGreedyMutableParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseGreedyMutable(this, in arguments); - // Array Segment is a relatively heavy struct. It contains an array pointer, an int offset, and and int length. // Copying this by value for each method call is actually slower than having a little private pointer to a single item. private class ArraySegmentPointer diff --git a/src/FlatSharp.Runtime/IO/IInputBuffer.cs b/src/FlatSharp.Runtime/IO/IInputBuffer.cs index 690ebfad..9563a864 100644 --- a/src/FlatSharp.Runtime/IO/IInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/IInputBuffer.cs @@ -112,28 +112,4 @@ public interface IInputBuffer /// Gets a memory covering the entire input buffer. /// Memory GetMemory(); - - /// - /// Invokes the parse method on the parameter. Allows passing - /// generic parameters. - /// - TItem InvokeLazyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments); - - /// - /// Invokes the parse method on the parameter. Allows passing - /// generic parameters. - /// - TItem InvokeProgressiveParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments); - - /// - /// Invokes the parse method on the parameter. Allows passing - /// generic parameters. - /// - TItem InvokeGreedyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments); - - /// - /// Invokes the parse method on the parameter. Allows passing - /// generic parameters. - /// - TItem InvokeGreedyMutableParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments); } \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs index d9f260c5..dedeb7ee 100644 --- a/src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs @@ -138,22 +138,6 @@ public Memory GetMemory() return this.pointer.memory; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeLazyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseLazy(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeProgressiveParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseProgressive(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeGreedyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseGreedy(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeGreedyMutableParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseGreedyMutable(this, in arguments); - // Memory is a relatively heavy struct. It's cheaper to wrap it in a // a reference that will be collected ephemerally in Gen0 than it is to // copy it around. diff --git a/src/FlatSharp.Runtime/IO/ReadOnlyMemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/ReadOnlyMemoryInputBuffer.cs index 046940c1..84df4e78 100644 --- a/src/FlatSharp.Runtime/IO/ReadOnlyMemoryInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/ReadOnlyMemoryInputBuffer.cs @@ -132,22 +132,6 @@ public ReadOnlyMemory GetReadOnlyMemory() return this.pointer.memory; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeLazyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseLazy(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeProgressiveParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseProgressive(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeGreedyParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseGreedy(this, in arguments); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TItem InvokeGreedyMutableParse(IGeneratedSerializer serializer, in GeneratedSerializerParseArguments arguments) - => serializer.ParseGreedyMutable(this, in arguments); - public Span GetSpan() { FSThrow.InvalidOperation(ErrorMessage); diff --git a/src/FlatSharp.Runtime/SortedVectorHelpers.cs b/src/FlatSharp.Runtime/SortedVectorHelpers.cs index 1e134fb2..0c65d044 100644 --- a/src/FlatSharp.Runtime/SortedVectorHelpers.cs +++ b/src/FlatSharp.Runtime/SortedVectorHelpers.cs @@ -35,6 +35,7 @@ of this software and associated documentation files (the "Software"), to deal using System.Reflection; using System.Diagnostics.CodeAnalysis; using System.Net.WebSockets; +using System.Runtime.InteropServices; namespace FlatSharp; @@ -187,9 +188,18 @@ internal static class KeyLookup { static KeyLookup() { +#if NETCOREAPP || NETSTANDARD2_1_OR_GREATER // Convention is for static constructors in the table to register key lookups. Force them to run here before fields // are accessed. + if (RuntimeFeature.IsDynamicCodeSupported) // this should be true for all cases except native AOT. This does not need to run for NativeAOT since static constructors are pre-executed. + { +#pragma warning disable IL2059 + RuntimeHelpers.RunClassConstructor(typeof(TTable).TypeHandle); +#pragma warning restore IL2059 + } +#else RuntimeHelpers.RunClassConstructor(typeof(TTable).TypeHandle); +#endif } private static string NotInitializedErrorMessage = $"Type '{typeof(TTable).Name}' has not registered a sorted vector key of type '{typeof(TKey).Name}'."; diff --git a/src/Tests/CompileTests/NativeAot/NativeAot.csproj b/src/Tests/CompileTests/NativeAot/NativeAot.csproj new file mode 100644 index 00000000..ae61f423 --- /dev/null +++ b/src/Tests/CompileTests/NativeAot/NativeAot.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + annotations + true + true + true + + + + + + + diff --git a/src/Tests/CompileTests/NativeAot/NativeAot.sln b/src/Tests/CompileTests/NativeAot/NativeAot.sln new file mode 100644 index 00000000..19ccea0d --- /dev/null +++ b/src/Tests/CompileTests/NativeAot/NativeAot.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34616.47 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeAot", "NativeAot.csproj", "{FE0D4FB2-1E8E-4A2B-81C0-A23384C66598}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FlatSharp.Runtime", "..\..\..\FlatSharp.Runtime\FlatSharp.Runtime.csproj", "{39A4F280-BC8A-498A-8F0A-E617DEA455FA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FE0D4FB2-1E8E-4A2B-81C0-A23384C66598}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE0D4FB2-1E8E-4A2B-81C0-A23384C66598}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE0D4FB2-1E8E-4A2B-81C0-A23384C66598}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE0D4FB2-1E8E-4A2B-81C0-A23384C66598}.Release|Any CPU.Build.0 = Release|Any CPU + {39A4F280-BC8A-498A-8F0A-E617DEA455FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39A4F280-BC8A-498A-8F0A-E617DEA455FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39A4F280-BC8A-498A-8F0A-E617DEA455FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39A4F280-BC8A-498A-8F0A-E617DEA455FA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DAA100EE-7E3A-4209-985E-2CA4FCB8F1E4} + EndGlobalSection +EndGlobal diff --git a/src/Tests/CompileTests/NativeAot/Program.cs b/src/Tests/CompileTests/NativeAot/Program.cs new file mode 100644 index 00000000..a098acbf --- /dev/null +++ b/src/Tests/CompileTests/NativeAot/Program.cs @@ -0,0 +1,205 @@ +using FlatSharp; +using System.Diagnostics; +using System.Text; + +namespace NativeAot +{ + internal class Program + { + static void Main(string[] args) + { + IndexedVector indexedVector = new(); + for (int i = 0; i < 1000; ++i) + { + indexedVector.Add(new KeyValuePair { Key = Guid.NewGuid().ToString(), Value = i }); + } + + Root root = new() + { + IndexedVector = indexedVector, + IntVector = new int[] { 1, 2, 3 }, + StructVector = new List + { + new Vec3 { X = 1, Y = 2, Z = 3 }, + new Vec3 { X = 4, Y = 5, Z = 6 }, + new Vec3 { X = 7, Y = 8, Z = 9 }, + } + }; + + int maxSize = Root.Serializer.GetMaxSize(root); + Console.WriteLine("Max size: " + maxSize); + + byte[] buffer = new byte[maxSize]; + int bytesWritten = 0; + + for (int i = 0; i < 10; ++i) + { + bytesWritten = Root.Serializer.Write(buffer, root); + } + + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < 1000; ++i) + { + bytesWritten = Root.Serializer.Write(buffer, root); + } + sw.Stop(); + + Console.WriteLine($"Serialization complete. Bytes written = {bytesWritten}. TotalTime = {sw.Elapsed.TotalMicroseconds:N0}us"); + Console.WriteLine(); + + foreach (var option in Enum.GetValues()) + { + Traverse(root, new(buffer), option); + Traverse(root, new(buffer), option); + Traverse(root, new(buffer), option); + Traverse(root, new(buffer), option); + Traverse(root, new(new ArrayInputBuffer(buffer)), option); + Console.WriteLine(); + } + } + + public static void Traverse(Root original, TInputBuffer buffer, FlatBufferDeserializationOption option) + where TInputBuffer : IInputBuffer + { + for (int i = 0; i < 10; ++i) + { + ParseAndTraverse(original, buffer, option); + } + + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < 1000; ++i) + { + ParseAndTraverse(original, buffer, option); + } + sw.Stop(); + + Console.WriteLine($"Parsing [ {option} ][ {typeof(TInputBuffer).Name} ]. TotalTime = {sw.Elapsed.TotalMicroseconds:N0}us"); + + static void ParseAndTraverse(Root original, TInputBuffer buffer, FlatBufferDeserializationOption option) + { + Root parsed = Root.Serializer.Parse(buffer, option); + + for (int i = 0; i < original.IntVector.Count; ++i) + { + if (original.IntVector[i] != parsed.IntVector[i]) + { + throw new Exception(); + } + } + + foreach (var kvp in original.IndexedVector) + { + string key = kvp.Key; + int value = kvp.Value.Value; + + if (!parsed.IndexedVector.TryGetValue(key, out var parsedValue) || parsedValue.Value != value) + { + throw new Exception(); + } + } + + for (int i = 0; i < original.StructVector.Count; ++i) + { + Vec3 originalItem = original.StructVector[i]; + Vec3 parsedItem = parsed.StructVector[i]; + + if (originalItem.X != parsedItem.X || originalItem.Y != parsedItem.Y || originalItem.Z != parsedItem.Z) + { + throw new Exception(); + } + } + } + } + + private class CustomInputBuffer : IInputBuffer + { + private readonly IInputBuffer inner; + + public CustomInputBuffer(IInputBuffer inner) + { + this.inner = inner; + } + + public bool IsReadOnly => inner.IsReadOnly; + + public bool IsPinned => inner.IsPinned; + + public int Length => inner.Length; + + public Memory GetMemory() + { + return inner.GetMemory(); + } + + public ReadOnlyMemory GetReadOnlyMemory() + { + return inner.GetReadOnlyMemory(); + } + + public ReadOnlySpan GetReadOnlySpan() + { + return inner.GetReadOnlySpan(); + } + + public Span GetSpan() + { + return inner.GetSpan(); + } + + public byte ReadByte(int offset) + { + return inner.ReadByte(offset); + } + + public double ReadDouble(int offset) + { + return inner.ReadDouble(offset); + } + + public float ReadFloat(int offset) + { + return inner.ReadFloat(offset); + } + + public int ReadInt(int offset) + { + return inner.ReadInt(offset); + } + + public long ReadLong(int offset) + { + return inner.ReadLong(offset); + } + + public sbyte ReadSByte(int offset) + { + return inner.ReadSByte(offset); + } + + public short ReadShort(int offset) + { + return inner.ReadShort(offset); + } + + public string ReadString(int offset, int byteLength, Encoding encoding) + { + return inner.ReadString(offset, byteLength, encoding); + } + + public uint ReadUInt(int offset) + { + return inner.ReadUInt(offset); + } + + public ulong ReadULong(int offset) + { + return inner.ReadULong(offset); + } + + public ushort ReadUShort(int offset) + { + return inner.ReadUShort(offset); + } + } + } +} diff --git a/src/Tests/CompileTests/NativeAot/Schema.fbs b/src/Tests/CompileTests/NativeAot/Schema.fbs new file mode 100644 index 00000000..07242a62 --- /dev/null +++ b/src/Tests/CompileTests/NativeAot/Schema.fbs @@ -0,0 +1,26 @@ + +attribute "fs_serializer"; +attribute "fs_valueStruct"; +attribute "fs_vector"; + +namespace NativeAot; + +struct Vec3 +{ + x : float; + y : float; + z : float; +} + +table KeyValuePair +{ + key : string (key); + value : int; +} + +table Root (fs_serializer) +{ + struct_vector : [ Vec3 ]; + int_vector : [ int ]; + indexed_vector : [ KeyValuePair ] (fs_vector:"IIndexedVector"); +} \ No newline at end of file