aboutsummaryrefslogtreecommitdiff
path: root/lib/Utils/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Utils/src')
-rw-r--r--lib/Utils/src/ArgumentList.cs99
-rw-r--r--lib/Utils/src/Async/AsyncQueue.cs8
-rw-r--r--lib/Utils/src/Async/IAsyncQueue.cs11
-rw-r--r--lib/Utils/src/Extensions/MemoryExtensions.cs119
-rw-r--r--lib/Utils/src/Extensions/ThreadingExtensions.cs75
-rw-r--r--lib/Utils/src/IO/ArrayPoolStreamBuffer.cs26
-rw-r--r--lib/Utils/src/IO/IVnTextReader.cs7
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs109
-rw-r--r--lib/Utils/src/IO/VnStreamWriter.cs36
-rw-r--r--lib/Utils/src/IO/VnTextReaderExtensions.cs4
-rw-r--r--lib/Utils/src/Memory/MemoryHandle.cs8
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs2
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs52
-rw-r--r--lib/Utils/src/Memory/MemoryUtilAlloc.cs378
-rw-r--r--lib/Utils/src/Memory/UnmanagedHeapBase.cs16
-rw-r--r--lib/Utils/src/Memory/UnsafeMemoryHandle.cs49
-rw-r--r--lib/Utils/src/VnEncoding.cs365
17 files changed, 806 insertions, 558 deletions
diff --git a/lib/Utils/src/ArgumentList.cs b/lib/Utils/src/ArgumentList.cs
index c02ebee..0f092c6 100644
--- a/lib/Utils/src/ArgumentList.cs
+++ b/lib/Utils/src/ArgumentList.cs
@@ -41,18 +41,16 @@ namespace VNLib.Utils
/// </summary>
/// <param name="args">The array of arguments to clone</param>
/// <exception cref="ArgumentNullException"></exception>
- public ArgumentList(string[] args)
- {
- ArgumentNullException.ThrowIfNull(args);
- _args = args.ToList();
- }
+ [Obsolete("Deprecated in preference to ctor(IEnumerable<string>)")]
+ public ArgumentList(string[] args) : this(args as IEnumerable<string>)
+ { }
/// <summary>
/// Initalizes the argument parser by copying the given argument list
/// </summary>
/// <param name="args">The argument list to clone</param>
/// <exception cref="ArgumentNullException"></exception>
- public ArgumentList(IReadOnlyList<string> args)
+ public ArgumentList(IEnumerable<string> args)
{
ArgumentNullException.ThrowIfNull(args);
_args = args.ToList();
@@ -73,9 +71,9 @@ namespace VNLib.Utils
/// <summary>
/// Determines of the given argument is present in the argument list
/// </summary>
- /// <param name="arg"></param>
+ /// <param name="arg">The name of the argument to check existence of</param>
/// <returns>A value that indicates if the argument is present in the list</returns>
- public bool HasArgument(string arg) => _args.Contains(arg);
+ public bool HasArgument(string arg) => HasArgument(_args, arg);
/// <summary>
/// Determines if the argument is present in the argument list and
@@ -91,16 +89,91 @@ namespace VNLib.Utils
/// </summary>
/// <param name="arg">The argument to get following value of</param>
/// <returns>The argument value if found</returns>
- public string? GetArgument(string arg)
- {
- int index = _args.IndexOf(arg);
- return index == -1 || index + 1 >= _args.Count ? null : this[index + 1];
- }
+ public string? GetArgument(string arg) => GetArgument(_args, arg);
///<inheritdoc/>
public IEnumerator<string> GetEnumerator() => _args.GetEnumerator();
///<inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ /// <summary>
+ /// Captures the command line arguments from the currently executing process
+ /// and returns them as an ArgumentList
+ /// </summary>
+ /// <returns>The <see cref="ArgumentList"/> containing the current process's argument list</returns>
+ public static ArgumentList CaptureCurrentArgs()
+ {
+ /*
+ * Capture the current command line arguments and
+ * pop the first argument which is always the program
+ * name
+ */
+ string[] strings = Environment.GetCommandLineArgs();
+ return new ArgumentList(strings.Skip(1));
+ }
+
+ /// <summary>
+ /// Determines of the given argument is present in the argument list
+ /// </summary>
+ /// <param name="argsList">The collection to search for the arugment within</param>
+ /// <param name="argName">The name of the argument to check existence of</param>
+ /// <returns>A value that indicates if the argument is present in the list</returns>
+ public static bool HasArgument<T>(T argsList, string argName) where T: IEnumerable<string>
+ => argsList.Contains(argName, StringComparer.OrdinalIgnoreCase);
+
+ /// <summary>
+ /// Determines if the argument is present in the argument list and
+ /// has a non-null value following it.
+ /// </summary>
+ /// <param name="argsList">The collection to search for the arugment within</param>
+ /// <param name="argName">The name of the argument to check existence of</param>
+ /// <returns>A value that indicates if a non-null argument is present in the list</returns>
+ public static bool HasArgumentValue<T>(T argsList, string argName) where T : IEnumerable<string>
+ => GetArgument(argsList, argName) != null;
+
+ /// <summary>
+ /// Gets the value following the specified argument, or
+ /// null no value follows the specified argument
+ /// </summary>
+ /// <param name="argsList">The collection to search for the arugment within</param>
+ /// <param name="argName">The name of the argument to check existence of</param>
+ /// <returns>The argument value if found</returns>
+ public static string? GetArgument<T>(T argsList, string argName) where T : IEnumerable<string>
+ {
+ ArgumentNullException.ThrowIfNull(argsList);
+
+ /*
+ * Try to optimize some fetching for types that have
+ * better performance for searching/indexing
+ */
+ if (argsList is IList<string> argList)
+ {
+ int index = argList.IndexOf(argName);
+ return index == -1 || index + 1 >= argList.Count
+ ? null
+ : argList[index + 1];
+ }
+ else if(argsList is string[] argsArr)
+ {
+ return findInArray(argsArr, argName);
+ }
+ else
+ {
+ //TODO use linq instead of converting to array on every call
+ return findInArray(
+ argsList.ToArray(),
+ argName
+ );
+ }
+
+ static string? findInArray(string[] argsArr, string argName)
+ {
+ int index = Array.IndexOf(argsArr, argName);
+ return index == -1 || index + 1 >= argsArr.Length
+ ? null
+ : argsArr[index + 1];
+ }
+ }
}
} \ No newline at end of file
diff --git a/lib/Utils/src/Async/AsyncQueue.cs b/lib/Utils/src/Async/AsyncQueue.cs
index e94d08e..a64733f 100644
--- a/lib/Utils/src/Async/AsyncQueue.cs
+++ b/lib/Utils/src/Async/AsyncQueue.cs
@@ -60,7 +60,9 @@ namespace VNLib.Utils.Async
/// </summary>
/// <param name="singleWriter">A value that specifies only a single thread be enqueing items?</param>
/// <param name="singleReader">A value that specifies only a single thread will be dequeing</param>
- /// <param name="capacity">The maxium number of items to enque without failing</param>
+ /// <param name="capacity">
+ /// The maxium number of items to enque without failing. If set to <see cref="int.MaxValue"/> maximum is disabled
+ /// </param>
public AsyncQueue(bool singleWriter, bool singleReader, int capacity = int.MaxValue)
{
if(capacity == int.MaxValue)
@@ -102,7 +104,7 @@ namespace VNLib.Utils.Async
public AsyncQueue(BoundedChannelOptions options) => _channel = Channel.CreateBounded<T>(options);
/// <inheritdoc/>
- public bool TryEnque(T item) => _channel.Writer.TryWrite(item);
+ public bool TryEnqueue(T item) => _channel.Writer.TryWrite(item);
/// <inheritdoc/>
/// <exception cref="ObjectDisposedException"></exception>
@@ -119,5 +121,7 @@ namespace VNLib.Utils.Async
/// <inheritdoc/>
/// <exception cref="ObjectDisposedException"></exception>
public bool TryPeek([MaybeNullWhen(false)] out T result) => _channel.Reader.TryPeek(out result);
+
+ bool IAsyncQueue<T>.TryEnque(T item) => TryEnqueue(item);
}
}
diff --git a/lib/Utils/src/Async/IAsyncQueue.cs b/lib/Utils/src/Async/IAsyncQueue.cs
index ab786f1..677f34f 100644
--- a/lib/Utils/src/Async/IAsyncQueue.cs
+++ b/lib/Utils/src/Async/IAsyncQueue.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -22,6 +22,7 @@
* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
*/
+using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics.CodeAnalysis;
@@ -41,6 +42,14 @@ namespace VNLib.Utils.Async
/// </summary>
/// <param name="item">The item to eqneue</param>
/// <returns>True if the queue can accept another item, false otherwise</returns>
+ bool TryEnqueue(T item);
+
+ /// <summary>
+ /// Attemts to enqueue an item if the queue has the capacity
+ /// </summary>
+ /// <param name="item">The item to eqneue</param>
+ /// <returns>True if the queue can accept another item, false otherwise</returns>
+ [Obsolete("Use TryEnqueue instead")]
bool TryEnque(T item);
/// <summary>
diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs
index c433527..f2399a2 100644
--- a/lib/Utils/src/Extensions/MemoryExtensions.cs
+++ b/lib/Utils/src/Extensions/MemoryExtensions.cs
@@ -48,19 +48,8 @@ namespace VNLib.Utils.Extensions
/// <param name="size">The minimum size array to allocate</param>
/// <param name="zero">Should elements from 0 to size be set to default(T)</param>
/// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
- public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged
- {
- ArgumentNullException.ThrowIfNull(pool);
-
- T[] array = pool.Rent(size);
-
- if (zero)
- {
- MemoryUtil.InitializeBlock(array, (uint)size);
- }
-
- return new(pool, array, size);
- }
+ public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged
+ => MemoryUtil.UnsafeAlloc<T>(pool, size, zero);
/// <summary>
/// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the
@@ -72,19 +61,7 @@ namespace VNLib.Utils.Extensions
/// <param name="zero">Should elements from 0 to size be set to default(T)</param>
/// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
public static IMemoryHandle<T> SafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : struct
- {
- ArgumentNullException.ThrowIfNull(pool);
-
- T[] array = pool.Rent(size);
-
- if (zero)
- {
- MemoryUtil.InitializeBlock(array, (uint)size);
- }
-
- //Use the array pool buffer wrapper to return the array to the pool when the handle is disposed
- return new ArrayPoolBuffer<T>(pool, array, size);
- }
+ => MemoryUtil.SafeAlloc<T>(pool, size, zero);
/// <summary>
/// Retreives a buffer that is at least the reqested length, and clears the array from 0-size.
@@ -111,13 +88,6 @@ namespace VNLib.Utils.Extensions
}
/// <summary>
- /// Copies the characters within the memory handle to a <see cref="string"/>
- /// </summary>
- /// <returns>The string representation of the buffer</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string ToString<T>(this T charBuffer) where T : IMemoryHandle<char> => charBuffer.Span.ToString();
-
- /// <summary>
/// Wraps the <see cref="IMemoryHandle{T}"/> instance in System.Buffers.MemoryManager
/// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles.
/// </summary>
@@ -131,7 +101,8 @@ namespace VNLib.Utils.Extensions
/// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MemoryManager<T> ToMemoryManager<T>(this IMemoryHandle<T> handle, bool ownsHandle) => new SysBufferMemoryManager<T>(handle, ownsHandle);
+ public static MemoryManager<T> ToMemoryManager<T>(this IMemoryHandle<T> handle, bool ownsHandle)
+ => new SysBufferMemoryManager<T>(handle, ownsHandle);
/// <summary>
/// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="IUnmangedHeap"/> instance
@@ -168,7 +139,8 @@ namespace VNLib.Utils.Extensions
/// </returns>
/// <exception cref="OverflowException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int GetIntLength<T>(this IMemoryHandle<T> handle) => Convert.ToInt32(handle.Length);
+ public static int GetIntLength<T>(this IMemoryHandle<T> handle)
+ => Convert.ToInt32(handle.Length);
/// <summary>
/// Gets the integer length (number of elements) of the <see cref="UnsafeMemoryHandle{T}"/>
@@ -181,7 +153,8 @@ namespace VNLib.Utils.Extensions
/// </returns>
//Method only exists for consistancy since unsafe handles are always 32bit
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T : unmanaged => handle.IntLength;
+ public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T : unmanaged
+ => handle.IntLength;
/// <summary>
/// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks
@@ -295,6 +268,23 @@ namespace VNLib.Utils.Extensions
}
/// <summary>
+ /// Gets a reference to the element at the specified offset from the base
+ /// address of the <see cref="MemoryHandle{T}"/> and casts it to a byte reference
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="block"></param>
+ /// <param name="offset">The number of elements to offset the base reference by</param>
+ /// <returns>The reinterpreted byte reference at the first byte of the element offset</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref byte GetOffsetByteRef<T>(this IMemoryHandle<T> block, nint offset)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(offset);
+ return ref GetOffsetByteRef(block, (nuint)offset);
+ }
+
+ /// <summary>
/// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/>
/// </summary>
/// <typeparam name="T"></typeparam>
@@ -440,16 +430,8 @@ namespace VNLib.Utils.Extensions
/// <exception cref="ArgumentException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
- {
- ArgumentNullException.ThrowIfNull(heap);
- //Minimum of one element
- elements = Math.Max(elements, 1);
- //If zero flag is set then specify zeroing memory
- IntPtr block = heap.Alloc(elements, (nuint)sizeof(T), zero);
- //Return handle wrapper
- return new MemoryHandle<T>(heap, block, elements, zero);
- }
+ public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
+ => MemoryUtil.SafeAlloc<T>(heap, elements, zero);
/// <summary>
/// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
@@ -464,10 +446,7 @@ namespace VNLib.Utils.Extensions
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nint elements, bool zero = false) where T : unmanaged
- {
- ArgumentOutOfRangeException.ThrowIfNegative(elements);
- return Alloc<T>(heap, (nuint)elements, zero);
- }
+ => MemoryUtil.SafeAlloc<T>(heap, elements, zero);
/// <summary>
/// Allocates a buffer from the current heap and initialzies it by copying the initial data buffer
@@ -481,10 +460,8 @@ namespace VNLib.Utils.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlySpan<T> initialData) where T : unmanaged
{
- //Aloc block
- MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length);
-
- //Copy initial data
+ MemoryHandle<T> handle = Alloc<T>(heap, initialData.Length);
+
MemoryUtil.Copy(initialData, 0, handle, 0, initialData.Length);
return handle;
@@ -502,10 +479,8 @@ namespace VNLib.Utils.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlyMemory<T> initialData) where T : unmanaged
{
- //Aloc block
- MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length);
-
- //Copy initial data
+ MemoryHandle<T> handle = Alloc<T>(heap, initialData.Length);
+
MemoryUtil.Copy(initialData, 0, handle, 0, initialData.Length);
return handle;
@@ -542,25 +517,8 @@ namespace VNLib.Utils.Extensions
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
- {
- ArgumentNullException.ThrowIfNull(heap);
-
- if (elements < 1)
- {
- //Return an empty handle
- return new UnsafeMemoryHandle<T>();
- }
-
- //Get element size
- nuint elementSize = (nuint)Unsafe.SizeOf<T>();
-
- //If zero flag is set then specify zeroing memory (safe case because of the above check)
- IntPtr block = heap.Alloc((nuint)elements, elementSize, zero);
-
- //handle wrapper
- return new (heap, block, elements);
- }
+ public unsafe static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
+ => MemoryUtil.UnsafeAlloc<T>(heap, elements, zero);
#region VnBufferWriter
@@ -775,13 +733,6 @@ namespace VNLib.Utils.Extensions
}
/// <summary>
- /// Converts the buffer data to a <see cref="PrivateString"/>
- /// </summary>
- /// <returns>A <see cref="PrivateString"/> instance that owns the underlying string memory</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static PrivateString ToPrivate(this ref ForwardOnlyWriter<char> buffer) => new(buffer.ToString(), true);
-
- /// <summary>
/// Gets a <see cref="Span{T}"/> over the modified section of the internal buffer
/// </summary>
/// <returns>A <see cref="Span{T}"/> over the modified data</returns>
diff --git a/lib/Utils/src/Extensions/ThreadingExtensions.cs b/lib/Utils/src/Extensions/ThreadingExtensions.cs
index a80a0ae..c29ab63 100644
--- a/lib/Utils/src/Extensions/ThreadingExtensions.cs
+++ b/lib/Utils/src/Extensions/ThreadingExtensions.cs
@@ -38,20 +38,6 @@ namespace VNLib.Utils.Extensions
public static class ThreadingExtensions
{
/// <summary>
- /// Allows an <see cref="OpenResourceHandle{TResource}"/> to execute within a scope limited context
- /// </summary>
- /// <typeparam name="TResource">The resource type</typeparam>
- /// <param name="rh"></param>
- /// <param name="safeCallback">The function body that will execute with controlled access to the resource</param>
- public static void EnterSafeContext<TResource>(this OpenResourceHandle<TResource> rh, Action<TResource> safeCallback)
- {
- using (rh)
- {
- safeCallback(rh.Resource);
- }
- }
-
- /// <summary>
/// Waits for exlcusive access to the resource identified by the given moniker
/// and returns a handle that will release the lock when disposed.
/// </summary>
@@ -106,6 +92,7 @@ namespace VNLib.Utils.Extensions
await semaphore.WaitAsync(cancellationToken);
return new SemSlimReleaser(semaphore);
}
+
/// <summary>
/// Asynchronously waits to enter the <see cref="SemaphoreSlim"/> using a 32-bit signed integer to measure the time intervale
/// and getting a releaser handle
@@ -135,6 +122,7 @@ namespace VNLib.Utils.Extensions
semaphore.Wait();
return new SemSlimReleaser(semaphore);
}
+
/// <summary>
/// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>
/// </summary>
@@ -164,6 +152,7 @@ namespace VNLib.Utils.Extensions
mutex.WaitOne();
return new MutexReleaser(mutex);
}
+
/// <summary>
/// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>
/// </summary>
@@ -201,6 +190,7 @@ namespace VNLib.Utils.Extensions
public static Task<bool> WaitAsync(this WaitHandle handle, int timeoutMs = Timeout.Infinite)
{
ArgumentNullException.ThrowIfNull(handle);
+
//test non-blocking handle state
if (handle.WaitOne(0))
{
@@ -223,13 +213,64 @@ namespace VNLib.Utils.Extensions
return TrueCompleted;
}
}
+
+ return NoSpinWaitAsync(handle, timeoutMs);
+ }
+
+ /// <summary>
+ /// Asynchronously waits for a the <see cref="WaitHandle"/> to receive a signal. This method spins until
+ /// a thread yield will occur, then asynchronously yields.
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <param name="timeoutMs">The timeout interval in milliseconds</param>
+ /// <param name="cancellation">A <see cref="CancellationToken"/> used to cancel the asynct wait event</param>
+ /// <returns>
+ /// A task that compeletes when the wait handle receives a signal or times-out,
+ /// the result of the awaited task will be <c>true</c> if the signal is received, or
+ /// <c>false</c> if the timeout interval expires
+ /// </returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static Task<bool> WaitAsync(this WaitHandle handle, int timeoutMs, CancellationToken cancellation = default)
+ {
+ Task<bool> withoutToken = WaitAsync(handle, timeoutMs);
+
+ return withoutToken.IsCompleted
+ ? withoutToken
+ : withoutToken.WaitAsync(cancellation);
+ }
+
+ /// <summary>
+ /// Asynchronously waits for a the <see cref="WaitHandle"/> to receive a signal, without checking
+ /// current state or spinning. This function always returns a new task that will complete when the
+ /// handle is signaled or the timeout interval expires.
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <param name="timeoutMs">Time (in ms)</param>
+ /// <returns></returns>
+ public static Task<bool> NoSpinWaitAsync(this WaitHandle handle, int timeoutMs)
+ {
//Completion source used to signal the awaiter when the wait handle is signaled
TaskCompletionSource<bool> completion = new(TaskCreationOptions.None);
+
//Register wait on threadpool to complete the task source
- RegisteredWaitHandle registration = ThreadPool.RegisterWaitForSingleObject(handle, TaskCompletionCallback, completion, timeoutMs, true);
+ RegisteredWaitHandle registration = ThreadPool.RegisterWaitForSingleObject(
+ handle,
+ TaskCompletionCallback,
+ completion,
+ timeoutMs, executeOnlyOnce: true
+ );
+
//Register continuation to cleanup
- _ = completion.Task.ContinueWith(CleanupContinuation, registration, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default)
- .ConfigureAwait(false);
+ _ = completion.Task.ContinueWith(
+ CleanupContinuation,
+ registration,
+ CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously,
+ TaskScheduler.Default
+ ).ConfigureAwait(false);
+
return completion.Task;
}
diff --git a/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs b/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs
index b62412f..a943a5b 100644
--- a/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs
+++ b/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -28,7 +28,13 @@ using System.Buffers;
namespace VNLib.Utils.IO
{
- internal class ArrayPoolStreamBuffer<T> : ISlindingWindowBuffer<T>
+ /// <summary>
+ /// Creates a new <see cref="ArrayPoolStreamBuffer{T}"/> from the
+ /// given array instance and <see cref="ArrayPool{T}"/> it came from.
+ /// </summary>
+ /// <param name="array">The rented array to use</param>
+ /// <param name="pool">The pool to return the array to when completed</param>
+ internal class ArrayPoolStreamBuffer<T>(T[] array, ArrayPool<T> pool) : ISlindingWindowBuffer<T>
{
/// <summary>
/// The shared <see cref="IStreamBufferFactory{T}"/> instance to allocate buffers
@@ -36,20 +42,8 @@ namespace VNLib.Utils.IO
/// </summary>
public static IStreamBufferFactory<T> Shared { get; } = new DefaultFactory();
- private readonly ArrayPool<T> _pool;
- private T[] _buffer;
-
- /// <summary>
- /// Creates a new <see cref="ArrayPoolStreamBuffer{T}"/> from the
- /// given array instance and <see cref="ArrayPool{T}"/> it came from.
- /// </summary>
- /// <param name="array">The rented array to use</param>
- /// <param name="pool">The pool to return the array to when completed</param>
- public ArrayPoolStreamBuffer(T[] array, ArrayPool<T> pool)
- {
- _pool = pool;
- _buffer = array;
- }
+ private readonly ArrayPool<T> _pool = pool;
+ private T[] _buffer = array;
///<inheritdoc/>
public int WindowStartPos { get; set; }
diff --git a/lib/Utils/src/IO/IVnTextReader.cs b/lib/Utils/src/IO/IVnTextReader.cs
index 625ba78..93de2d1 100644
--- a/lib/Utils/src/IO/IVnTextReader.cs
+++ b/lib/Utils/src/IO/IVnTextReader.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -23,7 +23,6 @@
*/
using System;
-using System.IO;
using System.Text;
namespace VNLib.Utils.IO
@@ -34,10 +33,6 @@ namespace VNLib.Utils.IO
public interface IVnTextReader
{
/// <summary>
- /// The base stream to read data from
- /// </summary>
- Stream BaseStream { get; }
- /// <summary>
/// The character encoding used by the TextReader
/// </summary>
Encoding Encoding { get; }
diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs
index f4bf970..3f14061 100644
--- a/lib/Utils/src/IO/VnMemoryStream.cs
+++ b/lib/Utils/src/IO/VnMemoryStream.cs
@@ -41,15 +41,21 @@ namespace VNLib.Utils.IO
/// </summary>
public sealed class VnMemoryStream : Stream, ICloneable
{
+ public const int DefaultBufferSize = 4096;
+
private nint _position;
private nint _length;
private bool _isReadonly;
//Memory
private readonly IResizeableMemoryHandle<byte> _buffer;
+
//Default owns handle
private readonly bool OwnsHandle = true;
+ //Lazy loaded memory wrapper
+ private MemoryManager<byte>? _memoryWrapper;
+
/// <summary>
/// Creates a new <see cref="VnMemoryStream"/> pointing to the begining of memory, and consumes the handle.
/// </summary>
@@ -78,15 +84,19 @@ namespace VNLib.Utils.IO
ArgumentNullException.ThrowIfNull(handle);
return handle.CanRealloc || readOnly
- ? new VnMemoryStream(handle, length, readOnly, ownsHandle)
+ ? new VnMemoryStream(handle, existingManager: null, length, readOnly, ownsHandle)
: throw new ArgumentException("The supplied memory handle must be resizable on a writable stream", nameof(handle));
}
/// <summary>
/// Converts a writable <see cref="VnMemoryStream"/> to readonly to allow shallow copies
/// </summary>
+ /// <remarks>
+ /// This funciton will convert the stream passed into it to a readonly stream.
+ /// The function passes through the input stream as the return value
+ /// </remarks>
/// <param name="stream">The stream to make readonly</param>
- /// <returns>The readonly stream</returns>
+ /// <returns>A reference to the modified input stream</returns>
public static VnMemoryStream CreateReadonly(VnMemoryStream stream)
{
ArgumentNullException.ThrowIfNull(stream);
@@ -101,15 +111,21 @@ namespace VNLib.Utils.IO
/// global heap instance.
/// </summary>
public VnMemoryStream() : this(MemoryUtil.Shared) { }
-
+
/// <summary>
/// Create a new memory stream where buffers will be allocated from the specified heap
/// </summary>
/// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ArgumentNullException"></exception>
- public VnMemoryStream(IUnmangedHeap heap) : this(heap, 0, false) { }
-
+ public VnMemoryStream(IUnmangedHeap heap) : this(heap, DefaultBufferSize, false) { }
+
+ /// <summary>
+ /// Creates a new memory stream using the <see cref="MemoryUtil.Shared"/>
+ /// global heap instance.
+ /// </summary>
+ public VnMemoryStream(nuint bufferSize, bool zero) : this(MemoryUtil.Shared, bufferSize, zero) { }
+
/// <summary>
/// Creates a new memory stream and pre-allocates the internal
/// buffer of the specified size on the specified heap to avoid resizing.
@@ -163,7 +179,14 @@ namespace VNLib.Utils.IO
/// <param name="length">The length property of the stream</param>
/// <param name="readOnly">Is the stream readonly (should mostly be true!)</param>
/// <param name="ownsHandle">Does the new stream own the memory -> <paramref name="buffer"/></param>
- private VnMemoryStream(IResizeableMemoryHandle<byte> buffer, nint length, bool readOnly, bool ownsHandle)
+ /// <param name="existingManager">A reference to an existing memory manager class</param>
+ private VnMemoryStream(
+ IResizeableMemoryHandle<byte> buffer,
+ MemoryManager<byte>? existingManager,
+ nint length,
+ bool readOnly,
+ bool ownsHandle
+ )
{
Debug.Assert(length >= 0, "Length must be positive");
Debug.Assert(buffer.CanRealloc || readOnly, "The supplied buffer is not resizable on a writable stream");
@@ -172,6 +195,7 @@ namespace VNLib.Utils.IO
_buffer = buffer; //Consume the handle
_length = length; //Store length of the buffer
_isReadonly = readOnly;
+ _memoryWrapper = existingManager;
}
/// <summary>
@@ -189,7 +213,7 @@ namespace VNLib.Utils.IO
//Create a new readonly copy (stream does not own the handle)
return !_isReadonly
? throw new NotSupportedException("This stream is not readonly. Cannot create shallow copy on a mutable stream")
- : new VnMemoryStream(_buffer, _length, true, false);
+ : new VnMemoryStream(_buffer, _memoryWrapper, _length, readOnly: true, ownsHandle: false);
}
/// <summary>
@@ -247,27 +271,31 @@ namespace VNLib.Utils.IO
cancellationToken.ThrowIfCancellationRequested();
+ //Memory manager requires 32bit or less in length
if(_length < Int32.MaxValue)
{
- //Safe to alloc a memory manager to do copy
- using MemoryManager<byte> asMemManager = _buffer.ToMemoryManager(false);
+ //Get/alloc the internal memory manager and get the block
+ ReadOnlyMemory<byte> asMemory = AsMemory();
+
+ Debug.Assert(asMemory.Length >= LenToPosDiff, "Internal memory block smaller than desired for stream copy");
/*
* CopyTo starts at the current position, as if calling Read()
* so the reader must be offset to match and the _length gives us the
* actual length of the stream and therefor the segment size
- */
+ */
- while(LenToPosDiff > 0)
+ while (LenToPosDiff > 0)
{
int blockSize = Math.Min((int)LenToPosDiff, bufferSize);
- Memory<byte> window = asMemManager.Memory.Slice((int)_position, blockSize);
+
+ ReadOnlyMemory<byte> window = asMemory.Slice((int)_position, blockSize);
//write async
await destination.WriteAsync(window, cancellationToken);
//Update position
- _position+= bufferSize;
+ _position += bufferSize;
}
}
else
@@ -354,7 +382,7 @@ namespace VNLib.Utils.IO
}
///<inheritdoc/>
- public override unsafe int ReadByte()
+ public override int ReadByte()
{
if (LenToPosDiff == 0)
{
@@ -362,7 +390,7 @@ namespace VNLib.Utils.IO
}
//get the value at the current position
- ref byte ptr = ref _buffer.GetOffsetByteRef((nuint)_position);
+ ref byte ptr = ref _buffer.GetOffsetByteRef(_position);
//Increment position
_position++;
@@ -486,7 +514,7 @@ namespace VNLib.Utils.IO
throw new NotSupportedException("Write operation is not allowed on readonly stream!");
}
//Calculate the new final position
- nint newPos = (_position + buffer.Length);
+ nint newPos = checked(_position + buffer.Length);
//Determine if the buffer needs to be expanded
if (buffer.Length > LenToPosDiff)
{
@@ -495,8 +523,16 @@ namespace VNLib.Utils.IO
//Update length
_length = newPos;
}
+
//Copy the input buffer to the internal buffer
- MemoryUtil.Copy(buffer, 0, _buffer, (nuint)_position, buffer.Length);
+ MemoryUtil.Copy(
+ source: buffer,
+ sourceOffset: 0,
+ dest: _buffer,
+ destOffset: (nuint)_position,
+ count: buffer.Length
+ );
+
//Update the position
_position = newPos;
}
@@ -550,6 +586,7 @@ namespace VNLib.Utils.IO
/// <summary>
/// Returns a <see cref="ReadOnlySpan{T}"/> window over the data within the entire stream
+ /// that is equal in length to the stream length.
/// </summary>
/// <returns>A <see cref="ReadOnlySpan{T}"/> of the data within the entire stream</returns>
/// <exception cref="OverflowException"></exception>
@@ -560,6 +597,40 @@ namespace VNLib.Utils.IO
//Get span with no offset
return _buffer.AsSpan(0, len);
}
+
+ /// <summary>
+ /// Returns a <see cref="ReadOnlyMemory{T}"/> structure which is a window of the buffered
+ /// data as it currently sits. For writeable straems, you must call this function
+ /// every time the size of the stream changes. The memory structure is just a "pointer" to
+ /// the internal buffer.
+ /// </summary>
+ /// <returns>
+ /// A memory snapshot of the stream.
+ /// </returns>
+ /// <remarks>
+ /// This function causes an internal allocation on the first call. After the first call
+ /// to this function, all calls are thread-safe.
+ /// </remarks>
+ public ReadOnlyMemory<byte> AsMemory()
+ {
+ /*
+ * Safe cast stram length to int, because memory window requires a 32bit
+ * integer. Also will throw before allocating the mmemory manager
+ */
+
+ int len = Convert.ToInt32(_length);
+
+ //Defer/lazy init the memory manager
+ MemoryManager<byte> asMemory = LazyInitializer.EnsureInitialized(ref _memoryWrapper, AllocMemManager);
+
+ Debug.Assert(asMemory != null);
+
+ /*
+ * Buffer window may be larger than the actual stream legnth, so
+ * slice the memory to the actual length of the stream
+ */
+ return asMemory.Memory[..len];
+ }
/// <summary>
/// If the current stream is a readonly stream, creates a shallow copy for reading only.
@@ -568,5 +639,7 @@ namespace VNLib.Utils.IO
/// <exception cref="NotSupportedException"></exception>
public object Clone() => GetReadonlyShallowCopy();
+ private MemoryManager<byte> AllocMemManager() => _buffer.ToMemoryManager(false);
+
}
-} \ No newline at end of file
+}
diff --git a/lib/Utils/src/IO/VnStreamWriter.cs b/lib/Utils/src/IO/VnStreamWriter.cs
index ddebc07..7d43f48 100644
--- a/lib/Utils/src/IO/VnStreamWriter.cs
+++ b/lib/Utils/src/IO/VnStreamWriter.cs
@@ -41,21 +41,29 @@ namespace VNLib.Utils.IO
/// Provides a memory optimized <see cref="TextWriter"/> implementation. Optimized for writing
/// to network streams
/// </summary>
- public class VnStreamWriter : TextWriter
+ /// <remarks>
+ /// Creates a new <see cref="VnStreamWriter"/> that writes encoded data to the base stream
+ /// and uses the specified buffer.
+ /// </remarks>
+ /// <param name="baseStream">The underlying stream to write data to</param>
+ /// <param name="encoding">The <see cref="Encoding"/> to use when writing to the stream</param>
+ /// <param name="buffer">The internal <see cref="ISlindingWindowBuffer{T}"/> to use</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public class VnStreamWriter(Stream baseStream, Encoding encoding, ISlindingWindowBuffer<byte> buffer) : TextWriter
{
- private readonly Encoder Enc;
+ private readonly Encoder Enc = encoding.GetEncoder();
- private readonly ISlindingWindowBuffer<byte> _buffer;
+ private readonly ISlindingWindowBuffer<byte> _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
private bool closed;
/// <summary>
/// Gets the underlying stream that interfaces with the backing store
/// </summary>
- public virtual Stream BaseStream { get; }
+ public virtual Stream BaseStream { get; } = baseStream ?? throw new ArgumentNullException(nameof(buffer));
///<inheritdoc/>
- public override Encoding Encoding { get; }
+ public override Encoding Encoding { get; } = encoding ?? throw new ArgumentNullException(nameof(encoding));
/// <summary>
/// Line termination to use when writing lines to the output
@@ -97,24 +105,6 @@ namespace VNLib.Utils.IO
{
}
- /// <summary>
- /// Creates a new <see cref="VnStreamWriter"/> that writes encoded data to the base stream
- /// and uses the specified buffer.
- /// </summary>
- /// <param name="baseStream">The underlying stream to write data to</param>
- /// <param name="encoding">The <see cref="Encoding"/> to use when writing to the stream</param>
- /// <param name="buffer">The internal <see cref="ISlindingWindowBuffer{T}"/> to use</param>
- /// <exception cref="ArgumentNullException"></exception>
- public VnStreamWriter(Stream baseStream, Encoding encoding, ISlindingWindowBuffer<byte> buffer)
- {
- BaseStream = baseStream ?? throw new ArgumentNullException(nameof(buffer));
- Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
- _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
-
- //Get an encoder
- Enc = encoding.GetEncoder();
- }
-
///<inheritdoc/>
public void Write(byte value)
{
diff --git a/lib/Utils/src/IO/VnTextReaderExtensions.cs b/lib/Utils/src/IO/VnTextReaderExtensions.cs
index 9ca5ae5..5dc6117 100644
--- a/lib/Utils/src/IO/VnTextReaderExtensions.cs
+++ b/lib/Utils/src/IO/VnTextReaderExtensions.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -66,6 +66,7 @@ namespace VNLib.Utils.IO
/// <remarks>Allows reading lines of data from the stream without allocations</remarks>
public static ERRNO ReadLine<T>(this T reader, Span<char> charBuffer) where T : class, IVnTextReader
{
+ ArgumentNullException.ThrowIfNull(reader);
return ReadLineInternal(ref reader, charBuffer);
}
@@ -118,6 +119,7 @@ namespace VNLib.Utils.IO
/// <remarks>You should use the <see cref="IVnTextReader.Available"/> property to know how much remaining data is buffered</remarks>
public static int ReadRemaining<T>(this T reader, Span<byte> buffer) where T : class, IVnTextReader
{
+ ArgumentNullException.ThrowIfNull(reader);
return ReadRemainingInternal(ref reader, buffer);
}
diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs
index 16fc555..fbaae95 100644
--- a/lib/Utils/src/Memory/MemoryHandle.cs
+++ b/lib/Utils/src/Memory/MemoryHandle.cs
@@ -229,8 +229,7 @@ namespace VNLib.Utils.Memory
//If adding ref failed, the handle is closed
ObjectDisposedException.ThrowIf(!addRef, this);
-
- //Create a new system.buffers memory handle from the offset ptr address
+
return new MemoryHandle(ptr, pinnable: this);
}
@@ -250,7 +249,10 @@ namespace VNLib.Utils.Memory
/// <exception cref="ObjectDisposedException"></exception>
public bool Equals(MemoryHandle<T>? other)
{
- return other != null && (IsClosed | other.IsClosed) == false && _length == other._length && handle == other.handle;
+ return other != null
+ && (IsClosed | other.IsClosed) == false
+ && _length == other._length
+ && handle == other.handle;
}
///<inheritdoc/>
diff --git a/lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs b/lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs
index 9decef7..6899877 100644
--- a/lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.CopyUtilCore.cs
@@ -308,4 +308,4 @@ namespace VNLib.Utils.Memory
}
}
}
-} \ No newline at end of file
+}
diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs
index 7ce7c81..09c36d5 100644
--- a/lib/Utils/src/Memory/MemoryUtil.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.cs
@@ -143,7 +143,8 @@ namespace VNLib.Utils.Memory
* get the heap's stats, otherwise return an empty handle
*/
return _lazyHeap.IsLoaded && _lazyHeap.Instance is TrackedHeapWrapper h
- ? h.GetCurrentStats() : default;
+ ? h.GetCurrentStats()
+ : default;
}
/// <summary>
@@ -518,7 +519,8 @@ namespace VNLib.Utils.Memory
/// <param name="target">A pointer to initialized target structure to copy data to</param>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void CopyStruct<T>(ref readonly byte source, void* target) where T : unmanaged => CopyStruct(in source, (T*)target);
+ public static void CopyStruct<T>(ref readonly byte source, void* target) where T : unmanaged
+ => CopyStruct(in source, (T*)target);
/// <summary>
/// Copies structure data from a source sequence of data to the target structure reference.
@@ -547,7 +549,8 @@ namespace VNLib.Utils.Memory
/// <param name="target">A pointer to initialized target structure to copy data to</param>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void CopyStruct<T>(ReadOnlySpan<byte> sourceData, T* target) where T : unmanaged => CopyStruct(sourceData, ref *target);
+ public static void CopyStruct<T>(ReadOnlySpan<byte> sourceData, T* target) where T : unmanaged
+ => CopyStruct(sourceData, ref *target);
/// <summary>
/// Copies structure data from a source sequence of data to the target structure reference.
@@ -557,7 +560,8 @@ namespace VNLib.Utils.Memory
/// <param name="target">A pointer to initialized target structure to copy data to</param>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void CopyStruct<T>(ReadOnlySpan<byte> sourceData, void* target) where T: unmanaged => CopyStruct(sourceData, (T*)target);
+ public static void CopyStruct<T>(ReadOnlySpan<byte> sourceData, void* target) where T: unmanaged
+ => CopyStruct(sourceData, (T*)target);
/// <summary>
@@ -742,7 +746,7 @@ namespace VNLib.Utils.Memory
ref Refs.AsByte(source, (nuint)sourceOffset),
ref Refs.AsByte(dest, destOffset),
ByteCount<T>((uint)count),
- false
+ forceAcceleration: false
);
}
@@ -773,7 +777,7 @@ namespace VNLib.Utils.Memory
RMemCopyHandle<T> src = new(source, (nuint)sourceOffset);
MemhandleCopyHandle<T> dst = new(dest, destOffset);
- MemmoveInternal<T, RMemCopyHandle<T>, MemhandleCopyHandle<T>>(in src, in dst, (nuint)count, false);
+ MemmoveInternal<T, RMemCopyHandle<T>, MemhandleCopyHandle<T>>(in src, in dst, (nuint)count, forceAcceleration: false);
}
/// <summary>
@@ -808,7 +812,7 @@ namespace VNLib.Utils.Memory
ref Refs.AsByte(source, (nuint)sourceOffset),
ref Refs.AsByte(dest, (nuint)destOffset),
ByteCount<T>((uint)count),
- false
+ forceAcceleration: false
);
}
@@ -841,7 +845,7 @@ namespace VNLib.Utils.Memory
MemhandleCopyHandle<T> src = new(source, (nuint)sourceOffset);
WMemCopyHandle<T> dst = new(dest, (nuint)destOffset);
- MemmoveInternal<T, MemhandleCopyHandle<T>, WMemCopyHandle<T>>(in src, in dst, (nuint)count, false);
+ MemmoveInternal<T, MemhandleCopyHandle<T>, WMemCopyHandle<T>>(in src, in dst, (nuint)count, forceAcceleration: false);
}
/// <summary>
@@ -871,7 +875,7 @@ namespace VNLib.Utils.Memory
MemhandleCopyHandle<T> src = new(source, sourceOffset);
MemhandleCopyHandle<T> dst = new(dest, destOffset);
- MemmoveInternal<T, MemhandleCopyHandle<T>, MemhandleCopyHandle<T>>(in src, in dst, count, false);
+ MemmoveInternal<T, MemhandleCopyHandle<T>, MemhandleCopyHandle<T>>(in src, in dst, count, forceAcceleration: false);
}
/// <summary>
@@ -900,7 +904,7 @@ namespace VNLib.Utils.Memory
MemhandleCopyHandle<T> src = new(source, sourceOffset);
ArrayCopyHandle<T> dst = new(dest, destOffset);
- MemmoveInternal<T, MemhandleCopyHandle<T>, ArrayCopyHandle<T>>(in src, in dst, count, false);
+ MemmoveInternal<T, MemhandleCopyHandle<T>, ArrayCopyHandle<T>>(in src, in dst, count, forceAcceleration: false);
}
/// <summary>
@@ -929,7 +933,7 @@ namespace VNLib.Utils.Memory
ArrayCopyHandle<T> ach = new(source, sourceOffset);
MemhandleCopyHandle<T> mch = new(dest, destOffset);
- MemmoveInternal<T, ArrayCopyHandle<T>, MemhandleCopyHandle<T>>(in ach, in mch, count, false);
+ MemmoveInternal<T, ArrayCopyHandle<T>, MemhandleCopyHandle<T>>(in ach, in mch, count, forceAcceleration: false);
}
/// <summary>
@@ -958,13 +962,14 @@ namespace VNLib.Utils.Memory
ArrayCopyHandle<T> srcH = new(source, sourceOffset);
ArrayCopyHandle<T> dstH = new(dest, destOffset);
- MemmoveInternal<T, ArrayCopyHandle<T>, ArrayCopyHandle<T>>(in srcH, in dstH, count, false);
+ MemmoveInternal<T, ArrayCopyHandle<T>, ArrayCopyHandle<T>>(in srcH, in dstH, count, forceAcceleration: false);
}
/// <summary>
/// Optimized memmove for known small memory blocks. This method is faster than
/// <see cref="Memmove{T}(ref readonly T, nuint, ref T, nuint, nuint)"/> when the
- /// number of elements to copy is known to be small.
+ /// number of elements to copy is known to be small. Pointers to src and dst may be
+ /// overlapping regions of memory.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="src">A readonly reference to the first element in the source memory sequence</param>
@@ -999,7 +1004,7 @@ namespace VNLib.Utils.Memory
/// <summary>
/// Low level api for copying data from source memory to destination memory of an
- /// umanged data type.
+ /// umanged data type. Pointers to src and dst may be overlapping regions of memory.
/// </summary>
/// <remarks>
/// WARNING: It's not possible to do bounds checking when using references. Be sure you
@@ -1027,13 +1032,14 @@ namespace VNLib.Utils.Memory
in Refs.AsByteR(in src, srcOffset),
ref Refs.AsByte(ref dst, dstOffset),
ByteCount<T>(elementCount),
- false
+ forceAcceleration: false
);
}
/// <summary>
/// Low level api for copying data from source memory to destination memory of an
/// umanged data type. This call attempts to force hadrware acceleration if supported.
+ /// Pointers to src and dst may be overlapping regions of memory.
/// <para>
/// Understand that using this function attempts to force hardware acceleration, which
/// may hurt performance if the data is not large enough to justify the overhead.
@@ -1329,7 +1335,7 @@ namespace VNLib.Utils.Memory
//Pin the array
GCHandle arrHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
- //safe to get array basee pointer
+ //safe to get array base pointer
ref T arrBase = ref MemoryMarshal.GetArrayDataReference(array);
//Get element offset
@@ -1571,8 +1577,6 @@ namespace VNLib.Utils.Memory
MemoryHandle Pin();
- nuint Size { get; }
-
nuint Offset { get; }
void Validate(nuint count);
@@ -1584,9 +1588,6 @@ namespace VNLib.Utils.Memory
public readonly nuint Offset => offset;
///<inheritdoc/>
- public readonly nuint Size => ByteCount<T>((nuint)array.Length);
-
- ///<inheritdoc/>
public readonly MemoryHandle Pin() => PinArrayAndGetHandle(array, 0);
///<inheritdoc/>
@@ -1602,9 +1603,6 @@ namespace VNLib.Utils.Memory
public readonly nuint Offset => offset;
///<inheritdoc/>
- public readonly nuint Size => ByteCount<T>((nuint)block.Length);
-
- ///<inheritdoc/>
public readonly MemoryHandle Pin() => block.Pin();
///<inheritdoc/>
@@ -1620,9 +1618,6 @@ namespace VNLib.Utils.Memory
public readonly nuint Offset => offset;
///<inheritdoc/>
- public readonly nuint Size => ByteCount<T>((nuint)block.Length);
-
- ///<inheritdoc/>
public readonly MemoryHandle Pin() => block.Pin();
///<inheritdoc/>
@@ -1638,9 +1633,6 @@ namespace VNLib.Utils.Memory
public readonly nuint Offset => offset;
///<inheritdoc/>
- public readonly nuint Size => handle.Length;
-
- ///<inheritdoc/>
public readonly MemoryHandle Pin() => handle.Pin(0);
///<inheritdoc/>
diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs
index e2e7434..742346c 100644
--- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs
+++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs
@@ -27,14 +27,83 @@ using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
-using VNLib.Utils.Extensions;
-
namespace VNLib.Utils.Memory
{
public static unsafe partial class MemoryUtil
{
#region alloc
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool CanUseUnmanagedHeap<T>(IUnmangedHeap heap, nuint elements)
+ {
+ /*
+ * We may allocate from the share heap only if the heap is not using locks
+ * or if the element size could cause performance issues because its too large
+ * to use a managed array.
+ *
+ * We want to avoid allocations, that may end up in the LOH if we can
+ */
+
+ return (heap.CreationFlags & HeapCreation.UseSynchronization) == 0
+ || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE;
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
+ /// </summary>
+ /// <typeparam name="T">Unmanaged data type to create a block of</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="elements">The size of the block (number of elements)</param>
+ /// <param name="zero">A flag that zeros the allocated block before returned</param>
+ /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentNullException.ThrowIfNull(heap);
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+
+ if (elements == 0)
+ {
+ //Return an empty handle
+ return default;
+ }
+
+ //If zero flag is set then specify zeroing memory (safe case because of the above check)
+ IntPtr block = heap.Alloc((nuint)elements, (nuint)sizeof(T), zero);
+
+ return new(heap, block, elements);
+ }
+
+ /// <summary>
+ /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the
+ /// array when work is completed
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="pool"></param>
+ /// <param name="size">The minimum size array to allocate</param>
+ /// <param name="zero">Should elements from 0 to size be set to default(T)</param>
+ /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
+ public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged
+ {
+ ArgumentNullException.ThrowIfNull(pool);
+
+ if (size <= 0)
+ {
+ return default;
+ }
+
+ T[] array = pool.Rent(size);
+
+ if (zero)
+ {
+ InitializeBlock(array, (uint)size);
+ }
+
+ return new(pool, array, size);
+ }
+
/// <summary>
/// Allocates a block of unmanaged, or pooled manaaged memory depending on
/// compilation flags and runtime unamanged allocators.
@@ -43,40 +112,20 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(int elements, bool zero = false) where T : unmanaged
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
if (elements == 0)
{
return default;
}
- /*
- * We may allocate from the share heap only if the heap is not using locks
- * or if the element size could cause performance issues because its too large
- * to use a managed array.
- *
- * We want to avoid allocations, that may end up in the LOH if we can
- */
-
- if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE)
- {
- // Alloc from heap
- IntPtr block = Shared.Alloc((uint)elements, (uint)sizeof(T), zero);
- //Init new handle
- return new(Shared, block, elements);
- }
- else
- {
- //Rent the array from the pool
- return ArrayPool<T>.Shared.UnsafeAlloc(elements, zero);
- }
+ return CanUseUnmanagedHeap<T>(Shared, (uint)elements)
+ ? UnsafeAlloc<T>(Shared, elements, zero)
+ : UnsafeAlloc(ArrayPool<T>.Shared, elements, zero);
}
/// <summary>
@@ -88,18 +137,81 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeMemoryHandle<T> UnsafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged
{
- if (elements < 0)
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return UnsafeAlloc<T>(elements: (int)NearestPage<T>(elements), zero);
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
+ /// </summary>
+ /// <typeparam name="T">Unmanaged data type to create a block of</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="elements">The size of the block (number of elements)</param>
+ /// <param name="zero">A flag that zeros the allocated block before returned</param>
+ /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static MemoryHandle<T> SafeAlloc<T>(IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentNullException.ThrowIfNull(heap);
+
+ //Return empty handle if no elements were specified
+ if (elements == 0)
{
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
+ return new MemoryHandle<T>();
}
+
+ IntPtr block = heap.Alloc(elements, (nuint)sizeof(T), zero);
- //Round to nearest page (in bytes)
- nint np = NearestPage<T>(elements);
- return UnsafeAlloc<T>((int)np, zero);
+ return new MemoryHandle<T>(heap, block, elements, zero);
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
+ /// </summary>
+ /// <typeparam name="T">Unmanaged data type to create a block of</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="elements">The size of the block (number of elements)</param>
+ /// <param name="zero">A flag that zeros the allocated block before returned</param>
+ /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryHandle<T> SafeAlloc<T>(IUnmangedHeap heap, nint elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return SafeAlloc<T>(heap, (nuint)elements, zero);
+ }
+
+ /// <summary>
+ /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the
+ /// array when work is completed
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="pool"></param>
+ /// <param name="size">The minimum size array to allocate</param>
+ /// <param name="zero">Should elements from 0 to size be set to default(T)</param>
+ /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
+ public static ArrayPoolBuffer<T> SafeAlloc<T>(ArrayPool<T> pool, int size, bool zero = false) where T : struct
+ {
+ ArgumentNullException.ThrowIfNull(pool);
+
+ T[] array = pool.Rent(size);
+
+ if (zero)
+ {
+ InitializeBlock(array, (uint)size);
+ }
+
+ //Use the array pool buffer wrapper to return the array to the pool when the handle is disposed
+ return new ArrayPoolBuffer<T>(pool, array, size);
}
/// <summary>
@@ -110,35 +222,60 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
- public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T : unmanaged
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IMemoryHandle<T> SafeAlloc<T>(nuint elements, bool zero = false) where T : unmanaged
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
-
- /*
- * We may allocate from the share heap only if the heap is not using locks
- * or if the element size could cause performance issues because its too large
- * to use a managed array.
- *
- * We want to avoid allocations, that may end up in the LOH if we can
- */
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
- if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE)
+ if (CanUseUnmanagedHeap<T>(Shared, elements))
{
- return Shared.Alloc<T>(elements, zero);
+ return SafeAlloc<T>(Shared, elements, zero);
}
else
{
- return new ArrayPoolBuffer<T>(ArrayPool<T>.Shared, elements, zero);
+ //Should never happen because max pool size guards against this
+ Debug.Assert(elements <= int.MaxValue);
+
+ return SafeAlloc(ArrayPool<T>.Shared, (int)elements, zero);
}
}
/// <summary>
/// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return SafeAlloc<T>((nuint)elements, zero);
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators, rounded up to the
+ /// neareset memory page.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IMemoryHandle<T> SafeAllocNearestPage<T>(nuint elements, bool zero = false) where T : unmanaged
+ => SafeAlloc<T>(elements: NearestPage<T>(elements), zero);
+
+ /// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
/// compilation flags and runtime unamanged allocators, rounded up to the
/// neareset memory page.
/// </summary>
@@ -146,21 +283,52 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IMemoryHandle<T> SafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return SafeAllocNearestPage<T>((nuint)elements, zero);
+ }
- //Round to nearest page (in bytes)
- nint np = NearestPage<T>(elements);
- return SafeAlloc<T>((int)np, zero);
+ /// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators, rounded up to the
+ /// neareset memory page.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <param name="heap">The heap to allocate the block of memory from</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryHandle<T> SafeAllocNearestPage<T>(IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+
+ return SafeAllocNearestPage<T>(heap, (nuint)elements, zero);
}
/// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators, rounded up to the
+ /// neareset memory page.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <param name="heap">The heap to allocate the block of memory from</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryHandle<T> SafeAllocNearestPage<T>(IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
+ => SafeAlloc<T>(heap, elements: NearestPage<T>(elements), zero);
+
+ /// <summary>
/// Allocates a structure of the specified type on the specified
/// unmanged heap and optionally zero's it's memory
/// </summary>
@@ -191,13 +359,8 @@ namespace VNLib.Utils.Memory
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ref T StructAllocRef<T>(IUnmangedHeap heap, bool zero) where T : unmanaged
- {
- //Alloc structure
- T* ptr = StructAlloc<T>(heap, zero);
- //Get a reference and assign it
- return ref Unsafe.AsRef<T>(ptr);
- }
+ public static ref T StructAllocRef<T>(IUnmangedHeap heap, bool zero) where T : unmanaged
+ => ref Unsafe.AsRef<T>(StructAlloc<T>(heap, zero));
/// <summary>
/// Frees a structure allocated with <see cref="StructAlloc{T}(IUnmangedHeap, bool)"/>
@@ -207,7 +370,8 @@ namespace VNLib.Utils.Memory
/// <param name="structPtr">A pointer to the unmanaged structure to free</param>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void StructFree<T>(IUnmangedHeap heap, T* structPtr) where T : unmanaged => StructFree(heap, (void*)structPtr);
+ public static void StructFree<T>(IUnmangedHeap heap, T* structPtr) where T : unmanaged
+ => StructFree(heap, (void*)structPtr);
/// <summary>
/// Frees a structure allocated with <see cref="StructAllocRef{T}(IUnmangedHeap, bool)"/>
@@ -218,7 +382,8 @@ namespace VNLib.Utils.Memory
/// <param name="structRef">A reference to the unmanaged structure to free</param>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void StructFreeRef<T>(IUnmangedHeap heap, ref T structRef) where T : unmanaged => StructFree(heap, Unsafe.AsPointer(ref structRef));
+ public static void StructFreeRef<T>(IUnmangedHeap heap, ref T structRef) where T : unmanaged
+ => StructFree(heap, Unsafe.AsPointer(ref structRef));
/// <summary>
/// Frees a structure allocated with <see cref="StructAlloc{T}(IUnmangedHeap, bool)"/>
@@ -233,7 +398,7 @@ namespace VNLib.Utils.Memory
//Get intpointer
IntPtr ptr = (IntPtr)structPtr;
- //Free
+
bool isFree = heap.Free(ref ptr);
Debug.Assert(isFree, $"Structure free failed for heap {heap.GetHashCode()}, struct address {ptr:x}");
}
@@ -249,39 +414,20 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
public static UnsafeMemoryHandle<byte> UnsafeAlloc(int elements, bool zero = false)
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
if(elements == 0)
{
return default;
}
- /*
- * We may allocate from the share heap only if the heap is not using locks
- * or if the element size could cause performance issues because its too large
- * to use a managed array.
- *
- * We want to avoid allocations, that may end up in the LOH if we can
- */
-
- if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || elements > MAX_UNSAFE_POOL_SIZE)
- {
- // Alloc from heap
- IntPtr block = Shared.Alloc((uint)elements, 1, zero);
- //Init new handle
- return new(Shared, block, elements);
- }
- else
- {
- return ArrayPool<byte>.Shared.UnsafeAlloc(elements, zero);
- }
+ return CanUseUnmanagedHeap<byte>(Shared, (uint)elements)
+ ? UnsafeAlloc<byte>(Shared, elements, zero)
+ : UnsafeAlloc(ArrayPool<byte>.Shared, elements, zero);
}
/// <summary>
@@ -292,19 +438,15 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeMemoryHandle<byte> UnsafeAllocNearestPage(int elements, bool zero = false)
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
//Round to nearest page (in bytes)
- nint np = NearestPage(elements);
- return UnsafeAlloc((int)np, zero);
+ return UnsafeAlloc(elements: (int)NearestPage(elements), zero);
}
/// <summary>
@@ -314,31 +456,15 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
public static IMemoryHandle<byte> SafeAlloc(int elements, bool zero = false)
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
-
- /*
- * We may allocate from the share heap only if the heap is not using locks
- * or if the element size could cause performance issues because its too large
- * to use a managed array.
- *
- * We want to avoid allocations, that may end up in the LOH if we can
- */
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
- if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || elements > MAX_UNSAFE_POOL_SIZE)
- {
- return Shared.Alloc<byte>(elements, zero);
- }
- else
- {
- return new ArrayPoolBuffer<byte>(ArrayPool<byte>.Shared, elements, zero);
- }
+ return CanUseUnmanagedHeap<byte>(Shared, (uint)elements)
+ ? SafeAlloc<byte>(Shared, (nuint)elements, zero)
+ : SafeAlloc(ArrayPool<byte>.Shared, elements, zero);
}
/// <summary>
@@ -349,18 +475,12 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
public static IMemoryHandle<byte> SafeAllocNearestPage(int elements, bool zero = false)
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
-
- //Round to nearest page (in bytes)
- nint np = NearestPage(elements);
- return SafeAlloc((int)np, zero);
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return SafeAlloc(elements: (int)NearestPage(elements), zero);
}
#endregion
diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs
index 7f42761..a9730b7 100644
--- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs
+++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs
@@ -23,6 +23,7 @@
*/
using System;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
@@ -111,25 +112,32 @@ namespace VNLib.Utils.Memory
bool result;
//If disposed, set the block handle to zero and exit to avoid raising exceptions during finalization
- if (IsClosed || IsInvalid)
+ if (IsClosed)
{
block = LPVOID.Zero;
return true;
}
+ /*
+ * Checking for invalid is not really necesasry because
+ * the only way the handle can be invalidated is
+ * if some derrived class mutates the handle value
+ * and doesn't close the handle
+ */
+ Debug.Assert(IsInvalid == false);
+
if ((flags & HeapCreation.UseSynchronization) > 0)
{
//wait for lock
lock (HeapLock)
- {
- //Free block
+ {
result = FreeBlock(block);
//Release lock before releasing handle
}
}
else
{
- //No lock
+ //No lock needed
result = FreeBlock(block);
}
diff --git a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs
index d93739d..4041d0b 100644
--- a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs
+++ b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs
@@ -29,8 +29,6 @@ using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
-using VNLib.Utils.Extensions;
-
namespace VNLib.Utils.Memory
{
@@ -62,16 +60,7 @@ namespace VNLib.Utils.Memory
public readonly Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- return _handleType switch
- {
- HandleType.None => Span<T>.Empty,
- HandleType.Pool => _poolArr!.AsSpan(0, _length),
- HandleType.PrivateHeap => MemoryUtil.GetSpan<T>(_memoryPtr, _length),
- _ => throw new InvalidOperationException("Invalid handle type"),
- };
- }
+ get => AsSpan();
}
/// <summary>
@@ -153,7 +142,7 @@ namespace VNLib.Utils.Memory
IntPtr unalloc = _memoryPtr;
//Free the unmanaged handle
bool unsafeFreed = _heap!.Free(ref unalloc);
- Debug.Assert(unsafeFreed, "A previously allocated unsafe memhandle failed to free");
+ Debug.Assert(unsafeFreed, "A previously allocated unsafe memhandle failed to free block");
}
break;
}
@@ -193,6 +182,8 @@ namespace VNLib.Utils.Memory
{
switch (_handleType)
{
+ case HandleType.None:
+ return ref Unsafe.NullRef<T>();
case HandleType.Pool:
return ref MemoryMarshal.GetArrayDataReference(_poolArr!);
case HandleType.PrivateHeap:
@@ -207,7 +198,7 @@ namespace VNLib.Utils.Memory
/// </summary>
/// <returns>The memory block that is held by the internl handle</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly Span<T> AsSpan() => Span;
+ public readonly Span<T> AsSpan() => AsSpan(0, _length);
/// <summary>
/// Returns a <see cref="Span{T}"/> that represents the memory block pointed to by this handle
@@ -216,7 +207,7 @@ namespace VNLib.Utils.Memory
/// <returns>The desired memory block at the desired element offset</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly Span<T> AsSpan(int start) => Span[start..];
+ public readonly Span<T> AsSpan(int start) => AsSpan(start, _length - start);
/// <summary>
/// Returns a <see cref="Span{T}"/> that represents the memory block pointed to by this handle
@@ -226,7 +217,23 @@ namespace VNLib.Utils.Memory
/// <returns>The desired memory block at the desired element offset and length</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>"
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly Span<T> AsSpan(int start, int length) => Span.Slice(start, length);
+ public readonly Span<T> AsSpan(int start, int length)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(start);
+ ArgumentOutOfRangeException.ThrowIfNegative(length);
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(length - start, _length);
+
+ /*
+ * If the handle is empty, a null ref should be returned. The
+ * check above will gaurd against calling this function on non-empty
+ * handles. So adding 0 to 0 on the reference should not cause any issues.
+ */
+
+ return MemoryMarshal.CreateSpan(
+ ref Unsafe.Add(ref GetReference(), start),
+ length
+ );
+ }
///<inheritdoc/>
public readonly override int GetHashCode()
@@ -248,7 +255,9 @@ namespace VNLib.Utils.Memory
/// <returns>True if the other handle points to the same block of memory as the current handle</returns>
public readonly bool Equals(in UnsafeMemoryHandle<T> other)
{
- return _handleType == other._handleType && Length == other.Length && GetHashCode() == other.GetHashCode();
+ return _handleType == other._handleType
+ && Length == other.Length
+ && GetHashCode() == other.GetHashCode();
}
/// <summary>
@@ -277,7 +286,7 @@ namespace VNLib.Utils.Memory
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns>True if handles are equal, flase otherwise</returns>
- public static bool operator ==(in UnsafeMemoryHandle<T> left, in UnsafeMemoryHandle<T> right) => left.Equals(right);
+ public static bool operator ==(in UnsafeMemoryHandle<T> left, in UnsafeMemoryHandle<T> right) => left.Equals(in right);
/// <summary>
/// Equality overload
@@ -285,7 +294,7 @@ namespace VNLib.Utils.Memory
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns>True if handles are equal, flase otherwise</returns>
- public static bool operator !=(in UnsafeMemoryHandle<T> left, in UnsafeMemoryHandle<T> right) => !left.Equals(right);
+ public static bool operator !=(in UnsafeMemoryHandle<T> left, in UnsafeMemoryHandle<T> right) => !left.Equals(in right);
}
-} \ No newline at end of file
+}
diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs
index b7b3783..dd67f9f 100644
--- a/lib/Utils/src/VnEncoding.cs
+++ b/lib/Utils/src/VnEncoding.cs
@@ -91,64 +91,17 @@ namespace VNLib.Utils
/// <returns>The object decoded from the stream</returns>
/// <exception cref="JsonException"></exception>
/// <exception cref="NotSupportedException"></exception>
- public static ValueTask<T?> JSONDeserializeFromBinaryAsync<T>(Stream? data, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
+ public static ValueTask<T?> JSONDeserializeFromBinaryAsync<T>(
+ Stream? data,
+ JsonSerializerOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
{
//Return default if null
- return data == null || data.Length == 0 ? ValueTask.FromResult<T?>(default) : JsonSerializer.DeserializeAsync<T>(data, options, cancellationToken);
- }
-
- /// <summary>
- /// Attempts to deserialze a json object from a stream of UTF8 data
- /// </summary>
- /// <param name="data">Binary data to read from</param>
- /// <param name="type"></param>
- /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to deserializer</param>
- /// <param name="cancellationToken"></param>
- /// <returns>The object decoded from the stream</returns>
- /// <exception cref="JsonException"></exception>
- /// <exception cref="NotSupportedException"></exception>
- public static ValueTask<object?> JSONDeserializeFromBinaryAsync(Stream? data, Type type, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
- {
- //Return default if null
- return data == null || data.Length == 0 ? ValueTask.FromResult<object?>(default) : JsonSerializer.DeserializeAsync(data, type, options, cancellationToken);
- }
-
- /// <summary>
- /// Attempts to serialize the object to json and write the encoded data to the stream
- /// </summary>
- /// <typeparam name="T">The object type to serialize</typeparam>
- /// <param name="data">The object to serialize</param>
- /// <param name="output">The <see cref="Stream"/> to write output data to</param>
- /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to serializer</param>
- /// <exception cref="JsonException"></exception>
- public static void JSONSerializeToBinary<T>(T data, Stream output, JsonSerializerOptions? options = null)
- {
- //return if null
- if(data == null)
- {
- return;
- }
- //Serialize
- JsonSerializer.Serialize(output, data, options);
- }
- /// <summary>
- /// Attempts to serialize the object to json and write the encoded data to the stream
- /// </summary>
- /// <param name="data">The object to serialize</param>
- /// <param name="output">The <see cref="Stream"/> to write output data to</param>
- /// <param name="type"></param>
- /// <param name="options"><see cref="JsonSerializerOptions"/> object to pass to serializer</param>
- /// <exception cref="JsonException"></exception>
- public static void JSONSerializeToBinary(object data, Stream output, Type type, JsonSerializerOptions? options = null)
- {
- //return if null
- if (data == null)
- {
- return;
- }
- //Serialize
- JsonSerializer.Serialize(output, data, type, options);
- }
+ return data == null || data.Length == 0
+ ? ValueTask.FromResult<T?>(default)
+ : JsonSerializer.DeserializeAsync<T>(data, options, cancellationToken);
+ }
#region Base32
@@ -250,11 +203,8 @@ namespace VNLib.Utils
//right shift the value to lower 5 bits
val >>= 3;
- //Lookup charcode
- char base32Char = RFC_4648_BASE32_CHARS[val];
-
//append the character to the writer
- writer.Append(base32Char);
+ writer.Append(RFC_4648_BASE32_CHARS[val]);
//Shift input left by 5 bits so the next 5 bits can be read
inputAsLong <<= 5;
@@ -282,6 +232,15 @@ namespace VNLib.Utils
}
/// <summary>
+ /// Gets the size of the buffer required to decode a base32 encoded
+ /// string. This buffer size will always be smaller than the input size.
+ /// </summary>
+ /// <param name="inputSize">The base32 encoded data input size</param>
+ /// <returns>The size of the output buffer needed to write decoded data to</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint Base32DecodedSizeSize(nint inputSize) => (inputSize * 5) / 8;
+
+ /// <summary>
/// Attempts to decode the Base32 encoded string
/// </summary>
/// <param name="input">The Base32 encoded data to decode</param>
@@ -292,53 +251,57 @@ namespace VNLib.Utils
{
//TODO support Big-Endian byte order
+ int count = 0;
+ ulong bufferLong = 0; //buffer used to shift data while decoding
+ byte* buffer = (byte*)&bufferLong; //re-cast to byte* to use it as a byte buffer
+
//trim padding characters
input = input.Trim('=');
//Calc the number of bytes to write
- int outputSize = (input.Length * 5) / 8;
+ nint outputSize = Base32DecodedSizeSize(input.Length);
//make sure the output buffer is large enough
if(writer.RemainingSize < outputSize)
{
return false;
}
-
- //buffer used to shift data while decoding
- ulong bufferLong = 0;
-
- //re-cast to byte* to index it as a byte buffer
- byte* buffer = (byte*)&bufferLong;
-
- int count = 0, len = input.Length;
- while(count < len)
+ while(count < input.Length)
{
- //Convert the character to its char code
- byte charCode = GetCharCode(input[count]);
-
- //write byte to buffer
- buffer[0] |= charCode;
+ /*
+ * Attempts to accumulate 8 bytes from the input buffer
+ * and write it from hi-lo byte order to the output buffer
+ *
+ * The underlying 64-bit integer is shifted left by 5 bits
+ * on every loop, removing leading zero bits. The OR operation
+ * ignores the zeros when the next byte is written, and anything
+ * leading is shifted off the end when 8 bytes are written.
+ *
+ * Reemeber: each character only contains 5 bits of useful data
+ */
+
+ buffer[0] |= GetCharCode(input[count]);
count++;
//If 8 characters have been decoded, reset the buffer
- if((count % 8) == 0)
+ if ((count % 8) == 0)
{
//Write the 5 upper bytes in reverse order to the output buffer
for(int j = 0; j < 5; j++)
{
writer.Append(buffer[4 - j]);
}
- //reset
+
bufferLong = 0;
}
- //left shift the buffer up by 5 bits
+ //left shift the buffer up by 5 bits, because thats all we
bufferLong <<= 5;
}
- //If remaining data has not be written, but has been buffed, finalize it
+ //If remaining data has not be written, but has been bufferedd, finalize it
if (writer.Written < outputSize)
{
//calculate how many bits the buffer still needs to be shifted by (will be 5 bits off because of the previous loop)
@@ -348,7 +311,7 @@ namespace VNLib.Utils
bufferLong <<= remainingShift;
//calc remaining bytes
- int remaining = (outputSize - writer.Written);
+ nint remaining = (outputSize - writer.Written);
//Write remaining bytes to the output
for(int i = 0; i < remaining; i++)
@@ -455,31 +418,24 @@ namespace VNLib.Utils
/// <returns>The base32 encoded string representation of the specified buffer</returns>
/// <exception cref="InternalBufferTooSmallException"></exception>
public static string ToBase32String(ReadOnlySpan<byte> binBuffer, bool withPadding = false)
- {
- string value;
+ {
//Calculate the base32 entropy to alloc an appropriate buffer (minium buffer of 2 chars)
int entropy = Base32CalcMaxBufferSize(binBuffer.Length);
-
- //Alloc buffer for enough size (2*long bytes) is not an issue
+
using UnsafeMemoryHandle<char> charBuffer = MemoryUtil.UnsafeAlloc<char>(entropy);
//Encode
ERRNO encoded = TryToBase32Chars(binBuffer, charBuffer.Span);
+
if (!encoded)
{
throw new InternalBufferTooSmallException("Base32 char buffer was too small");
}
- //Convert with or w/o padding
- if (withPadding)
- {
- value = charBuffer.Span[0..(int)encoded].ToString();
- }
- else
- {
- value = charBuffer.Span[0..(int)encoded].Trim('=').ToString();
- }
- return value;
+ //Convert with or w/o padding
+ return withPadding
+ ? charBuffer.Span[0..(int)encoded].ToString()
+ : charBuffer.Span[0..(int)encoded].Trim('=').ToString();
}
/// <summary>
@@ -494,12 +450,14 @@ namespace VNLib.Utils
{
//calc size of bin buffer
int size = base32.Length;
- //Rent a bin buffer
+
using UnsafeMemoryHandle<byte> binBuffer = MemoryUtil.UnsafeAlloc(size);
- //Try to decode the data
+
ERRNO decoded = TryFromBase32Chars(base32, binBuffer.Span);
- //Marshal back to a struct
- return decoded ? MemoryMarshal.Read<T>(binBuffer.Span[..(int)decoded]) : throw new InternalBufferTooSmallException("Binbuffer was too small");
+
+ return decoded
+ ? MemoryMarshal.Read<T>(binBuffer.Span[..(int)decoded])
+ : throw new InternalBufferTooSmallException("Binbuffer was too small");
}
/// <summary>
@@ -536,11 +494,11 @@ namespace VNLib.Utils
{
//get the size of the structure
int binSize = Unsafe.SizeOf<T>();
- //Rent a bin buffer
+
Span<byte> binBuffer = stackalloc byte[binSize];
- //Write memory to buffer
+
MemoryMarshal.Write(binBuffer, in value);
- //Convert to base32
+
return ToBase32String(binBuffer, withPadding);
}
@@ -548,28 +506,9 @@ namespace VNLib.Utils
#region percent encoding
- private const int MAX_STACKALLOC = 1024;
+ private const int MAX_STACKALLOC = 512;
- private static readonly ReadOnlyMemory<byte> HexToUtf8Pos = new byte[16]
- {
- 0x30, //0
- 0x31, //1
- 0x32, //2
- 0x33, //3
- 0x34, //4
- 0x35, //5
- 0x36, //6
- 0x37, //7
- 0x38, //8
- 0x39, //9
-
- 0x41, //A
- 0x42, //B
- 0x43, //C
- 0x44, //D
- 0x45, //E
- 0x46 //F
- };
+ private static readonly byte[] HexToUtf8Pos = "0123456789ABCDEF"u8.ToArray();
/// <summary>
/// Deterimes the size of the buffer needed to encode a utf8 encoded
@@ -630,7 +569,7 @@ namespace VNLib.Utils
public static ERRNO PercentEncode(ReadOnlySpan<byte> utf8Bytes, Span<byte> utf8Output, ReadOnlySpan<byte> allowedChars = default)
{
int outPos = 0, len = utf8Bytes.Length;
- ReadOnlySpan<byte> lookupTable = HexToUtf8Pos.Span;
+ ReadOnlySpan<byte> lookupTable = HexToUtf8Pos.AsSpan();
if (allowedChars.IsEmpty)
{
@@ -645,11 +584,13 @@ namespace VNLib.Utils
}
else
{
- //Percent encode
+ /*
+ * Leading byte is %, followed by a single byte
+ * for the hi and low nibble of the value
+ */
+
utf8Output[outPos++] = 0x25; // '%'
- //Calc and store the encoded by the upper 4 bits
utf8Output[outPos++] = lookupTable[(value & 0xf0) >> 4];
- //Store lower 4 bits in encoded value
utf8Output[outPos++] = lookupTable[value & 0x0f];
}
}
@@ -667,11 +608,13 @@ namespace VNLib.Utils
}
else
{
- //Percent encode
- utf8Output[outPos++] = 0x25; // '%'
- //Calc and store the encoded by the upper 4 bits
+ /*
+ * Leading byte is %, followed by a single byte
+ * for the hi and low nibble of the value
+ */
+
+ utf8Output[outPos++] = 0x25; // '%'
utf8Output[outPos++] = lookupTable[(value & 0xf0) >> 4];
- //Store lower 4 bits in encoded value
utf8Output[outPos++] = lookupTable[value & 0x0f];
}
}
@@ -711,7 +654,7 @@ namespace VNLib.Utils
public static ERRNO PercentDecode(ReadOnlySpan<byte> utf8Encoded, Span<byte> utf8Output)
{
int outPos = 0, len = utf8Encoded.Length;
- ReadOnlySpan<byte> lookupTable = HexToUtf8Pos.Span;
+ ReadOnlySpan<byte> lookupTable = HexToUtf8Pos.AsSpan();
for (int i = 0; i < len; i++)
{
@@ -816,9 +759,13 @@ namespace VNLib.Utils
/// return value. The default value is System.Base64FormattingOptions.None.
/// </param>
/// <returns>The number of characters encoded, or <see cref="ERRNO.E_FAIL"/> if conversion was unsuccessful</returns>
- public static ERRNO TryToBase64Chars(ReadOnlySpan<byte> buffer, Span<char> base64, Base64FormattingOptions options = Base64FormattingOptions.None)
+ public static ERRNO TryToBase64Chars(
+ ReadOnlySpan<byte> buffer,
+ Span<char> base64,
+ Base64FormattingOptions options = Base64FormattingOptions.None
+ )
{
- return Convert.TryToBase64Chars(buffer, base64, out int charsWritten, options: options) ? charsWritten : ERRNO.E_FAIL;
+ return Convert.TryToBase64Chars(buffer, base64, out int charsWritten, options) ? charsWritten : ERRNO.E_FAIL;
}
@@ -905,8 +852,17 @@ namespace VNLib.Utils
/// <returns>The size of the <paramref name="base64"/> buffer</returns>
public static ERRNO Base64ToUrlSafe(ReadOnlySpan<byte> base64, Span<byte> base64Url)
{
+ ArgumentOutOfRangeException.ThrowIfLessThan(base64.Length, base64Url.Length, nameof(base64));
+
//Aligned copy to the output buffer
- base64.CopyTo(base64Url);
+ MemoryUtil.Memmove(
+ ref MemoryMarshal.GetReference(base64Url),
+ 0,
+ ref MemoryMarshal.GetReference(base64),
+ 0,
+ (nuint)base64Url.Length
+ );
+
//One time convert the output buffer to url safe
Base64ToUrlSafeInPlace(base64Url);
return base64.Length;
@@ -923,8 +879,17 @@ namespace VNLib.Utils
/// <returns>The size of the <paramref name="base64Url"/> buffer</returns>
public static ERRNO Base64FromUrlSafe(ReadOnlySpan<byte> base64Url, Span<byte> base64)
{
+ ArgumentOutOfRangeException.ThrowIfLessThan(base64.Length, base64Url.Length, nameof(base64));
+
//Aligned copy to the output buffer
- base64Url.CopyTo(base64);
+ MemoryUtil.Memmove(
+ ref MemoryMarshal.GetReference(base64Url),
+ 0,
+ ref MemoryMarshal.GetReference(base64),
+ 0,
+ (nuint)base64Url.Length
+ );
+
//One time convert the output buffer to url safe
Base64FromUrlSafeInPlace(base64);
return base64Url.Length;
@@ -982,27 +947,62 @@ namespace VNLib.Utils
//Set the encoding to utf8
encoding ??= Encoding.UTF8;
+
//get the number of bytes to alloc a buffer
int decodedSize = encoding.GetByteCount(chars);
if(decodedSize > MAX_STACKALLOC)
{
- //Alloc heap buffer
using UnsafeMemoryHandle<byte> decodeHandle = MemoryUtil.UnsafeAlloc(decodedSize);
+
//Get the utf8 binary data
int count = encoding.GetBytes(chars, decodeHandle.Span);
return Base64UrlDecode(decodeHandle.Span[..count], output);
}
else
- {
- //Alloc stack buffer
+ {
Span<byte> decodeBuffer = stackalloc byte[decodedSize];
+
//Get the utf8 binary data
int count = encoding.GetBytes(chars, decodeBuffer);
return Base64UrlDecode(decodeBuffer[..count], output);
}
}
+ private static string ConvertToBase64UrlStringInternal(
+ ReadOnlySpan<byte> rawData,
+ Span<byte> buffer,
+ bool includePadding,
+ Encoding encoding
+ )
+ {
+ //Conver to base64
+ OperationStatus status = Base64.EncodeToUtf8(rawData, buffer, out _, out int written, true);
+
+ //Check for invalid states
+ Debug.Assert(status != OperationStatus.DestinationTooSmall, "Buffer allocation was too small for the conversion");
+ Debug.Assert(status != OperationStatus.NeedMoreData, "Need more data status was returned but is not valid for an encoding operation");
+
+ //Should never occur, but just in case, this is an input error
+ if (status == OperationStatus.InvalidData)
+ {
+ throw new ArgumentException("Your input data contained values that could not be converted to base64", nameof(rawData));
+ }
+
+ Span<byte> base64 = buffer[..written];
+
+ //Make url safe
+ Base64ToUrlSafeInPlace(base64);
+
+ //Remove padding
+ if (!includePadding)
+ {
+ base64 = base64.TrimEnd((byte)0x3d);
+ }
+
+ //Convert to string
+ return encoding.GetString(base64);
+ }
/// <summary>
/// Base64url encodes the binary buffer to its utf8 binary representation
@@ -1068,58 +1068,8 @@ namespace VNLib.Utils
/// <param name="includePadding">A value that indicates if the base64 padding characters should be included</param>
/// <returns>The base64url encoded string</returns>
/// <exception cref="ArgumentException"></exception>
- public static string ToBase64UrlSafeString(ReadOnlySpan<byte> rawData, bool includePadding)
- {
- if (rawData.IsEmpty)
- {
- throw new ArgumentException("The input buffer was empty", nameof(rawData));
- }
-
- int maxBufSize = Base64.GetMaxEncodedToUtf8Length(rawData.Length);
-
- if(maxBufSize > MAX_STACKALLOC)
- {
- //Alloc heap buffer
- using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage(maxBufSize);
- return ConvertToBase64UrlStringInternal(rawData, buffer.Span, includePadding);
- }
- else
- {
- //Stack alloc buffer
- Span<byte> buffer = stackalloc byte[maxBufSize];
- return ConvertToBase64UrlStringInternal(rawData, buffer, includePadding);
- }
- }
-
- private static string ConvertToBase64UrlStringInternal(ReadOnlySpan<byte> rawData, Span<byte> buffer, bool includePadding)
- {
- //Conver to base64
- OperationStatus status = Base64.EncodeToUtf8(rawData, buffer, out _, out int written, true);
-
- //Check for invalid states
- Debug.Assert(status != OperationStatus.DestinationTooSmall, "Buffer allocation was too small for the conversion");
- Debug.Assert(status != OperationStatus.NeedMoreData, "Need more data status was returned but is not valid for an encoding operation");
-
- //Should never occur, but just in case, this is an input error
- if (status == OperationStatus.InvalidData)
- {
- throw new ArgumentException("Your input data contained values that could not be converted to base64", nameof(rawData));
- }
-
- Span<byte> base64 = buffer[..written];
-
- //Make url safe
- Base64ToUrlSafeInPlace(base64);
-
- //Remove padding
- if (!includePadding)
- {
- base64 = base64.TrimEnd((byte)0x3d);
- }
-
- //Convert to string
- return Encoding.UTF8.GetString(base64);
- }
+ [Obsolete("Use Base64UrlEncode instead")]
+ public static string ToBase64UrlSafeString(ReadOnlySpan<byte> rawData, bool includePadding) => Base64UrlEncode(rawData, includePadding);
/// <summary>
/// Encodes the binary input buffer to its base64url safe utf8 encoding, and writes the output
@@ -1155,6 +1105,41 @@ namespace VNLib.Utils
}
/// <summary>
+ /// Encodes the binary intput buffer to its base64url safe encoding, then converts the binary
+ /// value to it character encoded value and allocates a new string. Defaults to UTF8 character
+ /// encoding. Base64url is a subset of ASCII,UTF7,UTF8,UTF16 etc so most encodings should be safe.
+ /// </summary>
+ /// <param name="input">The input binary intput buffer</param>
+ /// <param name="includePadding">A value that indicates if base64 padding should be url encoded(true), or removed(false).</param>
+ /// <param name="encoding">The encoding used to convert the binary buffer to its character representation.</param>
+ /// <returns>The base64url encoded string of the input data using the desired encoding</returns>
+ public static string Base64UrlEncode(ReadOnlySpan<byte> input, bool includePadding, Encoding? encoding = null)
+ {
+ if (input.IsEmpty)
+ {
+ return string.Empty;
+ }
+
+ encoding ??= Encoding.UTF8;
+
+ //We need to alloc an intermediate buffer, get the base64 max size
+ int maxSize = Base64.GetMaxEncodedToUtf8Length(input.Length);
+
+ if (maxSize > MAX_STACKALLOC)
+ {
+ //Alloc heap buffer
+ using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(maxSize);
+ return ConvertToBase64UrlStringInternal(input, buffer.Span, includePadding, encoding);
+ }
+ else
+ {
+ //Alloc stack buffer
+ Span<byte> bufer = stackalloc byte[maxSize];
+ return ConvertToBase64UrlStringInternal(input, bufer, includePadding, encoding);
+ }
+ }
+
+ /// <summary>
/// Encodes the binary intput buffer to its base64url safe encoding, then converts the internal buffer
/// to its character encoding using the supplied <paramref name="encoding"/>, and writes the characters
/// to the output buffer. Defaults to UTF8 character encoding. Base64url is a subset of ASCII,UTF7,UTF8,UTF16 etc