diff --git a/src/Benchmarks/Benchmark/Benchmark.csproj b/src/Benchmarks/Benchmark/Benchmark.csproj index dd3af888..bd93e92a 100644 --- a/src/Benchmarks/Benchmark/Benchmark.csproj +++ b/src/Benchmarks/Benchmark/Benchmark.csproj @@ -4,7 +4,7 @@ net8.0 false false - $(DefineContants);CURRENT_VERSION_ONLY;RUN_COMPARISON_BENCHMARKS;FLATSHARP_7_0_0_OR_GREATER + $(DefineContants);CURRENT_VERSION_ONLY;FLATSHARP_7_0_0_OR_GREATER False true diff --git a/src/Benchmarks/Benchmark/Program.cs b/src/Benchmarks/Benchmark/Program.cs index 5f9849e9..8ada96a7 100644 --- a/src/Benchmarks/Benchmark/Program.cs +++ b/src/Benchmarks/Benchmark/Program.cs @@ -48,7 +48,7 @@ public static void Main(string[] args) Job job = Job.ShortRun .WithAnalyzeLaunchVariance(true) - .WithLaunchCount(7) + .WithLaunchCount(3) .WithWarmupCount(3) .WithIterationCount(5) #if AOT @@ -58,14 +58,14 @@ public static void Main(string[] args) #else .WithRuntime(CoreRuntime.Core80) #endif - ; + ; // job = job.WithEnvironmentVariable(new EnvironmentVariable("DOTNET_TieredPGO", "0")); var config = DefaultConfig.Instance .AddColumn(new[] { StatisticColumn.P25, StatisticColumn.P95 }) .AddDiagnoser(MemoryDiagnoser.Default) - .AddJob(job); + .AddJob(job.DontEnforcePowerPlan()); summaries.Add(BenchmarkRunner.Run(typeof(FBBench.FBSerializeBench), config)); summaries.Add(BenchmarkRunner.Run(typeof(FBBench.FBDeserializeBench), config)); diff --git a/src/Benchmarks/Benchmark/Serializers/FlatSharpHelper.cs b/src/Benchmarks/Benchmark/Serializers/FlatSharpHelper.cs index 7e50f971..247c953d 100644 --- a/src/Benchmarks/Benchmark/Serializers/FlatSharpHelper.cs +++ b/src/Benchmarks/Benchmark/Serializers/FlatSharpHelper.cs @@ -16,11 +16,11 @@ internal class FlatSharpHelper private static FS.SortedVectorContainer sortedInts; private static FS.SortedVectorContainer unsortedInts; - public static int Prepare(int length) + public static long Prepare(int length) { BenchmarkUtilities.Prepare(length, out container); buffer = new byte[GetMaxSize()]; - int bytesWritten = Serialize(); + long bytesWritten = Serialize(); sortedStrings = new() { SortedStrings = new List() }; unsortedStrings = new() { UnsortedStrings = new List() }; @@ -41,14 +41,14 @@ public static int Prepare(int length) return bytesWritten; } - public static int GetMaxSize() + public static long GetMaxSize() { return FS.FooBarContainer.Serializer.GetMaxSize(container); } - public static int Serialize() + public static long Serialize() { - return FS.FooBarContainer.Serializer.Write(new SpanWriter(), buffer, container); + return FS.FooBarContainer.Serializer.Write(buffer, container); } public static int ParseAndTraverse(int iterations, FlatBufferDeserializationOption option) @@ -63,15 +63,15 @@ public static int ParseAndTraversePartial(int iterations, FlatBufferDeserializat return BenchmarkUtilities.TraverseFooBarContainerPartial(parsed, iterations); } - public static int SerializeSortedStrings() - => FS.SortedVectorContainer.Serializer.Write(new SpanWriter(), buffer, sortedStrings); + public static long SerializeSortedStrings() + => FS.SortedVectorContainer.Serializer.Write(buffer, sortedStrings); - public static int SerializeUnsortedStrings() - => FS.SortedVectorContainer.Serializer.Write(new SpanWriter(), buffer, unsortedStrings); + public static long SerializeUnsortedStrings() + => FS.SortedVectorContainer.Serializer.Write(buffer, unsortedStrings); - public static int SerializeSortedInts() - => FS.SortedVectorContainer.Serializer.Write(new SpanWriter(), buffer, sortedInts); + public static long SerializeSortedInts() + => FS.SortedVectorContainer.Serializer.Write(buffer, sortedInts); - public static int SerializeUnsortedInts() - => FS.SortedVectorContainer.Serializer.Write(new SpanWriter(), buffer, unsortedInts); + public static long SerializeUnsortedInts() + => FS.SortedVectorContainer.Serializer.Write(buffer, unsortedInts); } diff --git a/src/Benchmarks/Benchmark/Serializers/FlatSharpValueStructs.cs b/src/Benchmarks/Benchmark/Serializers/FlatSharpValueStructs.cs index e62dd4e9..90122446 100644 --- a/src/Benchmarks/Benchmark/Serializers/FlatSharpValueStructs.cs +++ b/src/Benchmarks/Benchmark/Serializers/FlatSharpValueStructs.cs @@ -9,21 +9,21 @@ internal class FlatSharpValueStructs private static byte[] buffer; private static FS.FooBarContainerValue container; - public static int Prepare(int length) + public static long Prepare(int length) { BenchmarkUtilities.Prepare(length, out container); buffer = new byte[GetMaxSize()]; return Serialize(); } - public static int GetMaxSize() + public static long GetMaxSize() { return FS.FooBarContainerValue.Serializer.GetMaxSize(container); } - public static int Serialize() + public static long Serialize() { - return FS.FooBarContainerValue.Serializer.Write(new SpanWriter(), buffer, container); + return FS.FooBarContainerValue.Serializer.Write(buffer, container); } public static int ParseAndTraverse(int iterations, FlatBufferDeserializationOption option) diff --git a/src/Benchmarks/MicroBench.Current/Constants.cs b/src/Benchmarks/MicroBench.Current/Constants.cs index 2a542f00..8ec51970 100644 --- a/src/Benchmarks/MicroBench.Current/Constants.cs +++ b/src/Benchmarks/MicroBench.Current/Constants.cs @@ -22,6 +22,7 @@ namespace Microbench using System.Linq; using FlatSharp; using FlatSharp.Internal; + using Microsoft.Diagnostics.Tracing.Parsers.FrameworkEventSource; public static class Constants { @@ -61,6 +62,22 @@ public static class PrimitiveTables Float = 1, Double = 1, }; + + public static NestedTable RandomEntries = new() + { + Tables = Enumerable.Range(0, 1000).Select(i => new PrimitivesTable + { + Bool = Random.Shared.Next() % 2 == 0, + Byte = (byte)(Random.Shared.Next() % 2), + SByte = (sbyte)(Random.Shared.Next() % 2), + Short = (short)(Random.Shared.Next() % 2), + UShort = (ushort)(Random.Shared.Next() % 2), + Int = (int)(Random.Shared.Next() % 2), + UInt = (uint)(Random.Shared.Next() % 2), + Long = (long)(Random.Shared.Next() % 2), + ULong = (ulong)(Random.Shared.Next() % 2), + }).ToArray(), + }; } public static class StructTables @@ -155,6 +172,7 @@ public static class Buffers public static readonly byte[] PrimitivesTable_Empty = AllocateAndSerialize(PrimitiveTables.Empty); public static readonly byte[] PrimitivesTable_Full = AllocateAndSerialize(PrimitiveTables.Full); + public static readonly byte[] PrimitivesTable_RandomEntries = AllocateAndSerialize(PrimitiveTables.RandomEntries); public static readonly byte[] StructTable_SingleRef = AllocateAndSerialize(StructTables.SingleRef); public static readonly byte[] StructTable_SingleValue = AllocateAndSerialize(StructTables.SingleValue); @@ -169,7 +187,7 @@ public static class Buffers private static byte[] AllocateAndSerialize(T value) where T : class, IFlatBufferSerializable { byte[] buffer = new byte[value.Serializer.GetMaxSize(value)]; - int length = value.Serializer.Write(buffer, value); + long length = value.Serializer.Write(buffer, value); return buffer; } diff --git a/src/Benchmarks/MicroBench.Current/Program.cs b/src/Benchmarks/MicroBench.Current/Program.cs index e4495c8c..2b2ca8d6 100644 --- a/src/Benchmarks/MicroBench.Current/Program.cs +++ b/src/Benchmarks/MicroBench.Current/Program.cs @@ -40,7 +40,9 @@ public static void Main(string[] args) .WithLaunchCount(1) .WithWarmupCount(3) .WithIterationCount(6) - .WithRuntime(CoreRuntime.Core70); + .WithRuntime(CoreRuntime.Core80) + .DontEnforcePowerPlan(); + //.WithEnvironmentVariable(new EnvironmentVariable("DOTNET_TieredPGO", "1")); var config = DefaultConfig.Instance diff --git a/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs b/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs index adf5052f..81192b8e 100644 --- a/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs +++ b/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs @@ -24,73 +24,79 @@ namespace Microbench public class SerializeBenchmarks { [Benchmark] - public int Serialize_StringTable_SingleString() + public long Serialize_StringTable_SingleString() { return StringTable.Serializer.Write(Constants.Buffers.StringTable_WithString, Constants.StringTables.WithString); } [Benchmark] - public int Serialize_StringTable_Vector() + public long Serialize_StringTable_Vector() { return StringTable.Serializer.Write(Constants.Buffers.StringTable_WithVector, Constants.StringTables.WithVector); } [Benchmark] - public int Serialize_StringTable_EmptyTable() + public long Serialize_StringTable_EmptyTable() { return StringTable.Serializer.Write(Constants.Buffers.Stringtable_Empty, Constants.StringTables.Empty); } [Benchmark] - public int Serialize_PrimitivesTable_Empty() + public long Serialize_PrimitivesTable_Empty() { return PrimitivesTable.Serializer.Write(Constants.Buffers.PrimitivesTable_Empty, Constants.PrimitiveTables.Empty); } [Benchmark] - public int Serialize_PrimitivesTable_Full() + public long Serialize_PrimitivesTable_Full() { return PrimitivesTable.Serializer.Write(Constants.Buffers.PrimitivesTable_Full, Constants.PrimitiveTables.Full); } [Benchmark] - public int Serialize_StructTable_SingleRef() + public long Serialize_PrimitivesTable_RandomlyPopulated() + { + return NestedTable.Serializer.Write(Constants.Buffers.PrimitivesTable_RandomEntries, Constants.PrimitiveTables.RandomEntries); + } + + [Benchmark] + public long Serialize_StructTable_SingleRef() { return StructsTable.Serializer.Write(Constants.Buffers.StructTable_SingleRef, Constants.StructTables.SingleRef); } [Benchmark] - public int Serialize_StructTable_SingleValue() + public long Serialize_StructTable_SingleValue() { return StructsTable.Serializer.Write(Constants.Buffers.StructTable_SingleValue, Constants.StructTables.SingleValue); } [Benchmark] - public int Serialize_StructTable_VecRef() + public long Serialize_StructTable_VecRef() { return StructsTable.Serializer.Write(Constants.Buffers.StructTable_VecRef, Constants.StructTables.VectorRef); } [Benchmark] - public int Serialize_StructTable_VecValue() + public long Serialize_StructTable_VecValue() { return StructsTable.Serializer.Write(Constants.Buffers.StructTable_VecValue, Constants.StructTables.VectorValue); } [Benchmark] - public int Serialize_SafeUnion() + public long Serialize_SafeUnion() { return UnionTable.Serializer.Write(Constants.Buffers.UnionTable_Safe, Constants.UnionTables.Safe); } [Benchmark] - public int Serialize_UnsafeUnion() + public long Serialize_UnsafeUnion() { return UnionTable.Serializer.Write(Constants.Buffers.UnionTable_Unsafe, Constants.UnionTables.Unsafe); } [Benchmark] - public int Serialize_MixedUnion() + public long Serialize_MixedUnion() { return UnionTable.Serializer.Write(Constants.Buffers.UnionTable_Mixed, Constants.UnionTables.Mixed); } diff --git a/src/Benchmarks/Microbench.fbs b/src/Benchmarks/Microbench.fbs index 79ac4f89..e193e151 100644 --- a/src/Benchmarks/Microbench.fbs +++ b/src/Benchmarks/Microbench.fbs @@ -31,6 +31,10 @@ table PrimitivesTable (fs_serializer:"Lazy") { Float : float; } +table NestedTable (fs_serializer:"Lazy") { + Tables : [ PrimitivesTable ]; +} + struct RefStruct (fs_writeThrough) { Value : int; } struct ValueStruct (fs_valueStruct) { Value : int; } @@ -51,7 +55,6 @@ table SortedTable (fs_serializer:"Lazy") Ints : [IntKey] (fs_vector:"IIndexedVector"); } - struct ValueStructA (fs_valueStruct) { x : int; } struct ValueStructB (fs_valueStruct) { y : long; } struct ValueStructC (fs_valueStruct) { a : ValueStructA; b : ValueStructB; } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 896562bf..4852125f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ true false $(MSBuildThisFileDirectory)\..\misc\strongname.snk - 12.0 + preview true $(NoWarn);CS8032 True diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 2546a6f7..a4e55699 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -13,9 +13,12 @@ - + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/FlatSharp.Runtime/BufferTooSmallException.cs b/src/FlatSharp.Runtime/BufferTooSmallException.cs index 6af02ea9..2ca529eb 100644 --- a/src/FlatSharp.Runtime/BufferTooSmallException.cs +++ b/src/FlatSharp.Runtime/BufferTooSmallException.cs @@ -28,5 +28,5 @@ public BufferTooSmallException() : base($"The provided buffer was too small to h /// /// The maximum amount of size needed for this message. /// - public int SizeNeeded { get; internal set; } + public long SizeNeeded { get; internal set; } } diff --git a/src/FlatSharp.Runtime/FSThrow.cs b/src/FlatSharp.Runtime/FSThrow.cs index c082cea5..bde337ae 100644 --- a/src/FlatSharp.Runtime/FSThrow.cs +++ b/src/FlatSharp.Runtime/FSThrow.cs @@ -135,7 +135,7 @@ public static T Argument(string message) #region BufferTooSmall [DoesNotReturn] - public static void BufferTooSmall(int sizeNeeded) + public static void BufferTooSmall(long sizeNeeded) { throw new BufferTooSmallException { diff --git a/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj b/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj index 55ff7475..0cf6e92e 100644 --- a/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj +++ b/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj @@ -9,6 +9,7 @@ embedded true $(NoWarn);CS1591 + true @@ -17,6 +18,10 @@ + + BigSpanHelpers.cs + TextTemplatingFileGenerator + SpanComparers.cs TextTemplatingFileGenerator @@ -32,6 +37,11 @@ + + True + True + BigSpanHelpers.tt + True True @@ -43,4 +53,8 @@ UnionTypes.tt + + + + diff --git a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs index ff867d1c..bff73f56 100644 --- a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs +++ b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs @@ -60,7 +60,7 @@ private GeneratedSerializerWrapper(GeneratedSerializerWrapper template) public FlatBufferDeserializationOption DeserializationOption => this.option; - public int GetMaxSize(T item) + public long GetMaxSize(T item) { if (item is null) { @@ -83,9 +83,8 @@ item is IFlatBufferDeserializedObject deserializedObj && // than to introduce an 'if'. } - int ISerializer.GetMaxSize(object item) + long ISerializer.GetMaxSize(object item) { - return item switch { T t => this.GetMaxSize(t), @@ -144,8 +143,7 @@ public T Parse(TInputBuffer buffer, FlatBufferDeserializationOptio object ISerializer.Parse(TInputBuffer buffer, FlatBufferDeserializationOption? option) => this.Parse(buffer, option); - public int Write(TSpanWriter writer, Span destination, T item) - where TSpanWriter : ISpanWriter + public long Write(BigSpan destination, T item) { if (item is null) { @@ -163,14 +161,7 @@ item is IFlatBufferDeserializedObject deserializedObj && { IInputBuffer? inputBuffer = deserializedObj.InputBuffer; FlatSharpInternal.Assert(inputBuffer is not null, "Input buffer was null"); - - if (destination.Length < inputBuffer.Length) - { - FSThrow.BufferTooSmall(inputBuffer.Length); - } - - inputBuffer.GetReadOnlySpan().CopyTo(destination); - return inputBuffer.Length; + return inputBuffer.CopyTo(destination); } var serializationContext = SerializationContext.ThreadLocalContext.Value!; @@ -187,11 +178,11 @@ item is IFlatBufferDeserializedObject deserializedObj && Debug.Assert(!sharedStringWriter.IsDirty); } - this.innerSerializer.Write(writer, destination, item, serializationContext); + this.innerSerializer.Write(destination, item, serializationContext); if (sharedStringWriter?.IsDirty == true) { - writer.FlushSharedStrings(sharedStringWriter, destination, serializationContext); + sharedStringWriter.FlushWrites(destination, serializationContext); Debug.Assert(!sharedStringWriter.IsDirty); } @@ -206,11 +197,11 @@ item is IFlatBufferDeserializedObject deserializedObj && return serializationContext.Offset; } - int ISerializer.Write(TSpanWriter writer, Span destination, object item) + long ISerializer.Write(BigSpan target, object item) { return item switch { - T t => this.Write(writer, destination, t), + T t => this.Write(target, t), null => FSThrow.ArgumentNull(nameof(item)), _ => FSThrow.Argument($"Argument was not of the correct type. Type = {item.GetType().FullName}, Expected Type = {typeof(T).FullName}") }; diff --git a/src/FlatSharp.Runtime/IFlatBufferDeserializedVector.cs b/src/FlatSharp.Runtime/IFlatBufferDeserializedVector.cs index 9746ebe6..302c9504 100644 --- a/src/FlatSharp.Runtime/IFlatBufferDeserializedVector.cs +++ b/src/FlatSharp.Runtime/IFlatBufferDeserializedVector.cs @@ -40,12 +40,12 @@ public interface IFlatBufferDeserializedVector /// /// Gets the base offset of the vector. /// - int OffsetBase { get; } + long OffsetBase { get; } /// /// Returns the absolute position in the Input Buffer of the given index in the vector. /// - int OffsetOf(int index); + long OffsetOf(int index); /// /// Gets the item at the given index. diff --git a/src/FlatSharp.Runtime/IGeneratedSerializer.cs b/src/FlatSharp.Runtime/IGeneratedSerializer.cs index cb92646e..e4f07c63 100644 --- a/src/FlatSharp.Runtime/IGeneratedSerializer.cs +++ b/src/FlatSharp.Runtime/IGeneratedSerializer.cs @@ -38,17 +38,15 @@ public GeneratedSerializerParseArguments(int offset, short depthLimit) public interface IGeneratedSerializer { /// - /// Writes the given item to the buffer using the given spanwriter. + /// Writes the given item to the given target. /// - /// The span writer. - /// The span to write to. - /// The object to serialize. + /// The target. + /// The item. /// The serialization context. - void Write( - TSpanWriter writer, - Span destination, + void Write( + BigSpan target, T item, - SerializationContext context) where TSpanWriter : ISpanWriter; + SerializationContext context); /// /// Computes the maximum size necessary to serialize the given instance of . diff --git a/src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs b/src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs deleted file mode 100644 index 11692f89..00000000 --- a/src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2018 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Text; - -namespace FlatSharp; - -/// -/// An implementation of for managed arrays. -/// -public struct ArrayInputBuffer : IInputBuffer -{ - private readonly byte[] memory; - - public ArrayInputBuffer(byte[] buffer) - { - this.memory = buffer; - } - - public bool IsPinned => false; - - public bool IsReadOnly => false; - - public int Length => this.memory.Length; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte(int offset) - { - return ScalarSpanReader.ReadByte(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadSByte(int offset) - { - return ScalarSpanReader.ReadSByte(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUShort(int offset) - { - this.CheckAlignment(offset, sizeof(ushort)); - return ScalarSpanReader.ReadUShort(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadShort(int offset) - { - this.CheckAlignment(offset, sizeof(short)); - return ScalarSpanReader.ReadShort(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt(int offset) - { - this.CheckAlignment(offset, sizeof(uint)); - return ScalarSpanReader.ReadUInt(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt(int offset) - { - this.CheckAlignment(offset, sizeof(int)); - return ScalarSpanReader.ReadInt(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadULong(int offset) - { - this.CheckAlignment(offset, sizeof(ulong)); - return ScalarSpanReader.ReadULong(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadLong(int offset) - { - this.CheckAlignment(offset, sizeof(long)); - return ScalarSpanReader.ReadLong(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ReadFloat(int offset) - { - this.CheckAlignment(offset, sizeof(float)); - return ScalarSpanReader.ReadFloat(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double ReadDouble(int offset) - { - this.CheckAlignment(offset, sizeof(double)); - return ScalarSpanReader.ReadDouble(this.memory.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadString(int offset, int byteLength, Encoding encoding) - { - return ScalarSpanReader.ReadString(this.memory.AsSpan().Slice(offset, byteLength), encoding); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan() - { - return this.memory; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan() - { - return this.memory; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory GetMemory() - { - return this.memory; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory GetReadOnlyMemory() - { - return this.memory; - } -} diff --git a/src/FlatSharp.Runtime/IO/ArraySegmentInputBuffer.cs b/src/FlatSharp.Runtime/IO/ArraySegmentInputBuffer.cs deleted file mode 100644 index e3de0e77..00000000 --- a/src/FlatSharp.Runtime/IO/ArraySegmentInputBuffer.cs +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2021 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Text; - -namespace FlatSharp; - -/// -/// An implementation of for array segments. -/// -public struct ArraySegmentInputBuffer : IInputBuffer -{ - private readonly ArraySegmentPointer pointer; - - public ArraySegmentInputBuffer(ArraySegment memory) - { - this.pointer = new ArraySegmentPointer { segment = memory }; - } - - public bool IsPinned => false; - - public bool IsReadOnly => false; - - public int Length => this.pointer.segment.Count; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte(int offset) - { - return ScalarSpanReader.ReadByte(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadSByte(int offset) - { - return ScalarSpanReader.ReadSByte(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUShort(int offset) - { - this.CheckAlignment(offset, sizeof(ushort)); - return ScalarSpanReader.ReadUShort(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadShort(int offset) - { - this.CheckAlignment(offset, sizeof(short)); - return ScalarSpanReader.ReadShort(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt(int offset) - { - this.CheckAlignment(offset, sizeof(uint)); - return ScalarSpanReader.ReadUInt(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt(int offset) - { - this.CheckAlignment(offset, sizeof(int)); - return ScalarSpanReader.ReadInt(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadULong(int offset) - { - this.CheckAlignment(offset, sizeof(ulong)); - return ScalarSpanReader.ReadULong(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadLong(int offset) - { - this.CheckAlignment(offset, sizeof(long)); - return ScalarSpanReader.ReadLong(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ReadFloat(int offset) - { - this.CheckAlignment(offset, sizeof(float)); - return ScalarSpanReader.ReadFloat(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double ReadDouble(int offset) - { - this.CheckAlignment(offset, sizeof(double)); - return ScalarSpanReader.ReadDouble(this.pointer.segment.AsSpan().Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadString(int offset, int byteLength, Encoding encoding) - { - return ScalarSpanReader.ReadString(this.pointer.segment.AsSpan().Slice(offset, byteLength), encoding); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan() - { - return this.pointer.segment; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan() - { - return this.pointer.segment; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory GetMemory() - { - return this.pointer.segment; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory GetReadOnlyMemory() - { - return this.pointer.segment; - } - - // 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 - { - public ArraySegment segment; - } -} diff --git a/src/FlatSharp.Runtime/IO/IInputBuffer.cs b/src/FlatSharp.Runtime/IO/IInputBuffer.cs deleted file mode 100644 index 9563a864..00000000 --- a/src/FlatSharp.Runtime/IO/IInputBuffer.cs +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2020 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Text; - -namespace FlatSharp; - -/// -/// Defines a buffer that FlatSharp can parse from. Implementations will be fastest when using a struct. -/// -public interface IInputBuffer -{ - /// - /// Indicates if this instance is read only. - /// - bool IsReadOnly { get; } - - /// - /// Indicates if this instance represents pinned (non-movable) memory. - /// - bool IsPinned { get; } - - /// - /// Gets the length of this input buffer. - /// - int Length { get; } - - /// - /// Reads the byte at the given offset. - /// - byte ReadByte(int offset); - - /// - /// Reads the sbyte at the given offset. - /// - sbyte ReadSByte(int offset); - - /// - /// Reads the ushort at the given offset. - /// - ushort ReadUShort(int offset); - - /// - /// Reads the short at the given offset. - /// - short ReadShort(int offset); - - /// - /// Reads the uint at the given offset. - /// - uint ReadUInt(int offset); - - /// - /// Reads the int at the given offset. - /// - int ReadInt(int offset); - - /// - /// Reads the ulong at the given offset. - /// - ulong ReadULong(int offset); - - /// - /// Reads the long at the given offset. - /// - long ReadLong(int offset); - - /// - /// Reads the float at the given offset. - /// - float ReadFloat(int offset); - - /// - /// Reads the double at the given offset. - /// - double ReadDouble(int offset); - - /// - /// Reads the string of the given length at the given offset with the given encoding. - /// - string ReadString(int offset, int byteLength, Encoding encoding); - - /// - /// Gets a read only span covering the entire input buffer. - /// - ReadOnlySpan GetReadOnlySpan(); - - /// - /// Gets a read only memory covering the entire input buffer. - /// - ReadOnlyMemory GetReadOnlyMemory(); - - /// - /// Gets a span covering the entire input buffer. - /// - Span GetSpan(); - - /// - /// Gets a memory covering the entire input buffer. - /// - Memory GetMemory(); -} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs index fad0bda2..b562af78 100644 --- a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs @@ -38,16 +38,13 @@ public interface ISharedStringWriter /// Writes the given string to the span. /// /// The spanwriter. - /// The span. /// The location in the buffer of the uoffset to the string. /// The string to write. /// The serialization context. - void WriteSharedString(TSpanWriter spanWriter, Span data, int offset, string value, SerializationContext context) - where TSpanWriter : ISpanWriter; + void WriteSharedString(BigSpan spanWriter, long offset, string value, SerializationContext context); /// /// Flushes any pending writes. Invoked at the end of a serialization operation. /// - void FlushWrites(TSpanWriter writer, Span data, SerializationContext context) - where TSpanWriter : ISpanWriter; + void FlushWrites(BigSpan writer, SerializationContext context); } diff --git a/src/FlatSharp.Runtime/IO/ISpanWriter.cs b/src/FlatSharp.Runtime/IO/ISpanWriter.cs deleted file mode 100644 index beb8a93f..00000000 --- a/src/FlatSharp.Runtime/IO/ISpanWriter.cs +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Text; - -namespace FlatSharp; - -/// -/// Defines a span writer. -/// -public interface ISpanWriter -{ - /// - /// Writes the given byte to the span at the given offset. - /// - void WriteByte(Span span, byte value, int offset); - - /// - /// Writes the given double to the span at the given offset. - /// - void WriteDouble(Span span, double value, int offset); - - /// - /// Writes the given float to the span at the given offset. - /// - void WriteFloat(Span span, float value, int offset); - - /// - /// Writes the given int to the span at the given offset. - /// - void WriteInt(Span span, int value, int offset); - - /// - /// Writes the given long to the span at the given offset. - /// - void WriteLong(Span span, long value, int offset); - - /// - /// Writes the given sbyte to the span at the given offset. - /// - void WriteSByte(Span span, sbyte value, int offset); - - /// - /// Writes the given short to the span at the given offset. - /// - void WriteShort(Span span, short value, int offset); - - /// - /// Writes the given uint to the span at the given offset. - /// - void WriteUInt(Span span, uint value, int offset); - - /// - /// Writes the given ulong to the span at the given offset. - /// - void WriteULong(Span span, ulong value, int offset); - - /// - /// Writes the given ushort to the span at the given offset. - /// - void WriteUShort(Span span, ushort value, int offset); - - /// - /// Writes the bytes of the given string to the destination span according to the given encoding. - /// - int GetStringBytes(Span destination, string value, Encoding encoding); - - /// - /// Invokes the method. - /// - void FlushSharedStrings( - ISharedStringWriter writer, - Span destination, - SerializationContext context); -} diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs new file mode 100644 index 00000000..8ba548db --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs @@ -0,0 +1,65 @@ +/* + * Copyright 2018 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; + +namespace FlatSharp; + +/// +/// An implementation of for managed arrays. +/// +public struct ArrayInputBuffer : IInputBuffer +{ + private readonly byte[] memory; + + public ArrayInputBuffer(byte[] buffer) + { + this.memory = buffer; + } + + public bool IsPinned => false; + + public bool IsReadOnly => false; + + public long Length => this.memory.Length; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigReadOnlySpan GetReadOnlySpan() + { + return new(this.GetSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan GetSpan() + { + return new(this.memory.AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory GetMemory(long offset, int length) + { + checked + { + return this.memory.AsMemory().Slice((int)offset, length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetReadOnlyMemory(long offset, int length) + { + return this.GetMemory(offset, length); + } +} diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs new file mode 100644 index 00000000..430ec745 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs @@ -0,0 +1,72 @@ +/* + * Copyright 2021 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Text; + +namespace FlatSharp; + +/// +/// An implementation of for array segments. +/// +public struct ArraySegmentInputBuffer : IInputBuffer +{ + private readonly ArraySegmentPointer pointer; + + public ArraySegmentInputBuffer(ArraySegment memory) + { + this.pointer = new ArraySegmentPointer { segment = memory }; + } + + public bool IsPinned => false; + + public bool IsReadOnly => false; + + public long Length => this.pointer.segment.Count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigReadOnlySpan GetReadOnlySpan() + { + return new(this.GetSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan GetSpan() + { + return new(this.pointer.segment.AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory GetMemory(long offset, int length) + { + checked + { + return this.pointer.segment.AsMemory((int)offset, length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetReadOnlyMemory(long offset, int length) + { + return this.GetMemory(offset, length); + } + + // 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 + { + public ArraySegment segment; + } +} diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs new file mode 100644 index 00000000..852033cd --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs @@ -0,0 +1,58 @@ +/* + * Copyright 2020 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace FlatSharp; + +/// +/// Defines a buffer that FlatSharp can parse from. Implementations will be fastest when using a struct. +/// +public interface IInputBuffer +{ + /// + /// Indicates if this instance is read only. + /// + bool IsReadOnly { get; } + + /// + /// Indicates if this instance represents pinned (non-movable) memory. + /// + bool IsPinned { get; } + + /// + /// Gets the length of this input buffer. + /// + long Length { get; } + + /// + /// Gets a read only span covering the entire input buffer. + /// + BigReadOnlySpan GetReadOnlySpan(); + + /// + /// Gets a read only memory covering the entire input buffer. + /// + ReadOnlyMemory GetReadOnlyMemory(long offset, int length); + + /// + /// Gets a span covering the entire input buffer. + /// + BigSpan GetSpan(); + + /// + /// Gets a memory covering the entire input buffer. + /// + Memory GetMemory(long offset, int length); +} diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs new file mode 100644 index 00000000..198b7b9b --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs @@ -0,0 +1,265 @@ +/* + * Copyright 2020 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Buffers; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.InteropServices; + +namespace FlatSharp.Internal; + +/// +/// Extensions for input buffers. +/// +public static class InputBufferExtensions +{ + /// + /// Reads a bool. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ReadBool(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.ReadByte(offset) != SerializationHelpers.False; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ReadByte(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan()[offset]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte ReadSByte(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadSByte(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadUShort(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadUShort(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadShort(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadShort(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadUInt(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadUInt(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadInt(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadInt(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadULong(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadULong(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ReadLong(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadLong(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ReadFloat(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadFloat(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ReadDouble(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer + { + return buffer.GetReadOnlySpan().ReadDouble(offset); + } + + /// + /// Reads a string at the given offset. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReadString(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + checked + { + // Strings are stored by reference. + offset += buffer.ReadUOffset(offset); + return buffer.ReadStringFromUOffset(offset); + } + } + + /// + /// Reads a string from the given uoffset. + /// + public static string ReadStringFromUOffset(this TBuffer buffer, long stringStart) + where TBuffer : IInputBuffer + { + int numberOfBytes = (int)buffer.ReadUInt(stringStart); + ReadOnlySpan stringValue = buffer.GetReadOnlySpan().ToSpan(stringStart + sizeof(int), numberOfBytes); + +#if NETSTANDARD2_0 + byte[] temp = ArrayPool.Shared.Rent(numberOfBytes); + stringValue.CopyTo(temp); + string result = SerializationHelpers.Encoding.GetString(temp, 0, numberOfBytes); + ArrayPool.Shared.Return(temp); + return result; +#else + return SerializationHelpers.Encoding.GetString(stringValue); +#endif + } + + /// + /// Reads the given uoffset. + /// + public static int ReadUOffset(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + int uoffset = buffer.ReadInt(offset); + if (uoffset < sizeof(uint)) + { + FSThrow.InvalidData_UOffsetTooSmall((uint)uoffset); + } + + return uoffset; + } + + /// + /// Validates a vtable and reads the initial bytes of a vtable. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitializeVTable( + this TBuffer buffer, + long tableOffset, + out long vtableOffset, + out ulong vtableFieldCount, + out ReadOnlySpan fieldData) where TBuffer : IInputBuffer + { + BigReadOnlySpan span = buffer.GetReadOnlySpan(); + vtableOffset = tableOffset - span.ReadInt(tableOffset); + + ushort vtableLength = span.ReadUShort(vtableOffset); + if (vtableLength < 4) + { + FSThrow.InvalidData_VTableTooShort(); + } + + fieldData = span.ToSpan(vtableOffset, vtableLength).Slice(4); + vtableFieldCount = (ulong)(fieldData.Length / 2); + } + + // Seems to break JIT in .NET Core 2.1. Framework 4.7 and Core 3.1 work as expected. + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory ReadByteMemoryBlock(this TBuffer buffer, long uoffset) + where TBuffer : IInputBuffer + { + // The local value stores a uoffset_t, so follow that now. + uoffset += buffer.ReadUOffset(uoffset); + return buffer.GetMemory(uoffset + sizeof(int), buffer.ReadInt(uoffset)); + } + + // Seems to break JIT in .NET Core 2.1. Framework 4.7 and Core 3.1 work as expected. + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory ReadByteReadOnlyMemoryBlock(this TBuffer buffer, long uoffset) + where TBuffer : IInputBuffer + { + // The local value stores a uoffset_t, so follow that now. + uoffset += buffer.ReadUOffset(uoffset); + return buffer.GetReadOnlyMemory(uoffset + sizeof(uint), buffer.ReadInt(uoffset)); + } + + /// + /// Reads a sequence of TElement items from the buffer at the given offset using the equivalent of reinterpret_cast. + /// + public static Span UnsafeReadSpan(this TBuffer buffer, long uoffset) + where TBuffer : IInputBuffer + where TElement : unmanaged + { + // The local value stores a uoffset_t, so follow that now. + uoffset = uoffset + buffer.ReadUOffset(uoffset); + + // We need to construct a Span from byte buffer that: + // 1. starts at correct offset for vector data + // 2. has a length based on *TElement* count not *byte* count + var byteSpanAtDataOffset = buffer + .GetSpan() + .ToSpan( + uoffset + sizeof(uint), + checked(Unsafe.SizeOf() * (int)buffer.ReadUInt(uoffset))); + + var sourceSpan = MemoryMarshal.Cast(byteSpanAtDataOffset); + + return sourceSpan; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static long CopyTo(this TBuffer buffer, BigSpan target) + where TBuffer : IInputBuffer + { + if (target.Length < buffer.Length) + { + FSThrow.BufferTooSmall(buffer.Length); + return 0; + } + + long offset = 0; + while (offset < buffer.Length) + { + long remaining = buffer.Length - offset; + var chunk = buffer.GetReadOnlySpan().ToSpan(offset, (int)Math.Min(int.MaxValue, remaining)); + chunk.CopyTo(target.ToSpan(offset, chunk.Length)); + + offset += chunk.Length; + } + + return offset; + } + + [ExcludeFromCodeCoverage] // Not currently used. + [Conditional("DEBUG")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckAlignment(this TBuffer buffer, long offset, int size) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { +#if DEBUG + if (offset % size != 0) + { + FSThrow.InvalidOperation( + $"BugCheck: attempted to read unaligned data at index: {offset}, expected alignment: {size}"); + } +#endif + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs new file mode 100644 index 00000000..d69168e3 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs @@ -0,0 +1,76 @@ +/* + * Copyright 2018 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace FlatSharp; + +/// +/// An implementation of InputBuffer for writable memory segments. +/// +public readonly struct MemoryInputBuffer : IInputBuffer +{ + private readonly MemoryPointer pointer; + + public MemoryInputBuffer(Memory memory, bool isPinned = false) + { + this.pointer = new MemoryPointer { memory = memory, isPinned = isPinned }; + } + + public bool IsPinned => this.pointer.isPinned; + + public bool IsReadOnly => false; + + public long Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.pointer.memory.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigReadOnlySpan GetReadOnlySpan() + { + return new(this.GetSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan GetSpan() + { + return new(this.pointer.memory.Span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetReadOnlyMemory(long offset, int length) + { + return this.GetMemory(offset, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory GetMemory(long offset, int length) + { + checked + { + return this.pointer.memory.Slice((int)offset, length); + } + } + + // 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. + private class MemoryPointer + { + public Memory memory; + public bool isPinned; + } +} diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs new file mode 100644 index 00000000..7e9d03bf --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs @@ -0,0 +1,85 @@ +/* + * Copyright 2020 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Runtime.InteropServices; +using System.Text; + +namespace FlatSharp; + +/// +/// An implemenation of InputBuffer that accepts ReadOnlyMemory. ReadOnlyMemoryInputBuffer +/// behaves identically to MemoryInputBuffer with one exception, which is that it will refuse +/// to deserialize any mutable memory (Memory{T}) instances. These will result in an exception +/// being thrown. ReadOnlyMemoryInputBuffer guarantees that the objects returned will +/// not modify in the input buffer (unless unsafe operations / MemoryMarshal) are used. +/// +public struct ReadOnlyMemoryInputBuffer : IInputBuffer +{ + private const string ErrorMessage = "ReadOnlyMemory inputs may not deserialize writable memory."; + + private readonly MemoryPointer pointer; + + public ReadOnlyMemoryInputBuffer(ReadOnlyMemory memory, bool isPinned = false) + { + this.pointer = new MemoryPointer { memory = memory, isPinned = isPinned }; + } + + public bool IsPinned => this.pointer.isPinned; + + public bool IsReadOnly => true; + + public long Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.pointer.memory.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigReadOnlySpan GetReadOnlySpan() + { + var rwMemory = MemoryMarshal.AsMemory(this.pointer.memory); + return new(new BigSpan(rwMemory.Span)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetReadOnlyMemory(long offset, int length) + { + checked + { + return this.pointer.memory.Slice((int)offset, length); + } + } + + public BigSpan GetSpan() + { + FSThrow.InvalidOperation(ErrorMessage); + return default; + } + + public Memory GetMemory(long offset, int length) + { + return FSThrow.InvalidOperation>(ErrorMessage); + } + + // Memory is a relatively heavy struct. It's cheaper to wrap it in a + // a reference that will be collected ephemerally in Gen0 than is is to + // copy it around. + private class MemoryPointer + { + public ReadOnlyMemory memory; + public bool isPinned; + } +} diff --git a/src/FlatSharp.Runtime/IO/InputBufferExtensions.cs b/src/FlatSharp.Runtime/IO/InputBufferExtensions.cs deleted file mode 100644 index 29f365fe..00000000 --- a/src/FlatSharp.Runtime/IO/InputBufferExtensions.cs +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2020 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.IO; -using System.Runtime.InteropServices; - -namespace FlatSharp.Internal; - -/// -/// Extensions for input buffers. -/// -public static class InputBufferExtensions -{ - /// - /// Reads a bool. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ReadBool(this TBuffer buffer, int offset) where TBuffer : IInputBuffer - { - return buffer.ReadByte(offset) != SerializationHelpers.False; - } - - /// - /// Reads a string at the given offset. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ReadString(this TBuffer buffer, int offset) where TBuffer : IInputBuffer - { - checked - { - // Strings are stored by reference. - offset += buffer.ReadUOffset(offset); - return buffer.ReadStringFromUOffset(offset); - } - } - - /// - /// Reads a string from the given uoffset. - /// - public static string ReadStringFromUOffset(this TBuffer buffer, int uoffset) where TBuffer : IInputBuffer - { - int numberOfBytes = (int)buffer.ReadUInt(uoffset); - return buffer.ReadString(uoffset + sizeof(int), numberOfBytes, SerializationHelpers.Encoding); - } - - /// - /// Reads the given uoffset. - /// - public static int ReadUOffset(this TBuffer buffer, int offset) where TBuffer : IInputBuffer - { - int uoffset = buffer.ReadInt(offset); - if (uoffset < sizeof(uint)) - { - FSThrow.InvalidData_UOffsetTooSmall((uint)uoffset); - } - - return uoffset; - } - - /// - /// Validates a vtable and reads the initial bytes of a vtable. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeVTable( - this TBuffer buffer, - int tableOffset, - out int vtableOffset, - out nuint vtableFieldCount, - out ReadOnlySpan fieldData) where TBuffer : IInputBuffer - { - vtableOffset = tableOffset - buffer.ReadInt(tableOffset); - ushort vtableLength = buffer.ReadUShort(vtableOffset); - - if (vtableLength < 4) - { - FSThrow.InvalidData_VTableTooShort(); - } - - fieldData = buffer.GetReadOnlySpan().Slice(vtableOffset, vtableLength).Slice(4); - vtableFieldCount = (nuint)fieldData.Length / 2; - } - - // Seems to break JIT in .NET Core 2.1. Framework 4.7 and Core 3.1 work as expected. - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory ReadByteMemoryBlock(this TBuffer buffer, int uoffset) where TBuffer : IInputBuffer - { - // The local value stores a uoffset_t, so follow that now. - uoffset = uoffset + buffer.ReadUOffset(uoffset); - return buffer.GetMemory().Slice(uoffset + sizeof(uint), (int)buffer.ReadUInt(uoffset)); - } - - // Seems to break JIT in .NET Core 2.1. Framework 4.7 and Core 3.1 work as expected. - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlyMemory ReadByteReadOnlyMemoryBlock(this TBuffer buffer, int uoffset) where TBuffer : IInputBuffer - { - // The local value stores a uoffset_t, so follow that now. - uoffset = uoffset + buffer.ReadUOffset(uoffset); - return buffer.GetReadOnlyMemory().Slice(uoffset + sizeof(uint), (int)buffer.ReadUInt(uoffset)); - } - - /// - /// Reads a sequence of TElement items from the buffer at the given offset using the equivalent of reinterpret_cast. - /// - public static Span UnsafeReadSpan(this TBuffer buffer, int uoffset) where TBuffer : IInputBuffer where TElement : struct - { - // The local value stores a uoffset_t, so follow that now. - uoffset = uoffset + buffer.ReadUOffset(uoffset); - - // We need to construct a Span from byte buffer that: - // 1. starts at correct offset for vector data - // 2. has a length based on *TElement* count not *byte* count - var byteSpanAtDataOffset = buffer.GetSpan().Slice(uoffset + sizeof(uint)); - var sourceSpan = MemoryMarshal.Cast(byteSpanAtDataOffset).Slice(0, (int)buffer.ReadUInt(uoffset)); - - return sourceSpan; - } - - [ExcludeFromCodeCoverage] // Not currently used. - [Conditional("DEBUG")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CheckAlignment(this TBuffer buffer, int offset, int size) where TBuffer : IInputBuffer - { -#if DEBUG - if (offset % size != 0) - { - FSThrow.InvalidOperation($"BugCheck: attempted to read unaligned data at index: {offset}, expected alignment: {size}"); - } -#endif - } -} diff --git a/src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs deleted file mode 100644 index dedeb7ee..00000000 --- a/src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2018 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Text; - -namespace FlatSharp; - -/// -/// An implementation of InputBuffer for writable memory segments. -/// -public struct MemoryInputBuffer : IInputBuffer -{ - private readonly MemoryPointer pointer; - - public MemoryInputBuffer(Memory memory, bool isPinned = false) - { - this.pointer = new MemoryPointer { memory = memory, isPinned = isPinned }; - } - - public bool IsPinned => this.pointer.isPinned; - - public bool IsReadOnly => false; - - public int Length - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.pointer.memory.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte(int offset) - { - return ScalarSpanReader.ReadByte(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadSByte(int offset) - { - return ScalarSpanReader.ReadSByte(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUShort(int offset) - { - this.CheckAlignment(offset, sizeof(ushort)); - return ScalarSpanReader.ReadUShort(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadShort(int offset) - { - this.CheckAlignment(offset, sizeof(short)); - return ScalarSpanReader.ReadShort(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt(int offset) - { - this.CheckAlignment(offset, sizeof(uint)); - return ScalarSpanReader.ReadUInt(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt(int offset) - { - this.CheckAlignment(offset, sizeof(int)); - return ScalarSpanReader.ReadInt(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadULong(int offset) - { - this.CheckAlignment(offset, sizeof(ulong)); - return ScalarSpanReader.ReadULong(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadLong(int offset) - { - this.CheckAlignment(offset, sizeof(long)); - return ScalarSpanReader.ReadLong(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ReadFloat(int offset) - { - this.CheckAlignment(offset, sizeof(float)); - return ScalarSpanReader.ReadFloat(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double ReadDouble(int offset) - { - this.CheckAlignment(offset, sizeof(double)); - return ScalarSpanReader.ReadDouble(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadString(int offset, int byteLength, Encoding encoding) - { - return ScalarSpanReader.ReadString(this.pointer.memory.Span.Slice(offset, byteLength), encoding); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan() - { - return this.pointer.memory.Span; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan() - { - return this.pointer.memory.Span; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory GetReadOnlyMemory() - { - return this.pointer.memory; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory GetMemory() - { - return this.pointer.memory; - } - - // 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. - private class MemoryPointer - { - public Memory memory; - public bool isPinned; - } -} diff --git a/src/FlatSharp.Runtime/IO/ReadOnlyMemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/ReadOnlyMemoryInputBuffer.cs deleted file mode 100644 index 84df4e78..00000000 --- a/src/FlatSharp.Runtime/IO/ReadOnlyMemoryInputBuffer.cs +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2020 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Text; - -namespace FlatSharp; - -/// -/// An implemenation of InputBuffer that accepts ReadOnlyMemory. ReadOnlyMemoryInputBuffer -/// behaves identically to MemoryInputBuffer with one exception, which is that it will refuse -/// to deserialize any mutable memory (Memory{T}) instances. These will result in an exception -/// being thrown. ReadOnlyMemoryInputBuffer guarantees that the objects returned will -/// not modify in the input buffer (unless unsafe operations / MemoryMarshal) are used. -/// -public struct ReadOnlyMemoryInputBuffer : IInputBuffer -{ - private const string ErrorMessage = "ReadOnlyMemory inputs may not deserialize writable memory."; - - private readonly MemoryPointer pointer; - - public ReadOnlyMemoryInputBuffer(ReadOnlyMemory memory, bool isPinned = false) - { - this.pointer = new MemoryPointer { memory = memory, isPinned = isPinned }; - } - - public bool IsPinned => this.pointer.isPinned; - - public bool IsReadOnly => true; - - public int Length - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.pointer.memory.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte(int offset) - { - return ScalarSpanReader.ReadByte(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadSByte(int offset) - { - return ScalarSpanReader.ReadSByte(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUShort(int offset) - { - this.CheckAlignment(offset, sizeof(ushort)); - return ScalarSpanReader.ReadUShort(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadShort(int offset) - { - this.CheckAlignment(offset, sizeof(short)); - return ScalarSpanReader.ReadShort(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt(int offset) - { - this.CheckAlignment(offset, sizeof(uint)); - return ScalarSpanReader.ReadUInt(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt(int offset) - { - this.CheckAlignment(offset, sizeof(int)); - return ScalarSpanReader.ReadInt(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadULong(int offset) - { - this.CheckAlignment(offset, sizeof(ulong)); - return ScalarSpanReader.ReadULong(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadLong(int offset) - { - this.CheckAlignment(offset, sizeof(long)); - return ScalarSpanReader.ReadLong(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ReadFloat(int offset) - { - this.CheckAlignment(offset, sizeof(float)); - return ScalarSpanReader.ReadFloat(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double ReadDouble(int offset) - { - this.CheckAlignment(offset, sizeof(double)); - return ScalarSpanReader.ReadDouble(this.pointer.memory.Span.Slice(offset)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadString(int offset, int byteLength, Encoding encoding) - { - return ScalarSpanReader.ReadString(this.pointer.memory.Span.Slice(offset, byteLength), encoding); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan() - { - return this.pointer.memory.Span; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory GetReadOnlyMemory() - { - return this.pointer.memory; - } - - public Span GetSpan() - { - FSThrow.InvalidOperation(ErrorMessage); - return default; - } - - public Memory GetMemory() - { - return FSThrow.InvalidOperation>(ErrorMessage); - } - - // Memory is a relatively heavy struct. It's cheaper to wrap it in a - // a reference that will be collected ephemerally in Gen0 than is is to - // copy it around. - private class MemoryPointer - { - public ReadOnlyMemory memory; - public bool isPinned; - } -} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs new file mode 100644 index 00000000..f29b36c4 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs @@ -0,0 +1,77 @@ +/* + * Copyright 2024 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace FlatSharp; + +using System.Buffers; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + + +public readonly ref partial struct BigReadOnlySpan +{ + private readonly BigSpan span; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigReadOnlySpan(BigSpan span) + { + this.span = span; + } + + internal BigSpan Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.span; + } + + public long Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.span.Length; + } + + public readonly ref byte this[long index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this.span[index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigReadOnlySpan Slice(long start, long length) + { + return new(this.span.Slice(start, length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigReadOnlySpan Slice(long start) + { + return new(this.span.Slice(start)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan ToSpan(long start, int length) + { + return this.span.ToSpan(start, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBool(long offset) => this.span.ReadBool(offset); + + private static void ThrowOutOfRange() + { + throw new IndexOutOfRangeException(); + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs new file mode 100644 index 00000000..1f5a0ba1 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs @@ -0,0 +1,343 @@ +/* + * Copyright 2024 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace FlatSharp; + +using System.Buffers; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +public readonly ref partial struct BigSpan +{ +#if NET7_0_OR_GREATER + private readonly long length; + private readonly ref byte value; +#else + private readonly Span span; +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan(Span span) + { +#if NET7_0_OR_GREATER + this.length = span.Length; + this.value = ref span[0]; +#else + this.span = span; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private BigSpan(ref byte value, long length) + { +#if NET7_0_OR_GREATER + this.value = ref value; + this.length = length; +#else + unsafe + { + fixed (byte* pByte = &value) + { + this.span = new(pByte, checked((int)length)); + } + } +#endif + } + + public long Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET7_0_OR_GREATER + get => this.length; +#else + get => this.span.Length; +#endif + } + + public ref byte this[long index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((ulong)index >= (ulong)this.Length) + { + ThrowOutOfRange(); + } + +#if NET7_0_OR_GREATER + return ref Unsafe.Add(ref this.value, (IntPtr)index); +#else + return ref this.span[(int)index]; +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan Slice(long start, long length) + { + bool isOutOfRange = (length | start) < 0; + isOutOfRange |= (ulong)(start + length) > (ulong)this.Length; + + if (isOutOfRange) + { + ThrowOutOfRange(); + } + +#if NET7_0_OR_GREATER + return new BigSpan(ref Unsafe.Add(ref this.value, (IntPtr)start), length); +#else + return new(this.span.Slice((int)start, (int)length)); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan Slice(long start) + { + if ((ulong)start > (ulong)this.Length) + { + ThrowOutOfRange(); + } + +#if NET7_0_OR_GREATER + return new BigSpan(ref Unsafe.Add(ref this.value, (IntPtr)start), this.Length - start); +#else + return new(this.span.Slice((int)start)); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span ToSpan(long start, int length) + { + this.CheckRange(start, length); + +#if NET7_0_OR_GREATER + return MemoryMarshal.CreateSpan( + ref Unsafe.Add(ref this.value, (IntPtr)start), + length); +#else + return this.span.Slice((int)start, length); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBool(long offset, bool value) + { + this[offset] = value ? SerializationHelpers.True : SerializationHelpers.False; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBool(long offset) => this[offset] != SerializationHelpers.False; + + public void WriteReadOnlyByteMemoryBlock( + ReadOnlyMemory memory, + long offset, + SerializationContext ctx) + { + int numberOfItems = memory.Length; + long vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); + + this.WriteUOffset(offset, vectorStartOffset); + this.WriteInt(vectorStartOffset, numberOfItems); + + memory.Span.CopyTo(this.ToSpan(vectorStartOffset + sizeof(uint), numberOfItems)); + } + + public void UnsafeWriteSpan( + Span buffer, + long offset, + int alignment, + SerializationContext ctx) + where TElement : unmanaged + { + // Since we are copying bytes here, only LE is supported. + FlatSharpInternal.AssertLittleEndian(); + FlatSharpInternal.AssertWellAligned(alignment); + + int numberOfItems = buffer.Length; + long vectorStartOffset = ctx.AllocateVector( + itemAlignment: alignment, + numberOfItems, + sizePerItem: Unsafe.SizeOf()); + + this.WriteUOffset(offset, vectorStartOffset); + this.WriteInt(vectorStartOffset, numberOfItems); + + Span destination = this.ToSpan( + vectorStartOffset + sizeof(uint), + checked(numberOfItems * Unsafe.SizeOf())); + + MemoryMarshal.Cast(buffer).CopyTo(destination); + } + + /// + /// Writes the given string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteString( + string value, + long offset, + SerializationContext context) + { + long stringOffset = this.WriteAndProvisionString(value, context); + this.WriteUOffset(offset, stringOffset); + } + + /// + /// Writes the string to the buffer, returning the absolute offset of the string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long WriteAndProvisionString( + string value, + SerializationContext context) + { + var encoding = SerializationHelpers.Encoding; + + // Allocate more than we need and then give back what we don't use. + int maxItems = encoding.GetMaxByteCount(value.Length) + 1; + long stringStartOffset = context.AllocateVector(sizeof(byte), maxItems, sizeof(byte)); + + BigSpan scopedThis = this.Slice(stringStartOffset, maxItems + sizeof(uint)); + Span destination = scopedThis.ToSpan(sizeof(uint), maxItems); + +#if NETSTANDARD2_0 + int length = value.Length; + byte[] buffer = ArrayPool.Shared.Rent(encoding.GetMaxByteCount(length)); + int bytesWritten = encoding.GetBytes(value, 0, length, buffer, 0); + buffer.AsSpan().Slice(0, bytesWritten).CopyTo(destination); + ArrayPool.Shared.Return(buffer); +#else + int bytesWritten = encoding.GetBytes(value, destination); +#endif + + // null teriminator + scopedThis.UnsafeWriteByte(sizeof(uint) + bytesWritten, 0); + + // write length + scopedThis.UnsafeWriteInt(0, bytesWritten); + + // give back unused space. Account for null terminator. + context.Offset -= maxItems - (bytesWritten + 1); + + return stringStartOffset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUOffset( + long offset, + long secondOffset) + { + long difference = secondOffset - offset; + uint uoffset = checked((uint)difference); + this.WriteUInt(offset, uoffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private T ReadUnaligned(long offset) + where T : unmanaged + { + this.CheckRange(offset, Unsafe.SizeOf()); + return this.ReadUnalignedUnsafe(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteUnaligned(long offset, T value) + where T : unmanaged + { + this.CheckRange(offset, Unsafe.SizeOf()); + this.WriteUnalignedUnsafe(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal T ReadUnalignedUnsafe(long offset) + where T : unmanaged + { +#if DEBUG + CheckAlignment(offset, Unsafe.SizeOf()); + CheckRange(offset, Unsafe.SizeOf()); +#endif + +#if NET7_0_OR_GREATER + return Unsafe.ReadUnaligned(ref Unsafe.Add(ref this.value, (IntPtr)offset)); +#else + var slice = this.ToSpan(offset, Unsafe.SizeOf()); + return Unsafe.ReadUnaligned(ref slice[0]); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteUnalignedUnsafe(long offset, T value) + where T : unmanaged + { +#if DEBUG + CheckAlignment(offset, Unsafe.SizeOf()); + CheckRange(offset, Unsafe.SizeOf()); +#endif + +#if NET7_0_OR_GREATER + Unsafe.WriteUnaligned(ref Unsafe.Add(ref this.value, (IntPtr)offset), value); +#else + var slice = this.ToSpan(offset, Unsafe.SizeOf()); + Unsafe.WriteUnaligned(ref slice[0], value); +#endif + } + + internal static double ReverseEndianness(double value) + { + long longValue = Unsafe.As(ref value); + longValue = BinaryPrimitives.ReverseEndianness(longValue); + return BitConverter.Int64BitsToDouble(longValue); + } + + internal static float ReverseEndianness(float value) + { + uint intValue = Unsafe.As(ref value); + intValue = BinaryPrimitives.ReverseEndianness(intValue); + + ScalarSpanReader.FloatLayout f = new() + { + bytes = intValue + }; + + return f.value; + } + + private static void ThrowOutOfRange() + { + throw new IndexOutOfRangeException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CheckRange(long start, int length) + { + long sum = start + (long)length; + if ((ulong)sum > (ulong)this.Length) + { + ThrowOutOfRange(); + } + } + + [ExcludeFromCodeCoverage] + [Conditional("DEBUG")] + private static void CheckAlignment(long offset, int size) + { +#if DEBUG + if (offset % size != 0) + { + FSThrow.InvalidOperation($"BugCheck: attempted to read unaligned data at index: {offset}, expected alignment: {size}"); + } +#endif + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanExtensions.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanExtensions.cs new file mode 100644 index 00000000..f42438ae --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanExtensions.cs @@ -0,0 +1,42 @@ +/* + * Copyright 2024 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Buffers; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace FlatSharp.Internal; + +public static partial class BigSpanExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool UnsafeReadBool(this BigSpan span, long offset) + { + return span.UnsafeReadByte(offset) != SerializationHelpers.False; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool UnsafeReadBool(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadBool(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteBool(this BigSpan span, long offset, bool value) + { + span.WriteByte(offset, value ? SerializationHelpers.True : SerializationHelpers.False); + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.cs new file mode 100644 index 00000000..04086fb0 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.cs @@ -0,0 +1,610 @@ +/* + * Copyright 2024 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +using System.Buffers.Binary; + +namespace FlatSharp +{ + + public readonly ref partial struct BigSpan + { + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteByte(long offset, byte value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadSByte(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSByte(long offset, sbyte value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUShort(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUShort(long offset, ushort value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadShort(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteShort(long offset, short value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt(long offset, int value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt(long offset, uint value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadLong(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLong(long offset, long value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadULong(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteULong(long offset, ulong value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float ReadFloat(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFloat(long offset, float value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double ReadDouble(long offset) + { + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteDouble(long offset, double value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + } + + public readonly ref partial struct BigReadOnlySpan + { + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte(long offset) => this.span.ReadByte(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadSByte(long offset) => this.span.ReadSByte(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUShort(long offset) => this.span.ReadUShort(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadShort(long offset) => this.span.ReadShort(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt(long offset) => this.span.ReadInt(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt(long offset) => this.span.ReadUInt(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadLong(long offset) => this.span.ReadLong(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadULong(long offset) => this.span.ReadULong(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float ReadFloat(long offset) => this.span.ReadFloat(offset); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double ReadDouble(long offset) => this.span.ReadDouble(offset); + } +} + +namespace FlatSharp.Internal +{ + public static partial class BigSpanExtensions + { + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte UnsafeReadByte(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteByte(this BigSpan span, long offset, byte value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte UnsafeReadByte(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadByte(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte UnsafeReadSByte(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteSByte(this BigSpan span, long offset, sbyte value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte UnsafeReadSByte(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadSByte(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort UnsafeReadUShort(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteUShort(this BigSpan span, long offset, ushort value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort UnsafeReadUShort(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadUShort(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short UnsafeReadShort(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteShort(this BigSpan span, long offset, short value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short UnsafeReadShort(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadShort(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int UnsafeReadInt(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteInt(this BigSpan span, long offset, int value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int UnsafeReadInt(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadInt(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint UnsafeReadUInt(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteUInt(this BigSpan span, long offset, uint value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint UnsafeReadUInt(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadUInt(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long UnsafeReadLong(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteLong(this BigSpan span, long offset, long value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long UnsafeReadLong(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadLong(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong UnsafeReadULong(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteULong(this BigSpan span, long offset, ulong value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong UnsafeReadULong(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadULong(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UnsafeReadFloat(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BigSpan.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteFloat(this BigSpan span, long offset, float value) + { + if (!BitConverter.IsLittleEndian) + { + value = BigSpan.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UnsafeReadFloat(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadFloat(offset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double UnsafeReadDouble(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BigSpan.ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteDouble(this BigSpan span, long offset, double value) + { + if (!BitConverter.IsLittleEndian) + { + value = BigSpan.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double UnsafeReadDouble(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadDouble(offset); + } + + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt new file mode 100644 index 00000000..26a8bc5f --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt @@ -0,0 +1,149 @@ +/* + * Copyright 2024 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> + +<# + (string casedName, string typeName, bool useBinaryPrimitives)[] types = + { + ("Byte", "byte", true), + ("SByte", "sbyte", true), + ("UShort", "ushort", true), + ("Short", "short", true), + ("Int", "int", true), + ("UInt", "uint", true), + ("Long", "long", true), + ("ULong", "ulong", true), + ("Float", "float", false), + ("Double", "double", false), + }; +#> + +using System.Buffers.Binary; + +namespace FlatSharp +{ + + public readonly ref partial struct BigSpan + { + + <# + foreach (var tuple in types) + { + string casedName = tuple.casedName; + string typeName = tuple.typeName; + string bp = tuple.useBinaryPrimitives ? "BinaryPrimitives." : ""; + #> + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public <#= typeName #> Read<#=casedName#>(long offset) + { + var value = this.ReadUnaligned<<#=typeName#>>(offset); + if (!BitConverter.IsLittleEndian) + { + value = <#= bp #>ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write<#=casedName#>(long offset, <#= typeName #> value) + { + if (!BitConverter.IsLittleEndian) + { + value = <#=bp#>ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } + <# + } + #> + } + + public readonly ref partial struct BigReadOnlySpan + { + + <# + foreach (var tuple in types) + { + string casedName = tuple.casedName; + string typeName = tuple.typeName; + string bp = tuple.useBinaryPrimitives ? "BinaryPrimitives." : ""; + #> + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public <#= typeName #> Read<#=casedName#>(long offset) => this.span.Read<#=casedName#>(offset); + <# + } + #> + } +} + +namespace FlatSharp.Internal +{ + public static partial class BigSpanExtensions + { + <# + foreach (var tuple in types) + { + string casedName = tuple.casedName; + string typeName = tuple.typeName; + string bp = tuple.useBinaryPrimitives ? "BinaryPrimitives." : "BigSpan."; + #> + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static <#=typeName#> UnsafeRead<#=casedName#>(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe<<#=typeName#>>(offset); + if (!BitConverter.IsLittleEndian) + { + value = <#=bp#>ReverseEndianness(value); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWrite<#=casedName#>(this BigSpan span, long offset, <#=typeName#> value) + { + if (!BitConverter.IsLittleEndian) + { + value = <#=bp#>ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe<<#=typeName#>>(offset, value); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static <#=typeName#> UnsafeRead<#=casedName#>(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeRead<#=casedName#>(offset); + } + + <# + } + #> + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs index 03f326a4..58636755 100644 --- a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs @@ -61,7 +61,7 @@ public void Reset() if (entry.Offsets == null) { - entry.Offsets = new List(); + entry.Offsets = new List(); } entry.Offsets.Clear(); @@ -73,12 +73,11 @@ public void Reset() /// /// Writes a shared string. /// - public void WriteSharedString( - TSpanWriter spanWriter, - Span data, - int offset, + public void WriteSharedString( + BigSpan target, + long offset, string value, - SerializationContext context) where TSpanWriter : ISpanWriter + SerializationContext context) { // Find the associative set that must contain our key. var cache = this.sharedStringOffsetCache; @@ -95,7 +94,7 @@ public void WriteSharedString( string? sharedString = line.String; if (sharedString is not null) { - FlushSharedString(spanWriter, data, sharedString, offsets, context); + FlushSharedString(target, sharedString, offsets, context); } line.String = value; @@ -107,7 +106,9 @@ public void WriteSharedString( /// /// Flush any pending writes. /// - public void FlushWrites(TSpanWriter writer, Span data, SerializationContext context) where TSpanWriter : ISpanWriter + public void FlushWrites( + BigSpan target, + SerializationContext context) { var cache = this.sharedStringOffsetCache; for (int i = 0; i < cache.Length; ++i) @@ -117,7 +118,7 @@ public void FlushWrites(TSpanWriter writer, Span data, Serial if (str is not null) { - FlushSharedString(writer, data, str, item.Offsets, context); + FlushSharedString(target, str, item.Offsets, context); item.String = null; } @@ -128,18 +129,17 @@ public void FlushWrites(TSpanWriter writer, Span data, Serial } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void FlushSharedString( - TSpanWriter spanWriter, - Span span, + private static void FlushSharedString( + BigSpan target, string value, - List offsets, - SerializationContext context) where TSpanWriter : ISpanWriter + List offsets, + SerializationContext context) { - int stringOffset = spanWriter.WriteAndProvisionString(span, value, context); + long stringOffset = target.WriteAndProvisionString(value, context); int count = offsets.Count; for (int i = 0; i < count; ++i) { - spanWriter.WriteUOffset(span, offsets[i], stringOffset); + target.WriteUOffset(offsets[i], stringOffset); } offsets.Clear(); @@ -151,6 +151,6 @@ private struct WriteCacheEntry // The string public string? String; - public List Offsets; + public List Offsets; } } diff --git a/src/FlatSharp.Runtime/IO/SpanWriter.cs b/src/FlatSharp.Runtime/IO/SpanWriter.cs deleted file mode 100644 index 304a6596..00000000 --- a/src/FlatSharp.Runtime/IO/SpanWriter.cs +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2020 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Buffers; -using System.Buffers.Binary; -using System.Text; - -namespace FlatSharp; - -/// -/// Utility class for writing items to spans. -/// -public struct SpanWriter : ISpanWriter -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteByte(Span span, byte value, int offset) - { - span[offset] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteSByte(Span span, sbyte value, int offset) - { - span[offset] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUShort(Span span, ushort value, int offset) - { - this.CheckAlignment(offset, sizeof(ushort)); - BinaryPrimitives.WriteUInt16LittleEndian(span.Slice(offset), value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteShort(Span span, short value, int offset) - { - this.CheckAlignment(offset, sizeof(short)); - BinaryPrimitives.WriteInt16LittleEndian(span.Slice(offset), value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt(Span span, uint value, int offset) - { - this.CheckAlignment(offset, sizeof(uint)); - BinaryPrimitives.WriteUInt32LittleEndian(span.Slice(offset), value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt(Span span, int value, int offset) - { - this.CheckAlignment(offset, sizeof(int)); - BinaryPrimitives.WriteInt32LittleEndian(span.Slice(offset), value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteULong(Span span, ulong value, int offset) - { - this.CheckAlignment(offset, sizeof(ulong)); - BinaryPrimitives.WriteUInt64LittleEndian(span.Slice(offset), value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLong(Span span, long value, int offset) - { - this.CheckAlignment(offset, sizeof(long)); - BinaryPrimitives.WriteInt64LittleEndian(span.Slice(offset), value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteFloat(Span span, float value, int offset) - { - ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout - { - value = value - }; - - this.WriteUInt(span, floatLayout.bytes, offset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteDouble(Span span, double value, int offset) - { - this.WriteLong(span, BitConverter.DoubleToInt64Bits(value), offset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetStringBytes(Span destination, string value, Encoding encoding) - { -#if NETSTANDARD2_0 - int length = value.Length; - byte[] buffer = ArrayPool.Shared.Rent(encoding.GetMaxByteCount(length)); - int bytesWritten = encoding.GetBytes(value, 0, length, buffer, 0); - buffer.AsSpan().Slice(0, bytesWritten).CopyTo(destination); - ArrayPool.Shared.Return(buffer); -#else - int bytesWritten = encoding.GetBytes(value, destination); -#endif - - return bytesWritten; - } - - public void FlushSharedStrings(ISharedStringWriter writer, Span destination, SerializationContext context) - { - writer.FlushWrites(this, destination, context); - } -} diff --git a/src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs b/src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs deleted file mode 100644 index ac506470..00000000 --- a/src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2024 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Runtime.InteropServices; - -namespace FlatSharp.Internal; - -/// -/// Extension methods that apply to all implementations. -/// -public static class SpanWriterExtensions -{ - public static void WriteReadOnlyByteMemoryBlock( - this TSpanWriter spanWriter, - Span span, - ReadOnlyMemory memory, - int offset, - SerializationContext ctx) where TSpanWriter : ISpanWriter - { - int numberOfItems = memory.Length; - int vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); - - spanWriter.WriteUOffset(span, offset, vectorStartOffset); - spanWriter.WriteInt(span, numberOfItems, vectorStartOffset); - - memory.Span.CopyTo(span.Slice(vectorStartOffset + sizeof(uint))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnsafeWriteSpan( - this TSpanWriter spanWriter, - Span span, - Span buffer, - int offset, - int alignment, - SerializationContext ctx) where TSpanWriter : ISpanWriter where TElement : unmanaged - { - // Since we are copying bytes here, only LE is supported. - FlatSharpInternal.AssertLittleEndian(); - FlatSharpInternal.AssertWellAligned(alignment); - - int numberOfItems = buffer.Length; - int vectorStartOffset = ctx.AllocateVector( - itemAlignment: alignment, - numberOfItems, - sizePerItem: Unsafe.SizeOf()); - - spanWriter.WriteUOffset(span, offset, vectorStartOffset); - spanWriter.WriteInt(span, numberOfItems, vectorStartOffset); - - var start = span.Slice(vectorStartOffset + sizeof(uint), checked(numberOfItems * Unsafe.SizeOf())); - - MemoryMarshal.Cast(buffer).CopyTo(start); - } - - /// - /// Writes the given string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteString( - this TSpanWriter spanWriter, - Span span, - string value, - int offset, - SerializationContext context) where TSpanWriter : ISpanWriter - { - int stringOffset = spanWriter.WriteAndProvisionString(span, value, context); - spanWriter.WriteUOffset(span, offset, stringOffset); - } - - /// - /// Writes the string to the buffer, returning the absolute offset of the string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteAndProvisionString(this TSpanWriter spanWriter, Span span, string value, SerializationContext context) - where TSpanWriter : ISpanWriter - { - var encoding = SerializationHelpers.Encoding; - - // Allocate more than we need and then give back what we don't use. - int maxItems = encoding.GetMaxByteCount(value.Length) + 1; - int stringStartOffset = context.AllocateVector(sizeof(byte), maxItems, sizeof(byte)); - - int bytesWritten = spanWriter.GetStringBytes(span.Slice(stringStartOffset + sizeof(uint), maxItems), value, encoding); - - // null teriminator - span[stringStartOffset + bytesWritten + sizeof(uint)] = 0; - - // write length - spanWriter.WriteInt(span, bytesWritten, stringStartOffset); - - // give back unused space. Account for null terminator. - context.Offset -= maxItems - (bytesWritten + 1); - - return stringStartOffset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteUOffset(this TSpanWriter spanWriter, Span span, int offset, int secondOffset) - where TSpanWriter : ISpanWriter - { - checked - { - uint uoffset = (uint)(secondOffset - offset); - spanWriter.WriteUInt(span, uoffset, offset); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteBool(this TSpanWriter spanWriter, Span span, bool b, int offset) - where TSpanWriter : ISpanWriter - { - spanWriter.WriteByte(span, b ? SerializationHelpers.True : SerializationHelpers.False, offset); - } - - [ExcludeFromCodeCoverage] - [Conditional("DEBUG")] - public static void CheckAlignment(this TSpanWriter spanWriter, int offset, int size) where TSpanWriter : ISpanWriter - { -#if DEBUG - if (offset % size != 0) - { - FSThrow.InvalidOperation($"BugCheck: attempted to read unaligned data at index: {offset}, expected alignment: {size}"); - } -#endif - } -} diff --git a/src/FlatSharp.Runtime/IPostSerializeAction.cs b/src/FlatSharp.Runtime/IPostSerializeAction.cs new file mode 100644 index 00000000..3c1b0346 --- /dev/null +++ b/src/FlatSharp.Runtime/IPostSerializeAction.cs @@ -0,0 +1,25 @@ +/* + * Copyright 2024 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace FlatSharp.Internal; + +/// +/// Describes an action that can be invoked after a serialize operation. +/// +public interface IPostSerializeAction +{ + void Invoke(BigSpan destination, SerializationContext context); +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/ISerializer.cs b/src/FlatSharp.Runtime/ISerializer.cs index a86b45aa..e2918bf8 100644 --- a/src/FlatSharp.Runtime/ISerializer.cs +++ b/src/FlatSharp.Runtime/ISerializer.cs @@ -36,16 +36,15 @@ public interface ISerializer /// /// Writes the given item to the buffer using the given spanwriter. /// - /// The span writer. - /// The span to write to. + /// The destination. /// The object to serialize. /// The number of bytes written. - int Write(TSpanWriter writer, Span destination, object item) where TSpanWriter : ISpanWriter; + long Write(BigSpan target, object item); /// /// Computes the maximum size necessary to serialize the given instance. /// - int GetMaxSize(object item); + long GetMaxSize(object item); /// /// Parses the given buffer as an instance of this ISerializer's type. @@ -74,16 +73,15 @@ public interface ISerializer /// /// Writes the given item to the buffer using the given spanwriter. /// - /// The span writer. - /// The span to write to. + /// The destination. /// The object to serialize. /// The number of bytes written. - int Write(TSpanWriter writer, Span destination, T item) where TSpanWriter : ISpanWriter; + long Write(BigSpan destination, T item); /// /// Computes the maximum size necessary to serialize the given instance of . /// - int GetMaxSize(T item); + long GetMaxSize(T item); /// /// Parses the given buffer as an instance of . diff --git a/src/FlatSharp.Runtime/ISerializerExtensions.cs b/src/FlatSharp.Runtime/ISerializerExtensions.cs index 39b9ba4b..fe5bb823 100644 --- a/src/FlatSharp.Runtime/ISerializerExtensions.cs +++ b/src/FlatSharp.Runtime/ISerializerExtensions.cs @@ -116,9 +116,9 @@ public static object Parse(this ISerializer serializer, ReadOnlyMemory dat /// /// The number of bytes written. [ExcludeFromCodeCoverage] // Just a helper - public static int Write(this ISerializer serializer, byte[] buffer, T item) where T : class + public static long Write(this ISerializer serializer, byte[] buffer, T item) where T : class { - return Write(serializer, buffer.AsSpan(), item); + return serializer.Write(new BigSpan(buffer.AsSpan()), item); } /// @@ -126,9 +126,9 @@ public static int Write(this ISerializer serializer, byte[] buffer, T item /// /// The number of bytes written. [ExcludeFromCodeCoverage] // Just a helper - public static int Write(this ISerializer serializer, byte[] buffer, object item) + public static long Write(this ISerializer serializer, byte[] buffer, object item) { - return Write(serializer, buffer.AsSpan(), item); + return serializer.Write(new BigSpan(buffer.AsSpan()), item); } /// @@ -136,9 +136,9 @@ public static int Write(this ISerializer serializer, byte[] buffer, object item) /// /// The number of bytes written. [ExcludeFromCodeCoverage] // Just a helper - public static int Write(this ISerializer serializer, ArraySegment buffer, T item) where T : class + public static long Write(this ISerializer serializer, ArraySegment buffer, T item) where T : class { - return Write(serializer, buffer.AsSpan(), item); + return serializer.Write(new BigSpan(buffer.AsSpan()), item); } /// @@ -146,9 +146,9 @@ public static int Write(this ISerializer serializer, ArraySegment bu /// /// The number of bytes written. [ExcludeFromCodeCoverage] // Just a helper - public static int Write(this ISerializer serializer, ArraySegment buffer, object item) + public static long Write(this ISerializer serializer, ArraySegment buffer, object item) { - return Write(serializer, buffer.AsSpan(), item); + return serializer.Write(new BigSpan(buffer.AsSpan()), item); } /// @@ -156,9 +156,9 @@ public static int Write(this ISerializer serializer, ArraySegment buffer, /// /// The number of bytes written. [ExcludeFromCodeCoverage] // Just a helper - public static int Write(this ISerializer serializer, Memory buffer, T item) where T : class + public static long Write(this ISerializer serializer, Memory buffer, T item) where T : class { - return Write(serializer, buffer.Span, item); + return serializer.Write(new BigSpan(buffer.Span), item); } /// @@ -166,9 +166,9 @@ public static int Write(this ISerializer serializer, Memory buffer, /// /// The number of bytes written. [ExcludeFromCodeCoverage] // Just a helper - public static int Write(this ISerializer serializer, Memory buffer, object item) + public static long Write(this ISerializer serializer, Memory buffer, object item) { - return Write(serializer, buffer.Span, item); + return serializer.Write(new BigSpan(buffer.Span), item); } /// @@ -176,9 +176,9 @@ public static int Write(this ISerializer serializer, Memory buffer, object /// /// The number of bytes written. [ExcludeFromCodeCoverage] // Just a helper - public static int Write(this ISerializer serializer, Span buffer, T item) where T : class + public static long Write(this ISerializer serializer, Span buffer, T item) where T : class { - return serializer.Write(default(SpanWriter), buffer, item); + return serializer.Write(new BigSpan(buffer), item); } /// @@ -186,22 +186,27 @@ public static int Write(this ISerializer serializer, Span buffer, T /// /// The number of bytes written. [ExcludeFromCodeCoverage] // Just a helper - public static int Write(this ISerializer serializer, Span buffer, object item) + public static long Write(this ISerializer serializer, Span buffer, object item) { - return serializer.Write(default(SpanWriter), buffer, item); + return serializer.Write(new BigSpan(buffer), item); } /// /// Writes the given item into the given buffer writer using the default SpanWriter. /// /// The number of bytes written. - public static int Write(this ISerializer serializer, IBufferWriter bufferWriter, T item) where T : class + public static long Write(this ISerializer serializer, IBufferWriter bufferWriter, T item) where T : class { - int maxSize = serializer.GetMaxSize(item); - Span buffer = bufferWriter.GetSpan(maxSize); - int bytesWritten = serializer.Write(default(SpanWriter), buffer, item); - bufferWriter.Advance(bytesWritten); + long maxSize = serializer.GetMaxSize(item); + if (maxSize > int.MaxValue) + { + FSThrow.InvalidData("The data is too large. This overload only supports the 32 bit address space."); + } + + Span buffer = bufferWriter.GetSpan((int)maxSize); + int bytesWritten = (int)serializer.Write(new BigSpan(buffer), item); + bufferWriter.Advance(bytesWritten); return bytesWritten; } @@ -211,11 +216,16 @@ public static int Write(this ISerializer serializer, IBufferWriter b /// The number of bytes written. public static int Write(this ISerializer serializer, IBufferWriter bufferWriter, object item) { - int maxSize = serializer.GetMaxSize(item); - Span buffer = bufferWriter.GetSpan(maxSize); - int bytesWritten = serializer.Write(default(SpanWriter), buffer, item); - bufferWriter.Advance(bytesWritten); + long maxSize = serializer.GetMaxSize(item); + if (maxSize > int.MaxValue) + { + FSThrow.InvalidData("The data is too large. This overload only supports the 32 bit address space."); + } + Span buffer = bufferWriter.GetSpan((int)maxSize); + int bytesWritten = (int)serializer.Write(new BigSpan(buffer), item); + + bufferWriter.Advance(bytesWritten); return bytesWritten; } } diff --git a/src/FlatSharp.Runtime/SerializationContext.cs b/src/FlatSharp.Runtime/SerializationContext.cs index 56e17530..91d4bdbc 100644 --- a/src/FlatSharp.Runtime/SerializationContext.cs +++ b/src/FlatSharp.Runtime/SerializationContext.cs @@ -14,7 +14,19 @@ * limitations under the License. */ +using System.Buffers.Binary; using System.Threading; +using System.Runtime.InteropServices; +using System.Linq; + + + + +#if NETCOREAPP +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using System.Runtime.Intrinsics.Arm; +#endif namespace FlatSharp.Internal; @@ -24,31 +36,36 @@ namespace FlatSharp.Internal; /// public sealed class SerializationContext { - /// - /// A delegate to invoke after the serialization process has completed. Used for sorting vectors. - /// - public delegate void PostSerializeAction(Span span, SerializationContext context); - internal static readonly ThreadLocal ThreadLocalContext = new ThreadLocal(() => new SerializationContext()); - private int offset; - private int capacity; - private readonly List postSerializeActions; - private readonly List vtableOffsets; + private long offset; + private long capacity; + private readonly List postSerializeActions; + private readonly List[] vtableOffsets; + +#if NETCOREAPP + private const int ListLength = 19; +#else + private const int ListLength = 1; +#endif /// /// Initializes a new serialization context. /// public SerializationContext() { - this.postSerializeActions = new List(); - this.vtableOffsets = new List(); + this.postSerializeActions = new List(); + this.vtableOffsets = new List[ListLength]; + for (int i = 0; i < this.vtableOffsets.Length; ++i) + { + this.vtableOffsets[i] = new(); + } } /// /// The maximum offset within the buffer. /// - public int Offset + public long Offset { get => this.offset; set => this.offset = value; @@ -63,32 +80,36 @@ public int Offset /// Resets the context. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset(int capacity) + public void Reset(long capacity) { this.offset = 0; this.capacity = capacity; this.SharedStringWriter = null; this.postSerializeActions.Clear(); - this.vtableOffsets.Clear(); + + for (int i = 0; i < this.vtableOffsets.Length; ++i) + { + this.vtableOffsets[i].Clear(); + } } /// /// Invokes any post-serialize actions. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InvokePostSerializeActions(Span span) + public void InvokePostSerializeActions(BigSpan target) { var actions = this.postSerializeActions; int count = actions.Count; for (int i = 0; i < count; ++i) { - actions[i](span, this); + actions[i].Invoke(target, this); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddPostSerializeAction(PostSerializeAction action) + public void AddPostSerializeAction(IPostSerializeAction action) { this.postSerializeActions.Add(action); } @@ -96,7 +117,7 @@ public void AddPostSerializeAction(PostSerializeAction action) /// /// Allocate a vector and return the index. Does not populate any details of the vector. /// - public int AllocateVector(int itemAlignment, int numberOfItems, int sizePerItem) + public long AllocateVector(int itemAlignment, int numberOfItems, int sizePerItem) { if (numberOfItems < 0) { @@ -113,7 +134,7 @@ public int AllocateVector(int itemAlignment, int numberOfItems, int sizePerItem) // // Obviously, if N <= 4 this is trivial. If N = 8, it gets a bit more interesting. // First, align the offset to 4. - int offset = this.offset; + long offset = this.offset; offset += SerializationHelpers.GetAlignmentError(offset, sizeof(uint)); // Now, align offset + 4 to item alignment. @@ -131,14 +152,14 @@ public int AllocateVector(int itemAlignment, int numberOfItems, int sizePerItem) /// /// Allocates a block of memory. Returns the offset. /// - public int AllocateSpace(int bytesNeeded, int alignment) + public long AllocateSpace(int bytesNeeded, int alignment) { - int offset = this.offset; + long offset = this.offset; Debug.Assert(alignment == 1 || alignment % 2 == 0); offset += SerializationHelpers.GetAlignmentError(offset, alignment); - int finalOffset = offset + bytesNeeded; + long finalOffset = offset + bytesNeeded; if (finalOffset >= this.capacity) { FSThrow.BufferTooSmall(0); @@ -149,34 +170,39 @@ public int AllocateSpace(int bytesNeeded, int alignment) } [MethodImpl(MethodImplOptions.NoInlining)] // Common method; don't inline - public int FinishVTable( - Span buffer, + public long FinishVTable( + BigSpan buffer, + uint crc, Span vtable) { - var offsets = this.vtableOffsets; + var offsets = this.vtableOffsets[crc % ListLength]; int count = offsets.Count; for (int i = 0; i < count; ++i) { - int offset = offsets[i]; - - ReadOnlySpan existingVTable = buffer.Slice(offset); - existingVTable = existingVTable.Slice(0, ScalarSpanReader.ReadUShort(existingVTable)); + long offset = offsets[i]; + ushort vtableLength = buffer.ReadUShort(offset); - if (existingVTable.SequenceEqual(vtable)) + if (vtableLength == vtable.Length) { - // Slowly bubble used things towards the front of the list. - // This is not exact, but should keep frequently used - // items towards the front. - Promote(i, offsets); + Span existingVTable = buffer.ToSpan(offset, vtableLength); + + if (existingVTable.SequenceEqual(vtable)) + { + // Slowly bubble used things towards the front of the list. + // This is not exact, but should keep frequently used + // items towards the front. + Promote(i, offsets); - return offset; + return offset; + } } } // Oh, well. Write the new table. - int newVTableOffset = this.AllocateSpace(vtable.Length, sizeof(ushort)); - vtable.CopyTo(buffer.Slice(newVTableOffset)); + long newVTableOffset = this.AllocateSpace(vtable.Length, sizeof(ushort)); + + vtable.CopyTo(buffer.ToSpan(newVTableOffset, vtable.Length)); offsets.Add(newVTableOffset); // "Insert" this item in the middle of the list. @@ -189,11 +215,11 @@ public int FinishVTable( // This is done with a swap to avoid shuffling the whole list by inserting // at a given index. An alternative might be an unrolled linked list data structure. [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void Promote(int i, List offsets) + static void Promote(int i, List offsets) { int swapIndex = i / 2; - int temp = offsets[i]; + long temp = offsets[i]; offsets[i] = offsets[swapIndex]; offsets[swapIndex] = temp; } diff --git a/src/FlatSharp.Runtime/SerializationHelpers.cs b/src/FlatSharp.Runtime/SerializationHelpers.cs index 50bf5820..c15a050e 100644 --- a/src/FlatSharp.Runtime/SerializationHelpers.cs +++ b/src/FlatSharp.Runtime/SerializationHelpers.cs @@ -74,10 +74,10 @@ public static void CombineMask(ref byte source, byte mask) /// Returns the number of padding bytes to be added to the given offset to acheive the given alignment. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetAlignmentError(int offset, int alignment) + public static int GetAlignmentError(long offset, long alignment) { Debug.Assert(alignment == 1 || alignment % 2 == 0); - return (-offset) & (alignment - 1); + return (int)((-offset) & (alignment - 1)); } /// @@ -106,4 +106,23 @@ public static void EnsureDepthLimit(short remainingDepth) FSThrow.InvalidData_DepthLimit(); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ComputeCrc(ref uint crc, ushort value) + { +#if NETCOREAPP + if (System.Runtime.Intrinsics.X86.Sse42.IsSupported) + { + crc = System.Runtime.Intrinsics.X86.Sse42.Crc32(crc, value); + return; + } + else if (System.Runtime.Intrinsics.Arm.Crc32.IsSupported) + { + crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32(crc, value); + return; + } +#endif + + crc = (crc << 1) ^ value; + } } diff --git a/src/FlatSharp.Runtime/SortedVectorHelpers.cs b/src/FlatSharp.Runtime/SortedVectorHelpers.cs index 0c65d044..90705fe2 100644 --- a/src/FlatSharp.Runtime/SortedVectorHelpers.cs +++ b/src/FlatSharp.Runtime/SortedVectorHelpers.cs @@ -317,13 +317,13 @@ public ReadOnlyMemory KeyAt(int index) IInputBuffer buffer = this.inputBuffer; // Read uoffset. - int offset = vector.OffsetOf(index); + long offset = vector.OffsetOf(index); // Increment uoffset to move to start of table. offset += buffer.ReadUOffset(offset); // Follow soffset to start of vtable. - int vtableStart = offset - buffer.ReadInt(offset); + long vtableStart = offset - buffer.ReadInt(offset); ushort vtableLength = buffer.ReadUShort(vtableStart); int tableOffset = 0; @@ -347,7 +347,7 @@ public ReadOnlyMemory KeyAt(int index) // Length of the string. int stringLength = (int)buffer.ReadUInt(offset); - return buffer.GetReadOnlyMemory().Slice(offset + sizeof(int), stringLength); + return buffer.GetReadOnlyMemory(offset + sizeof(int), stringLength); } public int Count => this.vector.Count; diff --git a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs index d949758d..3128d839 100644 --- a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs +++ b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs @@ -38,8 +38,26 @@ namespace FlatSharp.Internal; /// Helper methods for dealing with sorted vectors. This class provides functionality for both sorting vectors and /// binary searching through them. /// -public static class SortedVectorHelpersInternal +public sealed class VectorSortAction : IPostSerializeAction + where TSpanComparer : ISpanComparer { + private readonly TSpanComparer comparer; + private readonly long vectorUOffset; + private readonly int vTableIndex; + private readonly int? keyInlineSize; + + public VectorSortAction( + long vectorUOffset, + int vtableIndex, + int? keyInlineSize, + TSpanComparer comparer) + { + this.vectorUOffset = vectorUOffset; + this.vTableIndex = vtableIndex; + this.keyInlineSize = keyInlineSize; + this.comparer = comparer; + } + /// /// Sorts the given flatbuffer vector. This method, used incorrectly, is a fantastic way to corrupt your buffer. /// @@ -56,30 +74,26 @@ public static class SortedVectorHelpersInternal /// Furthermore, this method is left without checked multiply operations since this is a post-serialize action, which means the input /// has already been sanitized since FlatSharp wrote it. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static void SortVector( - Span buffer, - int vectorUOffset, - int vtableIndex, - int? keyInlineSize, - TSpanComparer comparer) where TSpanComparer : ISpanComparer + public void Invoke(BigSpan buffer, SerializationContext context) { - int vectorStartOffset = vectorUOffset + (int)ScalarSpanReader.ReadUInt(buffer.Slice(vectorUOffset)); - int vectorLength = (int)ScalarSpanReader.ReadUInt(buffer.Slice(vectorStartOffset)); - int index0Position = vectorStartOffset + sizeof(int); + long vectorStartOffset = + vectorUOffset + buffer.ReadUInt(vectorUOffset); + int vectorLength = (int)buffer.ReadUInt(vectorStartOffset); + long index0Position = vectorStartOffset + sizeof(int); - (int, int, int)[]? pooledArray = null; + (long, int, long)[]? pooledArray = null; // Traverse the vector and figure out the offsets of all the keys. // Store that in some local data, hopefully on the stack. 512 is somewhat arbitrary, but we want to avoid stack overflows. - Span<(int offset, int length, int tableOffset)> keyOffsets = + Span<(long offset, int length, long tableOffset)> keyOffsets = vectorLength < 512 - ? stackalloc (int, int, int)[vectorLength] - : (pooledArray = ArrayPool<(int, int, int)>.Shared.Rent(vectorLength)).AsSpan().Slice(0, vectorLength); + ? stackalloc (long, int, long)[vectorLength] + : (pooledArray = ArrayPool<(long, int, long)>.Shared.Rent(vectorLength)).AsSpan() + .Slice(0, vectorLength); for (int i = 0; i < keyOffsets.Length; ++i) { - keyOffsets[i] = GetKeyOffset(buffer, index0Position, i, vtableIndex, keyInlineSize); + keyOffsets[i] = GetKeyOffset(buffer, index0Position, i, this.vTableIndex, keyInlineSize); } // Sort the offsets. @@ -87,18 +101,19 @@ public static void SortVector( // Overwrite the vector with the sorted offsets. Bound the vector so we're confident we aren't // partying inappropriately in the rest of the buffer. - Span boundedVector = buffer.Slice(index0Position, sizeof(uint) * vectorLength); - int nextPosition = index0Position; + BigSpan boundedVector = buffer.Slice(index0Position, sizeof(uint) * vectorLength); + long nextPosition = index0Position; for (int i = 0; i < keyOffsets.Length; ++i) { - (_, _, int tableOffset) = keyOffsets[i]; - BinaryPrimitives.WriteUInt32LittleEndian(boundedVector.Slice(sizeof(uint) * i), (uint)(tableOffset - nextPosition)); + ref (long _, int __, long tableOffset) tuple = ref keyOffsets[i]; + + boundedVector.WriteUInt(nextPosition - index0Position, (uint)(tuple.tableOffset - nextPosition)); nextPosition += sizeof(uint); } if (pooledArray is not null) { - ArrayPool<(int, int, int)>.Shared.Return(pooledArray); + ArrayPool<(long, int, long)>.Shared.Return(pooledArray); } } @@ -107,12 +122,12 @@ public static void SortVector( /// Due to the amount of indirection in FlatBuffers, it's not possible to use the built-in sorting algorithms, /// so we do the next best thing. Note that this is not a true IntroSort, since we omit the HeapSort component. /// - private static void IntroSort( - ReadOnlySpan buffer, + private static void IntroSort( + BigSpan buffer, TSpanComparer keyComparer, int lo, int hi, - Span<(int offset, int length, int tableOffset)> keyLocations) where TSpanComparer : ISpanComparer + Span<(long offset, int length, long tableOffset)> keyLocations) { checked { @@ -156,7 +171,7 @@ private static void IntroSort( SwapVectorPositions(middle, hi - 1, keyLocations); var (pivotOffset, pivotLength, _) = keyLocations[hi - 1]; bool pivotExists = pivotOffset != 0; - var pivotSpan = buffer.Slice(pivotOffset, pivotLength); + var pivotSpan = buffer.ToSpan(pivotOffset, pivotLength); // Partition int num2 = lo; @@ -165,9 +180,9 @@ private static void IntroSort( { while (true) { - var (keyOffset, keyLength, _) = keyLocations[++num2]; - var keySpan = buffer.Slice(keyOffset, keyLength); - if (keyComparer.Compare(keyOffset != 0, keySpan, pivotExists, pivotSpan) >= 0) + ref (long keyOffset, int keyLength, long _) tuple = ref keyLocations[++num2]; + var keySpan = buffer.ToSpan(tuple.keyOffset, tuple.keyLength); + if (keyComparer.Compare(tuple.keyOffset != 0, keySpan, pivotExists, pivotSpan) >= 0) { break; } @@ -175,9 +190,9 @@ private static void IntroSort( while (true) { - var (keyOffset, keyLength, _) = keyLocations[--num3]; - var keySpan = buffer.Slice(keyOffset, keyLength); - if (keyComparer.Compare(pivotExists, pivotSpan, keyOffset != 0, keySpan) >= 0) + ref (long keyOffset, int keyLength, long _) tuple = ref keyLocations[--num3]; + var keySpan = buffer.ToSpan(tuple.keyOffset, tuple.keyLength); + if (keyComparer.Compare(pivotExists, pivotSpan, tuple.keyOffset != 0, keySpan) >= 0) { break; } @@ -203,26 +218,26 @@ private static void IntroSort( } } - private static void InsertionSort( - ReadOnlySpan buffer, + private static void InsertionSort( + BigSpan buffer, TSpanComparer comparer, int lo, int hi, - Span<(int offset, int length, int tableOffset)> keyLocations) where TSpanComparer : ISpanComparer + Span<(long offset, int length, long tableOffset)> keyLocations) { for (int i = lo; i < hi; i++) { int num = i; var valTuple = keyLocations[i + 1]; - ReadOnlySpan valSpan = buffer.Slice(valTuple.offset, valTuple.length); + ReadOnlySpan valSpan = buffer.ToSpan(valTuple.offset, valTuple.length); while (num >= lo) { - (int keyOffset, int keyLength, _) = keyLocations[num]; - ReadOnlySpan keySpan = buffer.Slice(keyOffset, keyLength); + ref (long keyOffset, int keyLength, long table) tuple = ref keyLocations[num]; + ReadOnlySpan keySpan = buffer.ToSpan(tuple.keyOffset, tuple.keyLength); - if (comparer.Compare(valTuple.offset != 0, valSpan, keyOffset != 0, keySpan) < 0) + if (comparer.Compare(valTuple.offset != 0, valSpan, tuple.keyOffset != 0, keySpan) < 0) { keyLocations[num + 1] = keyLocations[num]; num--; @@ -237,21 +252,31 @@ private static void InsertionSort( } } - private static void SwapIfGreater( - ReadOnlySpan vector, + private static void SwapIfGreater( + BigSpan target, TSpanComparer comparer, int leftIndex, int rightIndex, - Span<(int, int, int)> keyOffsets) where TSpanComparer : ISpanComparer + Span<(long, int, long)> keyOffsets) { - (int leftOffset, int leftLength, _) = keyOffsets[leftIndex]; - (int rightOffset, int rightLength, _) = keyOffsets[rightIndex]; + ref (long offset, int length, long _) left = ref keyOffsets[leftIndex]; + ref (long offset, int length, long _) right = ref keyOffsets[rightIndex]; + + bool leftExists = left.offset != 0; + bool rightExists = right.offset != 0; - bool leftExists = leftOffset != 0; - bool rightExists = rightOffset != 0; + Span leftSpan = default; + Span rightSpan = default; - var leftSpan = vector.Slice(leftOffset, leftLength); - var rightSpan = vector.Slice(rightOffset, rightLength); + if (leftExists) + { + leftSpan = target.ToSpan(left.offset, left.length); + } + + if (rightExists) + { + rightSpan = target.ToSpan(right.offset, right.length); + } if (comparer.Compare(leftExists, leftSpan, rightExists, rightSpan) > 0) { @@ -259,7 +284,7 @@ private static void SwapIfGreater( } } - private static void SwapVectorPositions(int leftIndex, int rightIndex, Span<(int, int, int)> keyOffsets) + private static void SwapVectorPositions(int leftIndex, int rightIndex, Span<(long, int, long)> keyOffsets) { checked { @@ -277,22 +302,22 @@ private static void SwapVectorPositions(int leftIndex, int rightIndex, Span<(int /// /// Left as unchecked since this is a sort operation (not a search). /// - private static (int offset, int length, int tableOffset) GetKeyOffset( - ReadOnlySpan buffer, - int index0Position, + private static (long offset, int length, long tableOffset) GetKeyOffset( + BigSpan buffer, + long index0Position, int vectorIndex, int vtableIndex, int? inlineItemSize) { // Find offset to the table at the index. - int tableOffset = index0Position + (sizeof(uint) * vectorIndex); - tableOffset += (int)ScalarSpanReader.ReadUInt(buffer.Slice(tableOffset)); + long tableOffset = index0Position + (sizeof(uint) * vectorIndex); + tableOffset += (int)buffer.ReadUInt(tableOffset); // Consult the vtable. - int vtableOffset = tableOffset - ScalarSpanReader.ReadInt(buffer.Slice(tableOffset)); + long vtableOffset = tableOffset - buffer.ReadInt(tableOffset); // Vtables have two extra entries: vtable length and table length. The number of entries is vtableLengthBytes / 2 - 2 - int vtableLengthEntries = (ScalarSpanReader.ReadUShort(buffer.Slice(vtableOffset)) / 2) - 2; + int vtableLengthEntries = (buffer.ReadUShort(vtableOffset) / 2) - 2; if (vtableIndex >= vtableLengthEntries) { @@ -300,7 +325,7 @@ private static (int offset, int length, int tableOffset) GetKeyOffset( } // Absolute offset of the field within the table. - int fieldOffset = tableOffset + ScalarSpanReader.ReadUShort(buffer.Slice(vtableOffset + 2 * (2 + vtableIndex))); + long fieldOffset = tableOffset + buffer.ReadUShort(vtableOffset + 2 * (2 + vtableIndex)); if (inlineItemSize is not null) { return (fieldOffset, inlineItemSize.Value, tableOffset); @@ -312,8 +337,8 @@ private static (int offset, int length, int tableOffset) GetKeyOffset( } // Strings are stored as a uoffset reference. Follow the indirection one more time. - int uoffsetToString = fieldOffset + (int)ScalarSpanReader.ReadUInt(buffer.Slice(fieldOffset)); - int stringLength = (int)ScalarSpanReader.ReadUInt(buffer.Slice(uoffsetToString)); + long uoffsetToString = fieldOffset + (int)buffer.ReadUInt(fieldOffset); + int stringLength = (int)buffer.ReadUInt(uoffsetToString); return (uoffsetToString + sizeof(uint), stringLength, tableOffset); } } diff --git a/src/FlatSharp.Runtime/VTables/VTable4.cs b/src/FlatSharp.Runtime/VTables/VTable4.cs index 23e57a57..ac5fd60e 100644 --- a/src/FlatSharp.Runtime/VTables/VTable4.cs +++ b/src/FlatSharp.Runtime/VTables/VTable4.cs @@ -39,7 +39,7 @@ public struct VTable4 : IVTable public int MaxSupportedIndex => 3; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Create(TInputBuffer inputBuffer, int offset, out VTable4 item) + public static void Create(TInputBuffer inputBuffer, long offset, out VTable4 item) where TInputBuffer : IInputBuffer { if (BitConverter.IsLittleEndian) @@ -70,13 +70,13 @@ public int OffsetOf(TInputBuffer buffer, int index) /// A generic/safe initialize method for BE archtectures. /// [MethodImpl(MethodImplOptions.NoInlining)] - public static void CreateBigEndian(TInputBuffer inputBuffer, int offset, out VTable4 item) + public static void CreateBigEndian(TInputBuffer inputBuffer, long offset, out VTable4 item) where TInputBuffer : IInputBuffer { inputBuffer.InitializeVTable( offset, out _, - out nuint fieldCount, + out ulong fieldCount, out ReadOnlySpan fieldData); item = new VTable4(); @@ -107,7 +107,7 @@ public static void CreateBigEndian(TInputBuffer inputBuffer, int o /// An optimized load mmethod for LE architectures. /// [MethodImpl(MethodImplOptions.NoInlining)] - internal static void CreateLittleEndian(TInputBuffer inputBuffer, int offset, out VTable4 item) + internal static void CreateLittleEndian(TInputBuffer inputBuffer, long offset, out VTable4 item) where TInputBuffer : IInputBuffer { #if NETSTANDARD2_0 @@ -117,7 +117,7 @@ internal static void CreateLittleEndian(TInputBuffer inputBuffer, inputBuffer.InitializeVTable( offset, out _, - out nuint fieldCount, + out ulong fieldCount, out ReadOnlySpan fieldData); if (fieldData.Length >= Unsafe.SizeOf()) diff --git a/src/FlatSharp.Runtime/VTables/VTable8.cs b/src/FlatSharp.Runtime/VTables/VTable8.cs index d47abdcc..92287be0 100644 --- a/src/FlatSharp.Runtime/VTables/VTable8.cs +++ b/src/FlatSharp.Runtime/VTables/VTable8.cs @@ -51,7 +51,7 @@ public struct VTable8 : IVTable public int MaxSupportedIndex => 7; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Create(TInputBuffer inputBuffer, int offset, out VTable8 item) + public static void Create(TInputBuffer inputBuffer, long offset, out VTable8 item) where TInputBuffer : IInputBuffer { if (BitConverter.IsLittleEndian) @@ -86,13 +86,13 @@ public int OffsetOf(TInputBuffer buffer, int index) /// A generic/safe initialize method for BE archtectures. /// [MethodImpl(MethodImplOptions.NoInlining)] - public static void CreateBigEndian(TInputBuffer inputBuffer, int offset, out VTable8 item) + public static void CreateBigEndian(TInputBuffer inputBuffer, long offset, out VTable8 item) where TInputBuffer : IInputBuffer { inputBuffer.InitializeVTable( offset, out _, - out nuint fieldCount, + out ulong fieldCount, out ReadOnlySpan fieldData); item = new VTable8(); @@ -139,7 +139,7 @@ public static void CreateBigEndian(TInputBuffer inputBuffer, int o /// An optimized load mmethod for LE architectures. /// [MethodImpl(MethodImplOptions.NoInlining)] - public static void CreateLittleEndian(TInputBuffer inputBuffer, int offset, out VTable8 item) + public static void CreateLittleEndian(TInputBuffer inputBuffer, long offset, out VTable8 item) where TInputBuffer : IInputBuffer { #if NETSTANDARD2_0 @@ -148,7 +148,7 @@ public static void CreateLittleEndian(TInputBuffer inputBuffer, in inputBuffer.InitializeVTable( offset, out _, - out nuint fieldCount, + out ulong fieldCount, out ReadOnlySpan fieldData); if (fieldData.Length >= Unsafe.SizeOf()) diff --git a/src/FlatSharp.Runtime/VTables/VTableGeneric.cs b/src/FlatSharp.Runtime/VTables/VTableGeneric.cs index 75b6c285..7d7bc2e6 100644 --- a/src/FlatSharp.Runtime/VTables/VTableGeneric.cs +++ b/src/FlatSharp.Runtime/VTables/VTableGeneric.cs @@ -21,13 +21,13 @@ namespace FlatSharp.Internal; /// public struct VTableGeneric : IVTable { - private int offset; - private nuint count; + private long offset; + private ulong count; public int MaxSupportedIndex => 255; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Create(TInputBuffer buffer, int offset, out VTableGeneric item) + public static void Create(TInputBuffer buffer, long offset, out VTableGeneric item) where TInputBuffer : IInputBuffer { checked diff --git a/src/FlatSharp/FlatBufferSerializer.cs b/src/FlatSharp/FlatBufferSerializer.cs index 6a4da297..af5bfb9f 100644 --- a/src/FlatSharp/FlatBufferSerializer.cs +++ b/src/FlatSharp/FlatBufferSerializer.cs @@ -174,26 +174,16 @@ public T Parse(TInputBuffer buffer) where TInputBuffer : IInput /// Writes the given object to the given memory block. /// /// The length of data that was written to the memory block. - public int Serialize(T item, Span destination) where T : class - { - return this.Serialize(item, destination, default(SpanWriter)); - } - - /// - /// Writes the given object to the given memory block. - /// - /// The length of data that was written to the memory block. - public int Serialize(T item, Span destination, TSpanWriter writer) + public long Serialize(T item, BigSpan destination) where T : class - where TSpanWriter : ISpanWriter { - return this.GetOrCreateTypedSerializer().Write(writer, destination, item); + return this.GetOrCreateTypedSerializer().Write(destination, item); } /// /// Gets the maximum serialized size of the given item. /// - public int GetMaxSize(T item) where T : class + public long GetMaxSize(T item) where T : class { return this.GetOrCreateUntypedSerializer(typeof(T)).GetMaxSize(item); } diff --git a/src/FlatSharp/FlatBufferVectorHelpers.cs b/src/FlatSharp/FlatBufferVectorHelpers.cs index 0cd70027..4191753b 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers.cs @@ -117,11 +117,11 @@ private static string CreateIFlatBufferDeserializedVectorMethods( int IFlatBufferDeserializedVector.ItemSize => {{inlineSize}}; - int IFlatBufferDeserializedVector.OffsetBase => this.{{offsetVariableName}}; + long IFlatBufferDeserializedVector.OffsetBase => this.{{offsetVariableName}}; object IFlatBufferDeserializedVector.ItemAt(int index) => this.{{checkedParseItemMethodName}}(index)!; - int IFlatBufferDeserializedVector.OffsetOf(int index) + long IFlatBufferDeserializedVector.OffsetOf(int index) { {{nameof(VectorUtilities)}}.{{nameof(VectorUtilities.CheckIndex)}}(index, this.Count); return this.{{offsetVariableName}} + ({{GetEfficientMultiply(inlineSize, "index")}}); @@ -158,7 +158,7 @@ public static string CreateWriteThroughMethod( if (isEverWriteThrough) { - var serializeContext = context.GetWriteThroughContext("data", "value", "0"); + var serializeContext = context.GetWriteThroughContext("data", "value", "itemOffset"); writeThroughBody = @$" if (!{context.TableFieldContextVariableName}.WriteThrough) @@ -166,9 +166,8 @@ public static string CreateWriteThroughMethod( {nameof(FSThrow)}.{nameof(FSThrow.NotMutable)}(); }} - int offset = this.offset + ({GetEfficientMultiply(inlineSize, "index")}); - Span {serializeContext.SpanVariableName} = {context.InputBufferVariableName}.GetSpan().Slice(offset, {inlineSize}); - + long itemOffset = this.offset + ({GetEfficientMultiply(inlineSize, "index")}); + var data = {context.InputBufferVariableName}.GetSpan(); {serializeContext.GetSerializeInvocation(itemTypeModel.ClrType)}; "; } diff --git a/src/FlatSharp/FlatBufferVectorHelpers_Greedy.cs b/src/FlatSharp/FlatBufferVectorHelpers_Greedy.cs index 03fb43cc..20b1daf3 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers_Greedy.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers_Greedy.cs @@ -44,7 +44,7 @@ internal sealed class {{className}} public {{className}}( TInputBuffer {{context.InputBufferVariableName}}, - int {{context.OffsetVariableName}}, + long {{context.OffsetVariableName}}, short {{context.RemainingDepthVariableName}}, TableFieldContext {{context.TableFieldContextVariableName}}) { diff --git a/src/FlatSharp/FlatBufferVectorHelpers_GreedyMutable.cs b/src/FlatSharp/FlatBufferVectorHelpers_GreedyMutable.cs index 9a404d42..1e11bdc8 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers_GreedyMutable.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers_GreedyMutable.cs @@ -61,7 +61,7 @@ internal sealed class {{className}} public {{className}}( TInputBuffer {{context.InputBufferVariableName}}, - int {{context.OffsetVariableName}}, + long {{context.OffsetVariableName}}, short {{context.RemainingDepthVariableName}}, TableFieldContext {{context.TableFieldContextVariableName}}) { diff --git a/src/FlatSharp/FlatBufferVectorHelpers_GreedyMutableUnion.cs b/src/FlatSharp/FlatBufferVectorHelpers_GreedyMutableUnion.cs index 0b883d37..506f1568 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers_GreedyMutableUnion.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers_GreedyMutableUnion.cs @@ -42,12 +42,12 @@ internal sealed class {{className}} public {{className}}( TInputBuffer {{context.InputBufferVariableName}}, - ref (int offset0, int offset1) offsets, + ref (long offset0, long offset1) offsets, short {{context.RemainingDepthVariableName}}, TableFieldContext {{context.TableFieldContextVariableName}}) { - int dvo = offsets.offset0; - int ovo = offsets.offset1; + long dvo = offsets.offset0; + long ovo = offsets.offset1; int discriminatorCount = (int){{context.InputBufferVariableName}}.ReadUInt(dvo); int offsetCount = (int){{context.InputBufferVariableName}}.ReadUInt(ovo); diff --git a/src/FlatSharp/FlatBufferVectorHelpers_GreedyUnion.cs b/src/FlatSharp/FlatBufferVectorHelpers_GreedyUnion.cs index dca57761..be10d832 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers_GreedyUnion.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers_GreedyUnion.cs @@ -44,12 +44,12 @@ internal sealed class {{className}} public {{className}}( TInputBuffer {{context.InputBufferVariableName}}, - ref (int offset0, int offset1) offsets, + ref (long offset0, long offset1) offsets, short {{context.RemainingDepthVariableName}}, TableFieldContext {{context.TableFieldContextVariableName}}) { - int dvo = offsets.offset0; - int ovo = offsets.offset1; + long dvo = offsets.offset0; + long ovo = offsets.offset1; int discriminatorCount = (int){{context.InputBufferVariableName}}.ReadUInt(dvo); int offsetCount = (int){{context.InputBufferVariableName}}.ReadUInt(ovo); diff --git a/src/FlatSharp/FlatBufferVectorHelpers_Lazy.cs b/src/FlatSharp/FlatBufferVectorHelpers_Lazy.cs index 56c35079..ffc1d9aa 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers_Lazy.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers_Lazy.cs @@ -43,7 +43,7 @@ internal sealed class {{className}} , IFlatBufferDeserializedVector where TInputBuffer : IInputBuffer { - private readonly int {{context.OffsetVariableName}}; + private readonly long {{context.OffsetVariableName}}; private readonly int count; private readonly {{context.InputBufferTypeName}} {{context.InputBufferVariableName}}; private readonly TableFieldContext {{context.TableFieldContextVariableName}}; @@ -51,7 +51,7 @@ internal sealed class {{className}} public {{className}}( TInputBuffer memory, - int offset, + long offset, short remainingDepth, TableFieldContext fieldContext) { @@ -86,7 +86,7 @@ internal sealed class {{className}} [MethodImpl(MethodImplOptions.AggressiveInlining)] private {{derivedTypeName}} UnsafeParseItem(int index) { - int {{context.OffsetVariableName}} = this.offset + ({{GetEfficientMultiply(inlineSize, "index")}}); + long {{context.OffsetVariableName}} = this.offset + ({{GetEfficientMultiply(inlineSize, "index")}}); return {{context.GetParseInvocation(itemTypeModel.ClrType)}}; } diff --git a/src/FlatSharp/FlatBufferVectorHelpers_LazyUnion.cs b/src/FlatSharp/FlatBufferVectorHelpers_LazyUnion.cs index 54516ef5..1bf3243c 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers_LazyUnion.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers_LazyUnion.cs @@ -42,8 +42,8 @@ internal sealed class {{{className}}} , IReadOnlyList<{{{baseTypeName}}}> where TInputBuffer : IInputBuffer { - private readonly int discriminatorVectorOffset; - private readonly int offsetVectorOffset; + private readonly long discriminatorVectorOffset; + private readonly long offsetVectorOffset; private readonly int count; private readonly {{{context.InputBufferTypeName}}} {{{context.InputBufferVariableName}}}; private readonly TableFieldContext {{{context.TableFieldContextVariableName}}}; @@ -51,12 +51,12 @@ internal sealed class {{{className}}} public {{{className}}}( TInputBuffer memory, - ref (int offset0, int offset1) offsets, + ref (long offset0, long offset1) offsets, short remainingDepth, TableFieldContext fieldContext) { - int dvo = offsets.offset0; - int ovo = offsets.offset1; + long dvo = offsets.offset0; + long ovo = offsets.offset1; uint discriminatorCount = memory.ReadUInt(dvo); uint offsetCount = memory.ReadUInt(ovo); diff --git a/src/FlatSharp/FlatBufferVectorHelpers_Progressive.cs b/src/FlatSharp/FlatBufferVectorHelpers_Progressive.cs index eb5b8ba8..5361854f 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers_Progressive.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers_Progressive.cs @@ -48,7 +48,7 @@ internal sealed class {{className}} { private const uint ChunkSize = {{chunkSize}}; - private readonly int {{context.OffsetVariableName}}; + private readonly long {{context.OffsetVariableName}}; private readonly int count; private readonly {{context.InputBufferTypeName}} {{context.InputBufferVariableName}}; private readonly TableFieldContext {{context.TableFieldContextVariableName}}; @@ -57,7 +57,7 @@ internal sealed class {{className}} public {{className}}( TInputBuffer memory, - int offset, + long offset, short remainingDepth, TableFieldContext fieldContext) { @@ -124,7 +124,7 @@ private static void GetAddress(uint index, out uint rowIndex, out uint colIndex) copyCount = remainingItems; } - int offset = this.offset + ({{GetEfficientMultiply(inlineSize, "absoluteStartIndex")}}); + long offset = this.offset + ({{GetEfficientMultiply(inlineSize, "absoluteStartIndex")}}); for (int i = 0; i < copyCount; ++i) { row[i] = this.UnsafeParseFromOffset(offset); @@ -185,12 +185,12 @@ private void InlineProgressiveSet(int index, {{baseTypeName}} value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private {{derivedTypeName}} UnsafeParseItem(int index) { - int offset = this.offset + ({{GetEfficientMultiply(inlineSize, "index")}}); + long offset = this.offset + ({{GetEfficientMultiply(inlineSize, "index")}}); return UnsafeParseFromOffset(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private {{derivedTypeName}} UnsafeParseFromOffset(int {{context.OffsetVariableName}}) + private {{derivedTypeName}} UnsafeParseFromOffset(long {{context.OffsetVariableName}}) { return {{context.GetParseInvocation(itemTypeModel.ClrType)}}; } diff --git a/src/FlatSharp/FlatBufferVectorHelpers_ProgressiveUnion.cs b/src/FlatSharp/FlatBufferVectorHelpers_ProgressiveUnion.cs index d9ec842b..ac864a57 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers_ProgressiveUnion.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers_ProgressiveUnion.cs @@ -43,8 +43,8 @@ internal sealed class {{className}} { private const uint ChunkSize = {{chunkSize}}; - private readonly int discriminatorVectorOffset; - private readonly int offsetVectorOffset; + private readonly long discriminatorVectorOffset; + private readonly long offsetVectorOffset; private readonly int count; private readonly {{context.InputBufferTypeName}} {{context.InputBufferVariableName}}; private readonly TableFieldContext {{context.TableFieldContextVariableName}}; @@ -53,12 +53,12 @@ internal sealed class {{className}} public {{className}}( TInputBuffer memory, - ref (int offset0, int offset1) offsets, + ref (long offset0, long offset1) offsets, short remainingDepth, TableFieldContext fieldContext) { - int dvo = offsets.offset0; - int ovo = offsets.offset1; + long dvo = offsets.offset0; + long ovo = offsets.offset1; uint discriminatorCount = memory.ReadUInt(dvo); uint offsetCount = memory.ReadUInt(ovo); diff --git a/src/FlatSharp/FlatSharp.csproj b/src/FlatSharp/FlatSharp.csproj index 4167cb60..ec9baf71 100644 --- a/src/FlatSharp/FlatSharp.csproj +++ b/src/FlatSharp/FlatSharp.csproj @@ -33,13 +33,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - @@ -58,4 +51,8 @@ True + + + + diff --git a/src/FlatSharp/Serialization/CSharpHelpers.cs b/src/FlatSharp/Serialization/CSharpHelpers.cs index 39fd5540..767ea581 100644 --- a/src/FlatSharp/Serialization/CSharpHelpers.cs +++ b/src/FlatSharp/Serialization/CSharpHelpers.cs @@ -26,6 +26,7 @@ internal static class CSharpHelpers { internal const string Net7PreprocessorVariable = "NET7_0_OR_GREATER"; internal const string Net8PreprocessorVariable = "NET8_0_OR_GREATER"; + internal const string Net9PreprocessorVariable = "NET9_0_OR_GREATER"; /// /// Shortcut for GetGlobalCompilableTypeName diff --git a/src/FlatSharp/Serialization/DeserializeClassDefinition.cs b/src/FlatSharp/Serialization/DeserializeClassDefinition.cs index 58a02339..3804a55b 100644 --- a/src/FlatSharp/Serialization/DeserializeClassDefinition.cs +++ b/src/FlatSharp/Serialization/DeserializeClassDefinition.cs @@ -74,7 +74,7 @@ public DeserializeClassDefinition( // maintain reference to buffer. this.instanceFieldDefinitions[InputBufferVariableName] = $"private TInputBuffer {InputBufferVariableName};"; - this.instanceFieldDefinitions[OffsetVariableName] = $"private int {OffsetVariableName};"; + this.instanceFieldDefinitions[OffsetVariableName] = $"private long {OffsetVariableName};"; this.instanceFieldDefinitions[RemainingDepthVariableName] = $"private short {RemainingDepthVariableName};"; this.initializeStatements.Add($"this.{InputBufferVariableName} = buffer;"); @@ -152,7 +152,7 @@ private void AddReadMethod( {GetAggressiveInliningAttribute()} private static {typeName} {GetReadIndexMethodName(itemModel)}( TInputBuffer buffer, - int offset, + long offset, {this.vtableTypeName} vtable, short remainingDepth) {{ @@ -163,7 +163,7 @@ private void AddReadMethod( private void AddWriteThroughMethod(ItemMemberModel itemModel, ParserCodeGenContext parserContext) { var context = parserContext.GetWriteThroughContext( - $"buffer.{nameof(IInputBuffer.GetSpan)}()", + $"destination", "value", "offset"); @@ -171,10 +171,11 @@ private void AddWriteThroughMethod(ItemMemberModel itemModel, ParserCodeGenConte {GetAggressiveInliningAttribute()} private static void {GetWriteMethodName(itemModel)}( TInputBuffer buffer, - int offset, + long offset, {itemModel.GetNullableAnnotationTypeName(this.typeModel.SchemaType)} value, {this.vtableTypeName} vtable) {{ + var destination = buffer.{nameof(IInputBuffer.GetSpan)}(); {itemModel.CreateWriteThroughBody(context, "vtable")} }}"); } @@ -362,7 +363,7 @@ protected virtual string GetCtorMethodDefinition(string onDeserializedStatement, [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] #endif [{typeof(MethodImplAttribute).GetGlobalCompilableTypeName()}({typeof(MethodImplOptions).GetGlobalCompilableTypeName()}.AggressiveInlining)] - public {this.ClassName}(TInputBuffer buffer, int offset, short remainingDepth) + public {this.ClassName}(TInputBuffer buffer, long offset, short remainingDepth) : base({baseCtorParams}) {{ {string.Join("\r\n", this.initializeStatements)} diff --git a/src/FlatSharp/Serialization/ParserCodeGenContext.cs b/src/FlatSharp/Serialization/ParserCodeGenContext.cs index c8821a10..c062ad07 100644 --- a/src/FlatSharp/Serialization/ParserCodeGenContext.cs +++ b/src/FlatSharp/Serialization/ParserCodeGenContext.cs @@ -121,12 +121,11 @@ public string GetParseInvocation(Type type) return sb.ToString(); } - public SerializationCodeGenContext GetWriteThroughContext(string spanVariableName, string valueVariableName, string offsetVariableName) + public SerializationCodeGenContext GetWriteThroughContext(string targetVariableName, string valueVariableName, string offsetVariableName) { return new SerializationCodeGenContext( "null", - spanVariableName, - "default(SpanWriter)", + targetVariableName, valueVariableName, offsetVariableName, this.TableFieldContextVariableName, diff --git a/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs b/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs index 33c4fe7b..056a02e5 100644 --- a/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs +++ b/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs @@ -41,8 +41,12 @@ internal class RoslynSerializerGenerator .Cast() .Distinct() .ToList(); - -#if NET8_0_OR_GREATER + +#if NET9_0_OR_GREATER + private static readonly CSharpParseOptions ParseOptions = new CSharpParseOptions( + LanguageVersion.CSharp13, + preprocessorSymbols: new[] { CSharpHelpers.Net7PreprocessorVariable, CSharpHelpers.Net8PreprocessorVariable, CSharpHelpers.Net9PreprocessorVariable }); +#elif NET8_0_OR_GREATER private static readonly CSharpParseOptions ParseOptions = new CSharpParseOptions( LanguageVersion.CSharp11, preprocessorSymbols: new[] { CSharpHelpers.Net7PreprocessorVariable, CSharpHelpers.Net8PreprocessorVariable }); @@ -419,20 +423,19 @@ private HashSet TraverseAssemblyReferenceGraph() { writeFileId = $""" context.Offset = 8; - target[7] = {(byte)fileId[3]}; - target[6] = {(byte)fileId[2]}; - target[5] = {(byte)fileId[1]}; - target[4] = {(byte)fileId[0]}; + destination[7] = {(byte)fileId[3]}; + destination[6] = {(byte)fileId[2]}; + destination[5] = {(byte)fileId[1]}; + destination[4] = {(byte)fileId[0]}; """; } string methodText = $@" - public void Write(TSpanWriter writer, Span target, {CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root, SerializationContext context) - where TSpanWriter : ISpanWriter + public void Write({typeof(BigSpan).GGCTN()} destination, {rootType.GGCTN()} root, SerializationContext context) {{ {writeFileId} - {parts.@namespace}.{parts.className}.{parts.methodName}(writer, target, root, 0, context); + {parts.@namespace}.{parts.className}.{parts.methodName}(destination, root, 0, context); }} "; bodyParts.Add(methodText); @@ -501,41 +504,7 @@ public int GetMaxSize({CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root namespace {resolvedName.@namespace} {{ {(this.options.EnableFileVisibility ? "file" : "internal")} class {resolvedName.name} : {nameof(IGeneratedSerializer)}<{rootType.GetGlobalCompilableTypeName()}> - {{ - // Method generated to help AOT compilers make good decisions about generics. - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - public void __AotHelper() - {{ - this.Write(default!, new byte[10], default!, default!); - this.Write(default!, new byte[10], default!, default!); - - this.ParseLazy(default!, default); - this.ParseLazy(default!, default); - this.ParseLazy(default!, default); - this.ParseLazy(default!, default); - this.ParseLazy(default!, default); - - this.ParseProgressive(default!, default); - this.ParseProgressive(default!, default); - this.ParseProgressive(default!, default); - this.ParseProgressive(default!, default); - this.ParseProgressive(default!, default); - - this.ParseGreedy(default!, default); - this.ParseGreedy(default!, default); - this.ParseGreedy(default!, default); - this.ParseGreedy(default!, default); - this.ParseGreedy(default!, default); - - this.ParseGreedyMutable(default!, default); - this.ParseGreedyMutable(default!, default); - this.ParseGreedyMutable(default!, default); - this.ParseGreedyMutable(default!, default); - this.ParseGreedyMutable(default!, default); - - {typeof(FSThrow).GGCTN()}.{nameof(FSThrow.InvalidOperation_AotHelper)}(); - }} - + {{ [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public {resolvedName.name}() {{ @@ -593,7 +562,7 @@ internal string ImplementHelperClass(ITypeModel typeModel, IEnumerable( - TSpanWriter {context.SpanWriterVariableName}, - Span {context.SpanVariableName}, + internal static void {DefaultMethodNameResolver.ResolveSerialize(typeModel).methodName}( + BigSpan {context.SpanVariableName}, {CSharpHelpers.GetGlobalCompilableTypeName(typeModel.ClrType)} {context.ValueVariableName}, {GetVTableOffsetVariableType(typeModel.PhysicalLayout.Length)} {context.OffsetVariableName} {serializationContextParameter} - {tableFieldContextParameter}) where TSpanWriter : ISpanWriter + {tableFieldContextParameter}) {{ {method.MethodBody} }} @@ -792,11 +760,11 @@ private static string GetVTableOffsetVariableType(int vtableLength) { if (vtableLength == 1) { - return "int"; + return "long"; } else { - return $"ref ({string.Join(", ", Enumerable.Range(0, vtableLength).Select(x => $"int offset{x}"))})"; + return $"ref ({string.Join(", ", Enumerable.Range(0, vtableLength).Select(x => $"long offset{x}"))})"; } } } diff --git a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs index d6af83d9..df6dce05 100644 --- a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs +++ b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs @@ -27,7 +27,6 @@ public record SerializationCodeGenContext public SerializationCodeGenContext( string serializationContextVariableName, string spanVariableName, - string spanWriterVariableName, string valueVariableName, string offsetVariableName, string tableFieldContextVariableName, @@ -37,7 +36,6 @@ public SerializationCodeGenContext( IReadOnlyDictionary> allFieldContexts) { this.SerializationContextVariableName = serializationContextVariableName; - this.SpanWriterVariableName = spanWriterVariableName; this.SpanVariableName = spanVariableName; this.ValueVariableName = valueVariableName; this.OffsetVariableName = offsetVariableName; @@ -59,15 +57,10 @@ public SerializationCodeGenContext( public string TableFieldContextVariableName { get; init; } /// - /// The variable name of the span. Represents a value. + /// The variable name of the span. Represents a reference. /// public string SpanVariableName { get; init; } - - /// - /// The variable name of the span writer. Represents a value. - /// - public string SpanWriterVariableName { get; init; } - + /// /// The variable name of the current value to serialize. /// @@ -104,6 +97,7 @@ public SerializationCodeGenContext( public string GetSerializeInvocation(Type type) { ITypeModel typeModel = this.TypeModelContainer.CreateTypeModel(type); + string byRef = string.Empty; if (this.IsOffsetByRef) { @@ -113,7 +107,7 @@ public string GetSerializeInvocation(Type type) StringBuilder sb = new StringBuilder(); var methodParts = DefaultMethodNameResolver.ResolveSerialize(typeModel); - sb.Append($"{methodParts.@namespace}.{methodParts.className}.{methodParts.methodName}({this.SpanWriterVariableName}, {this.SpanVariableName}, {this.ValueVariableName}, {byRef}{this.OffsetVariableName}"); + sb.Append($"{methodParts.@namespace}.{methodParts.className}.{methodParts.methodName}({this.SpanVariableName}, {this.ValueVariableName}, {byRef}{this.OffsetVariableName}"); if (typeModel.SerializeMethodRequiresContext) { diff --git a/src/FlatSharp/TypeModel/ITypeModel.cs b/src/FlatSharp/TypeModel/ITypeModel.cs index 775f55ce..423836be 100644 --- a/src/FlatSharp/TypeModel/ITypeModel.cs +++ b/src/FlatSharp/TypeModel/ITypeModel.cs @@ -211,4 +211,10 @@ public interface ITypeModel /// Gets the fully qualified name of the deserialized type. /// string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName); + + /// + /// Attempts to get an invocation that can serialize this type model into the given span unsafely (ie, without bounds checks). + /// This is only appropriate if the target buffer has already been bounds-checked. + /// + bool TryGetUnsafeSerializeInvocation(string spanVariableName, string valueVariableName, string offsetVariableName, [NotNullWhen(true)] out string? invocation); } diff --git a/src/FlatSharp/TypeModel/RuntimeTypeModel.cs b/src/FlatSharp/TypeModel/RuntimeTypeModel.cs index 4fcaf10b..a979f694 100644 --- a/src/FlatSharp/TypeModel/RuntimeTypeModel.cs +++ b/src/FlatSharp/TypeModel/RuntimeTypeModel.cs @@ -197,4 +197,14 @@ public IEnumerable GetReferencedTypes() } public abstract string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName); + + /// + /// Attempts to get an invocation that can serialize this type model into the given span unsafely (ie, without bounds checks). + /// This is only appropriate if the target buffer has already been bounds-checked. + /// + public virtual bool TryGetUnsafeSerializeInvocation(string spanVariableName, string valueVariableName, string offsetVariableName, [NotNullWhen(true)] out string? invocation) + { + invocation = null; + return false; + } } diff --git a/src/FlatSharp/TypeModel/ScalarTypeModel.cs b/src/FlatSharp/TypeModel/ScalarTypeModel.cs index b48e9d4e..7ead0d9f 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModel.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModel.cs @@ -48,11 +48,6 @@ internal ScalarTypeModel( /// public override bool IsParsingInvariant => true; - /// - /// Scalars are fixed size. - /// - public override bool IsFixedSize => true; - /// /// Scalars can be part of Structs. /// @@ -79,7 +74,12 @@ internal ScalarTypeModel( public override bool SerializesInline => true; /// - /// We don't care about serialization context. + /// Scalars are of fixed size. + /// + public override bool IsFixedSize => true; + + /// + /// Scalars don't need a serialization context. /// public override bool SerializeMethodRequiresContext => false; @@ -96,7 +96,17 @@ internal ScalarTypeModel( /// /// The name of a write method for an input buffer. /// - protected abstract string SpanWriterWriteMethodName { get; } + protected abstract string WriteMethodName { get; } + + /// + /// The name of the unsafe write method for the input buffer. + /// + protected abstract string UnsafeWriteMethodName { get; } + + /// + /// Gets the type name alias (int, short, etc). + /// + protected abstract string TypeNameAlias { get; } /// /// Force children to reimplement. @@ -130,7 +140,7 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeGenContext context) { string variableName = context.ValueVariableName; - return new CodeGeneratedMethod($"{context.SpanWriterVariableName}.{this.SpanWriterWriteMethodName}({context.SpanVariableName}, {variableName}, {context.OffsetVariableName});") + return new CodeGeneratedMethod($"{context.SpanVariableName}.{this.WriteMethodName}({context.OffsetVariableName}, {variableName});") { IsMethodInline = true, }; @@ -153,4 +163,10 @@ public override string FormatDefaultValueAsLiteral(object? defaultValue) return base.FormatDefaultValueAsLiteral(defaultValue); } + + public override bool TryGetUnsafeSerializeInvocation(string spanVariableName, string valueVariableName, string offsetVariableName, [NotNullWhen(true)] out string? invocation) + { + invocation = $"{spanVariableName}.{this.UnsafeWriteMethodName}({offsetVariableName}, {valueVariableName})"; + return true; + } } diff --git a/src/FlatSharp/TypeModel/ScalarTypeModelPartials.cs b/src/FlatSharp/TypeModel/ScalarTypeModelPartials.cs index c6b19279..dda96919 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModelPartials.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModelPartials.cs @@ -14,6 +14,9 @@ * limitations under the License. */ + +using System.Buffers.Binary; + namespace FlatSharp.TypeModel; public partial class DoubleTypeModel diff --git a/src/FlatSharp/TypeModel/ScalarTypeModels.cs b/src/FlatSharp/TypeModel/ScalarTypeModels.cs index 46088677..175c7d5b 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.cs @@ -35,9 +35,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(InputBufferExtensions.ReadBool); + protected override string InputBufferReadMethodName => "ReadBool"; - protected override string SpanWriterWriteMethodName => nameof(SpanWriterExtensions.WriteBool); + protected override string WriteMethodName => "WriteBool"; + + protected override string UnsafeWriteMethodName => "UnsafeWriteBool"; + + protected override string TypeNameAlias => "bool"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -62,9 +66,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadByte); + protected override string InputBufferReadMethodName => "ReadByte"; + + protected override string WriteMethodName => "WriteByte"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteByte); + protected override string UnsafeWriteMethodName => "UnsafeWriteByte"; + + protected override string TypeNameAlias => "byte"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -89,9 +97,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadSByte); + protected override string InputBufferReadMethodName => "ReadSByte"; + + protected override string WriteMethodName => "WriteSByte"; + + protected override string UnsafeWriteMethodName => "UnsafeWriteSByte"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteSByte); + protected override string TypeNameAlias => "sbyte"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -116,9 +128,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadUShort); + protected override string InputBufferReadMethodName => "ReadUShort"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteUShort); + protected override string WriteMethodName => "WriteUShort"; + + protected override string UnsafeWriteMethodName => "UnsafeWriteUShort"; + + protected override string TypeNameAlias => "ushort"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -143,9 +159,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadShort); + protected override string InputBufferReadMethodName => "ReadShort"; + + protected override string WriteMethodName => "WriteShort"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteShort); + protected override string UnsafeWriteMethodName => "UnsafeWriteShort"; + + protected override string TypeNameAlias => "short"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -170,9 +190,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadInt); + protected override string InputBufferReadMethodName => "ReadInt"; + + protected override string WriteMethodName => "WriteInt"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteInt); + protected override string UnsafeWriteMethodName => "UnsafeWriteInt"; + + protected override string TypeNameAlias => "int"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -197,9 +221,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadUInt); + protected override string InputBufferReadMethodName => "ReadUInt"; + + protected override string WriteMethodName => "WriteUInt"; + + protected override string UnsafeWriteMethodName => "UnsafeWriteUInt"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteUInt); + protected override string TypeNameAlias => "uint"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -224,9 +252,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadLong); + protected override string InputBufferReadMethodName => "ReadLong"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteLong); + protected override string WriteMethodName => "WriteLong"; + + protected override string UnsafeWriteMethodName => "UnsafeWriteLong"; + + protected override string TypeNameAlias => "long"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -251,9 +283,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadULong); + protected override string InputBufferReadMethodName => "ReadULong"; + + protected override string WriteMethodName => "WriteULong"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteULong); + protected override string UnsafeWriteMethodName => "UnsafeWriteULong"; + + protected override string TypeNameAlias => "ulong"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -278,9 +314,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadFloat); + protected override string InputBufferReadMethodName => "ReadFloat"; + + protected override string WriteMethodName => "WriteFloat"; + + protected override string UnsafeWriteMethodName => "UnsafeWriteFloat"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteFloat); + protected override string TypeNameAlias => "float"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -305,9 +345,13 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar return true; } - protected override string InputBufferReadMethodName => nameof(IInputBuffer.ReadDouble); + protected override string InputBufferReadMethodName => "ReadDouble"; + + protected override string WriteMethodName => "WriteDouble"; + + protected override string UnsafeWriteMethodName => "UnsafeWriteDouble"; - protected override string SpanWriterWriteMethodName => nameof(ISpanWriter.WriteDouble); + protected override string TypeNameAlias => "double"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { diff --git a/src/FlatSharp/TypeModel/ScalarTypeModels.tt b/src/FlatSharp/TypeModel/ScalarTypeModels.tt index c6374c3a..48b8fad7 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.tt +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.tt @@ -23,20 +23,21 @@ <#@ output extension=".cs" #> <# - (string casedName, string typeName, string literalSpecifier, string[] fbsAliases, string writeNameClass, string readNameClass)[] types = - { - ("Bool", "bool", "", new[] { "bool" }, "SpanWriterExtensions", "InputBufferExtensions"), - ("Byte", "byte", "", new[] { "ubyte", "uint8" }, null, null), - ("SByte", "sbyte", "", new[] { "byte", "int8" }, null, null), - ("UShort", "ushort", "", new[] { "ushort", "uint16" }, null, null), - ("Short", "short", "", new[] { "short", "int16" }, null, null), - ("Int", "int", "", new[] { "int", "int32" }, null, null), - ("UInt", "uint", "u", new[] { "uint", "uint32" }, null, null), - ("Long", "long", "L", new[] { "long", "int64" }, null, null), - ("ULong", "ulong", "ul", new[] { "ulong", "uint64" }, null, null), - ("Float", "float", "f", new[] { "float", "float32" }, null, null), - ("Double", "double", "d", new[] { "double", "float64" }, null, null), - }; + ( + string casedName, string commonName, string typeName, string literalSpecifier, string[] fbsAliases, string inlineWriteMethod)[] types = + { + ("Bool", "Bool", "bool", "", new[] { "bool" }, "WriteBool"), + ("UInt8", "Byte", "byte", "", new[] { "ubyte", "uint8" }, "WriteByte"), + ("Int8", "SByte", "sbyte", "", new[] { "byte", "int8" }, "WriteSByte"), + ("UInt16", "UShort", "ushort", "", new[] { "ushort", "uint16" }, "WriteUShort"), + ("Int16", "Short", "short", "", new[] { "short", "int16" }, "WriteShort"), + ("Int32", "Int", "int", "", new[] { "int", "int32" }, "WriteInt"), + ("UInt32", "UInt", "uint", "u", new[] { "uint", "uint32" }, "WriteUInt"), + ("Int64", "Long", "long", "L", new[] { "long", "int64" }, "WriteLong"), + ("UInt64", "ULong", "ulong", "ul", new[] { "ulong", "uint64" }, "WriteULong"), + ("Float32", "Float", "float", "f", new[] { "float", "float32" }, "WriteFloat"), + ("Float64", "Double", "double", "d", new[] { "double", "float64" }, "WriteDouble"), + }; #> namespace FlatSharp.TypeModel; @@ -46,7 +47,9 @@ namespace FlatSharp.TypeModel; { var casedName = pair.casedName; var typeName = pair.typeName; - var className = $"{casedName}TypeModel"; + var commonName = pair.commonName; + var className = $"{commonName}TypeModel"; + var writeMethod = pair.inlineWriteMethod; #> /// @@ -61,13 +64,17 @@ public partial class <#= className #> : ScalarTypeModel public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? comparerType) { - comparerType = typeof(<#= casedName #>SpanComparer); + comparerType = typeof(<#= commonName #>SpanComparer); return true; } - protected override string InputBufferReadMethodName => nameof(<#= pair.readNameClass ?? "IInputBuffer" #>.Read<#= casedName #>); + protected override string InputBufferReadMethodName => "Read<#= commonName #>"; + + protected override string WriteMethodName => "<#= writeMethod #>"; + + protected override string UnsafeWriteMethodName => "Unsafe<#= writeMethod #>"; - protected override string SpanWriterWriteMethodName => nameof(<#= pair.writeNameClass ?? "ISpanWriter" #>.Write<#= casedName #>); + protected override string TypeNameAlias => "<#=typeName#>"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -88,9 +95,9 @@ public class ScalarTypeModelProvider : ITypeModelProvider <# foreach (var pair in types) { - var casedName = pair.casedName; + var commonName = pair.commonName; var typeName = pair.typeName; - var className = $"{casedName}TypeModel"; + var className = $"{commonName}TypeModel"; #> if (type == typeof(<#= typeName #>)) diff --git a/src/FlatSharp/TypeModel/StringTypeModel.cs b/src/FlatSharp/TypeModel/StringTypeModel.cs index 8931e974..2eef29c2 100644 --- a/src/FlatSharp/TypeModel/StringTypeModel.cs +++ b/src/FlatSharp/TypeModel/StringTypeModel.cs @@ -95,7 +95,7 @@ public override CodeGeneratedMethod CreateGetMaxSizeMethodBody(GetMaxSizeCodeGen public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext context) { - return new CodeGeneratedMethod($"return {context.InputBufferVariableName}.{nameof(IInputBuffer.ReadString)}({context.OffsetVariableName});") + return new CodeGeneratedMethod($"return {context.InputBufferVariableName}.ReadString({context.OffsetVariableName});") { IsMethodInline = true }; @@ -114,7 +114,6 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG if (!(sharedStringWriter is null)) {{ sharedStringWriter.{nameof(ISharedStringWriter.WriteSharedString)}( - {context.SpanWriterVariableName}, {context.SpanVariableName}, {context.OffsetVariableName}, {context.ValueVariableName}, @@ -123,8 +122,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG }} }} - {context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteString)}( - {context.SpanVariableName}, + {context.SpanVariableName}.WriteString( {context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName}); @@ -134,8 +132,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG { // otherwise, we can omit that code entirely. body = $@" - {context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteString)}( - {context.SpanVariableName}, + {context.SpanVariableName}.WriteString( {context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName}); diff --git a/src/FlatSharp/TypeModel/StructTypeModel.cs b/src/FlatSharp/TypeModel/StructTypeModel.cs index 029f3bb3..58c7a69b 100644 --- a/src/FlatSharp/TypeModel/StructTypeModel.cs +++ b/src/FlatSharp/TypeModel/StructTypeModel.cs @@ -132,15 +132,17 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeGenContext context) { - List body = new List(); - body.Add($"Span scopedSpan = {context.SpanVariableName}.Slice({context.OffsetVariableName}, {this.PhysicalLayout[0].InlineSize});"); - FlatSharpInternal.Assert(!this.ClrType.IsValueType, "Value-type struct is unexpected"); + List body = new List(); + + body.Add($"var scopedTarget = {context.SpanVariableName}.Slice({context.OffsetVariableName}, {this.PhysicalLayout[0].InlineSize});"); + body.Add( $@" if ({context.ValueVariableName} is null) {{ + Span scopedSpan = scopedTarget.{nameof(BigSpan.ToSpan)}(0, {this.PhysicalLayout[0].InlineSize}); scopedSpan.Clear(); return; }} @@ -158,7 +160,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG var propContext = context with { - SpanVariableName = "scopedSpan", + SpanVariableName = "scopedTarget", OffsetVariableName = $"{memberInfo.Offset}", ValueVariableName = $"{propertyAccessor}" }; diff --git a/src/FlatSharp/TypeModel/TableMemberModel.cs b/src/FlatSharp/TypeModel/TableMemberModel.cs index e5831497..65fb549f 100644 --- a/src/FlatSharp/TypeModel/TableMemberModel.cs +++ b/src/FlatSharp/TypeModel/TableMemberModel.cs @@ -268,7 +268,7 @@ private string GetFindFieldLocationBlock( string vtableVariableName) { return $@" - int {locationVariableName}; + long {locationVariableName}; {{ int relativeOffset = {vtableVariableName}.OffsetOf(buffer, {this.Index}); if (relativeOffset == 0) diff --git a/src/FlatSharp/TypeModel/TableTypeModel.cs b/src/FlatSharp/TypeModel/TableTypeModel.cs index 7d6622a2..1c414985 100644 --- a/src/FlatSharp/TypeModel/TableTypeModel.cs +++ b/src/FlatSharp/TypeModel/TableTypeModel.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Buffers.Binary; using System.Collections.Immutable; using System.Linq; using FlatSharp.Attributes; @@ -471,12 +472,13 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG // Start by asking for the worst-case number of bytes from the serializationcontext. string methodStart = $@" - int tableStart = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateSpace)}({maxInlineSize}, sizeof(int)); - {context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.SpanVariableName}, {context.OffsetVariableName}, tableStart); - int currentOffset = tableStart + sizeof(int); // skip past vtable soffset_t. + long tableStart = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateSpace)}({maxInlineSize}, sizeof(int)); + {context.SpanVariableName}.WriteUOffset({context.OffsetVariableName}, tableStart); + long currentOffset = tableStart + sizeof(int); // skip past vtable soffset_t. int vtableLength = {minVtableLength}; Span vtable = stackalloc byte[{4 + 2 * (maxIndex + 1)}]; + uint crc = 0; "; List body = new(); @@ -487,7 +489,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG // Unfortunately, this isn't readily testable since dotnet *does* zero out stackalloc memory. foreach (var deprecatedIndex in deprecatedIndexes) { - body.Add($"{context.SpanWriterVariableName}.{nameof(ISpanWriter.WriteUShort)}(vtable, 0, {GetVTablePosition(deprecatedIndex)});"); + body.Add($"{typeof(BinaryPrimitives).GGCTN()}.WriteUInt16LittleEndian(vtable.Slice({GetVTablePosition(deprecatedIndex)}), 0);"); } body.AddRange(getters); @@ -495,15 +497,17 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG // We probably over-allocated. Figure out by how much and back up the cursor. // Then we can write the vtable. - body.Add("int tableLength = currentOffset - tableStart;"); + body.Add("long tableLength = currentOffset - tableStart;"); body.Add($"{context.SerializationContextVariableName}.{nameof(SerializationContext.Offset)} -= {maxInlineSize} - tableLength;"); // Finish vtable. - body.Add($"{context.SpanWriterVariableName}.{nameof(ISpanWriter.WriteUShort)}(vtable, (ushort)vtableLength, 0);"); - body.Add($"{context.SpanWriterVariableName}.{nameof(ISpanWriter.WriteUShort)}(vtable, (ushort)tableLength, sizeof(ushort));"); + body.Add($"{typeof(SerializationHelpers).GGCTN()}.{nameof(SerializationHelpers.ComputeCrc)}(ref crc, (ushort)vtableLength);"); + body.Add($"{typeof(SerializationHelpers).GGCTN()}.{nameof(SerializationHelpers.ComputeCrc)}(ref crc, (ushort)tableLength);"); + body.Add($"{typeof(BinaryPrimitives).GGCTN()}.WriteUInt16LittleEndian(vtable, (ushort)vtableLength);"); + body.Add($"{typeof(BinaryPrimitives).GGCTN()}.WriteUInt16LittleEndian(vtable.Slice(sizeof(ushort)), (ushort)tableLength);"); - body.Add($"int vtablePosition = {context.SerializationContextVariableName}.{nameof(SerializationContext.FinishVTable)}({context.SpanVariableName}, vtable.Slice(0, vtableLength));"); - body.Add($"{context.SpanWriterVariableName}.{nameof(SpanWriter.WriteInt)}({context.SpanVariableName}, tableStart - vtablePosition, tableStart);"); + body.Add($"long vtablePosition = {context.SerializationContextVariableName}.{nameof(SerializationContext.FinishVTable)}({context.SpanVariableName}, crc, vtable.Slice(0, vtableLength));"); + body.Add($"{context.SpanVariableName}.WriteInt(tableStart, checked((int)(tableStart - vtablePosition)));"); body.AddRange(writeBlocks); @@ -576,10 +580,15 @@ private string GetPrepareSerializeBlock( } } - string writeVTableBlock = - $"{context.SpanWriterVariableName}.{nameof(ISpanWriter.WriteUShort)}(vtable, (ushort)({OffsetVariableName(index, i)} - tableStart), {vTableIndex});"; + string writeVTableBlock = @$" + {{ + ushort fieldOffset = (ushort)({OffsetVariableName(index, i)} - tableStart); + {typeof(BinaryPrimitives).GGCTN()}.WriteUInt16LittleEndian(vtable.Slice({vTableIndex}), fieldOffset); + {typeof(SerializationHelpers).GGCTN()}.{nameof(SerializationHelpers.ComputeCrc)}(ref crc, fieldOffset); + }}"; string inlineSerialize = string.Empty; + if (memberModel.ItemTypeModel.SerializesInline) { inlineSerialize = this.GetSerializeCoreBlock( @@ -611,19 +620,17 @@ private string GetSerializeCoreBlock( string sortInvocation = string.Empty; if (memberModel.IsSortedVector) { - var (tableModel, keyMember, spanComparerType) = ValidateSortedVector(memberModel)!.Value; + var (_, keyMember, spanComparerType) = ValidateSortedVector(memberModel)!.Value; string inlineSize = keyMember.ItemTypeModel.IsFixedSize ? keyMember.ItemTypeModel.PhysicalLayout[0].InlineSize.ToString() : "null"; sortInvocation = @$" {context.SerializationContextVariableName}.{nameof(SerializationContext.AddPostSerializeAction)}( - (tempSpan, ctx) => - {nameof(SortedVectorHelpersInternal)}.{nameof(SortedVectorHelpersInternal.SortVector)}( - tempSpan, + new VectorSortAction<{spanComparerType.GGCTN()}>( {OffsetVariableName(index, 0)}, {keyMember.Index}, {inlineSize}, - new {CSharpHelpers.GetCompilableTypeName(spanComparerType)}({keyMember.DefaultValueLiteral})));"; + new {spanComparerType.GGCTN()}({keyMember.DefaultValueLiteral})));"; } // NULL FORGIVENESS @@ -635,20 +642,29 @@ private string GetSerializeCoreBlock( string serializeInvocation; string offsetTuple = string.Empty; + valueVariableName = $"{valueVariableName}{nullForgiving}"; + if (vtableEntries == 1) { - serializeInvocation = (context with + if (memberModel.ItemTypeModel.TryGetUnsafeSerializeInvocation(context.SpanVariableName, valueVariableName, OffsetVariableName(index, 0), out string? invocation)) { - ValueVariableName = $"{valueVariableName}{nullForgiving}", - OffsetVariableName = $"{OffsetVariableName(index, 0)}", - TableFieldContextVariableName = $"{this.MetadataClassName}.{memberModel.PropertyInfo.Name}", - }).GetSerializeInvocation(memberModel.ItemTypeModel.ClrType); + serializeInvocation = invocation + ";"; + } + else + { + serializeInvocation = (context with + { + ValueVariableName = valueVariableName, + OffsetVariableName = $"{OffsetVariableName(index, 0)}", + TableFieldContextVariableName = $"{this.MetadataClassName}.{memberModel.PropertyInfo.Name}", + }).GetSerializeInvocation(memberModel.ItemTypeModel.ClrType); + } } else { serializeInvocation = (context with { - ValueVariableName = $"{valueVariableName}{nullForgiving}", + ValueVariableName = valueVariableName, OffsetVariableName = $"offsetTuple", IsOffsetByRef = true, TableFieldContextVariableName = $"{this.MetadataClassName}.{memberModel.PropertyInfo.Name}", diff --git a/src/FlatSharp/TypeModel/UnionTypeModel.cs b/src/FlatSharp/TypeModel/UnionTypeModel.cs index f3663e1c..7e570452 100644 --- a/src/FlatSharp/TypeModel/UnionTypeModel.cs +++ b/src/FlatSharp/TypeModel/UnionTypeModel.cs @@ -150,8 +150,8 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c } string body = $@" - byte discriminator = {context.InputBufferVariableName}.{nameof(IInputBuffer.ReadByte)}({context.OffsetVariableName}.offset0); - int offsetLocation = {context.OffsetVariableName}.offset1; + byte discriminator = {context.InputBufferVariableName}.ReadByte({context.OffsetVariableName}.offset0); + long offsetLocation = {context.OffsetVariableName}.offset1; switch (discriminator) {{ @@ -182,7 +182,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG // a pointer to a struct. inlineAdjustment =$@" var writeOffset = context.{nameof(SerializationContext.AllocateSpace)}({elementModel.PhysicalLayout.Single().InlineSize}, {elementModel.PhysicalLayout.Single().Alignment}); - {context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}(span, {context.OffsetVariableName}.offset1, writeOffset);"; + {context.SpanVariableName}.WriteUOffset({context.OffsetVariableName}.offset1, writeOffset);"; } else { @@ -210,10 +210,9 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string serializeBlock = $@" byte discriminatorValue = {context.ValueVariableName}.Discriminator; - {context.SpanWriterVariableName}.{nameof(SpanWriter.WriteByte)}( - {context.SpanVariableName}, - discriminatorValue, - {context.OffsetVariableName}.offset0); + {context.SpanVariableName}.{nameof(BigSpan.WriteByte)}( + {context.OffsetVariableName}.offset0, + discriminatorValue); switch (discriminatorValue) {{ diff --git a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs index 0b758f60..284dd2cb 100644 --- a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs +++ b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs @@ -143,7 +143,7 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c {StrykerSuppressor.SuppressNextLine("boolean")} if ({StrykerSuppressor.BitConverterTypeName}.IsLittleEndian) {{ - var mem = {context.InputBufferVariableName}.{nameof(IInputBuffer.GetReadOnlySpan)}().Slice({context.OffsetVariableName}, {this.inlineSize}); + var mem = {context.InputBufferVariableName}.{nameof(IInputBuffer.GetReadOnlySpan)}().ToSpan({context.OffsetVariableName}, {this.inlineSize}); return {typeof(MemoryMarshal).GetGlobalCompilableTypeName()}.{nameof(MemoryMarshal.Read)}<{globalName}>(mem); }} else @@ -168,7 +168,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG var member = this.members[i]; var fieldContext = context with { - SpanVariableName = "sizedSpan", + SpanVariableName = "sizedTarget", OffsetVariableName = $"{member.offset}", ValueVariableName = $"{context.ValueVariableName}.{member.accessor}", }; @@ -177,23 +177,24 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG } string body; - string slice = $"Span sizedSpan = {context.SpanVariableName}.Slice({context.OffsetVariableName}, {this.inlineSize});"; + string slice = $"var sizedTarget = {context.SpanVariableName}.Slice({context.OffsetVariableName}, {this.inlineSize});"; if (this.CanMarshalOnSerialize && context.Options.EnableValueStructMemoryMarshalDeserialization) { body = $@" - {slice} - {StrykerSuppressor.SuppressNextLine("boolean")} if ({StrykerSuppressor.BitConverterTypeName}.IsLittleEndian) {{ + var sizedSpan = {context.SpanVariableName}.ToSpan({context.OffsetVariableName}, {this.inlineSize}); + #if {CSharpHelpers.Net8PreprocessorVariable} - {typeof(MemoryMarshal).GetGlobalCompilableTypeName()}.Write(sizedSpan, in {context.ValueVariableName}); + {typeof(MemoryMarshal).GGCTN()}.Write(sizedSpan, in {context.ValueVariableName}); #else - {typeof(MemoryMarshal).GetGlobalCompilableTypeName()}.Write(sizedSpan, ref {context.ValueVariableName}); + {typeof(MemoryMarshal).GGCTN()}.Write(sizedSpan, ref {context.ValueVariableName}); #endif }} else {{ + {slice} {string.Join("\r\n", propertyStatements)} }} "; @@ -214,7 +215,7 @@ private CodeGeneratedMethod CreateExternalSerializeMethod(SerializationCodeGenCo string body = $@" FlatSharpInternal.AssertLittleEndian(); FlatSharpInternal.AssertSizeOf<{globalName}>({this.inlineSize}); - Span sizedSpan = {context.SpanVariableName}.Slice({context.OffsetVariableName}, {this.inlineSize}); + Span sizedSpan = {context.SpanVariableName}.{nameof(BigSpan.ToSpan)}({context.OffsetVariableName}, {this.inlineSize}); #if NET8_0_OR_GREATER {typeof(MemoryMarshal).GetGlobalCompilableTypeName()}.Write(sizedSpan, in {context.ValueVariableName}); @@ -223,7 +224,7 @@ private CodeGeneratedMethod CreateExternalSerializeMethod(SerializationCodeGenCo #endif "; - return new CodeGeneratedMethod(body) { IsMethodInline = true }; + return new CodeGeneratedMethod(body); } private CodeGeneratedMethod CreateExternalParseMethod(ParserCodeGenContext context) @@ -233,7 +234,7 @@ private CodeGeneratedMethod CreateExternalParseMethod(ParserCodeGenContext conte string body = $@" FlatSharpInternal.AssertLittleEndian(); FlatSharpInternal.AssertSizeOf<{globalName}>({this.inlineSize}); - var slice = {context.InputBufferVariableName}.{nameof(IInputBuffer.GetReadOnlySpan)}().Slice({context.OffsetVariableName}, {this.inlineSize}); + var slice = {context.InputBufferVariableName}.{nameof(IInputBuffer.GetReadOnlySpan)}().ToSpan({context.OffsetVariableName}, {this.inlineSize}); return {typeof(MemoryMarshal).GetGlobalCompilableTypeName()}.Read<{globalName}>(slice); "; diff --git a/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs b/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs index cb4a117f..0655cf7c 100644 --- a/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs +++ b/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs @@ -146,9 +146,9 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string body = $@" int count = {context.ValueVariableName}.{this.LengthPropertyName}; - int vectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}({itemTypeModel.PhysicalLayout[0].Alignment}, count, {this.PaddedMemberInlineSize}); - {context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.SpanVariableName}, {context.OffsetVariableName}, vectorOffset); - {context.SpanWriterVariableName}.{nameof(SpanWriter.WriteInt)}({context.SpanVariableName}, count, vectorOffset); + long vectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}({itemTypeModel.PhysicalLayout[0].Alignment}, count, {this.PaddedMemberInlineSize}); + {context.SpanVariableName}.WriteUOffset({context.OffsetVariableName}, vectorOffset); + {context.SpanVariableName}.WriteInt(vectorOffset, count); vectorOffset += sizeof(int); {this.CreateLoop(context.Options, context.ValueVariableName, "count", "current", loopBody)}"; diff --git a/src/FlatSharp/TypeModel/Vectors/MemoryVectorTypeModel.cs b/src/FlatSharp/TypeModel/Vectors/MemoryVectorTypeModel.cs index 1e2ed985..6a9ef025 100644 --- a/src/FlatSharp/TypeModel/Vectors/MemoryVectorTypeModel.cs +++ b/src/FlatSharp/TypeModel/Vectors/MemoryVectorTypeModel.cs @@ -78,7 +78,7 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeGenContext context) { string body = - $"{context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteReadOnlyByteMemoryBlock)}({context.SpanVariableName}, {context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName});"; + $"{context.SpanVariableName}.WriteReadOnlyByteMemoryBlock({context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName});"; return new CodeGeneratedMethod(body); } diff --git a/src/FlatSharp/TypeModel/Vectors/UnityNativeArrayVectorTypeModel.cs b/src/FlatSharp/TypeModel/Vectors/UnityNativeArrayVectorTypeModel.cs index cf893221..f8f21cce 100644 --- a/src/FlatSharp/TypeModel/Vectors/UnityNativeArrayVectorTypeModel.cs +++ b/src/FlatSharp/TypeModel/Vectors/UnityNativeArrayVectorTypeModel.cs @@ -94,8 +94,7 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeGenContext context) { string writeNativeArray = $@" - {context.SpanWriterVariableName}.UnsafeWriteSpan( - {context.SpanVariableName}, + {context.SpanVariableName}.{nameof(BigSpan.UnsafeWriteSpan)}( {context.ValueVariableName}.AsSpan(), {context.OffsetVariableName}, {this.ItemTypeModel.PhysicalLayout[0].Alignment}, diff --git a/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs b/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs index 9e5245d9..58694a63 100644 --- a/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs +++ b/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs @@ -122,14 +122,14 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string body = $@" int count = {context.ValueVariableName}.{this.LengthPropertyName}; - int discriminatorVectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}(sizeof(byte), count, sizeof(byte)); - {context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.SpanVariableName}, {context.OffsetVariableName}.offset0, discriminatorVectorOffset); - {context.SpanWriterVariableName}.{nameof(SpanWriter.WriteInt)}({context.SpanVariableName}, count, discriminatorVectorOffset); + long discriminatorVectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}(sizeof(byte), count, sizeof(byte)); + {context.SpanVariableName}.{nameof(BigSpan.WriteUOffset)}({context.OffsetVariableName}.offset0, discriminatorVectorOffset); + {context.SpanVariableName}.{nameof(BigSpan.WriteInt)}(discriminatorVectorOffset, count); discriminatorVectorOffset += sizeof(int); - int offsetVectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}(sizeof(int), count, sizeof(int)); - {context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.SpanVariableName}, {context.OffsetVariableName}.offset1, offsetVectorOffset); - {context.SpanWriterVariableName}.{nameof(SpanWriter.WriteInt)}({context.SpanVariableName}, count, offsetVectorOffset); + long offsetVectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}(sizeof(int), count, sizeof(int)); + {context.SpanVariableName}.{nameof(BigSpan.WriteUOffset)}({context.OffsetVariableName}.offset1, offsetVectorOffset); + {context.SpanVariableName}.{nameof(BigSpan.WriteInt)}(offsetVectorOffset, count); offsetVectorOffset += sizeof(int); for (int i = 0; i < count; ++i) diff --git a/src/Tests/CompileTests/NativeAot/Program.cs b/src/Tests/CompileTests/NativeAot/Program.cs index 85cab241..d3580068 100644 --- a/src/Tests/CompileTests/NativeAot/Program.cs +++ b/src/Tests/CompileTests/NativeAot/Program.cs @@ -49,11 +49,11 @@ private static void RunBenchmark() } }; - int maxSize = Root.Serializer.GetMaxSize(root); + long maxSize = Root.Serializer.GetMaxSize(root); Console.WriteLine("Max size: " + maxSize); byte[] buffer = new byte[maxSize]; - int bytesWritten = 0; + long bytesWritten = 0; Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 200; ++i) @@ -321,82 +321,27 @@ public CustomInputBuffer(IInputBuffer inner) public bool IsPinned => inner.IsPinned; - public int Length => inner.Length; + public long Length => inner.Length; - public Memory GetMemory() + public Memory GetMemory(long start, int length) { - return inner.GetMemory(); + return inner.GetMemory(start, length); } - public ReadOnlyMemory GetReadOnlyMemory() + public ReadOnlyMemory GetReadOnlyMemory(long start, int length) { - return inner.GetReadOnlyMemory(); + return inner.GetReadOnlyMemory(start, length); } - public ReadOnlySpan GetReadOnlySpan() + public BigReadOnlySpan GetReadOnlySpan() { return inner.GetReadOnlySpan(); } - public Span GetSpan() + public BigSpan 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/FlatSharpCompilerTests/FullTests.cs b/src/Tests/FlatSharpCompilerTests/FullTests.cs index 5db44b48..14437e67 100644 --- a/src/Tests/FlatSharpCompilerTests/FullTests.cs +++ b/src/Tests/FlatSharpCompilerTests/FullTests.cs @@ -22,7 +22,7 @@ public class FullTests { #if NET5_0_OR_GREATER /// - /// Tests that we can compile a complex schema. + /// Tests that we can compile a complex schema.r /// [Fact] public void FullTest() diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs new file mode 100644 index 00000000..8aa25771 --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs @@ -0,0 +1,202 @@ +/* + * Copyright 2024 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using FlatSharp.Internal; +using System.IO; + +namespace FlatSharpEndToEndTests.ClassLib; + +[TestClass] +public class BigSpanTests +{ + private delegate T SpanReadValue(Span span) where T : unmanaged; + private delegate T BigSpanReadValue(BigSpan span) where T : unmanaged; + + private delegate void SpanWriteValue(Span span, T value) where T : unmanaged; + private delegate void BigSpanWriteValue(BigSpan span, T value) where T : unmanaged; + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void ReadWrite_Bool(bool value) => TestReadWrite( + s => s[0] != 0, + bs => bs.ReadBool(0), + (s, v) => s[0] = v ? (byte)1 : (byte)0, + (bs, v) => bs.WriteBool(0, v), + value); + + [TestMethod] + [DataRow((byte)0)] + [DataRow((byte)1)] + [DataRow(byte.MaxValue)] + public void ReadWrite_Byte(byte value) => TestReadWrite( + s => s[0], + bs => bs.ReadByte(0), + (s, v) => s[0] = v, + (bs, v) => bs.WriteByte(0, v), + value); + + [TestMethod] + [DataRow(sbyte.MinValue)] + [DataRow((sbyte)-1)] + [DataRow((sbyte)0)] + [DataRow((sbyte)1)] + [DataRow(sbyte.MaxValue)] + public void ReadWrite_SByte(sbyte value) => TestReadWrite( + s => (sbyte)s[0], + bs => bs.ReadSByte(0), + (s, v) => s[0] = (byte)v, + (bs, v) => bs.WriteSByte(0, v), + value); + + [TestMethod] + [DataRow(short.MinValue)] + [DataRow((short)-1)] + [DataRow((short)0)] + [DataRow((short)1)] + [DataRow(short.MaxValue)] + public void ReadWrite_Short(short value) => TestReadWrite( + s => BinaryPrimitives.ReadInt16LittleEndian(s), + bs => bs.ReadShort(0), + (s, v) => BinaryPrimitives.WriteInt16LittleEndian(s, v), + (bs, v) => bs.WriteShort(0, v), + value); + + [TestMethod] + [DataRow(ushort.MinValue)] + [DataRow((ushort)1)] + [DataRow(ushort.MaxValue)] + public void ReadWrite_UShort(ushort value) => TestReadWrite( + s => BinaryPrimitives.ReadUInt16LittleEndian(s), + bs => bs.ReadUShort(0), + (s, v) => BinaryPrimitives.WriteUInt16LittleEndian(s, v), + (bs, v) => bs.WriteUShort(0, v), + value); + + [TestMethod] + [DataRow(int.MinValue)] + [DataRow((int)-1)] + [DataRow((int)0)] + [DataRow((int)1)] + [DataRow(int.MaxValue)] + public void ReadWrite_Int(int value) => TestReadWrite( + s => BinaryPrimitives.ReadInt32LittleEndian(s), + bs => bs.ReadInt(0), + (s, v) => BinaryPrimitives.WriteInt32LittleEndian(s, v), + (bs, v) => bs.WriteInt(0, v), + value); + + [TestMethod] + [DataRow(uint.MinValue)] + [DataRow((uint)1)] + [DataRow(uint.MaxValue)] + public void ReadWrite_UInt(uint value) => TestReadWrite( + s => BinaryPrimitives.ReadUInt32LittleEndian(s), + bs => bs.ReadUInt(0), + (s, v) => BinaryPrimitives.WriteUInt32LittleEndian(s, v), + (bs, v) => bs.WriteUInt(0, v), + value); + + [TestMethod] + [DataRow(long.MinValue)] + [DataRow((long)-1)] + [DataRow((long)0)] + [DataRow((long)1)] + [DataRow(long.MaxValue)] + public void ReadWrite_Long(long value) => TestReadWrite( + s => BinaryPrimitives.ReadInt64LittleEndian(s), + bs => bs.ReadLong(0), + (s, v) => BinaryPrimitives.WriteInt64LittleEndian(s, v), + (bs, v) => bs.WriteLong(0, v), + value); + + [TestMethod] + [DataRow(ulong.MinValue)] + [DataRow((ulong)1)] + [DataRow(ulong.MaxValue)] + public void ReadWrite_ULong(ulong value) => TestReadWrite( + s => BinaryPrimitives.ReadUInt64LittleEndian(s), + bs => bs.ReadULong(0), + (s, v) => BinaryPrimitives.WriteUInt64LittleEndian(s, v), + (bs, v) => bs.WriteULong(0, v), + value); + +#if NET8_0_OR_GREATER +#if !AOT + [TestMethod] + [DataRow(float.MinValue)] + [DataRow(-1f)] + [DataRow(0f)] + [DataRow(1f)] + [DataRow(float.MaxValue)] + public void ReadWrite_Float(float value) => TestReadWrite( + s => BinaryPrimitives.ReadSingleLittleEndian(s), + bs => bs.ReadFloat(0), + (s, v) => BinaryPrimitives.WriteSingleLittleEndian(s, v), + (bs, v) => bs.WriteFloat(0, v), + value); +#endif + + [TestMethod] + [DataRow(double.MinValue)] + [DataRow(-1d)] + [DataRow(0d)] + [DataRow(1d)] + [DataRow(double.MaxValue)] + public void ReadWrite_Double(double value) => TestReadWrite( + s => BinaryPrimitives.ReadDoubleLittleEndian(s), + bs => bs.ReadDouble(0), + (s, v) => BinaryPrimitives.WriteDoubleLittleEndian(s, v), + (bs, v) => bs.WriteDouble(0, v), + value); +#endif + + private void TestReadWrite( + SpanReadValue spanRead, + BigSpanReadValue bigSpanRead, + SpanWriteValue spanWrite, + BigSpanWriteValue bigSpanWrite, + T value) + where T : unmanaged + { + int size = Unsafe.SizeOf(); + + Span empty = stackalloc byte[size]; + empty.Clear(); + + Span fullSpan = stackalloc byte[3 * size]; + + Span span = fullSpan.Slice(size, size); + BigSpan bigSpan = new(span); + + span.Fill(0); + Assert.AreEqual(bigSpanRead(bigSpan), spanRead(span)); + + spanWrite(span, value); + Assert.AreEqual(value, bigSpanRead(bigSpan)); + Assert.AreEqual(value, spanRead(span)); + + span.Fill(0); + bigSpanWrite(bigSpan, value); + + Assert.AreEqual(value, spanRead(span)); + Assert.AreEqual(value, bigSpanRead(bigSpan)); + + // Ensure the guard bytes were not written. + Assert.IsTrue(empty.SequenceEqual(fullSpan.Slice(0, size))); + Assert.IsTrue(empty.SequenceEqual(fullSpan.Slice(2 * size, size))); + } +} diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferSerializerNonGenericTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferSerializerNonGenericTests.cs index 8cc9d4de..f012a110 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferSerializerNonGenericTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferSerializerNonGenericTests.cs @@ -59,7 +59,7 @@ public void NonGenericSerializer_FromInstance() Assert.ThrowsException(() => serializer.Write(bw, null)); Assert.ThrowsException(() => serializer.Write(bw, new SomeOtherTable())); - int written = serializer.Write(bw, new SomeTable { A = 3 }); + long written = serializer.Write(bw, new SomeTable { A = 3 }); Assert.IsTrue(written > 0); object parsed = serializer.Parse(bw); diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs index fc87dc26..a678543e 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs @@ -101,16 +101,16 @@ public void UseMemoryCopySerialization(FlatBufferDeserializationOption option, b // overallocate byte[] data = new byte[1024]; - int maxBytes = compiled.GetMaxSize(t); + long maxBytes = compiled.GetMaxSize(t); Assert.AreEqual(88, maxBytes); - int actualBytes = compiled.Write(data, t); + long actualBytes = compiled.Write(data, t); Assert.AreEqual(58, actualBytes); // First test: Parse the array but don't trim the buffer. This causes the underlying // buffer to be much larger than the actual data. var parsed = compiled.Parse(data); byte[] data2 = new byte[2048]; - int bytesWritten = compiled.Write(data2, parsed); + long bytesWritten = compiled.Write(data2, parsed); if (expectMemCopy) { @@ -119,7 +119,7 @@ public void UseMemoryCopySerialization(FlatBufferDeserializationOption option, b Assert.ThrowsException(() => compiled.Write(new byte[maxBytes], parsed)); // Repeat, but now using the trimmed array. - parsed = compiled.Parse(data.AsMemory().Slice(0, actualBytes)); + parsed = compiled.Parse(data.AsMemory().Slice(0, (int)actualBytes)); bytesWritten = compiled.Write(data2, parsed); Assert.AreEqual(58, bytesWritten); Assert.AreEqual(58, compiled.GetMaxSize(parsed)); @@ -174,7 +174,7 @@ public void SharedStringWriters() private class PerfectSharedStringWriter : ISharedStringWriter { - private readonly Dictionary> offsets = new(); + private readonly Dictionary> offsets = new(); public PerfectSharedStringWriter() { @@ -182,34 +182,12 @@ public PerfectSharedStringWriter() public bool IsDirty => this.offsets.Count > 0; - public void FlushWrites(TSpanWriter writer, Span data, SerializationContext context) where TSpanWriter : ISpanWriter - { - foreach (var kvp in this.offsets) - { - string value = kvp.Key; - - int stringOffset = writer.WriteAndProvisionString(data, value, context); - - for (int i = 0; i < kvp.Value.Count; ++i) - { - writer.WriteUOffset(data, kvp.Value[i], stringOffset); - } - } - - this.offsets.Clear(); - } - public void Reset() { this.offsets.Clear(); } - public void WriteSharedString( - TSpanWriter spanWriter, - Span data, - int offset, - string value, - SerializationContext context) where TSpanWriter : ISpanWriter + public void WriteSharedString(BigSpan spanWriter, long offset, string value, SerializationContext context) { if (!this.offsets.TryGetValue(value, out var list)) { @@ -219,5 +197,22 @@ public void WriteSharedString( list.Add(offset); } + + public void FlushWrites(BigSpan writer, SerializationContext context) + { + foreach (var kvp in this.offsets) + { + string value = kvp.Key; + + long stringOffset = writer.WriteAndProvisionString(value, context); + + for (int i = 0; i < kvp.Value.Count; ++i) + { + writer.WriteUOffset(kvp.Value[i], stringOffset); + } + } + + this.offsets.Clear(); + } } } diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/VTableTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/VTableTests.cs index e321a9b2..3bb52cd4 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/VTableTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/VTableTests.cs @@ -22,7 +22,7 @@ namespace FlatSharpEndToEndTests.ClassLib.VTables; [TestClass] public class VTableTests { - public delegate void CreateCallback(ArrayInputBuffer buffer, int offset, out TVTable vt); + public delegate void CreateCallback(ArrayInputBuffer buffer, long offset, out TVTable vt); [TestMethod] public void Test_VTable4_Auto() => this.RunTests(3, VTable4.Create); @@ -61,12 +61,12 @@ public void InitializeVTable_NormalTable() new ArrayInputBuffer(buffer).InitializeVTable( 8, - out int vtableOffset, - out nuint fieldCount, + out long vtableOffset, + out ulong fieldCount, out ReadOnlySpan fieldData); Assert.AreEqual(0, vtableOffset); - Assert.AreEqual((nuint)2, fieldCount); + Assert.AreEqual((ulong)2, fieldCount); Assert.AreEqual(4, fieldData.Length); } @@ -82,12 +82,12 @@ public void InitializeVTable_EmptyTable() new ArrayInputBuffer(buffer).InitializeVTable( 4, - out int vtableOffset, - out nuint fieldCount, + out long vtableOffset, + out ulong fieldCount, out ReadOnlySpan fieldData); Assert.AreEqual(0, vtableOffset); - Assert.AreEqual((nuint)0, fieldCount); + Assert.AreEqual((ulong)0, fieldCount); Assert.AreEqual(0, fieldData.Length); } diff --git a/src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj b/src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj index 876746ca..1aedb35b 100644 --- a/src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj +++ b/src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj @@ -1,7 +1,7 @@  true - net9.0 + net9.0;net8.0 net472;net6.0;net7.0;net8.0;net9.0 net6.0;net7.0;net8.0;net9.0 false diff --git a/src/Tests/FlatSharpEndToEndTests/Grpc/EchoServiceTestCases.cs b/src/Tests/FlatSharpEndToEndTests/Grpc/EchoServiceTestCases.cs index 4e5765e3..a3814bdd 100644 --- a/src/Tests/FlatSharpEndToEndTests/Grpc/EchoServiceTestCases.cs +++ b/src/Tests/FlatSharpEndToEndTests/Grpc/EchoServiceTestCases.cs @@ -80,7 +80,7 @@ await this.EchoTest(async client => Assert.IsInstanceOfType(response); IFlatBufferDeserializedObject obj = (IFlatBufferDeserializedObject)response; - length = obj.InputBuffer.Length; + length = checked((int)obj.InputBuffer.Length); }); return length; diff --git a/src/Tests/FlatSharpEndToEndTests/Helpers.cs b/src/Tests/FlatSharpEndToEndTests/Helpers.cs index 0c93456d..feac25a7 100644 --- a/src/Tests/FlatSharpEndToEndTests/Helpers.cs +++ b/src/Tests/FlatSharpEndToEndTests/Helpers.cs @@ -62,8 +62,8 @@ public static byte[] AllocateAndSerialize(this T item) where T : class, IFlat public static byte[] AllocateAndSerialize(this T item, ISerializer serializer) where T : class, IFlatBufferSerializable { byte[] data = new byte[serializer.GetMaxSize(item)]; - int bytesWritten = serializer.Write(data, item); - return data.AsMemory().Slice(0, bytesWritten).ToArray(); + long bytesWritten = serializer.Write(data, item); + return data.AsMemory().Slice(0, (int)bytesWritten).ToArray(); } public static T SerializeAndParse(this T item) where T : class, IFlatBufferSerializable diff --git a/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs b/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs index 979a07dc..2180bdf7 100644 --- a/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs @@ -85,9 +85,7 @@ public void MemoryInputBuffer() this.StringInputBufferTest(new MemoryInputBuffer(StringInput)); this.TestDeserializeBoth(b => new MemoryInputBuffer(b)); this.TestReadByteArray(false, b => new MemoryInputBuffer(b)); - this.TableSerializationTest( - default(SpanWriter), - (s, b) => s.Parse(b.AsMemory())); + this.TableSerializationTest((s, b) => s.Parse(b.AsMemory())); } [TestMethod] @@ -102,9 +100,7 @@ public void ReadOnlyMemoryInputBuffer() () => this.TestDeserialize(b => new ReadOnlyMemoryInputBuffer(b))); Assert.AreEqual("ReadOnlyMemory inputs may not deserialize writable memory.", ex.Message); - this.TableSerializationTest( - default(SpanWriter), - (s, b) => s.Parse((ReadOnlyMemory)b.AsMemory())); + this.TableSerializationTest((s, b) => s.Parse((ReadOnlyMemory)b.AsMemory())); } [TestMethod] @@ -114,9 +110,7 @@ public void ArrayInputBuffer() this.StringInputBufferTest(new ArrayInputBuffer(StringInput)); this.TestDeserializeBoth(b => new ArrayInputBuffer(b)); this.TestReadByteArray(false, b => new ArrayInputBuffer(b)); - this.TableSerializationTest( - default(SpanWriter), - (s, b) => s.Parse(b)); + this.TableSerializationTest((s, b) => s.Parse(b)); } [TestMethod] @@ -127,12 +121,10 @@ public void ArraySegmentInputBuffer() this.TestDeserializeBoth(b => new ArraySegmentInputBuffer(new ArraySegment(b))); this.TestReadByteArray(false, b => new ArraySegmentInputBuffer(new ArraySegment(b))); this.TableSerializationTest( - default(SpanWriter), (s, b) => s.Parse(new ArraySegment(b))); } private void TableSerializationTest( - ISpanWriter writer, Func, byte[], PrimitiveTypesTable> parse) { PrimitiveTypesTable original = new() @@ -153,7 +145,7 @@ private void TableSerializationTest( }; byte[] data = new byte[1024]; - PrimitiveTypesTable.Serializer.Write(writer, data, original); + PrimitiveTypesTable.Serializer.Write(data, original); PrimitiveTypesTable parsed = parse(PrimitiveTypesTable.Serializer, data); Assert.AreEqual(original.A, parsed.A); @@ -230,18 +222,18 @@ private void TestReadByteArray(bool isReadOnly, Func b Assert.IsTrue(inputBuffer.ReadByteReadOnlyMemoryBlock(0).Span.SequenceEqual(expected)); Assert.AreEqual(isReadOnly, inputBuffer.IsReadOnly); - Assert.IsTrue(inputBuffer.GetReadOnlySpan().SequenceEqual(buffer)); - Assert.IsTrue(inputBuffer.GetReadOnlyMemory().Span.SequenceEqual(buffer)); + Assert.IsTrue(inputBuffer.GetReadOnlySpan().ToSpan(0, (int)inputBuffer.Length).SequenceEqual(buffer)); + Assert.IsTrue(inputBuffer.GetReadOnlyMemory(0, (int)inputBuffer.Length).Span.SequenceEqual(buffer)); if (isReadOnly) { - Assert.ThrowsException(() => inputBuffer.GetSpan()); - Assert.ThrowsException(() => inputBuffer.GetMemory()); + Assert.ThrowsException(() => inputBuffer.GetSpan().ToSpan(0, 1)); + Assert.ThrowsException(() => inputBuffer.GetMemory(0, 1)); } else { - Assert.IsTrue(inputBuffer.GetMemory().Span.SequenceEqual(buffer)); - Assert.IsTrue(inputBuffer.GetSpan().SequenceEqual(buffer)); + Assert.IsTrue(inputBuffer.GetMemory(0, (int)inputBuffer.Length).Span.SequenceEqual(buffer)); + Assert.IsTrue(inputBuffer.GetSpan().ToSpan(0, (int)inputBuffer.Length).SequenceEqual(buffer)); Assert.IsTrue(inputBuffer.ReadByteMemoryBlock(0).Span.SequenceEqual(expected)); } } diff --git a/src/Tests/FlatSharpEndToEndTests/Oracle/AlignmentTests.cs b/src/Tests/FlatSharpEndToEndTests/Oracle/AlignmentTests.cs index 401ee20a..13f30ddb 100644 --- a/src/Tests/FlatSharpEndToEndTests/Oracle/AlignmentTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/Oracle/AlignmentTests.cs @@ -74,10 +74,10 @@ public void NestedStructWithOddAlignment_Serialize() } }; - Span memory = new byte[10240]; - int offset = FS.AlignmentTestOuterHoder.Serializer.Write(memory, holder); + byte[] memory = new byte[10240]; + long offset = FS.AlignmentTestOuterHoder.Serializer.Write(memory, holder); - var parsed = Flatc.AlignmentTestOuterHoder.GetRootAsAlignmentTestOuterHoder(new ByteBuffer(memory.Slice(0, offset).ToArray())); + var parsed = Flatc.AlignmentTestOuterHoder.GetRootAsAlignmentTestOuterHoder(new ByteBuffer(memory.AsSpan().Slice(0, (int)offset).ToArray())); var outer = parsed.Value.Value; Assert.AreEqual(1, outer.A); diff --git a/src/Tests/FlatSharpEndToEndTests/Oracle/OracleSerializeTests.cs b/src/Tests/FlatSharpEndToEndTests/Oracle/OracleSerializeTests.cs index 4ca44dec..5d69ec57 100644 --- a/src/Tests/FlatSharpEndToEndTests/Oracle/OracleSerializeTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/Oracle/OracleSerializeTests.cs @@ -45,10 +45,10 @@ public void SimpleTypes_WithValues() UShort = GetRandom(), }; - Span memory = new byte[10240]; - int size = FS.BasicTypes.Serializer.Write(memory, simple); + byte[] memory = new byte[10240]; + long size = FS.BasicTypes.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.BasicTypesVerify.Verify(new(bb), 4)); var oracle = Flatc.BasicTypes.GetRootAsBasicTypes(bb); @@ -74,10 +74,10 @@ public void SimpleTypes_Defaults() { var simple = new FS.BasicTypes(); - Span memory = new byte[10240]; - int size = FS.BasicTypes.Serializer.Write(memory, simple); + byte[] memory = new byte[10240]; + long size = FS.BasicTypes.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.BasicTypesVerify.Verify(new(bb), 4)); var oracle = Flatc.BasicTypes.GetRootAsBasicTypes(bb); @@ -111,10 +111,10 @@ public void LinkedList() } }; - Span memory = new byte[10240]; - int size = FS.LinkedListNode.Serializer.Write(memory, simple); + byte[] memory = new byte[10240]; + long size = FS.LinkedListNode.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.LinkedListNodeVerify.Verify(new(bb), 4)); var oracle = Flatc.LinkedListNode.GetRootAsLinkedListNode(bb); @@ -138,10 +138,10 @@ public void StructWithinTable() }, }; - Span memory = new byte[10240]; - int size = FS.LocationHolder.Serializer.Write(memory, holder); + byte[] memory = new byte[10240]; + long size = FS.LocationHolder.Serializer.Write(memory, holder); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.LocationHolderVerify.Verify(new(bb), 4)); var oracle = Flatc.LocationHolder.GetRootAsLocationHolder(bb); @@ -176,10 +176,10 @@ public void ScalarVectors() ByteVector3 = new byte[0], }; - Span memory = new byte[10240]; - int size = FS.Vectors.Serializer.Write(memory, table); + byte[] memory = new byte[10240]; + long size = FS.Vectors.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.VectorsVerify.Verify(new(bb), 4)); var oracle = Flatc.Vectors.GetRootAsVectors(bb); @@ -223,11 +223,11 @@ public void FiveByteStructVector() } }; - Span buffer = new byte[1024]; + byte[] buffer = new byte[1024]; var bytecount = FS.FiveByteStructTable.Serializer.Write(buffer, table); - buffer = buffer.Slice(0, bytecount); + var span = buffer.AsSpan().Slice(0, (int)bytecount); - var bb = new ByteBuffer(buffer.ToArray()); + var bb = new ByteBuffer(span.ToArray()); Assert.IsTrue(Flatc.FiveByteStructTableVerify.Verify(new Verifier(bb), 4)); var oracle = Flatc.FiveByteStructTable.GetRootAsFiveByteStructTable(bb); @@ -267,10 +267,10 @@ public void Union_Table_BasicTypes() Value = new FS.Union(simple) }; - Span memory = new byte[10240]; - int size = FS.UnionTable.Serializer.Write(memory, table); + byte[] memory = new byte[10240]; + long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionTable.GetRootAsUnionTable(bb); @@ -303,10 +303,10 @@ public void Union_String() Value = new("foobar") }; - Span memory = new byte[10240]; - int size = FS.UnionTable.Serializer.Write(memory, table); + byte[] memory = new byte[10240]; + long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionTable.GetRootAsUnionTable(bb); @@ -322,10 +322,10 @@ public void Union_NotSet() Value = null, }; - Span memory = new byte[10240]; - int size = FS.UnionTable.Serializer.Write(memory, table); + byte[] memory = new byte[10240]; + long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionTable.GetRootAsUnionTable(bb); @@ -347,10 +347,10 @@ public void Union_Struct_Location() Value = new(location) }; - Span memory = new byte[10240]; - int size = FS.UnionTable.Serializer.Write(memory, table); + byte[] memory = new byte[10240]; + long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionTable.GetRootAsUnionTable(bb); @@ -374,10 +374,10 @@ public void NestedStruct_InnerStructSet() } }; - Span memory = new byte[10240]; - int size = FS.NestedStructs.Serializer.Write(memory, nested); + byte[] memory = new byte[10240]; + long size = FS.NestedStructs.Serializer.Write(memory, nested); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.NestedStructsVerify.Verify(new(bb), 4)); var oracle = Flatc.NestedStructs.GetRootAsNestedStructs(bb); @@ -400,10 +400,10 @@ public void NestedStruct_InnerStructNotSet() } }; - Span memory = new byte[10240]; - int size = FS.NestedStructs.Serializer.Write(memory, nested); + byte[] memory = new byte[10240]; + long size = FS.NestedStructs.Serializer.Write(memory, nested); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.NestedStructsVerify.Verify(new(bb), 4)); var oracle = Flatc.NestedStructs.GetRootAsNestedStructs(bb); @@ -427,10 +427,10 @@ public void VectorOfUnion() } }; - Span memory = new byte[10240]; - int size = FS.VectorOfUnionTable.Serializer.Write(memory, table); + byte[] memory = new byte[10240]; + long size = FS.VectorOfUnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.AsSpan().Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionVectorTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionVectorTable.GetRootAsUnionVectorTable(bb); @@ -474,7 +474,7 @@ public void SortedVectors() } byte[] data = new byte[1024 * 1024]; - int bytesWritten = FS.SortedVectorTest.Serializer.Write(data, test); + long bytesWritten = FS.SortedVectorTest.Serializer.Write(data, test); var parsed = FS.SortedVectorTest.Serializer.Parse(data); @@ -510,7 +510,7 @@ public void SortedVectors_DefaultValue() test.Int32.Add(myItem); - int bytesWritten = FS.SortedVectorTest.Serializer.Write(data, test); + long bytesWritten = FS.SortedVectorTest.Serializer.Write(data, test); var parsed = FS.SortedVectorTest.Serializer.Parse(data); diff --git a/src/Tests/FlatSharpEndToEndTests/RawData/RawDataTableTests.cs b/src/Tests/FlatSharpEndToEndTests/RawData/RawDataTableTests.cs index 8fe9256a..910020b9 100644 --- a/src/Tests/FlatSharpEndToEndTests/RawData/RawDataTableTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/RawData/RawDataTableTests.cs @@ -151,7 +151,7 @@ public void EmptyTable_Serialize_MaxSize() Assert.IsTrue(expectedData.AsSpan().SequenceEqual(buffer)); - int maxSize = EmptyTable.Serializer.GetMaxSize(table); + long maxSize = EmptyTable.Serializer.GetMaxSize(table); Assert.AreEqual(23, maxSize); } diff --git a/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembersTests.cs b/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembersTests.cs index 60e2b705..9a9899cc 100644 --- a/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembersTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembersTests.cs @@ -27,7 +27,7 @@ public class TableMemberTests [DynamicData(nameof(DynamicDataHelper.DeserializationModes), typeof(DynamicDataHelper))] public void Byte(FlatBufferDeserializationOption option) { - this.RunTest(1, option); + this.RunTest(1, option); ByteTable table = new(); table.ItemMemory = new byte[] { 1, 2, 3, 4, 5, }; @@ -186,14 +186,46 @@ public interface ITypedTable where T : struct IReadOnlyList? ItemReadonlyList { get; set; } } -public partial class BoolTable : ITypedTable { } -public partial class ByteTable : ITypedTable { } -public partial class SByteTable : ITypedTable { } -public partial class ShortTable : ITypedTable { } -public partial class UShortTable : ITypedTable { } -public partial class IntTable : ITypedTable { } -public partial class UIntTable : ITypedTable { } -public partial class LongTable : ITypedTable { } -public partial class ULongTable : ITypedTable { } -public partial class FloatTable : ITypedTable { } -public partial class DoubleTable : ITypedTable { } \ No newline at end of file +public partial class BoolTable : ITypedTable +{ +} + +public partial class ByteTable : ITypedTable +{ +} + +public partial class SByteTable : ITypedTable +{ +} + +public partial class ShortTable : ITypedTable +{ +} + +public partial class UShortTable : ITypedTable +{ +} + +public partial class IntTable : ITypedTable +{ +} + +public partial class UIntTable : ITypedTable +{ +} + +public partial class LongTable : ITypedTable +{ +} + +public partial class ULongTable : ITypedTable +{ +} + +public partial class FloatTable : ITypedTable +{ +} + +public partial class DoubleTable : ITypedTable +{ +} \ No newline at end of file diff --git a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs index 77064414..bf85ae97 100644 --- a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs @@ -131,10 +131,10 @@ public void SerializeAndParse_Full(FlatBufferDeserializationOption option) }; ISerializer serializer = RootTable.Serializer; - int maxBytes = serializer.GetMaxSize(table); + long maxBytes = serializer.GetMaxSize(table); byte[] buffer = new byte[maxBytes]; - int written = serializer.Write(buffer, table); - RootTable parsed = serializer.Parse(buffer.AsMemory().Slice(0, written), option); + long written = serializer.Write(buffer, table); + RootTable parsed = serializer.Parse(buffer.AsMemory().Slice(0, (int)written), option); Assert.IsNotNull(parsed.RefStruct); Assert.IsNotNull(parsed.ValueStruct); @@ -466,10 +466,10 @@ public void SerializeAndParse_Empty() Assert.IsNull(t.ValueStruct); ISerializer serializer = RootTable.Serializer; - int maxBytes = serializer.GetMaxSize(t); + long maxBytes = serializer.GetMaxSize(t); byte[] buffer = new byte[maxBytes]; - int written = serializer.Write(buffer, t); - RootTable parsed = serializer.Parse(buffer.AsMemory().Slice(0, written)); + long written = serializer.Write(buffer, t); + RootTable parsed = serializer.Parse(buffer.AsMemory().Slice(0, (int)written)); Assert.IsNull(parsed.RefStruct); Assert.IsNull(parsed.ValueStruct); diff --git a/src/Tests/Stryker/Tests/FullTreeTests.cs b/src/Tests/Stryker/Tests/FullTreeTests.cs index 76cfd9b4..577f2f81 100644 --- a/src/Tests/Stryker/Tests/FullTreeTests.cs +++ b/src/Tests/Stryker/Tests/FullTreeTests.cs @@ -59,7 +59,7 @@ public void VectorFieldTests_ProgressiveClear() public void GetMaxSize() { Root root = this.CreateRoot(); - int maxSize = Root.Serializer.GetMaxSize(root); + long maxSize = Root.Serializer.GetMaxSize(root); Assert.AreEqual(898, maxSize); } diff --git a/src/Tests/Stryker/Tests/RefStructVectorTests.cs b/src/Tests/Stryker/Tests/RefStructVectorTests.cs index 3b68fff9..774cda29 100644 --- a/src/Tests/Stryker/Tests/RefStructVectorTests.cs +++ b/src/Tests/Stryker/Tests/RefStructVectorTests.cs @@ -72,7 +72,7 @@ public void WithNull() => Helpers.Repeat(() => Assert.AreEqual("FlatSharp encountered a null reference in an invalid context, such as a vector. Vectors are not permitted to have null objects.", ex.Message); // no exception here. Reason is that structs are constant size, so we don't traverse the vector to figure out the max size. - int maxSize = Root.Serializer.GetMaxSize(root); + long maxSize = Root.Serializer.GetMaxSize(root); }); private Root CreateRoot(out byte[] expectedData)