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