Skip to content

Commit 7b09239

Browse files
committed
feat(int64): change namespace to NumSharp.Utilities and fix compilation
This commit completes the UnmanagedSpan implementation for long indexing support: **Namespace Change** - Changed all SpanSource files from `namespace System` to `namespace NumSharp.Utilities` - Added `using System;` to all files for standard types **Removed .NET Internal Dependencies** - Removed internal attributes: [NonVersionable], [Intrinsic], [RequiresUnsafe], [CompExactlyDependsOn], [OverloadResolutionPriority] - Replaced RuntimeHelpers.QCall P/Invoke with NativeMemory.Copy/Fill - Removed Unsafe.IsOpportunisticallyAligned (NET9+ only) - Removed BulkMoveWithWriteBarrier (internal .NET method) **Added `unmanaged` Constraint** - Added `where T : unmanaged` to UnmanagedSpan<T>, ReadOnlyUnmanagedSpan<T> - Added constraint to UnmanagedSpanDebugView<T> and helper methods - Removed CastUp<TDerived> method (for reference types only) **Deleted Unnecessary Files (~85K lines)** - UnmanagedSpanExtensions*.cs (5 files) - advanced string/char features - UnmanagedSpanHelpers.BinarySearch.cs - search helpers - UnmanagedSpanHelpers.Byte.cs, .Char.cs - type-specific helpers - UnmanagedSpanHelpers.Packed.cs - SIMD packed operations - UnmanagedSpanHelpers.T.cs - complex SIMD with ISimdVector<> - Utilities/UnmanagedSpan.cs - old simple implementation (backup exists) **Simplified Core Files** - UnmanagedBuffer.cs: Simplified to only Memmove<T> for unmanaged types - UnmanagedSpanHelpers.cs: Added vectorized Fill<T> method - Fixed ulong→nuint conversions for Clear, Fill, CopyTo methods - Fixed ToString() to use char* for string creation **Remaining Files (7 files, ~52K lines)** - UnmanagedSpan.cs - main type with long indexing - ReadOnlyUnmanagedSpan.cs - read-only variant - UnmanagedBuffer.cs - memory copy operations - UnmanagedSpanHelpers.cs - ClearWithReferences, Reverse, Fill<T> - UnmanagedSpanHelpers.ByteMemOps.cs - Memmove, ClearWithoutReferences - UnmanagedSpanDebugView.cs - debugger visualization - UnmanagedSpanThrowHelper.cs - exception helpers Build: SUCCESS (4399 tests pass)
1 parent 87f49c4 commit 7b09239

18 files changed

Lines changed: 216 additions & 16601 deletions

src/NumSharp.Core/Utilities/SpanSource/ReadOnlyUnmanagedSpan.cs

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
// Licensed to the .NET Foundation under one or more agreements.
23
// The .NET Foundation licenses this file to you under the MIT license.
34

@@ -15,18 +16,15 @@
1516

1617
#pragma warning disable 0809 // Obsolete member 'UnmanagedSpan<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)'
1718

