From a132736682ecab28436f314a80ba94c995e95537 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 10:10:33 +0300 Subject: [PATCH 1/3] Initial commit with task details for issue #22 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/linksplatform/Memory/issues/22 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8c631ea --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/linksplatform/Memory/issues/22 +Your prepared branch: issue-22-2734ad85 +Your prepared working directory: /tmp/gh-issue-solver-1757833828999 + +Proceed. \ No newline at end of file From 5220a94b5ffeb32a11143ac5bf95a838dd407f92 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 10:19:01 +0300 Subject: [PATCH 2/3] Add ArrayMemory to DirectMemory adapter implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement solution for issue #22 by creating adapters that convert IArrayMemory into IDirectMemory through GCHandle pinning mechanism. - ArrayMemoryAsDirectMemoryAdapter: Optimized adapter for ArrayMemory using reflection to access internal array and pin it directly - ArrayMemoryAsDirectMemoryAdapterGeneral: General adapter for any IArrayMemory implementation using array copy and pinning strategy - Comprehensive unit tests covering both adapters with memory safety and pointer stability verification πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- ...MemoryAsDirectMemoryAdapterGeneralTests.cs | 130 +++++++++++++++ .../ArrayMemoryAsDirectMemoryAdapterTests.cs | 85 ++++++++++ .../ArrayMemoryAsDirectMemoryAdapter.cs | 95 +++++++++++ ...ArrayMemoryAsDirectMemoryAdapterGeneral.cs | 150 ++++++++++++++++++ 4 files changed, 460 insertions(+) create mode 100644 csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterGeneralTests.cs create mode 100644 csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterTests.cs create mode 100644 csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapter.cs create mode 100644 csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapterGeneral.cs diff --git a/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterGeneralTests.cs b/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterGeneralTests.cs new file mode 100644 index 0000000..90a7ab9 --- /dev/null +++ b/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterGeneralTests.cs @@ -0,0 +1,130 @@ +using System; +using Xunit; +using Platform.Unsafe; + +namespace Platform.Memory.Tests +{ + public unsafe class ArrayMemoryAsDirectMemoryAdapterGeneralTests + { + [Fact] + public void BasicFunctionalityWithArrayMemoryTest() + { + var arrayMemory = new ArrayMemory(10); + arrayMemory[0] = 42; + arrayMemory[9] = 84; + + using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(arrayMemory); + + Assert.Equal(10 * sizeof(int), adapter.Size); + Assert.NotEqual(IntPtr.Zero, adapter.Pointer); + + var pointer = (int*)adapter.Pointer; + Assert.Equal(42, pointer[0]); + Assert.Equal(84, pointer[9]); + } + + [Fact] + public void ReadOnlyModeTest() + { + var arrayMemory = new ArrayMemory(5); + arrayMemory[0] = 100; + + var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(arrayMemory, isReadOnly: true); + + var pointer = (int*)adapter.Pointer; + Assert.Equal(100, pointer[0]); + + // Modify through pointer + pointer[0] = 200; + + // In read-only mode, changes should not sync back to original + adapter.Dispose(); + + Assert.Equal(100, arrayMemory[0]); // Original should be unchanged + } + + [Fact] + public void ReadWriteModeTest() + { + var arrayMemory = new ArrayMemory(5); + arrayMemory[0] = 100; + + using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(arrayMemory, isReadOnly: false); + + var pointer = (int*)adapter.Pointer; + Assert.Equal(100, pointer[0]); + + // Modify through pointer + pointer[0] = 200; + + // Manually sync changes back + adapter.SyncToArrayMemory(); + + Assert.Equal(200, arrayMemory[0]); + } + + [Fact] + public void SyncOnDisposeTest() + { + var arrayMemory = new ArrayMemory(3); + arrayMemory[1] = 50; + + var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(arrayMemory, isReadOnly: false); + + var pointer = (byte*)adapter.Pointer; + pointer[1] = 75; + + // Changes should sync back on dispose + adapter.Dispose(); + + Assert.Equal(75, arrayMemory[1]); + } + + [Fact] + public void WorksWithFileArrayMemoryTest() + { + var tempFile = System.IO.Path.GetTempFileName(); + try + { + using var fileArrayMemory = new FileArrayMemory(tempFile); + + // Initialize some data + fileArrayMemory[0] = 123; + + using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(fileArrayMemory, isReadOnly: true); + + Assert.NotEqual(IntPtr.Zero, adapter.Pointer); + + var pointer = (int*)adapter.Pointer; + Assert.Equal(123, pointer[0]); + } + finally + { + if (System.IO.File.Exists(tempFile)) + { + System.IO.File.Delete(tempFile); + } + } + } + + [Fact] + public void PointerStabilityTest() + { + var arrayMemory = new ArrayMemory(50); + using var adapter = new ArrayMemoryAsDirectMemoryAdapterGeneral(arrayMemory); + + var pointer1 = adapter.Pointer; + var pointer2 = adapter.Pointer; + + Assert.Equal(pointer1, pointer2); + + // Force garbage collection + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + var pointer3 = adapter.Pointer; + Assert.Equal(pointer1, pointer3); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterTests.cs b/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterTests.cs new file mode 100644 index 0000000..0ed8495 --- /dev/null +++ b/csharp/Platform.Memory.Tests/ArrayMemoryAsDirectMemoryAdapterTests.cs @@ -0,0 +1,85 @@ +using System; +using Xunit; +using Platform.Unsafe; + +namespace Platform.Memory.Tests +{ + public unsafe class ArrayMemoryAsDirectMemoryAdapterTests + { + [Fact] + public void BasicFunctionalityTest() + { + var arrayMemory = new ArrayMemory(10); + arrayMemory[0] = 42; + arrayMemory[9] = 84; + + using var adapter = new ArrayMemoryAsDirectMemoryAdapter(arrayMemory); + + Assert.Equal(10 * sizeof(int), adapter.Size); + Assert.NotEqual(IntPtr.Zero, adapter.Pointer); + + var pointer = (int*)adapter.Pointer; + Assert.Equal(42, pointer[0]); + Assert.Equal(84, pointer[9]); + } + + [Fact] + public void PointerStabilityTest() + { + var arrayMemory = new ArrayMemory(100); + using var adapter = new ArrayMemoryAsDirectMemoryAdapter(arrayMemory); + + var pointer1 = adapter.Pointer; + var pointer2 = adapter.Pointer; + + Assert.Equal(pointer1, pointer2); + + // Force garbage collection + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + var pointer3 = adapter.Pointer; + Assert.Equal(pointer1, pointer3); + } + + [Fact] + public void WriteAccessTest() + { + var arrayMemory = new ArrayMemory(5); + using var adapter = new ArrayMemoryAsDirectMemoryAdapter(arrayMemory); + + var pointer = (byte*)adapter.Pointer; + pointer[0] = 255; + pointer[4] = 128; + + Assert.Equal(255, arrayMemory[0]); + Assert.Equal(128, arrayMemory[4]); + } + + [Fact] + public void SizeCalculationTest() + { + var arrayMemory = new ArrayMemory(25); + using var adapter = new ArrayMemoryAsDirectMemoryAdapter(arrayMemory); + + Assert.Equal(25 * sizeof(double), adapter.Size); + } + + [Fact] + public void DisposalTest() + { + var arrayMemory = new ArrayMemory(10); + var adapter = new ArrayMemoryAsDirectMemoryAdapter(arrayMemory); + + var pointer = adapter.Pointer; + Assert.NotEqual(IntPtr.Zero, pointer); + + adapter.Dispose(); + + // After disposal, the pointer should still be the same value, + // but the GCHandle should be freed (we can't easily test this without internals access) + Assert.NotEqual(IntPtr.Zero, adapter.Pointer); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapter.cs b/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapter.cs new file mode 100644 index 0000000..8350750 --- /dev/null +++ b/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapter.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Platform.Disposables; +using Platform.Exceptions; +using Platform.Unsafe; + +namespace Platform.Memory +{ + /// + /// Represents adapter from a memory block with access via indexer to direct memory access. + /// ΠŸΡ€Π΅Π΄ΡΡ‚Π°Π²Π»ΡΠ΅Ρ‚ Π°Π΄Π°ΠΏΡ‚Π΅Ρ€ ΠΎΡ‚ Π±Π»ΠΎΠΊΠ° памяти с доступом Ρ‡Π΅Ρ€Π΅Π· индСксатор ΠΊ прямому доступу ΠΊ памяти. + /// + /// Element type.Π’ΠΈΠΏ элСмСнта. + public class ArrayMemoryAsDirectMemoryAdapter : DisposableBase, IDirectMemory + where TElement : struct + { + #region Fields + private readonly ArrayMemory _arrayMemory; + private readonly GCHandle _pinnedHandle; + private readonly IntPtr _pointer; + + #endregion + + #region Properties + + /// + /// + public long Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _arrayMemory.Size * Structure.Size; + } + + /// + /// + public IntPtr Pointer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _pointer; + } + + #endregion + + #region DisposableBase Properties + + /// + protected override string ObjectName + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => $"Pinned array memory at '{_pointer}' address."; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ Π½ΠΎΠ²Ρ‹ΠΉ экзСмпляр класса . + /// + /// An object implementing class.ΠžΠ±ΡŠΠ΅ΠΊΡ‚, Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΡŽΡ‰ΠΈΠΉ класс . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayMemoryAsDirectMemoryAdapter(ArrayMemory arrayMemory) + { + Ensure.Always.ArgumentNotNull(arrayMemory, nameof(arrayMemory)); + _arrayMemory = arrayMemory; + + // Use reflection to get the underlying array from ArrayMemory + var field = typeof(ArrayMemory).GetField("_array", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + Ensure.Always.ArgumentMeetsCriteria(field, f => f != null, nameof(arrayMemory), "Cannot access internal array field of ArrayMemory."); + var array = (TElement[])field!.GetValue(_arrayMemory)!; + + // Pin the array in memory + _pinnedHandle = GCHandle.Alloc(array, GCHandleType.Pinned); + _pointer = _pinnedHandle.AddrOfPinnedObject(); + } + + #endregion + + #region DisposableBase Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void Dispose(bool manual, bool wasDisposed) + { + if (!wasDisposed && _pinnedHandle.IsAllocated) + { + _pinnedHandle.Free(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapterGeneral.cs b/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapterGeneral.cs new file mode 100644 index 0000000..1915a58 --- /dev/null +++ b/csharp/Platform.Memory/ArrayMemoryAsDirectMemoryAdapterGeneral.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Platform.Disposables; +using Platform.Exceptions; +using Platform.Unsafe; + +namespace Platform.Memory +{ + /// + /// Represents adapter from any IArrayMemory implementation to direct memory access. + /// ΠŸΡ€Π΅Π΄ΡΡ‚Π°Π²Π»ΡΠ΅Ρ‚ Π°Π΄Π°ΠΏΡ‚Π΅Ρ€ ΠΎΡ‚ любой Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ IArrayMemory ΠΊ прямому доступу ΠΊ памяти. + /// + /// Element type.Π’ΠΈΠΏ элСмСнта. + public class ArrayMemoryAsDirectMemoryAdapterGeneral : DisposableBase, IDirectMemory + where TElement : struct + { + #region Fields + private readonly IArrayMemory _arrayMemory; + private readonly TElement[] _pinnedArray; + private readonly GCHandle _pinnedHandle; + private readonly IntPtr _pointer; + private readonly bool _isReadOnly; + + #endregion + + #region Properties + + /// + /// + public long Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _arrayMemory.Size * Structure.Size; + } + + /// + /// + public IntPtr Pointer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + SyncFromArrayMemory(); + return _pointer; + } + } + + #endregion + + #region DisposableBase Properties + + /// + protected override string ObjectName + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => $"Pinned array memory adapter at '{_pointer}' address."; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ Π½ΠΎΠ²Ρ‹ΠΉ экзСмпляр класса . + /// + /// An object implementing interface.ΠžΠ±ΡŠΠ΅ΠΊΡ‚, Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΡŽΡ‰ΠΈΠΉ интСрфСйс . + /// Whether the adapter is read-only (won't sync changes back).ЯвляСтся Π»ΠΈ Π°Π΄Π°ΠΏΡ‚Π΅Ρ€ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для чтСния (Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ измСнСния ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎ). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayMemoryAsDirectMemoryAdapterGeneral(IArrayMemory arrayMemory, bool isReadOnly = false) + { + Ensure.Always.ArgumentNotNull(arrayMemory, nameof(arrayMemory)); + _arrayMemory = arrayMemory; + _isReadOnly = isReadOnly; + + // Create a managed array copy + _pinnedArray = new TElement[_arrayMemory.Size]; + + // Copy data from the source + for (long i = 0; i < _arrayMemory.Size; i++) + { + _pinnedArray[i] = _arrayMemory[i]; + } + + // Pin the array in memory + _pinnedHandle = GCHandle.Alloc(_pinnedArray, GCHandleType.Pinned); + _pointer = _pinnedHandle.AddrOfPinnedObject(); + } + + #endregion + + #region Methods + + /// + /// Synchronizes changes from the original array memory to the pinned copy. + /// Π‘ΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ измСнСния ΠΈΠ· исходной памяти массива Π² Π·Π°ΠΊΡ€Π΅ΠΏΠ»Π΅Π½Π½ΡƒΡŽ копию. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SyncFromArrayMemory() + { + for (long i = 0; i < _arrayMemory.Size; i++) + { + _pinnedArray[i] = _arrayMemory[i]; + } + } + + /// + /// Synchronizes changes from the pinned copy back to the original array memory. + /// Π‘ΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ измСнСния ΠΈΠ· Π·Π°ΠΊΡ€Π΅ΠΏΠ»Π΅Π½Π½ΠΎΠΉ ΠΊΠΎΠΏΠΈΠΈ ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎ Π² ΠΈΡΡ…ΠΎΠ΄Π½ΡƒΡŽ ΠΏΠ°ΠΌΡΡ‚ΡŒ массива. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SyncToArrayMemory() + { + if (_isReadOnly) + { + return; + } + + for (long i = 0; i < _arrayMemory.Size; i++) + { + _arrayMemory[i] = _pinnedArray[i]; + } + } + + #endregion + + #region DisposableBase Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void Dispose(bool manual, bool wasDisposed) + { + if (!wasDisposed) + { + if (!_isReadOnly) + { + SyncToArrayMemory(); + } + + if (_pinnedHandle.IsAllocated) + { + _pinnedHandle.Free(); + } + } + } + + #endregion + } +} \ No newline at end of file From 9bc9b4844ee39e02e01b5eab2b0675e117cbb9e1 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 10:19:49 +0300 Subject: [PATCH 3/3] Remove CLAUDE.md - Claude command completed --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 8c631ea..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/linksplatform/Memory/issues/22 -Your prepared branch: issue-22-2734ad85 -Your prepared working directory: /tmp/gh-issue-solver-1757833828999 - -Proceed. \ No newline at end of file