From bb706bbfa7519c8b5c506e76a787b9b016acfb75 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 14 Sep 2024 15:54:30 -0400 Subject: Squashed commit of the following: commit 322bbe00f77772ba6b0e25759de95dd517b6014c Author: vnugent Date: Sat Sep 14 15:43:45 2024 -0400 build: Testing updates and easier dev-testing commit abcd0e0d6cb5532c8a19a8cd8c7dd83e7f143442 Author: vnugent Date: Wed Sep 11 16:43:20 2024 -0400 Managed library & package updates commit 2ae018af277b808786cf398c689910bc016e7ef0 Author: vnugent Date: Tue Sep 10 18:59:06 2024 -0400 fix: zero/unsafezero with data types > sizeof(byte) commit 17c646a619eaa101d66871faa8f57c76500a8ad2 Merge: 97d0c46 a19807f Author: vnugent Date: Sat Sep 7 15:31:01 2024 -0400 Merge branch 'master' into develop --- Module.Taskfile.yaml | 54 ++++++-- lib/Plugins.Essentials/src/EventProcessor.cs | 4 + lib/Plugins.Runtime/src/LivePlugin.cs | 40 ++---- lib/Utils/src/ERRNO.cs | 25 ++-- lib/Utils/src/Extensions/CacheExtensions.cs | 77 +++-------- lib/Utils/src/IO/TemporayIsolatedFile.cs | 57 -------- lib/Utils/src/Memory/ArrayPoolBuffer.cs | 30 ++--- lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs | 8 +- lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs | 69 +++++----- lib/Utils/src/Memory/MemoryUtil.cs | 143 +++++++++++--------- lib/Utils/src/Memory/NativeHeap.cs | 17 ++- lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs | 14 +- lib/Utils/src/Memory/PrivateStringManager.cs | 23 ++-- lib/Utils/src/Memory/SubSequence.cs | 7 +- lib/Utils/src/Memory/VnString.cs | 164 +++++++++++++++-------- lib/Utils/src/Native/SafeLibraryHandle.cs | 3 +- lib/Utils/src/Resources/ManagedLibrary.cs | 80 ++++++++++- lib/Utils/tests/.runsettings | 3 + lib/Utils/tests/IO/VnMemoryStreamTests.cs | 15 +-- lib/Utils/tests/Memory/MemoryHandleTest.cs | 2 +- lib/Utils/tests/Memory/MemoryUtilTests.cs | 116 ++++++++++++---- lib/Utils/tests/Memory/NativeHeapTests.cs | 10 +- 22 files changed, 548 insertions(+), 413 deletions(-) delete mode 100644 lib/Utils/src/IO/TemporayIsolatedFile.cs diff --git a/Module.Taskfile.yaml b/Module.Taskfile.yaml index 6ec8fc6..99ea9b2 100644 --- a/Module.Taskfile.yaml +++ b/Module.Taskfile.yaml @@ -10,42 +10,68 @@ version: '3' -vars: - INT_DIR: '{{ .SCRATCH_DIR }}/obj/{{ .MODULE_NAME }}/' - MS_ARGS: '/p:RunAnalyzersDuringBuild=false /p:IntermediateOutputPath="{{.INT_DIR}}" /p:UseCommonOutputDirectory=true /p:BuildInParallel=true /p:MultiProcessorCompilation=true /p:ErrorOnDuplicatePublishOutputFiles=false' +vars: + MS_ARGS: '/p:RunAnalyzersDuringBuild=false /p:UseCommonOutputDirectory=true /p:BuildInParallel=true /p:MultiProcessorCompilation=true /p:ErrorOnDuplicatePublishOutputFiles=false' PACK_OUT: '{{ .OUTPUT_DIR }}/{{ .HEAD_SHA }}/pkg' tasks: + default: + desc: 'Builds the managed libraries in this module for development' + cmds: + - dotnet build -c debug {{ .MS_ARGS }} + + #called by build pipeline to sync repo update: cmds: - git reset --hard #clean up any local changes - - git remote update + - git remote update - git pull origin {{ .BRANCH_NAME }} --verify-signatures #re-write semver after hard reset - dotnet-gitversion.exe /updateprojectfiles #called by build pipeline to build module build: + desc: "Used by vnbuild to build the entire module at CI time" + vars: + INT_DIR: '{{ .SCRATCH_DIR }}/obj/{{ .MODULE_NAME }}/' + MS_ARGS: '{{ .MS_ARGS }} /p:IntermediateOutputPath="{{ .INT_DIR }}"' cmds: - - echo "building module {{.MODULE_NAME}}" - + - cmd: echo "building module {{ .MODULE_NAME }}" + silent: true + #build debug mode first - task: build_debug + vars: { MS_ARGS: '{{ .MS_ARGS }}'} - task: build_release + vars: { MS_ARGS: '{{ .MS_ARGS }}'} publish: + desc: "Used by vnbuild to prepare the packages for build servers" cmds: #git archive in the module directory - - git archive --format {{.ARCHIVE_FILE_FORMAT}} --output {{.ARCHIVE_FILE_NAME}} HEAD + - git archive --format {{ .ARCHIVE_FILE_FORMAT }} --output {{ .ARCHIVE_FILE_NAME }} HEAD #push packages to the sleet feed (feed path is vnbuild global) - - sleet push "{{.PACK_OUT}}/debug/" --source debug --config "{{.SLEET_CONFIG_PATH}}" --force - - sleet push "{{.PACK_OUT}}/release/" --source release --config "{{.SLEET_CONFIG_PATH}}" --force + - sleet push "{{ .PACK_OUT }}/debug/" --source debug --config "{{ .SLEET_CONFIG_PATH }}" --force + - sleet push "{{ .PACK_OUT }}/release/" --source release --config "{{ .SLEET_CONFIG_PATH }}" --force test: + desc: "Runs managed tests against the entire solution and all loaded test projects" + vars: + RPMALLOC_LIB_PATH: '{{ .USER_WORKING_DIR }}/lib/Utils.Memory/vnlib_rpmalloc/build/Debug/vnlib_rpmalloc' + MIMALLOC_LIB_PATH: '{{ .USER_WORKING_DIR }}/lib/Utils.Memory/vnlib_mimalloc/build/Debug/vnlib_mimalloc' cmds: - - cmd: dotnet test --verbosity normal + - cmd: echo "Ensure you have run 'task dev-init' before running tests to build native libraries" + silent: true + - cmd: dotnet test + {{ .CLI_ARGS }} + --verbosity normal + --framework {{ .FRAMEWORK | default "net8.0" }} + --environment VNLIB_SHARED_HEAP_DIAGNOSTICS="1" + --environment TEST_RPMALLOC_LIB_PATH="{{ .RPMALLOC_LIB_PATH }}" + --environment TEST_MIMALLOC_LIB_PATH="{{ .MIMALLOC_LIB_PATH }}" + #called by build pipeline to clean module clean: @@ -72,12 +98,12 @@ tasks: build_debug: internal: true cmds: - - dotnet publish -c debug {{.MS_ARGS}} - - dotnet pack -c debug {{.MS_ARGS}} -o "{{.PACK_OUT}}/debug/" -p:PackageVersion={{.BUILD_VERSION}} + - dotnet publish -c debug {{ .MS_ARGS }} + - dotnet pack -c debug {{ .MS_ARGS }} -o "{{ .PACK_OUT }}/debug/" -p:PackageVersion={{ .BUILD_VERSION }} build_release: internal: true cmds: - - dotnet publish -c release {{.MS_ARGS}} - - dotnet pack -c release {{.MS_ARGS}} -o "{{.PACK_OUT}}/release/" -p:PackageVersion={{.BUILD_VERSION}} + - dotnet publish -c release {{ .MS_ARGS }} + - dotnet pack -c release {{ .MS_ARGS }} -o "{{ .PACK_OUT }}/release/" -p:PackageVersion={{ .BUILD_VERSION }} \ No newline at end of file diff --git a/lib/Plugins.Essentials/src/EventProcessor.cs b/lib/Plugins.Essentials/src/EventProcessor.cs index 908ad07..c3082a1 100644 --- a/lib/Plugins.Essentials/src/EventProcessor.cs +++ b/lib/Plugins.Essentials/src/EventProcessor.cs @@ -416,8 +416,12 @@ namespace VNLib.Plugins.Essentials //set last modified time as the files last write time entity.Server.LastModified(fileLastModified); + //Open the file handle directly, reading will always be sequentially read and async + +#pragma warning disable CA2000 // Dispose objects before losing scope DirectFileStream dfs = DirectFileStream.Open(filename); +#pragma warning restore CA2000 // Dispose objects before losing scope long endOffset = checked((long)entity.Server.Range.End); long startOffset = checked((long)entity.Server.Range.Start); diff --git a/lib/Plugins.Runtime/src/LivePlugin.cs b/lib/Plugins.Runtime/src/LivePlugin.cs index 3ed2ad6..26276dd 100644 --- a/lib/Plugins.Runtime/src/LivePlugin.cs +++ b/lib/Plugins.Runtime/src/LivePlugin.cs @@ -26,6 +26,7 @@ using System; using System.Linq; using System.Reflection; +using VNLib.Utils.Resources; using VNLib.Plugins.Attributes; namespace VNLib.Plugins.Runtime @@ -76,18 +77,14 @@ namespace VNLib.Plugins.Runtime Plugin = plugin; OriginAsm = originAsm; PluginType = plugin.GetType(); - GetConsoleHandler(); + PluginConsoleHandler = GetConsoleHandler(plugin); } - private void GetConsoleHandler() + private static ConsoleEventHandlerSignature? GetConsoleHandler(IPlugin plugin) { - //Get the console handler method from the plugin instance - MethodInfo? handler = (from m in PluginType.GetMethods() - where m.GetCustomAttribute() != null - select m) - .FirstOrDefault(); //Get a delegate handler for the plugin - PluginConsoleHandler = handler?.CreateDelegate(Plugin); + return ManagedLibrary.GetMethodsWithAttribute(plugin) + .FirstOrDefault(); } /// @@ -97,18 +94,9 @@ namespace VNLib.Plugins.Runtime /// The host configuration DOM internal void InitConfig(ReadOnlySpan configData) { - //Get the console handler method from the plugin instance - MethodInfo? confHan = PluginType.GetMethods().Where(static m => m.GetCustomAttribute() != null) - .FirstOrDefault(); - //Get a delegate handler for the plugin - ConfigInitializer? configInit = confHan?.CreateDelegate(Plugin); - if (configInit == null) - { - return; - } - - //Invoke - configInit.Invoke(configData); + ManagedLibrary.GetMethodsWithAttribute(Plugin!) + .FirstOrDefault() + ?.Invoke(configData); } /// @@ -118,15 +106,9 @@ namespace VNLib.Plugins.Runtime /// The current process's CLI args internal void InitLog(string[] cliArgs) { - //Get the console handler method from the plugin instance - MethodInfo? logInit = (from m in PluginType.GetMethods() - where m.GetCustomAttribute() != null - select m) - .FirstOrDefault(); - //Get a delegate handler for the plugin - LogInitializer? logFunc = logInit?.CreateDelegate(Plugin); - //Invoke - logFunc?.Invoke(cliArgs); + ManagedLibrary.GetMethodsWithAttribute(Plugin!) + .FirstOrDefault() + ?.Invoke(cliArgs); } /// diff --git a/lib/Utils/src/ERRNO.cs b/lib/Utils/src/ERRNO.cs index 684a3c7..0a95780 100644 --- a/lib/Utils/src/ERRNO.cs +++ b/lib/Utils/src/ERRNO.cs @@ -30,8 +30,12 @@ namespace VNLib.Utils /// /// Implements a C style integer error code type. Size is platform dependent /// + /// + /// Creates a new from the specified error value + /// + /// The value of the error to represent [StructLayout(LayoutKind.Sequential)] - public readonly struct ERRNO : IEquatable, ISpanFormattable, IFormattable + public readonly struct ERRNO(nint errno) : IEquatable, ISpanFormattable, IFormattable { /// /// Represents a successfull error code (true) @@ -43,13 +47,7 @@ namespace VNLib.Utils /// public static readonly ERRNO E_FAIL = false; - private readonly nint ErrorCode; - - /// - /// Creates a new from the specified error value - /// - /// The value of the error to represent - public ERRNO(nint errno) => ErrorCode = errno; + private readonly nint ErrorCode = errno; /// /// Creates a new from an error code. null = 0 = false @@ -130,13 +128,14 @@ namespace VNLib.Utils } return false; } +#pragma warning disable CA1305 // Specify IFormatProvider /// /// The integer error value of the current instance in radix 10 /// /// The radix 10 formatted error code - public readonly override string ToString() => ErrorCode.ToString(); + public override readonly string ToString() => ErrorCode.ToString(); /// /// Formats the internal nint error code as a string in specified format /// @@ -144,11 +143,15 @@ namespace VNLib.Utils /// The formatted error code public readonly string ToString(string format) => ErrorCode.ToString(format); +#pragma warning restore CA1305 // Specify IFormatProvider + /// - public readonly bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => ErrorCode.TryFormat(destination, out charsWritten, format, provider); + public readonly bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) + => ErrorCode.TryFormat(destination, out charsWritten, format, provider); /// - public readonly string ToString(string? format, IFormatProvider? formatProvider) => ErrorCode.ToString(format, formatProvider); + public readonly string ToString(string? format, IFormatProvider? formatProvider) + => ErrorCode.ToString(format, formatProvider); public static ERRNO operator +(ERRNO err, int add) => new(err.ErrorCode + add); public static ERRNO operator +(ERRNO err, nint add) => new(err.ErrorCode + add); diff --git a/lib/Utils/src/Extensions/CacheExtensions.cs b/lib/Utils/src/Extensions/CacheExtensions.cs index 665e282..7efc36d 100644 --- a/lib/Utils/src/Extensions/CacheExtensions.cs +++ b/lib/Utils/src/Extensions/CacheExtensions.cs @@ -52,10 +52,7 @@ namespace VNLib.Utils.Extensions /// public static void StoreRecord(this IDictionary store, TKey key, T record) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } + ArgumentNullException.ThrowIfNull(store); T ?oldRecord = default; lock (store) @@ -128,10 +125,7 @@ namespace VNLib.Utils.Extensions /// public static ERRNO TryGetOrEvictRecord(this IDictionary store, TKey key, out T? value) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } + ArgumentNullException.ThrowIfNull(store); value = default; //Cache current date time before entering the lock @@ -194,10 +188,7 @@ namespace VNLib.Utils.Extensions /// True if the record was found and evicted public static bool EvictRecord(this IDictionary store, TKey key) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } + ArgumentNullException.ThrowIfNull(store); T? record = default; lock (store) @@ -218,10 +209,8 @@ namespace VNLib.Utils.Extensions /// /// /// - public static void CollectRecords(this IDictionary store) where T : ICacheable - { - CollectRecords(store, DateTime.UtcNow); - } + public static void CollectRecords(this IDictionary store) where T : ICacheable + => CollectRecords(store, DateTime.UtcNow); /// /// Evicts all expired records from the store @@ -232,10 +221,7 @@ namespace VNLib.Utils.Extensions /// A time that specifies the time which expired records should be evicted public static void CollectRecords(this IDictionary store, DateTime validAfter) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } + ArgumentNullException.ThrowIfNull(store); //Build a query to get the keys that belong to the expired records IEnumerable> expired = store.Where(s => s.Value.Expires < validAfter); //temp list for expired records @@ -273,15 +259,8 @@ namespace VNLib.Utils.Extensions /// A callback method that will be passed the record to use within an exclusive context public static void UseRecord(this IDictionary store, TKey key, TState state, Action useCtx) where T: ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } - - if (useCtx is null) - { - throw new ArgumentNullException(nameof(useCtx)); - } + ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(useCtx); lock (store) { @@ -303,15 +282,8 @@ namespace VNLib.Utils.Extensions /// A callback method that will be passed the record to use within an exclusive context public static void UseRecord(this IDictionary store, TKey key, Action useCtx) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } - - if (useCtx is null) - { - throw new ArgumentNullException(nameof(useCtx)); - } + ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(useCtx); lock (store) { @@ -335,17 +307,15 @@ namespace VNLib.Utils.Extensions /// A user-token type state parameter to pass to the use callback method /// A callback method that will be passed the record to use within an exclusive context /// If the record is found, but is expired, the record is evicted from the store. The callback is never invoked - public static void UseIfValid(this IDictionary store, TKey key, TState state, Action useCtx) where T : ICacheable + public static void UseIfValid( + this IDictionary store, + TKey key, + TState state, + Action useCtx + ) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } - - if (useCtx is null) - { - throw new ArgumentNullException(nameof(useCtx)); - } + ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(useCtx); DateTime now = DateTime.UtcNow; T? record; @@ -376,15 +346,8 @@ namespace VNLib.Utils.Extensions /// If the record is found, but is expired, the record is evicted from the store. The callback is never invoked public static void UseIfValid(this IDictionary store, TKey key, Action useCtx) where T : ICacheable { - if (store is null) - { - throw new ArgumentNullException(nameof(store)); - } - - if (useCtx is null) - { - throw new ArgumentNullException(nameof(useCtx)); - } + ArgumentNullException.ThrowIfNull(store); + ArgumentNullException.ThrowIfNull(useCtx); DateTime now = DateTime.UtcNow; T? record; diff --git a/lib/Utils/src/IO/TemporayIsolatedFile.cs b/lib/Utils/src/IO/TemporayIsolatedFile.cs deleted file mode 100644 index 3bee92b..0000000 --- a/lib/Utils/src/IO/TemporayIsolatedFile.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: TemporayIsolatedFile.cs -* -* TemporayIsolatedFile.cs is part of VNLib.Utils which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Utils is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Utils is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.IO; -using System.IO.IsolatedStorage; - -namespace VNLib.Utils.IO -{ - /// - /// Allows for temporary files to be generated, used, then removed from an - /// - public sealed class TemporayIsolatedFile : BackingStream - { - private readonly IsolatedStorageDirectory Storage; - private readonly string Filename; - /// - /// Creates a new temporary filestream within the specified - /// - /// The file store to genreate temporary files within - public TemporayIsolatedFile(IsolatedStorageDirectory storage) - { - //Store ref - this.Storage = storage; - //Creaet a new random filename - this.Filename = Path.GetRandomFileName(); - //try to created a new file within the isolaged storage - this.BaseStream = storage.CreateFile(this.Filename); - } - protected override void OnClose() - { - //Remove the file from the storage - Storage.DeleteFile(this.Filename); - } - } -} \ No newline at end of file diff --git a/lib/Utils/src/Memory/ArrayPoolBuffer.cs b/lib/Utils/src/Memory/ArrayPoolBuffer.cs index e728cd7..67789de 100644 --- a/lib/Utils/src/Memory/ArrayPoolBuffer.cs +++ b/lib/Utils/src/Memory/ArrayPoolBuffer.cs @@ -150,20 +150,16 @@ namespace VNLib.Utils.Memory /// A memory structure over the buffer /// /// - public Memory AsMemory() - { - Check(); - return new Memory(Buffer, 0, InitSize); - } + public Memory AsMemory() => AsMemory(start: 0, InitSize); /// /// Gets a memory structure around the internal buffer /// - /// The number of elements included in the result + /// The number of elements included in the result /// A memory structure over the buffer /// /// - public Memory AsMemory(int count) => AsMemory()[..count]; + public Memory AsMemory(int start) => AsMemory(start, InitSize - start); /// /// Gets a memory structure around the internal buffer @@ -173,18 +169,20 @@ namespace VNLib.Utils.Memory /// A memory structure over the buffer /// /// - public Memory AsMemory(int start, int count) => AsMemory().Slice(start, count); + public Memory AsMemory(int start, int count) + { + Check(); + //Memory constructor will check for array bounds + return new(Buffer, start, count); + } /// /// Gets an array segment around the internal buffer /// /// The internal array segment /// - public ArraySegment AsArraySegment() - { - Check(); - return new ArraySegment(Buffer, 0, InitSize); - } + public ArraySegment AsArraySegment() => GetOffsetWrapper(0, InitSize); + /// /// Gets an array segment around the internal buffer @@ -194,10 +192,8 @@ namespace VNLib.Utils.Memory /// public ArraySegment AsArraySegment(int start, int count) { - if(start< 0 || count < 0) - { - throw new ArgumentOutOfRangeException(start < 0 ? nameof(start) : nameof(count), "Cannot be less than zero"); - } + ArgumentOutOfRangeException.ThrowIfNegative(start); + ArgumentOutOfRangeException.ThrowIfNegative(count); MemoryUtil.CheckBounds(Buffer, (uint)start, (uint)count); diff --git a/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs b/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs index 53d3d77..df3990c 100644 --- a/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs +++ b/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs @@ -59,13 +59,17 @@ namespace VNLib.Utils.Memory /// The number of elements remaining in the window /// public readonly int WindowSize => _size - _position; - /// /// Advances the window position the specified number of elements /// /// The number of elements to advance the widnow position - public void Advance(int count) => _position += count; + public void Advance(int count) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(count, WindowSize); + + _position += count; + } /// /// Resets the sliding window to the begining of the buffer diff --git a/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs b/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs index e0fbe41..791d63c 100644 --- a/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs +++ b/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -29,43 +29,39 @@ namespace VNLib.Utils.Memory /// /// Provides a mutable sliding buffer writer /// - public record struct ForwardOnlyMemoryWriter + /// The buffer to write data to + public record struct ForwardOnlyMemoryWriter(Memory Buffer) { - /// - /// The buffer for writing output data to - /// - public readonly Memory Buffer { get; } + private int _written; /// /// The number of characters written to the buffer /// - public int Written { readonly get; set; } + public int Written + { + readonly get => _written; + set + { + ArgumentOutOfRangeException.ThrowIfNegative(value); + _written = value; + } + } /// /// The number of characters remaining in the buffer /// - public readonly int RemainingSize => Buffer.Length - Written; + public readonly int RemainingSize => Buffer.Length - _written; /// /// The remaining buffer window /// - public readonly Memory Remaining => Buffer[Written..]; - - /// - /// Creates a new assigning the specified buffer - /// - /// The buffer to write data to - public ForwardOnlyMemoryWriter(Memory buffer) - { - Buffer = buffer; - Written = 0; - } - + public readonly Memory Remaining => Buffer[_written..]; + /// /// Returns a compiled string from the characters written to the buffer /// /// A string of the characters written to the buffer - public readonly override string ToString() => Buffer[..Written].ToString(); + public readonly override string ToString() => Buffer[.._written].ToString(); /// /// Appends a sequence to the buffer @@ -75,15 +71,14 @@ namespace VNLib.Utils.Memory public void Append(ReadOnlyMemory data) { //Make sure the current window is large enough to buffer the new string - if (data.Length > RemainingSize) - { - throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space"); - } - Memory window = Buffer[Written..]; + ArgumentOutOfRangeException.ThrowIfGreaterThan(data.Length, RemainingSize, nameof(data)); + + Memory window = Buffer[_written..]; + //write data to window data.CopyTo(window); - //update char position - Written += data.Length; + + Advance(data.Length); } /// @@ -94,12 +89,10 @@ namespace VNLib.Utils.Memory public void Append(T c) { //Make sure the current window is large enough to buffer the new string - if (RemainingSize == 0) - { - throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space"); - } + ArgumentOutOfRangeException.ThrowIfZero(RemainingSize); + //Write data to buffer and increment the buffer position - Buffer.Span[Written++] = c; + Buffer.Span[_written++] = c; } /// @@ -109,17 +102,15 @@ namespace VNLib.Utils.Memory /// public void Advance(int count) { - if (count > RemainingSize) - { - throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot advance past the end of the buffer"); - } - Written += count; + ArgumentOutOfRangeException.ThrowIfGreaterThan(count, RemainingSize); + + _written += count; } /// /// Resets the writer by setting the /// property to 0. /// - public void Reset() => Written = 0; + public void Reset() => _written = 0; } } diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 1d3bccb..cbaaa2f 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -155,7 +155,8 @@ namespace VNLib.Utils.Memory /// An for the current process /// /// - public static IUnmangedHeap InitializeNewHeapForProcess(bool globalZero = false) => InitHeapInternal(false, false, globalZero); + public static IUnmangedHeap InitializeNewHeapForProcess(bool globalZero = false) + => InitHeapInternal(false, false, globalZero); private static IUnmangedHeap InitHeapInternal(bool isShared, bool enableStats, bool globalZero) { @@ -228,6 +229,19 @@ namespace VNLib.Utils.Memory #region Zero + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static void ZeroByRef(ref T src, uint elements) + { + Debug.Assert(Unsafe.IsNullRef(ref src) == false, "Null reference passed to ZeroByRef"); + + //Call init block on bytes + Unsafe.InitBlock( + ref Refs.AsByte(ref src, 0), + value: 0, + byteCount: ByteCount(elements) + ); + } + /// /// Zeros a block of memory of umanged type. If Windows is detected at runtime, calls RtlSecureZeroMemory Win32 function /// @@ -241,11 +255,10 @@ namespace VNLib.Utils.Memory return; } - //Calls memset ZeroByRef( - ref Refs.AsByte(block, 0), - (uint)block.Length - ); + ref MemoryMarshal.GetReference(block), //Get typed reference + (uint)block.Length //block must be a positive value + ); } /// @@ -260,28 +273,18 @@ namespace VNLib.Utils.Memory { return; } - - uint byteSize = ByteCount((uint)block.Length); //Pin memory and get pointer using MemoryHandle handle = block.Pin(); - //Calls memset - Unsafe.InitBlock(handle.Pointer, 0, byteSize); - } - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - private static void ZeroByRef(ref T src, uint elements) - { - Debug.Assert(Unsafe.IsNullRef(ref src) == false, "Null reference passed to ZeroByRef"); - //Call init block on bytes + //Calls memset Unsafe.InitBlock( - ref Refs.AsByte(ref src, 0), - 0, - ByteCount(elements) + startAddress: handle.Pointer, + value: 0, + byteCount: ByteCount((uint)block.Length) ); } - + /* * Initializing a non-readonly span/memory as of .NET 6.0 is a reference * reintpretation, essentially a pointer cast, so there is little/no cost @@ -294,7 +297,8 @@ namespace VNLib.Utils.Memory /// The unmanaged /// The block of memory to initialize [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock(Span block) where T : struct => UnsafeZeroMemory(block); + public static void InitializeBlock(Span block) where T : struct + => UnsafeZeroMemory(block); /// /// Initializes a block of memory with zeros @@ -302,7 +306,8 @@ namespace VNLib.Utils.Memory /// The unmanaged /// The block of memory to initialize [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock(Memory block) where T : struct => UnsafeZeroMemory(block); + public static void InitializeBlock(Memory block) where T : struct + => UnsafeZeroMemory(block); /// /// Initializes the entire array with zeros @@ -398,7 +403,8 @@ namespace VNLib.Utils.Memory /// A pointer to the block of memory to zero /// The number of elements in the block to zero [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock(IntPtr block, int itemCount) where T : unmanaged => InitializeBlock((T*)block, itemCount); + public static void InitializeBlock(IntPtr block, int itemCount) where T : unmanaged + => InitializeBlock((T*)block, itemCount); /// /// Zeroes a block of memory pointing to the structure @@ -434,7 +440,8 @@ namespace VNLib.Utils.Memory /// The structure type /// The pointer to the allocated structure [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ZeroStruct(void* structPtr) where T: unmanaged => ZeroStruct((T*)structPtr); + public static void ZeroStruct(void* structPtr) where T: unmanaged + => ZeroStruct((T*)structPtr); /// /// Zeroes a block of memory pointing to the structure @@ -442,7 +449,8 @@ namespace VNLib.Utils.Memory /// The structure type /// The pointer to the allocated structure [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ZeroStruct(IntPtr block) where T : unmanaged => ZeroStruct(block.ToPointer()); + public static void ZeroStruct(IntPtr block) where T : unmanaged + => ZeroStruct(block.ToPointer()); #endregion @@ -462,10 +470,11 @@ namespace VNLib.Utils.Memory ThrowIfNullRef(in source, nameof(target)); ThrowIfNullRef(ref target, nameof(target)); - //Recover byte reference of target struct - ref byte dst = ref Unsafe.As(ref target); - - Unsafe.CopyBlockUnaligned(ref dst, in source, (uint)sizeof(T)); + Unsafe.CopyBlockUnaligned( + destination: ref Unsafe.As(ref target), //Recover byte reference of target struct + in source, + byteCount: ByteCount(1u) + ); } /// @@ -485,11 +494,12 @@ namespace VNLib.Utils.Memory ThrowIfNullRef(in source, nameof(source)); ThrowIfNullRef(in target, nameof(target)); - //Recover byte reference to struct - ref byte src = ref Unsafe.As(ref Unsafe.AsRef(in source)); - //Memmove - Unsafe.CopyBlockUnaligned(ref target, ref src, (uint)sizeof(T)); + Unsafe.CopyBlockUnaligned( + ref target, + in Unsafe.As(ref Unsafe.AsRef(in source)), //Recover byte reference to struct + byteCount: ByteCount(1u) + ); } @@ -594,7 +604,8 @@ namespace VNLib.Utils.Memory /// A reference to the first byte of the memory location to copy the struct data to /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyStruct(void* source, ref byte target) where T : unmanaged => CopyStruct((T*)source, ref target); + public static void CopyStruct(void* source, ref byte target) where T : unmanaged + => CopyStruct((T*)source, ref target); /// /// Copies the memory of the structure pointed to by the source pointer to the target @@ -660,7 +671,8 @@ namespace VNLib.Utils.Memory /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyStruct(void* source, Span target) where T : unmanaged => CopyStruct((T*)source, target); + public static void CopyStruct(void* source, Span target) where T : unmanaged + => CopyStruct((T*)source, target); /// /// Copies the memory of the structure pointed to by the source pointer to the target @@ -675,7 +687,8 @@ namespace VNLib.Utils.Memory /// A reference to the first byte of the memory location to copy the struct data to /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyStruct(IntPtr source, ref byte target) where T : unmanaged => CopyStruct(ref GetRef(source), ref target); + public static void CopyStruct(IntPtr source, ref byte target) where T : unmanaged + => CopyStruct(ref GetRef(source), ref target); /// @@ -694,7 +707,7 @@ namespace VNLib.Utils.Memory Unsafe.CopyBlockUnaligned( ref Refs.AsByte(ref target, 0), in Refs.AsByteR(in source, 0), - (uint)sizeof(T) + byteCount: ByteCount(1u) ); } @@ -711,7 +724,11 @@ namespace VNLib.Utils.Memory ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(target); - Unsafe.CopyBlockUnaligned(target, source, (uint)sizeof(T)); + Unsafe.CopyBlockUnaligned( + destination: target, + source, + byteCount: ByteCount(1u) + ); } @@ -735,7 +752,7 @@ namespace VNLib.Utils.Memory return; } - //Check bounds + //Check bounds (will verify that count is a positive integer) CheckBounds(source, sourceOffset, count); CheckBounds(dest, destOffset, (uint)count); @@ -1151,7 +1168,7 @@ namespace VNLib.Utils.Memory public static nuint ByteSize(IMemoryHandle handle) { ArgumentNullException.ThrowIfNull(handle); - return checked(handle.Length * (nuint)Unsafe.SizeOf()); + return ByteCount(handle.Length); } /// @@ -1162,7 +1179,8 @@ namespace VNLib.Utils.Memory /// The number of bytes pointed to by the handle /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nuint ByteSize(in UnsafeMemoryHandle handle) where T : unmanaged => checked(handle.Length * (nuint)sizeof(T)); + public static nuint ByteSize(in UnsafeMemoryHandle handle) where T : unmanaged + => ByteCount(handle.Length); /// /// Gets the byte multiple of the length parameter @@ -1172,7 +1190,8 @@ namespace VNLib.Utils.Memory /// The byte multiple of the number of elments /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nuint ByteCount(nuint elementCount) => checked(elementCount * (nuint)Unsafe.SizeOf()); + public static nuint ByteCount(nuint elementCount) + => checked(elementCount * (nuint)Unsafe.SizeOf()); /// /// Gets the byte multiple of the length parameter @@ -1182,7 +1201,8 @@ namespace VNLib.Utils.Memory /// The byte multiple of the number of elments /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ByteCount(uint elementCount) => checked(elementCount * (uint)Unsafe.SizeOf()); + public static uint ByteCount(uint elementCount) + => checked(elementCount * (uint)Unsafe.SizeOf()); /// /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values @@ -1192,7 +1212,8 @@ namespace VNLib.Utils.Memory /// The byte multiple of the number of elments /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint ByteCount(nint elementCount) => checked(elementCount * Unsafe.SizeOf()); + public static nint ByteCount(nint elementCount) + => checked(elementCount * Unsafe.SizeOf()); /// /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values @@ -1202,7 +1223,8 @@ namespace VNLib.Utils.Memory /// The byte multiple of the number of elments /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ByteCount(int elementCount) => checked(elementCount * Unsafe.SizeOf()); + public static int ByteCount(int elementCount) + => checked(elementCount * Unsafe.SizeOf()); /// /// Checks if the offset/count paramters for the given memory handle @@ -1243,12 +1265,8 @@ namespace VNLib.Utils.Memory /// The number of bytes expected to be assigned or dereferrenced /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CheckBounds(Span block, int offset, int count) - { - ArgumentOutOfRangeException.ThrowIfNegative(offset); - ArgumentOutOfRangeException.ThrowIfNegative(count); - ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + count, block.Length, nameof(count)); - } + public static void CheckBounds(Span block, int offset, int count) + => CheckBounds((ReadOnlySpan)block, offset, count); /// /// Checks if the offset/count paramters for the given block @@ -1277,12 +1295,8 @@ namespace VNLib.Utils.Memory /// The number of bytes expected to be assigned or dereferrenced /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CheckBounds(Memory block, int offset, int count) - { - ArgumentOutOfRangeException.ThrowIfNegative(offset); - ArgumentOutOfRangeException.ThrowIfNegative(count); - ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + count, block.Length, nameof(count)); - } + public static void CheckBounds(Memory block, int offset, int count) + => CheckBounds((ReadOnlyMemory)block, offset, count); /// /// Checks if the offset/count paramters for the given block @@ -1406,7 +1420,8 @@ namespace VNLib.Utils.Memory /// The size of the sequence /// The span pointing to the memory at the supplied addres [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetSpan(IntPtr address, int size) => new(address.ToPointer(), size); + public static Span GetSpan(IntPtr address, int size) + => new(address.ToPointer(), size); /// /// Gets a over the block of memory pointed to by the supplied handle. @@ -1417,7 +1432,8 @@ namespace VNLib.Utils.Memory /// The size of the span (the size of the block) /// A span over the block of memory pointed to by the handle of the specified size [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetSpan(ref readonly MemoryHandle handle, int size) => new(handle.Pointer, size); + public static Span GetSpan(ref readonly MemoryHandle handle, int size) + => new(handle.Pointer, size); /// /// Gets a over the block of memory pointed to by the supplied handle. @@ -1427,7 +1443,8 @@ namespace VNLib.Utils.Memory /// The size of the span (the size of the block) /// A span over the block of memory pointed to by the handle of the specified size [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetSpan(MemoryHandle handle, int size) => new(handle.Pointer, size); + public static Span GetSpan(MemoryHandle handle, int size) + => new(handle.Pointer, size); /// /// Recovers a reference to the supplied pointer @@ -1436,7 +1453,8 @@ namespace VNLib.Utils.Memory /// The base address to cast to a reference /// The reference to the supplied address [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T GetRef(IntPtr address) => ref Unsafe.AsRef(address.ToPointer()); + public static ref T GetRef(IntPtr address) + => ref Unsafe.AsRef(address.ToPointer()); /// /// Recovers a reference to the supplied pointer @@ -1458,7 +1476,8 @@ namespace VNLib.Utils.Memory /// A reference to the handle to get the intpr for /// A managed pointer from the handle [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IntPtr GetIntptr(ref readonly MemoryHandle handle) => new(handle.Pointer); + public static IntPtr GetIntptr(ref readonly MemoryHandle handle) + => new(handle.Pointer); /// /// Rounds the requested byte size up to the nearest page diff --git a/lib/Utils/src/Memory/NativeHeap.cs b/lib/Utils/src/Memory/NativeHeap.cs index fb9612c..9a2e19a 100644 --- a/lib/Utils/src/Memory/NativeHeap.cs +++ b/lib/Utils/src/Memory/NativeHeap.cs @@ -62,9 +62,9 @@ namespace VNLib.Utils.Memory //Create a flags structure with defaults UnmanagedHeapDescriptor hFlags = new() { - CreationFlags = creationFlags, - Flags = flags, - HeapPointer = IntPtr.Zero + CreationFlags = creationFlags, + Flags = flags, + HeapPointer = IntPtr.Zero }; //Create the heap @@ -117,15 +117,18 @@ namespace VNLib.Utils.Memory /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override IntPtr AllocBlock(nuint elements, nuint size, bool zero) => MethodTable.Alloc(handle, elements, size, zero); + protected override IntPtr AllocBlock(nuint elements, nuint size, bool zero) + => MethodTable.Alloc(handle, elements, size, zero); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override IntPtr ReAllocBlock(IntPtr block, nuint elements, nuint size, bool zero) => MethodTable.Realloc(handle, block, elements, size, zero); + protected override IntPtr ReAllocBlock(IntPtr block, nuint elements, nuint size, bool zero) + => MethodTable.Realloc(handle, block, elements, size, zero); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override bool FreeBlock(IntPtr block) => MethodTable.Free(handle, block); + protected override bool FreeBlock(IntPtr block) + => MethodTable.Free(handle, block); /// protected override bool ReleaseHandle() @@ -139,7 +142,7 @@ namespace VNLib.Utils.Memory //Cleanup the method table MethodTable = default; - Trace.WriteLine($"Successfully deestroyed user defined heap 0x{handle:x}"); + Trace.WriteLine($"Successfully destroyed user defined heap 0x{handle:x}"); return ret; } diff --git a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs index a17a906..8a752f9 100644 --- a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs +++ b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -50,7 +50,8 @@ namespace VNLib.Utils.Memory /// /// /// - public override IMemoryOwner Rent(int minBufferSize = 0) => Heap.DirectAlloc(minBufferSize, false); + public override IMemoryOwner Rent(int minBufferSize = 0) + => Heap.DirectAlloc(minBufferSize, zero: false); /// /// Allocates a new of a different data type from the pool @@ -58,13 +59,10 @@ namespace VNLib.Utils.Memory /// The unmanaged data type to allocate for /// Minumum size of the buffer /// The memory owner of a different data type - public IMemoryOwner Rent(int minBufferSize = 0) where TDifType : unmanaged => Heap.DirectAlloc(minBufferSize, false); + public IMemoryOwner Rent(int minBufferSize = 0) where TDifType : unmanaged + => Heap.DirectAlloc(minBufferSize, zero: false); /// - protected override void Dispose(bool disposing) - { - //Dispose the heap - Heap.Dispose(); - } + protected override void Dispose(bool disposing) => Heap.Dispose(); } } diff --git a/lib/Utils/src/Memory/PrivateStringManager.cs b/lib/Utils/src/Memory/PrivateStringManager.cs index 2bc825c..1a7b3cf 100644 --- a/lib/Utils/src/Memory/PrivateStringManager.cs +++ b/lib/Utils/src/Memory/PrivateStringManager.cs @@ -61,11 +61,11 @@ namespace VNLib.Utils.Memory private void SetValue(int index, string? value) { //Try to get the old reference and erase it - StringRef strRef = ProtectedElements[index]; + ref StringRef strRef = ref ProtectedElements[index]; strRef.Erase(); - //Set the new value and determine if it is interned - ProtectedElements[index] = StringRef.Create(value); + //Assign new string reference + strRef = StringRef.Create(value); } /// @@ -78,7 +78,7 @@ namespace VNLib.Utils.Memory protected string? CopyStringAtIndex(int index) { Check(); - StringRef str = ProtectedElements[index]; + ref readonly StringRef str = ref ProtectedElements[index]; if(str.Value is null) { @@ -101,7 +101,8 @@ namespace VNLib.Utils.Memory } /// - protected override void Free() => Array.ForEach(ProtectedElements, static p => p.Erase()); + protected override void Free() + => Array.ForEach(ProtectedElements, static p => p.Erase()); /// /// Erases the contents of the supplied string if it @@ -109,10 +110,14 @@ namespace VNLib.Utils.Memory /// not be erased, nor will a null string /// /// The reference to the string to zero - public static void EraseString(string? str) => StringRef.Create(str).Erase(); + public static void EraseString(string? str) + => StringRef.Create(str).Erase(); - private readonly record struct StringRef(string? Value, bool IsInterned) + private readonly struct StringRef(string? value, bool isInterned) { + public readonly string? Value = value; + public readonly bool IsInterned = isInterned; + public readonly void Erase() { /* @@ -125,8 +130,8 @@ namespace VNLib.Utils.Memory } } - internal static StringRef Create(string? str) => str is null ? - new(null, false) + internal static StringRef Create(string? str) => str is null + ? new(value: null, isInterned: false) : new(str, string.IsInterned(str) != null); } } diff --git a/lib/Utils/src/Memory/SubSequence.cs b/lib/Utils/src/Memory/SubSequence.cs index 80aa084..57c902e 100644 --- a/lib/Utils/src/Memory/SubSequence.cs +++ b/lib/Utils/src/Memory/SubSequence.cs @@ -58,12 +58,13 @@ namespace VNLib.Utils.Memory { ArgumentNullException.ThrowIfNull(block); ArgumentOutOfRangeException.ThrowIfNegative(size); - Size = size; - Handle = block; - _offset = offset; //Check handle bounds MemoryUtil.CheckBounds(block, offset, (uint)size); + + Size = size; + Handle = block; + _offset = offset; } /// diff --git a/lib/Utils/src/Memory/VnString.cs b/lib/Utils/src/Memory/VnString.cs index 429de43..e0f7283 100644 --- a/lib/Utils/src/Memory/VnString.cs +++ b/lib/Utils/src/Memory/VnString.cs @@ -64,7 +64,11 @@ namespace VNLib.Utils.Memory /// public bool IsEmpty => Length == 0; - private VnString(SubSequence sequence) => _stringSequence = sequence; + private VnString(IMemoryHandle? handle, SubSequence sequence) + { + Handle = handle; + _stringSequence = sequence; + } private VnString(IMemoryHandle handle, nuint start, int length) { @@ -155,15 +159,8 @@ namespace VNLib.Utils.Memory /// public static VnString ConsumeHandle(IMemoryHandle handle, nuint start, int length) { - if (handle is null) - { - throw new ArgumentNullException(nameof(handle)); - } - - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } + ArgumentNullException.ThrowIfNull(handle); + ArgumentOutOfRangeException.ThrowIfNegative(length); //Check handle bounts MemoryUtil.CheckBounds(handle, start, (nuint)length); @@ -217,37 +214,46 @@ namespace VNLib.Utils.Memory try { int length = 0; - //span ref to bin buffer - Span buffer = binBuffer.Span; + //Run in checked context for overflows checked { do - { - //read - int read = stream.Read(buffer); - //guard + { + int read = stream.Read(binBuffer.Span); + if (read <= 0) { break; } + //Slice into only the read data - ReadOnlySpan readbytes = buffer[..read]; + ReadOnlySpan readbytes = binBuffer.AsSpan(0, read); + //get num chars int numChars = encoding.GetCharCount(readbytes); + //Guard for overflow if (((ulong)(numChars + length)) >= int.MaxValue) { throw new OverflowException(); } + //Re-alloc buffer charBuffer.ResizeIfSmaller(length + numChars); + //Decode and update position - _= encoding.GetChars(readbytes, charBuffer.Span.Slice(length, numChars)); + _= encoding.GetChars( + bytes: readbytes, + chars: charBuffer.AsSpan(length, numChars) + ); + //Update char count length += numChars; + } while (true); } + return ConsumeHandle(charBuffer, 0, length); } catch @@ -330,11 +336,15 @@ namespace VNLib.Utils.Memory charBuffer.ResizeIfSmaller(length + numChars); //Decode and update position - _ = encoding.GetChars(binBuffer.GetSpan()[..read], charBuffer.Span.Slice(length, numChars)); + _ = encoding.GetChars( + bytes: binBuffer.GetSpan()[..read], + chars: charBuffer.AsSpan(length, numChars) + ); //Update char count length += numChars; } while (true); + return ConsumeHandle(charBuffer, 0, length); } catch @@ -355,7 +365,6 @@ namespace VNLib.Utils.Memory /// public char CharAt(int index) { - //Check Check(); //Check bounds @@ -377,18 +386,21 @@ namespace VNLib.Utils.Memory /// /// public VnString Substring(int start, int count) - { - //Check + { Check(); + ArgumentOutOfRangeException.ThrowIfNegative(start, nameof(start)); ArgumentOutOfRangeException.ThrowIfNegative(count, nameof(count)); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(start + count, Length, nameof(start)); - //get sub-sequence slice for the current string - SubSequence sub = _stringSequence.Slice((nuint)start, count); - - //Create new string with offsets pointing to same internal referrence - return new VnString(sub); + /* + * Slice the string and do not pass the handle even if we have it because the new + * instance does own the buffer + */ + return new VnString( + handle: null, + _stringSequence.Slice((nuint)start, count) + ); } /// @@ -418,12 +430,18 @@ namespace VNLib.Utils.Memory { get { - //get start - int start = range.Start.IsFromEnd ? Length - range.Start.Value : range.Start.Value; - //Get end - int end = range.End.IsFromEnd ? Length - range.End.Value : range.End.Value; - //Handle strings with no ending range - return (end >= start) ? Substring(start, (end - start)) : Substring(start); + + int start = range.Start.IsFromEnd + ? (Length - range.Start.Value) + : range.Start.Value; + + int end = range.End.IsFromEnd + ? (Length - range.End.Value) + : range.End.Value; + + return (end >= start) + ? Substring(start, (end - start)) + : Substring(start); } } #pragma warning restore IDE0057 // Use range operator @@ -461,47 +479,85 @@ namespace VNLib.Utils.Memory public static explicit operator VnString(string value) => new (value); public static explicit operator VnString(ReadOnlySpan value) => new (value); public static explicit operator VnString(char[] value) => new (value); - /// + + /// + /// + /// NOTE: Avoid this overload if possible. If no explict overload is provided, + /// it's assumed the datatype is not supported and will return false + /// public override bool Equals(object? obj) { - if(obj is null) - { - return false; - } return obj switch { - VnString => Equals(obj as VnString), //Use operator overload - string => Equals(obj as string), //Use operator overload - char[] => Equals(obj as char[]), //Use operator overload + VnString => Equals(obj as VnString), + string => Equals(obj as string), + char[] => Equals(obj as char[]), _ => false, }; } + + /// + public bool Equals(ReadOnlySpan other, StringComparison stringComparison = StringComparison.Ordinal) + => Length == other.Length && AsSpan().Equals(other, stringComparison); + + /// + public bool Equals(VnString? other) + => Equals(other, StringComparison.Ordinal); + /// - public bool Equals(VnString? other) => other is not null && Equals(other.AsSpan()); + public bool Equals(VnString? other, StringComparison stringComparison) + => other is not null && Equals(other.AsSpan(), stringComparison); + /// - public bool Equals(VnString? other, StringComparison stringComparison) => other is not null && Equals(other.AsSpan(), stringComparison); + public bool Equals(string? other) + => Equals(other, StringComparison.Ordinal); + /// - public bool Equals(string? other) => Equals(other.AsSpan()); + public bool Equals(string? other, StringComparison stringComparison) + => Equals(other.AsSpan(), stringComparison); + /// - public bool Equals(string? other, StringComparison stringComparison) => Equals(other.AsSpan(), stringComparison); + public bool Equals(char[]? other) + => Equals(other, StringComparison.Ordinal); + /// - public bool Equals(char[]? other) => Equals(other.AsSpan()); + public bool Equals(char[]? other, StringComparison stringComparison) + => Equals(other.AsSpan(), stringComparison); + /// - public bool Equals(char[]? other, StringComparison stringComparison) => Equals(other.AsSpan(), stringComparison); + public bool Equals(in SubSequence other) + => Equals(in other, StringComparison.Ordinal); + /// - public bool Equals(ReadOnlySpan other, StringComparison stringComparison = StringComparison.Ordinal) => Length == other.Length && AsSpan().Equals(other, stringComparison); + public bool Equals(in SubSequence other, StringComparison stringComparison) + => Length == other.Size && Equals(other.Span, stringComparison); + /// - public bool Equals(in SubSequence other) => Length == other.Size && AsSpan().SequenceEqual(other.Span); + public int CompareTo(string? other) + => CompareTo(other, StringComparison.Ordinal); + /// - public int CompareTo(string? other) => AsSpan().CompareTo(other, StringComparison.Ordinal); + /// + public int CompareTo(string? other, StringComparison stringComparison) + { + ArgumentNullException.ThrowIfNull(other); + return CompareTo(other.AsSpan(), stringComparison); + } + /// /// public int CompareTo(VnString? other) { ArgumentNullException.ThrowIfNull(other); - return AsSpan().CompareTo(other.AsSpan(), StringComparison.Ordinal); + return CompareTo(other.AsSpan(), StringComparison.Ordinal); } + public int CompareTo(ReadOnlySpan other) + => AsSpan().CompareTo(other, StringComparison.Ordinal); + + public int CompareTo(ReadOnlySpan other, StringComparison comparison) + => AsSpan().CompareTo(other, comparison); + /// /// Gets a hashcode for the underyling string by using the .NET /// method on the character representation of the data @@ -512,7 +568,8 @@ namespace VNLib.Utils.Memory /// a character span etc /// /// - public override int GetHashCode() => GetHashCode(StringComparison.Ordinal); + public override int GetHashCode() + => GetHashCode(StringComparison.Ordinal); /// /// Gets a hashcode for the underyling string by using the .NET @@ -525,7 +582,8 @@ namespace VNLib.Utils.Memory /// a character span etc /// /// - public int GetHashCode(StringComparison stringComparison) => string.GetHashCode(AsSpan(), stringComparison); + public int GetHashCode(StringComparison stringComparison) + => string.GetHashCode(AsSpan(), stringComparison); /// protected override void Free() => Handle?.Dispose(); diff --git a/lib/Utils/src/Native/SafeLibraryHandle.cs b/lib/Utils/src/Native/SafeLibraryHandle.cs index 4b4ead4..263ac0c 100644 --- a/lib/Utils/src/Native/SafeLibraryHandle.cs +++ b/lib/Utils/src/Native/SafeLibraryHandle.cs @@ -39,7 +39,8 @@ namespace VNLib.Utils.Native /// public override bool IsInvalid => handle == IntPtr.Zero; - private SafeLibraryHandle(IntPtr libHandle, bool ownsHandle) : base(IntPtr.Zero, ownsHandle) => SetHandle(libHandle); + private SafeLibraryHandle(IntPtr libHandle, bool ownsHandle) : base(IntPtr.Zero, ownsHandle) + => SetHandle(libHandle); /// /// Loads a native function pointer from the library of the specified name and diff --git a/lib/Utils/src/Resources/ManagedLibrary.cs b/lib/Utils/src/Resources/ManagedLibrary.cs index c899156..5faaa19 100644 --- a/lib/Utils/src/Resources/ManagedLibrary.cs +++ b/lib/Utils/src/Resources/ManagedLibrary.cs @@ -263,7 +263,8 @@ namespace VNLib.Utils.Resources /// The optional method binind flags /// The delegate if found otherwise /// - public static TDelegate? TryGetStaticMethod(Type type, string methodName, BindingFlags flags = BindingFlags.Public) where TDelegate : Delegate + public static TDelegate? TryGetStaticMethod(Type type, string methodName, BindingFlags flags = BindingFlags.Public) + where TDelegate : Delegate => TryGetMethodInternal(type, methodName, null, flags | BindingFlags.Static); /// @@ -276,7 +277,8 @@ namespace VNLib.Utils.Resources /// The optional method binind flags /// The delegate if found otherwise /// - public static TDelegate? TryGetStaticMethod(string methodName,BindingFlags flags = BindingFlags.Public) where TDelegate : Delegate + public static TDelegate? TryGetStaticMethod(string methodName,BindingFlags flags = BindingFlags.Public) + where TDelegate : Delegate => TryGetMethodInternal(typeof(TType), methodName, null, flags | BindingFlags.Static); private static TDelegate? TryGetMethodInternal(Type type, string methodName, object? target, BindingFlags flags) where TDelegate : Delegate @@ -293,5 +295,79 @@ namespace VNLib.Utils.Resources return type.GetMethod(methodName, flags, delegateArgs) ?.CreateDelegate(target); } + + /* + * NOTE: These functions cannot be optimized (condensed) any furhter. IE: static + * and instance method searches. This is because the C# compiler will embed the + * call to getType() of the object instead of loading reflection if possible + * at runtime. This can cause the type to be undefined at runtime and will not + * be able to find some members + */ + + /// + /// Gets an array of methods that have the specified attribute and match the + /// delegate signature of the desired type, and returns them as an array of delegates. + /// + /// The function attribute type + /// The function delegate type + /// The object instance to get the method delegates for + /// The method binding flags to search for + /// An array of function with the desired attribute assigned, an empty array if no methods are found + public static TFunc[] GetMethodsWithAttribute(object obj, BindingFlags flags = BindingFlags.Public | BindingFlags.Instance) + where TFunc : Delegate + where TAttr : Attribute + { + ArgumentNullException.ThrowIfNull(obj); + + //Get the delegate type + Type funcType = typeof(TFunc); + + //Get the delegate method signature + Type[] delegateArgs = funcType.GetMethod("Invoke")! + .GetParameters() + .Select(static p => p.ParameterType) + .ToArray(); + + //Get the method with the attribute that matches the same signature as the delegate + return obj.GetType() + .GetMethods(flags) + .Where(static m => m.GetCustomAttribute(typeof(TAttr)) != null) + .Where(m => m.GetParameters().Select(static p => p.ParameterType).SequenceEqual(delegateArgs)) + .Select(method => method.CreateDelegate(obj)) + .ToArray(); + } + + /// + /// Gets an array of static methods that have the specified attribute and match the + /// delegate signature of the desired type, and returns them as an array of delegates. + /// + /// The function attribute type + /// The function delegate type + /// Type of the static class to get methods for + /// The method binding flags to search for + /// An array of function with the desired attribute assigned, an empty array if no methods are found + public static TFunc[] GetStaticMethodsWithAttribute(Type classType, BindingFlags flags = BindingFlags.Public | BindingFlags.Static) + where TFunc : Delegate + where TAttr : Attribute + { + ArgumentNullException.ThrowIfNull(classType); + + //Get the delegate type + Type funcType = typeof(TFunc); + + //Get the delegate method signature + Type[] delegateArgs = funcType.GetMethod("Invoke")! + .GetParameters() + .Select(static p => p.ParameterType) + .ToArray(); + + //Get the method with the attribute that matches the same signature as the delegate + return classType.GetType() + .GetMethods(flags) + .Where(static m => m.GetCustomAttribute(typeof(TAttr)) != null) + .Where(m => m.GetParameters().Select(static p => p.ParameterType).SequenceEqual(delegateArgs)) + .Select(method => method.CreateDelegate(null)) + .ToArray(); + } } } diff --git a/lib/Utils/tests/.runsettings b/lib/Utils/tests/.runsettings index 0e7a703..b13ddb7 100644 --- a/lib/Utils/tests/.runsettings +++ b/lib/Utils/tests/.runsettings @@ -3,6 +3,9 @@ 1 + ../../../../../Utils.Memory/vnlib_rpmalloc/build/Debug/vnlib_rpmalloc.dll + ../../../../../Utils.Memory/vnlib_mimalloc/build/Debug/vnlib_mimalloc.dll + \ No newline at end of file diff --git a/lib/Utils/tests/IO/VnMemoryStreamTests.cs b/lib/Utils/tests/IO/VnMemoryStreamTests.cs index 9bcb823..6bbf328 100644 --- a/lib/Utils/tests/IO/VnMemoryStreamTests.cs +++ b/lib/Utils/tests/IO/VnMemoryStreamTests.cs @@ -47,8 +47,11 @@ namespace VNLib.Utils.IO.Tests Assert.IsTrue(vms.CanWrite == true); } + //Handle should throw since the stream owns the handle and it gets dispoed + Assert.ThrowsException(handle.ThrowIfClosed); + //From existing data - ReadOnlySpan testSpan = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + ReadOnlySpan testSpan = [1, 2, 3, 4, 5, 6, 7, 8]; using (VnMemoryStream vms = new (privateHeap, testSpan)) { Assert.IsTrue(vms.Length == testSpan.Length); @@ -125,19 +128,13 @@ namespace VNLib.Utils.IO.Tests ReadOnlyMemory memory = vms.AsMemory(); Assert.AreEqual(memory.Length, testData.Length); - for (int i = 0; i < memory.Length; i++) - { - Assert.AreEqual(memory.Span[i], testData[i]); - } + Assert.IsTrue(memory.Span.SequenceEqual(testData)); //Get the data as a byte array byte[] array = vms.ToArray(); Assert.AreEqual(array.Length, testData.Length); - for (int i = 0; i < array.Length; i++) - { - Assert.AreEqual(array[i], testData[i]); - } + Assert.IsTrue(array.AsSpan().SequenceEqual(testData)); } } } \ No newline at end of file diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs index 8880010..32d8883 100644 --- a/lib/Utils/tests/Memory/MemoryHandleTest.cs +++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs @@ -23,7 +23,6 @@ */ using System; -using System.Runtime.CompilerServices; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -89,6 +88,7 @@ namespace VNLib.Utils.Memory.Tests handle.Span[120] = 10; Assert.IsTrue(*handle.GetOffset(120) == 10); + Assert.IsTrue(handle.GetOffsetRef(120) == 10); } diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index 68bc35c..bdf8a02 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -33,39 +34,54 @@ namespace VNLib.Utils.Memory.Tests //TODO verify the heap type by loading a dynamic heap dll } - [TestMethod()] - public void UnsafeZeroMemoryTest() - { - //Get random data buffer as a readonly span - ReadOnlyMemory buffer = RandomNumberGenerator.GetBytes(1024); + private static bool AllZero(Span span) where T : struct + => AllZero((ReadOnlySpan)span); - //confirm buffer is not all zero - Assert.IsFalse(AllZero(buffer.Span)); - - //Zero readonly memory - MemoryUtil.UnsafeZeroMemory(buffer); - - //Confirm all zero - Assert.IsTrue(AllZero(buffer.Span)); - } - - private static bool AllZero(ReadOnlySpan span) + private static bool AllZero(ReadOnlySpan span) + where T : struct { - for (int i = 0; i < span.Length; i++) + ReadOnlySpan asBytes = MemoryMarshal.Cast(span); + + for (int i = 0; i < asBytes.Length; i++) { - if (span[i] != 0) + if (asBytes[i] != 0) { return false; } } + return true; } [TestMethod()] - public void UnsafeZeroMemoryTest1() + public void UnsafeZeroMemoryTest() { - //Get random data buffer as a readonly span - ReadOnlySpan buffer = RandomNumberGenerator.GetBytes(1024); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + TestZeroWithDataType(); + } + + + private static void TestZeroWithDataType() + where T : struct + { + Trace.WriteLine($"Testing unsafe zero with data type {typeof(T).Name}"); + + //Get a random buffer that is known to not be all zeros of a given data type + ReadOnlySpan buffer = MemoryMarshal.Cast(RandomNumberGenerator.GetBytes(1024)); //confirm buffer is not all zero Assert.IsFalse(AllZero(buffer)); @@ -545,7 +561,7 @@ namespace VNLib.Utils.Memory.Tests Assert.IsTrue(byteSize == (nuint)Environment.SystemPageSize); } - using(IMemoryHandle safeByteBuffer = MemoryUtil.SafeAllocNearestPage(TEST_1, false)) + using (IMemoryHandle safeByteBuffer = MemoryUtil.SafeAllocNearestPage(TEST_1, false)) { nuint byteSize = MemoryUtil.ByteSize(safeByteBuffer); @@ -712,7 +728,7 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException(() => MemoryUtil.Copy(ReadOnlyMemory.Empty, 0, null, 0, 1)); Assert.ThrowsException(() => MemoryUtil.Copy(ReadOnlySpan.Empty, 0, null, 0, 1)); - + Assert.ThrowsException(() => MemoryUtil.CopyArray((IMemoryHandle)null, 0, testArray, 0, 1)); Assert.ThrowsException(() => MemoryUtil.CopyArray(testHandle, 0, null, 0, 1)); @@ -736,7 +752,7 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException(() => MemoryUtil.CopyArray(testHandle, 0, Array.Empty(), 0, 1)); Assert.ThrowsException(() => MemoryUtil.CopyArray(Array.Empty(), 0, Array.Empty(), 0, 1)); - + /* @@ -826,10 +842,10 @@ namespace VNLib.Utils.Memory.Tests MemoryUtil.CopyArray(testArray, 0, testHandle, 0, 0); MemoryUtil.CopyArray(testArray, 0, [], 0, 0); - /* - * Test negative values for span/memory overloads that - * accept integers - */ + /* + * Test negative values for span/memory overloads that + * accept integers + */ Assert.ThrowsException(() => MemoryUtil.Copy(testHandle, -1, testMem2, 0, 16)); Assert.ThrowsException(() => MemoryUtil.Copy(testHandle, 0, testMem2, -1, 16)); @@ -845,5 +861,49 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException(() => MemoryUtil.Copy(testMem.Span, -1, testHandle, 0, 16)); Assert.ThrowsException(() => MemoryUtil.Copy(testMem.Span, 0, testHandle, 0, -1)); } + + [TestMethod] + public unsafe void ByteSizeTest() + { + Assert.AreEqual( + MemoryUtil.ByteCount(16), + actual: 16 + ); + + Assert.AreEqual( + MemoryUtil.ByteCount(16), + actual: 16 * sizeof(int) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount(16), + actual: 16 * sizeof(long) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount(16), + actual: 16 * sizeof(float) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount(16), + actual: 16 * sizeof(double) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount(16), + actual: 16 * sizeof(nint) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount(16), + actual: 16 * sizeof(TestStruct) + ); + + Assert.AreEqual( + MemoryUtil.ByteCount(0), + actual: 0 + ); + } } } \ No newline at end of file diff --git a/lib/Utils/tests/Memory/NativeHeapTests.cs b/lib/Utils/tests/Memory/NativeHeapTests.cs index 8653bd0..a7072ed 100644 --- a/lib/Utils/tests/Memory/NativeHeapTests.cs +++ b/lib/Utils/tests/Memory/NativeHeapTests.cs @@ -1,20 +1,22 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Runtime.InteropServices; namespace VNLib.Utils.Memory.Tests { [TestClass()] public class NativeHeapTests { - const string RpMallocLibPath = "../../../../../Utils.Memory/vnlib_rpmalloc/build/Debug/vnlib_rpmalloc.dll"; - const string MimallocLibPath = "../../../../../Utils.Memory/vnlib_mimalloc/build/Debug/vnlib_mimalloc.dll"; + private static string? RpMallocLibPath => Environment.GetEnvironmentVariable("TEST_RPMALLOC_LIB_PATH"); + + private static string? MimallocLibPath => Environment.GetEnvironmentVariable("TEST_MIMALLOC_LIB_PATH"); [TestMethod()] public void LoadInTreeRpmallocTest() { //Try to load the shared heap - using NativeHeap heap = NativeHeap.LoadHeap(RpMallocLibPath, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories, HeapCreation.Shared, 0); + using NativeHeap heap = NativeHeap.LoadHeap(RpMallocLibPath, DllImportSearchPath.SafeDirectories, HeapCreation.Shared, flags: 0); Assert.IsFalse(heap.IsInvalid); @@ -36,7 +38,7 @@ namespace VNLib.Utils.Memory.Tests public void LoadInTreeMimallocTest() { //Try to load the shared heap - using NativeHeap heap = NativeHeap.LoadHeap(MimallocLibPath, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories, HeapCreation.Shared, 0); + using NativeHeap heap = NativeHeap.LoadHeap(MimallocLibPath, DllImportSearchPath.SafeDirectories, HeapCreation.Shared, flags: 0); Assert.IsFalse(heap.IsInvalid); -- cgit