Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 3240aab

Browse files
ektrahahsonkhan
authored andcommitted
Add Overlaps extension method to SpanExtensions (#24980)
* Add Overlaps extension method to SpanExtensions * Fix integer overflow * this * Fix off-by-one error * Naming * Replace ~x + 1 with -x * Add overloads that output elementOffset * Fix bug, address PR reviews * check (first.IsEmpty || second.IsEmpty) to fix bug * separate implementations for 32-bit and 64-bit platforms * throw exception for unaligned overlap * Add unit tests * Fix ref\ * Do not use Math.DivRem * Add exception message * Actually add tests to test project * Revert "Add exception message" This reverts commit c956191. * Round away from zero if unaligned * Improve doc comments * Nit * Add implementation notes * Fix botched rebase * Throw an exception if spans are not correctly aligned * Fix nit * Fix nit (2nd try) * Update documentation comment
1 parent 595502f commit 3240aab

File tree

6 files changed

+1229
-3
lines changed

6 files changed

+1229
-3
lines changed

src/System.Memory/ref/System.Memory.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ public static class MemoryExtensions
134134
public static ReadOnlySpan<TTo> NonPortableCast<TFrom, TTo>(this ReadOnlySpan<TFrom> source) where TFrom : struct where TTo : struct { throw null; }
135135

136136
public static bool TryGetString(this ReadOnlyMemory<char> readOnlyMemory, out string text, out int start, out int length) { throw null; }
137+
138+
public static bool Overlaps<T>(this Span<T> first, ReadOnlySpan<T> second) { throw null; }
139+
public static bool Overlaps<T>(this Span<T> first, ReadOnlySpan<T> second, out int elementOffset) { throw null; }
140+
public static bool Overlaps<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second) { throw null; }
141+
public static bool Overlaps<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second, out int elementOffset) { throw null; }
137142
}
138143

139144
public readonly struct ReadOnlyMemory<T>

src/System.Memory/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,7 @@
150150
<data name="Argument_PrecisionTooLarge" xml:space="preserve">
151151
<value>Precision cannot be larger than {0}.</value>
152152
</data>
153+
<data name="Argument_OverlapAlignmentMismatch" xml:space="preserve">
154+
<value>Overlapping spans have mismatching alignment.</value>
155+
</data>
153156
</root>

src/System.Memory/src/System/MemoryExtensions.cs

Lines changed: 226 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

