Skip to content

Commit 82d6d46

Browse files
hamarb123jkotas
andauthored
Implement Unsafe.IsAddressLessThanOrEqualTo & Unsafe.IsAddressGreaterThanOrEqualTo (#117800)
* Implement Unsafe. IsAddressLessThanOrEqualTo & Unsafe.IsAddressGreaterThanOrEqualTo * Update vectorization-guidelines.md Co-authored-by: Jan Kotas <[email protected]>
1 parent a8c169a commit 82d6d46

File tree

15 files changed

+184
-60
lines changed

15 files changed

+184
-60
lines changed

docs/coding-guidelines/vectorization-guidelines.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ int ManagedReferencesSum(int[] buffer)
523523

524524
Vector128<int> sum = Vector128<int>.Zero;
525525

526-
while (!Unsafe.IsAddressGreaterThan(ref current, ref oneVectorAwayFromEnd))
526+
while (Unsafe.IsAddressLessThanOrEqualTo(ref current, ref oneVectorAwayFromEnd))
527527
{
528528
sum += Vector128.LoadUnsafe(ref current);
529529

@@ -561,7 +561,7 @@ do
561561

562562
return ...;
563563
}
564-
while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace));
564+
while (Unsafe.IsAddressGreaterThanOrEqualTo(ref currentSearchSpace, ref searchSpace));
565565
```
566566

567567
It was part of `LastIndexOf` implementation, where we were iterating from the end to the beginning of the buffer. In the last iteration of the loop, `currentSearchSpace` could become a pointer to unknown memory that lied before the beginning of the buffer:
@@ -573,7 +573,7 @@ currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128<TValu
573573
And it was fine until GC kicked right after that, moved objects in memory, updated all valid managed references and resumed the execution, which run following condition:
574574

575575
```csharp
576-
while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace));
576+
while (Unsafe.IsAddressGreaterThanOrEqualTo(ref currentSearchSpace, ref searchSpace));
577577
```
578578

579579
Which could return true because `currentSearchSpace` was invalid and not updated. If you are interested in more details, you can check the [issue](https://github.com/dotnet/runtime/issues/75792#issuecomment-1249973858) and the [fix](https://github.com/dotnet/runtime/pull/75857).

src/coreclr/jit/fgbasic.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,9 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed
12451245
case NI_SRCS_UNSAFE_AreSame:
12461246
case NI_SRCS_UNSAFE_ByteOffset:
12471247
case NI_SRCS_UNSAFE_IsAddressGreaterThan:
1248+
case NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo:
12481249
case NI_SRCS_UNSAFE_IsAddressLessThan:
1250+
case NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo:
12491251
case NI_SRCS_UNSAFE_IsNullRef:
12501252
case NI_SRCS_UNSAFE_Subtract:
12511253
case NI_SRCS_UNSAFE_SubtractByteOffset:
@@ -1260,7 +1262,9 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed
12601262
{
12611263
case NI_SRCS_UNSAFE_AreSame:
12621264
case NI_SRCS_UNSAFE_IsAddressGreaterThan:
1265+
case NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo:
12631266
case NI_SRCS_UNSAFE_IsAddressLessThan:
1267+
case NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo:
12641268
case NI_SRCS_UNSAFE_IsNullRef:
12651269
{
12661270
fgObserveInlineConstants(opcode, pushedStack, isInlining);

src/coreclr/jit/importercalls.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5503,6 +5503,25 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic,
55035503
return gtFoldExpr(tmp);
55045504
}
55055505

5506+
case NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo:
5507+
{
5508+
assert(sig->sigInst.methInstCount == 1);
5509+
5510+
// ldarg.0
5511+
// ldarg.1
5512+
// clt.un
5513+
// ldc.i4.0
5514+
// ceq
5515+
// ret
5516+
5517+
GenTree* op2 = impPopStack().val;
5518+
GenTree* op1 = impPopStack().val;
5519+
5520+
GenTree* tmp = gtNewOperNode(GT_GE, TYP_INT, op1, op2);
5521+
tmp->gtFlags |= GTF_UNSIGNED;
5522+
return gtFoldExpr(tmp);
5523+
}
5524+
55065525
case NI_SRCS_UNSAFE_IsAddressLessThan:
55075526
{
55085527
assert(sig->sigInst.methInstCount == 1);
@@ -5520,6 +5539,25 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic,
55205539
return gtFoldExpr(tmp);
55215540
}
55225541

5542+
case NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo:
5543+
{
5544+
assert(sig->sigInst.methInstCount == 1);
5545+
5546+
// ldarg.0
5547+
// ldarg.1
5548+
// cgt.un
5549+
// ldc.i4.0
5550+
// ceq
5551+
// ret
5552+
5553+
GenTree* op2 = impPopStack().val;
5554+
GenTree* op1 = impPopStack().val;
5555+
5556+
GenTree* tmp = gtNewOperNode(GT_LE, TYP_INT, op1, op2);
5557+
tmp->gtFlags |= GTF_UNSIGNED;
5558+
return gtFoldExpr(tmp);
5559+
}
5560+
55235561
case NI_SRCS_UNSAFE_IsNullRef:
55245562
{
55255563
assert(sig->sigInst.methInstCount == 1);
@@ -10722,10 +10760,18 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
1072210760
{
1072310761
result = NI_SRCS_UNSAFE_IsAddressGreaterThan;
1072410762
}
10763+
else if (strcmp(methodName, "IsAddressGreaterThanOrEqualTo") == 0)
10764+
{
10765+
result = NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo;
10766+
}
1072510767
else if (strcmp(methodName, "IsAddressLessThan") == 0)
1072610768
{
1072710769
result = NI_SRCS_UNSAFE_IsAddressLessThan;
1072810770
}
10771+
else if (strcmp(methodName, "IsAddressLessThanOrEqualTo") == 0)
10772+
{
10773+
result = NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo;
10774+
}
1072910775
else if (strcmp(methodName, "IsNullRef") == 0)
1073010776
{
1073110777
result = NI_SRCS_UNSAFE_IsNullRef;

src/coreclr/jit/namedintrinsiclist.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ enum NamedIntrinsic : unsigned short
221221
NI_SRCS_UNSAFE_InitBlock,
222222
NI_SRCS_UNSAFE_InitBlockUnaligned,
223223
NI_SRCS_UNSAFE_IsAddressGreaterThan,
224+
NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo,
224225
NI_SRCS_UNSAFE_IsAddressLessThan,
226+
NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo,
225227
NI_SRCS_UNSAFE_IsNullRef,
226228
NI_SRCS_UNSAFE_NullRef,
227229
NI_SRCS_UNSAFE_Read,

src/libraries/System.Linq/src/System/Linq/Range.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private static void FillIncrementing<T>(Span<T> destination, T value) where T :
9797
current += increment;
9898
pos = ref Unsafe.Add(ref pos, Vector<T>.Count);
9999
}
100-
while (!Unsafe.IsAddressGreaterThan(ref pos, ref oneVectorFromEnd));
100+
while (Unsafe.IsAddressLessThanOrEqualTo(ref pos, ref oneVectorFromEnd));
101101

102102
value = current[0];
103103
}

src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ArraySortHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ private static unsafe int PickPivotAndPartition(Span<T> keys)
487487
while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) && LessThan(ref pivot, ref rightRef = ref Unsafe.Add(ref rightRef, -1))) ;
488488
}
489489

490-
if (!Unsafe.IsAddressLessThan(ref leftRef, ref rightRef))
490+
if (Unsafe.IsAddressGreaterThanOrEqualTo(ref leftRef, ref rightRef))
491491
{
492492
break;
493493
}

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,22 @@ public static bool IsAddressGreaterThan<T>([AllowNull] ref readonly T left, [All
404404
// ret
405405
}
406406

407+
/// <summary>
408+
/// Determines whether the memory address referenced by <paramref name="left"/> is greater than
409+
/// or equal to the memory address referenced by <paramref name="right"/>.
410+
/// </summary>
411+
/// <remarks>
412+
/// This check is conceptually similar to "(void*)(&amp;left) &gt;= (void*)(&amp;right)".
413+
/// </remarks>
414+
[Intrinsic]
415+
[NonVersionable]
416+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
417+
public static bool IsAddressGreaterThanOrEqualTo<T>([AllowNull] ref readonly T left, [AllowNull] ref readonly T right)
418+
where T : allows ref struct
419+
{
420+
return !IsAddressLessThan(in left, in right);
421+
}
422+
407423
/// <summary>
408424
/// Determines whether the memory address referenced by <paramref name="left"/> is less than
409425
/// the memory address referenced by <paramref name="right"/>.
@@ -428,6 +444,22 @@ public static bool IsAddressLessThan<T>([AllowNull] ref readonly T left, [AllowN
428444
// ret
429445
}
430446

447+
/// <summary>
448+
/// Determines whether the memory address referenced by <paramref name="left"/> is less than
449+
/// or equal to the memory address referenced by <paramref name="right"/>.
450+
/// </summary>
451+
/// <remarks>
452+
/// This check is conceptually similar to "(void*)(&amp;left) &lt;= (void*)(&amp;right)".
453+
/// </remarks>
454+
[Intrinsic]
455+
[NonVersionable]
456+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
457+
public static bool IsAddressLessThanOrEqualTo<T>([AllowNull] ref readonly T left, [AllowNull] ref readonly T right)
458+
where T : allows ref struct
459+
{
460+
return !IsAddressGreaterThan(in left, in right);
461+
}
462+
431463
/// <summary>
432464
/// Initializes a block of memory at the given location with a given initial value.
433465
/// </summary>

src/libraries/System.Private.CoreLib/src/System/SearchValues/IndexOfAnyAsciiSearcher.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ private static TResult IndexOfAnyCore<TResult, TNegator, TOptimizations, TUnique
321321
// As packing two Vector256<short>s into a Vector256<byte> is cheap compared to the lookup, we can effectively double the throughput.
322322
// If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
323323
// Let the fallback below handle it instead. This is why the condition is
324-
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan".
324+
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo".
325325
ref short twoVectorsAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - (2 * Vector256<short>.Count));
326326

327327
do
@@ -374,7 +374,7 @@ private static TResult IndexOfAnyCore<TResult, TNegator, TOptimizations, TUnique
374374
// As packing two Vector128<short>s into a Vector128<byte> is cheap compared to the lookup, we can effectively double the throughput.
375375
// If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
376376
// Let the fallback below handle it instead. This is why the condition is
377-
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan".
377+
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo".
378378
ref short twoVectorsAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - (2 * Vector128<short>.Count));
379379

380380
do
@@ -453,7 +453,7 @@ public static int LastIndexOfAny<TNegator, TOptimizations, TUniqueLowNibble>(ref
453453
// As packing two Vector256<short>s into a Vector256<byte> is cheap compared to the lookup, we can effectively double the throughput.
454454
// If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
455455
// Let the fallback below handle it instead. This is why the condition is
456-
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan".
456+
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrEqualTo".
457457
ref short twoVectorsAfterStart = ref Unsafe.Add(ref searchSpace, 2 * Vector256<short>.Count);
458458

459459
do
@@ -504,7 +504,7 @@ public static int LastIndexOfAny<TNegator, TOptimizations, TUniqueLowNibble>(ref
504504
// As packing two Vector128<short>s into a Vector128<byte> is cheap compared to the lookup, we can effectively double the throughput.
505505
// If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
506506
// Let the fallback below handle it instead. This is why the condition is
507-
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan".
507+
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrEqualTo".
508508
ref short twoVectorsAfterStart = ref Unsafe.Add(ref searchSpace, 2 * Vector128<short>.Count);
509509

510510
do
@@ -604,7 +604,7 @@ private static TResult IndexOfAnyCore<TResult, TNegator, TUniqueLowNibble, TResu
604604
// Process the input in chunks of 32 bytes.
605605
// If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
606606
// Let the fallback below handle it instead. This is why the condition is
607-
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan".
607+
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo".
608608
ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector256<byte>.Count);
609609

610610
do
@@ -653,7 +653,7 @@ private static TResult IndexOfAnyCore<TResult, TNegator, TUniqueLowNibble, TResu
653653
// Process the input in chunks of 16 bytes.
654654
// If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
655655
// Let the fallback below handle it instead. This is why the condition is
656-
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan".
656+
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo".
657657
ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128<byte>.Count);
658658

659659
do
@@ -729,7 +729,7 @@ public static int LastIndexOfAny<TNegator, TUniqueLowNibble>(ref byte searchSpac
729729
// Process the input in chunks of 32 bytes.
730730
// If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
731731
// Let the fallback below handle it instead. This is why the condition is
732-
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan".
732+
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrEqualTo".
733733
ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector256<byte>.Count);
734734

735735
do
@@ -778,7 +778,7 @@ public static int LastIndexOfAny<TNegator, TUniqueLowNibble>(ref byte searchSpac
778778
// Process the input in chunks of 16 bytes.
779779
// If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
780780
// Let the fallback below handle it instead. This is why the condition is
781-
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan".
781+
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrEqualTo".
782782
ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128<byte>.Count);
783783

784784
do
@@ -876,7 +876,7 @@ private static TResult IndexOfAnyCore<TResult, TNegator, TResultMapper>(ref byte
876876
// Process the input in chunks of 32 bytes.
877877
// If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
878878
// Let the fallback below handle it instead. This is why the condition is
879-
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan".
879+
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo".
880880
ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector256<byte>.Count);
881881

882882
do
@@ -928,7 +928,7 @@ private static TResult IndexOfAnyCore<TResult, TNegator, TResultMapper>(ref byte
928928
// Process the input in chunks of 16 bytes.
929929
// If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
930930
// Let the fallback below handle it instead. This is why the condition is
931-
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "!IsAddressGreaterThan".
931+
// ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo".
932932
ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128<byte>.Count);
933933

934934
do
@@ -1004,7 +1004,7 @@ public static int LastIndexOfAny<TNegator>(ref byte searchSpace, int searchSpace
10041004
// Process the input in chunks of 32 bytes.
10051005
// If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
10061006
// Let the fallback below handle it instead. This is why the condition is
1007-
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan".
1007+
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrEqualTo".
10081008
ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector256<byte>.Count);
10091009

10101010
do
@@ -1056,7 +1056,7 @@ public static int LastIndexOfAny<TNegator>(ref byte searchSpace, int searchSpace
10561056
// Process the input in chunks of 16 bytes.
10571057
// If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
10581058
// Let the fallback below handle it instead. This is why the condition is
1059-
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "!IsAddressLessThan".
1059+
// ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrEqualTo".
10601060
ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128<byte>.Count);
10611061

10621062
do

src/libraries/System.Private.CoreLib/src/System/SearchValues/ProbabilisticMap.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ private static int LastIndexOfAnyVectorizedAvx512<TUseFastContains>(ref char sea
626626
}
627627
}
628628

629-
if (!Unsafe.IsAddressGreaterThan(ref cur, ref lastStartVector))
629+
if (Unsafe.IsAddressLessThanOrEqualTo(ref cur, ref lastStartVector))
630630
{
631631
if (Unsafe.AreSame(ref cur, ref searchSpace))
632632
{
@@ -715,7 +715,7 @@ private static int LastIndexOfAnyVectorized<TUseFastContains>(ref char searchSpa
715715
}
716716
}
717717

718-
if (!Unsafe.IsAddressGreaterThan(ref cur, ref lastStartVectorAvx2))
718+
if (Unsafe.IsAddressLessThanOrEqualTo(ref cur, ref lastStartVectorAvx2))
719719
{
720720
if (Unsafe.AreSame(ref cur, ref searchSpace))
721721
{
@@ -757,7 +757,7 @@ private static int LastIndexOfAnyVectorized<TUseFastContains>(ref char searchSpa
757757
}
758758
}
759759

760-
if (!Unsafe.IsAddressGreaterThan(ref cur, ref lastStartVector))
760+
if (Unsafe.IsAddressLessThanOrEqualTo(ref cur, ref lastStartVector))
761761
{
762762
if (Unsafe.AreSame(ref cur, ref searchSpace))
763763
{

src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/RabinKarp.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ private readonly int IndexOfAnyCore<TCaseSensitivity>(ReadOnlySpan<char> span)
142142
}
143143
}
144144

145-
if (!Unsafe.IsAddressLessThan(ref current, ref end))
145+
if (Unsafe.IsAddressGreaterThanOrEqualTo(ref current, ref end))
146146
{
147147
break;
148148
}

0 commit comments

Comments
 (0)