diff options
author | vnugent <public@vaughnnugent.com> | 2024-09-10 18:59:06 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-09-10 18:59:06 -0400 |
commit | 2ae018af277b808786cf398c689910bc016e7ef0 (patch) | |
tree | 88cc50f9c155d3415a938b0679c42a2a1c640932 /lib/Utils | |
parent | 17c646a619eaa101d66871faa8f57c76500a8ad2 (diff) |
fix: zero/unsafezero with data types > sizeof(byte)
Diffstat (limited to 'lib/Utils')
-rw-r--r-- | lib/Utils/src/Extensions/CacheExtensions.cs | 77 | ||||
-rw-r--r-- | lib/Utils/src/IO/TemporayIsolatedFile.cs | 57 | ||||
-rw-r--r-- | lib/Utils/src/Memory/ArrayPoolBuffer.cs | 30 | ||||
-rw-r--r-- | lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs | 8 | ||||
-rw-r--r-- | lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs | 69 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtil.cs | 140 | ||||
-rw-r--r-- | lib/Utils/src/Memory/NativeHeap.cs | 17 | ||||
-rw-r--r-- | lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs | 14 | ||||
-rw-r--r-- | lib/Utils/src/Memory/PrivateStringManager.cs | 23 | ||||
-rw-r--r-- | lib/Utils/src/Memory/SubSequence.cs | 7 | ||||
-rw-r--r-- | lib/Utils/src/Memory/VnString.cs | 164 | ||||
-rw-r--r-- | lib/Utils/src/Native/SafeLibraryHandle.cs | 3 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryHandleTest.cs | 2 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryUtilTests.cs | 116 |
14 files changed, 384 insertions, 343 deletions
diff --git a/lib/Utils/src/Extensions/CacheExtensions.cs b/lib/Utils/src/Extensions/CacheExtensions.cs index 665e282..7efc36d 100644 --- a/lib/Utils/src/Extensions/CacheExtensions.cs +++ b/lib/Utils/src/Extensions/CacheExtensions.cs @@ -52,10 +52,7 @@ namespace VNLib.Utils.Extensions /// </remarks> public static void StoreRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key, T record) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } + ArgumentNullException.ThrowIfNull(store); T ?oldRecord = default; lock (store) @@ -128,10 +125,7 @@ namespace VNLib.Utils.Extensions /// </remarks> public static ERRNO TryGetOrEvictRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key, out T? value) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } + ArgumentNullException.ThrowIfNull(store); value = default; //Cache current date time before entering the lock @@ -194,10 +188,7 @@ namespace VNLib.Utils.Extensions /// <returns>True if the record was found and evicted</returns> public static bool EvictRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } + ArgumentNullException.ThrowIfNull(store); T? record = default; lock (store) @@ -218,10 +209,8 @@ namespace VNLib.Utils.Extensions /// </summary> /// <typeparam name="TKey"></typeparam> /// <typeparam name="T"></typeparam> - public static void CollectRecords<TKey, T>(this IDictionary<TKey, T> store) where T : ICacheable - { - CollectRecords(store, DateTime.UtcNow); - } + public static void CollectRecords<TKey, T>(this IDictionary<TKey, T> store) where T : ICacheable + => CollectRecords(store, DateTime.UtcNow); /// <summary> /// Evicts all expired records from the store @@ -232,10 +221,7 @@ namespace VNLib.Utils.Extensions /// <param name="validAfter">A time that specifies the time which expired records should be evicted</param> public static void CollectRecords<TKey, T>(this IDictionary<TKey, T> store, DateTime validAfter) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } + ArgumentNullException.ThrowIfNull(store); //Build a query to get the keys that belong to the expired records IEnumerable<KeyValuePair<TKey, T>> expired = store.Where(s => s.Value.Expires < validAfter); //temp list for expired records @@ -273,15 +259,8 @@ namespace VNLib.Utils.Extensions /// <param name="useCtx">A callback method that will be passed the record to use within an exclusive context</param> public static void UseRecord<TKey, T, TState>(this IDictionary<TKey, T> store, TKey key, TState state, Action<T, TState> useCtx) where T: ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } - - if (useCtx is null) - { - throw new ArgumentNullException(nameof(useCtx)); - } + ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(useCtx); lock (store) { @@ -303,15 +282,8 @@ namespace VNLib.Utils.Extensions /// <param name="useCtx">A callback method that will be passed the record to use within an exclusive context</param> public static void UseRecord<TKey, T>(this IDictionary<TKey, T> store, TKey key, Action<T> useCtx) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } - - if (useCtx is null) - { - throw new ArgumentNullException(nameof(useCtx)); - } + ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(useCtx); lock (store) { @@ -335,17 +307,15 @@ namespace VNLib.Utils.Extensions /// <param name="state">A user-token type state parameter to pass to the use callback method</param> /// <param name="useCtx">A callback method that will be passed the record to use within an exclusive context</param> /// <remarks>If the record is found, but is expired, the record is evicted from the store. The callback is never invoked</remarks> - public static void UseIfValid<TKey, T, TState>(this IDictionary<TKey, T> store, TKey key, TState state, Action<T, TState> useCtx) where T : ICacheable + public static void UseIfValid<TKey, T, TState>( + this IDictionary<TKey, T> store, + TKey key, + TState state, + Action<T, TState> useCtx + ) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } - - if (useCtx is null) - { - throw new ArgumentNullException(nameof(useCtx)); - } + ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(useCtx); DateTime now = DateTime.UtcNow; T? record; @@ -376,15 +346,8 @@ namespace VNLib.Utils.Extensions /// <remarks>If the record is found, but is expired, the record is evicted from the store. The callback is never invoked</remarks> public static void UseIfValid<TKey, T>(this IDictionary<TKey, T> store, TKey key, Action<T> useCtx) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } - - if (useCtx is null) - { - throw new ArgumentNullException(nameof(useCtx)); - } + ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(useCtx); DateTime now = DateTime.UtcNow; T? record; diff --git a/lib/Utils/src/IO/TemporayIsolatedFile.cs b/lib/Utils/src/IO/TemporayIsolatedFile.cs deleted file mode 100644 index 3bee92b..0000000 --- a/lib/Utils/src/IO/TemporayIsolatedFile.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: TemporayIsolatedFile.cs -* -* TemporayIsolatedFile.cs is part of VNLib.Utils which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Utils is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Utils is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.IO; -using System.IO.IsolatedStorage; - -namespace VNLib.Utils.IO -{ - /// <summary> - /// Allows for temporary files to be generated, used, then removed from an <see cref="IsolatedStorageFile"/> - /// </summary> - public sealed class TemporayIsolatedFile : BackingStream<IsolatedStorageFileStream> - { - private readonly IsolatedStorageDirectory Storage; - private readonly string Filename; - /// <summary> - /// Creates a new temporary filestream within the specified <see cref="IsolatedStorageFile"/> - /// </summary> - /// <param name="storage">The file store to genreate temporary files within</param> - public TemporayIsolatedFile(IsolatedStorageDirectory storage) - { - //Store ref - this.Storage = storage; - //Creaet a new random filename - this.Filename = Path.GetRandomFileName(); - //try to created a new file within the isolaged storage - this.BaseStream = storage.CreateFile(this.Filename); - } - protected override void OnClose() - { - //Remove the file from the storage - Storage.DeleteFile(this.Filename); - } - } -}
\ No newline at end of file diff --git a/lib/Utils/src/Memory/ArrayPoolBuffer.cs b/lib/Utils/src/Memory/ArrayPoolBuffer.cs index e728cd7..67789de 100644 --- a/lib/Utils/src/Memory/ArrayPoolBuffer.cs +++ b/lib/Utils/src/Memory/ArrayPoolBuffer.cs @@ -150,20 +150,16 @@ namespace VNLib.Utils.Memory /// <returns>A memory structure over the buffer</returns> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public Memory<T> AsMemory() - { - Check(); - return new Memory<T>(Buffer, 0, InitSize); - } + public Memory<T> AsMemory() => AsMemory(start: 0, InitSize); /// <summary> /// Gets a memory structure around the internal buffer /// </summary> - /// <param name="count">The number of elements included in the result</param> + /// <param name="start">The number of elements included in the result</param> /// <returns>A memory structure over the buffer</returns> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public Memory<T> AsMemory(int count) => AsMemory()[..count]; + public Memory<T> AsMemory(int start) => AsMemory(start, InitSize - start); /// <summary> /// Gets a memory structure around the internal buffer @@ -173,18 +169,20 @@ namespace VNLib.Utils.Memory /// <returns>A memory structure over the buffer</returns> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public Memory<T> AsMemory(int start, int count) => AsMemory().Slice(start, count); + public Memory<T> AsMemory(int start, int count) + { + Check(); + //Memory constructor will check for array bounds + return new(Buffer, start, count); + } /// <summary> /// Gets an array segment around the internal buffer /// </summary> /// <returns>The internal array segment</returns> /// <exception cref="ObjectDisposedException"></exception> - public ArraySegment<T> AsArraySegment() - { - Check(); - return new ArraySegment<T>(Buffer, 0, InitSize); - } + public ArraySegment<T> AsArraySegment() => GetOffsetWrapper(0, InitSize); + /// <summary> /// Gets an array segment around the internal buffer @@ -194,10 +192,8 @@ namespace VNLib.Utils.Memory /// <exception cref="ArgumentOutOfRangeException"></exception> public ArraySegment<T> AsArraySegment(int start, int count) { - if(start< 0 || count < 0) - { - throw new ArgumentOutOfRangeException(start < 0 ? nameof(start) : nameof(count), "Cannot be less than zero"); - } + ArgumentOutOfRangeException.ThrowIfNegative(start); + ArgumentOutOfRangeException.ThrowIfNegative(count); MemoryUtil.CheckBounds(Buffer, (uint)start, (uint)count); diff --git a/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs b/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs index 53d3d77..df3990c 100644 --- a/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs +++ b/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs @@ -59,13 +59,17 @@ namespace VNLib.Utils.Memory /// The number of elements remaining in the window /// </summary> public readonly int WindowSize => _size - _position; - /// <summary> /// Advances the window position the specified number of elements /// </summary> /// <param name="count">The number of elements to advance the widnow position</param> - public void Advance(int count) => _position += count; + public void Advance(int count) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(count, WindowSize); + + _position += count; + } /// <summary> /// Resets the sliding window to the begining of the buffer diff --git a/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs b/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs index e0fbe41..791d63c 100644 --- a/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs +++ b/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -29,43 +29,39 @@ namespace VNLib.Utils.Memory /// <summary> /// Provides a mutable sliding buffer writer /// </summary> - public record struct ForwardOnlyMemoryWriter<T> + /// <param name="Buffer">The buffer to write data to</param> + public record struct ForwardOnlyMemoryWriter<T>(Memory<T> Buffer) { - /// <summary> - /// The buffer for writing output data to - /// </summary> - public readonly Memory<T> Buffer { get; } + private int _written; /// <summary> /// The number of characters written to the buffer /// </summary> - public int Written { readonly get; set; } + public int Written + { + readonly get => _written; + set + { + ArgumentOutOfRangeException.ThrowIfNegative(value); + _written = value; + } + } /// <summary> /// The number of characters remaining in the buffer /// </summary> - public readonly int RemainingSize => Buffer.Length - Written; + public readonly int RemainingSize => Buffer.Length - _written; /// <summary> /// The remaining buffer window /// </summary> - public readonly Memory<T> Remaining => Buffer[Written..]; - - /// <summary> - /// Creates a new <see cref="ForwardOnlyWriter{T}"/> assigning the specified buffer - /// </summary> - /// <param name="buffer">The buffer to write data to</param> - public ForwardOnlyMemoryWriter(Memory<T> buffer) - { - Buffer = buffer; - Written = 0; - } - + public readonly Memory<T> Remaining => Buffer[_written..]; + /// <summary> /// Returns a compiled string from the characters written to the buffer /// </summary> /// <returns>A string of the characters written to the buffer</returns> - public readonly override string ToString() => Buffer[..Written].ToString(); + public readonly override string ToString() => Buffer[.._written].ToString(); /// <summary> /// Appends a sequence to the buffer @@ -75,15 +71,14 @@ namespace VNLib.Utils.Memory public void Append(ReadOnlyMemory<T> data) { //Make sure the current window is large enough to buffer the new string - if (data.Length > RemainingSize) - { - throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space"); - } - Memory<T> window = Buffer[Written..]; + ArgumentOutOfRangeException.ThrowIfGreaterThan(data.Length, RemainingSize, nameof(data)); + + Memory<T> window = Buffer[_written..]; + //write data to window data.CopyTo(window); - //update char position - Written += data.Length; + + Advance(data.Length); } /// <summary> @@ -94,12 +89,10 @@ namespace VNLib.Utils.Memory public void Append(T c) { //Make sure the current window is large enough to buffer the new string - if (RemainingSize == 0) - { - throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space"); - } + ArgumentOutOfRangeException.ThrowIfZero(RemainingSize); + //Write data to buffer and increment the buffer position - Buffer.Span[Written++] = c; + Buffer.Span[_written++] = c; } /// <summary> @@ -109,17 +102,15 @@ namespace VNLib.Utils.Memory /// <exception cref="ArgumentOutOfRangeException"></exception> public void Advance(int count) { - if (count > RemainingSize) - { - throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot advance past the end of the buffer"); - } - Written += count; + ArgumentOutOfRangeException.ThrowIfGreaterThan(count, RemainingSize); + + _written += count; } /// <summary> /// Resets the writer by setting the <see cref="Written"/> /// property to 0. /// </summary> - public void Reset() => Written = 0; + public void Reset() => _written = 0; } } diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 1d3bccb..2ef9c24 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -155,7 +155,8 @@ namespace VNLib.Utils.Memory /// <returns>An <see cref="IUnmangedHeap"/> for the current process</returns> /// <exception cref="SystemException"></exception> /// <exception cref="DllNotFoundException"></exception> - public static IUnmangedHeap InitializeNewHeapForProcess(bool globalZero = false) => InitHeapInternal(false, false, globalZero); + public static IUnmangedHeap InitializeNewHeapForProcess(bool globalZero = false) + => InitHeapInternal(false, false, globalZero); private static IUnmangedHeap InitHeapInternal(bool isShared, bool enableStats, bool globalZero) { @@ -228,6 +229,19 @@ namespace VNLib.Utils.Memory #region Zero + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static void ZeroByRef<T>(ref T src, uint elements) + { + Debug.Assert(Unsafe.IsNullRef(ref src) == false, "Null reference passed to ZeroByRef"); + + //Call init block on bytes + Unsafe.InitBlock( + ref Refs.AsByte(ref src, 0), + value: 0, + byteCount: ByteCount<T>(elements) + ); + } + /// <summary> /// Zeros a block of memory of umanged type. If Windows is detected at runtime, calls RtlSecureZeroMemory Win32 function /// </summary> @@ -241,11 +255,10 @@ namespace VNLib.Utils.Memory return; } - //Calls memset ZeroByRef( - ref Refs.AsByte(block, 0), - (uint)block.Length - ); + ref MemoryMarshal.GetReference(block), //Get typed reference + (uint)block.Length //block must be a positive value + ); } /// <summary> @@ -260,28 +273,18 @@ namespace VNLib.Utils.Memory { return; } - - uint byteSize = ByteCount<T>((uint)block.Length); //Pin memory and get pointer using MemoryHandle handle = block.Pin(); - //Calls memset - Unsafe.InitBlock(handle.Pointer, 0, byteSize); - } - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - private static void ZeroByRef<T>(ref T src, uint elements) - { - Debug.Assert(Unsafe.IsNullRef(ref src) == false, "Null reference passed to ZeroByRef"); - //Call init block on bytes + //Calls memset Unsafe.InitBlock( - ref Refs.AsByte(ref src, 0), - 0, - ByteCount<T>(elements) + startAddress: handle.Pointer, + value: 0, + byteCount: ByteCount<T>((uint)block.Length) ); } - + /* * Initializing a non-readonly span/memory as of .NET 6.0 is a reference * reintpretation, essentially a pointer cast, so there is little/no cost @@ -294,7 +297,8 @@ namespace VNLib.Utils.Memory /// <typeparam name="T">The unmanaged</typeparam> /// <param name="block">The block of memory to initialize</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock<T>(Span<T> block) where T : struct => UnsafeZeroMemory<T>(block); + public static void InitializeBlock<T>(Span<T> block) where T : struct + => UnsafeZeroMemory<T>(block); /// <summary> /// Initializes a block of memory with zeros @@ -302,7 +306,8 @@ namespace VNLib.Utils.Memory /// <typeparam name="T">The unmanaged</typeparam> /// <param name="block">The block of memory to initialize</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock<T>(Memory<T> block) where T : struct => UnsafeZeroMemory<T>(block); + public static void InitializeBlock<T>(Memory<T> block) where T : struct + => UnsafeZeroMemory<T>(block); /// <summary> /// Initializes the entire array with zeros @@ -398,7 +403,8 @@ namespace VNLib.Utils.Memory /// <param name="block">A pointer to the block of memory to zero</param> /// <param name="itemCount">The number of elements in the block to zero</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock<T>(IntPtr block, int itemCount) where T : unmanaged => InitializeBlock((T*)block, itemCount); + public static void InitializeBlock<T>(IntPtr block, int itemCount) where T : unmanaged + => InitializeBlock((T*)block, itemCount); /// <summary> /// Zeroes a block of memory pointing to the structure @@ -434,7 +440,8 @@ namespace VNLib.Utils.Memory /// <typeparam name="T">The structure type</typeparam> /// <param name="structPtr">The pointer to the allocated structure</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ZeroStruct<T>(void* structPtr) where T: unmanaged => ZeroStruct((T*)structPtr); + public static void ZeroStruct<T>(void* structPtr) where T: unmanaged + => ZeroStruct((T*)structPtr); /// <summary> /// Zeroes a block of memory pointing to the structure @@ -442,7 +449,8 @@ namespace VNLib.Utils.Memory /// <typeparam name="T">The structure type</typeparam> /// <param name="block">The pointer to the allocated structure</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ZeroStruct<T>(IntPtr block) where T : unmanaged => ZeroStruct<T>(block.ToPointer()); + public static void ZeroStruct<T>(IntPtr block) where T : unmanaged + => ZeroStruct<T>(block.ToPointer()); #endregion @@ -462,10 +470,11 @@ namespace VNLib.Utils.Memory ThrowIfNullRef(in source, nameof(target)); ThrowIfNullRef(ref target, nameof(target)); - //Recover byte reference of target struct - ref byte dst = ref Unsafe.As<T, byte>(ref target); - - Unsafe.CopyBlockUnaligned(ref dst, in source, (uint)sizeof(T)); + Unsafe.CopyBlockUnaligned( + destination: ref Unsafe.As<T, byte>(ref target), //Recover byte reference of target struct + in source, + byteCount: ByteCount<T>(1u) + ); } /// <summary> @@ -485,11 +494,12 @@ namespace VNLib.Utils.Memory ThrowIfNullRef(in source, nameof(source)); ThrowIfNullRef(in target, nameof(target)); - //Recover byte reference to struct - ref byte src = ref Unsafe.As<T, byte>(ref Unsafe.AsRef(in source)); - //Memmove - Unsafe.CopyBlockUnaligned(ref target, ref src, (uint)sizeof(T)); + Unsafe.CopyBlockUnaligned( + ref target, + in Unsafe.As<T, byte>(ref Unsafe.AsRef(in source)), //Recover byte reference to struct + byteCount: ByteCount<T>(1u) + ); } @@ -660,7 +670,8 @@ namespace VNLib.Utils.Memory /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyStruct<T>(void* source, Span<byte> target) where T : unmanaged => CopyStruct((T*)source, target); + public static void CopyStruct<T>(void* source, Span<byte> target) where T : unmanaged + => CopyStruct((T*)source, target); /// <summary> /// Copies the memory of the structure pointed to by the source pointer to the target @@ -675,7 +686,8 @@ namespace VNLib.Utils.Memory /// <param name="target">A reference to the first byte of the memory location to copy the struct data to</param> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyStruct<T>(IntPtr source, ref byte target) where T : unmanaged => CopyStruct(ref GetRef<T>(source), ref target); + public static void CopyStruct<T>(IntPtr source, ref byte target) where T : unmanaged + => CopyStruct(ref GetRef<T>(source), ref target); /// <summary> @@ -694,7 +706,7 @@ namespace VNLib.Utils.Memory Unsafe.CopyBlockUnaligned( ref Refs.AsByte(ref target, 0), in Refs.AsByteR(in source, 0), - (uint)sizeof(T) + byteCount: ByteCount<T>(1u) ); } @@ -711,7 +723,11 @@ namespace VNLib.Utils.Memory ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(target); - Unsafe.CopyBlockUnaligned(target, source, (uint)sizeof(T)); + Unsafe.CopyBlockUnaligned( + destination: target, + source, + byteCount: ByteCount<T>(1u) + ); } @@ -735,7 +751,7 @@ namespace VNLib.Utils.Memory return; } - //Check bounds + //Check bounds (will verify that count is a positive integer) CheckBounds(source, sourceOffset, count); CheckBounds(dest, destOffset, (uint)count); @@ -1151,7 +1167,7 @@ namespace VNLib.Utils.Memory public static nuint ByteSize<T>(IMemoryHandle<T> handle) { ArgumentNullException.ThrowIfNull(handle); - return checked(handle.Length * (nuint)Unsafe.SizeOf<T>()); + return ByteCount<T>(handle.Length); } /// <summary> @@ -1162,7 +1178,8 @@ namespace VNLib.Utils.Memory /// <returns>The number of bytes pointed to by the handle</returns> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nuint ByteSize<T>(in UnsafeMemoryHandle<T> handle) where T : unmanaged => checked(handle.Length * (nuint)sizeof(T)); + public static nuint ByteSize<T>(in UnsafeMemoryHandle<T> handle) where T : unmanaged + => ByteCount<T>(handle.Length); /// <summary> /// Gets the byte multiple of the length parameter @@ -1172,7 +1189,8 @@ namespace VNLib.Utils.Memory /// <returns>The byte multiple of the number of elments</returns> /// <exception cref="OverflowException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nuint ByteCount<T>(nuint elementCount) => checked(elementCount * (nuint)Unsafe.SizeOf<T>()); + public static nuint ByteCount<T>(nuint elementCount) + => checked(elementCount * (nuint)Unsafe.SizeOf<T>()); /// <summary> /// Gets the byte multiple of the length parameter @@ -1182,7 +1200,8 @@ namespace VNLib.Utils.Memory /// <returns>The byte multiple of the number of elments</returns> /// <exception cref="OverflowException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ByteCount<T>(uint elementCount) => checked(elementCount * (uint)Unsafe.SizeOf<T>()); + public static uint ByteCount<T>(uint elementCount) + => checked(elementCount * (uint)Unsafe.SizeOf<T>()); /// <summary> /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values @@ -1192,7 +1211,8 @@ namespace VNLib.Utils.Memory /// <returns>The byte multiple of the number of elments</returns> /// <exception cref="OverflowException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint ByteCount<T>(nint elementCount) => checked(elementCount * Unsafe.SizeOf<T>()); + public static nint ByteCount<T>(nint elementCount) + => checked(elementCount * Unsafe.SizeOf<T>()); /// <summary> /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values @@ -1202,7 +1222,8 @@ namespace VNLib.Utils.Memory /// <returns>The byte multiple of the number of elments</returns> /// <exception cref="OverflowException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ByteCount<T>(int elementCount) => checked(elementCount * Unsafe.SizeOf<T>()); + public static int ByteCount<T>(int elementCount) + => checked(elementCount * Unsafe.SizeOf<T>()); /// <summary> /// Checks if the offset/count paramters for the given memory handle @@ -1243,12 +1264,8 @@ namespace VNLib.Utils.Memory /// <param name="count">The number of bytes expected to be assigned or dereferrenced</param> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CheckBounds<T>(Span<T> block, int offset, int count) - { - ArgumentOutOfRangeException.ThrowIfNegative(offset); - ArgumentOutOfRangeException.ThrowIfNegative(count); - ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + count, block.Length, nameof(count)); - } + public static void CheckBounds<T>(Span<T> block, int offset, int count) + => CheckBounds((ReadOnlySpan<T>)block, offset, count); /// <summary> /// Checks if the offset/count paramters for the given block @@ -1277,12 +1294,8 @@ namespace VNLib.Utils.Memory /// <param name="count">The number of bytes expected to be assigned or dereferrenced</param> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CheckBounds<T>(Memory<T> block, int offset, int count) - { - ArgumentOutOfRangeException.ThrowIfNegative(offset); - ArgumentOutOfRangeException.ThrowIfNegative(count); - ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + count, block.Length, nameof(count)); - } + public static void CheckBounds<T>(Memory<T> block, int offset, int count) + => CheckBounds((ReadOnlyMemory<T>)block, offset, count); /// <summary> /// Checks if the offset/count paramters for the given block @@ -1406,7 +1419,8 @@ namespace VNLib.Utils.Memory /// <param name="size">The size of the sequence</param> /// <returns>The span pointing to the memory at the supplied addres</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span<T> GetSpan<T>(IntPtr address, int size) => new(address.ToPointer(), size); + public static Span<T> GetSpan<T>(IntPtr address, int size) + => new(address.ToPointer(), size); /// <summary> /// Gets a <see cref="Span{T}"/> over the block of memory pointed to by the supplied handle. @@ -1417,7 +1431,8 @@ namespace VNLib.Utils.Memory /// <param name="size">The size of the span (the size of the block)</param> /// <returns>A span over the block of memory pointed to by the handle of the specified size</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span<T> GetSpan<T>(ref readonly MemoryHandle handle, int size) => new(handle.Pointer, size); + public static Span<T> GetSpan<T>(ref readonly MemoryHandle handle, int size) + => new(handle.Pointer, size); /// <summary> /// Gets a <see cref="Span{T}"/> over the block of memory pointed to by the supplied handle. @@ -1427,7 +1442,8 @@ namespace VNLib.Utils.Memory /// <param name="size">The size of the span (the size of the block)</param> /// <returns>A span over the block of memory pointed to by the handle of the specified size</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span<T> GetSpan<T>(MemoryHandle handle, int size) => new(handle.Pointer, size); + public static Span<T> GetSpan<T>(MemoryHandle handle, int size) + => new(handle.Pointer, size); /// <summary> /// Recovers a reference to the supplied pointer @@ -1436,7 +1452,8 @@ namespace VNLib.Utils.Memory /// <param name="address">The base address to cast to a reference</param> /// <returns>The reference to the supplied address</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T GetRef<T>(IntPtr address) => ref Unsafe.AsRef<T>(address.ToPointer()); + public static ref T GetRef<T>(IntPtr address) + => ref Unsafe.AsRef<T>(address.ToPointer()); /// <summary> /// Recovers a reference to the supplied pointer @@ -1458,7 +1475,8 @@ namespace VNLib.Utils.Memory /// <param name="handle">A reference to the handle to get the intpr for</param> /// <returns>A managed pointer from the handle</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IntPtr GetIntptr(ref readonly MemoryHandle handle) => new(handle.Pointer); + public static IntPtr GetIntptr(ref readonly MemoryHandle handle) + => new(handle.Pointer); /// <summary> /// Rounds the requested byte size up to the nearest page diff --git a/lib/Utils/src/Memory/NativeHeap.cs b/lib/Utils/src/Memory/NativeHeap.cs index fb9612c..9a2e19a 100644 --- a/lib/Utils/src/Memory/NativeHeap.cs +++ b/lib/Utils/src/Memory/NativeHeap.cs @@ -62,9 +62,9 @@ namespace VNLib.Utils.Memory //Create a flags structure with defaults UnmanagedHeapDescriptor hFlags = new() { - CreationFlags = creationFlags, - Flags = flags, - HeapPointer = IntPtr.Zero + CreationFlags = creationFlags, + Flags = flags, + HeapPointer = IntPtr.Zero }; //Create the heap @@ -117,15 +117,18 @@ namespace VNLib.Utils.Memory ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override IntPtr AllocBlock(nuint elements, nuint size, bool zero) => MethodTable.Alloc(handle, elements, size, zero); + protected override IntPtr AllocBlock(nuint elements, nuint size, bool zero) + => MethodTable.Alloc(handle, elements, size, zero); ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override IntPtr ReAllocBlock(IntPtr block, nuint elements, nuint size, bool zero) => MethodTable.Realloc(handle, block, elements, size, zero); + protected override IntPtr ReAllocBlock(IntPtr block, nuint elements, nuint size, bool zero) + => MethodTable.Realloc(handle, block, elements, size, zero); ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override bool FreeBlock(IntPtr block) => MethodTable.Free(handle, block); + protected override bool FreeBlock(IntPtr block) + => MethodTable.Free(handle, block); ///<inheritdoc/> protected override bool ReleaseHandle() @@ -139,7 +142,7 @@ namespace VNLib.Utils.Memory //Cleanup the method table MethodTable = default; - Trace.WriteLine($"Successfully deestroyed user defined heap 0x{handle:x}"); + Trace.WriteLine($"Successfully destroyed user defined heap 0x{handle:x}"); return ret; } diff --git a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs index a17a906..8a752f9 100644 --- a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs +++ b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -50,7 +50,8 @@ namespace VNLib.Utils.Memory ///<exception cref="OutOfMemoryException"></exception> ///<exception cref="ObjectDisposedException"></exception> ///<exception cref="ArgumentOutOfRangeException"></exception> - public override IMemoryOwner<T> Rent(int minBufferSize = 0) => Heap.DirectAlloc<T>(minBufferSize, false); + public override IMemoryOwner<T> Rent(int minBufferSize = 0) + => Heap.DirectAlloc<T>(minBufferSize, zero: false); /// <summary> /// Allocates a new <see cref="MemoryManager{T}"/> of a different data type from the pool @@ -58,13 +59,10 @@ namespace VNLib.Utils.Memory /// <typeparam name="TDifType">The unmanaged data type to allocate for</typeparam> /// <param name="minBufferSize">Minumum size of the buffer</param> /// <returns>The memory owner of a different data type</returns> - public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged => Heap.DirectAlloc<TDifType>(minBufferSize, false); + public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged + => Heap.DirectAlloc<TDifType>(minBufferSize, zero: false); ///<inheritdoc/> - protected override void Dispose(bool disposing) - { - //Dispose the heap - Heap.Dispose(); - } + protected override void Dispose(bool disposing) => Heap.Dispose(); } } diff --git a/lib/Utils/src/Memory/PrivateStringManager.cs b/lib/Utils/src/Memory/PrivateStringManager.cs index 2bc825c..1a7b3cf 100644 --- a/lib/Utils/src/Memory/PrivateStringManager.cs +++ b/lib/Utils/src/Memory/PrivateStringManager.cs @@ -61,11 +61,11 @@ namespace VNLib.Utils.Memory private void SetValue(int index, string? value) { //Try to get the old reference and erase it - StringRef strRef = ProtectedElements[index]; + ref StringRef strRef = ref ProtectedElements[index]; strRef.Erase(); - //Set the new value and determine if it is interned - ProtectedElements[index] = StringRef.Create(value); + //Assign new string reference + strRef = StringRef.Create(value); } /// <summary> @@ -78,7 +78,7 @@ namespace VNLib.Utils.Memory protected string? CopyStringAtIndex(int index) { Check(); - StringRef str = ProtectedElements[index]; + ref readonly StringRef str = ref ProtectedElements[index]; if(str.Value is null) { @@ -101,7 +101,8 @@ namespace VNLib.Utils.Memory } ///<inheritdoc/> - protected override void Free() => Array.ForEach(ProtectedElements, static p => p.Erase()); + protected override void Free() + => Array.ForEach(ProtectedElements, static p => p.Erase()); /// <summary> /// Erases the contents of the supplied string if it @@ -109,10 +110,14 @@ namespace VNLib.Utils.Memory /// not be erased, nor will a null string /// </summary> /// <param name="str">The reference to the string to zero</param> - public static void EraseString(string? str) => StringRef.Create(str).Erase(); + public static void EraseString(string? str) + => StringRef.Create(str).Erase(); - private readonly record struct StringRef(string? Value, bool IsInterned) + private readonly struct StringRef(string? value, bool isInterned) { + public readonly string? Value = value; + public readonly bool IsInterned = isInterned; + public readonly void Erase() { /* @@ -125,8 +130,8 @@ namespace VNLib.Utils.Memory } } - internal static StringRef Create(string? str) => str is null ? - new(null, false) + internal static StringRef Create(string? str) => str is null + ? new(value: null, isInterned: false) : new(str, string.IsInterned(str) != null); } } diff --git a/lib/Utils/src/Memory/SubSequence.cs b/lib/Utils/src/Memory/SubSequence.cs index 80aa084..57c902e 100644 --- a/lib/Utils/src/Memory/SubSequence.cs +++ b/lib/Utils/src/Memory/SubSequence.cs @@ -58,12 +58,13 @@ namespace VNLib.Utils.Memory { ArgumentNullException.ThrowIfNull(block); ArgumentOutOfRangeException.ThrowIfNegative(size); - Size = size; - Handle = block; - _offset = offset; //Check handle bounds MemoryUtil.CheckBounds(block, offset, (uint)size); + + Size = size; + Handle = block; + _offset = offset; } /// <summary> diff --git a/lib/Utils/src/Memory/VnString.cs b/lib/Utils/src/Memory/VnString.cs index 429de43..e0f7283 100644 --- a/lib/Utils/src/Memory/VnString.cs +++ b/lib/Utils/src/Memory/VnString.cs @@ -64,7 +64,11 @@ namespace VNLib.Utils.Memory /// </summary> public bool IsEmpty => Length == 0; - private VnString(SubSequence<char> sequence) => _stringSequence = sequence; + private VnString(IMemoryHandle<char>? handle, SubSequence<char> sequence) + { + Handle = handle; + _stringSequence = sequence; + } private VnString(IMemoryHandle<char> handle, nuint start, int length) { @@ -155,15 +159,8 @@ namespace VNLib.Utils.Memory /// <exception cref="ArgumentOutOfRangeException"></exception> public static VnString ConsumeHandle(IMemoryHandle<char> handle, nuint start, int length) { - if (handle is null) - { - throw new ArgumentNullException(nameof(handle)); - } - - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } + ArgumentNullException.ThrowIfNull(handle); + ArgumentOutOfRangeException.ThrowIfNegative(length); //Check handle bounts MemoryUtil.CheckBounds(handle, start, (nuint)length); @@ -217,37 +214,46 @@ namespace VNLib.Utils.Memory try { int length = 0; - //span ref to bin buffer - Span<byte> buffer = binBuffer.Span; + //Run in checked context for overflows checked { do - { - //read - int read = stream.Read(buffer); - //guard + { + int read = stream.Read(binBuffer.Span); + if (read <= 0) { break; } + //Slice into only the read data - ReadOnlySpan<byte> readbytes = buffer[..read]; + ReadOnlySpan<byte> readbytes = binBuffer.AsSpan(0, read); + //get num chars int numChars = encoding.GetCharCount(readbytes); + //Guard for overflow if (((ulong)(numChars + length)) >= int.MaxValue) { throw new OverflowException(); } + //Re-alloc buffer charBuffer.ResizeIfSmaller(length + numChars); + //Decode and update position - _= encoding.GetChars(readbytes, charBuffer.Span.Slice(length, numChars)); + _= encoding.GetChars( + bytes: readbytes, + chars: charBuffer.AsSpan(length, numChars) + ); + //Update char count length += numChars; + } while (true); } + return ConsumeHandle(charBuffer, 0, length); } catch @@ -330,11 +336,15 @@ namespace VNLib.Utils.Memory charBuffer.ResizeIfSmaller(length + numChars); //Decode and update position - _ = encoding.GetChars(binBuffer.GetSpan()[..read], charBuffer.Span.Slice(length, numChars)); + _ = encoding.GetChars( + bytes: binBuffer.GetSpan()[..read], + chars: charBuffer.AsSpan(length, numChars) + ); //Update char count length += numChars; } while (true); + return ConsumeHandle(charBuffer, 0, length); } catch @@ -355,7 +365,6 @@ namespace VNLib.Utils.Memory /// <exception cref="ObjectDisposedException"></exception> public char CharAt(int index) { - //Check Check(); //Check bounds @@ -377,18 +386,21 @@ namespace VNLib.Utils.Memory /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public VnString Substring(int start, int count) - { - //Check + { Check(); + ArgumentOutOfRangeException.ThrowIfNegative(start, nameof(start)); ArgumentOutOfRangeException.ThrowIfNegative(count, nameof(count)); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(start + count, Length, nameof(start)); - //get sub-sequence slice for the current string - SubSequence<char> sub = _stringSequence.Slice((nuint)start, count); - - //Create new string with offsets pointing to same internal referrence - return new VnString(sub); + /* + * Slice the string and do not pass the handle even if we have it because the new + * instance does own the buffer + */ + return new VnString( + handle: null, + _stringSequence.Slice((nuint)start, count) + ); } /// <summary> @@ -418,12 +430,18 @@ namespace VNLib.Utils.Memory { get { - //get start - int start = range.Start.IsFromEnd ? Length - range.Start.Value : range.Start.Value; - //Get end - int end = range.End.IsFromEnd ? Length - range.End.Value : range.End.Value; - //Handle strings with no ending range - return (end >= start) ? Substring(start, (end - start)) : Substring(start); + + int start = range.Start.IsFromEnd + ? (Length - range.Start.Value) + : range.Start.Value; + + int end = range.End.IsFromEnd + ? (Length - range.End.Value) + : range.End.Value; + + return (end >= start) + ? Substring(start, (end - start)) + : Substring(start); } } #pragma warning restore IDE0057 // Use range operator @@ -461,47 +479,85 @@ namespace VNLib.Utils.Memory public static explicit operator VnString(string value) => new (value); public static explicit operator VnString(ReadOnlySpan<char> value) => new (value); public static explicit operator VnString(char[] value) => new (value); - ///<inheritdoc/> + + /// <inheritdoc/> + /// <remarks> + /// NOTE: Avoid this overload if possible. If no explict overload is provided, + /// it's assumed the datatype is not supported and will return false + /// </remarks> public override bool Equals(object? obj) { - if(obj is null) - { - return false; - } return obj switch { - VnString => Equals(obj as VnString), //Use operator overload - string => Equals(obj as string), //Use operator overload - char[] => Equals(obj as char[]), //Use operator overload + VnString => Equals(obj as VnString), + string => Equals(obj as string), + char[] => Equals(obj as char[]), _ => false, }; } + + ///<inheritdoc/> + public bool Equals(ReadOnlySpan<char> other, StringComparison stringComparison = StringComparison.Ordinal) + => Length == other.Length && AsSpan().Equals(other, stringComparison); + + ///<inheritdoc/> + public bool Equals(VnString? other) + => Equals(other, StringComparison.Ordinal); + ///<inheritdoc/> - public bool Equals(VnString? other) => other is not null && Equals(other.AsSpan()); + public bool Equals(VnString? other, StringComparison stringComparison) + => other is not null && Equals(other.AsSpan(), stringComparison); + ///<inheritdoc/> - public bool Equals(VnString? other, StringComparison stringComparison) => other is not null && Equals(other.AsSpan(), stringComparison); + public bool Equals(string? other) + => Equals(other, StringComparison.Ordinal); + ///<inheritdoc/> - public bool Equals(string? other) => Equals(other.AsSpan()); + public bool Equals(string? other, StringComparison stringComparison) + => Equals(other.AsSpan(), stringComparison); + ///<inheritdoc/> - public bool Equals(string? other, StringComparison stringComparison) => Equals(other.AsSpan(), stringComparison); + public bool Equals(char[]? other) + => Equals(other, StringComparison.Ordinal); + ///<inheritdoc/> - public bool Equals(char[]? other) => Equals(other.AsSpan()); + public bool Equals(char[]? other, StringComparison stringComparison) + => Equals(other.AsSpan(), stringComparison); + ///<inheritdoc/> - public bool Equals(char[]? other, StringComparison stringComparison) => Equals(other.AsSpan(), stringComparison); + public bool Equals(in SubSequence<char> other) + => Equals(in other, StringComparison.Ordinal); + ///<inheritdoc/> - public bool Equals(ReadOnlySpan<char> other, StringComparison stringComparison = StringComparison.Ordinal) => Length == other.Length && AsSpan().Equals(other, stringComparison); + public bool Equals(in SubSequence<char> other, StringComparison stringComparison) + => Length == other.Size && Equals(other.Span, stringComparison); + ///<inheritdoc/> - public bool Equals(in SubSequence<char> other) => Length == other.Size && AsSpan().SequenceEqual(other.Span); + public int CompareTo(string? other) + => CompareTo(other, StringComparison.Ordinal); + ///<inheritdoc/> - public int CompareTo(string? other) => AsSpan().CompareTo(other, StringComparison.Ordinal); + ///<exception cref="ArgumentNullException"></exception> + public int CompareTo(string? other, StringComparison stringComparison) + { + ArgumentNullException.ThrowIfNull(other); + return CompareTo(other.AsSpan(), stringComparison); + } + ///<inheritdoc/> ///<exception cref="ArgumentNullException"></exception> public int CompareTo(VnString? other) { ArgumentNullException.ThrowIfNull(other); - return AsSpan().CompareTo(other.AsSpan(), StringComparison.Ordinal); + return CompareTo(other.AsSpan(), StringComparison.Ordinal); } + public int CompareTo(ReadOnlySpan<char> other) + => AsSpan().CompareTo(other, StringComparison.Ordinal); + + public int CompareTo(ReadOnlySpan<char> other, StringComparison comparison) + => AsSpan().CompareTo(other, comparison); + /// <summary> /// Gets a hashcode for the underyling string by using the .NET <see cref="string.GetHashCode()"/> /// method on the character representation of the data @@ -512,7 +568,8 @@ namespace VNLib.Utils.Memory /// a character span etc /// </remarks> /// <exception cref="ObjectDisposedException"></exception> - public override int GetHashCode() => GetHashCode(StringComparison.Ordinal); + public override int GetHashCode() + => GetHashCode(StringComparison.Ordinal); /// <summary> /// Gets a hashcode for the underyling string by using the .NET <see cref="string.GetHashCode()"/> @@ -525,7 +582,8 @@ namespace VNLib.Utils.Memory /// a character span etc /// </remarks> /// <exception cref="ObjectDisposedException"></exception> - public int GetHashCode(StringComparison stringComparison) => string.GetHashCode(AsSpan(), stringComparison); + public int GetHashCode(StringComparison stringComparison) + => string.GetHashCode(AsSpan(), stringComparison); ///<inheritdoc/> protected override void Free() => Handle?.Dispose(); diff --git a/lib/Utils/src/Native/SafeLibraryHandle.cs b/lib/Utils/src/Native/SafeLibraryHandle.cs index 4b4ead4..263ac0c 100644 --- a/lib/Utils/src/Native/SafeLibraryHandle.cs +++ b/lib/Utils/src/Native/SafeLibraryHandle.cs @@ -39,7 +39,8 @@ namespace VNLib.Utils.Native ///<inheritdoc/> public override bool IsInvalid => handle == IntPtr.Zero; - private SafeLibraryHandle(IntPtr libHandle, bool ownsHandle) : base(IntPtr.Zero, ownsHandle) => SetHandle(libHandle); + private SafeLibraryHandle(IntPtr libHandle, bool ownsHandle) : base(IntPtr.Zero, ownsHandle) + => SetHandle(libHandle); /// <summary> /// Loads a native function pointer from the library of the specified name and diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs index 8880010..32d8883 100644 --- a/lib/Utils/tests/Memory/MemoryHandleTest.cs +++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs @@ -23,7 +23,6 @@ */ using System; -using System.Runtime.CompilerServices; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -89,6 +88,7 @@ namespace VNLib.Utils.Memory.Tests handle.Span[120] = 10; Assert.IsTrue(*handle.GetOffset(120) == 10); + Assert.IsTrue(handle.GetOffsetRef(120) == 10); } diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index 68bc35c..bdf8a02 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -33,39 +34,54 @@ namespace VNLib.Utils.Memory.Tests //TODO verify the heap type by loading a dynamic heap dll } - [TestMethod()] - public void UnsafeZeroMemoryTest() - { - //Get random data buffer as a readonly span - ReadOnlyMemory<byte> buffer = RandomNumberGenerator.GetBytes(1024); + private static bool AllZero<T>(Span<T> span) where T : struct + => AllZero((ReadOnlySpan<T>)span); - //confirm buffer is not all zero - Assert.IsFalse(AllZero(buffer.Span)); - - //Zero readonly memory - MemoryUtil.UnsafeZeroMemory(buffer); - - //Confirm all zero - Assert.IsTrue(AllZero(buffer.Span)); - } - - private static bool AllZero(ReadOnlySpan<byte> span) + private static bool AllZero<T>(ReadOnlySpan<T> span) + where T : struct { - for (int i = 0; i < span.Length; i++) + ReadOnlySpan<byte> asBytes = MemoryMarshal.Cast<T, byte>(span); + + for (int i = 0; i < asBytes.Length; i++) { - if (span[i] != 0) + if (asBytes[i] != 0) { return false; } } + return true; } [TestMethod()] - public void UnsafeZeroMemoryTest1() + public void UnsafeZeroMemoryTest() { - //Get random data buffer as a readonly span - ReadOnlySpan<byte> buffer = RandomNumberGenerator.GetBytes(1024); + TestZeroWithDataType<byte>(); + TestZeroWithDataType<sbyte>(); + TestZeroWithDataType<short>(); + TestZeroWithDataType<ushort>(); + TestZeroWithDataType<int>(); + TestZeroWithDataType<uint>(); + TestZeroWithDataType<long>(); + TestZeroWithDataType<ulong>(); + TestZeroWithDataType<float>(); + TestZeroWithDataType<double>(); + TestZeroWithDataType<decimal>(); + TestZeroWithDataType<char>(); + TestZeroWithDataType<bool>(); + TestZeroWithDataType<nint>(); + TestZeroWithDataType<nuint>(); + TestZeroWithDataType<TestStruct>(); + } + + + private static void TestZeroWithDataType<T>() + where T : struct + { + Trace.WriteLine($"Testing unsafe zero with data type {typeof(T).Name}"); + + //Get a random buffer that is known to not be all zeros of a given data type + ReadOnlySpan<T> buffer = MemoryMarshal.Cast<byte, T>(RandomNumberGenerator.GetBytes(1024)); //confirm buffer is not all zero Assert.IsFalse(AllZero(buffer)); @@ -545,7 +561,7 @@ namespace VNLib.Utils.Memory.Tests Assert.IsTrue(byteSize == (nuint)Environment.SystemPageSize); } - using(IMemoryHandle<byte> safeByteBuffer = MemoryUtil.SafeAllocNearestPage(TEST_1, false)) + using (IMemoryHandle<byte> safeByteBuffer = MemoryUtil.SafeAllocNearestPage(TEST_1, false)) { nuint byteSize = MemoryUtil.ByteSize(safeByteBuffer); @@ -712,7 +728,7 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.Copy(ReadOnlyMemory<byte>.Empty, 0, null, 0, 1)); Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.Copy(ReadOnlySpan<byte>.Empty, 0, null, 0, 1)); - + Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.CopyArray((IMemoryHandle<byte>)null, 0, testArray, 0, 1)); Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.CopyArray(testHandle, 0, null, 0, 1)); @@ -736,7 +752,7 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(testHandle, 0, Array.Empty<byte>(), 0, 1)); Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(Array.Empty<byte>(), 0, Array.Empty<byte>(), 0, 1)); - + /* @@ -826,10 +842,10 @@ namespace VNLib.Utils.Memory.Tests MemoryUtil.CopyArray(testArray, 0, testHandle, 0, 0); MemoryUtil.CopyArray(testArray, 0, [], 0, 0); - /* - * Test negative values for span/memory overloads that - * accept integers - */ + /* + * Test negative values for span/memory overloads that + * accept integers + */ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, -1, testMem2, 0, 16)); Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2, -1, 16)); @@ -845,5 +861,49 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem.Span, -1, testHandle, 0, 16)); Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem.Span, 0, testHandle, 0, -1)); } + + [TestMethod] + public unsafe void ByteSizeTest() + { + Assert.AreEqual( + MemoryUtil.ByteCount<byte>(16), + actual: 16 + ); + + Assert.AreEqual( + MemoryUtil.ByteCount<int>(16), + actual: 16 * sizeof(int) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount<long>(16), + actual: 16 * sizeof(long) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount<float>(16), + actual: 16 * sizeof(float) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount<double>(16), + actual: 16 * sizeof(double) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount<nint>(16), + actual: 16 * sizeof(nint) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount<TestStruct>(16), + actual: 16 * sizeof(TestStruct) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount<TestStruct>(0), + actual: 0 + ); + } } }
\ No newline at end of file |