@@ -299,7 +299,230 @@ public static void CopyTo<T>(this T[] array, Span<T> destination)
299299
[MethodImpl(MethodImplOptions.AggressiveInlining)]
300300
public static void CopyTo<T>(this T[] array, Memory<T> destination)
301301
{
302-
array.CopyTo(destination.Span);
302+
array.CopyTo(destination.Span);
303+
}
304+
305+
//
306+
// Overlaps
307+
// ========
308+
//
309+
// The following methods can be used to determine if two sequences
310+
// overlap in memory.
311+
//
312+
// Two sequences overlap if they have positions in common and neither
313+
// is empty. Empty sequences do not overlap with any other sequence.
314+
//
315+
// If two sequences overlap, the element offset is the number of
316+
// elements by which the second sequence is offset from the first
317+
// sequence (i.e., second minus first). An exception is thrown if the
318+
// number is not a whole number, which can happen when a sequence of a
319+
// smaller type is cast to a sequence of a larger type with unsafe code
320+
// or NonPortableCast. If the sequences do not overlap, the offset is
321+
// meaningless and arbitrarily set to zero.
322+
//
323+
// Implementation
324+
// --------------
325+
//
326+
// Implementing this correctly is quite tricky due of two problems:
327+
//
328+
// * If the sequences refer to two different objects on the managed
329+
// heap, the garbage collector can move them freely around or change
330+
// their relative order in memory.
331+
//
332+
// * The distance between two sequences can be greater than
333+
// int.MaxValue (on a 32-bit system) or long.MaxValue (on a 64-bit
334+
// system).
335+
//
336+
// (For simplicity, the following text assumes a 32-bit system, but
337+
// everything also applies to a 64-bit system if every 32 is replaced a
338+
// 64.)
339+
//
340+
// The first problem is solved by calculating the distance with exactly
341+
// one atomic operation. If the garbage collector happens to move the
342+
// sequences afterwards and the sequences overlapped before, they will
343+
// still overlap after the move and their distance hasn't changed. If
344+
// the sequences did not overlap, the distance can change but the
345+
// sequences still won't overlap.
346+
//
347+
// The second problem is solved by making all addresses relative to the
348+
// start of the first sequence and performing all operations in
349+
// unsigned integer arithmetic modulo 2³².
350+
//
351+
// Example
352+
// -------
353+
//
354+
// Let's say there are two sequences, x and y. Let
355+
//
356+
// ref T xRef = x.DangerousGetPinnableReference()
357+
// uint xLength = x.Length * Unsafe.SizeOf<T>()
358+
// ref T yRef = y.DangerousGetPinnableReference()
359+
// uint yLength = y.Length * Unsafe.SizeOf<T>()
360+
//
361+
// Visually, the two sequences are located somewhere in the 32-bit
362+
// address space as follows:
363+
//
364+
// [----------------------------------------------) normal address space
365+
// 0 2³²
366+
// [------------------) first sequence
367+
// xRef xRef + xLength
368+
// [--------------------------) . second sequence
369+
// yRef . yRef + yLength
370+
// : . . .
371+
// : . . .
372+
// . . .
373+
// . . .
374+
// . . .
375+
// [----------------------------------------------) relative address space
376+
// 0 . . 2³²
377+
// [------------------) : first sequence
378+
// x1 . x2 :
379+
// -------------) [------------- second sequence
380+
// y2 y1
381+
//
382+
// The idea is to make all addresses relative to xRef: Let x1 be the
383+
// start address of x in this relative address space, x2 the end
384+
// address of x, y1 the start address of y, and y2 the end address of
385+
// y:
386+
//
387+
// nuint x1 = 0
388+
// nuint x2 = xLength
389+
// nuint y1 = (nuint)Unsafe.ByteOffset(xRef, yRef)
390+
// nuint y2 = y1 + yLength
391+
//
392+
// xRef relative to xRef is 0.
393+
//
394+
// x2 is simply x1 + xLength. This cannot overflow.
395+
//
396+
// yRef relative to xRef is (yRef - xRef). If (yRef - xRef) is
397+
// negative, casting it to an unsigned 32-bit integer turns it into
398+
// (yRef - xRef + 2³²). So, in the example above, y1 moves to the right
399+
// of x2.
400+
//
401+
// y2 is simply y1 + yLength. Note that this can overflow, as in the
402+
// example above, which must be avoided.
403+
//
404+
// The two sequences do *not* overlap if y is entirely in the space
405+
// right of x in the relative address space. (It can't be left of it!)
406+
//
407+
// (y1 >= x2) && (y2 <= 2³²)
408+
//
409+
// Inversely, they do overlap if
410+
//
411+
// (y1 < x2) || (y2 > 2³²)
412+
//
413+
// After substituting x2 and y2 with their respective definition:
414+
//
415+
// == (y1 < xLength) || (y1 + yLength > 2³²)
416+
//
417+
// Since yLength can't be greater than the size of the address space,
418+
// the overflow can be avoided as follows:
419+
//
420+
// == (y1 < xLength) || (y1 > 2³² - yLength)
421+
//
422+
// However, 2³² cannot be stored in an unsigned 32-bit integer, so one
423+
// more change is needed to keep doing everything with unsigned 32-bit
424+
// integers:
425+
//
426+
// == (y1 < xLength) || (y1 > -yLength)
427+
//
428+
// Due to modulo arithmetic, this gives exactly same result *except* if
429+
// yLength is zero, since 2³² - 0 is 0 and not 2³². So the case
430+
// y.IsEmpty must be handled separately first.
431+
//
432+
433+
/// <summary>
434+
/// Determines whether two sequences overlap in memory.
435+
/// </summary>
436+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
437+
public static bool Overlaps<T>(this Span<T> first, ReadOnlySpan<T> second)
438+
{
439+
return Overlaps((ReadOnlySpan<T>)first, second);
440+
}
441+
442+
/// <summary>
443+
/// Determines whether two sequences overlap in memory and outputs the element offset.
444+
/// </summary>
445+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
446+
public static bool Overlaps<T>(this Span<T> first, ReadOnlySpan<T> second, out int elementOffset)
447+
{
448+
return Overlaps((ReadOnlySpan<T>)first, second, out elementOffset);
449+
}
450+
451+
/// <summary>
452+
/// Determines whether two sequences overlap in memory.
453+
/// </summary>
454+
public static bool Overlaps<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second)
455+
{
456+
if (first.IsEmpty || second.IsEmpty)
457+
{
458+
return false;
459+
}
460+
461+
IntPtr byteOffset = Unsafe.ByteOffset(
462+
ref first.DangerousGetPinnableReference(),
463+
ref second.DangerousGetPinnableReference());
464+
465+
if (Unsafe.SizeOf<IntPtr>() == sizeof(int))
466+
{
467+
return (uint)byteOffset < (uint)(first.Length * Unsafe.SizeOf<T>()) ||
468+
(uint)byteOffset > (uint)-(second.Length * Unsafe.SizeOf<T>());
469+
}
470+
else
471+
{
472+
return (ulong)byteOffset < (ulong)((long)first.Length * Unsafe.SizeOf<T>()) ||
473+
(ulong)byteOffset > (ulong)-((long)second.Length * Unsafe.SizeOf<T>());
474+
}
475+
}
476+
477+
/// <summary>
478+
/// Determines whether two sequences overlap in memory and outputs the element offset.
479+
/// </summary>
480+
public static bool Overlaps<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second, out int elementOffset)
481+
{
482+
if (first.IsEmpty || second.IsEmpty)
483+
{
484+
elementOffset = 0;
485+
return false;
486+
}
487+
488+
IntPtr byteOffset = Unsafe.ByteOffset(
489+
ref first.DangerousGetPinnableReference(),
490+
ref second.DangerousGetPinnableReference());
491+
492+
if (Unsafe.SizeOf<IntPtr>() == sizeof(int))
493+
{
494+
if ((uint)byteOffset < (uint)(first.Length * Unsafe.SizeOf<T>()) ||
495+
(uint)byteOffset > (uint)-(second.Length * Unsafe.SizeOf<T>()))
496+
{
497+
if ((int)byteOffset % Unsafe.SizeOf<T>() != 0)
498+
ThrowHelper.ThrowArgumentException_OverlapAlignmentMismatch();
499+
500+
elementOffset = (int)byteOffset / Unsafe.SizeOf<T>();
501+
return true;
502+
}
503+
else
504+
{
505+
elementOffset = 0;
506+
return false;
507+
}
508+
}
509+
else
510+
{
511+
if ((ulong)byteOffset < (ulong)((long)first.Length * Unsafe.SizeOf<T>()) ||
512+
(ulong)byteOffset > (ulong)-((long)second.Length * Unsafe.SizeOf<T>()))
513+
{
514+
if ((long)byteOffset % Unsafe.SizeOf<T>() != 0)
515+
ThrowHelper.ThrowArgumentException_OverlapAlignmentMismatch();
516+
517+
elementOffset = (int)((long)byteOffset / Unsafe.SizeOf<T>());
518+
return true;
519+
}
520+
else
521+
{
522+
elementOffset = 0;
523+
return false;
524+
}
525+
}
303526
}
304527
}
305-
}
528+
}

src/System.Memory/src/System/ThrowHelper.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ internal static class ThrowHelper
6868
[MethodImpl(MethodImplOptions.NoInlining)]
6969
private static Exception CreateFormatException_BadFormatSpecifier() { return new FormatException(SR.Argument_BadFormatSpecifier); }
7070

71+
internal static void ThrowArgumentException_OverlapAlignmentMismatch() { throw CreateArgumentException_OverlapAlignmentMismatch(); }
72+
[MethodImpl(MethodImplOptions.NoInlining)]
73+
private static Exception CreateArgumentException_OverlapAlignmentMismatch() { return new ArgumentException(SR.Argument_OverlapAlignmentMismatch); }
74+
7175
//
7276
// Enable use of ThrowHelper from TryFormat() routines without introducing dozens of non-code-coveraged "bytesWritten = 0; return false" boilerplate.
7377
//

0 commit comments

Comments
 (0)