18-
namespace System
19+
namespace NumSharp.Utilities
1920
{
2021
/// <summary>
2122
/// ReadOnlyUnmanagedSpan represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed
2223
/// or native memory, or to memory allocated on the stack. It is type-safe and memory-safe.
2324
/// </summary>
2425
[DebuggerTypeProxy(typeof(UnmanagedSpanDebugView<>))]
2526
[DebuggerDisplay("{ToString(),raw}")]
26-
[NonVersionable]
27-
[NativeMarshalling(typeof(ReadOnlyUnmanagedSpanMarshaller<,>))]
28-
[Intrinsic]
29-
public readonly ref struct ReadOnlyUnmanagedSpan<T>
27+
public readonly ref struct ReadOnlyUnmanagedSpan<T> where T : unmanaged
3028
{
3129
/// <summary>A byref or a native ptr.</summary>
3230
internal readonly ref T _reference;
@@ -101,7 +99,6 @@ public ReadOnlyUnmanagedSpan(T[]? array, int start, int length)
10199
/// </exception>
102100
[CLSCompliant(false)]
103101
[MethodImpl(MethodImplOptions.AggressiveInlining)]
104-
[RequiresUnsafe]
105102
public unsafe ReadOnlyUnmanagedSpan(void* pointer, long length)
106103
{
107104
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
@@ -142,9 +139,7 @@ internal ReadOnlyUnmanagedSpan(ref T reference, long length)
142139
/// </exception>
143140
public ref readonly T this[long index]
144141
{
145-
[Intrinsic]
146142
[MethodImpl(MethodImplOptions.AggressiveInlining)]
147-
[NonVersionable]
148143
get
149144
{
150145
if ((ulong)index >= (ulong)_length)
@@ -158,8 +153,6 @@ public ref readonly T this[long index]
158153
/// </summary>
159154
public long Length
160155
{
161-
[Intrinsic]
162-
[NonVersionable]
163156
get => _length;
164157
}
165158

@@ -169,7 +162,6 @@ public long Length
169162
/// <value><see langword="true"/> if this span is empty; otherwise, <see langword="false"/>.</value>
170163
public bool IsEmpty
171164
{
172-
[NonVersionable]
173165
get => _length == 0;
174166
}
175167

@@ -217,17 +209,7 @@ public static implicit operator ReadOnlyUnmanagedSpan<T>(ArraySegment<T> segment
217209
/// </summary>
218210
public static ReadOnlyUnmanagedSpan<T> Empty => default;
219211

220-
/// <summary>
221-
/// Casts a read-only span of <typeparamref name="TDerived"/> to a read-only span of <typeparamref name="T"/>.
222-
/// </summary>
223-
/// <typeparam name="TDerived">The element type of the source read-only span, which must be derived from <typeparamref name="T"/>.</typeparam>
224-
/// <param name="items">The source read-only span. No copy is made.</param>
225-
/// <returns>A read-only span with elements cast to the new type.</returns>
226-
/// <remarks>This method uses a covariant cast, producing a read-only span that shares the same memory as the source. The relationships expressed in the type constraints ensure that the cast is a safe operation.</remarks>
227-
public static ReadOnlyUnmanagedSpan<T> CastUp<TDerived>(ReadOnlyUnmanagedSpan<TDerived> items) where TDerived : class?, T
228-
{
229-
return new ReadOnlyUnmanagedSpan<T>(ref Unsafe.As<TDerived, T>(ref items._reference), items.Length);
230-
}
212+
// Note: CastUp<TDerived> method removed - not applicable for unmanaged types
231213

232214
/// <summary>Gets an enumerator for this span.</summary>
233215
public Enumerator GetEnumerator() => new Enumerator(this);
@@ -308,13 +290,9 @@ public ref readonly T GetPinnableReference()
308290
[MethodImpl(MethodImplOptions.AggressiveInlining)]
309291
public void CopyTo(UnmanagedSpan<T> destination)
310292
{
311-
// Using "if (!TryCopyTo(...))" results in two branches: one for the length
312-
// check, and one for the result of TryCopyTo. Since these checks are equivalent,
313-
// we can optimize by performing the check once ourselves then calling Memmove directly.
314-
315293
if ((ulong)_length <= (ulong)destination.Length)
316294
{
317-
UnmanagedBuffer.Memmove(ref destination._reference, ref _reference, (ulong)_length);
295+
UnmanagedBuffer.Memmove(ref destination._reference, ref Unsafe.AsRef(in _reference), checked((nuint)_length));
318296
}
319297
else
320298
{
@@ -335,7 +313,7 @@ public bool TryCopyTo(UnmanagedSpan<T> destination)
335313
bool retVal = false;
336314
if ((ulong)_length <= (ulong)destination.Length)
337315
{
338-
UnmanagedBuffer.Memmove(ref destination._reference, ref _reference, (ulong)_length);
316+
UnmanagedBuffer.Memmove(ref destination._reference, ref Unsafe.AsRef(in _reference), checked((nuint)_length));
339317
retVal = true;
340318
}
341319
return retVal;
@@ -353,13 +331,16 @@ public bool TryCopyTo(UnmanagedSpan<T> destination)
353331
/// For <see cref="ReadOnlyUnmanagedSpan{Char}"/>, returns a new instance of string that represents the characters pointed to by the span.
354332
/// Otherwise, returns a <see cref="string"/> with the name of the type and the number of elements.
355333
/// </summary>
356-
public override string ToString()
334+
public override unsafe string ToString()
357335
{
358336
if (typeof(T) == typeof(char))
359337
{
360-
return new string(new ReadOnlyUnmanagedSpan<char>(ref Unsafe.As<T, char>(ref _reference), _length));
338+
// For char spans, create string. Need to handle long length.
339+
if (_length > int.MaxValue)
340+
return $"NumSharp.Utilities.ReadOnlyUnmanagedSpan<Char>[{_length}]";
341+
return new string((char*)Unsafe.AsPointer(ref Unsafe.AsRef(in _reference)), 0, (int)_length);
361342
}
362-
return $"System.ReadOnlyUnmanagedSpan<{typeof(T).Name}>[{_length}]";
343+
return $"NumSharp.Utilities.ReadOnlyUnmanagedSpan<{typeof(T).Name}>[{_length}]";
363344
}
364345

365346
/// <summary>
Lines changed: 31 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,23 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
3+
// Simplified for NumSharp - only supports unmanaged types (no reference type handling)
34

4-
using System.Diagnostics;
5-
using System.Diagnostics.CodeAnalysis;
5+
using System;
66
using System.Runtime.CompilerServices;
77
using System.Runtime.InteropServices;
8-
using System.Threading;
98

10-
namespace System
9+
namespace NumSharp.Utilities
1110
{
11+
/// <summary>
12+
/// Provides low-level memory copy operations for unmanaged types.
13+
/// </summary>
1214
public static partial class UnmanagedBuffer
1315
{
14-
// Copies from one primitive array to another primitive array without
15-
// respecting types. This calls memmove internally. The count and
16-
// offset parameters here are in bytes. If you want to use traditional
17-
// array element indices and counts, use Array.Copy.
18-
public static void BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count)
19-
{
20-
ArgumentNullException.ThrowIfNull(src);
21-
ArgumentNullException.ThrowIfNull(dst);
22-
23-
nuint uSrcLen = src.NativeLength;
24-
if (src.GetType() != typeof(byte[]))
25-
{
26-
if (!src.GetCorElementTypeOfElementType().IsPrimitiveType())
27-
throw new ArgumentException(SR.Arg_MustBePrimArray, nameof(src));
28-
uSrcLen *= (nuint)src.GetElementSize();
29-
}
30-
31-
nuint uDstLen = uSrcLen;
32-
if (src != dst)
33-
{
34-
uDstLen = dst.NativeLength;
35-
if (dst.GetType() != typeof(byte[]))
36-
{
37-
if (!dst.GetCorElementTypeOfElementType().IsPrimitiveType())
38-
throw new ArgumentException(SR.Arg_MustBePrimArray, nameof(dst));
39-
uDstLen *= (nuint)dst.GetElementSize();
40-
}
41-
}
42-
43-
ArgumentOutOfRangeException.ThrowIfNegative(srcOffset);
44-
ArgumentOutOfRangeException.ThrowIfNegative(dstOffset);
45-
ArgumentOutOfRangeException.ThrowIfNegative(count);
46-
47-
nuint uCount = (nuint)count;
48-
nuint uSrcOffset = (nuint)srcOffset;
49-
nuint uDstOffset = (nuint)dstOffset;
50-
51-
if ((uSrcLen < uSrcOffset + uCount) || (uDstLen < uDstOffset + uCount))
52-
throw new ArgumentException(SR.Argument_InvalidOffLen);
53-
54-
Memmove(ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(dst), uDstOffset), ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(src), uSrcOffset), uCount);
55-
}
56-
57-
public static int ByteLength(Array array)
58-
{
59-
ArgumentNullException.ThrowIfNull(array);
60-
61-
// Is it of primitive types?
62-
if (!array.GetCorElementTypeOfElementType().IsPrimitiveType())
63-
throw new ArgumentException(SR.Arg_MustBePrimArray, nameof(array));
64-
65-
nuint byteLength = array.NativeLength * (nuint)array.GetElementSize();
66-
67-
// This API is exposed both as UnmanagedBuffer.ByteLength and also used indirectly in argument
68-
// checks for UnmanagedBuffer.GetByte/SetByte.
69-
//
70-
// If somebody called Get/SetByte on 2GB+ arrays, there is a decent chance that
71-
// the computation of the index has overflowed. Thus we intentionally always
72-
// throw on 2GB+ arrays in Get/SetByte argument checks (even for indices <2GB)
73-
// to prevent people from running into a trap silently.
74-
75-
return checked((int)byteLength);
76-
}
77-
78-
public static byte GetByte(Array array, int index)
79-
{
80-
// array argument validation done via ByteLength
81-
if ((uint)index >= (uint)ByteLength(array))
82-
{
83-
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
84-
}
85-
86-
return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
87-
}
88-
89-
public static void SetByte(Array array, int index, byte value)
90-
{
91-
// array argument validation done via ByteLength
92-
if ((uint)index >= (uint)ByteLength(array))
93-
{
94-
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
95-
}
96-
97-
Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index) = value;
98-
}
99-
100-
// The attributes on this method are chosen for best JIT performance.
101-
// Please do not edit unless intentional.
16+
/// <summary>
17+
/// Copies bytes from source to destination using unmanaged memory copy.
18+
/// </summary>
10219
[MethodImpl(MethodImplOptions.AggressiveInlining)]
10320
[CLSCompliant(false)]
104-
[RequiresUnsafe]
10521
public static unsafe void MemoryCopy(void* source, void* destination, long destinationSizeInBytes, long sourceBytesToCopy)
10622
{
10723
if (sourceBytesToCopy > destinationSizeInBytes)
@@ -112,11 +28,11 @@ public static unsafe void MemoryCopy(void* source, void* destination, long desti
11228
Memmove(ref *(byte*)destination, ref *(byte*)source, checked((nuint)sourceBytesToCopy));
11329
}
11430

115-
// The attributes on this method are chosen for best JIT performance.
116-
// Please do not edit unless intentional.
31+
/// <summary>
32+
/// Copies bytes from source to destination using unmanaged memory copy.
33+
/// </summary>
11734
[MethodImpl(MethodImplOptions.AggressiveInlining)]
11835
[CLSCompliant(false)]
119-
[RequiresUnsafe]
12036
public static unsafe void MemoryCopy(void* source, void* destination, ulong destinationSizeInBytes, ulong sourceBytesToCopy)
12137
{
12238
if (sourceBytesToCopy > destinationSizeInBytes)
@@ -127,88 +43,30 @@ public static unsafe void MemoryCopy(void* source, void* destination, ulong dest
12743
Memmove(ref *(byte*)destination, ref *(byte*)source, checked((nuint)sourceBytesToCopy));
12844
}
12945

130-
#if !MONO // Mono BulkMoveWithWriteBarrier is in terms of elements (not bytes) and takes a type handle.
131-
132-
[Intrinsic]
46+
/// <summary>
47+
/// Copies elements from source to destination.
48+
/// For unmanaged types only - does not handle reference types.
49+
/// </summary>
13350
[MethodImpl(MethodImplOptions.AggressiveInlining)]
134-
internal static unsafe void Memmove<T>(ref T destination, ref T source, nuint elementCount)
135-
{
136-
if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>())
137-
{
138-
// Blittable memmove
139-
UnmanagedSpanHelpers.Memmove(
140-
ref Unsafe.As<T, byte>(ref destination),
141-
ref Unsafe.As<T, byte>(ref source),
142-
elementCount * (nuint)sizeof(T));
143-
}
144-
else
145-
{
146-
// Non-blittable memmove
147-
BulkMoveWithWriteBarrier(
148-
ref Unsafe.As<T, byte>(ref destination),
149-
ref Unsafe.As<T, byte>(ref source),
150-
elementCount * (nuint)sizeof(T));
151-
}
152-
}
153-
154-
// The maximum block size to for BulkMoveWithWriteBarrierInternal FCall. This is required to avoid GC starvation.
155-
#if DEBUG // Stress the mechanism in debug builds
156-
private const uint BulkMoveWithWriteBarrierChunk = 0x400;
157-
#else
158-
private const uint BulkMoveWithWriteBarrierChunk = 0x4000;
159-
#endif
160-
161-
internal static void BulkMoveWithWriteBarrier(ref byte destination, ref byte source, nuint byteCount)
51+
internal static unsafe void Memmove<T>(ref T destination, ref T source, nuint elementCount) where T : unmanaged
16252
{
163-
if (byteCount <= BulkMoveWithWriteBarrierChunk)
164-
{
165-
BulkMoveWithWriteBarrierInternal(ref destination, ref source, byteCount);
166-
Thread.FastPollGC();
167-
}
168-
else
169-
{
170-
BulkMoveWithWriteBarrierBatch(ref destination, ref source, byteCount);
171-
}
53+
UnmanagedSpanHelpers.Memmove(
54+
ref Unsafe.As<T, byte>(ref destination),
55+
ref Unsafe.As<T, byte>(ref source),
56+
elementCount * (nuint)sizeof(T));
17257
}
17358

174-
// Non-inlinable wrapper around the loop for copying large blocks in chunks
175-
[MethodImpl(MethodImplOptions.NoInlining)]
176-
private static void BulkMoveWithWriteBarrierBatch(ref byte destination, ref byte source, nuint byteCount)
59+
/// <summary>
60+
/// Copies elements from source to destination with ulong element count.
61+
/// For unmanaged types only - does not handle reference types.
62+
/// </summary>
63+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
64+
internal static unsafe void Memmove<T>(ref T destination, ref T source, ulong elementCount) where T : unmanaged
17765
{
178-
Debug.Assert(byteCount > BulkMoveWithWriteBarrierChunk);
179-
180-
if (Unsafe.AreSame(ref source, ref destination))
181-
return;
182-
183-
// This is equivalent to: (destination - source) >= byteCount || (destination - source) < 0
184-
if ((nuint)(nint)Unsafe.ByteOffset(ref source, ref destination) >= byteCount)
185-
{
186-
// Copy forwards
187-
do
188-
{
189-
byteCount -= BulkMoveWithWriteBarrierChunk;
190-
BulkMoveWithWriteBarrierInternal(ref destination, ref source, BulkMoveWithWriteBarrierChunk);
191-
Thread.FastPollGC();
192-
destination = ref Unsafe.AddByteOffset(ref destination, BulkMoveWithWriteBarrierChunk);
193-
source = ref Unsafe.AddByteOffset(ref source, BulkMoveWithWriteBarrierChunk);
194-
}
195-
while (byteCount > BulkMoveWithWriteBarrierChunk);
196-
}
197-
else
198-
{
199-
// Copy backwards
200-
do
201-
{
202-
byteCount -= BulkMoveWithWriteBarrierChunk;
203-
BulkMoveWithWriteBarrierInternal(ref Unsafe.AddByteOffset(ref destination, byteCount), ref Unsafe.AddByteOffset(ref source, byteCount), BulkMoveWithWriteBarrierChunk);
204-
Thread.FastPollGC();
205-
}
206-
while (byteCount > BulkMoveWithWriteBarrierChunk);
207-
}
208-
BulkMoveWithWriteBarrierInternal(ref destination, ref source, byteCount);
209-
Thread.FastPollGC();
66+
UnmanagedSpanHelpers.Memmove(
67+
ref Unsafe.As<T, byte>(ref destination),
68+
ref Unsafe.As<T, byte>(ref source),
69+
checked((nuint)(elementCount * (ulong)sizeof(T))));
21070
}
211-
212-
#endif // !MONO
21371
}
21472
}

0 commit comments

Comments
 (0)