From 8cdbe0be010375900248a9ec39d968e5edba5c8a Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sun, 10 Nov 2024 13:34:28 -0800 Subject: [PATCH 01/13] Add x64 support --- .../Benchmark/Serializers/FlatSharpHelper.cs | 26 +- .../Serializers/FlatSharpValueStructs.cs | 8 +- .../MicroBench.Current/Constants.cs | 2 +- .../MicroBench.Current/SerializeBenchmarks.cs | 24 +- src/Directory.Build.props | 2 +- src/Directory.Packages.props | 7 +- .../BufferTooSmallException.cs | 2 +- src/FlatSharp.Runtime/FSThrow.cs | 2 +- .../GeneratedSerializerWrapper.cs | 29 +- .../IFlatBufferDeserializedVector.cs | 4 +- src/FlatSharp.Runtime/IGeneratedSerializer.cs | 19 +- src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs | 136 -------- .../IO/ArraySegmentInputBuffer.cs | 143 -------- .../IO/ISharedStringWriter.cs | 17 +- src/FlatSharp.Runtime/IO/ISpanWriter.cs | 2 +- .../IO/InputBuffer/ArrayInputBuffer.cs | 68 ++++ .../IO/InputBuffer/ArraySegmentInputBuffer.cs | 75 ++++ .../IO/{ => InputBuffer}/IInputBuffer.cs | 73 ++-- .../IO/InputBuffer/InputBufferExtensions.cs | 327 ++++++++++++++++++ .../IO/InputBuffer/MemoryInputBuffer.cs | 79 +++++ .../InputBuffer/ReadOnlyMemoryInputBuffer.cs | 86 +++++ .../IO/InputBufferExtensions.cs | 143 -------- src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs | 149 -------- .../IO/ReadOnlyMemoryInputBuffer.cs | 154 --------- .../ArraySerializationTarget.cs | 74 ++++ .../IFlatBufferSerializationTarget.cs | 190 ++++++++++ .../InputBufferSerializationTarget.cs | 67 ++++ .../MemorySerializationTarget.cs | 62 ++++ .../SpanSerializationTarget.cs | 73 ++++ .../IO/SharedStringWriter.cs | 46 ++- src/FlatSharp.Runtime/IO/SpanWriter.cs | 2 +- .../IO/SpanWriterExtensions.cs | 109 ++++-- src/FlatSharp.Runtime/IPostSerializeAction.cs | 30 ++ src/FlatSharp.Runtime/ISerializer.cs | 24 +- .../ISerializerExtensions.cs | 90 +++-- src/FlatSharp.Runtime/SerializationContext.cs | 67 ++-- src/FlatSharp.Runtime/SerializationHelpers.cs | 4 +- src/FlatSharp.Runtime/SortedVectorHelpers.cs | 6 +- .../SortedVectorHelpersInternal.cs | 134 ++++--- src/FlatSharp.Runtime/VTables/VTable4.cs | 10 +- src/FlatSharp.Runtime/VTables/VTable8.cs | 10 +- .../VTables/VTableGeneric.cs | 6 +- src/FlatSharp/FlatBufferSerializer.cs | 20 +- src/FlatSharp/FlatBufferVectorHelpers.cs | 8 +- .../FlatBufferVectorHelpers_Greedy.cs | 2 +- .../FlatBufferVectorHelpers_GreedyMutable.cs | 2 +- ...tBufferVectorHelpers_GreedyMutableUnion.cs | 6 +- .../FlatBufferVectorHelpers_GreedyUnion.cs | 6 +- src/FlatSharp/FlatBufferVectorHelpers_Lazy.cs | 6 +- .../FlatBufferVectorHelpers_LazyUnion.cs | 10 +- .../FlatBufferVectorHelpers_Progressive.cs | 10 +- ...latBufferVectorHelpers_ProgressiveUnion.cs | 10 +- src/FlatSharp/FlatSharp.csproj | 7 - src/FlatSharp/Serialization/CSharpHelpers.cs | 1 + .../DeserializeClassDefinition.cs | 11 +- .../Serialization/ParserCodeGenContext.cs | 5 +- .../RoslynSerializerGenerator.cs | 69 ++-- .../SerializationCodeGenContext.cs | 20 +- src/FlatSharp/TypeModel/ScalarTypeModel.cs | 2 +- src/FlatSharp/TypeModel/ScalarTypeModels.cs | 44 +-- src/FlatSharp/TypeModel/ScalarTypeModels.tt | 43 +-- src/FlatSharp/TypeModel/StringTypeModel.cs | 11 +- src/FlatSharp/TypeModel/StructTypeModel.cs | 10 +- src/FlatSharp/TypeModel/TableMemberModel.cs | 2 +- src/FlatSharp/TypeModel/TableTypeModel.cs | 29 +- src/FlatSharp/TypeModel/UnionTypeModel.cs | 13 +- .../TypeModel/ValueStructTypeModel.cs | 11 +- .../TypeModel/Vectors/BaseVectorTypeModel.cs | 6 +- .../Vectors/MemoryVectorTypeModel.cs | 2 +- .../UnityNativeArrayVectorTypeModel.cs | 3 +- .../BaseVectorOfUnionTypeModel.cs | 12 +- src/Tests/CompileTests/NativeAot/Program.cs | 77 +---- .../FlatBufferSerializerNonGenericTests.cs | 2 +- .../ClassLib/SerializerConfigurationTests.cs | 59 ++-- .../ClassLib/VTableTests.cs | 14 +- .../Grpc/EchoServiceTestCases.cs | 2 +- src/Tests/FlatSharpEndToEndTests/Helpers.cs | 4 +- .../IO/InputBufferTests.cs | 28 +- .../Oracle/AlignmentTests.cs | 4 +- .../Oracle/OracleSerializeTests.cs | 54 +-- .../RawData/RawDataTableTests.cs | 2 +- .../TableMembers/TableMembersTests.cs | 56 ++- .../ValueStructs/ValueStructTests.cs | 12 +- src/Tests/Stryker/Tests/FullTreeTests.cs | 2 +- .../Stryker/Tests/RefStructVectorTests.cs | 2 +- 85 files changed, 1882 insertions(+), 1398 deletions(-) delete mode 100644 src/FlatSharp.Runtime/IO/ArrayInputBuffer.cs delete mode 100644 src/FlatSharp.Runtime/IO/ArraySegmentInputBuffer.cs create mode 100644 src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs create mode 100644 src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs rename src/FlatSharp.Runtime/IO/{ => InputBuffer}/IInputBuffer.cs (73%) create mode 100644 src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs create mode 100644 src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs create mode 100644 src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs delete mode 100644 src/FlatSharp.Runtime/IO/InputBufferExtensions.cs delete mode 100644 src/FlatSharp.Runtime/IO/MemoryInputBuffer.cs delete mode 100644 src/FlatSharp.Runtime/IO/ReadOnlyMemoryInputBuffer.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs create mode 100644 src/FlatSharp.Runtime/IPostSerializeAction.cs 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..3d1dd1b6 100644 --- a/src/Benchmarks/MicroBench.Current/Constants.cs +++ b/src/Benchmarks/MicroBench.Current/Constants.cs @@ -169,7 +169,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/SerializeBenchmarks.cs b/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs index adf5052f..4e447182 100644 --- a/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs +++ b/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs @@ -24,73 +24,73 @@ 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_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/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/GeneratedSerializerWrapper.cs b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs index ff867d1c..353bdf8d 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,11 @@ 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(TTarget destination, T item) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { if (item is null) { @@ -163,14 +165,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 +182,11 @@ item is IFlatBufferDeserializedObject deserializedObj && Debug.Assert(!sharedStringWriter.IsDirty); } - this.innerSerializer.Write(writer, destination, item, serializationContext); + this.innerSerializer.Write(ref destination, item, serializationContext); if (sharedStringWriter?.IsDirty == true) { - writer.FlushSharedStrings(sharedStringWriter, destination, serializationContext); + sharedStringWriter.FlushWrites(destination, serializationContext); Debug.Assert(!sharedStringWriter.IsDirty); } @@ -206,11 +201,11 @@ item is IFlatBufferDeserializedObject deserializedObj && return serializationContext.Offset; } - int ISerializer.Write(TSpanWriter writer, Span destination, object item) + long ISerializer.Write(TTarget 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..a528fe8e 100644 --- a/src/FlatSharp.Runtime/IGeneratedSerializer.cs +++ b/src/FlatSharp.Runtime/IGeneratedSerializer.cs @@ -38,17 +38,20 @@ 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( + ref TSerializationTarget target, T item, - SerializationContext context) where TSpanWriter : ISpanWriter; + SerializationContext context) + where TSerializationTarget : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + ; /// /// 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/ISharedStringWriter.cs b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs index fad0bda2..636c404e 100644 --- a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs @@ -38,16 +38,23 @@ 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(TTarget spanWriter, long offset, string value, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + ; /// /// 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(TTarget writer, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + ; } diff --git a/src/FlatSharp.Runtime/IO/ISpanWriter.cs b/src/FlatSharp.Runtime/IO/ISpanWriter.cs index beb8a93f..6bc398e2 100644 --- a/src/FlatSharp.Runtime/IO/ISpanWriter.cs +++ b/src/FlatSharp.Runtime/IO/ISpanWriter.cs @@ -79,7 +79,7 @@ public interface ISpanWriter int GetStringBytes(Span destination, string value, Encoding encoding); /// - /// Invokes the method. + /// Invokes the method. /// void FlushSharedStrings( ISharedStringWriter writer, diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs new file mode 100644 index 00000000..04993714 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs @@ -0,0 +1,68 @@ +/* + * 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 ReadOnlySpan GetReadOnlySpan(long offset, int length) + { + return this.GetSpan(offset, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpan(long offset, int length) + { + checked + { + return this.memory.AsSpan().Slice((int)offset, length); + } + } + + [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..d393787a --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs @@ -0,0 +1,75 @@ +/* + * 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 ReadOnlySpan GetReadOnlySpan(long offset, int length) + { + return this.GetSpan(offset, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpan(long offset, int length) + { + checked + { + return this.pointer.segment.AsSpan((int)offset, length); + } + } + + [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/IInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs similarity index 73% rename from src/FlatSharp.Runtime/IO/IInputBuffer.cs rename to src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs index 9563a864..bd6219da 100644 --- a/src/FlatSharp.Runtime/IO/IInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs @@ -36,80 +36,87 @@ public interface IInputBuffer /// /// Gets the length of this input buffer. /// - int Length { get; } - + long Length { get; } + /// - /// Reads the byte at the given offset. + /// Gets a read only span covering the entire input buffer. /// - byte ReadByte(int offset); + ReadOnlySpan GetReadOnlySpan(long offset, int length); /// - /// Reads the sbyte at the given offset. + /// Gets a read only memory covering the entire input buffer. /// - sbyte ReadSByte(int offset); + ReadOnlyMemory GetReadOnlyMemory(long offset, int length); /// - /// Reads the ushort at the given offset. + /// Gets a span covering the entire input buffer. /// - ushort ReadUShort(int offset); + Span GetSpan(long offset, int length); /// - /// Reads the short at the given offset. + /// Gets a memory covering the entire input buffer. /// - short ReadShort(int offset); + Memory GetMemory(long offset, int length); +} +/// +/// An implementation of IInputBuffer that contains all methods. Useful for +/// implementations that wish to completely control the behavior. +/// +public interface IInputBufferFull : IInputBuffer +{ /// - /// Reads the uint at the given offset. + /// Reads the byte at the given offset. /// - uint ReadUInt(int offset); + byte ReadByte(long offset); /// - /// Reads the int at the given offset. + /// Reads the sbyte at the given offset. /// - int ReadInt(int offset); + sbyte ReadSByte(long offset); /// - /// Reads the ulong at the given offset. + /// Reads the ushort at the given offset. /// - ulong ReadULong(int offset); + ushort ReadUShort(long offset); /// - /// Reads the long at the given offset. + /// Reads the short at the given offset. /// - long ReadLong(int offset); + short ReadShort(long offset); /// - /// Reads the float at the given offset. + /// Reads the uint at the given offset. /// - float ReadFloat(int offset); + uint ReadUInt(long offset); /// - /// Reads the double at the given offset. + /// Reads the int at the given offset. /// - double ReadDouble(int offset); + int ReadInt(long offset); /// - /// Reads the string of the given length at the given offset with the given encoding. + /// Reads the ulong at the given offset. /// - string ReadString(int offset, int byteLength, Encoding encoding); + ulong ReadULong(long offset); /// - /// Gets a read only span covering the entire input buffer. + /// Reads the long at the given offset. /// - ReadOnlySpan GetReadOnlySpan(); + long ReadLong(long offset); /// - /// Gets a read only memory covering the entire input buffer. + /// Reads the float at the given offset. /// - ReadOnlyMemory GetReadOnlyMemory(); + float ReadFloat(long offset); /// - /// Gets a span covering the entire input buffer. + /// Reads the double at the given offset. /// - Span GetSpan(); + double ReadDouble(long offset); /// - /// Gets a memory covering the entire input buffer. + /// Reads the string of the given length at the given offset with the given encoding. /// - Memory GetMemory(); -} \ No newline at end of file + string ReadString(long offset, int byteLength, Encoding encoding); +} diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs new file mode 100644 index 00000000..45717c3b --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs @@ -0,0 +1,327 @@ +/* + * 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 + { + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadBool(offset); + } + + return buffer.ReadByte(offset) != SerializationHelpers.False; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ReadByte(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadByte(offset); + } + + return buffer.GetReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte ReadSByte(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadSByte(offset); + } + + return (sbyte)buffer.ReadByte(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadUShort(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + buffer.CheckAlignment(offset, sizeof(ushort)); + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadUShort(offset); + } + + return BinaryPrimitives.ReadUInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ushort))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadShort(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + buffer.CheckAlignment(offset, sizeof(short)); + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadShort(offset); + } + + return BinaryPrimitives.ReadInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(short))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadUInt(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + buffer.CheckAlignment(offset, sizeof(uint)); + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadUInt(offset); + } + + return BinaryPrimitives.ReadUInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(uint))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadInt(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + buffer.CheckAlignment(offset, sizeof(int)); + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadInt(offset); + } + + return BinaryPrimitives.ReadInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(int))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadULong(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + buffer.CheckAlignment(offset, sizeof(ulong)); + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadULong(offset); + } + + return BinaryPrimitives.ReadUInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ulong))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ReadLong(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + buffer.CheckAlignment(offset, sizeof(long)); + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadLong(offset); + } + + return BinaryPrimitives.ReadInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(long))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ReadFloat(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + buffer.CheckAlignment(offset, sizeof(float)); + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadFloat(offset); + } + +#if NETSTANDARD + ScalarSpanReader.FloatLayout layout = new() + { + bytes = buffer.ReadUInt(offset) + }; + + return layout.value; +#else + return BinaryPrimitives.ReadSingleLittleEndian(buffer.GetReadOnlySpan(offset, sizeof(float))); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ReadDouble(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + { + buffer.CheckAlignment(offset, sizeof(double)); + if (buffer is IInputBufferFull) + { + return ((IInputBufferFull)buffer).ReadDouble(offset); + } + +#if NETSTANDARD + return BitConverter.Int64BitsToDouble(buffer.ReadLong(offset)); +#else + return BinaryPrimitives.ReadDoubleLittleEndian(buffer.GetReadOnlySpan(offset, sizeof(double))); +#endif + } + + /// + /// 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(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 + { + vtableOffset = tableOffset - buffer.ReadInt(tableOffset); + ushort vtableLength = buffer.ReadUShort(vtableOffset); + + if (vtableLength < 4) + { + FSThrow.InvalidData_VTableTooShort(); + } + + fieldData = buffer.GetReadOnlySpan(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( + 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, TTarget target) + where TBuffer : IInputBuffer + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + 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(offset, (int)Math.Min(int.MaxValue, remaining)); + chunk.CopyTo(target.AsSpan(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 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..40316cfb --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs @@ -0,0 +1,79 @@ +/* + * 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 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 ReadOnlySpan GetReadOnlySpan(long offset, int length) + { + return this.GetSpan(offset, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpan(long offset, int length) + { + checked + { + return this.pointer.memory.Span.Slice((int)offset, length); + } + } + + [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..ca7648c6 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs @@ -0,0 +1,86 @@ +/* + * 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 long Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.pointer.memory.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetReadOnlySpan(long offset, int length) + { + checked + { + return this.pointer.memory.Span.Slice((int)offset, length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetReadOnlyMemory(long offset, int length) + { + checked + { + return this.pointer.memory.Slice((int)offset, length); + } + } + + public Span GetSpan(long offset, int length) + { + 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/ArraySerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs new file mode 100644 index 00000000..041fa799 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs @@ -0,0 +1,74 @@ +/* + * 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; + +/// +/// A serialization target that wraps a Span{byte}. Only supports the 32-bit address space. +/// +public struct ArraySerializationTarget : IFlatBufferSerializationTarget +{ + private readonly byte[] array; + private readonly int start; + + public ArraySerializationTarget(byte[] array) + { + this.array = array; + this.Length = array.Length; + this.start = 0; + } + + public ArraySerializationTarget(byte[] array, int start, int length) + { + this.start = start; + this.Length = length; + this.array = array; + } + + public byte this[long index] + { + get => this.array[checked(this.start + (int)index)]; + set => this.array[checked(this.start + (int)index)] = value; + } + + public long Length { get; } + + public ArraySerializationTarget Slice(long start, long length) + { + checked + { + return new ArraySerializationTarget(this.array, (int)(this.start + start), (int)length); + } + } + + public ArraySerializationTarget Slice(long start) + { + checked + { + return new(this.array, (int)(this.start + start), (int)(this.Length - start)); + } + } + + public Span AsSpan(long start, int length) + { + checked + { + return this.array.AsSpan().Slice(this.start + (int)start, length); + } + } +} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs new file mode 100644 index 00000000..1a7e1acd --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs @@ -0,0 +1,190 @@ +/* + * 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; + +/// +/// Represents a target for a FlatBuffer serialization operation. +/// +public interface IFlatBufferSerializationTarget + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif +{ + /// + /// Gets or sets the value at the given index. + /// + byte this[long index] { get; set; } + + /// + /// Gets the length. + /// + long Length { get; } + + /// + /// Slices this object. + /// + T Slice(long start, long length); + + /// + /// Slices this object. + /// + T Slice(long start); + + /// + /// Returns a Span{byte} over the given range. + /// + Span AsSpan(long start, int length); +} + +/// +/// Extensions for IFlatBufferSerializationTarget +/// +public static class FlatBufferSerializationTargetExtensions +{ + public static void WriteBool(this T target, long offset, bool value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target.WriteUInt8(offset, value ? SerializationHelpers.True : SerializationHelpers.False); + } + + public static void WriteUInt8(this T target, long offset, byte value) + where T : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + target[offset] = value; + } + + public static void WriteInt8(this T target, long offset, sbyte value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target[offset] = unchecked((byte)value); + } + + public static void WriteUInt16(this T target, long offset, ushort value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + BinaryPrimitives.WriteUInt16LittleEndian( + target.AsSpan(offset, sizeof(ushort)), + value); + } + + public static void WriteInt16(this T target, long offset, short value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + BinaryPrimitives.WriteInt16LittleEndian( + target.AsSpan(offset, sizeof(short)), + value); + } + + public static void WriteUInt32(this T target, long offset, uint value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + BinaryPrimitives.WriteUInt32LittleEndian( + target.AsSpan(offset, sizeof(uint)), + value); + } + + public static void WriteInt32(this T target, long offset, int value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + BinaryPrimitives.WriteInt32LittleEndian( + target.AsSpan(offset, sizeof(int)), + value); + } + + public static void WriteUInt64(this T target, long offset, ulong value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + BinaryPrimitives.WriteUInt64LittleEndian( + target.AsSpan(offset, sizeof(ulong)), + value); + } + + public static void WriteInt64(this T target, long offset, long value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + BinaryPrimitives.WriteInt64LittleEndian( + target.AsSpan(offset, sizeof(long)), + value); + } + + + public static void WriteFloat32(this T target, long offset, float value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + +#if NETSTANDARD + ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout + { + value = value + }; + + target.WriteUInt32(offset, floatLayout.bytes); +#else + BinaryPrimitives.WriteSingleLittleEndian( + target.AsSpan(offset, sizeof(float)), + value); +#endif + } + + public static void WriteFloat64(this T target, long offset, double value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { +#if NETSTANDARD + target.WriteInt64(offset, BitConverter.DoubleToInt64Bits(value)); +#else + BinaryPrimitives.WriteDoubleLittleEndian( + target.AsSpan(offset, sizeof(double)), + value); +#endif + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs new file mode 100644 index 00000000..c8f574ad --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs @@ -0,0 +1,67 @@ +/* + * 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; + +/// +/// A serialization target that wraps an instance of . +/// +public struct InputBufferSerializationTarget : IFlatBufferSerializationTarget> + where TInputBuffer : IInputBuffer +{ + private readonly long start; + private readonly TInputBuffer buffer; + + public InputBufferSerializationTarget(TInputBuffer buffer) + { + this.buffer = buffer; + this.start = 0; + this.Length = buffer.Length; + } + + public InputBufferSerializationTarget(TInputBuffer buffer, long start, long length) + { + this.buffer = buffer; + this.start = start; + this.Length = length; + } + + public byte this[long index] + { + get => this.buffer.ReadByte(this.start + index); + set => this.buffer.GetSpan(this.start + index, sizeof(byte))[0] = value; + } + + public long Length { get; } + + public InputBufferSerializationTarget Slice(long start, long length) + { + return new(this.buffer, this.start + start, length); + } + + public InputBufferSerializationTarget Slice(long start) + { + return new(this.buffer, this.start + start, this.Length - start); + } + + public Span AsSpan(long start, int length) + { + checked + { + return this.buffer.GetSpan(this.start + start, length); + } + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs new file mode 100644 index 00000000..7fbef796 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs @@ -0,0 +1,62 @@ +/* + * 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; + +/// +/// A serialization target that wraps a Memory{byte}. Only supports the 32-bit address space. +/// +public struct MemorySerializationTarget : IFlatBufferSerializationTarget +{ + private readonly Memory memory; + + public MemorySerializationTarget(Memory memory) + { + this.memory = memory; + } + + public byte this[long index] + { + get => this.memory.Span[checked((int)index)]; + set => this.memory.Span[checked((int)index)] = value; + } + + public long Length => this.memory.Length; + + public MemorySerializationTarget Slice(long start, long length) + { + checked + { + return new MemorySerializationTarget(this.memory.Slice((int)start, (int)length)); + } + } + + public MemorySerializationTarget Slice(long start) + { + checked + { + return new MemorySerializationTarget(this.memory.Slice((int)start)); + } + } + + public Span AsSpan(long start, int length) + { + checked + { + return this.memory.Span.Slice((int)start, length); + } + } +} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs new file mode 100644 index 00000000..f9030c3f --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs @@ -0,0 +1,73 @@ +/* + * 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; + +#if NET9_0_OR_GREATER + +/// +/// A serialization target that wraps a Span{byte}. Only supports the 32-bit address space. +/// +public ref struct SpanSerializationTarget : IFlatBufferSerializationTarget +{ + private readonly Span span; + + public SpanSerializationTarget(Span span) + { + this.span = span; + } + + public SpanSerializationTarget(byte[] array) + { + this.span = array.AsSpan(); + } + + public SpanSerializationTarget(Memory memory) + { + this.span = memory.Span; + } + + public byte this[long index] + { + get => this.span[checked((int)index)]; + set => this.span[checked((int)index)] = value; + } + + public long Length => this.span.Length; + + public SpanSerializationTarget Slice(long start, long length) + { + checked + { + return new(this.span.Slice((int)start, (int)length)); + } + } + + public SpanSerializationTarget Slice(long start) + { + return new(this.span.Slice((int)start)); + } + + public Span AsSpan(long start, int length) + { + checked + { + return this.span.Slice((int)start, (int)length); + } + } +} + +#endif \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs index 03f326a4..127ef29c 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,15 @@ public void Reset() /// /// Writes a shared string. /// - public void WriteSharedString( - TSpanWriter spanWriter, - Span data, - int offset, + public void WriteSharedString( + TTarget target, + long offset, string value, - SerializationContext context) where TSpanWriter : ISpanWriter + SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { // Find the associative set that must contain our key. var cache = this.sharedStringOffsetCache; @@ -95,7 +98,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 +110,13 @@ public void WriteSharedString( /// /// Flush any pending writes. /// - public void FlushWrites(TSpanWriter writer, Span data, SerializationContext context) where TSpanWriter : ISpanWriter + public void FlushWrites( + TTarget target, + SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { var cache = this.sharedStringOffsetCache; for (int i = 0; i < cache.Length; ++i) @@ -117,7 +126,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 +137,21 @@ public void FlushWrites(TSpanWriter writer, Span data, Serial } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void FlushSharedString( - TSpanWriter spanWriter, - Span span, + private static void FlushSharedString( + TTarget target, string value, - List offsets, - SerializationContext context) where TSpanWriter : ISpanWriter + List offsets, + SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { - 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 +163,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 index 304a6596..16bb6dc1 100644 --- a/src/FlatSharp.Runtime/IO/SpanWriter.cs +++ b/src/FlatSharp.Runtime/IO/SpanWriter.cs @@ -114,6 +114,6 @@ public int GetStringBytes(Span destination, string value, Encoding encodin public void FlushSharedStrings(ISharedStringWriter writer, Span destination, SerializationContext context) { - writer.FlushWrites(this, destination, context); + throw new NotSupportedException(); } } diff --git a/src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs b/src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs index ac506470..a0b83b1f 100644 --- a/src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs +++ b/src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Buffers; using System.Runtime.InteropServices; namespace FlatSharp.Internal; @@ -23,84 +24,112 @@ namespace FlatSharp.Internal; /// public static class SpanWriterExtensions { - public static void WriteReadOnlyByteMemoryBlock( - this TSpanWriter spanWriter, - Span span, + public static void WriteReadOnlyByteMemoryBlock( + this TTarget target, ReadOnlyMemory memory, - int offset, - SerializationContext ctx) where TSpanWriter : ISpanWriter + long offset, + SerializationContext ctx) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { int numberOfItems = memory.Length; - int vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); + long vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); - spanWriter.WriteUOffset(span, offset, vectorStartOffset); - spanWriter.WriteInt(span, numberOfItems, vectorStartOffset); + target.WriteUOffset(offset, vectorStartOffset); + target.WriteInt32(vectorStartOffset, numberOfItems); - memory.Span.CopyTo(span.Slice(vectorStartOffset + sizeof(uint))); + memory.Span.CopyTo(target.AsSpan(vectorStartOffset + sizeof(uint), numberOfItems)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnsafeWriteSpan( - this TSpanWriter spanWriter, - Span span, + public static void UnsafeWriteSpan( + this TSerializationTarget target, Span buffer, - int offset, + long offset, int alignment, - SerializationContext ctx) where TSpanWriter : ISpanWriter where TElement : unmanaged + SerializationContext ctx) + where TSerializationTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + 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( + long vectorStartOffset = ctx.AllocateVector( itemAlignment: alignment, numberOfItems, sizePerItem: Unsafe.SizeOf()); - spanWriter.WriteUOffset(span, offset, vectorStartOffset); - spanWriter.WriteInt(span, numberOfItems, vectorStartOffset); + target.WriteUOffset(offset, vectorStartOffset); + target.WriteInt32(vectorStartOffset, numberOfItems); - var start = span.Slice(vectorStartOffset + sizeof(uint), checked(numberOfItems * Unsafe.SizeOf())); + Span destination = target.AsSpan( + vectorStartOffset + sizeof(uint), + checked(numberOfItems * Unsafe.SizeOf())); - MemoryMarshal.Cast(buffer).CopyTo(start); + MemoryMarshal.Cast(buffer).CopyTo(destination); } /// /// Writes the given string. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteString( - this TSpanWriter spanWriter, - Span span, + public static void WriteString( + this TTarget target, string value, - int offset, - SerializationContext context) where TSpanWriter : ISpanWriter + long offset, + SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { - int stringOffset = spanWriter.WriteAndProvisionString(span, value, context); - spanWriter.WriteUOffset(span, offset, stringOffset); + long stringOffset = target.WriteAndProvisionString(value, context); + target.WriteUOffset(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 + public static long WriteAndProvisionString( + this TTarget target, + string value, + SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { 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); + long stringStartOffset = context.AllocateVector(sizeof(byte), maxItems, sizeof(byte)); + + Span destination = target.AsSpan(stringStartOffset + 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 - span[stringStartOffset + bytesWritten + sizeof(uint)] = 0; + target[stringStartOffset + bytesWritten + sizeof(uint)] = 0; // write length - spanWriter.WriteInt(span, bytesWritten, stringStartOffset); + target.WriteInt32(stringStartOffset, bytesWritten); // give back unused space. Account for null terminator. context.Offset -= maxItems - (bytesWritten + 1); @@ -109,13 +138,19 @@ public static int WriteAndProvisionString(this TSpanWriter spanWrit } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteUOffset(this TSpanWriter spanWriter, Span span, int offset, int secondOffset) - where TSpanWriter : ISpanWriter + public static void WriteUOffset( + this TSerializationTarget target, + long offset, + long secondOffset) + where TSerializationTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { checked { uint uoffset = (uint)(secondOffset - offset); - spanWriter.WriteUInt(span, uoffset, offset); + target.WriteUInt32(offset, uoffset); } } @@ -128,7 +163,7 @@ public static void WriteBool(this TSpanWriter spanWriter, Span(this TSpanWriter spanWriter, int offset, int size) where TSpanWriter : ISpanWriter + public static void CheckAlignment(this TSpanWriter spanWriter, long offset, int size) where TSpanWriter : ISpanWriter { #if DEBUG if (offset % size != 0) diff --git a/src/FlatSharp.Runtime/IPostSerializeAction.cs b/src/FlatSharp.Runtime/IPostSerializeAction.cs new file mode 100644 index 00000000..c2112663 --- /dev/null +++ b/src/FlatSharp.Runtime/IPostSerializeAction.cs @@ -0,0 +1,30 @@ +/* + * 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(TTarget target, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + ; +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/ISerializer.cs b/src/FlatSharp.Runtime/ISerializer.cs index a86b45aa..b45e5a15 100644 --- a/src/FlatSharp.Runtime/ISerializer.cs +++ b/src/FlatSharp.Runtime/ISerializer.cs @@ -36,16 +36,20 @@ 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(TTarget target, object item) + where TTarget : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + ; /// /// 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 +78,20 @@ 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(TTarget destination, T item) + where TTarget : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + ; /// /// 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..9ca97d3e 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 ArraySerializationTarget(buffer), 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 ArraySerializationTarget(buffer), item); } /// @@ -136,9 +136,17 @@ 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); + byte[]? array = buffer.Array; + if (array is null) + { + return FSThrow.ArgumentNull(nameof(buffer)); + } + + return serializer.Write( + new ArraySerializationTarget(array, buffer.Offset, buffer.Count), + item); } /// @@ -146,9 +154,17 @@ 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); + byte[]? array = buffer.Array; + if (array is null) + { + return FSThrow.ArgumentNull(nameof(buffer)); + } + + return serializer.Write( + new ArraySerializationTarget(array, buffer.Offset, buffer.Count), + item); } /// @@ -156,9 +172,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 MemorySerializationTarget(buffer), item); } /// @@ -166,19 +182,21 @@ 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 MemorySerializationTarget(buffer), item); } + #if NET9_0_OR_GREATER + /// /// Writes the given item to the given buffer using the default SpanWriter. /// /// 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 SpanSerializationTarget(buffer), item); } /// @@ -186,22 +204,34 @@ 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 SpanSerializationTarget(buffer), item); } + + #endif /// /// 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); + 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."); + } + + #if NET9_0_OR_GREATER + Span buffer = bufferWriter.GetSpan((int)maxSize); + int bytesWritten = (int)serializer.Write(new SpanSerializationTarget(buffer), item); + #else + Memory buffer = bufferWriter.GetMemory((int)maxSize); + int bytesWritten = (int)serializer.Write(new MemorySerializationTarget(buffer), item); + #endif + bufferWriter.Advance(bytesWritten); - return bytesWritten; } @@ -211,11 +241,21 @@ 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); + 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."); + } + +#if NET9_0_OR_GREATER + Span buffer = bufferWriter.GetSpan((int)maxSize); + int bytesWritten = (int)serializer.Write(new SpanSerializationTarget(buffer), item); +#else + Memory buffer = bufferWriter.GetMemory((int)maxSize); + int bytesWritten = (int)serializer.Write(new MemorySerializationTarget(buffer), item); +#endif + bufferWriter.Advance(bytesWritten); - return bytesWritten; } } diff --git a/src/FlatSharp.Runtime/SerializationContext.cs b/src/FlatSharp.Runtime/SerializationContext.cs index 56e17530..0a65a330 100644 --- a/src/FlatSharp.Runtime/SerializationContext.cs +++ b/src/FlatSharp.Runtime/SerializationContext.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Buffers.Binary; using System.Threading; namespace FlatSharp.Internal; @@ -24,31 +25,26 @@ 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; /// /// Initializes a new serialization context. /// public SerializationContext() { - this.postSerializeActions = new List(); - this.vtableOffsets = new List(); + this.postSerializeActions = new List(); + this.vtableOffsets = new List(); } /// /// The maximum offset within the buffer. /// - public int Offset + public long Offset { get => this.offset; set => this.offset = value; @@ -63,7 +59,7 @@ 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; @@ -76,19 +72,23 @@ public void Reset(int capacity) /// Invokes any post-serialize actions. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InvokePostSerializeActions(Span span) + public void InvokePostSerializeActions(TTarget target) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { 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 +96,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 +113,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 +131,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,19 +149,25 @@ public int AllocateSpace(int bytesNeeded, int alignment) } [MethodImpl(MethodImplOptions.NoInlining)] // Common method; don't inline - public int FinishVTable( - Span buffer, + public long FinishVTable( + TTarget buffer, Span vtable) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { var offsets = this.vtableOffsets; int count = offsets.Count; for (int i = 0; i < count; ++i) { - int offset = offsets[i]; + long offset = offsets[i]; - ReadOnlySpan existingVTable = buffer.Slice(offset); - existingVTable = existingVTable.Slice(0, ScalarSpanReader.ReadUShort(existingVTable)); + ReadOnlySpan existingVTable = buffer.AsSpan(offset, sizeof(ushort)); + ushort vtableLength = BinaryPrimitives.ReadUInt16LittleEndian(existingVTable); + + existingVTable = buffer.AsSpan(offset, vtableLength); if (existingVTable.SequenceEqual(vtable)) { @@ -175,8 +181,9 @@ public int FinishVTable( } // 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.AsSpan(newVTableOffset, vtable.Length)); offsets.Add(newVTableOffset); // "Insert" this item in the middle of the list. @@ -189,11 +196,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..383ee45c 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)); } /// 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..2b9e9e36 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,49 +74,47 @@ 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(TTarget target, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { - 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 + ScalarSpanReader.ReadUInt(target.AsSpan(this.vectorUOffset, sizeof(uint))); + int vectorLength = (int)ScalarSpanReader.ReadUInt(target.AsSpan(vectorStartOffset, sizeof(uint))); + 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(target, index0Position, i, this.vTableIndex, keyInlineSize); } // Sort the offsets. - IntroSort(buffer, comparer, 0, vectorLength - 1, keyOffsets); + IntroSort(target, comparer, 0, vectorLength - 1, keyOffsets); // 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; + Span boundedVector = target.AsSpan(index0Position, sizeof(uint) * vectorLength); + long nextPosition = index0Position; for (int i = 0; i < keyOffsets.Length; ++i) { - (_, _, int tableOffset) = keyOffsets[i]; + (_, _, long tableOffset) = keyOffsets[i]; BinaryPrimitives.WriteUInt32LittleEndian(boundedVector.Slice(sizeof(uint) * i), (uint)(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 +123,16 @@ 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( + TTarget 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) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { checked { @@ -156,7 +176,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.AsSpan(pivotOffset, pivotLength); // Partition int num2 = lo; @@ -166,7 +186,7 @@ private static void IntroSort( while (true) { var (keyOffset, keyLength, _) = keyLocations[++num2]; - var keySpan = buffer.Slice(keyOffset, keyLength); + var keySpan = buffer.AsSpan(keyOffset, keyLength); if (keyComparer.Compare(keyOffset != 0, keySpan, pivotExists, pivotSpan) >= 0) { break; @@ -176,7 +196,7 @@ private static void IntroSort( while (true) { var (keyOffset, keyLength, _) = keyLocations[--num3]; - var keySpan = buffer.Slice(keyOffset, keyLength); + var keySpan = buffer.AsSpan(keyOffset, keyLength); if (keyComparer.Compare(pivotExists, pivotSpan, keyOffset != 0, keySpan) >= 0) { break; @@ -203,24 +223,28 @@ private static void IntroSort( } } - private static void InsertionSort( - ReadOnlySpan buffer, + private static void InsertionSort( + TTarget 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) + where TTarget : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif { 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.AsSpan(valTuple.offset, valTuple.length); while (num >= lo) { - (int keyOffset, int keyLength, _) = keyLocations[num]; - ReadOnlySpan keySpan = buffer.Slice(keyOffset, keyLength); + (long keyOffset, int keyLength, _) = keyLocations[num]; + ReadOnlySpan keySpan = buffer.AsSpan(keyOffset, keyLength); if (comparer.Compare(valTuple.offset != 0, valSpan, keyOffset != 0, keySpan) < 0) { @@ -237,21 +261,25 @@ private static void InsertionSort( } } - private static void SwapIfGreater( - ReadOnlySpan vector, + private static void SwapIfGreater( + TTarget target, TSpanComparer comparer, int leftIndex, int rightIndex, - Span<(int, int, int)> keyOffsets) where TSpanComparer : ISpanComparer + Span<(long, int, long)> keyOffsets) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { - (int leftOffset, int leftLength, _) = keyOffsets[leftIndex]; - (int rightOffset, int rightLength, _) = keyOffsets[rightIndex]; + (long leftOffset, int leftLength, _) = keyOffsets[leftIndex]; + (long rightOffset, int rightLength, _) = keyOffsets[rightIndex]; bool leftExists = leftOffset != 0; bool rightExists = rightOffset != 0; - var leftSpan = vector.Slice(leftOffset, leftLength); - var rightSpan = vector.Slice(rightOffset, rightLength); + var leftSpan = target.AsSpan(leftOffset, leftLength); + var rightSpan = target.AsSpan(rightOffset, rightLength); if (comparer.Compare(leftExists, leftSpan, rightExists, rightSpan) > 0) { @@ -259,7 +287,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 +305,26 @@ 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( + TTarget buffer, + long index0Position, int vectorIndex, int vtableIndex, int? inlineItemSize) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { // 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)ScalarSpanReader.ReadUInt(buffer.AsSpan(tableOffset, sizeof(uint))); // Consult the vtable. - int vtableOffset = tableOffset - ScalarSpanReader.ReadInt(buffer.Slice(tableOffset)); + long vtableOffset = tableOffset - ScalarSpanReader.ReadInt(buffer.AsSpan(tableOffset, sizeof(int))); // 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 = (ScalarSpanReader.ReadUShort(buffer.AsSpan(vtableOffset, sizeof(ushort))) / 2) - 2; if (vtableIndex >= vtableLengthEntries) { @@ -300,7 +332,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 + ScalarSpanReader.ReadUShort(buffer.AsSpan(vtableOffset + 2 * (2 + vtableIndex), sizeof(ushort))); if (inlineItemSize is not null) { return (fieldOffset, inlineItemSize.Value, tableOffset); @@ -312,8 +344,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)ScalarSpanReader.ReadUInt(buffer.AsSpan(fieldOffset, sizeof(uint))); + int stringLength = (int)ScalarSpanReader.ReadUInt(buffer.AsSpan(uoffsetToString, sizeof(uint))); 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..8f379f35 100644 --- a/src/FlatSharp/FlatBufferSerializer.cs +++ b/src/FlatSharp/FlatBufferSerializer.cs @@ -174,26 +174,20 @@ 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, TTarget destination) where T : class - where TSpanWriter : ISpanWriter + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { - 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..d1dcfa81 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")}}); @@ -166,8 +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 offset = this.offset + ({GetEfficientMultiply(inlineSize, "index")}); + var data = new InputBufferSerializationTarget<{context.InputBufferTypeName}>({context.InputBufferVariableName}, offset, {inlineSize}); {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..01803792 100644 --- a/src/FlatSharp/FlatSharp.csproj +++ b/src/FlatSharp/FlatSharp.csproj @@ -33,13 +33,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - 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..6abd5d55 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)}()", + $"target", "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 target = new InputBufferSerializationTarget<{parserContext.InputBufferTypeName}>(buffer); {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..d52af7b8 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 }); @@ -428,11 +432,14 @@ private HashSet TraverseAssemblyReferenceGraph() string methodText = $@" - public void Write(TSpanWriter writer, Span target, {CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root, SerializationContext context) - where TSpanWriter : ISpanWriter + public void Write(ref TTarget target, {CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if {CSharpHelpers.Net9PreprocessorVariable} + , allows ref struct + #endif {{ {writeFileId} - {parts.@namespace}.{parts.className}.{parts.methodName}(writer, target, root, 0, context); + {parts.@namespace}.{parts.className}.{parts.methodName}(ref target, root, 0, context); }} "; bodyParts.Add(methodText); @@ -501,41 +508,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 +566,7 @@ internal string ImplementHelperClass(ITypeModel typeModel, IEnumerable( - TSpanWriter {context.SpanWriterVariableName}, - Span {context.SpanVariableName}, + internal static void {DefaultMethodNameResolver.ResolveSerialize(typeModel).methodName}( + ref TTarget {context.TargetVariableName}, {CSharpHelpers.GetGlobalCompilableTypeName(typeModel.ClrType)} {context.ValueVariableName}, {GetVTableOffsetVariableType(typeModel.PhysicalLayout.Length)} {context.OffsetVariableName} {serializationContextParameter} - {tableFieldContextParameter}) where TSpanWriter : ISpanWriter + {tableFieldContextParameter}) where TTarget : IFlatBufferSerializationTarget + #if {CSharpHelpers.Net9PreprocessorVariable} + , allows ref struct + #endif {{ {method.MethodBody} }} @@ -792,11 +767,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..4e89c15b 100644 --- a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs +++ b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs @@ -26,8 +26,7 @@ public record SerializationCodeGenContext { public SerializationCodeGenContext( string serializationContextVariableName, - string spanVariableName, - string spanWriterVariableName, + string targetVariableName, string valueVariableName, string offsetVariableName, string tableFieldContextVariableName, @@ -37,8 +36,7 @@ public SerializationCodeGenContext( IReadOnlyDictionary> allFieldContexts) { this.SerializationContextVariableName = serializationContextVariableName; - this.SpanWriterVariableName = spanWriterVariableName; - this.SpanVariableName = spanVariableName; + this.TargetVariableName = targetVariableName; this.ValueVariableName = valueVariableName; this.OffsetVariableName = offsetVariableName; this.TypeModelContainer = typeModelContainer; @@ -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; } - + public string TargetVariableName { 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}(ref {this.TargetVariableName}, {this.ValueVariableName}, {byRef}{this.OffsetVariableName}"); if (typeModel.SerializeMethodRequiresContext) { diff --git a/src/FlatSharp/TypeModel/ScalarTypeModel.cs b/src/FlatSharp/TypeModel/ScalarTypeModel.cs index b48e9d4e..6811a3c6 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModel.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModel.cs @@ -130,7 +130,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.TargetVariableName}.{this.SpanWriterWriteMethodName}({context.OffsetVariableName}, {variableName});") { IsMethodInline = true, }; diff --git a/src/FlatSharp/TypeModel/ScalarTypeModels.cs b/src/FlatSharp/TypeModel/ScalarTypeModels.cs index 46088677..e9737cfa 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.cs @@ -35,9 +35,9 @@ 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 SpanWriterWriteMethodName => "WriteBool"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -62,9 +62,9 @@ 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 SpanWriterWriteMethodName => nameof(ISpanWriter.WriteByte); + protected override string SpanWriterWriteMethodName => "WriteUInt8"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -89,9 +89,9 @@ 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 SpanWriterWriteMethodName => nameof(ISpanWriter.WriteSByte); + protected override string SpanWriterWriteMethodName => "WriteInt8"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -116,9 +116,9 @@ 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 SpanWriterWriteMethodName => "WriteUInt16"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -143,9 +143,9 @@ 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 SpanWriterWriteMethodName => nameof(ISpanWriter.WriteShort); + protected override string SpanWriterWriteMethodName => "WriteInt16"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -170,9 +170,9 @@ 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 SpanWriterWriteMethodName => nameof(ISpanWriter.WriteInt); + protected override string SpanWriterWriteMethodName => "WriteInt32"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -197,9 +197,9 @@ 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 SpanWriterWriteMethodName => nameof(ISpanWriter.WriteUInt); + protected override string SpanWriterWriteMethodName => "WriteUInt32"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -224,9 +224,9 @@ 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 SpanWriterWriteMethodName => "WriteInt64"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -251,9 +251,9 @@ 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 SpanWriterWriteMethodName => nameof(ISpanWriter.WriteULong); + protected override string SpanWriterWriteMethodName => "WriteUInt64"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -278,9 +278,9 @@ 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 SpanWriterWriteMethodName => nameof(ISpanWriter.WriteFloat); + protected override string SpanWriterWriteMethodName => "WriteFloat32"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -305,9 +305,9 @@ 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 SpanWriterWriteMethodName => nameof(ISpanWriter.WriteDouble); + protected override string SpanWriterWriteMethodName => "WriteFloat64"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { diff --git a/src/FlatSharp/TypeModel/ScalarTypeModels.tt b/src/FlatSharp/TypeModel/ScalarTypeModels.tt index c6374c3a..655a85c5 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.tt +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.tt @@ -23,20 +23,22 @@ <#@ 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 writeNameClass, string readNameClass)[] types = + { + ("Bool", "Bool", "bool", "", new[] { "bool" }, "SpanWriterExtensions", "InputBufferExtensions"), + ("UInt8", "Byte", "byte", "", new[] { "ubyte", "uint8" }, null, null), + ("Int8", "SByte", "sbyte", "", new[] { "byte", "int8" }, null, null), + ("UInt16", "UShort", "ushort", "", new[] { "ushort", "uint16" }, null, null), + ("Int16", "Short", "short", "", new[] { "short", "int16" }, null, null), + ("Int32", "Int", "int", "", new[] { "int", "int32" }, null, null), + ("UInt32", "UInt", "uint", "u", new[] { "uint", "uint32" }, null, null), + ("Int64", "Long", "long", "L", new[] { "long", "int64" }, null, null), + ("UInt64", "ULong", "ulong", "ul", new[] { "ulong", "uint64" }, null, null), + ("Float32", "Float", "float", "f", new[] { "float", "float32" }, null, null), + ("Float64", "Double", "double", "d", new[] { "double", "float64" }, null, null), + }; #> namespace FlatSharp.TypeModel; @@ -46,7 +48,8 @@ namespace FlatSharp.TypeModel; { var casedName = pair.casedName; var typeName = pair.typeName; - var className = $"{casedName}TypeModel"; + var commonName = pair.commonName; + var className = $"{commonName}TypeModel"; #> /// @@ -61,13 +64,13 @@ 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 SpanWriterWriteMethodName => nameof(<#= pair.writeNameClass ?? "ISpanWriter" #>.Write<#= casedName #>); + protected override string SpanWriterWriteMethodName => "Write<#= casedName #>"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -88,9 +91,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..a3a282dd 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,8 +114,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG if (!(sharedStringWriter is null)) {{ sharedStringWriter.{nameof(ISharedStringWriter.WriteSharedString)}( - {context.SpanWriterVariableName}, - {context.SpanVariableName}, + {context.TargetVariableName}, {context.OffsetVariableName}, {context.ValueVariableName}, {context.SerializationContextVariableName}); @@ -123,8 +122,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG }} }} - {context.SpanWriterVariableName}.{nameof(SpanWriterExtensions.WriteString)}( - {context.SpanVariableName}, + {context.TargetVariableName}.{nameof(SpanWriterExtensions.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.TargetVariableName}.{nameof(SpanWriterExtensions.WriteString)}( {context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName}); diff --git a/src/FlatSharp/TypeModel/StructTypeModel.cs b/src/FlatSharp/TypeModel/StructTypeModel.cs index 029f3bb3..4cae60d3 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.TargetVariableName}.Slice({context.OffsetVariableName}, {this.PhysicalLayout[0].InlineSize});"); + body.Add( $@" if ({context.ValueVariableName} is null) {{ + Span scopedSpan = scopedTarget.AsSpan(0, {this.PhysicalLayout[0].InlineSize}); scopedSpan.Clear(); return; }} @@ -158,7 +160,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG var propContext = context with { - SpanVariableName = "scopedSpan", + TargetVariableName = "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..09a9a889 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,9 +472,9 @@ 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.TargetVariableName}.{nameof(SpanWriterExtensions.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)}]; @@ -487,7 +488,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 +496,15 @@ 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(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.TargetVariableName}, vtable.Slice(0, vtableLength));"); + body.Add($"{context.TargetVariableName}.WriteInt32(tableStart, checked((int)(tableStart - vtablePosition)));"); body.AddRange(writeBlocks); @@ -577,7 +578,7 @@ private string GetPrepareSerializeBlock( } string writeVTableBlock = - $"{context.SpanWriterVariableName}.{nameof(ISpanWriter.WriteUShort)}(vtable, (ushort)({OffsetVariableName(index, i)} - tableStart), {vTableIndex});"; + $"{typeof(BinaryPrimitives).GGCTN()}.WriteUInt16LittleEndian(vtable.Slice({vTableIndex}), (ushort)({OffsetVariableName(index, i)} - tableStart));"; string inlineSerialize = string.Empty; if (memberModel.ItemTypeModel.SerializesInline) @@ -611,19 +612,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 diff --git a/src/FlatSharp/TypeModel/UnionTypeModel.cs b/src/FlatSharp/TypeModel/UnionTypeModel.cs index f3663e1c..c5699eaf 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.TargetVariableName}.{nameof(SpanWriterExtensions.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.TargetVariableName}.WriteUInt8( + {context.OffsetVariableName}.offset0, + discriminatorValue); switch (discriminatorValue) {{ diff --git a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs index 0b758f60..674996ad 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}.GetReadOnlySpan({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", + TargetVariableName = "sizedTarget", OffsetVariableName = $"{member.offset}", ValueVariableName = $"{context.ValueVariableName}.{member.accessor}", }; @@ -177,7 +177,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG } string body; - string slice = $"Span sizedSpan = {context.SpanVariableName}.Slice({context.OffsetVariableName}, {this.inlineSize});"; + string slice = $"var sizedTarget = {context.TargetVariableName}.Slice({context.OffsetVariableName}, {this.inlineSize});"; if (this.CanMarshalOnSerialize && context.Options.EnableValueStructMemoryMarshalDeserialization) { body = $@" @@ -186,6 +186,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG {StrykerSuppressor.SuppressNextLine("boolean")} if ({StrykerSuppressor.BitConverterTypeName}.IsLittleEndian) {{ + var sizedSpan = sizedTarget.AsSpan(0, {this.inlineSize}); #if {CSharpHelpers.Net8PreprocessorVariable} {typeof(MemoryMarshal).GetGlobalCompilableTypeName()}.Write(sizedSpan, in {context.ValueVariableName}); #else @@ -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.TargetVariableName}.AsSpan({context.OffsetVariableName}, {this.inlineSize}); #if NET8_0_OR_GREATER {typeof(MemoryMarshal).GetGlobalCompilableTypeName()}.Write(sizedSpan, in {context.ValueVariableName}); @@ -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}.GetReadOnlySpan({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..a2cf0a30 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.TargetVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.OffsetVariableName}, vectorOffset); + {context.TargetVariableName}.WriteInt32(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..41c304f2 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.TargetVariableName}.{nameof(SpanWriterExtensions.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..9c7aa57e 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.TargetVariableName}.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..0cfb80bb 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.TargetVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.OffsetVariableName}.offset0, discriminatorVectorOffset); + {context.TargetVariableName}.WriteInt32(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.TargetVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.OffsetVariableName}.offset1, offsetVectorOffset); + {context.TargetVariableName}.WriteInt32(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..4d02d9ee 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,81 +321,26 @@ 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 ReadOnlySpan GetReadOnlySpan(long start, int length) { - return inner.GetReadOnlySpan(); + return inner.GetReadOnlySpan(start, length); } - public Span GetSpan() + public Span GetSpan(long start, int length) { - 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); + return inner.GetSpan(start, length); } } } 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..68c19083 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,16 @@ 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(TTarget spanWriter, long offset, string value, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif { if (!this.offsets.TryGetValue(value, out var list)) { @@ -219,5 +201,26 @@ public void WriteSharedString( list.Add(offset); } + + public void FlushWrites(TTarget writer, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + 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/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..a866815a 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(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(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(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..3fc4e4dd 100644 --- a/src/Tests/FlatSharpEndToEndTests/Oracle/AlignmentTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/Oracle/AlignmentTests.cs @@ -75,9 +75,9 @@ public void NestedStructWithOddAlignment_Serialize() }; Span memory = new byte[10240]; - int offset = FS.AlignmentTestOuterHoder.Serializer.Write(memory, holder); + 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.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..b5aea4be 100644 --- a/src/Tests/FlatSharpEndToEndTests/Oracle/OracleSerializeTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/Oracle/OracleSerializeTests.cs @@ -46,9 +46,9 @@ public void SimpleTypes_WithValues() }; Span memory = new byte[10240]; - int size = FS.BasicTypes.Serializer.Write(memory, simple); + long size = FS.BasicTypes.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.BasicTypesVerify.Verify(new(bb), 4)); var oracle = Flatc.BasicTypes.GetRootAsBasicTypes(bb); @@ -75,9 +75,9 @@ public void SimpleTypes_Defaults() var simple = new FS.BasicTypes(); Span memory = new byte[10240]; - int size = FS.BasicTypes.Serializer.Write(memory, simple); + long size = FS.BasicTypes.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.BasicTypesVerify.Verify(new(bb), 4)); var oracle = Flatc.BasicTypes.GetRootAsBasicTypes(bb); @@ -112,9 +112,9 @@ public void LinkedList() }; Span memory = new byte[10240]; - int size = FS.LinkedListNode.Serializer.Write(memory, simple); + long size = FS.LinkedListNode.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.LinkedListNodeVerify.Verify(new(bb), 4)); var oracle = Flatc.LinkedListNode.GetRootAsLinkedListNode(bb); @@ -139,9 +139,9 @@ public void StructWithinTable() }; Span memory = new byte[10240]; - int size = FS.LocationHolder.Serializer.Write(memory, holder); + long size = FS.LocationHolder.Serializer.Write(memory, holder); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.LocationHolderVerify.Verify(new(bb), 4)); var oracle = Flatc.LocationHolder.GetRootAsLocationHolder(bb); @@ -177,9 +177,9 @@ public void ScalarVectors() }; Span memory = new byte[10240]; - int size = FS.Vectors.Serializer.Write(memory, table); + long size = FS.Vectors.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.VectorsVerify.Verify(new(bb), 4)); var oracle = Flatc.Vectors.GetRootAsVectors(bb); @@ -225,7 +225,7 @@ public void FiveByteStructVector() Span buffer = new byte[1024]; var bytecount = FS.FiveByteStructTable.Serializer.Write(buffer, table); - buffer = buffer.Slice(0, bytecount); + buffer = buffer.Slice(0, (int)bytecount); var bb = new ByteBuffer(buffer.ToArray()); Assert.IsTrue(Flatc.FiveByteStructTableVerify.Verify(new Verifier(bb), 4)); @@ -268,9 +268,9 @@ public void Union_Table_BasicTypes() }; Span memory = new byte[10240]; - int size = FS.UnionTable.Serializer.Write(memory, table); + long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionTable.GetRootAsUnionTable(bb); @@ -304,9 +304,9 @@ public void Union_String() }; Span memory = new byte[10240]; - int size = FS.UnionTable.Serializer.Write(memory, table); + long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionTable.GetRootAsUnionTable(bb); @@ -323,9 +323,9 @@ public void Union_NotSet() }; Span memory = new byte[10240]; - int size = FS.UnionTable.Serializer.Write(memory, table); + long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionTable.GetRootAsUnionTable(bb); @@ -348,9 +348,9 @@ public void Union_Struct_Location() }; Span memory = new byte[10240]; - int size = FS.UnionTable.Serializer.Write(memory, table); + long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.UnionTableVerify.Verify(new(bb), 4)); var oracle = Flatc.UnionTable.GetRootAsUnionTable(bb); @@ -375,9 +375,9 @@ public void NestedStruct_InnerStructSet() }; Span memory = new byte[10240]; - int size = FS.NestedStructs.Serializer.Write(memory, nested); + long size = FS.NestedStructs.Serializer.Write(memory, nested); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.NestedStructsVerify.Verify(new(bb), 4)); var oracle = Flatc.NestedStructs.GetRootAsNestedStructs(bb); @@ -401,9 +401,9 @@ public void NestedStruct_InnerStructNotSet() }; Span memory = new byte[10240]; - int size = FS.NestedStructs.Serializer.Write(memory, nested); + long size = FS.NestedStructs.Serializer.Write(memory, nested); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.Slice(0, (int)size).ToArray()); Assert.IsTrue(Flatc.NestedStructsVerify.Verify(new(bb), 4)); var oracle = Flatc.NestedStructs.GetRootAsNestedStructs(bb); @@ -428,9 +428,9 @@ public void VectorOfUnion() }; Span memory = new byte[10240]; - int size = FS.VectorOfUnionTable.Serializer.Write(memory, table); + long size = FS.VectorOfUnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, size).ToArray()); + var bb = new ByteBuffer(memory.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) From a0d2466057d559f06ad5ab4b1a22648fbde2ecad Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sun, 10 Nov 2024 14:07:45 -0800 Subject: [PATCH 02/13] Cleanup --- src/FlatSharp.Runtime/IO/ISpanWriter.cs | 88 --------- ...latBufferSerializationTargetExtensions.cs} | 175 +++++++++++++++--- .../IFlatBufferSerializationTarget.cs | 138 -------------- src/FlatSharp.Runtime/IO/SpanWriter.cs | 119 ------------ .../ISerializerExtensions.cs | 24 +++ src/FlatSharp/TypeModel/StringTypeModel.cs | 4 +- src/FlatSharp/TypeModel/TableTypeModel.cs | 2 +- src/FlatSharp/TypeModel/UnionTypeModel.cs | 2 +- .../TypeModel/Vectors/BaseVectorTypeModel.cs | 2 +- .../Vectors/MemoryVectorTypeModel.cs | 2 +- .../BaseVectorOfUnionTypeModel.cs | 4 +- .../FlatSharpEndToEndTests.csproj | 2 +- .../Oracle/AlignmentTests.cs | 4 +- .../Oracle/OracleSerializeTests.cs | 54 +++--- 14 files changed, 208 insertions(+), 412 deletions(-) delete mode 100644 src/FlatSharp.Runtime/IO/ISpanWriter.cs rename src/FlatSharp.Runtime/IO/{SpanWriterExtensions.cs => SerializationTarget/FlatBufferSerializationTargetExtensions.cs} (53%) delete mode 100644 src/FlatSharp.Runtime/IO/SpanWriter.cs diff --git a/src/FlatSharp.Runtime/IO/ISpanWriter.cs b/src/FlatSharp.Runtime/IO/ISpanWriter.cs deleted file mode 100644 index 6bc398e2..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/SpanWriterExtensions.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs similarity index 53% rename from src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs rename to src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs index a0b83b1f..0a77ba97 100644 --- a/src/FlatSharp.Runtime/IO/SpanWriterExtensions.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs @@ -1,29 +1,153 @@ -/* - * 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; +namespace FlatSharp; /// -/// Extension methods that apply to all implementations. +/// Extensions for IFlatBufferSerializationTarget /// -public static class SpanWriterExtensions +public static class FlatBufferSerializationTargetExtensions { + public static void WriteBool(this T target, long offset, bool value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target.WriteUInt8(offset, value ? SerializationHelpers.True : SerializationHelpers.False); + } + + public static void WriteUInt8(this T target, long offset, byte value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target[offset] = value; + } + + public static void WriteInt8(this T target, long offset, sbyte value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target[offset] = unchecked((byte)value); + } + + public static void WriteUInt16(this T target, long offset, ushort value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(ushort)); + BinaryPrimitives.WriteUInt16LittleEndian( + target.AsSpan(offset, sizeof(ushort)), + value); + } + + public static void WriteInt16(this T target, long offset, short value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(short)); + BinaryPrimitives.WriteInt16LittleEndian( + target.AsSpan(offset, sizeof(short)), + value); + } + + public static void WriteUInt32(this T target, long offset, uint value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(uint)); + BinaryPrimitives.WriteUInt32LittleEndian( + target.AsSpan(offset, sizeof(uint)), + value); + } + + public static void WriteInt32(this T target, long offset, int value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian( + target.AsSpan(offset, sizeof(int)), + value); + } + + public static void WriteUInt64(this T target, long offset, ulong value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(ulong)); + BinaryPrimitives.WriteUInt64LittleEndian( + target.AsSpan(offset, sizeof(ulong)), + value); + } + + public static void WriteInt64(this T target, long offset, long value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(long)); + BinaryPrimitives.WriteInt64LittleEndian( + target.AsSpan(offset, sizeof(long)), + value); + } + + + public static void WriteFloat32(this T target, long offset, float value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(float)); + +#if NETSTANDARD + ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout + { + value = value + }; + + target.WriteUInt32(offset, floatLayout.bytes); +#else + BinaryPrimitives.WriteSingleLittleEndian( + target.AsSpan(offset, sizeof(float)), + value); +#endif + } + + public static void WriteFloat64(this T target, long offset, double value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(double)); + +#if NETSTANDARD + target.WriteInt64(offset, BitConverter.DoubleToInt64Bits(value)); +#else + BinaryPrimitives.WriteDoubleLittleEndian( + target.AsSpan(offset, sizeof(double)), + value); +#endif + } + public static void WriteReadOnlyByteMemoryBlock( this TTarget target, ReadOnlyMemory memory, @@ -50,11 +174,11 @@ public static void UnsafeWriteSpan( long offset, int alignment, SerializationContext ctx) + where TElement : unmanaged where TSerializationTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif - where TElement : unmanaged { // Since we are copying bytes here, only LE is supported. FlatSharpInternal.AssertLittleEndian(); @@ -154,16 +278,9 @@ public static void WriteUOffset( } } - [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, long offset, int size) where TSpanWriter : ISpanWriter + private static void CheckAlignment(long offset, int size) { #if DEBUG if (offset % size != 0) @@ -172,4 +289,4 @@ public static void CheckAlignment(this TSpanWriter spanWriter, long } #endif } -} +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs index 1a7e1acd..6cc1df32 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs @@ -14,8 +14,6 @@ * limitations under the License. */ -using System.Buffers.Binary; - namespace FlatSharp; /// @@ -51,140 +49,4 @@ public interface IFlatBufferSerializationTarget /// Returns a Span{byte} over the given range. /// Span AsSpan(long start, int length); -} - -/// -/// Extensions for IFlatBufferSerializationTarget -/// -public static class FlatBufferSerializationTargetExtensions -{ - public static void WriteBool(this T target, long offset, bool value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target.WriteUInt8(offset, value ? SerializationHelpers.True : SerializationHelpers.False); - } - - public static void WriteUInt8(this T target, long offset, byte value) - where T : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - target[offset] = value; - } - - public static void WriteInt8(this T target, long offset, sbyte value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target[offset] = unchecked((byte)value); - } - - public static void WriteUInt16(this T target, long offset, ushort value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - BinaryPrimitives.WriteUInt16LittleEndian( - target.AsSpan(offset, sizeof(ushort)), - value); - } - - public static void WriteInt16(this T target, long offset, short value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - BinaryPrimitives.WriteInt16LittleEndian( - target.AsSpan(offset, sizeof(short)), - value); - } - - public static void WriteUInt32(this T target, long offset, uint value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - BinaryPrimitives.WriteUInt32LittleEndian( - target.AsSpan(offset, sizeof(uint)), - value); - } - - public static void WriteInt32(this T target, long offset, int value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - BinaryPrimitives.WriteInt32LittleEndian( - target.AsSpan(offset, sizeof(int)), - value); - } - - public static void WriteUInt64(this T target, long offset, ulong value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - BinaryPrimitives.WriteUInt64LittleEndian( - target.AsSpan(offset, sizeof(ulong)), - value); - } - - public static void WriteInt64(this T target, long offset, long value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - BinaryPrimitives.WriteInt64LittleEndian( - target.AsSpan(offset, sizeof(long)), - value); - } - - - public static void WriteFloat32(this T target, long offset, float value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - -#if NETSTANDARD - ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout - { - value = value - }; - - target.WriteUInt32(offset, floatLayout.bytes); -#else - BinaryPrimitives.WriteSingleLittleEndian( - target.AsSpan(offset, sizeof(float)), - value); -#endif - } - - public static void WriteFloat64(this T target, long offset, double value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { -#if NETSTANDARD - target.WriteInt64(offset, BitConverter.DoubleToInt64Bits(value)); -#else - BinaryPrimitives.WriteDoubleLittleEndian( - target.AsSpan(offset, sizeof(double)), - value); -#endif - } } \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SpanWriter.cs b/src/FlatSharp.Runtime/IO/SpanWriter.cs deleted file mode 100644 index 16bb6dc1..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) - { - throw new NotSupportedException(); - } -} diff --git a/src/FlatSharp.Runtime/ISerializerExtensions.cs b/src/FlatSharp.Runtime/ISerializerExtensions.cs index 9ca97d3e..5b7f80dc 100644 --- a/src/FlatSharp.Runtime/ISerializerExtensions.cs +++ b/src/FlatSharp.Runtime/ISerializerExtensions.cs @@ -118,7 +118,11 @@ public static object Parse(this ISerializer serializer, ReadOnlyMemory dat [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, byte[] buffer, T item) where T : class { +#if NET9_0_OR_GREATER + return serializer.Write(buffer.AsSpan(), item); +#else return serializer.Write(new ArraySerializationTarget(buffer), item); +#endif } /// @@ -128,7 +132,11 @@ public static long Write(this ISerializer serializer, byte[] buffer, T ite [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, byte[] buffer, object item) { +#if NET9_0_OR_GREATER + return serializer.Write(buffer.AsSpan(), item); +#else return serializer.Write(new ArraySerializationTarget(buffer), item); +#endif } /// @@ -138,6 +146,9 @@ public static long Write(this ISerializer serializer, byte[] buffer, object item [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, ArraySegment buffer, T item) where T : class { +#if NET9_0_OR_GREATER + return serializer.Write(buffer.AsSpan(), item); +#else byte[]? array = buffer.Array; if (array is null) { @@ -147,6 +158,7 @@ public static long Write(this ISerializer serializer, ArraySegment b return serializer.Write( new ArraySerializationTarget(array, buffer.Offset, buffer.Count), item); +#endif } /// @@ -156,6 +168,9 @@ public static long Write(this ISerializer serializer, ArraySegment b [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, ArraySegment buffer, object item) { +#if NET9_0_OR_GREATER + return serializer.Write(buffer.AsSpan(), item); +#else byte[]? array = buffer.Array; if (array is null) { @@ -165,6 +180,7 @@ public static long Write(this ISerializer serializer, ArraySegment buffer, return serializer.Write( new ArraySerializationTarget(array, buffer.Offset, buffer.Count), item); +#endif } /// @@ -174,7 +190,11 @@ public static long Write(this ISerializer serializer, ArraySegment buffer, [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, Memory buffer, T item) where T : class { +#if NET9_0_OR_GREATER + return serializer.Write(buffer.Span, item); +#else return serializer.Write(new MemorySerializationTarget(buffer), item); +#endif } /// @@ -184,7 +204,11 @@ public static long Write(this ISerializer serializer, Memory buffer, [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, Memory buffer, object item) { +#if NET9_0_OR_GREATER + return serializer.Write(buffer.Span, item); +#else return serializer.Write(new MemorySerializationTarget(buffer), item); +#endif } #if NET9_0_OR_GREATER diff --git a/src/FlatSharp/TypeModel/StringTypeModel.cs b/src/FlatSharp/TypeModel/StringTypeModel.cs index a3a282dd..7a85891e 100644 --- a/src/FlatSharp/TypeModel/StringTypeModel.cs +++ b/src/FlatSharp/TypeModel/StringTypeModel.cs @@ -122,7 +122,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG }} }} - {context.TargetVariableName}.{nameof(SpanWriterExtensions.WriteString)}( + {context.TargetVariableName}.WriteString( {context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName}); @@ -132,7 +132,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG { // otherwise, we can omit that code entirely. body = $@" - {context.TargetVariableName}.{nameof(SpanWriterExtensions.WriteString)}( + {context.TargetVariableName}.WriteString( {context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName}); diff --git a/src/FlatSharp/TypeModel/TableTypeModel.cs b/src/FlatSharp/TypeModel/TableTypeModel.cs index 09a9a889..9a630940 100644 --- a/src/FlatSharp/TypeModel/TableTypeModel.cs +++ b/src/FlatSharp/TypeModel/TableTypeModel.cs @@ -473,7 +473,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string methodStart = $@" long tableStart = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateSpace)}({maxInlineSize}, sizeof(int)); - {context.TargetVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.OffsetVariableName}, tableStart); + {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}, tableStart); long currentOffset = tableStart + sizeof(int); // skip past vtable soffset_t. int vtableLength = {minVtableLength}; diff --git a/src/FlatSharp/TypeModel/UnionTypeModel.cs b/src/FlatSharp/TypeModel/UnionTypeModel.cs index c5699eaf..7102e457 100644 --- a/src/FlatSharp/TypeModel/UnionTypeModel.cs +++ b/src/FlatSharp/TypeModel/UnionTypeModel.cs @@ -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.TargetVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.OffsetVariableName}.offset1, writeOffset);"; + {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}.offset1, writeOffset);"; } else { diff --git a/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs b/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs index a2cf0a30..8548307d 100644 --- a/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs +++ b/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs @@ -147,7 +147,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string body = $@" int count = {context.ValueVariableName}.{this.LengthPropertyName}; long vectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}({itemTypeModel.PhysicalLayout[0].Alignment}, count, {this.PaddedMemberInlineSize}); - {context.TargetVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.OffsetVariableName}, vectorOffset); + {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}, vectorOffset); {context.TargetVariableName}.WriteInt32(vectorOffset, count); vectorOffset += sizeof(int); diff --git a/src/FlatSharp/TypeModel/Vectors/MemoryVectorTypeModel.cs b/src/FlatSharp/TypeModel/Vectors/MemoryVectorTypeModel.cs index 41c304f2..5e28c248 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.TargetVariableName}.{nameof(SpanWriterExtensions.WriteReadOnlyByteMemoryBlock)}({context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName});"; + $"{context.TargetVariableName}.WriteReadOnlyByteMemoryBlock({context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName});"; return new CodeGeneratedMethod(body); } diff --git a/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs b/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs index 0cfb80bb..e5f3e1fe 100644 --- a/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs +++ b/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs @@ -123,12 +123,12 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string body = $@" int count = {context.ValueVariableName}.{this.LengthPropertyName}; long discriminatorVectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}(sizeof(byte), count, sizeof(byte)); - {context.TargetVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.OffsetVariableName}.offset0, discriminatorVectorOffset); + {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}.offset0, discriminatorVectorOffset); {context.TargetVariableName}.WriteInt32(discriminatorVectorOffset, count); discriminatorVectorOffset += sizeof(int); long offsetVectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}(sizeof(int), count, sizeof(int)); - {context.TargetVariableName}.{nameof(SpanWriterExtensions.WriteUOffset)}({context.OffsetVariableName}.offset1, offsetVectorOffset); + {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}.offset1, offsetVectorOffset); {context.TargetVariableName}.WriteInt32(offsetVectorOffset, count); offsetVectorOffset += sizeof(int); 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/Oracle/AlignmentTests.cs b/src/Tests/FlatSharpEndToEndTests/Oracle/AlignmentTests.cs index 3fc4e4dd..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]; + byte[] memory = new byte[10240]; long offset = FS.AlignmentTestOuterHoder.Serializer.Write(memory, holder); - var parsed = Flatc.AlignmentTestOuterHoder.GetRootAsAlignmentTestOuterHoder(new ByteBuffer(memory.Slice(0, (int)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 b5aea4be..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]; + byte[] memory = new byte[10240]; long size = FS.BasicTypes.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.BasicTypes.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.LinkedListNode.Serializer.Write(memory, simple); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.LocationHolder.Serializer.Write(memory, holder); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.Vectors.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, (int)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, (int)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]; + byte[] memory = new byte[10240]; long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.UnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.NestedStructs.Serializer.Write(memory, nested); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.NestedStructs.Serializer.Write(memory, nested); - var bb = new ByteBuffer(memory.Slice(0, (int)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]; + byte[] memory = new byte[10240]; long size = FS.VectorOfUnionTable.Serializer.Write(memory, table); - var bb = new ByteBuffer(memory.Slice(0, (int)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); From fffc1d4d45d2ab2eeab1ea21fc1ea6cadf363415 Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sun, 10 Nov 2024 20:50:39 -0800 Subject: [PATCH 03/13] Tidy --- .../GeneratedSerializerWrapper.cs | 5 + .../IO/InputBuffer/IInputBuffer.cs | 64 -------- .../IO/InputBuffer/InputBufferExtensions.cs | 138 +++++++++--------- .../IO/InputBuffer/MemoryInputBuffer.cs | 2 +- .../SerializationTargetInputBuffer.cs | 68 +++++++++ .../VirtualSerializationTarget.cs | 45 ++++++ src/FlatSharp.Runtime/ISerializer.cs | 5 + .../SortedVectorHelpersInternal.cs | 123 +++++++++------- 8 files changed, 258 insertions(+), 192 deletions(-) create mode 100644 src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs diff --git a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs index 353bdf8d..94ff38bf 100644 --- a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs +++ b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs @@ -83,6 +83,11 @@ item is IFlatBufferDeserializedObject deserializedObj && // than to introduce an 'if'. } + public long GetActualSize(T item) + { + return this.Write(new VirtualSerializationTarget(), item); + } + long ISerializer.GetMaxSize(object item) { return item switch diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs index bd6219da..9d001c0f 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs @@ -14,8 +14,6 @@ * limitations under the License. */ -using System.Text; - namespace FlatSharp; /// @@ -58,65 +56,3 @@ public interface IInputBuffer /// Memory GetMemory(long offset, int length); } - -/// -/// An implementation of IInputBuffer that contains all methods. Useful for -/// implementations that wish to completely control the behavior. -/// -public interface IInputBufferFull : IInputBuffer -{ - /// - /// Reads the byte at the given offset. - /// - byte ReadByte(long offset); - - /// - /// Reads the sbyte at the given offset. - /// - sbyte ReadSByte(long offset); - - /// - /// Reads the ushort at the given offset. - /// - ushort ReadUShort(long offset); - - /// - /// Reads the short at the given offset. - /// - short ReadShort(long offset); - - /// - /// Reads the uint at the given offset. - /// - uint ReadUInt(long offset); - - /// - /// Reads the int at the given offset. - /// - int ReadInt(long offset); - - /// - /// Reads the ulong at the given offset. - /// - ulong ReadULong(long offset); - - /// - /// Reads the long at the given offset. - /// - long ReadLong(long offset); - - /// - /// Reads the float at the given offset. - /// - float ReadFloat(long offset); - - /// - /// Reads the double at the given offset. - /// - double ReadDouble(long offset); - - /// - /// Reads the string of the given length at the given offset with the given encoding. - /// - string ReadString(long offset, int byteLength, Encoding encoding); -} diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs index 45717c3b..d3bbd35b 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs @@ -30,118 +30,108 @@ public static class InputBufferExtensions /// Reads a bool. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ReadBool(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static bool ReadBool(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadBool(offset); - } - return buffer.ReadByte(offset) != SerializationHelpers.False; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte ReadByte(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static byte ReadByte(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadByte(offset); - } - return buffer.GetReadOnlySpan(offset, sizeof(byte))[0]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static sbyte ReadSByte(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static sbyte ReadSByte(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadSByte(offset); - } - return (sbyte)buffer.ReadByte(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ReadUShort(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static ushort ReadUShort(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { buffer.CheckAlignment(offset, sizeof(ushort)); - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadUShort(offset); - } - return BinaryPrimitives.ReadUInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ushort))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static short ReadShort(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static short ReadShort(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { buffer.CheckAlignment(offset, sizeof(short)); - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadShort(offset); - } - return BinaryPrimitives.ReadInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(short))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ReadUInt(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static uint ReadUInt(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { buffer.CheckAlignment(offset, sizeof(uint)); - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadUInt(offset); - } - return BinaryPrimitives.ReadUInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(uint))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ReadInt(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static int ReadInt(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - buffer.CheckAlignment(offset, sizeof(int)); - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadInt(offset); - } - return BinaryPrimitives.ReadInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(int))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong ReadULong(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static ulong ReadULong(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { buffer.CheckAlignment(offset, sizeof(ulong)); - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadULong(offset); - } - return BinaryPrimitives.ReadUInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ulong))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long ReadLong(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static long ReadLong(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { buffer.CheckAlignment(offset, sizeof(long)); - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadLong(offset); - } - return BinaryPrimitives.ReadInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(long))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float ReadFloat(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static float ReadFloat(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { buffer.CheckAlignment(offset, sizeof(float)); - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadFloat(offset); - } #if NETSTANDARD ScalarSpanReader.FloatLayout layout = new() @@ -156,14 +146,14 @@ public static float ReadFloat(this TBuffer buffer, long offset) where T } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double ReadDouble(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static double ReadDouble(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif { buffer.CheckAlignment(offset, sizeof(double)); - if (buffer is IInputBufferFull) - { - return ((IInputBufferFull)buffer).ReadDouble(offset); - } - + #if NETSTANDARD return BitConverter.Int64BitsToDouble(buffer.ReadLong(offset)); #else @@ -266,7 +256,7 @@ public static ReadOnlyMemory ReadByteReadOnlyMemoryBlock(this TBu /// 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 TBuffer : IInputBuffer where TElement : unmanaged { // The local value stores a uoffset_t, so follow that now. @@ -276,9 +266,9 @@ public static Span UnsafeReadSpan(this TBuffer buff // 1. starts at correct offset for vector data // 2. has a length based on *TElement* count not *byte* count var byteSpanAtDataOffset = buffer.GetSpan( - uoffset + sizeof(uint), + uoffset + sizeof(uint), checked(Unsafe.SizeOf() * (int)buffer.ReadUInt(uoffset))); - + var sourceSpan = MemoryMarshal.Cast(byteSpanAtDataOffset); return sourceSpan; @@ -288,9 +278,9 @@ public static Span UnsafeReadSpan(this TBuffer buff public static long CopyTo(this TBuffer buffer, TTarget target) where TBuffer : IInputBuffer where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER +#if NET9_0_OR_GREATER , allows ref struct - #endif +#endif { if (target.Length < buffer.Length) { @@ -314,7 +304,11 @@ public static long CopyTo(this TBuffer buffer, TTarget target) [ExcludeFromCodeCoverage] // Not currently used. [Conditional("DEBUG")] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CheckAlignment(this TBuffer buffer, long offset, int size) where TBuffer : IInputBuffer + 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) diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs index 40316cfb..a03fe6ee 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs @@ -19,7 +19,7 @@ namespace FlatSharp; /// /// An implementation of InputBuffer for writable memory segments. /// -public struct MemoryInputBuffer : IInputBuffer +public readonly struct MemoryInputBuffer : IInputBuffer { private readonly MemoryPointer pointer; diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs new file mode 100644 index 00000000..f805df96 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs @@ -0,0 +1,68 @@ +/* + * 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. +/// +internal +#if NET9_0_OR_GREATER + ref +#endif +struct SerializationTargetInputBuffer : IInputBuffer + where TTarget : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif +{ + private readonly TTarget target; + + public SerializationTargetInputBuffer(TTarget target) + { + this.target = target; + } + + public bool IsPinned => false; + + public bool IsReadOnly => false; + + public long Length => this.target.Length; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetReadOnlySpan(long offset, int length) + { + return this.GetSpan(offset, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpan(long offset, int length) + { + return this.target.AsSpan(offset, length); + } + + [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) + { + return FSThrow.InvalidOperation>("Unsupported"); + } +} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs new file mode 100644 index 00000000..d0520798 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs @@ -0,0 +1,45 @@ +/* + * 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; + +internal struct VirtualSerializationTarget : IFlatBufferSerializationTarget +{ + public byte this[long index] + { + get => 0; + set { } + } + + public long Length => long.MaxValue; + + public VirtualSerializationTarget Slice(long start, long length) + { + return this; + } + + public VirtualSerializationTarget Slice(long start) + { + return this; + } + + public Span AsSpan(long start, int length) + { + return default; + } +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/ISerializer.cs b/src/FlatSharp.Runtime/ISerializer.cs index b45e5a15..1c69e4a8 100644 --- a/src/FlatSharp.Runtime/ISerializer.cs +++ b/src/FlatSharp.Runtime/ISerializer.cs @@ -88,6 +88,11 @@ long Write(TTarget destination, T item) #endif ; + /// + /// Computes the exact size necessary to serialize the given instance of . + /// + long GetActualSize(T item); + /// /// Computes the maximum size necessary to serialize the given instance of . /// diff --git a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs index 2b9e9e36..8daeb157 100644 --- a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs +++ b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs @@ -80,41 +80,49 @@ public void Invoke(TTarget target, SerializationContext context) , allows ref struct #endif { - long vectorStartOffset = vectorUOffset + ScalarSpanReader.ReadUInt(target.AsSpan(this.vectorUOffset, sizeof(uint))); - int vectorLength = (int)ScalarSpanReader.ReadUInt(target.AsSpan(vectorStartOffset, sizeof(uint))); - long index0Position = vectorStartOffset + sizeof(int); + checked + { + var buffer = new SerializationTargetInputBuffer(target); - (long, int, long)[]? pooledArray = null; + long vectorStartOffset = + vectorUOffset + buffer.ReadUInt(vectorUOffset); + int vectorLength = (int)buffer.ReadUInt(vectorStartOffset); + long index0Position = vectorStartOffset + sizeof(int); - // 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<(long offset, int length, long tableOffset)> keyOffsets = - vectorLength < 512 - ? stackalloc (long, int, long)[vectorLength] - : (pooledArray = ArrayPool<(long, int, long)>.Shared.Rent(vectorLength)).AsSpan().Slice(0, vectorLength); + (long, int, long)[]? pooledArray = null; - for (int i = 0; i < keyOffsets.Length; ++i) - { - keyOffsets[i] = GetKeyOffset(target, index0Position, i, this.vTableIndex, keyInlineSize); - } + // 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<(long offset, int length, long tableOffset)> keyOffsets = + vectorLength < 512 + ? stackalloc (long, int, long)[vectorLength] + : (pooledArray = ArrayPool<(long, int, long)>.Shared.Rent(vectorLength)).AsSpan() + .Slice(0, vectorLength); - // Sort the offsets. - IntroSort(target, comparer, 0, vectorLength - 1, keyOffsets); + for (int i = 0; i < keyOffsets.Length; ++i) + { + keyOffsets[i] = GetKeyOffset(target, index0Position, i, this.vTableIndex, keyInlineSize); + } - // 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 = target.AsSpan(index0Position, sizeof(uint) * vectorLength); - long nextPosition = index0Position; - for (int i = 0; i < keyOffsets.Length; ++i) - { - (_, _, long tableOffset) = keyOffsets[i]; - BinaryPrimitives.WriteUInt32LittleEndian(boundedVector.Slice(sizeof(uint) * i), (uint)(tableOffset - nextPosition)); - nextPosition += sizeof(uint); - } + // Sort the offsets. + IntroSort(target, comparer, 0, vectorLength - 1, keyOffsets); - if (pooledArray is not null) - { - ArrayPool<(long, int, long)>.Shared.Return(pooledArray); + // 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 = target.AsSpan(index0Position, sizeof(uint) * vectorLength); + long nextPosition = index0Position; + for (int i = 0; i < keyOffsets.Length; ++i) + { + (_, _, long tableOffset) = keyOffsets[i]; + BinaryPrimitives.WriteUInt32LittleEndian(boundedVector.Slice(sizeof(uint) * i), + (uint)(tableOffset - nextPosition)); + nextPosition += sizeof(uint); + } + + if (pooledArray is not null) + { + ArrayPool<(long, int, long)>.Shared.Return(pooledArray); + } } } @@ -306,7 +314,7 @@ private static void SwapVectorPositions(int leftIndex, int rightIndex, Span<(lon /// Left as unchecked since this is a sort operation (not a search). /// private static (long offset, int length, long tableOffset) GetKeyOffset( - TTarget buffer, + TTarget target, long index0Position, int vectorIndex, int vtableIndex, @@ -316,36 +324,41 @@ private static (long offset, int length, long tableOffset) GetKeyOffset , allows ref struct #endif { - // Find offset to the table at the index. - long tableOffset = index0Position + (sizeof(uint) * vectorIndex); - tableOffset += (int)ScalarSpanReader.ReadUInt(buffer.AsSpan(tableOffset, sizeof(uint))); + checked + { + var buffer = new SerializationTargetInputBuffer(target); + + // Find offset to the table at the index. + long tableOffset = index0Position + (sizeof(uint) * vectorIndex); + tableOffset += (int)buffer.ReadUInt(tableOffset); - // Consult the vtable. - long vtableOffset = tableOffset - ScalarSpanReader.ReadInt(buffer.AsSpan(tableOffset, sizeof(int))); + // Consult the vtable. + 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.AsSpan(vtableOffset, sizeof(ushort))) / 2) - 2; + // Vtables have two extra entries: vtable length and table length. The number of entries is vtableLengthBytes / 2 - 2 + int vtableLengthEntries = (buffer.ReadUShort(vtableOffset) / 2) - 2; - if (vtableIndex >= vtableLengthEntries) - { - return (0, 0, tableOffset); - } + if (vtableIndex >= vtableLengthEntries) + { + return (0, 0, tableOffset); + } - // Absolute offset of the field within the table. - long fieldOffset = tableOffset + ScalarSpanReader.ReadUShort(buffer.AsSpan(vtableOffset + 2 * (2 + vtableIndex), sizeof(ushort))); - if (inlineItemSize is not null) - { - return (fieldOffset, inlineItemSize.Value, tableOffset); - } + // Absolute offset of the field within the table. + long fieldOffset = tableOffset + buffer.ReadUShort(vtableOffset + 2 * (2 + vtableIndex)); + if (inlineItemSize is not null) + { + return (fieldOffset, inlineItemSize.Value, tableOffset); + } - if (fieldOffset == 0) - { - return (0, 0, tableOffset); - } + if (fieldOffset == 0) + { + return (0, 0, tableOffset); + } - // Strings are stored as a uoffset reference. Follow the indirection one more time. - long uoffsetToString = fieldOffset + (int)ScalarSpanReader.ReadUInt(buffer.AsSpan(fieldOffset, sizeof(uint))); - int stringLength = (int)ScalarSpanReader.ReadUInt(buffer.AsSpan(uoffsetToString, sizeof(uint))); - return (uoffsetToString + sizeof(uint), stringLength, tableOffset); + // Strings are stored as a uoffset reference. Follow the indirection one more time. + long uoffsetToString = fieldOffset + (int)buffer.ReadUInt(fieldOffset); + int stringLength = (int)buffer.ReadUInt(uoffsetToString); + return (uoffsetToString + sizeof(uint), stringLength, tableOffset); + } } } From f9a4629e4a1d99d64c06527b9c785e8aea8c82f3 Mon Sep 17 00:00:00 2001 From: James Courtney Date: Mon, 11 Nov 2024 12:42:00 -0800 Subject: [PATCH 04/13] Temp --- src/Directory.Build.targets | 2 +- src/FlatSharp.Runtime/FSThrow.cs | 4 + .../FlatSharp.Runtime.csproj | 9 + .../GeneratedSerializerWrapper.cs | 6 +- src/FlatSharp.Runtime/IGeneratedSerializer.cs | 2 +- .../IO/ISharedStringWriter.cs | 8 +- .../IO/InputBuffer/InputBufferExtensions.cs | 162 +---- .../SerializationTargetInputBuffer.cs | 22 +- .../ArraySerializationTarget.cs | 17 +- .../FlatBufferReaderWriterExtensions.cs | 201 ++++++ ...FlatBufferSerializationTargetExtensions.cs | 292 -------- .../IFlatBufferReaderWriter.cs | 89 +++ .../IFlatBufferSerializationTarget.cs | 52 -- .../InputBufferSerializationTarget.cs | 15 +- .../MemorySerializationTarget.cs | 14 +- .../SerializationTargetHelpers.cs | 667 ++++++++++++++++++ .../SerializationTargetHelpers.tt | 210 ++++++ .../SpanSerializationTarget.cs | 12 +- .../VirtualSerializationTarget.cs | 98 ++- .../IO/SharedStringWriter.cs | 18 +- src/FlatSharp.Runtime/IPostSerializeAction.cs | 4 +- src/FlatSharp.Runtime/ISerializer.cs | 8 +- src/FlatSharp.Runtime/ISpanComparer.cs | 7 +- src/FlatSharp.Runtime/SerializationContext.cs | 10 +- .../SortedVectorHelpersInternal.cs | 48 +- src/FlatSharp.Runtime/SpanComparers.cs | 300 +++++--- src/FlatSharp.Runtime/SpanComparers.tt | 40 +- src/FlatSharp.Runtime/StringSpanComparer.cs | 18 +- src/FlatSharp/FlatBufferSerializer.cs | 4 +- .../RoslynSerializerGenerator.cs | 10 +- .../SerializationCodeGenContext.cs | 2 +- .../ClassLib/SerializerConfigurationTests.cs | 8 +- 32 files changed, 1624 insertions(+), 735 deletions(-) create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferReaderWriterExtensions.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferReaderWriter.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.tt diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 51803969..a95aa005 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -9,7 +9,7 @@ false false RuntimeIdentifier - TargetFramework=net8.0 + TargetFramework=net8.0 diff --git a/src/FlatSharp.Runtime/FSThrow.cs b/src/FlatSharp.Runtime/FSThrow.cs index bde337ae..c1312cbd 100644 --- a/src/FlatSharp.Runtime/FSThrow.cs +++ b/src/FlatSharp.Runtime/FSThrow.cs @@ -200,5 +200,9 @@ public static bool NotMutable_DeserializedVector() public static void NotSupported_NativeArray_NonPinned() => throw new NotSupportedException("Non-greedy parsing of a NativeArray requires a pinned buffer."); + [DoesNotReturn] + public static void NotSupported_Generic(string message) + => throw new NotSupportedException(message); + #endregion } diff --git a/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj b/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj index 55ff7475..3d063d14 100644 --- a/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj +++ b/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj @@ -25,6 +25,10 @@ TextTemplatingFileGenerator UnionTypes.cs + + SerializationTargetHelpers.cs + TextTemplatingFileGenerator + @@ -42,5 +46,10 @@ True UnionTypes.tt + + True + True + SerializationTargetHelpers.tt + diff --git a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs index 94ff38bf..0be894da 100644 --- a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs +++ b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs @@ -148,8 +148,8 @@ public T Parse(TInputBuffer buffer, FlatBufferDeserializationOptio object ISerializer.Parse(TInputBuffer buffer, FlatBufferDeserializationOption? option) => this.Parse(buffer, option); - public long Write(TTarget destination, T item) - where TTarget : IFlatBufferSerializationTarget + public long Write(TBuffer destination, T item) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -206,7 +206,7 @@ item is IFlatBufferDeserializedObject deserializedObj && return serializationContext.Offset; } - long ISerializer.Write(TTarget target, object item) + long ISerializer.Write(TBuffer target, object item) { return item switch { diff --git a/src/FlatSharp.Runtime/IGeneratedSerializer.cs b/src/FlatSharp.Runtime/IGeneratedSerializer.cs index a528fe8e..22840606 100644 --- a/src/FlatSharp.Runtime/IGeneratedSerializer.cs +++ b/src/FlatSharp.Runtime/IGeneratedSerializer.cs @@ -47,7 +47,7 @@ void Write( ref TSerializationTarget target, T item, SerializationContext context) - where TSerializationTarget : IFlatBufferSerializationTarget + where TSerializationTarget : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs index 636c404e..8a037e4b 100644 --- a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs @@ -41,8 +41,8 @@ public interface ISharedStringWriter /// The location in the buffer of the uoffset to the string. /// The string to write. /// The serialization context. - void WriteSharedString(TTarget spanWriter, long offset, string value, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + void WriteSharedString(TBuffer spanWriter, long offset, string value, SerializationContext context) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -51,8 +51,8 @@ void WriteSharedString(TTarget spanWriter, long offset, string value, S /// /// Flushes any pending writes. Invoked at the end of a serialization operation. /// - void FlushWrites(TTarget writer, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + void FlushWrites(TBuffer writer, SerializationContext context) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs index d3bbd35b..63752ad3 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs @@ -31,134 +31,12 @@ public static class InputBufferExtensions /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool ReadBool(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - return buffer.ReadByte(offset) != SerializationHelpers.False; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte ReadByte(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - return buffer.GetReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static sbyte ReadSByte(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - return (sbyte)buffer.ReadByte(offset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ReadUShort(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - buffer.CheckAlignment(offset, sizeof(ushort)); - return BinaryPrimitives.ReadUInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ushort))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static short ReadShort(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - buffer.CheckAlignment(offset, sizeof(short)); - return BinaryPrimitives.ReadInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(short))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ReadUInt(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif { - buffer.CheckAlignment(offset, sizeof(uint)); - return BinaryPrimitives.ReadUInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(uint))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ReadInt(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - return BinaryPrimitives.ReadInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(int))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong ReadULong(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - buffer.CheckAlignment(offset, sizeof(ulong)); - return BinaryPrimitives.ReadUInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ulong))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long ReadLong(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - buffer.CheckAlignment(offset, sizeof(long)); - return BinaryPrimitives.ReadInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(long))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float ReadFloat(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - buffer.CheckAlignment(offset, sizeof(float)); - -#if NETSTANDARD - ScalarSpanReader.FloatLayout layout = new() - { - bytes = buffer.ReadUInt(offset) - }; - - return layout.value; -#else - return BinaryPrimitives.ReadSingleLittleEndian(buffer.GetReadOnlySpan(offset, sizeof(float))); -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double ReadDouble(this TBuffer buffer, long offset) - where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - buffer.CheckAlignment(offset, sizeof(double)); - -#if NETSTANDARD - return BitConverter.Int64BitsToDouble(buffer.ReadLong(offset)); -#else - return BinaryPrimitives.ReadDoubleLittleEndian(buffer.GetReadOnlySpan(offset, sizeof(double))); -#endif + return buffer.ReadUInt8(offset) != SerializationHelpers.False; } /// @@ -179,7 +57,10 @@ public static string ReadString(this TBuffer buffer, long offset) where /// Reads a string from the given uoffset. /// public static string ReadStringFromUOffset(this TBuffer buffer, long stringStart) - where TBuffer : IInputBuffer + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { int numberOfBytes = (int)buffer.ReadUInt(stringStart); ReadOnlySpan stringValue = buffer.GetReadOnlySpan(stringStart + sizeof(int), numberOfBytes); @@ -198,9 +79,13 @@ public static string ReadStringFromUOffset(this TBuffer buffer, long st /// /// Reads the given uoffset. /// - public static int ReadUOffset(this TBuffer buffer, long offset) where TBuffer : IInputBuffer + public static int ReadUOffset(this TBuffer buffer, long offset) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - int uoffset = buffer.ReadInt(offset); + int uoffset = buffer.ReadUInt32(offset); if (uoffset < sizeof(uint)) { FSThrow.InvalidData_UOffsetTooSmall((uint)uoffset); @@ -218,7 +103,11 @@ public static void InitializeVTable( long tableOffset, out long vtableOffset, out ulong vtableFieldCount, - out ReadOnlySpan fieldData) where TBuffer : IInputBuffer + out ReadOnlySpan fieldData) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { vtableOffset = tableOffset - buffer.ReadInt(tableOffset); ushort vtableLength = buffer.ReadUShort(vtableOffset); @@ -235,7 +124,10 @@ public static void InitializeVTable( // 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 + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { // The local value stores a uoffset_t, so follow that now. uoffset += buffer.ReadUOffset(uoffset); @@ -275,9 +167,12 @@ public static Span UnsafeReadSpan(this TBuffer buff } [MethodImpl(MethodImplOptions.NoInlining)] - public static long CopyTo(this TBuffer buffer, TTarget target) - where TBuffer : IInputBuffer - where TTarget : IFlatBufferSerializationTarget + public static long CopyTo(this TBuffer1 buffer, TBuffer2 target) + where TBuffer1 : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif + where TBuffer2 : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -293,8 +188,7 @@ public static long CopyTo(this TBuffer buffer, TTarget target) { long remaining = buffer.Length - offset; var chunk = buffer.GetReadOnlySpan(offset, (int)Math.Min(int.MaxValue, remaining)); - chunk.CopyTo(target.AsSpan(offset, chunk.Length)); - + target.CopyFrom(offset, chunk); offset += chunk.Length; } diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs index f805df96..1b402357 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs @@ -19,21 +19,25 @@ namespace FlatSharp; /// /// An implementation of InputBuffer for writable memory segments. /// -internal +internal readonly #if NET9_0_OR_GREATER ref #endif -struct SerializationTargetInputBuffer : IInputBuffer - where TTarget : IFlatBufferSerializationTarget +struct SerializationTargetInputBuffer : IInputBuffer + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif { - private readonly TTarget target; + private readonly TBuffer target; + private readonly Func> getSpan; - public SerializationTargetInputBuffer(TTarget target) + public SerializationTargetInputBuffer( + TBuffer target, + Func> getSpan) { this.target = target; + this.getSpan = getSpan; } public bool IsPinned => false; @@ -42,16 +46,14 @@ public SerializationTargetInputBuffer(TTarget target) public long Length => this.target.Length; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan GetReadOnlySpan(long offset, int length) { return this.GetSpan(offset, length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetSpan(long offset, int length) { - return this.target.AsSpan(offset, length); + return this.getSpan(this.target, offset, length); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -60,9 +62,9 @@ public ReadOnlyMemory GetReadOnlyMemory(long offset, int length) return this.GetMemory(offset, length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory GetMemory(long offset, int length) { - return FSThrow.InvalidOperation>("Unsupported"); + FSThrow.NotSupported_Generic("SerializationTargetInputBuffer does not support getting a memory"); + return default; } } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs index 041fa799..215d48e7 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs @@ -14,14 +14,12 @@ * limitations under the License. */ -using System.Buffers.Binary; - namespace FlatSharp; /// /// A serialization target that wraps a Span{byte}. Only supports the 32-bit address space. /// -public struct ArraySerializationTarget : IFlatBufferSerializationTarget +public readonly partial struct ArraySerializationTarget : IFlatBufferReaderWriter { private readonly byte[] array; private readonly int start; @@ -39,12 +37,6 @@ public ArraySerializationTarget(byte[] array, int start, int length) this.Length = length; this.array = array; } - - public byte this[long index] - { - get => this.array[checked(this.start + (int)index)]; - set => this.array[checked(this.start + (int)index)] = value; - } public long Length { get; } @@ -64,11 +56,14 @@ public ArraySerializationTarget Slice(long start) } } - public Span AsSpan(long start, int length) + private partial Span AsSpan(long offset, int length) { checked { - return this.array.AsSpan().Slice(this.start + (int)start, length); + long value = this.start + offset; + return this.array.AsSpan((int)value, length); } } + + private partial ReadOnlySpan AsReadOnlySpan(long offset, int length) => this.AsSpan(offset, length); } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferReaderWriterExtensions.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferReaderWriterExtensions.cs new file mode 100644 index 00000000..40ce4e5c --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferReaderWriterExtensions.cs @@ -0,0 +1,201 @@ +using System.Buffers; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace FlatSharp; + +/// +/// Extensions for IFlatBufferReaderWriter +/// +public static class FlatBufferReaderWriterExtensions +{ + public static void WriteBool(this T target, long offset, bool value) + where T : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target.WriteUInt8(offset, value ? SerializationHelpers.True : SerializationHelpers.False); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteFloat32(this T target, long offset, float value) + where T : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout + { + value = value + }; + + target.WriteUInt32(offset, floatLayout.bytes); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ReadFloat32(this T target, long offset) + where T : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout + { + bytes = target.ReadUInt32(offset), + }; + + return floatLayout.value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteFloat64(this T target, long offset, double value) + where T : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target.WriteInt64(offset, BitConverter.DoubleToInt64Bits(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ReadFloat64(this T target, long offset) + where T : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + return target.ReadInt64(offset); + } + + public static void WriteReadOnlyByteMemoryBlock( + this TBuffer target, + ReadOnlyMemory memory, + long offset, + SerializationContext ctx) + where TBuffer : IFlatBufferReaderWriter + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + int numberOfItems = memory.Length; + long vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); + + target.WriteUOffset(offset, vectorStartOffset); + target.WriteInt32(vectorStartOffset, numberOfItems); + target.CopyFrom(vectorStartOffset + sizeof(uint), memory.Span); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteSpan( + this TSerializationTarget target, + Span buffer, + long offset, + int alignment, + SerializationContext ctx) + where TElement : unmanaged + where TSerializationTarget : IFlatBufferReaderWriter + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + // 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()); + + target.WriteUOffset(offset, vectorStartOffset); + target.WriteInt32(vectorStartOffset, numberOfItems); + + target.CopyFrom( + vectorStartOffset + sizeof(uint), + MemoryMarshal.Cast(buffer)); + } + + /// + /// Writes the given string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteString( + this TBuffer target, + string value, + long offset, + SerializationContext context) + where TBuffer : IFlatBufferReaderWriter + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + long stringOffset = target.WriteAndProvisionString(value, context); + target.WriteUOffset(offset, stringOffset); + } + + /// + /// Writes the string to the buffer, returning the absolute offset of the string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long WriteAndProvisionString( + this TBuffer target, + string value, + SerializationContext context) + where TBuffer : IFlatBufferReaderWriter + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + 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)); + int bytesWritten = target.WriteStringBytes( + stringStartOffset + sizeof(int), + value, + SerializationHelpers.Encoding); + + // null teriminator + target.WriteUInt8(stringStartOffset + bytesWritten + sizeof(uint), 0); + + // write length + target.WriteInt32(stringStartOffset, bytesWritten); + + // give back unused space. Account for null terminator. + context.Offset -= maxItems - (bytesWritten + 1); + + return stringStartOffset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUOffset( + this TSerializationTarget target, + long offset, + long secondOffset) + where TSerializationTarget : IFlatBufferReaderWriter + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + checked + { + uint uoffset = (uint)(secondOffset - offset); + target.WriteUInt32(offset, uoffset); + } + } + + [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/FlatBufferSerializationTargetExtensions.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs deleted file mode 100644 index 0a77ba97..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs +++ /dev/null @@ -1,292 +0,0 @@ -using System.Buffers; -using System.Buffers.Binary; -using System.Runtime.InteropServices; - -namespace FlatSharp; - -/// -/// Extensions for IFlatBufferSerializationTarget -/// -public static class FlatBufferSerializationTargetExtensions -{ - public static void WriteBool(this T target, long offset, bool value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target.WriteUInt8(offset, value ? SerializationHelpers.True : SerializationHelpers.False); - } - - public static void WriteUInt8(this T target, long offset, byte value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target[offset] = value; - } - - public static void WriteInt8(this T target, long offset, sbyte value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target[offset] = unchecked((byte)value); - } - - public static void WriteUInt16(this T target, long offset, ushort value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(ushort)); - BinaryPrimitives.WriteUInt16LittleEndian( - target.AsSpan(offset, sizeof(ushort)), - value); - } - - public static void WriteInt16(this T target, long offset, short value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(short)); - BinaryPrimitives.WriteInt16LittleEndian( - target.AsSpan(offset, sizeof(short)), - value); - } - - public static void WriteUInt32(this T target, long offset, uint value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(uint)); - BinaryPrimitives.WriteUInt32LittleEndian( - target.AsSpan(offset, sizeof(uint)), - value); - } - - public static void WriteInt32(this T target, long offset, int value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(int)); - BinaryPrimitives.WriteInt32LittleEndian( - target.AsSpan(offset, sizeof(int)), - value); - } - - public static void WriteUInt64(this T target, long offset, ulong value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(ulong)); - BinaryPrimitives.WriteUInt64LittleEndian( - target.AsSpan(offset, sizeof(ulong)), - value); - } - - public static void WriteInt64(this T target, long offset, long value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(long)); - BinaryPrimitives.WriteInt64LittleEndian( - target.AsSpan(offset, sizeof(long)), - value); - } - - - public static void WriteFloat32(this T target, long offset, float value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(float)); - -#if NETSTANDARD - ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout - { - value = value - }; - - target.WriteUInt32(offset, floatLayout.bytes); -#else - BinaryPrimitives.WriteSingleLittleEndian( - target.AsSpan(offset, sizeof(float)), - value); -#endif - } - - public static void WriteFloat64(this T target, long offset, double value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(double)); - -#if NETSTANDARD - target.WriteInt64(offset, BitConverter.DoubleToInt64Bits(value)); -#else - BinaryPrimitives.WriteDoubleLittleEndian( - target.AsSpan(offset, sizeof(double)), - value); -#endif - } - - public static void WriteReadOnlyByteMemoryBlock( - this TTarget target, - ReadOnlyMemory memory, - long offset, - SerializationContext ctx) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - int numberOfItems = memory.Length; - long vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); - - target.WriteUOffset(offset, vectorStartOffset); - target.WriteInt32(vectorStartOffset, numberOfItems); - - memory.Span.CopyTo(target.AsSpan(vectorStartOffset + sizeof(uint), numberOfItems)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnsafeWriteSpan( - this TSerializationTarget target, - Span buffer, - long offset, - int alignment, - SerializationContext ctx) - where TElement : unmanaged - where TSerializationTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - // 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()); - - target.WriteUOffset(offset, vectorStartOffset); - target.WriteInt32(vectorStartOffset, numberOfItems); - - Span destination = target.AsSpan( - vectorStartOffset + sizeof(uint), - checked(numberOfItems * Unsafe.SizeOf())); - - MemoryMarshal.Cast(buffer).CopyTo(destination); - } - - /// - /// Writes the given string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteString( - this TTarget target, - string value, - long offset, - SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - long stringOffset = target.WriteAndProvisionString(value, context); - target.WriteUOffset(offset, stringOffset); - } - - /// - /// Writes the string to the buffer, returning the absolute offset of the string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long WriteAndProvisionString( - this TTarget target, - string value, - SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - 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)); - - Span destination = target.AsSpan(stringStartOffset + 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 - target[stringStartOffset + bytesWritten + sizeof(uint)] = 0; - - // write length - target.WriteInt32(stringStartOffset, bytesWritten); - - // give back unused space. Account for null terminator. - context.Offset -= maxItems - (bytesWritten + 1); - - return stringStartOffset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteUOffset( - this TSerializationTarget target, - long offset, - long secondOffset) - where TSerializationTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - checked - { - uint uoffset = (uint)(secondOffset - offset); - target.WriteUInt32(offset, uoffset); - } - } - - [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/IFlatBufferReaderWriter.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferReaderWriter.cs new file mode 100644 index 00000000..8ebe7c7f --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferReaderWriter.cs @@ -0,0 +1,89 @@ +/* + * 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.Text; + +namespace FlatSharp; + +/// +/// Represents a target for a FlatBuffer serialization operation. +/// +public interface IFlatBufferReaderWriter + where T : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif +{ + /// + /// Gets the length. + /// + long Length { get; } + + /// + /// Slices this object. + /// + T Slice(long start, long length); + + /// + /// Slices this object. + /// + T Slice(long start); + + #region Write Methods for Primitives + + void WriteUInt8(long offset, byte value); + + byte ReadUInt8(long offset); + + void WriteInt8(long offset, sbyte value); + + sbyte ReadInt8(long offset); + + void WriteInt16(long offset, short value); + + short ReadInt16(long offset); + + void WriteUInt16(long offset, ushort value); + + ushort ReadUInt16(long offset); + + void WriteInt32(long offset, int value); + + int ReadInt32(long offset); + + void WriteUInt32(long offset, uint value); + + uint ReadUInt32(long offset); + + void WriteInt64(long offset, long value); + + long ReadInt64(long offset); + + void WriteUInt64(long offset, ulong value); + + ulong ReadUInt64(long offset); + + int WriteStringBytes(long offset, string value, Encoding encoding); + + /// + /// Copies the given data into this object starting at the given offset. + /// + void CopyFrom(long offset, ReadOnlySpan data); + + void CopyTo(long offset, Span destination); + + #endregion +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs deleted file mode 100644 index 6cc1df32..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs +++ /dev/null @@ -1,52 +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. - */ - -namespace FlatSharp; - -/// -/// Represents a target for a FlatBuffer serialization operation. -/// -public interface IFlatBufferSerializationTarget - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif -{ - /// - /// Gets or sets the value at the given index. - /// - byte this[long index] { get; set; } - - /// - /// Gets the length. - /// - long Length { get; } - - /// - /// Slices this object. - /// - T Slice(long start, long length); - - /// - /// Slices this object. - /// - T Slice(long start); - - /// - /// Returns a Span{byte} over the given range. - /// - Span AsSpan(long start, int length); -} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs index c8f574ad..16f25d78 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs @@ -19,7 +19,7 @@ namespace FlatSharp; /// /// A serialization target that wraps an instance of . /// -public struct InputBufferSerializationTarget : IFlatBufferSerializationTarget> +public partial struct InputBufferSerializationTarget : IFlatBufferReaderWriter> where TInputBuffer : IInputBuffer { private readonly long start; @@ -38,12 +38,6 @@ public InputBufferSerializationTarget(TInputBuffer buffer, long start, long leng this.start = start; this.Length = length; } - - public byte this[long index] - { - get => this.buffer.ReadByte(this.start + index); - set => this.buffer.GetSpan(this.start + index, sizeof(byte))[0] = value; - } public long Length { get; } @@ -57,11 +51,8 @@ public InputBufferSerializationTarget Slice(long start) return new(this.buffer, this.start + start, this.Length - start); } - public Span AsSpan(long start, int length) + private partial Span AsSpan(long offset, int length) { - checked - { - return this.buffer.GetSpan(this.start + start, length); - } + return this.buffer.GetSpan(start + offset, length); } } \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs index 7fbef796..1aa7948a 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs @@ -19,7 +19,7 @@ namespace FlatSharp; /// /// A serialization target that wraps a Memory{byte}. Only supports the 32-bit address space. /// -public struct MemorySerializationTarget : IFlatBufferSerializationTarget +public readonly partial struct MemorySerializationTarget : IFlatBufferReaderWriter { private readonly Memory memory; @@ -27,12 +27,6 @@ public MemorySerializationTarget(Memory memory) { this.memory = memory; } - - public byte this[long index] - { - get => this.memory.Span[checked((int)index)]; - set => this.memory.Span[checked((int)index)] = value; - } public long Length => this.memory.Length; @@ -51,12 +45,12 @@ public MemorySerializationTarget Slice(long start) return new MemorySerializationTarget(this.memory.Slice((int)start)); } } - - public Span AsSpan(long start, int length) + + private partial Span AsSpan(long offset, int length) { checked { - return this.memory.Span.Slice((int)start, length); + return this.memory.Span.Slice((int)offset, length); } } } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.cs new file mode 100644 index 00000000..e353b919 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.cs @@ -0,0 +1,667 @@ +/* + * 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; +using System.Buffers; +using System.Text; + +namespace FlatSharp; + +public partial struct ArraySerializationTarget +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt8(long offset, byte value) + { + this.AsSpan(offset, sizeof(byte))[0] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadUInt8(long offset) + { + return this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt8(long offset, sbyte value) + { + this.AsSpan(offset, sizeof(byte))[0] = (byte)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadInt8(long offset) + { + return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt16(long offset, short value) + { + BinaryPrimitives.WriteInt16LittleEndian( + this.AsSpan(offset, sizeof(short)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16(long offset) + { + return BinaryPrimitives.ReadInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(short))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt16(long offset, ushort value) + { + BinaryPrimitives.WriteUInt16LittleEndian( + this.AsSpan(offset, sizeof(ushort)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16(long offset) + { + return BinaryPrimitives.ReadUInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ushort))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(long offset, int value) + { + BinaryPrimitives.WriteInt32LittleEndian( + this.AsSpan(offset, sizeof(int)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32(long offset) + { + return BinaryPrimitives.ReadInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(int))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(long offset, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian( + this.AsSpan(offset, sizeof(uint)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32(long offset) + { + return BinaryPrimitives.ReadUInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(uint))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(long offset, long value) + { + BinaryPrimitives.WriteInt64LittleEndian( + this.AsSpan(offset, sizeof(long)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64(long offset) + { + return BinaryPrimitives.ReadInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(long))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(long offset, ulong value) + { + BinaryPrimitives.WriteUInt64LittleEndian( + this.AsSpan(offset, sizeof(ulong)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64(long offset) + { + return BinaryPrimitives.ReadUInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ulong))); + } + + public int WriteStringBytes(long offset, string value, Encoding encoding) + { + checked + { + int maxBytes = encoding.GetMaxByteCount(value.Length); + +#if NETSTANDARD2_0 + byte[] temp = ArrayPool.Shared.Rent(maxBytes); + int length = encoding.GetBytes(value, 0, value.Length, temp, 0); + temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); + ArrayPool.Shared.Return(temp); + + return length; +#else + int size = maxBytes; + long remainingSpace = this.Length - offset; + if (remainingSpace < maxBytes) + { + size = (int)Math.Min(int.MaxValue, remainingSpace); + } + + Span span = this.AsSpan(offset, size); + return encoding.GetBytes(value, span); +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(long offset, Span target) + { + this.AsReadOnlySpan(offset, target.Length).CopyTo(target); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyFrom(long offset, ReadOnlySpan data) + { + data.CopyTo(this.AsSpan(offset, data.Length)); + } + + private partial Span AsSpan(long offset, int length); + + private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); +} + + +public partial struct InputBufferSerializationTarget +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt8(long offset, byte value) + { + this.AsSpan(offset, sizeof(byte))[0] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadUInt8(long offset) + { + return this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt8(long offset, sbyte value) + { + this.AsSpan(offset, sizeof(byte))[0] = (byte)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadInt8(long offset) + { + return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt16(long offset, short value) + { + BinaryPrimitives.WriteInt16LittleEndian( + this.AsSpan(offset, sizeof(short)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16(long offset) + { + return BinaryPrimitives.ReadInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(short))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt16(long offset, ushort value) + { + BinaryPrimitives.WriteUInt16LittleEndian( + this.AsSpan(offset, sizeof(ushort)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16(long offset) + { + return BinaryPrimitives.ReadUInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ushort))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(long offset, int value) + { + BinaryPrimitives.WriteInt32LittleEndian( + this.AsSpan(offset, sizeof(int)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32(long offset) + { + return BinaryPrimitives.ReadInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(int))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(long offset, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian( + this.AsSpan(offset, sizeof(uint)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32(long offset) + { + return BinaryPrimitives.ReadUInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(uint))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(long offset, long value) + { + BinaryPrimitives.WriteInt64LittleEndian( + this.AsSpan(offset, sizeof(long)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64(long offset) + { + return BinaryPrimitives.ReadInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(long))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(long offset, ulong value) + { + BinaryPrimitives.WriteUInt64LittleEndian( + this.AsSpan(offset, sizeof(ulong)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64(long offset) + { + return BinaryPrimitives.ReadUInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ulong))); + } + + public int WriteStringBytes(long offset, string value, Encoding encoding) + { + checked + { + int maxBytes = encoding.GetMaxByteCount(value.Length); + +#if NETSTANDARD2_0 + byte[] temp = ArrayPool.Shared.Rent(maxBytes); + int length = encoding.GetBytes(value, 0, value.Length, temp, 0); + temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); + ArrayPool.Shared.Return(temp); + + return length; +#else + int size = maxBytes; + long remainingSpace = this.Length - offset; + if (remainingSpace < maxBytes) + { + size = (int)Math.Min(int.MaxValue, remainingSpace); + } + + Span span = this.AsSpan(offset, size); + return encoding.GetBytes(value, span); +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(long offset, Span target) + { + this.AsReadOnlySpan(offset, target.Length).CopyTo(target); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyFrom(long offset, ReadOnlySpan data) + { + data.CopyTo(this.AsSpan(offset, data.Length)); + } + + private partial Span AsSpan(long offset, int length); + + private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); +} + + +public partial struct MemorySerializationTarget +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt8(long offset, byte value) + { + this.AsSpan(offset, sizeof(byte))[0] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadUInt8(long offset) + { + return this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt8(long offset, sbyte value) + { + this.AsSpan(offset, sizeof(byte))[0] = (byte)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadInt8(long offset) + { + return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt16(long offset, short value) + { + BinaryPrimitives.WriteInt16LittleEndian( + this.AsSpan(offset, sizeof(short)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16(long offset) + { + return BinaryPrimitives.ReadInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(short))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt16(long offset, ushort value) + { + BinaryPrimitives.WriteUInt16LittleEndian( + this.AsSpan(offset, sizeof(ushort)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16(long offset) + { + return BinaryPrimitives.ReadUInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ushort))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(long offset, int value) + { + BinaryPrimitives.WriteInt32LittleEndian( + this.AsSpan(offset, sizeof(int)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32(long offset) + { + return BinaryPrimitives.ReadInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(int))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(long offset, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian( + this.AsSpan(offset, sizeof(uint)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32(long offset) + { + return BinaryPrimitives.ReadUInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(uint))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(long offset, long value) + { + BinaryPrimitives.WriteInt64LittleEndian( + this.AsSpan(offset, sizeof(long)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64(long offset) + { + return BinaryPrimitives.ReadInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(long))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(long offset, ulong value) + { + BinaryPrimitives.WriteUInt64LittleEndian( + this.AsSpan(offset, sizeof(ulong)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64(long offset) + { + return BinaryPrimitives.ReadUInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ulong))); + } + + public int WriteStringBytes(long offset, string value, Encoding encoding) + { + checked + { + int maxBytes = encoding.GetMaxByteCount(value.Length); + +#if NETSTANDARD2_0 + byte[] temp = ArrayPool.Shared.Rent(maxBytes); + int length = encoding.GetBytes(value, 0, value.Length, temp, 0); + temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); + ArrayPool.Shared.Return(temp); + + return length; +#else + int size = maxBytes; + long remainingSpace = this.Length - offset; + if (remainingSpace < maxBytes) + { + size = (int)Math.Min(int.MaxValue, remainingSpace); + } + + Span span = this.AsSpan(offset, size); + return encoding.GetBytes(value, span); +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(long offset, Span target) + { + this.AsReadOnlySpan(offset, target.Length).CopyTo(target); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyFrom(long offset, ReadOnlySpan data) + { + data.CopyTo(this.AsSpan(offset, data.Length)); + } + + private partial Span AsSpan(long offset, int length); + + private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); +} + + +public ref partial struct SpanSerializationTarget +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt8(long offset, byte value) + { + this.AsSpan(offset, sizeof(byte))[0] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadUInt8(long offset) + { + return this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt8(long offset, sbyte value) + { + this.AsSpan(offset, sizeof(byte))[0] = (byte)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadInt8(long offset) + { + return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt16(long offset, short value) + { + BinaryPrimitives.WriteInt16LittleEndian( + this.AsSpan(offset, sizeof(short)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16(long offset) + { + return BinaryPrimitives.ReadInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(short))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt16(long offset, ushort value) + { + BinaryPrimitives.WriteUInt16LittleEndian( + this.AsSpan(offset, sizeof(ushort)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16(long offset) + { + return BinaryPrimitives.ReadUInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ushort))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(long offset, int value) + { + BinaryPrimitives.WriteInt32LittleEndian( + this.AsSpan(offset, sizeof(int)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32(long offset) + { + return BinaryPrimitives.ReadInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(int))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(long offset, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian( + this.AsSpan(offset, sizeof(uint)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32(long offset) + { + return BinaryPrimitives.ReadUInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(uint))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(long offset, long value) + { + BinaryPrimitives.WriteInt64LittleEndian( + this.AsSpan(offset, sizeof(long)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64(long offset) + { + return BinaryPrimitives.ReadInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(long))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(long offset, ulong value) + { + BinaryPrimitives.WriteUInt64LittleEndian( + this.AsSpan(offset, sizeof(ulong)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64(long offset) + { + return BinaryPrimitives.ReadUInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ulong))); + } + + public int WriteStringBytes(long offset, string value, Encoding encoding) + { + checked + { + int maxBytes = encoding.GetMaxByteCount(value.Length); + +#if NETSTANDARD2_0 + byte[] temp = ArrayPool.Shared.Rent(maxBytes); + int length = encoding.GetBytes(value, 0, value.Length, temp, 0); + temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); + ArrayPool.Shared.Return(temp); + + return length; +#else + int size = maxBytes; + long remainingSpace = this.Length - offset; + if (remainingSpace < maxBytes) + { + size = (int)Math.Min(int.MaxValue, remainingSpace); + } + + Span span = this.AsSpan(offset, size); + return encoding.GetBytes(value, span); +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(long offset, Span target) + { + this.AsReadOnlySpan(offset, target.Length).CopyTo(target); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyFrom(long offset, ReadOnlySpan data) + { + data.CopyTo(this.AsSpan(offset, data.Length)); + } + + private partial Span AsSpan(long offset, int length); + + private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); +} + diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.tt b/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.tt new file mode 100644 index 00000000..09e48098 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.tt @@ -0,0 +1,210 @@ +/* + * 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 name, string classModifiers)[] types = + { + ("ArraySerializationTarget", "partial struct"), + ("InputBufferSerializationTarget", "partial struct"), + ("MemorySerializationTarget", "partial struct"), + ("SpanSerializationTarget", "ref partial struct"), + }; +#> + +using System.Buffers.Binary; +using System.Buffers; +using System.Text; + +namespace FlatSharp; + +<# + foreach (var pair in types) + { + var name = pair.name; + var modifier = pair.classModifiers; +#> + +public <#= modifier #> <#= name #> +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt8(long offset, byte value) + { + this.AsSpan(offset, sizeof(byte))[0] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadUInt8(long offset) + { + return this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt8(long offset, sbyte value) + { + this.AsSpan(offset, sizeof(byte))[0] = (byte)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadInt8(long offset) + { + return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt16(long offset, short value) + { + BinaryPrimitives.WriteInt16LittleEndian( + this.AsSpan(offset, sizeof(short)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16(long offset) + { + return BinaryPrimitives.ReadInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(short))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt16(long offset, ushort value) + { + BinaryPrimitives.WriteUInt16LittleEndian( + this.AsSpan(offset, sizeof(ushort)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16(long offset) + { + return BinaryPrimitives.ReadUInt16LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ushort))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(long offset, int value) + { + BinaryPrimitives.WriteInt32LittleEndian( + this.AsSpan(offset, sizeof(int)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32(long offset) + { + return BinaryPrimitives.ReadInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(int))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(long offset, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian( + this.AsSpan(offset, sizeof(uint)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32(long offset) + { + return BinaryPrimitives.ReadUInt32LittleEndian( + this.AsReadOnlySpan(offset, sizeof(uint))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(long offset, long value) + { + BinaryPrimitives.WriteInt64LittleEndian( + this.AsSpan(offset, sizeof(long)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64(long offset) + { + return BinaryPrimitives.ReadInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(long))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(long offset, ulong value) + { + BinaryPrimitives.WriteUInt64LittleEndian( + this.AsSpan(offset, sizeof(ulong)), + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64(long offset) + { + return BinaryPrimitives.ReadUInt64LittleEndian( + this.AsReadOnlySpan(offset, sizeof(ulong))); + } + + public int WriteStringBytes(long offset, string value, Encoding encoding) + { + checked + { + int maxBytes = encoding.GetMaxByteCount(value.Length); + +#if NETSTANDARD2_0 + byte[] temp = ArrayPool.Shared.Rent(maxBytes); + int length = encoding.GetBytes(value, 0, value.Length, temp, 0); + temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); + ArrayPool.Shared.Return(temp); + + return length; +#else + int size = maxBytes; + long remainingSpace = this.Length - offset; + if (remainingSpace < maxBytes) + { + size = (int)Math.Min(int.MaxValue, remainingSpace); + } + + Span span = this.AsSpan(offset, size); + return encoding.GetBytes(value, span); +#endif + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(long offset, Span target) + { + this.AsReadOnlySpan(offset, target.Length).CopyTo(target); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyFrom(long offset, ReadOnlySpan data) + { + data.CopyTo(this.AsSpan(offset, data.Length)); + } + + private partial Span AsSpan(long offset, int length); + + private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); +} + +<# + } +#> \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs index f9030c3f..64fa4d3a 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs @@ -21,7 +21,7 @@ namespace FlatSharp; /// /// A serialization target that wraps a Span{byte}. Only supports the 32-bit address space. /// -public ref struct SpanSerializationTarget : IFlatBufferSerializationTarget +public readonly ref partial struct SpanSerializationTarget : IFlatBufferReaderWriter { private readonly Span span; @@ -39,12 +39,6 @@ public SpanSerializationTarget(Memory memory) { this.span = memory.Span; } - - public byte this[long index] - { - get => this.span[checked((int)index)]; - set => this.span[checked((int)index)] = value; - } public long Length => this.span.Length; @@ -61,11 +55,11 @@ public SpanSerializationTarget Slice(long start) return new(this.span.Slice((int)start)); } - public Span AsSpan(long start, int length) + private partial Span AsSpan(long offset, int length) { checked { - return this.span.Slice((int)start, (int)length); + return this.span.Slice((int)offset, length); } } } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs index d0520798..a4e40b1f 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs @@ -15,17 +15,16 @@ */ using System.Buffers.Binary; +using System.Text; namespace FlatSharp; -internal struct VirtualSerializationTarget : IFlatBufferSerializationTarget +/// +/// A virtual serialization target that pretends to write data. Useful for simulating how many bytes +/// a write operation will consume. +/// +internal readonly struct VirtualSerializationTarget : IFlatBufferReaderWriter { - public byte this[long index] - { - get => 0; - set { } - } - public long Length => long.MaxValue; public VirtualSerializationTarget Slice(long start, long length) @@ -38,8 +37,89 @@ public VirtualSerializationTarget Slice(long start) return this; } - public Span AsSpan(long start, int length) + public void WriteUInt8(long offset, byte value) + { + } + + public byte ReadUInt8(long offset) + { + throw new NotImplementedException(); + } + + public void WriteInt8(long offset, sbyte value) + { + } + + public sbyte ReadInt8(long offset) + { + throw new NotImplementedException(); + } + + public void WriteInt16(long offset, short value) + { + } + + public short ReadInt16(long offset) + { + throw new NotImplementedException(); + } + + public void WriteUInt16(long offset, ushort value) + { + } + + public ushort ReadUInt16(long offset) + { + throw new NotImplementedException(); + } + + public void WriteInt32(long offset, int value) + { + } + + public int ReadInt32(long offset) + { + throw new NotImplementedException(); + } + + public void WriteUInt32(long offset, uint value) + { + } + + public uint ReadUInt32(long offset) + { + throw new NotImplementedException(); + } + + public void WriteInt64(long offset, long value) + { + } + + public long ReadInt64(long offset) + { + throw new NotImplementedException(); + } + + public void WriteUInt64(long offset, ulong value) + { + } + + public ulong ReadUInt64(long offset) + { + throw new NotImplementedException(); + } + + public int WriteStringBytes(long offset, string value, Encoding encoding) + { + return encoding.GetByteCount(value); + } + + public void CopyFrom(long offset, ReadOnlySpan data) + { + } + + public void CopyTo(long offset, Span destination) { - return default; + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs index 127ef29c..553e29fd 100644 --- a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs @@ -73,12 +73,12 @@ public void Reset() /// /// Writes a shared string. /// - public void WriteSharedString( - TTarget target, + public void WriteSharedString( + TBuffer target, long offset, string value, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -110,10 +110,10 @@ public void WriteSharedString( /// /// Flush any pending writes. /// - public void FlushWrites( - TTarget target, + public void FlushWrites( + TBuffer target, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -137,12 +137,12 @@ public void FlushWrites( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void FlushSharedString( - TTarget target, + private static void FlushSharedString( + TBuffer target, string value, List offsets, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/IPostSerializeAction.cs b/src/FlatSharp.Runtime/IPostSerializeAction.cs index c2112663..32b6ef60 100644 --- a/src/FlatSharp.Runtime/IPostSerializeAction.cs +++ b/src/FlatSharp.Runtime/IPostSerializeAction.cs @@ -21,8 +21,8 @@ namespace FlatSharp.Internal; /// public interface IPostSerializeAction { - void Invoke(TTarget target, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + void Invoke(TBuffer target, SerializationContext context) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/ISerializer.cs b/src/FlatSharp.Runtime/ISerializer.cs index 1c69e4a8..d974e69f 100644 --- a/src/FlatSharp.Runtime/ISerializer.cs +++ b/src/FlatSharp.Runtime/ISerializer.cs @@ -39,8 +39,8 @@ public interface ISerializer /// The destination. /// The object to serialize. /// The number of bytes written. - long Write(TTarget target, object item) - where TTarget : IFlatBufferSerializationTarget + long Write(TBuffer target, object item) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -81,8 +81,8 @@ public interface ISerializer /// The destination. /// The object to serialize. /// The number of bytes written. - long Write(TTarget destination, T item) - where TTarget : IFlatBufferSerializationTarget + long Write(TBuffer destination, T item) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/ISpanComparer.cs b/src/FlatSharp.Runtime/ISpanComparer.cs index ded8cc91..2524815e 100644 --- a/src/FlatSharp.Runtime/ISpanComparer.cs +++ b/src/FlatSharp.Runtime/ISpanComparer.cs @@ -24,5 +24,10 @@ public interface ISpanComparer /// /// Compares the two spans. /// - int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right); + int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif + ; } diff --git a/src/FlatSharp.Runtime/SerializationContext.cs b/src/FlatSharp.Runtime/SerializationContext.cs index 0a65a330..80d5f15f 100644 --- a/src/FlatSharp.Runtime/SerializationContext.cs +++ b/src/FlatSharp.Runtime/SerializationContext.cs @@ -72,8 +72,8 @@ public void Reset(long capacity) /// Invokes any post-serialize actions. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InvokePostSerializeActions(TTarget target) - where TTarget : IFlatBufferSerializationTarget + public void InvokePostSerializeActions(TBuffer target) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -149,10 +149,10 @@ public long AllocateSpace(int bytesNeeded, int alignment) } [MethodImpl(MethodImplOptions.NoInlining)] // Common method; don't inline - public long FinishVTable( - TTarget buffer, + public long FinishVTable( + TBuffer buffer, Span vtable) - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs index 8daeb157..54339844 100644 --- a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs +++ b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs @@ -74,19 +74,17 @@ public VectorSortAction( /// 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. /// - public void Invoke(TTarget target, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + public void Invoke(TBuffer target, SerializationContext context) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif { checked { - var buffer = new SerializationTargetInputBuffer(target); - long vectorStartOffset = - vectorUOffset + buffer.ReadUInt(vectorUOffset); - int vectorLength = (int)buffer.ReadUInt(vectorStartOffset); + vectorUOffset + target.ReadUInt32(vectorUOffset); + int vectorLength = (int)target.ReadUInt32(vectorStartOffset); long index0Position = vectorStartOffset + sizeof(int); (long, int, long)[]? pooledArray = null; @@ -109,7 +107,7 @@ public void Invoke(TTarget target, SerializationContext context) // 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 = target.AsSpan(index0Position, sizeof(uint) * vectorLength); + TBuffer boundedVector = target.Slice(index0Position, sizeof(uint) * vectorLength); long nextPosition = index0Position; for (int i = 0; i < keyOffsets.Length; ++i) { @@ -131,13 +129,13 @@ public void Invoke(TTarget target, SerializationContext context) /// 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( - TTarget buffer, + private static void IntroSort( + TBuffer buffer, TSpanComparer keyComparer, int lo, int hi, Span<(long offset, int length, long tableOffset)> keyLocations) - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -184,7 +182,7 @@ private static void IntroSort( SwapVectorPositions(middle, hi - 1, keyLocations); var (pivotOffset, pivotLength, _) = keyLocations[hi - 1]; bool pivotExists = pivotOffset != 0; - var pivotSpan = buffer.AsSpan(pivotOffset, pivotLength); + var pivotBuffer = buffer.Slice(pivotOffset, pivotLength); // Partition int num2 = lo; @@ -194,8 +192,8 @@ private static void IntroSort( while (true) { var (keyOffset, keyLength, _) = keyLocations[++num2]; - var keySpan = buffer.AsSpan(keyOffset, keyLength); - if (keyComparer.Compare(keyOffset != 0, keySpan, pivotExists, pivotSpan) >= 0) + var keyBuffer = buffer.Slice(keyOffset, keyLength); + if (keyComparer.Compare(keyOffset != 0, keyBuffer, pivotExists, pivotBuffer) >= 0) { break; } @@ -204,8 +202,8 @@ private static void IntroSort( while (true) { var (keyOffset, keyLength, _) = keyLocations[--num3]; - var keySpan = buffer.AsSpan(keyOffset, keyLength); - if (keyComparer.Compare(pivotExists, pivotSpan, keyOffset != 0, keySpan) >= 0) + var keyBuffer = buffer.Slice(keyOffset, keyLength); + if (keyComparer.Compare(pivotExists, pivotBuffer, keyOffset != 0, keyBuffer) >= 0) { break; } @@ -231,13 +229,13 @@ private static void IntroSort( } } - private static void InsertionSort( - TTarget buffer, + private static void InsertionSort( + TBuffer buffer, TSpanComparer comparer, int lo, int hi, Span<(long offset, int length, long tableOffset)> keyLocations) - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -269,13 +267,13 @@ private static void InsertionSort( } } - private static void SwapIfGreater( - TTarget target, + private static void SwapIfGreater( + TBuffer target, TSpanComparer comparer, int leftIndex, int rightIndex, Span<(long, int, long)> keyOffsets) - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -313,20 +311,20 @@ private static void SwapVectorPositions(int leftIndex, int rightIndex, Span<(lon /// /// Left as unchecked since this is a sort operation (not a search). /// - private static (long offset, int length, long tableOffset) GetKeyOffset( - TTarget target, + private static (long offset, int length, long tableOffset) GetKeyOffset( + TBuffer target, long index0Position, int vectorIndex, int vtableIndex, int? inlineItemSize) - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif { checked { - var buffer = new SerializationTargetInputBuffer(target); + var buffer = new SerializationTargetInputBuffer(target); // Find offset to the table at the index. long tableOffset = index0Position + (sizeof(uint) * vectorIndex); diff --git a/src/FlatSharp.Runtime/SpanComparers.cs b/src/FlatSharp.Runtime/SpanComparers.cs index f37d8663..4f1f8743 100644 --- a/src/FlatSharp.Runtime/SpanComparers.cs +++ b/src/FlatSharp.Runtime/SpanComparers.cs @@ -28,10 +28,14 @@ public BoolSpanComparer(bool defaultValue) this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - bool leftValue = leftExists ? ScalarSpanReader.ReadBool(left) : this.defaultValue; - bool rightValue = rightExists ? ScalarSpanReader.ReadBool(right) : this.defaultValue; + bool leftValue = leftExists ? left.ReadBool(0) : this.defaultValue; + bool rightValue = rightExists ? right.ReadBool(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } @@ -43,385 +47,469 @@ public struct NullableBoolSpanComparer : ISpanComparer public NullableBoolSpanComparer(bool? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadBool(left).CompareTo(ScalarSpanReader.ReadBool(right)); + return left.ReadBool(0).CompareTo(right.ReadBool(0)); } } -public struct ByteSpanComparer : ISpanComparer +public struct UInt8SpanComparer : ISpanComparer { private readonly byte defaultValue; - public ByteSpanComparer(byte defaultValue) + public UInt8SpanComparer(byte defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - byte leftValue = leftExists ? ScalarSpanReader.ReadByte(left) : this.defaultValue; - byte rightValue = rightExists ? ScalarSpanReader.ReadByte(right) : this.defaultValue; + byte leftValue = leftExists ? left.ReadUInt8(0) : this.defaultValue; + byte rightValue = rightExists ? right.ReadUInt8(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableByteSpanComparer : ISpanComparer +public struct NullableUInt8SpanComparer : ISpanComparer { - public NullableByteSpanComparer(byte? notUsed) + public NullableUInt8SpanComparer(byte? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadByte(left).CompareTo(ScalarSpanReader.ReadByte(right)); + return left.ReadUInt8(0).CompareTo(right.ReadUInt8(0)); } } -public struct SByteSpanComparer : ISpanComparer +public struct Int8SpanComparer : ISpanComparer { private readonly sbyte defaultValue; - public SByteSpanComparer(sbyte defaultValue) + public Int8SpanComparer(sbyte defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - sbyte leftValue = leftExists ? ScalarSpanReader.ReadSByte(left) : this.defaultValue; - sbyte rightValue = rightExists ? ScalarSpanReader.ReadSByte(right) : this.defaultValue; + sbyte leftValue = leftExists ? left.ReadInt8(0) : this.defaultValue; + sbyte rightValue = rightExists ? right.ReadInt8(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableSByteSpanComparer : ISpanComparer +public struct NullableInt8SpanComparer : ISpanComparer { - public NullableSByteSpanComparer(sbyte? notUsed) + public NullableInt8SpanComparer(sbyte? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadSByte(left).CompareTo(ScalarSpanReader.ReadSByte(right)); + return left.ReadInt8(0).CompareTo(right.ReadInt8(0)); } } -public struct UShortSpanComparer : ISpanComparer +public struct UInt16SpanComparer : ISpanComparer { private readonly ushort defaultValue; - public UShortSpanComparer(ushort defaultValue) + public UInt16SpanComparer(ushort defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - ushort leftValue = leftExists ? ScalarSpanReader.ReadUShort(left) : this.defaultValue; - ushort rightValue = rightExists ? ScalarSpanReader.ReadUShort(right) : this.defaultValue; + ushort leftValue = leftExists ? left.ReadUInt16(0) : this.defaultValue; + ushort rightValue = rightExists ? right.ReadUInt16(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableUShortSpanComparer : ISpanComparer +public struct NullableUInt16SpanComparer : ISpanComparer { - public NullableUShortSpanComparer(ushort? notUsed) + public NullableUInt16SpanComparer(ushort? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadUShort(left).CompareTo(ScalarSpanReader.ReadUShort(right)); + return left.ReadUInt16(0).CompareTo(right.ReadUInt16(0)); } } -public struct ShortSpanComparer : ISpanComparer +public struct Int16SpanComparer : ISpanComparer { private readonly short defaultValue; - public ShortSpanComparer(short defaultValue) + public Int16SpanComparer(short defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - short leftValue = leftExists ? ScalarSpanReader.ReadShort(left) : this.defaultValue; - short rightValue = rightExists ? ScalarSpanReader.ReadShort(right) : this.defaultValue; + short leftValue = leftExists ? left.ReadInt16(0) : this.defaultValue; + short rightValue = rightExists ? right.ReadInt16(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableShortSpanComparer : ISpanComparer +public struct NullableInt16SpanComparer : ISpanComparer { - public NullableShortSpanComparer(short? notUsed) + public NullableInt16SpanComparer(short? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadShort(left).CompareTo(ScalarSpanReader.ReadShort(right)); + return left.ReadInt16(0).CompareTo(right.ReadInt16(0)); } } -public struct IntSpanComparer : ISpanComparer +public struct Int32SpanComparer : ISpanComparer { private readonly int defaultValue; - public IntSpanComparer(int defaultValue) + public Int32SpanComparer(int defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - int leftValue = leftExists ? ScalarSpanReader.ReadInt(left) : this.defaultValue; - int rightValue = rightExists ? ScalarSpanReader.ReadInt(right) : this.defaultValue; + int leftValue = leftExists ? left.ReadInt32(0) : this.defaultValue; + int rightValue = rightExists ? right.ReadInt32(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableIntSpanComparer : ISpanComparer +public struct NullableInt32SpanComparer : ISpanComparer { - public NullableIntSpanComparer(int? notUsed) + public NullableInt32SpanComparer(int? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadInt(left).CompareTo(ScalarSpanReader.ReadInt(right)); + return left.ReadInt32(0).CompareTo(right.ReadInt32(0)); } } -public struct UIntSpanComparer : ISpanComparer +public struct UInt32SpanComparer : ISpanComparer { private readonly uint defaultValue; - public UIntSpanComparer(uint defaultValue) + public UInt32SpanComparer(uint defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - uint leftValue = leftExists ? ScalarSpanReader.ReadUInt(left) : this.defaultValue; - uint rightValue = rightExists ? ScalarSpanReader.ReadUInt(right) : this.defaultValue; + uint leftValue = leftExists ? left.ReadUInt32(0) : this.defaultValue; + uint rightValue = rightExists ? right.ReadUInt32(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableUIntSpanComparer : ISpanComparer +public struct NullableUInt32SpanComparer : ISpanComparer { - public NullableUIntSpanComparer(uint? notUsed) + public NullableUInt32SpanComparer(uint? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadUInt(left).CompareTo(ScalarSpanReader.ReadUInt(right)); + return left.ReadUInt32(0).CompareTo(right.ReadUInt32(0)); } } -public struct LongSpanComparer : ISpanComparer +public struct Int64SpanComparer : ISpanComparer { private readonly long defaultValue; - public LongSpanComparer(long defaultValue) + public Int64SpanComparer(long defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - long leftValue = leftExists ? ScalarSpanReader.ReadLong(left) : this.defaultValue; - long rightValue = rightExists ? ScalarSpanReader.ReadLong(right) : this.defaultValue; + long leftValue = leftExists ? left.ReadInt64(0) : this.defaultValue; + long rightValue = rightExists ? right.ReadInt64(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableLongSpanComparer : ISpanComparer +public struct NullableInt64SpanComparer : ISpanComparer { - public NullableLongSpanComparer(long? notUsed) + public NullableInt64SpanComparer(long? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadLong(left).CompareTo(ScalarSpanReader.ReadLong(right)); + return left.ReadInt64(0).CompareTo(right.ReadInt64(0)); } } -public struct ULongSpanComparer : ISpanComparer +public struct UInt64SpanComparer : ISpanComparer { private readonly ulong defaultValue; - public ULongSpanComparer(ulong defaultValue) + public UInt64SpanComparer(ulong defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - ulong leftValue = leftExists ? ScalarSpanReader.ReadULong(left) : this.defaultValue; - ulong rightValue = rightExists ? ScalarSpanReader.ReadULong(right) : this.defaultValue; + ulong leftValue = leftExists ? left.ReadUInt64(0) : this.defaultValue; + ulong rightValue = rightExists ? right.ReadUInt64(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableULongSpanComparer : ISpanComparer +public struct NullableUInt64SpanComparer : ISpanComparer { - public NullableULongSpanComparer(ulong? notUsed) + public NullableUInt64SpanComparer(ulong? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadULong(left).CompareTo(ScalarSpanReader.ReadULong(right)); + return left.ReadUInt64(0).CompareTo(right.ReadUInt64(0)); } } -public struct FloatSpanComparer : ISpanComparer +public struct Float32SpanComparer : ISpanComparer { private readonly float defaultValue; - public FloatSpanComparer(float defaultValue) + public Float32SpanComparer(float defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - float leftValue = leftExists ? ScalarSpanReader.ReadFloat(left) : this.defaultValue; - float rightValue = rightExists ? ScalarSpanReader.ReadFloat(right) : this.defaultValue; + float leftValue = leftExists ? left.ReadFloat32(0) : this.defaultValue; + float rightValue = rightExists ? right.ReadFloat32(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableFloatSpanComparer : ISpanComparer +public struct NullableFloat32SpanComparer : ISpanComparer { - public NullableFloatSpanComparer(float? notUsed) + public NullableFloat32SpanComparer(float? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadFloat(left).CompareTo(ScalarSpanReader.ReadFloat(right)); + return left.ReadFloat32(0).CompareTo(right.ReadFloat32(0)); } } -public struct DoubleSpanComparer : ISpanComparer +public struct Float64SpanComparer : ISpanComparer { private readonly double defaultValue; - public DoubleSpanComparer(double defaultValue) + public Float64SpanComparer(double defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - double leftValue = leftExists ? ScalarSpanReader.ReadDouble(left) : this.defaultValue; - double rightValue = rightExists ? ScalarSpanReader.ReadDouble(right) : this.defaultValue; + double leftValue = leftExists ? left.ReadFloat64(0) : this.defaultValue; + double rightValue = rightExists ? right.ReadFloat64(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableDoubleSpanComparer : ISpanComparer +public struct NullableFloat64SpanComparer : ISpanComparer { - public NullableDoubleSpanComparer(double? notUsed) + public NullableFloat64SpanComparer(double? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.ReadDouble(left).CompareTo(ScalarSpanReader.ReadDouble(right)); + return left.ReadFloat64(0).CompareTo(right.ReadFloat64(0)); } } diff --git a/src/FlatSharp.Runtime/SpanComparers.tt b/src/FlatSharp.Runtime/SpanComparers.tt index 2b677b1f..8959d65d 100644 --- a/src/FlatSharp.Runtime/SpanComparers.tt +++ b/src/FlatSharp.Runtime/SpanComparers.tt @@ -26,16 +26,16 @@ (string casedName, string typeName)[] types = { ("Bool", "bool"), - ("Byte", "byte"), - ("SByte", "sbyte"), - ("UShort", "ushort"), - ("Short", "short"), - ("Int", "int"), - ("UInt", "uint"), - ("Long", "long"), - ("ULong", "ulong"), - ("Float", "float"), - ("Double", "double"), + ("UInt8", "byte"), + ("Int8", "sbyte"), + ("UInt16", "ushort"), + ("Int16", "short"), + ("Int32", "int"), + ("UInt32", "uint"), + ("Int64", "long"), + ("UInt64", "ulong"), + ("Float32", "float"), + ("Float64", "double"), }; #> @@ -58,10 +58,14 @@ public struct <#=className#> : ISpanComparer this.defaultValue = defaultValue; } - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { - <#=typeName#> leftValue = leftExists ? ScalarSpanReader.Read<#=casedName#>(left) : this.defaultValue; - <#=typeName#> rightValue = rightExists ? ScalarSpanReader.Read<#=casedName#>(right) : this.defaultValue; + <#=typeName#> leftValue = leftExists ? left.Read<#=casedName#>(0) : this.defaultValue; + <#=typeName#> rightValue = rightExists ? right.Read<#=casedName#>(0) : this.defaultValue; return leftValue.CompareTo(rightValue); } @@ -73,15 +77,19 @@ public struct Nullable<#=className#> : ISpanComparer public Nullable<#=className#>(<#=typeName#>? notUsed) { } - - public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) + + public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) + where TBuffer : IFlatBufferReaderWriter +#if NET9_0_OR_GREATER + , allows ref struct +#endif { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return ScalarSpanReader.Read<#=casedName#>(left).CompareTo(ScalarSpanReader.Read<#=casedName#>(right)); + return left.Read<#=casedName#>(0).CompareTo(right.Read<#=casedName#>(0)); } } diff --git a/src/FlatSharp.Runtime/StringSpanComparer.cs b/src/FlatSharp.Runtime/StringSpanComparer.cs index fd1c7481..64afb0ed 100644 --- a/src/FlatSharp.Runtime/StringSpanComparer.cs +++ b/src/FlatSharp.Runtime/StringSpanComparer.cs @@ -35,7 +35,11 @@ public StringSpanComparer(string? notUsed) { } - public int Compare(bool leftExists, ReadOnlySpan x, bool rightExists, ReadOnlySpan y) + public int Compare(bool leftExists, TBuffer x, bool rightExists, TBuffer y) + where TBuffer : IFlatBufferReaderWriter + #if NET9_0_OR_GREATER + , allows ref struct + #endif { if (!leftExists || !rightExists) { @@ -43,7 +47,7 @@ public int Compare(bool leftExists, ReadOnlySpan x, bool rightExists, Read } int i = 0; - int minLength = x.Length; + long minLength = x.Length; if (y.Length < minLength) { minLength = y.Length; @@ -52,8 +56,8 @@ public int Compare(bool leftExists, ReadOnlySpan x, bool rightExists, Read while (i + sizeof(ulong) <= minLength) { // Use BE since we read from left to right. - ulong left = BinaryPrimitives.ReadUInt64BigEndian(x.Slice(i, sizeof(ulong))); - ulong right = BinaryPrimitives.ReadUInt64BigEndian(y.Slice(i, sizeof(ulong))); + ulong left = x.ReadUInt64(i); + ulong right = y.ReadUInt64(i); int cmp = left.CompareTo(right); if (cmp != 0) @@ -66,8 +70,8 @@ public int Compare(bool leftExists, ReadOnlySpan x, bool rightExists, Read for (; i < minLength; ++i) { - byte xByte = x[i]; - byte yByte = y[i]; + byte xByte = x.ReadUInt8(i); + byte yByte = y.ReadUInt8(i); if (xByte != yByte) { @@ -75,6 +79,6 @@ public int Compare(bool leftExists, ReadOnlySpan x, bool rightExists, Read } } - return x.Length - y.Length; + return (int)(x.Length - y.Length); } } diff --git a/src/FlatSharp/FlatBufferSerializer.cs b/src/FlatSharp/FlatBufferSerializer.cs index 8f379f35..214791ee 100644 --- a/src/FlatSharp/FlatBufferSerializer.cs +++ b/src/FlatSharp/FlatBufferSerializer.cs @@ -174,9 +174,9 @@ 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 long Serialize(T item, TTarget destination) + public long Serialize(T item, TBuffer destination) where T : class - where TTarget : IFlatBufferSerializationTarget + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs b/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs index d52af7b8..2dc624d9 100644 --- a/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs +++ b/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs @@ -432,8 +432,8 @@ private HashSet TraverseAssemblyReferenceGraph() string methodText = $@" - public void Write(ref TTarget target, {CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + public void Write(ref TBuffer target, {CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root, SerializationContext context) + where TBuffer : IFlatBufferReaderWriter #if {CSharpHelpers.Net9PreprocessorVariable} , allows ref struct #endif @@ -743,12 +743,12 @@ private string GenerateSerializeMethod(ITypeModel typeModel, CodeGeneratedMethod string fullText = $@" {method.GetMethodImplAttribute()} - internal static void {DefaultMethodNameResolver.ResolveSerialize(typeModel).methodName}( - ref TTarget {context.TargetVariableName}, + internal static void {DefaultMethodNameResolver.ResolveSerialize(typeModel).methodName}( + ref TBuffer {context.TargetVariableName}, {CSharpHelpers.GetGlobalCompilableTypeName(typeModel.ClrType)} {context.ValueVariableName}, {GetVTableOffsetVariableType(typeModel.PhysicalLayout.Length)} {context.OffsetVariableName} {serializationContextParameter} - {tableFieldContextParameter}) where TTarget : IFlatBufferSerializationTarget + {tableFieldContextParameter}) where TBuffer : IFlatBufferReaderWriter #if {CSharpHelpers.Net9PreprocessorVariable} , allows ref struct #endif diff --git a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs index 4e89c15b..23897b0c 100644 --- a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs +++ b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs @@ -57,7 +57,7 @@ public SerializationCodeGenContext( public string TableFieldContextVariableName { get; init; } /// - /// The variable name of the span. Represents a reference. + /// The variable name of the span. Represents a reference. /// public string TargetVariableName { get; init; } diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs index 68c19083..a8de16b8 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs @@ -187,8 +187,8 @@ public void Reset() this.offsets.Clear(); } - public void WriteSharedString(TTarget spanWriter, long offset, string value, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + public void WriteSharedString(TBuffer spanWriter, long offset, string value, SerializationContext context) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif @@ -202,8 +202,8 @@ public void WriteSharedString(TTarget spanWriter, long offset, string v list.Add(offset); } - public void FlushWrites(TTarget writer, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget + public void FlushWrites(TBuffer writer, SerializationContext context) + where TBuffer : IFlatBufferReaderWriter #if NET9_0_OR_GREATER , allows ref struct #endif From 17ad1059175a3343fb7e2df98bd168a9d8b8603d Mon Sep 17 00:00:00 2001 From: James Courtney Date: Mon, 11 Nov 2024 13:44:39 -0800 Subject: [PATCH 05/13] Revert --- src/Directory.Build.targets | 2 +- src/FlatSharp.Runtime/FSThrow.cs | 4 - .../FlatSharp.Runtime.csproj | 9 - .../GeneratedSerializerWrapper.cs | 6 +- src/FlatSharp.Runtime/IGeneratedSerializer.cs | 2 +- .../IO/ISharedStringWriter.cs | 8 +- .../IO/InputBuffer/InputBufferExtensions.cs | 162 ++++- .../SerializationTargetInputBuffer.cs | 22 +- .../ArraySerializationTarget.cs | 17 +- .../FlatBufferReaderWriterExtensions.cs | 201 ------ ...FlatBufferSerializationTargetExtensions.cs | 292 ++++++++ .../IFlatBufferReaderWriter.cs | 89 --- .../IFlatBufferSerializationTarget.cs | 52 ++ .../InputBufferSerializationTarget.cs | 15 +- .../MemorySerializationTarget.cs | 14 +- .../SerializationTargetHelpers.cs | 667 ------------------ .../SerializationTargetHelpers.tt | 210 ------ .../SpanSerializationTarget.cs | 12 +- .../VirtualSerializationTarget.cs | 98 +-- .../IO/SharedStringWriter.cs | 18 +- src/FlatSharp.Runtime/IPostSerializeAction.cs | 4 +- src/FlatSharp.Runtime/ISerializer.cs | 8 +- src/FlatSharp.Runtime/ISpanComparer.cs | 7 +- src/FlatSharp.Runtime/SerializationContext.cs | 10 +- .../SortedVectorHelpersInternal.cs | 48 +- src/FlatSharp.Runtime/SpanComparers.cs | 300 +++----- src/FlatSharp.Runtime/SpanComparers.tt | 40 +- src/FlatSharp.Runtime/StringSpanComparer.cs | 18 +- src/FlatSharp/FlatBufferSerializer.cs | 4 +- .../RoslynSerializerGenerator.cs | 10 +- .../SerializationCodeGenContext.cs | 2 +- .../ClassLib/SerializerConfigurationTests.cs | 8 +- 32 files changed, 735 insertions(+), 1624 deletions(-) delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferReaderWriterExtensions.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferReaderWriter.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.tt diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index a95aa005..51803969 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -9,7 +9,7 @@ false false RuntimeIdentifier - TargetFramework=net8.0 + TargetFramework=net8.0 diff --git a/src/FlatSharp.Runtime/FSThrow.cs b/src/FlatSharp.Runtime/FSThrow.cs index c1312cbd..bde337ae 100644 --- a/src/FlatSharp.Runtime/FSThrow.cs +++ b/src/FlatSharp.Runtime/FSThrow.cs @@ -200,9 +200,5 @@ public static bool NotMutable_DeserializedVector() public static void NotSupported_NativeArray_NonPinned() => throw new NotSupportedException("Non-greedy parsing of a NativeArray requires a pinned buffer."); - [DoesNotReturn] - public static void NotSupported_Generic(string message) - => throw new NotSupportedException(message); - #endregion } diff --git a/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj b/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj index 3d063d14..55ff7475 100644 --- a/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj +++ b/src/FlatSharp.Runtime/FlatSharp.Runtime.csproj @@ -25,10 +25,6 @@ TextTemplatingFileGenerator UnionTypes.cs - - SerializationTargetHelpers.cs - TextTemplatingFileGenerator - @@ -46,10 +42,5 @@ True UnionTypes.tt - - True - True - SerializationTargetHelpers.tt - diff --git a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs index 0be894da..94ff38bf 100644 --- a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs +++ b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs @@ -148,8 +148,8 @@ public T Parse(TInputBuffer buffer, FlatBufferDeserializationOptio object ISerializer.Parse(TInputBuffer buffer, FlatBufferDeserializationOption? option) => this.Parse(buffer, option); - public long Write(TBuffer destination, T item) - where TBuffer : IFlatBufferReaderWriter + public long Write(TTarget destination, T item) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -206,7 +206,7 @@ item is IFlatBufferDeserializedObject deserializedObj && return serializationContext.Offset; } - long ISerializer.Write(TBuffer target, object item) + long ISerializer.Write(TTarget target, object item) { return item switch { diff --git a/src/FlatSharp.Runtime/IGeneratedSerializer.cs b/src/FlatSharp.Runtime/IGeneratedSerializer.cs index 22840606..a528fe8e 100644 --- a/src/FlatSharp.Runtime/IGeneratedSerializer.cs +++ b/src/FlatSharp.Runtime/IGeneratedSerializer.cs @@ -47,7 +47,7 @@ void Write( ref TSerializationTarget target, T item, SerializationContext context) - where TSerializationTarget : IFlatBufferReaderWriter + where TSerializationTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs index 8a037e4b..636c404e 100644 --- a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs @@ -41,8 +41,8 @@ public interface ISharedStringWriter /// The location in the buffer of the uoffset to the string. /// The string to write. /// The serialization context. - void WriteSharedString(TBuffer spanWriter, long offset, string value, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + void WriteSharedString(TTarget spanWriter, long offset, string value, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -51,8 +51,8 @@ void WriteSharedString(TBuffer spanWriter, long offset, string value, S /// /// Flushes any pending writes. Invoked at the end of a serialization operation. /// - void FlushWrites(TBuffer writer, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + void FlushWrites(TTarget writer, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs index 63752ad3..d3bbd35b 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs @@ -31,12 +31,134 @@ public static class InputBufferExtensions /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool ReadBool(this TBuffer buffer, long offset) - where TBuffer : IFlatBufferReaderWriter + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + return buffer.ReadByte(offset) != SerializationHelpers.False; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ReadByte(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + return buffer.GetReadOnlySpan(offset, sizeof(byte))[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte ReadSByte(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + return (sbyte)buffer.ReadByte(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadUShort(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + buffer.CheckAlignment(offset, sizeof(ushort)); + return BinaryPrimitives.ReadUInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ushort))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadShort(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + buffer.CheckAlignment(offset, sizeof(short)); + return BinaryPrimitives.ReadInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(short))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadUInt(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer #if NET9_0_OR_GREATER , allows ref struct #endif { - return buffer.ReadUInt8(offset) != SerializationHelpers.False; + buffer.CheckAlignment(offset, sizeof(uint)); + return BinaryPrimitives.ReadUInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(uint))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadInt(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + return BinaryPrimitives.ReadInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(int))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadULong(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + buffer.CheckAlignment(offset, sizeof(ulong)); + return BinaryPrimitives.ReadUInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ulong))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ReadLong(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + buffer.CheckAlignment(offset, sizeof(long)); + return BinaryPrimitives.ReadInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(long))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ReadFloat(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + buffer.CheckAlignment(offset, sizeof(float)); + +#if NETSTANDARD + ScalarSpanReader.FloatLayout layout = new() + { + bytes = buffer.ReadUInt(offset) + }; + + return layout.value; +#else + return BinaryPrimitives.ReadSingleLittleEndian(buffer.GetReadOnlySpan(offset, sizeof(float))); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ReadDouble(this TBuffer buffer, long offset) + where TBuffer : IInputBuffer +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + buffer.CheckAlignment(offset, sizeof(double)); + +#if NETSTANDARD + return BitConverter.Int64BitsToDouble(buffer.ReadLong(offset)); +#else + return BinaryPrimitives.ReadDoubleLittleEndian(buffer.GetReadOnlySpan(offset, sizeof(double))); +#endif } /// @@ -57,10 +179,7 @@ public static string ReadString(this TBuffer buffer, long offset) where /// Reads a string from the given uoffset. /// public static string ReadStringFromUOffset(this TBuffer buffer, long stringStart) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + where TBuffer : IInputBuffer { int numberOfBytes = (int)buffer.ReadUInt(stringStart); ReadOnlySpan stringValue = buffer.GetReadOnlySpan(stringStart + sizeof(int), numberOfBytes); @@ -79,13 +198,9 @@ public static string ReadStringFromUOffset(this TBuffer buffer, long st /// /// Reads the given uoffset. /// - public static int ReadUOffset(this TBuffer buffer, long offset) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public static int ReadUOffset(this TBuffer buffer, long offset) where TBuffer : IInputBuffer { - int uoffset = buffer.ReadUInt32(offset); + int uoffset = buffer.ReadInt(offset); if (uoffset < sizeof(uint)) { FSThrow.InvalidData_UOffsetTooSmall((uint)uoffset); @@ -103,11 +218,7 @@ public static void InitializeVTable( long tableOffset, out long vtableOffset, out ulong vtableFieldCount, - out ReadOnlySpan fieldData) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + out ReadOnlySpan fieldData) where TBuffer : IInputBuffer { vtableOffset = tableOffset - buffer.ReadInt(tableOffset); ushort vtableLength = buffer.ReadUShort(vtableOffset); @@ -124,10 +235,7 @@ public static void InitializeVTable( // 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 : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + where TBuffer : IInputBuffer { // The local value stores a uoffset_t, so follow that now. uoffset += buffer.ReadUOffset(uoffset); @@ -167,12 +275,9 @@ public static Span UnsafeReadSpan(this TBuffer buff } [MethodImpl(MethodImplOptions.NoInlining)] - public static long CopyTo(this TBuffer1 buffer, TBuffer2 target) - where TBuffer1 : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif - where TBuffer2 : IFlatBufferReaderWriter + public static long CopyTo(this TBuffer buffer, TTarget target) + where TBuffer : IInputBuffer + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -188,7 +293,8 @@ public static long CopyTo(this TBuffer1 buffer, TBuffer2 tar { long remaining = buffer.Length - offset; var chunk = buffer.GetReadOnlySpan(offset, (int)Math.Min(int.MaxValue, remaining)); - target.CopyFrom(offset, chunk); + chunk.CopyTo(target.AsSpan(offset, chunk.Length)); + offset += chunk.Length; } diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs index 1b402357..f805df96 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs @@ -19,25 +19,21 @@ namespace FlatSharp; /// /// An implementation of InputBuffer for writable memory segments. /// -internal readonly +internal #if NET9_0_OR_GREATER ref #endif -struct SerializationTargetInputBuffer : IInputBuffer - where TBuffer : IFlatBufferReaderWriter +struct SerializationTargetInputBuffer : IInputBuffer + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif { - private readonly TBuffer target; - private readonly Func> getSpan; + private readonly TTarget target; - public SerializationTargetInputBuffer( - TBuffer target, - Func> getSpan) + public SerializationTargetInputBuffer(TTarget target) { this.target = target; - this.getSpan = getSpan; } public bool IsPinned => false; @@ -46,14 +42,16 @@ public SerializationTargetInputBuffer( public long Length => this.target.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan GetReadOnlySpan(long offset, int length) { return this.GetSpan(offset, length); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetSpan(long offset, int length) { - return this.getSpan(this.target, offset, length); + return this.target.AsSpan(offset, length); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -62,9 +60,9 @@ public ReadOnlyMemory GetReadOnlyMemory(long offset, int length) return this.GetMemory(offset, length); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory GetMemory(long offset, int length) { - FSThrow.NotSupported_Generic("SerializationTargetInputBuffer does not support getting a memory"); - return default; + return FSThrow.InvalidOperation>("Unsupported"); } } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs index 215d48e7..041fa799 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs @@ -14,12 +14,14 @@ * limitations under the License. */ +using System.Buffers.Binary; + namespace FlatSharp; /// /// A serialization target that wraps a Span{byte}. Only supports the 32-bit address space. /// -public readonly partial struct ArraySerializationTarget : IFlatBufferReaderWriter +public struct ArraySerializationTarget : IFlatBufferSerializationTarget { private readonly byte[] array; private readonly int start; @@ -37,6 +39,12 @@ public ArraySerializationTarget(byte[] array, int start, int length) this.Length = length; this.array = array; } + + public byte this[long index] + { + get => this.array[checked(this.start + (int)index)]; + set => this.array[checked(this.start + (int)index)] = value; + } public long Length { get; } @@ -56,14 +64,11 @@ public ArraySerializationTarget Slice(long start) } } - private partial Span AsSpan(long offset, int length) + public Span AsSpan(long start, int length) { checked { - long value = this.start + offset; - return this.array.AsSpan((int)value, length); + return this.array.AsSpan().Slice(this.start + (int)start, length); } } - - private partial ReadOnlySpan AsReadOnlySpan(long offset, int length) => this.AsSpan(offset, length); } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferReaderWriterExtensions.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferReaderWriterExtensions.cs deleted file mode 100644 index 40ce4e5c..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferReaderWriterExtensions.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System.Buffers; -using System.Buffers.Binary; -using System.Runtime.InteropServices; - -namespace FlatSharp; - -/// -/// Extensions for IFlatBufferReaderWriter -/// -public static class FlatBufferReaderWriterExtensions -{ - public static void WriteBool(this T target, long offset, bool value) - where T : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target.WriteUInt8(offset, value ? SerializationHelpers.True : SerializationHelpers.False); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteFloat32(this T target, long offset, float value) - where T : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout - { - value = value - }; - - target.WriteUInt32(offset, floatLayout.bytes); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float ReadFloat32(this T target, long offset) - where T : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout - { - bytes = target.ReadUInt32(offset), - }; - - return floatLayout.value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteFloat64(this T target, long offset, double value) - where T : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target.WriteInt64(offset, BitConverter.DoubleToInt64Bits(value)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double ReadFloat64(this T target, long offset) - where T : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - return target.ReadInt64(offset); - } - - public static void WriteReadOnlyByteMemoryBlock( - this TBuffer target, - ReadOnlyMemory memory, - long offset, - SerializationContext ctx) - where TBuffer : IFlatBufferReaderWriter - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - int numberOfItems = memory.Length; - long vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); - - target.WriteUOffset(offset, vectorStartOffset); - target.WriteInt32(vectorStartOffset, numberOfItems); - target.CopyFrom(vectorStartOffset + sizeof(uint), memory.Span); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnsafeWriteSpan( - this TSerializationTarget target, - Span buffer, - long offset, - int alignment, - SerializationContext ctx) - where TElement : unmanaged - where TSerializationTarget : IFlatBufferReaderWriter - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - // 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()); - - target.WriteUOffset(offset, vectorStartOffset); - target.WriteInt32(vectorStartOffset, numberOfItems); - - target.CopyFrom( - vectorStartOffset + sizeof(uint), - MemoryMarshal.Cast(buffer)); - } - - /// - /// Writes the given string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteString( - this TBuffer target, - string value, - long offset, - SerializationContext context) - where TBuffer : IFlatBufferReaderWriter - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - long stringOffset = target.WriteAndProvisionString(value, context); - target.WriteUOffset(offset, stringOffset); - } - - /// - /// Writes the string to the buffer, returning the absolute offset of the string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long WriteAndProvisionString( - this TBuffer target, - string value, - SerializationContext context) - where TBuffer : IFlatBufferReaderWriter - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - 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)); - int bytesWritten = target.WriteStringBytes( - stringStartOffset + sizeof(int), - value, - SerializationHelpers.Encoding); - - // null teriminator - target.WriteUInt8(stringStartOffset + bytesWritten + sizeof(uint), 0); - - // write length - target.WriteInt32(stringStartOffset, bytesWritten); - - // give back unused space. Account for null terminator. - context.Offset -= maxItems - (bytesWritten + 1); - - return stringStartOffset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteUOffset( - this TSerializationTarget target, - long offset, - long secondOffset) - where TSerializationTarget : IFlatBufferReaderWriter - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - checked - { - uint uoffset = (uint)(secondOffset - offset); - target.WriteUInt32(offset, uoffset); - } - } - - [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/FlatBufferSerializationTargetExtensions.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs new file mode 100644 index 00000000..0a77ba97 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs @@ -0,0 +1,292 @@ +using System.Buffers; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace FlatSharp; + +/// +/// Extensions for IFlatBufferSerializationTarget +/// +public static class FlatBufferSerializationTargetExtensions +{ + public static void WriteBool(this T target, long offset, bool value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target.WriteUInt8(offset, value ? SerializationHelpers.True : SerializationHelpers.False); + } + + public static void WriteUInt8(this T target, long offset, byte value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target[offset] = value; + } + + public static void WriteInt8(this T target, long offset, sbyte value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + target[offset] = unchecked((byte)value); + } + + public static void WriteUInt16(this T target, long offset, ushort value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(ushort)); + BinaryPrimitives.WriteUInt16LittleEndian( + target.AsSpan(offset, sizeof(ushort)), + value); + } + + public static void WriteInt16(this T target, long offset, short value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(short)); + BinaryPrimitives.WriteInt16LittleEndian( + target.AsSpan(offset, sizeof(short)), + value); + } + + public static void WriteUInt32(this T target, long offset, uint value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(uint)); + BinaryPrimitives.WriteUInt32LittleEndian( + target.AsSpan(offset, sizeof(uint)), + value); + } + + public static void WriteInt32(this T target, long offset, int value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian( + target.AsSpan(offset, sizeof(int)), + value); + } + + public static void WriteUInt64(this T target, long offset, ulong value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(ulong)); + BinaryPrimitives.WriteUInt64LittleEndian( + target.AsSpan(offset, sizeof(ulong)), + value); + } + + public static void WriteInt64(this T target, long offset, long value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(long)); + BinaryPrimitives.WriteInt64LittleEndian( + target.AsSpan(offset, sizeof(long)), + value); + } + + + public static void WriteFloat32(this T target, long offset, float value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(float)); + +#if NETSTANDARD + ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout + { + value = value + }; + + target.WriteUInt32(offset, floatLayout.bytes); +#else + BinaryPrimitives.WriteSingleLittleEndian( + target.AsSpan(offset, sizeof(float)), + value); +#endif + } + + public static void WriteFloat64(this T target, long offset, double value) + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif + { + CheckAlignment(offset, sizeof(double)); + +#if NETSTANDARD + target.WriteInt64(offset, BitConverter.DoubleToInt64Bits(value)); +#else + BinaryPrimitives.WriteDoubleLittleEndian( + target.AsSpan(offset, sizeof(double)), + value); +#endif + } + + public static void WriteReadOnlyByteMemoryBlock( + this TTarget target, + ReadOnlyMemory memory, + long offset, + SerializationContext ctx) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + int numberOfItems = memory.Length; + long vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); + + target.WriteUOffset(offset, vectorStartOffset); + target.WriteInt32(vectorStartOffset, numberOfItems); + + memory.Span.CopyTo(target.AsSpan(vectorStartOffset + sizeof(uint), numberOfItems)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteSpan( + this TSerializationTarget target, + Span buffer, + long offset, + int alignment, + SerializationContext ctx) + where TElement : unmanaged + where TSerializationTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + // 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()); + + target.WriteUOffset(offset, vectorStartOffset); + target.WriteInt32(vectorStartOffset, numberOfItems); + + Span destination = target.AsSpan( + vectorStartOffset + sizeof(uint), + checked(numberOfItems * Unsafe.SizeOf())); + + MemoryMarshal.Cast(buffer).CopyTo(destination); + } + + /// + /// Writes the given string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteString( + this TTarget target, + string value, + long offset, + SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + long stringOffset = target.WriteAndProvisionString(value, context); + target.WriteUOffset(offset, stringOffset); + } + + /// + /// Writes the string to the buffer, returning the absolute offset of the string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long WriteAndProvisionString( + this TTarget target, + string value, + SerializationContext context) + where TTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + 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)); + + Span destination = target.AsSpan(stringStartOffset + 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 + target[stringStartOffset + bytesWritten + sizeof(uint)] = 0; + + // write length + target.WriteInt32(stringStartOffset, bytesWritten); + + // give back unused space. Account for null terminator. + context.Offset -= maxItems - (bytesWritten + 1); + + return stringStartOffset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUOffset( + this TSerializationTarget target, + long offset, + long secondOffset) + where TSerializationTarget : IFlatBufferSerializationTarget + #if NET9_0_OR_GREATER + , allows ref struct + #endif + { + checked + { + uint uoffset = (uint)(secondOffset - offset); + target.WriteUInt32(offset, uoffset); + } + } + + [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/IFlatBufferReaderWriter.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferReaderWriter.cs deleted file mode 100644 index 8ebe7c7f..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferReaderWriter.cs +++ /dev/null @@ -1,89 +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.Text; - -namespace FlatSharp; - -/// -/// Represents a target for a FlatBuffer serialization operation. -/// -public interface IFlatBufferReaderWriter - where T : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif -{ - /// - /// Gets the length. - /// - long Length { get; } - - /// - /// Slices this object. - /// - T Slice(long start, long length); - - /// - /// Slices this object. - /// - T Slice(long start); - - #region Write Methods for Primitives - - void WriteUInt8(long offset, byte value); - - byte ReadUInt8(long offset); - - void WriteInt8(long offset, sbyte value); - - sbyte ReadInt8(long offset); - - void WriteInt16(long offset, short value); - - short ReadInt16(long offset); - - void WriteUInt16(long offset, ushort value); - - ushort ReadUInt16(long offset); - - void WriteInt32(long offset, int value); - - int ReadInt32(long offset); - - void WriteUInt32(long offset, uint value); - - uint ReadUInt32(long offset); - - void WriteInt64(long offset, long value); - - long ReadInt64(long offset); - - void WriteUInt64(long offset, ulong value); - - ulong ReadUInt64(long offset); - - int WriteStringBytes(long offset, string value, Encoding encoding); - - /// - /// Copies the given data into this object starting at the given offset. - /// - void CopyFrom(long offset, ReadOnlySpan data); - - void CopyTo(long offset, Span destination); - - #endregion -} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs new file mode 100644 index 00000000..6cc1df32 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs @@ -0,0 +1,52 @@ +/* + * 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; + +/// +/// Represents a target for a FlatBuffer serialization operation. +/// +public interface IFlatBufferSerializationTarget + where T : IFlatBufferSerializationTarget +#if NET9_0_OR_GREATER + , allows ref struct +#endif +{ + /// + /// Gets or sets the value at the given index. + /// + byte this[long index] { get; set; } + + /// + /// Gets the length. + /// + long Length { get; } + + /// + /// Slices this object. + /// + T Slice(long start, long length); + + /// + /// Slices this object. + /// + T Slice(long start); + + /// + /// Returns a Span{byte} over the given range. + /// + Span AsSpan(long start, int length); +} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs index 16f25d78..c8f574ad 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs @@ -19,7 +19,7 @@ namespace FlatSharp; /// /// A serialization target that wraps an instance of . /// -public partial struct InputBufferSerializationTarget : IFlatBufferReaderWriter> +public struct InputBufferSerializationTarget : IFlatBufferSerializationTarget> where TInputBuffer : IInputBuffer { private readonly long start; @@ -38,6 +38,12 @@ public InputBufferSerializationTarget(TInputBuffer buffer, long start, long leng this.start = start; this.Length = length; } + + public byte this[long index] + { + get => this.buffer.ReadByte(this.start + index); + set => this.buffer.GetSpan(this.start + index, sizeof(byte))[0] = value; + } public long Length { get; } @@ -51,8 +57,11 @@ public InputBufferSerializationTarget Slice(long start) return new(this.buffer, this.start + start, this.Length - start); } - private partial Span AsSpan(long offset, int length) + public Span AsSpan(long start, int length) { - return this.buffer.GetSpan(start + offset, length); + checked + { + return this.buffer.GetSpan(this.start + start, length); + } } } \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs index 1aa7948a..7fbef796 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs @@ -19,7 +19,7 @@ namespace FlatSharp; /// /// A serialization target that wraps a Memory{byte}. Only supports the 32-bit address space. /// -public readonly partial struct MemorySerializationTarget : IFlatBufferReaderWriter +public struct MemorySerializationTarget : IFlatBufferSerializationTarget { private readonly Memory memory; @@ -27,6 +27,12 @@ public MemorySerializationTarget(Memory memory) { this.memory = memory; } + + public byte this[long index] + { + get => this.memory.Span[checked((int)index)]; + set => this.memory.Span[checked((int)index)] = value; + } public long Length => this.memory.Length; @@ -45,12 +51,12 @@ public MemorySerializationTarget Slice(long start) return new MemorySerializationTarget(this.memory.Slice((int)start)); } } - - private partial Span AsSpan(long offset, int length) + + public Span AsSpan(long start, int length) { checked { - return this.memory.Span.Slice((int)offset, length); + return this.memory.Span.Slice((int)start, length); } } } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.cs deleted file mode 100644 index e353b919..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.cs +++ /dev/null @@ -1,667 +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.Buffers.Binary; -using System.Buffers; -using System.Text; - -namespace FlatSharp; - -public partial struct ArraySerializationTarget -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt8(long offset, byte value) - { - this.AsSpan(offset, sizeof(byte))[0] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadUInt8(long offset) - { - return this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt8(long offset, sbyte value) - { - this.AsSpan(offset, sizeof(byte))[0] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadInt8(long offset) - { - return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt16(long offset, short value) - { - BinaryPrimitives.WriteInt16LittleEndian( - this.AsSpan(offset, sizeof(short)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16(long offset) - { - return BinaryPrimitives.ReadInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(short))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt16(long offset, ushort value) - { - BinaryPrimitives.WriteUInt16LittleEndian( - this.AsSpan(offset, sizeof(ushort)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16(long offset) - { - return BinaryPrimitives.ReadUInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ushort))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt32(long offset, int value) - { - BinaryPrimitives.WriteInt32LittleEndian( - this.AsSpan(offset, sizeof(int)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt32(long offset) - { - return BinaryPrimitives.ReadInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(int))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt32(long offset, uint value) - { - BinaryPrimitives.WriteUInt32LittleEndian( - this.AsSpan(offset, sizeof(uint)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt32(long offset) - { - return BinaryPrimitives.ReadUInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(uint))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt64(long offset, long value) - { - BinaryPrimitives.WriteInt64LittleEndian( - this.AsSpan(offset, sizeof(long)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadInt64(long offset) - { - return BinaryPrimitives.ReadInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(long))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt64(long offset, ulong value) - { - BinaryPrimitives.WriteUInt64LittleEndian( - this.AsSpan(offset, sizeof(ulong)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadUInt64(long offset) - { - return BinaryPrimitives.ReadUInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ulong))); - } - - public int WriteStringBytes(long offset, string value, Encoding encoding) - { - checked - { - int maxBytes = encoding.GetMaxByteCount(value.Length); - -#if NETSTANDARD2_0 - byte[] temp = ArrayPool.Shared.Rent(maxBytes); - int length = encoding.GetBytes(value, 0, value.Length, temp, 0); - temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); - ArrayPool.Shared.Return(temp); - - return length; -#else - int size = maxBytes; - long remainingSpace = this.Length - offset; - if (remainingSpace < maxBytes) - { - size = (int)Math.Min(int.MaxValue, remainingSpace); - } - - Span span = this.AsSpan(offset, size); - return encoding.GetBytes(value, span); -#endif - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(long offset, Span target) - { - this.AsReadOnlySpan(offset, target.Length).CopyTo(target); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyFrom(long offset, ReadOnlySpan data) - { - data.CopyTo(this.AsSpan(offset, data.Length)); - } - - private partial Span AsSpan(long offset, int length); - - private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); -} - - -public partial struct InputBufferSerializationTarget -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt8(long offset, byte value) - { - this.AsSpan(offset, sizeof(byte))[0] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadUInt8(long offset) - { - return this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt8(long offset, sbyte value) - { - this.AsSpan(offset, sizeof(byte))[0] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadInt8(long offset) - { - return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt16(long offset, short value) - { - BinaryPrimitives.WriteInt16LittleEndian( - this.AsSpan(offset, sizeof(short)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16(long offset) - { - return BinaryPrimitives.ReadInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(short))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt16(long offset, ushort value) - { - BinaryPrimitives.WriteUInt16LittleEndian( - this.AsSpan(offset, sizeof(ushort)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16(long offset) - { - return BinaryPrimitives.ReadUInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ushort))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt32(long offset, int value) - { - BinaryPrimitives.WriteInt32LittleEndian( - this.AsSpan(offset, sizeof(int)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt32(long offset) - { - return BinaryPrimitives.ReadInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(int))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt32(long offset, uint value) - { - BinaryPrimitives.WriteUInt32LittleEndian( - this.AsSpan(offset, sizeof(uint)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt32(long offset) - { - return BinaryPrimitives.ReadUInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(uint))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt64(long offset, long value) - { - BinaryPrimitives.WriteInt64LittleEndian( - this.AsSpan(offset, sizeof(long)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadInt64(long offset) - { - return BinaryPrimitives.ReadInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(long))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt64(long offset, ulong value) - { - BinaryPrimitives.WriteUInt64LittleEndian( - this.AsSpan(offset, sizeof(ulong)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadUInt64(long offset) - { - return BinaryPrimitives.ReadUInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ulong))); - } - - public int WriteStringBytes(long offset, string value, Encoding encoding) - { - checked - { - int maxBytes = encoding.GetMaxByteCount(value.Length); - -#if NETSTANDARD2_0 - byte[] temp = ArrayPool.Shared.Rent(maxBytes); - int length = encoding.GetBytes(value, 0, value.Length, temp, 0); - temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); - ArrayPool.Shared.Return(temp); - - return length; -#else - int size = maxBytes; - long remainingSpace = this.Length - offset; - if (remainingSpace < maxBytes) - { - size = (int)Math.Min(int.MaxValue, remainingSpace); - } - - Span span = this.AsSpan(offset, size); - return encoding.GetBytes(value, span); -#endif - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(long offset, Span target) - { - this.AsReadOnlySpan(offset, target.Length).CopyTo(target); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyFrom(long offset, ReadOnlySpan data) - { - data.CopyTo(this.AsSpan(offset, data.Length)); - } - - private partial Span AsSpan(long offset, int length); - - private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); -} - - -public partial struct MemorySerializationTarget -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt8(long offset, byte value) - { - this.AsSpan(offset, sizeof(byte))[0] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadUInt8(long offset) - { - return this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt8(long offset, sbyte value) - { - this.AsSpan(offset, sizeof(byte))[0] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadInt8(long offset) - { - return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt16(long offset, short value) - { - BinaryPrimitives.WriteInt16LittleEndian( - this.AsSpan(offset, sizeof(short)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16(long offset) - { - return BinaryPrimitives.ReadInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(short))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt16(long offset, ushort value) - { - BinaryPrimitives.WriteUInt16LittleEndian( - this.AsSpan(offset, sizeof(ushort)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16(long offset) - { - return BinaryPrimitives.ReadUInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ushort))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt32(long offset, int value) - { - BinaryPrimitives.WriteInt32LittleEndian( - this.AsSpan(offset, sizeof(int)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt32(long offset) - { - return BinaryPrimitives.ReadInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(int))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt32(long offset, uint value) - { - BinaryPrimitives.WriteUInt32LittleEndian( - this.AsSpan(offset, sizeof(uint)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt32(long offset) - { - return BinaryPrimitives.ReadUInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(uint))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt64(long offset, long value) - { - BinaryPrimitives.WriteInt64LittleEndian( - this.AsSpan(offset, sizeof(long)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadInt64(long offset) - { - return BinaryPrimitives.ReadInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(long))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt64(long offset, ulong value) - { - BinaryPrimitives.WriteUInt64LittleEndian( - this.AsSpan(offset, sizeof(ulong)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadUInt64(long offset) - { - return BinaryPrimitives.ReadUInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ulong))); - } - - public int WriteStringBytes(long offset, string value, Encoding encoding) - { - checked - { - int maxBytes = encoding.GetMaxByteCount(value.Length); - -#if NETSTANDARD2_0 - byte[] temp = ArrayPool.Shared.Rent(maxBytes); - int length = encoding.GetBytes(value, 0, value.Length, temp, 0); - temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); - ArrayPool.Shared.Return(temp); - - return length; -#else - int size = maxBytes; - long remainingSpace = this.Length - offset; - if (remainingSpace < maxBytes) - { - size = (int)Math.Min(int.MaxValue, remainingSpace); - } - - Span span = this.AsSpan(offset, size); - return encoding.GetBytes(value, span); -#endif - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(long offset, Span target) - { - this.AsReadOnlySpan(offset, target.Length).CopyTo(target); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyFrom(long offset, ReadOnlySpan data) - { - data.CopyTo(this.AsSpan(offset, data.Length)); - } - - private partial Span AsSpan(long offset, int length); - - private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); -} - - -public ref partial struct SpanSerializationTarget -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt8(long offset, byte value) - { - this.AsSpan(offset, sizeof(byte))[0] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadUInt8(long offset) - { - return this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt8(long offset, sbyte value) - { - this.AsSpan(offset, sizeof(byte))[0] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadInt8(long offset) - { - return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt16(long offset, short value) - { - BinaryPrimitives.WriteInt16LittleEndian( - this.AsSpan(offset, sizeof(short)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16(long offset) - { - return BinaryPrimitives.ReadInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(short))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt16(long offset, ushort value) - { - BinaryPrimitives.WriteUInt16LittleEndian( - this.AsSpan(offset, sizeof(ushort)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16(long offset) - { - return BinaryPrimitives.ReadUInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ushort))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt32(long offset, int value) - { - BinaryPrimitives.WriteInt32LittleEndian( - this.AsSpan(offset, sizeof(int)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt32(long offset) - { - return BinaryPrimitives.ReadInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(int))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt32(long offset, uint value) - { - BinaryPrimitives.WriteUInt32LittleEndian( - this.AsSpan(offset, sizeof(uint)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt32(long offset) - { - return BinaryPrimitives.ReadUInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(uint))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt64(long offset, long value) - { - BinaryPrimitives.WriteInt64LittleEndian( - this.AsSpan(offset, sizeof(long)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadInt64(long offset) - { - return BinaryPrimitives.ReadInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(long))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt64(long offset, ulong value) - { - BinaryPrimitives.WriteUInt64LittleEndian( - this.AsSpan(offset, sizeof(ulong)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadUInt64(long offset) - { - return BinaryPrimitives.ReadUInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ulong))); - } - - public int WriteStringBytes(long offset, string value, Encoding encoding) - { - checked - { - int maxBytes = encoding.GetMaxByteCount(value.Length); - -#if NETSTANDARD2_0 - byte[] temp = ArrayPool.Shared.Rent(maxBytes); - int length = encoding.GetBytes(value, 0, value.Length, temp, 0); - temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); - ArrayPool.Shared.Return(temp); - - return length; -#else - int size = maxBytes; - long remainingSpace = this.Length - offset; - if (remainingSpace < maxBytes) - { - size = (int)Math.Min(int.MaxValue, remainingSpace); - } - - Span span = this.AsSpan(offset, size); - return encoding.GetBytes(value, span); -#endif - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(long offset, Span target) - { - this.AsReadOnlySpan(offset, target.Length).CopyTo(target); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyFrom(long offset, ReadOnlySpan data) - { - data.CopyTo(this.AsSpan(offset, data.Length)); - } - - private partial Span AsSpan(long offset, int length); - - private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); -} - diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.tt b/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.tt deleted file mode 100644 index 09e48098..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/SerializationTargetHelpers.tt +++ /dev/null @@ -1,210 +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. - */ - -<#@ 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 name, string classModifiers)[] types = - { - ("ArraySerializationTarget", "partial struct"), - ("InputBufferSerializationTarget", "partial struct"), - ("MemorySerializationTarget", "partial struct"), - ("SpanSerializationTarget", "ref partial struct"), - }; -#> - -using System.Buffers.Binary; -using System.Buffers; -using System.Text; - -namespace FlatSharp; - -<# - foreach (var pair in types) - { - var name = pair.name; - var modifier = pair.classModifiers; -#> - -public <#= modifier #> <#= name #> -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt8(long offset, byte value) - { - this.AsSpan(offset, sizeof(byte))[0] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadUInt8(long offset) - { - return this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt8(long offset, sbyte value) - { - this.AsSpan(offset, sizeof(byte))[0] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadInt8(long offset) - { - return (sbyte)this.AsReadOnlySpan(offset, sizeof(byte))[0]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt16(long offset, short value) - { - BinaryPrimitives.WriteInt16LittleEndian( - this.AsSpan(offset, sizeof(short)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16(long offset) - { - return BinaryPrimitives.ReadInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(short))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt16(long offset, ushort value) - { - BinaryPrimitives.WriteUInt16LittleEndian( - this.AsSpan(offset, sizeof(ushort)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16(long offset) - { - return BinaryPrimitives.ReadUInt16LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ushort))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt32(long offset, int value) - { - BinaryPrimitives.WriteInt32LittleEndian( - this.AsSpan(offset, sizeof(int)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt32(long offset) - { - return BinaryPrimitives.ReadInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(int))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt32(long offset, uint value) - { - BinaryPrimitives.WriteUInt32LittleEndian( - this.AsSpan(offset, sizeof(uint)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt32(long offset) - { - return BinaryPrimitives.ReadUInt32LittleEndian( - this.AsReadOnlySpan(offset, sizeof(uint))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt64(long offset, long value) - { - BinaryPrimitives.WriteInt64LittleEndian( - this.AsSpan(offset, sizeof(long)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadInt64(long offset) - { - return BinaryPrimitives.ReadInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(long))); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt64(long offset, ulong value) - { - BinaryPrimitives.WriteUInt64LittleEndian( - this.AsSpan(offset, sizeof(ulong)), - value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadUInt64(long offset) - { - return BinaryPrimitives.ReadUInt64LittleEndian( - this.AsReadOnlySpan(offset, sizeof(ulong))); - } - - public int WriteStringBytes(long offset, string value, Encoding encoding) - { - checked - { - int maxBytes = encoding.GetMaxByteCount(value.Length); - -#if NETSTANDARD2_0 - byte[] temp = ArrayPool.Shared.Rent(maxBytes); - int length = encoding.GetBytes(value, 0, value.Length, temp, 0); - temp.AsSpan().CopyTo(this.AsSpan((int)offset, length)); - ArrayPool.Shared.Return(temp); - - return length; -#else - int size = maxBytes; - long remainingSpace = this.Length - offset; - if (remainingSpace < maxBytes) - { - size = (int)Math.Min(int.MaxValue, remainingSpace); - } - - Span span = this.AsSpan(offset, size); - return encoding.GetBytes(value, span); -#endif - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(long offset, Span target) - { - this.AsReadOnlySpan(offset, target.Length).CopyTo(target); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyFrom(long offset, ReadOnlySpan data) - { - data.CopyTo(this.AsSpan(offset, data.Length)); - } - - private partial Span AsSpan(long offset, int length); - - private partial ReadOnlySpan AsReadOnlySpan(long offset, int length); -} - -<# - } -#> \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs index 64fa4d3a..f9030c3f 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs @@ -21,7 +21,7 @@ namespace FlatSharp; /// /// A serialization target that wraps a Span{byte}. Only supports the 32-bit address space. /// -public readonly ref partial struct SpanSerializationTarget : IFlatBufferReaderWriter +public ref struct SpanSerializationTarget : IFlatBufferSerializationTarget { private readonly Span span; @@ -39,6 +39,12 @@ public SpanSerializationTarget(Memory memory) { this.span = memory.Span; } + + public byte this[long index] + { + get => this.span[checked((int)index)]; + set => this.span[checked((int)index)] = value; + } public long Length => this.span.Length; @@ -55,11 +61,11 @@ public SpanSerializationTarget Slice(long start) return new(this.span.Slice((int)start)); } - private partial Span AsSpan(long offset, int length) + public Span AsSpan(long start, int length) { checked { - return this.span.Slice((int)offset, length); + return this.span.Slice((int)start, (int)length); } } } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs index a4e40b1f..d0520798 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs @@ -15,16 +15,17 @@ */ using System.Buffers.Binary; -using System.Text; namespace FlatSharp; -/// -/// A virtual serialization target that pretends to write data. Useful for simulating how many bytes -/// a write operation will consume. -/// -internal readonly struct VirtualSerializationTarget : IFlatBufferReaderWriter +internal struct VirtualSerializationTarget : IFlatBufferSerializationTarget { + public byte this[long index] + { + get => 0; + set { } + } + public long Length => long.MaxValue; public VirtualSerializationTarget Slice(long start, long length) @@ -37,89 +38,8 @@ public VirtualSerializationTarget Slice(long start) return this; } - public void WriteUInt8(long offset, byte value) - { - } - - public byte ReadUInt8(long offset) - { - throw new NotImplementedException(); - } - - public void WriteInt8(long offset, sbyte value) - { - } - - public sbyte ReadInt8(long offset) - { - throw new NotImplementedException(); - } - - public void WriteInt16(long offset, short value) - { - } - - public short ReadInt16(long offset) - { - throw new NotImplementedException(); - } - - public void WriteUInt16(long offset, ushort value) - { - } - - public ushort ReadUInt16(long offset) - { - throw new NotImplementedException(); - } - - public void WriteInt32(long offset, int value) - { - } - - public int ReadInt32(long offset) - { - throw new NotImplementedException(); - } - - public void WriteUInt32(long offset, uint value) - { - } - - public uint ReadUInt32(long offset) - { - throw new NotImplementedException(); - } - - public void WriteInt64(long offset, long value) - { - } - - public long ReadInt64(long offset) - { - throw new NotImplementedException(); - } - - public void WriteUInt64(long offset, ulong value) - { - } - - public ulong ReadUInt64(long offset) - { - throw new NotImplementedException(); - } - - public int WriteStringBytes(long offset, string value, Encoding encoding) - { - return encoding.GetByteCount(value); - } - - public void CopyFrom(long offset, ReadOnlySpan data) - { - } - - public void CopyTo(long offset, Span destination) + public Span AsSpan(long start, int length) { - throw new NotImplementedException(); + return default; } } \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs index 553e29fd..127ef29c 100644 --- a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs @@ -73,12 +73,12 @@ public void Reset() /// /// Writes a shared string. /// - public void WriteSharedString( - TBuffer target, + public void WriteSharedString( + TTarget target, long offset, string value, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -110,10 +110,10 @@ public void WriteSharedString( /// /// Flush any pending writes. /// - public void FlushWrites( - TBuffer target, + public void FlushWrites( + TTarget target, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -137,12 +137,12 @@ public void FlushWrites( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void FlushSharedString( - TBuffer target, + private static void FlushSharedString( + TTarget target, string value, List offsets, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/IPostSerializeAction.cs b/src/FlatSharp.Runtime/IPostSerializeAction.cs index 32b6ef60..c2112663 100644 --- a/src/FlatSharp.Runtime/IPostSerializeAction.cs +++ b/src/FlatSharp.Runtime/IPostSerializeAction.cs @@ -21,8 +21,8 @@ namespace FlatSharp.Internal; /// public interface IPostSerializeAction { - void Invoke(TBuffer target, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + void Invoke(TTarget target, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/ISerializer.cs b/src/FlatSharp.Runtime/ISerializer.cs index d974e69f..1c69e4a8 100644 --- a/src/FlatSharp.Runtime/ISerializer.cs +++ b/src/FlatSharp.Runtime/ISerializer.cs @@ -39,8 +39,8 @@ public interface ISerializer /// The destination. /// The object to serialize. /// The number of bytes written. - long Write(TBuffer target, object item) - where TBuffer : IFlatBufferReaderWriter + long Write(TTarget target, object item) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -81,8 +81,8 @@ public interface ISerializer /// The destination. /// The object to serialize. /// The number of bytes written. - long Write(TBuffer destination, T item) - where TBuffer : IFlatBufferReaderWriter + long Write(TTarget destination, T item) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/ISpanComparer.cs b/src/FlatSharp.Runtime/ISpanComparer.cs index 2524815e..ded8cc91 100644 --- a/src/FlatSharp.Runtime/ISpanComparer.cs +++ b/src/FlatSharp.Runtime/ISpanComparer.cs @@ -24,10 +24,5 @@ public interface ISpanComparer /// /// Compares the two spans. /// - int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif - ; + int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right); } diff --git a/src/FlatSharp.Runtime/SerializationContext.cs b/src/FlatSharp.Runtime/SerializationContext.cs index 80d5f15f..0a65a330 100644 --- a/src/FlatSharp.Runtime/SerializationContext.cs +++ b/src/FlatSharp.Runtime/SerializationContext.cs @@ -72,8 +72,8 @@ public void Reset(long capacity) /// Invokes any post-serialize actions. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InvokePostSerializeActions(TBuffer target) - where TBuffer : IFlatBufferReaderWriter + public void InvokePostSerializeActions(TTarget target) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -149,10 +149,10 @@ public long AllocateSpace(int bytesNeeded, int alignment) } [MethodImpl(MethodImplOptions.NoInlining)] // Common method; don't inline - public long FinishVTable( - TBuffer buffer, + public long FinishVTable( + TTarget buffer, Span vtable) - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs index 54339844..8daeb157 100644 --- a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs +++ b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs @@ -74,17 +74,19 @@ public VectorSortAction( /// 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. /// - public void Invoke(TBuffer target, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + public void Invoke(TTarget target, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif { checked { + var buffer = new SerializationTargetInputBuffer(target); + long vectorStartOffset = - vectorUOffset + target.ReadUInt32(vectorUOffset); - int vectorLength = (int)target.ReadUInt32(vectorStartOffset); + vectorUOffset + buffer.ReadUInt(vectorUOffset); + int vectorLength = (int)buffer.ReadUInt(vectorStartOffset); long index0Position = vectorStartOffset + sizeof(int); (long, int, long)[]? pooledArray = null; @@ -107,7 +109,7 @@ public void Invoke(TBuffer target, SerializationContext context) // 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. - TBuffer boundedVector = target.Slice(index0Position, sizeof(uint) * vectorLength); + Span boundedVector = target.AsSpan(index0Position, sizeof(uint) * vectorLength); long nextPosition = index0Position; for (int i = 0; i < keyOffsets.Length; ++i) { @@ -129,13 +131,13 @@ public void Invoke(TBuffer target, SerializationContext context) /// 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( - TBuffer buffer, + private static void IntroSort( + TTarget buffer, TSpanComparer keyComparer, int lo, int hi, Span<(long offset, int length, long tableOffset)> keyLocations) - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -182,7 +184,7 @@ private static void IntroSort( SwapVectorPositions(middle, hi - 1, keyLocations); var (pivotOffset, pivotLength, _) = keyLocations[hi - 1]; bool pivotExists = pivotOffset != 0; - var pivotBuffer = buffer.Slice(pivotOffset, pivotLength); + var pivotSpan = buffer.AsSpan(pivotOffset, pivotLength); // Partition int num2 = lo; @@ -192,8 +194,8 @@ private static void IntroSort( while (true) { var (keyOffset, keyLength, _) = keyLocations[++num2]; - var keyBuffer = buffer.Slice(keyOffset, keyLength); - if (keyComparer.Compare(keyOffset != 0, keyBuffer, pivotExists, pivotBuffer) >= 0) + var keySpan = buffer.AsSpan(keyOffset, keyLength); + if (keyComparer.Compare(keyOffset != 0, keySpan, pivotExists, pivotSpan) >= 0) { break; } @@ -202,8 +204,8 @@ private static void IntroSort( while (true) { var (keyOffset, keyLength, _) = keyLocations[--num3]; - var keyBuffer = buffer.Slice(keyOffset, keyLength); - if (keyComparer.Compare(pivotExists, pivotBuffer, keyOffset != 0, keyBuffer) >= 0) + var keySpan = buffer.AsSpan(keyOffset, keyLength); + if (keyComparer.Compare(pivotExists, pivotSpan, keyOffset != 0, keySpan) >= 0) { break; } @@ -229,13 +231,13 @@ private static void IntroSort( } } - private static void InsertionSort( - TBuffer buffer, + private static void InsertionSort( + TTarget buffer, TSpanComparer comparer, int lo, int hi, Span<(long offset, int length, long tableOffset)> keyLocations) - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -267,13 +269,13 @@ private static void InsertionSort( } } - private static void SwapIfGreater( - TBuffer target, + private static void SwapIfGreater( + TTarget target, TSpanComparer comparer, int leftIndex, int rightIndex, Span<(long, int, long)> keyOffsets) - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -311,20 +313,20 @@ private static void SwapVectorPositions(int leftIndex, int rightIndex, Span<(lon /// /// Left as unchecked since this is a sort operation (not a search). /// - private static (long offset, int length, long tableOffset) GetKeyOffset( - TBuffer target, + private static (long offset, int length, long tableOffset) GetKeyOffset( + TTarget target, long index0Position, int vectorIndex, int vtableIndex, int? inlineItemSize) - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif { checked { - var buffer = new SerializationTargetInputBuffer(target); + var buffer = new SerializationTargetInputBuffer(target); // Find offset to the table at the index. long tableOffset = index0Position + (sizeof(uint) * vectorIndex); diff --git a/src/FlatSharp.Runtime/SpanComparers.cs b/src/FlatSharp.Runtime/SpanComparers.cs index 4f1f8743..f37d8663 100644 --- a/src/FlatSharp.Runtime/SpanComparers.cs +++ b/src/FlatSharp.Runtime/SpanComparers.cs @@ -28,14 +28,10 @@ public BoolSpanComparer(bool defaultValue) this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - bool leftValue = leftExists ? left.ReadBool(0) : this.defaultValue; - bool rightValue = rightExists ? right.ReadBool(0) : this.defaultValue; + bool leftValue = leftExists ? ScalarSpanReader.ReadBool(left) : this.defaultValue; + bool rightValue = rightExists ? ScalarSpanReader.ReadBool(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } @@ -47,469 +43,385 @@ public struct NullableBoolSpanComparer : ISpanComparer public NullableBoolSpanComparer(bool? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadBool(0).CompareTo(right.ReadBool(0)); + return ScalarSpanReader.ReadBool(left).CompareTo(ScalarSpanReader.ReadBool(right)); } } -public struct UInt8SpanComparer : ISpanComparer +public struct ByteSpanComparer : ISpanComparer { private readonly byte defaultValue; - public UInt8SpanComparer(byte defaultValue) + public ByteSpanComparer(byte defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - byte leftValue = leftExists ? left.ReadUInt8(0) : this.defaultValue; - byte rightValue = rightExists ? right.ReadUInt8(0) : this.defaultValue; + byte leftValue = leftExists ? ScalarSpanReader.ReadByte(left) : this.defaultValue; + byte rightValue = rightExists ? ScalarSpanReader.ReadByte(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableUInt8SpanComparer : ISpanComparer +public struct NullableByteSpanComparer : ISpanComparer { - public NullableUInt8SpanComparer(byte? notUsed) + public NullableByteSpanComparer(byte? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadUInt8(0).CompareTo(right.ReadUInt8(0)); + return ScalarSpanReader.ReadByte(left).CompareTo(ScalarSpanReader.ReadByte(right)); } } -public struct Int8SpanComparer : ISpanComparer +public struct SByteSpanComparer : ISpanComparer { private readonly sbyte defaultValue; - public Int8SpanComparer(sbyte defaultValue) + public SByteSpanComparer(sbyte defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - sbyte leftValue = leftExists ? left.ReadInt8(0) : this.defaultValue; - sbyte rightValue = rightExists ? right.ReadInt8(0) : this.defaultValue; + sbyte leftValue = leftExists ? ScalarSpanReader.ReadSByte(left) : this.defaultValue; + sbyte rightValue = rightExists ? ScalarSpanReader.ReadSByte(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableInt8SpanComparer : ISpanComparer +public struct NullableSByteSpanComparer : ISpanComparer { - public NullableInt8SpanComparer(sbyte? notUsed) + public NullableSByteSpanComparer(sbyte? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadInt8(0).CompareTo(right.ReadInt8(0)); + return ScalarSpanReader.ReadSByte(left).CompareTo(ScalarSpanReader.ReadSByte(right)); } } -public struct UInt16SpanComparer : ISpanComparer +public struct UShortSpanComparer : ISpanComparer { private readonly ushort defaultValue; - public UInt16SpanComparer(ushort defaultValue) + public UShortSpanComparer(ushort defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - ushort leftValue = leftExists ? left.ReadUInt16(0) : this.defaultValue; - ushort rightValue = rightExists ? right.ReadUInt16(0) : this.defaultValue; + ushort leftValue = leftExists ? ScalarSpanReader.ReadUShort(left) : this.defaultValue; + ushort rightValue = rightExists ? ScalarSpanReader.ReadUShort(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableUInt16SpanComparer : ISpanComparer +public struct NullableUShortSpanComparer : ISpanComparer { - public NullableUInt16SpanComparer(ushort? notUsed) + public NullableUShortSpanComparer(ushort? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadUInt16(0).CompareTo(right.ReadUInt16(0)); + return ScalarSpanReader.ReadUShort(left).CompareTo(ScalarSpanReader.ReadUShort(right)); } } -public struct Int16SpanComparer : ISpanComparer +public struct ShortSpanComparer : ISpanComparer { private readonly short defaultValue; - public Int16SpanComparer(short defaultValue) + public ShortSpanComparer(short defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - short leftValue = leftExists ? left.ReadInt16(0) : this.defaultValue; - short rightValue = rightExists ? right.ReadInt16(0) : this.defaultValue; + short leftValue = leftExists ? ScalarSpanReader.ReadShort(left) : this.defaultValue; + short rightValue = rightExists ? ScalarSpanReader.ReadShort(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableInt16SpanComparer : ISpanComparer +public struct NullableShortSpanComparer : ISpanComparer { - public NullableInt16SpanComparer(short? notUsed) + public NullableShortSpanComparer(short? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadInt16(0).CompareTo(right.ReadInt16(0)); + return ScalarSpanReader.ReadShort(left).CompareTo(ScalarSpanReader.ReadShort(right)); } } -public struct Int32SpanComparer : ISpanComparer +public struct IntSpanComparer : ISpanComparer { private readonly int defaultValue; - public Int32SpanComparer(int defaultValue) + public IntSpanComparer(int defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - int leftValue = leftExists ? left.ReadInt32(0) : this.defaultValue; - int rightValue = rightExists ? right.ReadInt32(0) : this.defaultValue; + int leftValue = leftExists ? ScalarSpanReader.ReadInt(left) : this.defaultValue; + int rightValue = rightExists ? ScalarSpanReader.ReadInt(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableInt32SpanComparer : ISpanComparer +public struct NullableIntSpanComparer : ISpanComparer { - public NullableInt32SpanComparer(int? notUsed) + public NullableIntSpanComparer(int? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadInt32(0).CompareTo(right.ReadInt32(0)); + return ScalarSpanReader.ReadInt(left).CompareTo(ScalarSpanReader.ReadInt(right)); } } -public struct UInt32SpanComparer : ISpanComparer +public struct UIntSpanComparer : ISpanComparer { private readonly uint defaultValue; - public UInt32SpanComparer(uint defaultValue) + public UIntSpanComparer(uint defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - uint leftValue = leftExists ? left.ReadUInt32(0) : this.defaultValue; - uint rightValue = rightExists ? right.ReadUInt32(0) : this.defaultValue; + uint leftValue = leftExists ? ScalarSpanReader.ReadUInt(left) : this.defaultValue; + uint rightValue = rightExists ? ScalarSpanReader.ReadUInt(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableUInt32SpanComparer : ISpanComparer +public struct NullableUIntSpanComparer : ISpanComparer { - public NullableUInt32SpanComparer(uint? notUsed) + public NullableUIntSpanComparer(uint? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadUInt32(0).CompareTo(right.ReadUInt32(0)); + return ScalarSpanReader.ReadUInt(left).CompareTo(ScalarSpanReader.ReadUInt(right)); } } -public struct Int64SpanComparer : ISpanComparer +public struct LongSpanComparer : ISpanComparer { private readonly long defaultValue; - public Int64SpanComparer(long defaultValue) + public LongSpanComparer(long defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - long leftValue = leftExists ? left.ReadInt64(0) : this.defaultValue; - long rightValue = rightExists ? right.ReadInt64(0) : this.defaultValue; + long leftValue = leftExists ? ScalarSpanReader.ReadLong(left) : this.defaultValue; + long rightValue = rightExists ? ScalarSpanReader.ReadLong(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableInt64SpanComparer : ISpanComparer +public struct NullableLongSpanComparer : ISpanComparer { - public NullableInt64SpanComparer(long? notUsed) + public NullableLongSpanComparer(long? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadInt64(0).CompareTo(right.ReadInt64(0)); + return ScalarSpanReader.ReadLong(left).CompareTo(ScalarSpanReader.ReadLong(right)); } } -public struct UInt64SpanComparer : ISpanComparer +public struct ULongSpanComparer : ISpanComparer { private readonly ulong defaultValue; - public UInt64SpanComparer(ulong defaultValue) + public ULongSpanComparer(ulong defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - ulong leftValue = leftExists ? left.ReadUInt64(0) : this.defaultValue; - ulong rightValue = rightExists ? right.ReadUInt64(0) : this.defaultValue; + ulong leftValue = leftExists ? ScalarSpanReader.ReadULong(left) : this.defaultValue; + ulong rightValue = rightExists ? ScalarSpanReader.ReadULong(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableUInt64SpanComparer : ISpanComparer +public struct NullableULongSpanComparer : ISpanComparer { - public NullableUInt64SpanComparer(ulong? notUsed) + public NullableULongSpanComparer(ulong? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadUInt64(0).CompareTo(right.ReadUInt64(0)); + return ScalarSpanReader.ReadULong(left).CompareTo(ScalarSpanReader.ReadULong(right)); } } -public struct Float32SpanComparer : ISpanComparer +public struct FloatSpanComparer : ISpanComparer { private readonly float defaultValue; - public Float32SpanComparer(float defaultValue) + public FloatSpanComparer(float defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - float leftValue = leftExists ? left.ReadFloat32(0) : this.defaultValue; - float rightValue = rightExists ? right.ReadFloat32(0) : this.defaultValue; + float leftValue = leftExists ? ScalarSpanReader.ReadFloat(left) : this.defaultValue; + float rightValue = rightExists ? ScalarSpanReader.ReadFloat(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableFloat32SpanComparer : ISpanComparer +public struct NullableFloatSpanComparer : ISpanComparer { - public NullableFloat32SpanComparer(float? notUsed) + public NullableFloatSpanComparer(float? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadFloat32(0).CompareTo(right.ReadFloat32(0)); + return ScalarSpanReader.ReadFloat(left).CompareTo(ScalarSpanReader.ReadFloat(right)); } } -public struct Float64SpanComparer : ISpanComparer +public struct DoubleSpanComparer : ISpanComparer { private readonly double defaultValue; - public Float64SpanComparer(double defaultValue) + public DoubleSpanComparer(double defaultValue) { this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - double leftValue = leftExists ? left.ReadFloat64(0) : this.defaultValue; - double rightValue = rightExists ? right.ReadFloat64(0) : this.defaultValue; + double leftValue = leftExists ? ScalarSpanReader.ReadDouble(left) : this.defaultValue; + double rightValue = rightExists ? ScalarSpanReader.ReadDouble(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } } [ExcludeFromCodeCoverage] // Not currently used. -public struct NullableFloat64SpanComparer : ISpanComparer +public struct NullableDoubleSpanComparer : ISpanComparer { - public NullableFloat64SpanComparer(double? notUsed) + public NullableDoubleSpanComparer(double? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.ReadFloat64(0).CompareTo(right.ReadFloat64(0)); + return ScalarSpanReader.ReadDouble(left).CompareTo(ScalarSpanReader.ReadDouble(right)); } } diff --git a/src/FlatSharp.Runtime/SpanComparers.tt b/src/FlatSharp.Runtime/SpanComparers.tt index 8959d65d..2b677b1f 100644 --- a/src/FlatSharp.Runtime/SpanComparers.tt +++ b/src/FlatSharp.Runtime/SpanComparers.tt @@ -26,16 +26,16 @@ (string casedName, string typeName)[] types = { ("Bool", "bool"), - ("UInt8", "byte"), - ("Int8", "sbyte"), - ("UInt16", "ushort"), - ("Int16", "short"), - ("Int32", "int"), - ("UInt32", "uint"), - ("Int64", "long"), - ("UInt64", "ulong"), - ("Float32", "float"), - ("Float64", "double"), + ("Byte", "byte"), + ("SByte", "sbyte"), + ("UShort", "ushort"), + ("Short", "short"), + ("Int", "int"), + ("UInt", "uint"), + ("Long", "long"), + ("ULong", "ulong"), + ("Float", "float"), + ("Double", "double"), }; #> @@ -58,14 +58,10 @@ public struct <#=className#> : ISpanComparer this.defaultValue = defaultValue; } - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { - <#=typeName#> leftValue = leftExists ? left.Read<#=casedName#>(0) : this.defaultValue; - <#=typeName#> rightValue = rightExists ? right.Read<#=casedName#>(0) : this.defaultValue; + <#=typeName#> leftValue = leftExists ? ScalarSpanReader.Read<#=casedName#>(left) : this.defaultValue; + <#=typeName#> rightValue = rightExists ? ScalarSpanReader.Read<#=casedName#>(right) : this.defaultValue; return leftValue.CompareTo(rightValue); } @@ -77,19 +73,15 @@ public struct Nullable<#=className#> : ISpanComparer public Nullable<#=className#>(<#=typeName#>? notUsed) { } - - public int Compare(bool leftExists, TBuffer left, bool rightExists, TBuffer right) - where TBuffer : IFlatBufferReaderWriter -#if NET9_0_OR_GREATER - , allows ref struct -#endif + + public int Compare(bool leftExists, ReadOnlySpan left, bool rightExists, ReadOnlySpan right) { if (!leftExists || !rightExists) { return leftExists.CompareTo(rightExists); } - return left.Read<#=casedName#>(0).CompareTo(right.Read<#=casedName#>(0)); + return ScalarSpanReader.Read<#=casedName#>(left).CompareTo(ScalarSpanReader.Read<#=casedName#>(right)); } } diff --git a/src/FlatSharp.Runtime/StringSpanComparer.cs b/src/FlatSharp.Runtime/StringSpanComparer.cs index 64afb0ed..fd1c7481 100644 --- a/src/FlatSharp.Runtime/StringSpanComparer.cs +++ b/src/FlatSharp.Runtime/StringSpanComparer.cs @@ -35,11 +35,7 @@ public StringSpanComparer(string? notUsed) { } - public int Compare(bool leftExists, TBuffer x, bool rightExists, TBuffer y) - where TBuffer : IFlatBufferReaderWriter - #if NET9_0_OR_GREATER - , allows ref struct - #endif + public int Compare(bool leftExists, ReadOnlySpan x, bool rightExists, ReadOnlySpan y) { if (!leftExists || !rightExists) { @@ -47,7 +43,7 @@ public int Compare(bool leftExists, TBuffer x, bool rightExists, TBuffe } int i = 0; - long minLength = x.Length; + int minLength = x.Length; if (y.Length < minLength) { minLength = y.Length; @@ -56,8 +52,8 @@ public int Compare(bool leftExists, TBuffer x, bool rightExists, TBuffe while (i + sizeof(ulong) <= minLength) { // Use BE since we read from left to right. - ulong left = x.ReadUInt64(i); - ulong right = y.ReadUInt64(i); + ulong left = BinaryPrimitives.ReadUInt64BigEndian(x.Slice(i, sizeof(ulong))); + ulong right = BinaryPrimitives.ReadUInt64BigEndian(y.Slice(i, sizeof(ulong))); int cmp = left.CompareTo(right); if (cmp != 0) @@ -70,8 +66,8 @@ public int Compare(bool leftExists, TBuffer x, bool rightExists, TBuffe for (; i < minLength; ++i) { - byte xByte = x.ReadUInt8(i); - byte yByte = y.ReadUInt8(i); + byte xByte = x[i]; + byte yByte = y[i]; if (xByte != yByte) { @@ -79,6 +75,6 @@ public int Compare(bool leftExists, TBuffer x, bool rightExists, TBuffe } } - return (int)(x.Length - y.Length); + return x.Length - y.Length; } } diff --git a/src/FlatSharp/FlatBufferSerializer.cs b/src/FlatSharp/FlatBufferSerializer.cs index 214791ee..8f379f35 100644 --- a/src/FlatSharp/FlatBufferSerializer.cs +++ b/src/FlatSharp/FlatBufferSerializer.cs @@ -174,9 +174,9 @@ 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 long Serialize(T item, TBuffer destination) + public long Serialize(T item, TTarget destination) where T : class - where TBuffer : IFlatBufferReaderWriter + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif diff --git a/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs b/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs index 2dc624d9..d52af7b8 100644 --- a/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs +++ b/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs @@ -432,8 +432,8 @@ private HashSet TraverseAssemblyReferenceGraph() string methodText = $@" - public void Write(ref TBuffer target, {CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + public void Write(ref TTarget target, {CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget #if {CSharpHelpers.Net9PreprocessorVariable} , allows ref struct #endif @@ -743,12 +743,12 @@ private string GenerateSerializeMethod(ITypeModel typeModel, CodeGeneratedMethod string fullText = $@" {method.GetMethodImplAttribute()} - internal static void {DefaultMethodNameResolver.ResolveSerialize(typeModel).methodName}( - ref TBuffer {context.TargetVariableName}, + internal static void {DefaultMethodNameResolver.ResolveSerialize(typeModel).methodName}( + ref TTarget {context.TargetVariableName}, {CSharpHelpers.GetGlobalCompilableTypeName(typeModel.ClrType)} {context.ValueVariableName}, {GetVTableOffsetVariableType(typeModel.PhysicalLayout.Length)} {context.OffsetVariableName} {serializationContextParameter} - {tableFieldContextParameter}) where TBuffer : IFlatBufferReaderWriter + {tableFieldContextParameter}) where TTarget : IFlatBufferSerializationTarget #if {CSharpHelpers.Net9PreprocessorVariable} , allows ref struct #endif diff --git a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs index 23897b0c..4e89c15b 100644 --- a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs +++ b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs @@ -57,7 +57,7 @@ public SerializationCodeGenContext( public string TableFieldContextVariableName { get; init; } /// - /// The variable name of the span. Represents a reference. + /// The variable name of the span. Represents a reference. /// public string TargetVariableName { get; init; } diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs index a8de16b8..68c19083 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs @@ -187,8 +187,8 @@ public void Reset() this.offsets.Clear(); } - public void WriteSharedString(TBuffer spanWriter, long offset, string value, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + public void WriteSharedString(TTarget spanWriter, long offset, string value, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif @@ -202,8 +202,8 @@ public void WriteSharedString(TBuffer spanWriter, long offset, string v list.Add(offset); } - public void FlushWrites(TBuffer writer, SerializationContext context) - where TBuffer : IFlatBufferReaderWriter + public void FlushWrites(TTarget writer, SerializationContext context) + where TTarget : IFlatBufferSerializationTarget #if NET9_0_OR_GREATER , allows ref struct #endif From 376e2ae5b55abd41ed627da49adbc6d2a2280867 Mon Sep 17 00:00:00 2001 From: James Courtney Date: Fri, 15 Nov 2024 16:57:07 -0800 Subject: [PATCH 06/13] Tweaks --- .../IO/SerializationTarget/BigSpan.cs | 110 ++++++++++++++++++ src/FlatSharp/TypeModel/ScalarTypeModel.cs | 19 ++- .../TypeModel/ScalarTypeModelPartials.cs | 3 + src/FlatSharp/TypeModel/ScalarTypeModels.cs | 44 +++++-- src/FlatSharp/TypeModel/ScalarTypeModels.tt | 30 ++--- 5 files changed, 169 insertions(+), 37 deletions(-) create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs new file mode 100644 index 00000000..a73312aa --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs @@ -0,0 +1,110 @@ +/* + * 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.Binary; +using System.Runtime.InteropServices; + + +#if TRUE + +public readonly ref struct BigSpan +{ + private readonly nuint length; + private readonly ref byte value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan(Span span) + { + this.length = (nuint)span.Length; + this.value = ref span[0]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private BigSpan(ref byte value, nuint length) + { + this.value = ref value; + this.length = length; + } + + public ref byte this[nuint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (index >= this.length) + { + ThrowOutOfRange(); + } + + return ref Unsafe.Add(ref this.value, index); + } + } + + public nuint Length => this.length; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan Slice(nuint start, nuint length) + { + nuint sum = start + length; + if (sum < start || sum > this.length) + { + ThrowOutOfRange(); + } + + return new BigSpan(ref Unsafe.Add(ref this.value, start), length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BigSpan Slice(nuint start) + { + if (start > this.length) + { + ThrowOutOfRange(); + } + + return new BigSpan(ref Unsafe.Add(ref this.value, start), this.length - start); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span ToSpan(nuint start, int length) + { + if (length < 0 || (start + (uint)length) > this.length) + { + ThrowOutOfRange(); + } + + return MemoryMarshal.CreateSpan( + ref Unsafe.Add(ref this.value, start), + length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T ReadLittleEndian(nuint offset) + where T : unmanaged + { + var slice = this.ToSpan(offset, Unsafe.SizeOf()); + return Unsafe.ReadUnaligned(ref slice[0]); + } + + private static void ThrowOutOfRange() + { + throw new IndexOutOfRangeException(); + } +} + +#endif \ No newline at end of file diff --git a/src/FlatSharp/TypeModel/ScalarTypeModel.cs b/src/FlatSharp/TypeModel/ScalarTypeModel.cs index 6811a3c6..7108fab3 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. /// @@ -78,11 +73,6 @@ internal ScalarTypeModel( /// public override bool SerializesInline => true; - /// - /// We don't care about serialization context. - /// - public override bool SerializeMethodRequiresContext => false; - /// /// Scalars are leaf nodes. /// @@ -96,7 +86,12 @@ internal ScalarTypeModel( /// /// The name of a write method for an input buffer. /// - protected abstract string SpanWriterWriteMethodName { get; } + protected abstract string WriteMethodName { get; } + + /// + /// Gets the type name alias (int, short, etc). + /// + protected abstract string TypeNameAlias { get; } /// /// Force children to reimplement. @@ -130,7 +125,7 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeGenContext context) { string variableName = context.ValueVariableName; - return new CodeGeneratedMethod($"{context.TargetVariableName}.{this.SpanWriterWriteMethodName}({context.OffsetVariableName}, {variableName});") + return new CodeGeneratedMethod($"{this.WriteMethodName}({context.TargetVariableName}, {variableName});") { IsMethodInline = 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 e9737cfa..0755eb19 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.cs @@ -37,7 +37,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadBool"; - protected override string SpanWriterWriteMethodName => "WriteBool"; + protected override string WriteMethodName => "SerializationHelpers.WriteBool"; + + protected override string TypeNameAlias => "bool"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -64,7 +66,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadByte"; - protected override string SpanWriterWriteMethodName => "WriteUInt8"; + protected override string WriteMethodName => "SerializationHelpers.WriteUInt8"; + + protected override string TypeNameAlias => "byte"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -91,7 +95,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadSByte"; - protected override string SpanWriterWriteMethodName => "WriteInt8"; + protected override string WriteMethodName => "SerializationHelpers.WriteInt8"; + + protected override string TypeNameAlias => "sbyte"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -118,7 +124,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadUShort"; - protected override string SpanWriterWriteMethodName => "WriteUInt16"; + protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt16LittleEndian"; + + protected override string TypeNameAlias => "ushort"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -145,7 +153,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadShort"; - protected override string SpanWriterWriteMethodName => "WriteInt16"; + protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteInt16LittleEndian"; + + protected override string TypeNameAlias => "short"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -172,7 +182,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadInt"; - protected override string SpanWriterWriteMethodName => "WriteInt32"; + protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian"; + + protected override string TypeNameAlias => "int"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -199,7 +211,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadUInt"; - protected override string SpanWriterWriteMethodName => "WriteUInt32"; + protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian"; + + protected override string TypeNameAlias => "uint"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -226,7 +240,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadLong"; - protected override string SpanWriterWriteMethodName => "WriteInt64"; + protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteInt64LittleEndian"; + + protected override string TypeNameAlias => "long"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -253,7 +269,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadULong"; - protected override string SpanWriterWriteMethodName => "WriteUInt64"; + protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt64LittleEndian"; + + protected override string TypeNameAlias => "ulong"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -280,7 +298,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadFloat"; - protected override string SpanWriterWriteMethodName => "WriteFloat32"; + protected override string WriteMethodName => "SerializationHelpers.WriteFloat32"; + + protected override string TypeNameAlias => "float"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { @@ -307,7 +327,9 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadDouble"; - protected override string SpanWriterWriteMethodName => "WriteFloat64"; + protected override string WriteMethodName => "SerializationHelpers.WriteFloat64"; + + 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 655a85c5..624597bb 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.tt +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.tt @@ -24,20 +24,19 @@ <# ( - string casedName, string commonName, string typeName, string literalSpecifier, string[] fbsAliases, - string writeNameClass, string readNameClass)[] types = + string casedName, string commonName, string typeName, string literalSpecifier, string[] fbsAliases, string inlineWriteMethod)[] types = { - ("Bool", "Bool", "bool", "", new[] { "bool" }, "SpanWriterExtensions", "InputBufferExtensions"), - ("UInt8", "Byte", "byte", "", new[] { "ubyte", "uint8" }, null, null), - ("Int8", "SByte", "sbyte", "", new[] { "byte", "int8" }, null, null), - ("UInt16", "UShort", "ushort", "", new[] { "ushort", "uint16" }, null, null), - ("Int16", "Short", "short", "", new[] { "short", "int16" }, null, null), - ("Int32", "Int", "int", "", new[] { "int", "int32" }, null, null), - ("UInt32", "UInt", "uint", "u", new[] { "uint", "uint32" }, null, null), - ("Int64", "Long", "long", "L", new[] { "long", "int64" }, null, null), - ("UInt64", "ULong", "ulong", "ul", new[] { "ulong", "uint64" }, null, null), - ("Float32", "Float", "float", "f", new[] { "float", "float32" }, null, null), - ("Float64", "Double", "double", "d", new[] { "double", "float64" }, null, null), + ("Bool", "Bool", "bool", "", new[] { "bool" }, "SerializationHelpers.WriteBool"), + ("UInt8", "Byte", "byte", "", new[] { "ubyte", "uint8" }, "SerializationHelpers.WriteUInt8"), + ("Int8", "SByte", "sbyte", "", new[] { "byte", "int8" }, "SerializationHelpers.WriteInt8"), + ("UInt16", "UShort", "ushort", "", new[] { "ushort", "uint16" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt16LittleEndian"), + ("Int16", "Short", "short", "", new[] { "short", "int16" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteInt16LittleEndian"), + ("Int32", "Int", "int", "", new[] { "int", "int32" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian"), + ("UInt32", "UInt", "uint", "u", new[] { "uint", "uint32" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian"), + ("Int64", "Long", "long", "L", new[] { "long", "int64" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteInt64LittleEndian"), + ("UInt64", "ULong", "ulong", "ul", new[] { "ulong", "uint64" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt64LittleEndian"), + ("Float32", "Float", "float", "f", new[] { "float", "float32" }, "SerializationHelpers.WriteFloat32"), + ("Float64", "Double", "double", "d", new[] { "double", "float64" }, "SerializationHelpers.WriteFloat64"), }; #> @@ -50,6 +49,7 @@ namespace FlatSharp.TypeModel; var typeName = pair.typeName; var commonName = pair.commonName; var className = $"{commonName}TypeModel"; + var writeMethod = pair.inlineWriteMethod; #> /// @@ -70,7 +70,9 @@ public partial class <#= className #> : ScalarTypeModel protected override string InputBufferReadMethodName => "Read<#= commonName #>"; - protected override string SpanWriterWriteMethodName => "Write<#= casedName #>"; + protected override string WriteMethodName => "<#= writeMethod #>"; + + protected override string TypeNameAlias => "<#=typeName#>"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) { From da3bd94818e71be260623bef0d6d0e90eea5bab1 Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sat, 16 Nov 2024 05:56:36 -0800 Subject: [PATCH 07/13] Switch to bigspan --- .../FlatSharp.Runtime.csproj | 14 + .../GeneratedSerializerWrapper.cs | 15 +- src/FlatSharp.Runtime/IGeneratedSerializer.cs | 11 +- .../IO/ISharedStringWriter.cs | 14 +- .../IO/InputBuffer/ArrayInputBuffer.cs | 11 +- .../IO/InputBuffer/ArraySegmentInputBuffer.cs | 11 +- .../IO/InputBuffer/IInputBuffer.cs | 6 +- .../IO/InputBuffer/InputBufferExtensions.cs | 97 ++---- .../IO/InputBuffer/MemoryInputBuffer.cs | 11 +- .../InputBuffer/ReadOnlyMemoryInputBuffer.cs | 11 +- .../SerializationTargetInputBuffer.cs | 68 ---- .../ArraySerializationTarget.cs | 74 ----- .../IO/SerializationTarget/BigReadOnlySpan.cs | 83 +++++ .../IO/SerializationTarget/BigSpan.cs | 241 +++++++++++++-- .../IO/SerializationTarget/BigSpanHelpers.cs | 291 +++++++++++++++++ .../IO/SerializationTarget/BigSpanHelpers.tt | 99 ++++++ ...FlatBufferSerializationTargetExtensions.cs | 292 ------------------ .../IFlatBufferSerializationTarget.cs | 52 ---- .../InputBufferSerializationTarget.cs | 67 ---- .../MemorySerializationTarget.cs | 62 ---- .../SpanSerializationTarget.cs | 73 ----- .../VirtualSerializationTarget.cs | 45 --- .../IO/SharedStringWriter.cs | 28 +- src/FlatSharp.Runtime/IPostSerializeAction.cs | 7 +- src/FlatSharp.Runtime/ISerializer.cs | 19 +- .../ISerializerExtensions.cs | 80 +---- src/FlatSharp.Runtime/SerializationContext.cs | 20 +- .../SortedVectorHelpersInternal.cs | 182 +++++------ src/FlatSharp/FlatBufferSerializer.cs | 6 +- src/FlatSharp/FlatBufferVectorHelpers.cs | 7 +- src/FlatSharp/FlatSharp.csproj | 4 + .../DeserializeClassDefinition.cs | 4 +- .../RoslynSerializerGenerator.cs | 27 +- .../SerializationCodeGenContext.cs | 10 +- src/FlatSharp/TypeModel/ScalarTypeModel.cs | 12 +- src/FlatSharp/TypeModel/ScalarTypeModels.cs | 22 +- src/FlatSharp/TypeModel/ScalarTypeModels.tt | 22 +- src/FlatSharp/TypeModel/StringTypeModel.cs | 6 +- src/FlatSharp/TypeModel/StructTypeModel.cs | 6 +- src/FlatSharp/TypeModel/TableTypeModel.cs | 6 +- src/FlatSharp/TypeModel/UnionTypeModel.cs | 4 +- .../TypeModel/ValueStructTypeModel.cs | 12 +- .../TypeModel/Vectors/BaseVectorTypeModel.cs | 4 +- .../Vectors/MemoryVectorTypeModel.cs | 2 +- .../UnityNativeArrayVectorTypeModel.cs | 2 +- .../BaseVectorOfUnionTypeModel.cs | 8 +- src/Tests/CompileTests/NativeAot/Program.cs | 8 +- src/Tests/FlatSharpCompilerTests/FullTests.cs | 2 +- .../ClassLib/SerializerConfigurationTests.cs | 12 +- .../IO/InputBufferTests.cs | 6 +- 50 files changed, 951 insertions(+), 1225 deletions(-) delete mode 100644 src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.cs create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs delete mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs 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 94ff38bf..bff73f56 100644 --- a/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs +++ b/src/FlatSharp.Runtime/GeneratedSerializerWrapper.cs @@ -83,11 +83,6 @@ item is IFlatBufferDeserializedObject deserializedObj && // than to introduce an 'if'. } - public long GetActualSize(T item) - { - return this.Write(new VirtualSerializationTarget(), item); - } - long ISerializer.GetMaxSize(object item) { return item switch @@ -148,11 +143,7 @@ public T Parse(TInputBuffer buffer, FlatBufferDeserializationOptio object ISerializer.Parse(TInputBuffer buffer, FlatBufferDeserializationOption? option) => this.Parse(buffer, option); - public long Write(TTarget destination, T item) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif + public long Write(BigSpan destination, T item) { if (item is null) { @@ -187,7 +178,7 @@ item is IFlatBufferDeserializedObject deserializedObj && Debug.Assert(!sharedStringWriter.IsDirty); } - this.innerSerializer.Write(ref destination, item, serializationContext); + this.innerSerializer.Write(destination, item, serializationContext); if (sharedStringWriter?.IsDirty == true) { @@ -206,7 +197,7 @@ item is IFlatBufferDeserializedObject deserializedObj && return serializationContext.Offset; } - long ISerializer.Write(TTarget target, object item) + long ISerializer.Write(BigSpan target, object item) { return item switch { diff --git a/src/FlatSharp.Runtime/IGeneratedSerializer.cs b/src/FlatSharp.Runtime/IGeneratedSerializer.cs index a528fe8e..e4f07c63 100644 --- a/src/FlatSharp.Runtime/IGeneratedSerializer.cs +++ b/src/FlatSharp.Runtime/IGeneratedSerializer.cs @@ -43,15 +43,10 @@ public interface IGeneratedSerializer /// The target. /// The item. /// The serialization context. - void Write( - ref TSerializationTarget target, + void Write( + BigSpan target, T item, - SerializationContext context) - where TSerializationTarget : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - ; + SerializationContext context); /// /// Computes the maximum size necessary to serialize the given instance of . diff --git a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs index 636c404e..b562af78 100644 --- a/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/ISharedStringWriter.cs @@ -41,20 +41,10 @@ public interface ISharedStringWriter /// The location in the buffer of the uoffset to the string. /// The string to write. /// The serialization context. - void WriteSharedString(TTarget spanWriter, long offset, string value, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - ; + void WriteSharedString(BigSpan spanWriter, long offset, string value, SerializationContext context); /// /// Flushes any pending writes. Invoked at the end of a serialization operation. /// - void FlushWrites(TTarget writer, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - ; + void FlushWrites(BigSpan writer, SerializationContext context); } diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs index 04993714..8ba548db 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ArrayInputBuffer.cs @@ -37,18 +37,15 @@ public ArrayInputBuffer(byte[] buffer) public long Length => this.memory.Length; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan(long offset, int length) + public BigReadOnlySpan GetReadOnlySpan() { - return this.GetSpan(offset, length); + return new(this.GetSpan()); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan(long offset, int length) + public BigSpan GetSpan() { - checked - { - return this.memory.AsSpan().Slice((int)offset, length); - } + return new(this.memory.AsSpan()); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs index d393787a..430ec745 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ArraySegmentInputBuffer.cs @@ -37,18 +37,15 @@ public ArraySegmentInputBuffer(ArraySegment memory) public long Length => this.pointer.segment.Count; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan(long offset, int length) + public BigReadOnlySpan GetReadOnlySpan() { - return this.GetSpan(offset, length); + return new(this.GetSpan()); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan(long offset, int length) + public BigSpan GetSpan() { - checked - { - return this.pointer.segment.AsSpan((int)offset, length); - } + return new(this.pointer.segment.AsSpan()); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs index 9d001c0f..852033cd 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/IInputBuffer.cs @@ -35,11 +35,11 @@ public interface IInputBuffer /// Gets the length of this input buffer. /// long Length { get; } - + /// /// Gets a read only span covering the entire input buffer. /// - ReadOnlySpan GetReadOnlySpan(long offset, int length); + BigReadOnlySpan GetReadOnlySpan(); /// /// Gets a read only memory covering the entire input buffer. @@ -49,7 +49,7 @@ public interface IInputBuffer /// /// Gets a span covering the entire input buffer. /// - Span GetSpan(long offset, int length); + BigSpan GetSpan(); /// /// Gets a memory covering the entire input buffer. diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs index d3bbd35b..fc23c9fc 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs @@ -32,9 +32,6 @@ public static class InputBufferExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool ReadBool(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { return buffer.ReadByte(offset) != SerializationHelpers.False; } @@ -42,123 +39,71 @@ public static bool ReadBool(this TBuffer buffer, long offset) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte ReadByte(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - return buffer.GetReadOnlySpan(offset, sizeof(byte))[0]; + return buffer.GetReadOnlySpan()[offset]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static sbyte ReadSByte(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - return (sbyte)buffer.ReadByte(offset); + return buffer.GetReadOnlySpan().ReadSByte(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ReadUShort(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - buffer.CheckAlignment(offset, sizeof(ushort)); - return BinaryPrimitives.ReadUInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ushort))); + return buffer.GetReadOnlySpan().ReadUShort(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short ReadShort(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - buffer.CheckAlignment(offset, sizeof(short)); - return BinaryPrimitives.ReadInt16LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(short))); + return buffer.GetReadOnlySpan().ReadShort(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint ReadUInt(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - buffer.CheckAlignment(offset, sizeof(uint)); - return BinaryPrimitives.ReadUInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(uint))); + return buffer.GetReadOnlySpan().ReadUInt(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReadInt(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - return BinaryPrimitives.ReadInt32LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(int))); + return buffer.GetReadOnlySpan().ReadInt(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong ReadULong(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - buffer.CheckAlignment(offset, sizeof(ulong)); - return BinaryPrimitives.ReadUInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(ulong))); + return buffer.GetReadOnlySpan().ReadULong(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long ReadLong(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - buffer.CheckAlignment(offset, sizeof(long)); - return BinaryPrimitives.ReadInt64LittleEndian(buffer.GetReadOnlySpan(offset, sizeof(long))); + return buffer.GetReadOnlySpan().ReadLong(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float ReadFloat(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - buffer.CheckAlignment(offset, sizeof(float)); - -#if NETSTANDARD - ScalarSpanReader.FloatLayout layout = new() - { - bytes = buffer.ReadUInt(offset) - }; - - return layout.value; -#else - return BinaryPrimitives.ReadSingleLittleEndian(buffer.GetReadOnlySpan(offset, sizeof(float))); -#endif + return buffer.GetReadOnlySpan().ReadFloat(offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double ReadDouble(this TBuffer buffer, long offset) where TBuffer : IInputBuffer -#if NET9_0_OR_GREATER - , allows ref struct -#endif { - buffer.CheckAlignment(offset, sizeof(double)); - -#if NETSTANDARD - return BitConverter.Int64BitsToDouble(buffer.ReadLong(offset)); -#else - return BinaryPrimitives.ReadDoubleLittleEndian(buffer.GetReadOnlySpan(offset, sizeof(double))); -#endif + return buffer.GetReadOnlySpan().ReadDouble(offset); } /// @@ -182,7 +127,7 @@ public static string ReadStringFromUOffset(this TBuffer buffer, long st where TBuffer : IInputBuffer { int numberOfBytes = (int)buffer.ReadUInt(stringStart); - ReadOnlySpan stringValue = buffer.GetReadOnlySpan(stringStart + sizeof(int), numberOfBytes); + ReadOnlySpan stringValue = buffer.GetReadOnlySpan().ToSpan(stringStart + sizeof(int), numberOfBytes); #if NETSTANDARD2_0 byte[] temp = ArrayPool.Shared.Rent(numberOfBytes); @@ -228,7 +173,7 @@ public static void InitializeVTable( FSThrow.InvalidData_VTableTooShort(); } - fieldData = buffer.GetReadOnlySpan(vtableOffset, vtableLength).Slice(4); + fieldData = buffer.GetReadOnlySpan().ToSpan(vtableOffset, vtableLength).Slice(4); vtableFieldCount = (ulong)(fieldData.Length / 2); } @@ -265,9 +210,11 @@ public static Span UnsafeReadSpan(this TBuffer buff // 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( - uoffset + sizeof(uint), - checked(Unsafe.SizeOf() * (int)buffer.ReadUInt(uoffset))); + var byteSpanAtDataOffset = buffer + .GetSpan() + .ToSpan( + uoffset + sizeof(uint), + checked(Unsafe.SizeOf() * (int)buffer.ReadUInt(uoffset))); var sourceSpan = MemoryMarshal.Cast(byteSpanAtDataOffset); @@ -275,12 +222,8 @@ public static Span UnsafeReadSpan(this TBuffer buff } [MethodImpl(MethodImplOptions.NoInlining)] - public static long CopyTo(this TBuffer buffer, TTarget target) + public static long CopyTo(this TBuffer buffer, BigSpan target) where TBuffer : IInputBuffer - where TTarget : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif { if (target.Length < buffer.Length) { @@ -292,8 +235,8 @@ public static long CopyTo(this TBuffer buffer, TTarget target) while (offset < buffer.Length) { long remaining = buffer.Length - offset; - var chunk = buffer.GetReadOnlySpan(offset, (int)Math.Min(int.MaxValue, remaining)); - chunk.CopyTo(target.AsSpan(offset, chunk.Length)); + var chunk = buffer.GetReadOnlySpan().ToSpan(offset, (int)Math.Min(int.MaxValue, remaining)); + chunk.CopyTo(target.ToSpan(offset, chunk.Length)); offset += chunk.Length; } diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs index a03fe6ee..d69168e3 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/MemoryInputBuffer.cs @@ -39,18 +39,15 @@ public long Length } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan(long offset, int length) + public BigReadOnlySpan GetReadOnlySpan() { - return this.GetSpan(offset, length); + return new(this.GetSpan()); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan(long offset, int length) + public BigSpan GetSpan() { - checked - { - return this.pointer.memory.Span.Slice((int)offset, length); - } + return new(this.pointer.memory.Span); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs index ca7648c6..7e9d03bf 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/ReadOnlyMemoryInputBuffer.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Runtime.InteropServices; using System.Text; namespace FlatSharp; @@ -47,12 +48,10 @@ public long Length } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan(long offset, int length) + public BigReadOnlySpan GetReadOnlySpan() { - checked - { - return this.pointer.memory.Span.Slice((int)offset, length); - } + var rwMemory = MemoryMarshal.AsMemory(this.pointer.memory); + return new(new BigSpan(rwMemory.Span)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -64,7 +63,7 @@ public ReadOnlyMemory GetReadOnlyMemory(long offset, int length) } } - public Span GetSpan(long offset, int length) + public BigSpan GetSpan() { FSThrow.InvalidOperation(ErrorMessage); return default; diff --git a/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs b/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs deleted file mode 100644 index f805df96..00000000 --- a/src/FlatSharp.Runtime/IO/InputBuffer/SerializationTargetInputBuffer.cs +++ /dev/null @@ -1,68 +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. - */ - -namespace FlatSharp; - -/// -/// An implementation of InputBuffer for writable memory segments. -/// -internal -#if NET9_0_OR_GREATER - ref -#endif -struct SerializationTargetInputBuffer : IInputBuffer - where TTarget : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif -{ - private readonly TTarget target; - - public SerializationTargetInputBuffer(TTarget target) - { - this.target = target; - } - - public bool IsPinned => false; - - public bool IsReadOnly => false; - - public long Length => this.target.Length; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetReadOnlySpan(long offset, int length) - { - return this.GetSpan(offset, length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan(long offset, int length) - { - return this.target.AsSpan(offset, length); - } - - [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) - { - return FSThrow.InvalidOperation>("Unsupported"); - } -} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs deleted file mode 100644 index 041fa799..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/ArraySerializationTarget.cs +++ /dev/null @@ -1,74 +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.Buffers.Binary; - -namespace FlatSharp; - -/// -/// A serialization target that wraps a Span{byte}. Only supports the 32-bit address space. -/// -public struct ArraySerializationTarget : IFlatBufferSerializationTarget -{ - private readonly byte[] array; - private readonly int start; - - public ArraySerializationTarget(byte[] array) - { - this.array = array; - this.Length = array.Length; - this.start = 0; - } - - public ArraySerializationTarget(byte[] array, int start, int length) - { - this.start = start; - this.Length = length; - this.array = array; - } - - public byte this[long index] - { - get => this.array[checked(this.start + (int)index)]; - set => this.array[checked(this.start + (int)index)] = value; - } - - public long Length { get; } - - public ArraySerializationTarget Slice(long start, long length) - { - checked - { - return new ArraySerializationTarget(this.array, (int)(this.start + start), (int)length); - } - } - - public ArraySerializationTarget Slice(long start) - { - checked - { - return new(this.array, (int)(this.start + start), (int)(this.Length - start)); - } - } - - public Span AsSpan(long start, int length) - { - checked - { - return this.array.AsSpan().Slice(this.start + (int)start, length); - } - } -} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs new file mode 100644 index 00000000..5285ada4 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs @@ -0,0 +1,83 @@ +/* + * 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; + } + + 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(); + } + + [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/BigSpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs index a73312aa..9bb09c09 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs @@ -16,95 +16,288 @@ namespace FlatSharp; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.InteropServices; -#if TRUE - -public readonly ref struct BigSpan +public readonly ref partial struct BigSpan { - private readonly nuint length; +#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) { - this.length = (nuint)span.Length; +#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, nuint length) + 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[nuint index] + public ref byte this[long index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if (index >= this.length) + if ((ulong)index >= (ulong)this.Length) { ThrowOutOfRange(); } - return ref Unsafe.Add(ref this.value, index); +#if NET7_0_OR_GREATER + return ref Unsafe.Add(ref this.value, (IntPtr)index); +#else + return ref this.span[(int)index]; +#endif } } - public nuint Length => this.length; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BigSpan Slice(nuint start, nuint length) + public BigSpan Slice(long start, long length) { - nuint sum = start + length; - if (sum < start || sum > this.length) + if ((length | start) < 0 || start + length > this.Length) { ThrowOutOfRange(); } - return new BigSpan(ref Unsafe.Add(ref this.value, start), length); +#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(nuint start) + public BigSpan Slice(long start) { - if (start > this.length) + if (start > this.Length) { ThrowOutOfRange(); } - return new BigSpan(ref Unsafe.Add(ref this.value, start), this.length - start); +#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(nuint start, int length) + public Span ToSpan(long start, int length) { - if (length < 0 || (start + (uint)length) > this.length) + if (length < 0 || start < 0 || start + length > this.Length) { ThrowOutOfRange(); } +#if NET7_0_OR_GREATER return MemoryMarshal.CreateSpan( - ref Unsafe.Add(ref this.value, start), + 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. + /// + 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)); + + Span destination = this.ToSpan(stringStartOffset + 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 + this[stringStartOffset + bytesWritten + sizeof(uint)] = 0; + + // write length + this.WriteInt(stringStartOffset, bytesWritten); + + // give back unused space. Account for null terminator. + context.Offset -= maxItems - (bytesWritten + 1); + + return stringStartOffset; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T ReadLittleEndian(nuint offset) + public void WriteUOffset( + long offset, + long secondOffset) + { + checked + { + uint uoffset = (uint)(secondOffset - offset); + this.WriteUInt(offset, uoffset); + } + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private T ReadUnaligned(long offset) where T : unmanaged { + CheckAlignment(offset, Unsafe.SizeOf()); var slice = this.ToSpan(offset, Unsafe.SizeOf()); return Unsafe.ReadUnaligned(ref slice[0]); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteUnaligned(long offset, T value) + where T : unmanaged + { + CheckAlignment(offset, Unsafe.SizeOf()); + var slice = this.ToSpan(offset, Unsafe.SizeOf()); + Unsafe.WriteUnaligned(ref slice[0], value); + } + + private static double ReverseEndianness(double value) + { + long longValue = Unsafe.As(ref value); + longValue = BinaryPrimitives.ReverseEndianness(longValue); + return BitConverter.Int64BitsToDouble(longValue); + } + + private 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(); } -} -#endif \ No newline at end of file + [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/BigSpanHelpers.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.cs new file mode 100644 index 00000000..0d685448 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.cs @@ -0,0 +1,291 @@ +/* + * 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); +} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt new file mode 100644 index 00000000..e0687b35 --- /dev/null +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt @@ -0,0 +1,99 @@ +/* + * 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); +<# + } +#> +} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs deleted file mode 100644 index 0a77ba97..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/FlatBufferSerializationTargetExtensions.cs +++ /dev/null @@ -1,292 +0,0 @@ -using System.Buffers; -using System.Buffers.Binary; -using System.Runtime.InteropServices; - -namespace FlatSharp; - -/// -/// Extensions for IFlatBufferSerializationTarget -/// -public static class FlatBufferSerializationTargetExtensions -{ - public static void WriteBool(this T target, long offset, bool value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target.WriteUInt8(offset, value ? SerializationHelpers.True : SerializationHelpers.False); - } - - public static void WriteUInt8(this T target, long offset, byte value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target[offset] = value; - } - - public static void WriteInt8(this T target, long offset, sbyte value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - target[offset] = unchecked((byte)value); - } - - public static void WriteUInt16(this T target, long offset, ushort value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(ushort)); - BinaryPrimitives.WriteUInt16LittleEndian( - target.AsSpan(offset, sizeof(ushort)), - value); - } - - public static void WriteInt16(this T target, long offset, short value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(short)); - BinaryPrimitives.WriteInt16LittleEndian( - target.AsSpan(offset, sizeof(short)), - value); - } - - public static void WriteUInt32(this T target, long offset, uint value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(uint)); - BinaryPrimitives.WriteUInt32LittleEndian( - target.AsSpan(offset, sizeof(uint)), - value); - } - - public static void WriteInt32(this T target, long offset, int value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(int)); - BinaryPrimitives.WriteInt32LittleEndian( - target.AsSpan(offset, sizeof(int)), - value); - } - - public static void WriteUInt64(this T target, long offset, ulong value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(ulong)); - BinaryPrimitives.WriteUInt64LittleEndian( - target.AsSpan(offset, sizeof(ulong)), - value); - } - - public static void WriteInt64(this T target, long offset, long value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(long)); - BinaryPrimitives.WriteInt64LittleEndian( - target.AsSpan(offset, sizeof(long)), - value); - } - - - public static void WriteFloat32(this T target, long offset, float value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(float)); - -#if NETSTANDARD - ScalarSpanReader.FloatLayout floatLayout = new ScalarSpanReader.FloatLayout - { - value = value - }; - - target.WriteUInt32(offset, floatLayout.bytes); -#else - BinaryPrimitives.WriteSingleLittleEndian( - target.AsSpan(offset, sizeof(float)), - value); -#endif - } - - public static void WriteFloat64(this T target, long offset, double value) - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - { - CheckAlignment(offset, sizeof(double)); - -#if NETSTANDARD - target.WriteInt64(offset, BitConverter.DoubleToInt64Bits(value)); -#else - BinaryPrimitives.WriteDoubleLittleEndian( - target.AsSpan(offset, sizeof(double)), - value); -#endif - } - - public static void WriteReadOnlyByteMemoryBlock( - this TTarget target, - ReadOnlyMemory memory, - long offset, - SerializationContext ctx) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - int numberOfItems = memory.Length; - long vectorStartOffset = ctx.AllocateVector(itemAlignment: sizeof(byte), numberOfItems, sizePerItem: sizeof(byte)); - - target.WriteUOffset(offset, vectorStartOffset); - target.WriteInt32(vectorStartOffset, numberOfItems); - - memory.Span.CopyTo(target.AsSpan(vectorStartOffset + sizeof(uint), numberOfItems)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnsafeWriteSpan( - this TSerializationTarget target, - Span buffer, - long offset, - int alignment, - SerializationContext ctx) - where TElement : unmanaged - where TSerializationTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - // 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()); - - target.WriteUOffset(offset, vectorStartOffset); - target.WriteInt32(vectorStartOffset, numberOfItems); - - Span destination = target.AsSpan( - vectorStartOffset + sizeof(uint), - checked(numberOfItems * Unsafe.SizeOf())); - - MemoryMarshal.Cast(buffer).CopyTo(destination); - } - - /// - /// Writes the given string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteString( - this TTarget target, - string value, - long offset, - SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - long stringOffset = target.WriteAndProvisionString(value, context); - target.WriteUOffset(offset, stringOffset); - } - - /// - /// Writes the string to the buffer, returning the absolute offset of the string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long WriteAndProvisionString( - this TTarget target, - string value, - SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - 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)); - - Span destination = target.AsSpan(stringStartOffset + 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 - target[stringStartOffset + bytesWritten + sizeof(uint)] = 0; - - // write length - target.WriteInt32(stringStartOffset, bytesWritten); - - // give back unused space. Account for null terminator. - context.Offset -= maxItems - (bytesWritten + 1); - - return stringStartOffset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteUOffset( - this TSerializationTarget target, - long offset, - long secondOffset) - where TSerializationTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - { - checked - { - uint uoffset = (uint)(secondOffset - offset); - target.WriteUInt32(offset, uoffset); - } - } - - [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/IFlatBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs deleted file mode 100644 index 6cc1df32..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/IFlatBufferSerializationTarget.cs +++ /dev/null @@ -1,52 +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. - */ - -namespace FlatSharp; - -/// -/// Represents a target for a FlatBuffer serialization operation. -/// -public interface IFlatBufferSerializationTarget - where T : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif -{ - /// - /// Gets or sets the value at the given index. - /// - byte this[long index] { get; set; } - - /// - /// Gets the length. - /// - long Length { get; } - - /// - /// Slices this object. - /// - T Slice(long start, long length); - - /// - /// Slices this object. - /// - T Slice(long start); - - /// - /// Returns a Span{byte} over the given range. - /// - Span AsSpan(long start, int length); -} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs deleted file mode 100644 index c8f574ad..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/InputBufferSerializationTarget.cs +++ /dev/null @@ -1,67 +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. - */ - -namespace FlatSharp; - -/// -/// A serialization target that wraps an instance of . -/// -public struct InputBufferSerializationTarget : IFlatBufferSerializationTarget> - where TInputBuffer : IInputBuffer -{ - private readonly long start; - private readonly TInputBuffer buffer; - - public InputBufferSerializationTarget(TInputBuffer buffer) - { - this.buffer = buffer; - this.start = 0; - this.Length = buffer.Length; - } - - public InputBufferSerializationTarget(TInputBuffer buffer, long start, long length) - { - this.buffer = buffer; - this.start = start; - this.Length = length; - } - - public byte this[long index] - { - get => this.buffer.ReadByte(this.start + index); - set => this.buffer.GetSpan(this.start + index, sizeof(byte))[0] = value; - } - - public long Length { get; } - - public InputBufferSerializationTarget Slice(long start, long length) - { - return new(this.buffer, this.start + start, length); - } - - public InputBufferSerializationTarget Slice(long start) - { - return new(this.buffer, this.start + start, this.Length - start); - } - - public Span AsSpan(long start, int length) - { - checked - { - return this.buffer.GetSpan(this.start + start, length); - } - } -} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs deleted file mode 100644 index 7fbef796..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/MemorySerializationTarget.cs +++ /dev/null @@ -1,62 +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. - */ - -namespace FlatSharp; - -/// -/// A serialization target that wraps a Memory{byte}. Only supports the 32-bit address space. -/// -public struct MemorySerializationTarget : IFlatBufferSerializationTarget -{ - private readonly Memory memory; - - public MemorySerializationTarget(Memory memory) - { - this.memory = memory; - } - - public byte this[long index] - { - get => this.memory.Span[checked((int)index)]; - set => this.memory.Span[checked((int)index)] = value; - } - - public long Length => this.memory.Length; - - public MemorySerializationTarget Slice(long start, long length) - { - checked - { - return new MemorySerializationTarget(this.memory.Slice((int)start, (int)length)); - } - } - - public MemorySerializationTarget Slice(long start) - { - checked - { - return new MemorySerializationTarget(this.memory.Slice((int)start)); - } - } - - public Span AsSpan(long start, int length) - { - checked - { - return this.memory.Span.Slice((int)start, length); - } - } -} diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs deleted file mode 100644 index f9030c3f..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/SpanSerializationTarget.cs +++ /dev/null @@ -1,73 +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. - */ - -namespace FlatSharp; - -#if NET9_0_OR_GREATER - -/// -/// A serialization target that wraps a Span{byte}. Only supports the 32-bit address space. -/// -public ref struct SpanSerializationTarget : IFlatBufferSerializationTarget -{ - private readonly Span span; - - public SpanSerializationTarget(Span span) - { - this.span = span; - } - - public SpanSerializationTarget(byte[] array) - { - this.span = array.AsSpan(); - } - - public SpanSerializationTarget(Memory memory) - { - this.span = memory.Span; - } - - public byte this[long index] - { - get => this.span[checked((int)index)]; - set => this.span[checked((int)index)] = value; - } - - public long Length => this.span.Length; - - public SpanSerializationTarget Slice(long start, long length) - { - checked - { - return new(this.span.Slice((int)start, (int)length)); - } - } - - public SpanSerializationTarget Slice(long start) - { - return new(this.span.Slice((int)start)); - } - - public Span AsSpan(long start, int length) - { - checked - { - return this.span.Slice((int)start, (int)length); - } - } -} - -#endif \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs deleted file mode 100644 index d0520798..00000000 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/VirtualSerializationTarget.cs +++ /dev/null @@ -1,45 +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.Buffers.Binary; - -namespace FlatSharp; - -internal struct VirtualSerializationTarget : IFlatBufferSerializationTarget -{ - public byte this[long index] - { - get => 0; - set { } - } - - public long Length => long.MaxValue; - - public VirtualSerializationTarget Slice(long start, long length) - { - return this; - } - - public VirtualSerializationTarget Slice(long start) - { - return this; - } - - public Span AsSpan(long start, int length) - { - return default; - } -} \ No newline at end of file diff --git a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs index 127ef29c..58636755 100644 --- a/src/FlatSharp.Runtime/IO/SharedStringWriter.cs +++ b/src/FlatSharp.Runtime/IO/SharedStringWriter.cs @@ -73,15 +73,11 @@ public void Reset() /// /// Writes a shared string. /// - public void WriteSharedString( - TTarget target, + public void WriteSharedString( + BigSpan target, long offset, string value, - SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif + SerializationContext context) { // Find the associative set that must contain our key. var cache = this.sharedStringOffsetCache; @@ -110,13 +106,9 @@ public void WriteSharedString( /// /// Flush any pending writes. /// - public void FlushWrites( - TTarget target, - SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif + public void FlushWrites( + BigSpan target, + SerializationContext context) { var cache = this.sharedStringOffsetCache; for (int i = 0; i < cache.Length; ++i) @@ -137,15 +129,11 @@ public void FlushWrites( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void FlushSharedString( - TTarget target, + private static void FlushSharedString( + BigSpan target, string value, List offsets, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif { long stringOffset = target.WriteAndProvisionString(value, context); int count = offsets.Count; diff --git a/src/FlatSharp.Runtime/IPostSerializeAction.cs b/src/FlatSharp.Runtime/IPostSerializeAction.cs index c2112663..3c1b0346 100644 --- a/src/FlatSharp.Runtime/IPostSerializeAction.cs +++ b/src/FlatSharp.Runtime/IPostSerializeAction.cs @@ -21,10 +21,5 @@ namespace FlatSharp.Internal; /// public interface IPostSerializeAction { - void Invoke(TTarget target, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif - ; + 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 1c69e4a8..e2918bf8 100644 --- a/src/FlatSharp.Runtime/ISerializer.cs +++ b/src/FlatSharp.Runtime/ISerializer.cs @@ -39,12 +39,7 @@ public interface ISerializer /// The destination. /// The object to serialize. /// The number of bytes written. - long Write(TTarget target, object item) - where TTarget : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - ; + long Write(BigSpan target, object item); /// /// Computes the maximum size necessary to serialize the given instance. @@ -81,17 +76,7 @@ public interface ISerializer /// The destination. /// The object to serialize. /// The number of bytes written. - long Write(TTarget destination, T item) - where TTarget : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif - ; - - /// - /// Computes the exact size necessary to serialize the given instance of . - /// - long GetActualSize(T item); + long Write(BigSpan destination, T item); /// /// Computes the maximum size necessary to serialize the given instance of . diff --git a/src/FlatSharp.Runtime/ISerializerExtensions.cs b/src/FlatSharp.Runtime/ISerializerExtensions.cs index 5b7f80dc..fe5bb823 100644 --- a/src/FlatSharp.Runtime/ISerializerExtensions.cs +++ b/src/FlatSharp.Runtime/ISerializerExtensions.cs @@ -118,11 +118,7 @@ public static object Parse(this ISerializer serializer, ReadOnlyMemory dat [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, byte[] buffer, T item) where T : class { -#if NET9_0_OR_GREATER - return serializer.Write(buffer.AsSpan(), item); -#else - return serializer.Write(new ArraySerializationTarget(buffer), item); -#endif + return serializer.Write(new BigSpan(buffer.AsSpan()), item); } /// @@ -132,11 +128,7 @@ public static long Write(this ISerializer serializer, byte[] buffer, T ite [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, byte[] buffer, object item) { -#if NET9_0_OR_GREATER - return serializer.Write(buffer.AsSpan(), item); -#else - return serializer.Write(new ArraySerializationTarget(buffer), item); -#endif + return serializer.Write(new BigSpan(buffer.AsSpan()), item); } /// @@ -146,19 +138,7 @@ public static long Write(this ISerializer serializer, byte[] buffer, object item [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, ArraySegment buffer, T item) where T : class { -#if NET9_0_OR_GREATER - return serializer.Write(buffer.AsSpan(), item); -#else - byte[]? array = buffer.Array; - if (array is null) - { - return FSThrow.ArgumentNull(nameof(buffer)); - } - - return serializer.Write( - new ArraySerializationTarget(array, buffer.Offset, buffer.Count), - item); -#endif + return serializer.Write(new BigSpan(buffer.AsSpan()), item); } /// @@ -168,19 +148,7 @@ public static long Write(this ISerializer serializer, ArraySegment b [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, ArraySegment buffer, object item) { -#if NET9_0_OR_GREATER - return serializer.Write(buffer.AsSpan(), item); -#else - byte[]? array = buffer.Array; - if (array is null) - { - return FSThrow.ArgumentNull(nameof(buffer)); - } - - return serializer.Write( - new ArraySerializationTarget(array, buffer.Offset, buffer.Count), - item); -#endif + return serializer.Write(new BigSpan(buffer.AsSpan()), item); } /// @@ -190,11 +158,7 @@ public static long Write(this ISerializer serializer, ArraySegment buffer, [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, Memory buffer, T item) where T : class { -#if NET9_0_OR_GREATER - return serializer.Write(buffer.Span, item); -#else - return serializer.Write(new MemorySerializationTarget(buffer), item); -#endif + return serializer.Write(new BigSpan(buffer.Span), item); } /// @@ -204,15 +168,9 @@ public static long Write(this ISerializer serializer, Memory buffer, [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, Memory buffer, object item) { -#if NET9_0_OR_GREATER - return serializer.Write(buffer.Span, item); -#else - return serializer.Write(new MemorySerializationTarget(buffer), item); -#endif + return serializer.Write(new BigSpan(buffer.Span), item); } - #if NET9_0_OR_GREATER - /// /// Writes the given item to the given buffer using the default SpanWriter. /// @@ -220,7 +178,7 @@ public static long Write(this ISerializer serializer, Memory buffer, objec [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, Span buffer, T item) where T : class { - return serializer.Write(new SpanSerializationTarget(buffer), item); + return serializer.Write(new BigSpan(buffer), item); } /// @@ -230,10 +188,8 @@ public static long Write(this ISerializer serializer, Span buffer, T [ExcludeFromCodeCoverage] // Just a helper public static long Write(this ISerializer serializer, Span buffer, object item) { - return serializer.Write(new SpanSerializationTarget(buffer), item); + return serializer.Write(new BigSpan(buffer), item); } - - #endif /// /// Writes the given item into the given buffer writer using the default SpanWriter. @@ -247,14 +203,9 @@ public static long Write(this ISerializer serializer, IBufferWriter FSThrow.InvalidData("The data is too large. This overload only supports the 32 bit address space."); } - #if NET9_0_OR_GREATER Span buffer = bufferWriter.GetSpan((int)maxSize); - int bytesWritten = (int)serializer.Write(new SpanSerializationTarget(buffer), item); - #else - Memory buffer = bufferWriter.GetMemory((int)maxSize); - int bytesWritten = (int)serializer.Write(new MemorySerializationTarget(buffer), item); - #endif - + int bytesWritten = (int)serializer.Write(new BigSpan(buffer), item); + bufferWriter.Advance(bytesWritten); return bytesWritten; } @@ -270,15 +221,10 @@ public static int Write(this ISerializer serializer, IBufferWriter bufferW { FSThrow.InvalidData("The data is too large. This overload only supports the 32 bit address space."); } - -#if NET9_0_OR_GREATER + Span buffer = bufferWriter.GetSpan((int)maxSize); - int bytesWritten = (int)serializer.Write(new SpanSerializationTarget(buffer), item); -#else - Memory buffer = bufferWriter.GetMemory((int)maxSize); - int bytesWritten = (int)serializer.Write(new MemorySerializationTarget(buffer), item); -#endif - + 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 0a65a330..faddcf53 100644 --- a/src/FlatSharp.Runtime/SerializationContext.cs +++ b/src/FlatSharp.Runtime/SerializationContext.cs @@ -72,11 +72,7 @@ public void Reset(long capacity) /// Invokes any post-serialize actions. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InvokePostSerializeActions(TTarget target) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif + public void InvokePostSerializeActions(BigSpan target) { var actions = this.postSerializeActions; int count = actions.Count; @@ -149,13 +145,9 @@ public long AllocateSpace(int bytesNeeded, int alignment) } [MethodImpl(MethodImplOptions.NoInlining)] // Common method; don't inline - public long FinishVTable( - TTarget buffer, + public long FinishVTable( + BigSpan buffer, Span vtable) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif { var offsets = this.vtableOffsets; int count = offsets.Count; @@ -164,10 +156,10 @@ public long FinishVTable( { long offset = offsets[i]; - ReadOnlySpan existingVTable = buffer.AsSpan(offset, sizeof(ushort)); + ReadOnlySpan existingVTable = buffer.ToSpan(offset, sizeof(ushort)); ushort vtableLength = BinaryPrimitives.ReadUInt16LittleEndian(existingVTable); - existingVTable = buffer.AsSpan(offset, vtableLength); + existingVTable = buffer.ToSpan(offset, vtableLength); if (existingVTable.SequenceEqual(vtable)) { @@ -183,7 +175,7 @@ public long FinishVTable( // Oh, well. Write the new table. long newVTableOffset = this.AllocateSpace(vtable.Length, sizeof(ushort)); - vtable.CopyTo(buffer.AsSpan(newVTableOffset, vtable.Length)); + vtable.CopyTo(buffer.ToSpan(newVTableOffset, vtable.Length)); offsets.Add(newVTableOffset); // "Insert" this item in the middle of the list. diff --git a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs index 8daeb157..5d234986 100644 --- a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs +++ b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs @@ -45,7 +45,7 @@ public sealed class VectorSortAction : IPostSerializeAction private readonly long vectorUOffset; private readonly int vTableIndex; private readonly int? keyInlineSize; - + public VectorSortAction( long vectorUOffset, int vtableIndex, @@ -74,55 +74,46 @@ public VectorSortAction( /// 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. /// - public void Invoke(TTarget target, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif + public void Invoke(BigSpan buffer, SerializationContext context) { - checked + long vectorStartOffset = + vectorUOffset + buffer.ReadUInt(vectorUOffset); + int vectorLength = (int)buffer.ReadUInt(vectorStartOffset); + long index0Position = vectorStartOffset + sizeof(int); + + (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<(long offset, int length, long tableOffset)> keyOffsets = + vectorLength < 512 + ? 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) { - var buffer = new SerializationTargetInputBuffer(target); - - long vectorStartOffset = - vectorUOffset + buffer.ReadUInt(vectorUOffset); - int vectorLength = (int)buffer.ReadUInt(vectorStartOffset); - long index0Position = vectorStartOffset + sizeof(int); - - (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<(long offset, int length, long tableOffset)> keyOffsets = - vectorLength < 512 - ? 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(target, index0Position, i, this.vTableIndex, keyInlineSize); - } + keyOffsets[i] = GetKeyOffset(buffer, index0Position, i, this.vTableIndex, keyInlineSize); + } - // Sort the offsets. - IntroSort(target, comparer, 0, vectorLength - 1, keyOffsets); + // Sort the offsets. + IntroSort(buffer, comparer, 0, vectorLength - 1, keyOffsets); - // 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 = target.AsSpan(index0Position, sizeof(uint) * vectorLength); - long nextPosition = index0Position; - for (int i = 0; i < keyOffsets.Length; ++i) - { - (_, _, long tableOffset) = keyOffsets[i]; - BinaryPrimitives.WriteUInt32LittleEndian(boundedVector.Slice(sizeof(uint) * i), - (uint)(tableOffset - nextPosition)); - nextPosition += sizeof(uint); - } + // 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.ToSpan(index0Position, sizeof(uint) * vectorLength); + long nextPosition = index0Position; + for (int i = 0; i < keyOffsets.Length; ++i) + { + (_, _, long tableOffset) = keyOffsets[i]; + BinaryPrimitives.WriteUInt32LittleEndian(boundedVector.Slice(sizeof(uint) * i), + (uint)(tableOffset - nextPosition)); + nextPosition += sizeof(uint); + } - if (pooledArray is not null) - { - ArrayPool<(long, int, long)>.Shared.Return(pooledArray); - } + if (pooledArray is not null) + { + ArrayPool<(long, int, long)>.Shared.Return(pooledArray); } } @@ -131,16 +122,12 @@ public void Invoke(TTarget target, SerializationContext context) /// 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( - TTarget buffer, + private static void IntroSort( + BigSpan buffer, TSpanComparer keyComparer, int lo, int hi, Span<(long offset, int length, long tableOffset)> keyLocations) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif { checked { @@ -184,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.AsSpan(pivotOffset, pivotLength); + var pivotSpan = buffer.ToSpan(pivotOffset, pivotLength); // Partition int num2 = lo; @@ -194,7 +181,7 @@ private static void IntroSort( while (true) { var (keyOffset, keyLength, _) = keyLocations[++num2]; - var keySpan = buffer.AsSpan(keyOffset, keyLength); + var keySpan = buffer.ToSpan(keyOffset, keyLength); if (keyComparer.Compare(keyOffset != 0, keySpan, pivotExists, pivotSpan) >= 0) { break; @@ -204,7 +191,7 @@ private static void IntroSort( while (true) { var (keyOffset, keyLength, _) = keyLocations[--num3]; - var keySpan = buffer.AsSpan(keyOffset, keyLength); + var keySpan = buffer.ToSpan(keyOffset, keyLength); if (keyComparer.Compare(pivotExists, pivotSpan, keyOffset != 0, keySpan) >= 0) { break; @@ -231,28 +218,24 @@ private static void IntroSort( } } - private static void InsertionSort( - TTarget buffer, + private static void InsertionSort( + BigSpan buffer, TSpanComparer comparer, int lo, int hi, Span<(long offset, int length, long tableOffset)> keyLocations) - where TTarget : IFlatBufferSerializationTarget -#if NET9_0_OR_GREATER - , allows ref struct -#endif { for (int i = lo; i < hi; i++) { int num = i; var valTuple = keyLocations[i + 1]; - ReadOnlySpan valSpan = buffer.AsSpan(valTuple.offset, valTuple.length); + ReadOnlySpan valSpan = buffer.ToSpan(valTuple.offset, valTuple.length); while (num >= lo) { (long keyOffset, int keyLength, _) = keyLocations[num]; - ReadOnlySpan keySpan = buffer.AsSpan(keyOffset, keyLength); + ReadOnlySpan keySpan = buffer.ToSpan(keyOffset, keyLength); if (comparer.Compare(valTuple.offset != 0, valSpan, keyOffset != 0, keySpan) < 0) { @@ -269,16 +252,12 @@ private static void InsertionSort( } } - private static void SwapIfGreater( - TTarget target, + private static void SwapIfGreater( + BigSpan target, TSpanComparer comparer, int leftIndex, int rightIndex, Span<(long, int, long)> keyOffsets) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif { (long leftOffset, int leftLength, _) = keyOffsets[leftIndex]; (long rightOffset, int rightLength, _) = keyOffsets[rightIndex]; @@ -286,8 +265,8 @@ private static void SwapIfGreater( bool leftExists = leftOffset != 0; bool rightExists = rightOffset != 0; - var leftSpan = target.AsSpan(leftOffset, leftLength); - var rightSpan = target.AsSpan(rightOffset, rightLength); + var leftSpan = target.ToSpan(leftOffset, leftLength); + var rightSpan = target.ToSpan(rightOffset, rightLength); if (comparer.Compare(leftExists, leftSpan, rightExists, rightSpan) > 0) { @@ -313,52 +292,43 @@ private static void SwapVectorPositions(int leftIndex, int rightIndex, Span<(lon /// /// Left as unchecked since this is a sort operation (not a search). /// - private static (long offset, int length, long tableOffset) GetKeyOffset( - TTarget target, + private static (long offset, int length, long tableOffset) GetKeyOffset( + BigSpan buffer, long index0Position, int vectorIndex, int vtableIndex, int? inlineItemSize) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif { - checked - { - var buffer = new SerializationTargetInputBuffer(target); - - // Find offset to the table at the index. - long tableOffset = index0Position + (sizeof(uint) * vectorIndex); - tableOffset += (int)buffer.ReadUInt(tableOffset); + // Find offset to the table at the index. + long tableOffset = index0Position + (sizeof(uint) * vectorIndex); + tableOffset += (int)buffer.ReadUInt(tableOffset); - // Consult the vtable. - long vtableOffset = tableOffset - buffer.ReadInt(tableOffset); + // Consult the vtable. + 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 = (buffer.ReadUShort(vtableOffset) / 2) - 2; - - if (vtableIndex >= vtableLengthEntries) - { - return (0, 0, tableOffset); - } + // Vtables have two extra entries: vtable length and table length. The number of entries is vtableLengthBytes / 2 - 2 + int vtableLengthEntries = (buffer.ReadUShort(vtableOffset) / 2) - 2; - // Absolute offset of the field within the table. - long fieldOffset = tableOffset + buffer.ReadUShort(vtableOffset + 2 * (2 + vtableIndex)); - if (inlineItemSize is not null) - { - return (fieldOffset, inlineItemSize.Value, tableOffset); - } + if (vtableIndex >= vtableLengthEntries) + { + return (0, 0, tableOffset); + } - if (fieldOffset == 0) - { - return (0, 0, tableOffset); - } + // Absolute offset of the field within the table. + long fieldOffset = tableOffset + buffer.ReadUShort(vtableOffset + 2 * (2 + vtableIndex)); + if (inlineItemSize is not null) + { + return (fieldOffset, inlineItemSize.Value, tableOffset); + } - // Strings are stored as a uoffset reference. Follow the indirection one more time. - long uoffsetToString = fieldOffset + (int)buffer.ReadUInt(fieldOffset); - int stringLength = (int)buffer.ReadUInt(uoffsetToString); - return (uoffsetToString + sizeof(uint), stringLength, tableOffset); + if (fieldOffset == 0) + { + return (0, 0, tableOffset); } + + // Strings are stored as a uoffset reference. Follow the indirection one more time. + long uoffsetToString = fieldOffset + (int)buffer.ReadUInt(fieldOffset); + int stringLength = (int)buffer.ReadUInt(uoffsetToString); + return (uoffsetToString + sizeof(uint), stringLength, tableOffset); } } diff --git a/src/FlatSharp/FlatBufferSerializer.cs b/src/FlatSharp/FlatBufferSerializer.cs index 8f379f35..af5bfb9f 100644 --- a/src/FlatSharp/FlatBufferSerializer.cs +++ b/src/FlatSharp/FlatBufferSerializer.cs @@ -174,12 +174,8 @@ 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 long Serialize(T item, TTarget destination) + public long Serialize(T item, BigSpan destination) where T : class - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif { return this.GetOrCreateTypedSerializer().Write(destination, item); } diff --git a/src/FlatSharp/FlatBufferVectorHelpers.cs b/src/FlatSharp/FlatBufferVectorHelpers.cs index d1dcfa81..4191753b 100644 --- a/src/FlatSharp/FlatBufferVectorHelpers.cs +++ b/src/FlatSharp/FlatBufferVectorHelpers.cs @@ -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)}(); }} - long offset = this.offset + ({GetEfficientMultiply(inlineSize, "index")}); - var data = new InputBufferSerializationTarget<{context.InputBufferTypeName}>({context.InputBufferVariableName}, offset, {inlineSize}); - + long itemOffset = this.offset + ({GetEfficientMultiply(inlineSize, "index")}); + var data = {context.InputBufferVariableName}.GetSpan(); {serializeContext.GetSerializeInvocation(itemTypeModel.ClrType)}; "; } diff --git a/src/FlatSharp/FlatSharp.csproj b/src/FlatSharp/FlatSharp.csproj index 01803792..ec9baf71 100644 --- a/src/FlatSharp/FlatSharp.csproj +++ b/src/FlatSharp/FlatSharp.csproj @@ -51,4 +51,8 @@ True + + + + diff --git a/src/FlatSharp/Serialization/DeserializeClassDefinition.cs b/src/FlatSharp/Serialization/DeserializeClassDefinition.cs index 6abd5d55..3804a55b 100644 --- a/src/FlatSharp/Serialization/DeserializeClassDefinition.cs +++ b/src/FlatSharp/Serialization/DeserializeClassDefinition.cs @@ -163,7 +163,7 @@ private void AddReadMethod( private void AddWriteThroughMethod(ItemMemberModel itemModel, ParserCodeGenContext parserContext) { var context = parserContext.GetWriteThroughContext( - $"target", + $"destination", "value", "offset"); @@ -175,7 +175,7 @@ private void AddWriteThroughMethod(ItemMemberModel itemModel, ParserCodeGenConte {itemModel.GetNullableAnnotationTypeName(this.typeModel.SchemaType)} value, {this.vtableTypeName} vtable) {{ - var target = new InputBufferSerializationTarget<{parserContext.InputBufferTypeName}>(buffer); + var destination = buffer.{nameof(IInputBuffer.GetSpan)}(); {itemModel.CreateWriteThroughBody(context, "vtable")} }}"); } diff --git a/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs b/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs index d52af7b8..056a02e5 100644 --- a/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs +++ b/src/FlatSharp/Serialization/RoslynSerializerGenerator.cs @@ -423,23 +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(ref TTarget target, {CSharpHelpers.GetGlobalCompilableTypeName(rootType)} root, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if {CSharpHelpers.Net9PreprocessorVariable} - , allows ref struct - #endif + public void Write({typeof(BigSpan).GGCTN()} destination, {rootType.GGCTN()} root, SerializationContext context) {{ {writeFileId} - {parts.@namespace}.{parts.className}.{parts.methodName}(ref target, root, 0, context); + {parts.@namespace}.{parts.className}.{parts.methodName}(destination, root, 0, context); }} "; bodyParts.Add(methodText); @@ -566,7 +562,7 @@ internal string ImplementHelperClass(ITypeModel typeModel, IEnumerable( - ref TTarget {context.TargetVariableName}, + 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 TTarget : IFlatBufferSerializationTarget - #if {CSharpHelpers.Net9PreprocessorVariable} - , allows ref struct - #endif + {tableFieldContextParameter}) {{ {method.MethodBody} }} diff --git a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs index 4e89c15b..df6dce05 100644 --- a/src/FlatSharp/Serialization/SerializationCodeGenContext.cs +++ b/src/FlatSharp/Serialization/SerializationCodeGenContext.cs @@ -26,7 +26,7 @@ public record SerializationCodeGenContext { public SerializationCodeGenContext( string serializationContextVariableName, - string targetVariableName, + string spanVariableName, string valueVariableName, string offsetVariableName, string tableFieldContextVariableName, @@ -36,7 +36,7 @@ public SerializationCodeGenContext( IReadOnlyDictionary> allFieldContexts) { this.SerializationContextVariableName = serializationContextVariableName; - this.TargetVariableName = targetVariableName; + this.SpanVariableName = spanVariableName; this.ValueVariableName = valueVariableName; this.OffsetVariableName = offsetVariableName; this.TypeModelContainer = typeModelContainer; @@ -57,9 +57,9 @@ public SerializationCodeGenContext( public string TableFieldContextVariableName { get; init; } /// - /// The variable name of the span. Represents a reference. + /// The variable name of the span. Represents a reference. /// - public string TargetVariableName { get; init; } + public string SpanVariableName { get; init; } /// /// The variable name of the current value to serialize. @@ -107,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}(ref {this.TargetVariableName}, {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/ScalarTypeModel.cs b/src/FlatSharp/TypeModel/ScalarTypeModel.cs index 7108fab3..536dec55 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModel.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModel.cs @@ -73,6 +73,16 @@ internal ScalarTypeModel( /// public override bool SerializesInline => true; + /// + /// Scalars are of fixed size. + /// + public override bool IsFixedSize => true; + + /// + /// Scalars don't need a serialization context. + /// + public override bool SerializeMethodRequiresContext => false; + /// /// Scalars are leaf nodes. /// @@ -125,7 +135,7 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeGenContext context) { string variableName = context.ValueVariableName; - return new CodeGeneratedMethod($"{this.WriteMethodName}({context.TargetVariableName}, {variableName});") + return new CodeGeneratedMethod($"{context.SpanVariableName}.{this.WriteMethodName}({context.OffsetVariableName}, {variableName});") { IsMethodInline = true, }; diff --git a/src/FlatSharp/TypeModel/ScalarTypeModels.cs b/src/FlatSharp/TypeModel/ScalarTypeModels.cs index 0755eb19..26dd90ce 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.cs @@ -37,7 +37,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadBool"; - protected override string WriteMethodName => "SerializationHelpers.WriteBool"; + protected override string WriteMethodName => "WriteBool"; protected override string TypeNameAlias => "bool"; @@ -66,7 +66,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadByte"; - protected override string WriteMethodName => "SerializationHelpers.WriteUInt8"; + protected override string WriteMethodName => "WriteByte"; protected override string TypeNameAlias => "byte"; @@ -95,7 +95,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadSByte"; - protected override string WriteMethodName => "SerializationHelpers.WriteInt8"; + protected override string WriteMethodName => "WriteSByte"; protected override string TypeNameAlias => "sbyte"; @@ -124,7 +124,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadUShort"; - protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt16LittleEndian"; + protected override string WriteMethodName => "WriteUShort"; protected override string TypeNameAlias => "ushort"; @@ -153,7 +153,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadShort"; - protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteInt16LittleEndian"; + protected override string WriteMethodName => "WriteShort"; protected override string TypeNameAlias => "short"; @@ -182,7 +182,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadInt"; - protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian"; + protected override string WriteMethodName => "WriteInt"; protected override string TypeNameAlias => "int"; @@ -211,7 +211,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadUInt"; - protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian"; + protected override string WriteMethodName => "WriteUInt"; protected override string TypeNameAlias => "uint"; @@ -240,7 +240,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadLong"; - protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteInt64LittleEndian"; + protected override string WriteMethodName => "WriteLong"; protected override string TypeNameAlias => "long"; @@ -269,7 +269,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadULong"; - protected override string WriteMethodName => "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt64LittleEndian"; + protected override string WriteMethodName => "WriteULong"; protected override string TypeNameAlias => "ulong"; @@ -298,7 +298,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadFloat"; - protected override string WriteMethodName => "SerializationHelpers.WriteFloat32"; + protected override string WriteMethodName => "WriteFloat"; protected override string TypeNameAlias => "float"; @@ -327,7 +327,7 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string InputBufferReadMethodName => "ReadDouble"; - protected override string WriteMethodName => "SerializationHelpers.WriteFloat64"; + protected override string WriteMethodName => "WriteDouble"; protected override string TypeNameAlias => "double"; diff --git a/src/FlatSharp/TypeModel/ScalarTypeModels.tt b/src/FlatSharp/TypeModel/ScalarTypeModels.tt index 624597bb..eef8c38a 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.tt +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.tt @@ -26,17 +26,17 @@ ( string casedName, string commonName, string typeName, string literalSpecifier, string[] fbsAliases, string inlineWriteMethod)[] types = { - ("Bool", "Bool", "bool", "", new[] { "bool" }, "SerializationHelpers.WriteBool"), - ("UInt8", "Byte", "byte", "", new[] { "ubyte", "uint8" }, "SerializationHelpers.WriteUInt8"), - ("Int8", "SByte", "sbyte", "", new[] { "byte", "int8" }, "SerializationHelpers.WriteInt8"), - ("UInt16", "UShort", "ushort", "", new[] { "ushort", "uint16" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt16LittleEndian"), - ("Int16", "Short", "short", "", new[] { "short", "int16" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteInt16LittleEndian"), - ("Int32", "Int", "int", "", new[] { "int", "int32" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian"), - ("UInt32", "UInt", "uint", "u", new[] { "uint", "uint32" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian"), - ("Int64", "Long", "long", "L", new[] { "long", "int64" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteInt64LittleEndian"), - ("UInt64", "ULong", "ulong", "ul", new[] { "ulong", "uint64" }, "global::System.Buffers.Binary.BinaryPrimitives.WriteUInt64LittleEndian"), - ("Float32", "Float", "float", "f", new[] { "float", "float32" }, "SerializationHelpers.WriteFloat32"), - ("Float64", "Double", "double", "d", new[] { "double", "float64" }, "SerializationHelpers.WriteFloat64"), + ("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"), }; #> diff --git a/src/FlatSharp/TypeModel/StringTypeModel.cs b/src/FlatSharp/TypeModel/StringTypeModel.cs index 7a85891e..2eef29c2 100644 --- a/src/FlatSharp/TypeModel/StringTypeModel.cs +++ b/src/FlatSharp/TypeModel/StringTypeModel.cs @@ -114,7 +114,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG if (!(sharedStringWriter is null)) {{ sharedStringWriter.{nameof(ISharedStringWriter.WriteSharedString)}( - {context.TargetVariableName}, + {context.SpanVariableName}, {context.OffsetVariableName}, {context.ValueVariableName}, {context.SerializationContextVariableName}); @@ -122,7 +122,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG }} }} - {context.TargetVariableName}.WriteString( + {context.SpanVariableName}.WriteString( {context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName}); @@ -132,7 +132,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG { // otherwise, we can omit that code entirely. body = $@" - {context.TargetVariableName}.WriteString( + {context.SpanVariableName}.WriteString( {context.ValueVariableName}, {context.OffsetVariableName}, {context.SerializationContextVariableName}); diff --git a/src/FlatSharp/TypeModel/StructTypeModel.cs b/src/FlatSharp/TypeModel/StructTypeModel.cs index 4cae60d3..58c7a69b 100644 --- a/src/FlatSharp/TypeModel/StructTypeModel.cs +++ b/src/FlatSharp/TypeModel/StructTypeModel.cs @@ -136,13 +136,13 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG List body = new List(); - body.Add($"var scopedTarget = {context.TargetVariableName}.Slice({context.OffsetVariableName}, {this.PhysicalLayout[0].InlineSize});"); + body.Add($"var scopedTarget = {context.SpanVariableName}.Slice({context.OffsetVariableName}, {this.PhysicalLayout[0].InlineSize});"); body.Add( $@" if ({context.ValueVariableName} is null) {{ - Span scopedSpan = scopedTarget.AsSpan(0, {this.PhysicalLayout[0].InlineSize}); + Span scopedSpan = scopedTarget.{nameof(BigSpan.ToSpan)}(0, {this.PhysicalLayout[0].InlineSize}); scopedSpan.Clear(); return; }} @@ -160,7 +160,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG var propContext = context with { - TargetVariableName = "scopedTarget", + SpanVariableName = "scopedTarget", OffsetVariableName = $"{memberInfo.Offset}", ValueVariableName = $"{propertyAccessor}" }; diff --git a/src/FlatSharp/TypeModel/TableTypeModel.cs b/src/FlatSharp/TypeModel/TableTypeModel.cs index 9a630940..2b9e870e 100644 --- a/src/FlatSharp/TypeModel/TableTypeModel.cs +++ b/src/FlatSharp/TypeModel/TableTypeModel.cs @@ -473,7 +473,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string methodStart = $@" long tableStart = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateSpace)}({maxInlineSize}, sizeof(int)); - {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}, tableStart); + {context.SpanVariableName}.WriteUOffset({context.OffsetVariableName}, tableStart); long currentOffset = tableStart + sizeof(int); // skip past vtable soffset_t. int vtableLength = {minVtableLength}; @@ -503,8 +503,8 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG body.Add($"{typeof(BinaryPrimitives).GGCTN()}.WriteUInt16LittleEndian(vtable, (ushort)vtableLength);"); body.Add($"{typeof(BinaryPrimitives).GGCTN()}.WriteUInt16LittleEndian(vtable.Slice(sizeof(ushort)), (ushort)tableLength);"); - body.Add($"long vtablePosition = {context.SerializationContextVariableName}.{nameof(SerializationContext.FinishVTable)}({context.TargetVariableName}, vtable.Slice(0, vtableLength));"); - body.Add($"{context.TargetVariableName}.WriteInt32(tableStart, checked((int)(tableStart - vtablePosition)));"); + body.Add($"long vtablePosition = {context.SerializationContextVariableName}.{nameof(SerializationContext.FinishVTable)}({context.SpanVariableName}, vtable.Slice(0, vtableLength));"); + body.Add($"{context.SpanVariableName}.WriteInt(tableStart, checked((int)(tableStart - vtablePosition)));"); body.AddRange(writeBlocks); diff --git a/src/FlatSharp/TypeModel/UnionTypeModel.cs b/src/FlatSharp/TypeModel/UnionTypeModel.cs index 7102e457..7e570452 100644 --- a/src/FlatSharp/TypeModel/UnionTypeModel.cs +++ b/src/FlatSharp/TypeModel/UnionTypeModel.cs @@ -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.TargetVariableName}.WriteUOffset({context.OffsetVariableName}.offset1, writeOffset);"; + {context.SpanVariableName}.WriteUOffset({context.OffsetVariableName}.offset1, writeOffset);"; } else { @@ -210,7 +210,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string serializeBlock = $@" byte discriminatorValue = {context.ValueVariableName}.Discriminator; - {context.TargetVariableName}.WriteUInt8( + {context.SpanVariableName}.{nameof(BigSpan.WriteByte)}( {context.OffsetVariableName}.offset0, discriminatorValue); diff --git a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs index 674996ad..c834eb1e 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}.GetReadOnlySpan({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 { - TargetVariableName = "sizedTarget", + SpanVariableName = "sizedTarget", OffsetVariableName = $"{member.offset}", ValueVariableName = $"{context.ValueVariableName}.{member.accessor}", }; @@ -177,7 +177,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG } string body; - string slice = $"var sizedTarget = {context.TargetVariableName}.Slice({context.OffsetVariableName}, {this.inlineSize});"; + string slice = $"var sizedTarget = {context.SpanVariableName}.Slice({context.OffsetVariableName}, {this.inlineSize});"; if (this.CanMarshalOnSerialize && context.Options.EnableValueStructMemoryMarshalDeserialization) { body = $@" @@ -186,7 +186,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG {StrykerSuppressor.SuppressNextLine("boolean")} if ({StrykerSuppressor.BitConverterTypeName}.IsLittleEndian) {{ - var sizedSpan = sizedTarget.AsSpan(0, {this.inlineSize}); + var sizedSpan = sizedTarget.ToSpan(0, {this.inlineSize}); #if {CSharpHelpers.Net8PreprocessorVariable} {typeof(MemoryMarshal).GetGlobalCompilableTypeName()}.Write(sizedSpan, in {context.ValueVariableName}); #else @@ -215,7 +215,7 @@ private CodeGeneratedMethod CreateExternalSerializeMethod(SerializationCodeGenCo string body = $@" FlatSharpInternal.AssertLittleEndian(); FlatSharpInternal.AssertSizeOf<{globalName}>({this.inlineSize}); - Span sizedSpan = {context.TargetVariableName}.AsSpan({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}); @@ -234,7 +234,7 @@ private CodeGeneratedMethod CreateExternalParseMethod(ParserCodeGenContext conte string body = $@" FlatSharpInternal.AssertLittleEndian(); FlatSharpInternal.AssertSizeOf<{globalName}>({this.inlineSize}); - var slice = {context.InputBufferVariableName}.GetReadOnlySpan({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 8548307d..0655cf7c 100644 --- a/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs +++ b/src/FlatSharp/TypeModel/Vectors/BaseVectorTypeModel.cs @@ -147,8 +147,8 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string body = $@" int count = {context.ValueVariableName}.{this.LengthPropertyName}; long vectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}({itemTypeModel.PhysicalLayout[0].Alignment}, count, {this.PaddedMemberInlineSize}); - {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}, vectorOffset); - {context.TargetVariableName}.WriteInt32(vectorOffset, count); + {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 5e28c248..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.TargetVariableName}.WriteReadOnlyByteMemoryBlock({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 9c7aa57e..f8f21cce 100644 --- a/src/FlatSharp/TypeModel/Vectors/UnityNativeArrayVectorTypeModel.cs +++ b/src/FlatSharp/TypeModel/Vectors/UnityNativeArrayVectorTypeModel.cs @@ -94,7 +94,7 @@ public override CodeGeneratedMethod CreateParseMethodBody(ParserCodeGenContext c public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeGenContext context) { string writeNativeArray = $@" - {context.TargetVariableName}.UnsafeWriteSpan( + {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 e5f3e1fe..58694a63 100644 --- a/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs +++ b/src/FlatSharp/TypeModel/VectorsOfUnion/BaseVectorOfUnionTypeModel.cs @@ -123,13 +123,13 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG string body = $@" int count = {context.ValueVariableName}.{this.LengthPropertyName}; long discriminatorVectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}(sizeof(byte), count, sizeof(byte)); - {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}.offset0, discriminatorVectorOffset); - {context.TargetVariableName}.WriteInt32(discriminatorVectorOffset, count); + {context.SpanVariableName}.{nameof(BigSpan.WriteUOffset)}({context.OffsetVariableName}.offset0, discriminatorVectorOffset); + {context.SpanVariableName}.{nameof(BigSpan.WriteInt)}(discriminatorVectorOffset, count); discriminatorVectorOffset += sizeof(int); long offsetVectorOffset = {context.SerializationContextVariableName}.{nameof(SerializationContext.AllocateVector)}(sizeof(int), count, sizeof(int)); - {context.TargetVariableName}.WriteUOffset({context.OffsetVariableName}.offset1, offsetVectorOffset); - {context.TargetVariableName}.WriteInt32(offsetVectorOffset, count); + {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 4d02d9ee..d3580068 100644 --- a/src/Tests/CompileTests/NativeAot/Program.cs +++ b/src/Tests/CompileTests/NativeAot/Program.cs @@ -333,14 +333,14 @@ public ReadOnlyMemory GetReadOnlyMemory(long start, int length) return inner.GetReadOnlyMemory(start, length); } - public ReadOnlySpan GetReadOnlySpan(long start, int length) + public BigReadOnlySpan GetReadOnlySpan() { - return inner.GetReadOnlySpan(start, length); + return inner.GetReadOnlySpan(); } - public Span GetSpan(long start, int length) + public BigSpan GetSpan() { - return inner.GetSpan(start, length); + return inner.GetSpan(); } } } 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/SerializerConfigurationTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs index 68c19083..a678543e 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs @@ -187,11 +187,7 @@ public void Reset() this.offsets.Clear(); } - public void WriteSharedString(TTarget spanWriter, long offset, string value, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif + public void WriteSharedString(BigSpan spanWriter, long offset, string value, SerializationContext context) { if (!this.offsets.TryGetValue(value, out var list)) { @@ -202,11 +198,7 @@ public void WriteSharedString(TTarget spanWriter, long offset, string v list.Add(offset); } - public void FlushWrites(TTarget writer, SerializationContext context) - where TTarget : IFlatBufferSerializationTarget - #if NET9_0_OR_GREATER - , allows ref struct - #endif + public void FlushWrites(BigSpan writer, SerializationContext context) { foreach (var kvp in this.offsets) { diff --git a/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs b/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs index a866815a..2180bdf7 100644 --- a/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs @@ -222,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(0, (int)inputBuffer.Length).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(0, 1)); + Assert.ThrowsException(() => inputBuffer.GetSpan().ToSpan(0, 1)); Assert.ThrowsException(() => inputBuffer.GetMemory(0, 1)); } else { Assert.IsTrue(inputBuffer.GetMemory(0, (int)inputBuffer.Length).Span.SequenceEqual(buffer)); - Assert.IsTrue(inputBuffer.GetSpan(0, (int)inputBuffer.Length).SequenceEqual(buffer)); + Assert.IsTrue(inputBuffer.GetSpan().ToSpan(0, (int)inputBuffer.Length).SequenceEqual(buffer)); Assert.IsTrue(inputBuffer.ReadByteMemoryBlock(0).Span.SequenceEqual(expected)); } } From 4987cd71cb615c547bcb79d980877a95dc9b7659 Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sat, 16 Nov 2024 12:08:55 -0800 Subject: [PATCH 08/13] Fix sorting perf --- .../IO/SerializationTarget/BigSpan.cs | 20 ++++---- .../SortedVectorHelpersInternal.cs | 48 +++++++++++-------- .../TypeModel/ValueStructTypeModel.cs | 12 ++--- 3 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs index 9bb09c09..2377321a 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs @@ -89,7 +89,10 @@ public ref byte this[long index] [MethodImpl(MethodImplOptions.AggressiveInlining)] public BigSpan Slice(long start, long length) { - if ((length | start) < 0 || start + length > this.Length) + bool isOutOfRange = (length | start) < 0; + isOutOfRange |= (ulong)(start + length) > (ulong)this.Length; + + if (isOutOfRange) { ThrowOutOfRange(); } @@ -104,7 +107,7 @@ public BigSpan Slice(long start, long length) [MethodImpl(MethodImplOptions.AggressiveInlining)] public BigSpan Slice(long start) { - if (start > this.Length) + if ((ulong)start > (ulong)this.Length) { ThrowOutOfRange(); } @@ -119,7 +122,8 @@ public BigSpan Slice(long start) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span ToSpan(long start, int length) { - if (length < 0 || start < 0 || start + length > this.Length) + long sum = start + (long)length; + if ((ulong)sum > (ulong)this.Length) { ThrowOutOfRange(); } @@ -199,6 +203,7 @@ public void WriteString( /// /// Writes the string to the buffer, returning the absolute offset of the string. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public long WriteAndProvisionString( string value, SerializationContext context) @@ -238,14 +243,11 @@ public void WriteUOffset( long offset, long secondOffset) { - checked - { - uint uoffset = (uint)(secondOffset - offset); - this.WriteUInt(offset, uoffset); - } + long difference = secondOffset - offset; + uint uoffset = checked((uint)difference); + this.WriteUInt(offset, uoffset); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private T ReadUnaligned(long offset) where T : unmanaged diff --git a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs index 5d234986..3128d839 100644 --- a/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs +++ b/src/FlatSharp.Runtime/SortedVectorHelpersInternal.cs @@ -101,13 +101,13 @@ public void Invoke(BigSpan buffer, SerializationContext context) // 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.ToSpan(index0Position, sizeof(uint) * vectorLength); + BigSpan boundedVector = buffer.Slice(index0Position, sizeof(uint) * vectorLength); long nextPosition = index0Position; for (int i = 0; i < keyOffsets.Length; ++i) { - (_, _, long 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); } @@ -180,9 +180,9 @@ private static void IntroSort( { while (true) { - var (keyOffset, keyLength, _) = keyLocations[++num2]; - var keySpan = buffer.ToSpan(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; } @@ -190,9 +190,9 @@ private static void IntroSort( while (true) { - var (keyOffset, keyLength, _) = keyLocations[--num3]; - var keySpan = buffer.ToSpan(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; } @@ -234,10 +234,10 @@ private static void InsertionSort( while (num >= lo) { - (long keyOffset, int keyLength, _) = keyLocations[num]; - ReadOnlySpan keySpan = buffer.ToSpan(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--; @@ -259,14 +259,24 @@ private static void SwapIfGreater( int rightIndex, Span<(long, int, long)> keyOffsets) { - (long leftOffset, int leftLength, _) = keyOffsets[leftIndex]; - (long 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 = target.ToSpan(leftOffset, leftLength); - var rightSpan = target.ToSpan(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) { diff --git a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs index c834eb1e..284dd2cb 100644 --- a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs +++ b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs @@ -181,20 +181,20 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG if (this.CanMarshalOnSerialize && context.Options.EnableValueStructMemoryMarshalDeserialization) { body = $@" - {slice} - {StrykerSuppressor.SuppressNextLine("boolean")} if ({StrykerSuppressor.BitConverterTypeName}.IsLittleEndian) {{ - var sizedSpan = sizedTarget.ToSpan(0, {this.inlineSize}); + 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)} }} "; @@ -224,7 +224,7 @@ private CodeGeneratedMethod CreateExternalSerializeMethod(SerializationCodeGenCo #endif "; - return new CodeGeneratedMethod(body) { IsMethodInline = true }; + return new CodeGeneratedMethod(body); } private CodeGeneratedMethod CreateExternalParseMethod(ParserCodeGenContext context) From 2ad57fb0b7fc4d7b6dec5bb1c5a2122211a8aa71 Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sun, 17 Nov 2024 13:17:02 -0800 Subject: [PATCH 09/13] Tweaks --- src/Benchmarks/Benchmark/Program.cs | 4 ++-- .../IO/SerializationTarget/BigSpan.cs | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Benchmarks/Benchmark/Program.cs b/src/Benchmarks/Benchmark/Program.cs index 5f9849e9..83f64b97 100644 --- a/src/Benchmarks/Benchmark/Program.cs +++ b/src/Benchmarks/Benchmark/Program.cs @@ -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/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs index 2377321a..64e29d54 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs @@ -214,7 +214,8 @@ public long WriteAndProvisionString( int maxItems = encoding.GetMaxByteCount(value.Length) + 1; long stringStartOffset = context.AllocateVector(sizeof(byte), maxItems, sizeof(byte)); - Span destination = this.ToSpan(stringStartOffset + sizeof(uint), maxItems); + BigSpan scopedThis = this.Slice(stringStartOffset, maxItems + sizeof(uint)); + Span destination = scopedThis.ToSpan(sizeof(uint), maxItems); #if NETSTANDARD2_0 int length = value.Length; @@ -227,10 +228,10 @@ public long WriteAndProvisionString( #endif // null teriminator - this[stringStartOffset + bytesWritten + sizeof(uint)] = 0; + scopedThis.WriteUnalignedUnsafe(sizeof(uint) + bytesWritten, 0); // write length - this.WriteInt(stringStartOffset, bytesWritten); + scopedThis.WriteUnalignedUnsafe(0, bytesWritten); // give back unused space. Account for null terminator. context.Offset -= maxItems - (bytesWritten + 1); @@ -262,8 +263,19 @@ private void WriteUnaligned(long offset, T value) where T : unmanaged { CheckAlignment(offset, Unsafe.SizeOf()); + var slice = this.ToSpan(offset, Unsafe.SizeOf()); + this.WriteUnalignedUnsafe(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteUnalignedUnsafe(long offset, T value) + { +#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 } private static double ReverseEndianness(double value) From f80c8796043669df5cbd48a86af571b673ef5b7d Mon Sep 17 00:00:00 2001 From: James Courtney Date: Thu, 21 Nov 2024 09:24:02 -0800 Subject: [PATCH 10/13] temp --- src/Benchmarks/Benchmark/Benchmark.csproj | 2 +- src/Benchmarks/Benchmark/Program.cs | 2 +- .../MicroBench.Current/Constants.cs | 18 + src/Benchmarks/MicroBench.Current/Program.cs | 4 +- .../MicroBench.Current/SerializeBenchmarks.cs | 6 + src/Benchmarks/Microbench.fbs | 5 +- .../IO/InputBuffer/InputBufferExtensions.cs | 7 +- .../IO/SerializationTarget/BigReadOnlySpan.cs | 18 +- .../IO/SerializationTarget/BigSpan.cs | 56 +- .../SerializationTarget/BigSpanExtensions.cs | 42 ++ .../IO/SerializationTarget/BigSpanHelpers.cs | 671 +++++++++++++----- .../IO/SerializationTarget/BigSpanHelpers.tt | 124 +++- src/FlatSharp.Runtime/SerializationContext.cs | 57 +- src/FlatSharp.Runtime/SerializationHelpers.cs | 19 + src/FlatSharp/TypeModel/ITypeModel.cs | 6 + src/FlatSharp/TypeModel/RuntimeTypeModel.cs | 10 + src/FlatSharp/TypeModel/ScalarTypeModel.cs | 11 + src/FlatSharp/TypeModel/ScalarTypeModels.cs | 22 + src/FlatSharp/TypeModel/ScalarTypeModels.tt | 2 + src/FlatSharp/TypeModel/TableTypeModel.cs | 35 +- 20 files changed, 846 insertions(+), 271 deletions(-) create mode 100644 src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanExtensions.cs 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 83f64b97..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 diff --git a/src/Benchmarks/MicroBench.Current/Constants.cs b/src/Benchmarks/MicroBench.Current/Constants.cs index 3d1dd1b6..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); 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 4e447182..81192b8e 100644 --- a/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs +++ b/src/Benchmarks/MicroBench.Current/SerializeBenchmarks.cs @@ -53,6 +53,12 @@ public long Serialize_PrimitivesTable_Full() return PrimitivesTable.Serializer.Write(Constants.Buffers.PrimitivesTable_Full, Constants.PrimitiveTables.Full); } + [Benchmark] + public long Serialize_PrimitivesTable_RandomlyPopulated() + { + return NestedTable.Serializer.Write(Constants.Buffers.PrimitivesTable_RandomEntries, Constants.PrimitiveTables.RandomEntries); + } + [Benchmark] public long Serialize_StructTable_SingleRef() { 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/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs index fc23c9fc..198b7b9b 100644 --- a/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs +++ b/src/FlatSharp.Runtime/IO/InputBuffer/InputBufferExtensions.cs @@ -165,15 +165,16 @@ public static void InitializeVTable( out ulong vtableFieldCount, out ReadOnlySpan fieldData) where TBuffer : IInputBuffer { - vtableOffset = tableOffset - buffer.ReadInt(tableOffset); - ushort vtableLength = buffer.ReadUShort(vtableOffset); + BigReadOnlySpan span = buffer.GetReadOnlySpan(); + vtableOffset = tableOffset - span.ReadInt(tableOffset); + ushort vtableLength = span.ReadUShort(vtableOffset); if (vtableLength < 4) { FSThrow.InvalidData_VTableTooShort(); } - fieldData = buffer.GetReadOnlySpan().ToSpan(vtableOffset, vtableLength).Slice(4); + fieldData = span.ToSpan(vtableOffset, vtableLength).Slice(4); vtableFieldCount = (ulong)(fieldData.Length / 2); } diff --git a/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs index 5285ada4..f29b36c4 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigReadOnlySpan.cs @@ -31,6 +31,12 @@ public BigReadOnlySpan(BigSpan span) this.span = span; } + internal BigSpan Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.span; + } + public long Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -68,16 +74,4 @@ private static void ThrowOutOfRange() { throw new IndexOutOfRangeException(); } - - [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/BigSpan.cs b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs index 64e29d54..1f5a0ba1 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpan.cs @@ -20,7 +20,6 @@ namespace FlatSharp; using System.Buffers.Binary; using System.Runtime.InteropServices; - public readonly ref partial struct BigSpan { #if NET7_0_OR_GREATER @@ -122,11 +121,7 @@ public BigSpan Slice(long start) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span ToSpan(long start, int length) { - long sum = start + (long)length; - if ((ulong)sum > (ulong)this.Length) - { - ThrowOutOfRange(); - } + this.CheckRange(start, length); #if NET7_0_OR_GREATER return MemoryMarshal.CreateSpan( @@ -228,10 +223,10 @@ public long WriteAndProvisionString( #endif // null teriminator - scopedThis.WriteUnalignedUnsafe(sizeof(uint) + bytesWritten, 0); + scopedThis.UnsafeWriteByte(sizeof(uint) + bytesWritten, 0); // write length - scopedThis.WriteUnalignedUnsafe(0, bytesWritten); + scopedThis.UnsafeWriteInt(0, bytesWritten); // give back unused space. Account for null terminator. context.Offset -= maxItems - (bytesWritten + 1); @@ -253,23 +248,44 @@ public void WriteUOffset( private T ReadUnaligned(long offset) where T : unmanaged { - CheckAlignment(offset, Unsafe.SizeOf()); - var slice = this.ToSpan(offset, Unsafe.SizeOf()); - return Unsafe.ReadUnaligned(ref slice[0]); + 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()); - this.WriteUnalignedUnsafe(offset, value); + return Unsafe.ReadUnaligned(ref slice[0]); +#endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteUnalignedUnsafe(long offset, T value) + 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 @@ -278,14 +294,14 @@ private void WriteUnalignedUnsafe(long offset, T value) #endif } - private static double ReverseEndianness(double value) + internal static double ReverseEndianness(double value) { long longValue = Unsafe.As(ref value); longValue = BinaryPrimitives.ReverseEndianness(longValue); return BitConverter.Int64BitsToDouble(longValue); } - private static float ReverseEndianness(float value) + internal static float ReverseEndianness(float value) { uint intValue = Unsafe.As(ref value); intValue = BinaryPrimitives.ReverseEndianness(intValue); @@ -303,6 +319,16 @@ 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) 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 index 0d685448..04086fb0 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.cs +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.cs @@ -18,274 +18,593 @@ using System.Buffers.Binary; -namespace FlatSharp; - -public readonly ref partial struct BigSpan +namespace FlatSharp { + public readonly ref partial struct BigSpan + { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; } - return value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteByte(long offset, byte value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteByte(long offset, byte value) - { - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadSByte(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; } - this.WriteUnaligned(offset, value); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteSByte(long offset, sbyte value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadSByte(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUShort(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; } - return value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUShort(long offset, ushort value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteSByte(long offset, sbyte value) - { - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadShort(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; } - this.WriteUnaligned(offset, value); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteShort(long offset, short value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUShort(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; } - return value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt(long offset, int value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUShort(long offset, ushort value) - { - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; } - this.WriteUnaligned(offset, value); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt(long offset, uint value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadShort(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadLong(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; } - return value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLong(long offset, long value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteShort(long offset, short value) - { - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadULong(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return value; } - this.WriteUnaligned(offset, value); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteULong(long offset, ulong value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float ReadFloat(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + + return value; } - return value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFloat(long offset, float value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInt(long offset, int value) - { - if (!BitConverter.IsLittleEndian) + this.WriteUnaligned(offset, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double ReadDouble(long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + var value = this.ReadUnaligned(offset); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + + return value; } - this.WriteUnaligned(offset, value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteDouble(long offset, double value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + + this.WriteUnaligned(offset, value); + } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + 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) { - value = BinaryPrimitives.ReverseEndianness(value); + 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); + } - return value; - } + span.WriteUnalignedUnsafe(offset, value); + } + - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUInt(long offset, uint value) - { - if (!BitConverter.IsLittleEndian) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte UnsafeReadByte(this BigReadOnlySpan span, long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + return span.Span.UnsafeReadByte(offset); } - this.WriteUnaligned(offset, value); - } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte UnsafeReadSByte(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadLong(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteSByte(this BigSpan span, long offset, sbyte value) { - value = BinaryPrimitives.ReverseEndianness(value); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); } + - return value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte UnsafeReadSByte(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadSByte(offset); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLong(long offset, long value) - { - if (!BitConverter.IsLittleEndian) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort UnsafeReadUShort(this BigSpan span, long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + 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); + } - this.WriteUnaligned(offset, value); - } + span.WriteUnalignedUnsafe(offset, value); + } + - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadULong(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort UnsafeReadUShort(this BigReadOnlySpan span, long offset) { - value = BinaryPrimitives.ReverseEndianness(value); + return span.Span.UnsafeReadUShort(offset); } - return value; - } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short UnsafeReadShort(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteULong(long offset, ulong value) - { - if (!BitConverter.IsLittleEndian) + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteShort(this BigSpan span, long offset, short value) { - value = BinaryPrimitives.ReverseEndianness(value); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); } + - this.WriteUnaligned(offset, value); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short UnsafeReadShort(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadShort(offset); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ReadFloat(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int UnsafeReadInt(this BigSpan span, long offset) { - value = ReverseEndianness(value); + 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); + } - return value; - } + span.WriteUnalignedUnsafe(offset, value); + } + - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteFloat(long offset, float value) - { - if (!BitConverter.IsLittleEndian) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int UnsafeReadInt(this BigReadOnlySpan span, long offset) { - value = ReverseEndianness(value); + return span.Span.UnsafeReadInt(offset); } - this.WriteUnaligned(offset, value); - } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint UnsafeReadUInt(this BigSpan span, long offset) + { + var value = span.ReadUnalignedUnsafe(offset); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double ReadDouble(long offset) - { - var value = this.ReadUnaligned(offset); - if (!BitConverter.IsLittleEndian) + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteUInt(this BigSpan span, long offset, uint value) { - value = ReverseEndianness(value); + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + span.WriteUnalignedUnsafe(offset, value); } + - return value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint UnsafeReadUInt(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadUInt(offset); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteDouble(long offset, double value) - { - if (!BitConverter.IsLittleEndian) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long UnsafeReadLong(this BigSpan span, long offset) { - value = ReverseEndianness(value); + 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); + } - this.WriteUnaligned(offset, value); - } -} + span.WriteUnalignedUnsafe(offset, value); + } + -public readonly ref partial struct BigReadOnlySpan -{ + [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); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte(long offset) => this.span.ReadByte(offset); + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteULong(this BigSpan span, long offset, ulong value) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadSByte(long offset) => this.span.ReadSByte(offset); + span.WriteUnalignedUnsafe(offset, value); + } + - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUShort(long offset) => this.span.ReadUShort(offset); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong UnsafeReadULong(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadULong(offset); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadShort(long offset) => this.span.ReadShort(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); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt(long offset) => this.span.ReadInt(offset); + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteFloat(this BigSpan span, long offset, float value) + { + if (!BitConverter.IsLittleEndian) + { + value = BigSpan.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt(long offset) => this.span.ReadUInt(offset); + span.WriteUnalignedUnsafe(offset, value); + } + - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadLong(long offset) => this.span.ReadLong(offset); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UnsafeReadFloat(this BigReadOnlySpan span, long offset) + { + return span.Span.UnsafeReadFloat(offset); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadULong(long offset) => this.span.ReadULong(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); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ReadFloat(long offset) => this.span.ReadFloat(offset); + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeWriteDouble(this BigSpan span, long offset, double value) + { + if (!BitConverter.IsLittleEndian) + { + value = BigSpan.ReverseEndianness(value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double ReadDouble(long offset) => this.span.ReadDouble(offset); -} + 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 index e0687b35..26a8bc5f 100644 --- a/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt +++ b/src/FlatSharp.Runtime/IO/SerializationTarget/BigSpanHelpers.tt @@ -40,60 +40,110 @@ using System.Buffers.Binary; -namespace FlatSharp; - -public readonly ref partial struct BigSpan +namespace FlatSharp { -<# - foreach (var tuple in types) + public readonly ref partial struct BigSpan { - string casedName = tuple.casedName; - string typeName = tuple.typeName; - string bp = tuple.useBinaryPrimitives ? "BinaryPrimitives." : ""; -#> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public <#= typeName #> Read<#=casedName#>(long offset) + <# + foreach (var tuple in types) { - var value = this.ReadUnaligned<<#=typeName#>>(offset); - if (!BitConverter.IsLittleEndian) + string casedName = tuple.casedName; + string typeName = tuple.typeName; + string bp = tuple.useBinaryPrimitives ? "BinaryPrimitives." : ""; + #> + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public <#= typeName #> Read<#=casedName#>(long offset) { - value = <#= bp #>ReverseEndianness(value); + var value = this.ReadUnaligned<<#=typeName#>>(offset); + if (!BitConverter.IsLittleEndian) + { + value = <#= bp #>ReverseEndianness(value); + } + + return 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 + { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write<#=casedName#>(long offset, <#= typeName #> value) + <# + foreach (var tuple in types) { - if (!BitConverter.IsLittleEndian) - { - value = <#=bp#>ReverseEndianness(value); - } + string casedName = tuple.casedName; + string typeName = tuple.typeName; + string bp = tuple.useBinaryPrimitives ? "BinaryPrimitives." : ""; + #> - this.WriteUnaligned(offset, value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public <#= typeName #> Read<#=casedName#>(long offset) => this.span.Read<#=casedName#>(offset); + <# } -<# + #> } -#> } -public readonly ref partial struct BigReadOnlySpan +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."; + #> -<# - foreach (var tuple in types) - { - string casedName = tuple.casedName; - string typeName = tuple.typeName; - string bp = tuple.useBinaryPrimitives ? "BinaryPrimitives." : ""; -#> + [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); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public <#= typeName #> Read<#=casedName#>(long offset) => this.span.Read<#=casedName#>(offset); -<# + 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/SerializationContext.cs b/src/FlatSharp.Runtime/SerializationContext.cs index faddcf53..91d4bdbc 100644 --- a/src/FlatSharp.Runtime/SerializationContext.cs +++ b/src/FlatSharp.Runtime/SerializationContext.cs @@ -16,6 +16,17 @@ 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; @@ -30,7 +41,13 @@ public sealed class SerializationContext private long offset; private long capacity; private readonly List postSerializeActions; - private readonly List vtableOffsets; + private readonly List[] vtableOffsets; + +#if NETCOREAPP + private const int ListLength = 19; +#else + private const int ListLength = 1; +#endif /// /// Initializes a new serialization context. @@ -38,7 +55,11 @@ public sealed class SerializationContext public SerializationContext() { this.postSerializeActions = new List(); - this.vtableOffsets = new List(); + this.vtableOffsets = new List[ListLength]; + for (int i = 0; i < this.vtableOffsets.Length; ++i) + { + this.vtableOffsets[i] = new(); + } } /// @@ -65,7 +86,11 @@ public void Reset(long capacity) 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(); + } } /// @@ -147,28 +172,30 @@ public long AllocateSpace(int bytesNeeded, int alignment) [MethodImpl(MethodImplOptions.NoInlining)] // Common method; don't inline 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) { long offset = offsets[i]; + ushort vtableLength = buffer.ReadUShort(offset); - ReadOnlySpan existingVTable = buffer.ToSpan(offset, sizeof(ushort)); - ushort vtableLength = BinaryPrimitives.ReadUInt16LittleEndian(existingVTable); - - existingVTable = buffer.ToSpan(offset, vtableLength); - - 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; + } } } diff --git a/src/FlatSharp.Runtime/SerializationHelpers.cs b/src/FlatSharp.Runtime/SerializationHelpers.cs index 383ee45c..c15a050e 100644 --- a/src/FlatSharp.Runtime/SerializationHelpers.cs +++ b/src/FlatSharp.Runtime/SerializationHelpers.cs @@ -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/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 536dec55..7ead0d9f 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModel.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModel.cs @@ -98,6 +98,11 @@ internal ScalarTypeModel( /// 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). /// @@ -158,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/ScalarTypeModels.cs b/src/FlatSharp/TypeModel/ScalarTypeModels.cs index 26dd90ce..175c7d5b 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.cs +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.cs @@ -39,6 +39,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteBool"; + protected override string UnsafeWriteMethodName => "UnsafeWriteBool"; + protected override string TypeNameAlias => "bool"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -68,6 +70,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteByte"; + protected override string UnsafeWriteMethodName => "UnsafeWriteByte"; + protected override string TypeNameAlias => "byte"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -97,6 +101,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteSByte"; + protected override string UnsafeWriteMethodName => "UnsafeWriteSByte"; + protected override string TypeNameAlias => "sbyte"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -126,6 +132,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteUShort"; + protected override string UnsafeWriteMethodName => "UnsafeWriteUShort"; + protected override string TypeNameAlias => "ushort"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -155,6 +163,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteShort"; + protected override string UnsafeWriteMethodName => "UnsafeWriteShort"; + protected override string TypeNameAlias => "short"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -184,6 +194,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteInt"; + protected override string UnsafeWriteMethodName => "UnsafeWriteInt"; + protected override string TypeNameAlias => "int"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -213,6 +225,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteUInt"; + protected override string UnsafeWriteMethodName => "UnsafeWriteUInt"; + protected override string TypeNameAlias => "uint"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -242,6 +256,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteLong"; + protected override string UnsafeWriteMethodName => "UnsafeWriteLong"; + protected override string TypeNameAlias => "long"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -271,6 +287,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteULong"; + protected override string UnsafeWriteMethodName => "UnsafeWriteULong"; + protected override string TypeNameAlias => "ulong"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -300,6 +318,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteFloat"; + protected override string UnsafeWriteMethodName => "UnsafeWriteFloat"; + protected override string TypeNameAlias => "float"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) @@ -329,6 +349,8 @@ public override bool TryGetSpanComparerType([NotNullWhen(true)] out Type? compar protected override string WriteMethodName => "WriteDouble"; + protected override string UnsafeWriteMethodName => "UnsafeWriteDouble"; + 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 eef8c38a..48b8fad7 100644 --- a/src/FlatSharp/TypeModel/ScalarTypeModels.tt +++ b/src/FlatSharp/TypeModel/ScalarTypeModels.tt @@ -72,6 +72,8 @@ public partial class <#= className #> : ScalarTypeModel protected override string WriteMethodName => "<#= writeMethod #>"; + protected override string UnsafeWriteMethodName => "Unsafe<#= writeMethod #>"; + protected override string TypeNameAlias => "<#=typeName#>"; public override string GetDeserializedTypeName(FlatBufferDeserializationOption option, string inputBufferTypeName) diff --git a/src/FlatSharp/TypeModel/TableTypeModel.cs b/src/FlatSharp/TypeModel/TableTypeModel.cs index 2b9e870e..1c414985 100644 --- a/src/FlatSharp/TypeModel/TableTypeModel.cs +++ b/src/FlatSharp/TypeModel/TableTypeModel.cs @@ -478,6 +478,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG int vtableLength = {minVtableLength}; Span vtable = stackalloc byte[{4 + 2 * (maxIndex + 1)}]; + uint crc = 0; "; List body = new(); @@ -500,10 +501,12 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG body.Add($"{context.SerializationContextVariableName}.{nameof(SerializationContext.Offset)} -= {maxInlineSize} - tableLength;"); // Finish vtable. + 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($"long vtablePosition = {context.SerializationContextVariableName}.{nameof(SerializationContext.FinishVTable)}({context.SpanVariableName}, vtable.Slice(0, vtableLength));"); + 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); @@ -577,10 +580,15 @@ private string GetPrepareSerializeBlock( } } - string writeVTableBlock = - $"{typeof(BinaryPrimitives).GGCTN()}.WriteUInt16LittleEndian(vtable.Slice({vTableIndex}), (ushort)({OffsetVariableName(index, i)} - tableStart));"; + 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( @@ -634,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}", From 492c60f2ef386e9f606e121f3795299ac81f36eb Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sat, 23 Nov 2024 22:46:52 -0800 Subject: [PATCH 11/13] BigSpan unit tests --- .../ClassLib/BigSpanTests.cs | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs new file mode 100644 index 00000000..88c01ded --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs @@ -0,0 +1,198 @@ +/* + * 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); + + [TestMethod] + [DataRow(float.MinValue)] + [DataRow((float)-1)] + [DataRow((float)0)] + [DataRow((float)1)] + [DataRow(float.MaxValue)] + public void ReadWrite_Long(float value) => TestReadWrite( + s => BinaryPrimitives.ReadSingleLittleEndian(s), + bs => bs.ReadFloat(0), + (s, v) => BinaryPrimitives.WriteSingleLittleEndian(s, v), + (bs, v) => bs.WriteFloat(0, v), + value); + + [TestMethod] + [DataRow(double.MinValue)] + [DataRow((double)-1)] + [DataRow((double)0)] + [DataRow((double)1)] + [DataRow(double.MaxValue)] + public void ReadWrite_Long(double value) => TestReadWrite( + s => BinaryPrimitives.ReadDoubleLittleEndian(s), + bs => bs.ReadDouble(0), + (s, v) => BinaryPrimitives.WriteDoubleLittleEndian(s, v), + (bs, v) => bs.WriteDouble(0, v), + value); + + 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))); + } +} From ebc7948bbc24789569f2217ffb21a24ad55a0333 Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sun, 24 Nov 2024 01:24:36 -0800 Subject: [PATCH 12/13] Exclude float test --- .../ClassLib/BigSpanTests.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs index 88c01ded..af98291b 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs @@ -134,26 +134,28 @@ public void ReadWrite_ULong(ulong value) => TestReadWrite( (bs, v) => bs.WriteULong(0, v), value); +#if !AOT [TestMethod] [DataRow(float.MinValue)] - [DataRow((float)-1)] - [DataRow((float)0)] - [DataRow((float)1)] + [DataRow(-1f)] + [DataRow(0f)] + [DataRow(1f)] [DataRow(float.MaxValue)] - public void ReadWrite_Long(float value) => TestReadWrite( + 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((double)-1)] - [DataRow((double)0)] - [DataRow((double)1)] + [DataRow(-1d)] + [DataRow(0d)] + [DataRow(1d)] [DataRow(double.MaxValue)] - public void ReadWrite_Long(double value) => TestReadWrite( + public void ReadWrite_Double(double value) => TestReadWrite( s => BinaryPrimitives.ReadDoubleLittleEndian(s), bs => bs.ReadDouble(0), (s, v) => BinaryPrimitives.WriteDoubleLittleEndian(s, v), From ab7dca4cd68c986ced088af19658a326b43a1096 Mon Sep 17 00:00:00 2001 From: James Courtney Date: Sun, 24 Nov 2024 01:45:04 -0800 Subject: [PATCH 13/13] Disable tests for older frameworks --- src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs index af98291b..8aa25771 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/BigSpanTests.cs @@ -134,6 +134,7 @@ public void ReadWrite_ULong(ulong value) => TestReadWrite( (bs, v) => bs.WriteULong(0, v), value); +#if NET8_0_OR_GREATER #if !AOT [TestMethod] [DataRow(float.MinValue)] @@ -161,6 +162,7 @@ public void ReadWrite_Double(double value) => TestReadWrite( (s, v) => BinaryPrimitives.WriteDoubleLittleEndian(s, v), (bs, v) => bs.WriteDouble(0, v), value); +#endif private void TestReadWrite( SpanReadValue spanRead,