From 39c54e1bd45b8aabb9c1c437c22fe347c064ee79 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 25 Apr 2026 19:35:03 +0200 Subject: [PATCH 1/3] Use safe Span.Slice loop pattern in Enumerable.MinMaxInteger Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System.Linq/src/System/Linq/MaxMin.cs | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs index 7adf15a83dcd3e..0bb6d81dce5e04 100644 --- a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs +++ b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; namespace System.Linq @@ -50,18 +48,15 @@ private static T MinMaxInteger(this IEnumerable source) } else if (!Vector256.IsHardwareAccelerated || !Vector256.IsSupported || span.Length < Vector256.Count) { - ref T current = ref MemoryMarshal.GetReference(span); - ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector128.Count); + Vector128 best = Vector128.Create(span); + span = span.Slice(Vector128.Count); - Vector128 best = Vector128.LoadUnsafe(ref current); - current = ref Unsafe.Add(ref current, Vector128.Count); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) + while (span.Length >= Vector128.Count * 2) { - best = TMinMax.Compare(best, Vector128.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, Vector128.Count); + best = TMinMax.Compare(best, Vector128.Create(span)); + span = span.Slice(Vector128.Count); } - best = TMinMax.Compare(best, Vector128.LoadUnsafe(ref lastVectorStart)); + best = TMinMax.Compare(best, Vector128.Create(span.Slice(span.Length - Vector128.Count))); value = best[0]; for (int i = 1; i < Vector128.Count; i++) @@ -74,18 +69,15 @@ private static T MinMaxInteger(this IEnumerable source) } else if (!Vector512.IsHardwareAccelerated || !Vector512.IsSupported || span.Length < Vector512.Count) { - ref T current = ref MemoryMarshal.GetReference(span); - ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector256.Count); - - Vector256 best = Vector256.LoadUnsafe(ref current); - current = ref Unsafe.Add(ref current, Vector256.Count); + Vector256 best = Vector256.Create(span); + span = span.Slice(Vector256.Count); - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) + while (span.Length >= Vector256.Count * 2) { - best = TMinMax.Compare(best, Vector256.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, Vector256.Count); + best = TMinMax.Compare(best, Vector256.Create(span)); + span = span.Slice(Vector256.Count); } - best = TMinMax.Compare(best, Vector256.LoadUnsafe(ref lastVectorStart)); + best = TMinMax.Compare(best, Vector256.Create(span.Slice(span.Length - Vector256.Count))); value = best[0]; for (int i = 1; i < Vector256.Count; i++) @@ -98,18 +90,15 @@ private static T MinMaxInteger(this IEnumerable source) } else { - ref T current = ref MemoryMarshal.GetReference(span); - ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector512.Count); - - Vector512 best = Vector512.LoadUnsafe(ref current); - current = ref Unsafe.Add(ref current, Vector512.Count); + Vector512 best = Vector512.Create(span); + span = span.Slice(Vector512.Count); - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) + while (span.Length >= Vector512.Count * 2) { - best = TMinMax.Compare(best, Vector512.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, Vector512.Count); + best = TMinMax.Compare(best, Vector512.Create(span)); + span = span.Slice(Vector512.Count); } - best = TMinMax.Compare(best, Vector512.LoadUnsafe(ref lastVectorStart)); + best = TMinMax.Compare(best, Vector512.Create(span.Slice(span.Length - Vector512.Count))); value = best[0]; for (int i = 1; i < Vector512.Count; i++) From c1847b0ff55055845f1cf7de86133d3ef455bc5c Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 25 Apr 2026 19:57:50 +0200 Subject: [PATCH 2/3] Fix tail-load bounds bug for spans of exactly one vector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous loop pattern computed the trailing overlapping vector via `span.Slice(span.Length - Vector{N}.Count)` AFTER advancing the span by `Vector{N}.Count`. For inputs where `Vector{N}.Count <= original.Length < Vector{N}.Count * 2` (e.g. exactly one vector worth of elements), the post-slice span length is < Vector{N}.Count and the trailing slice index goes negative, throwing ArgumentOutOfRangeException. Capture the trailing overlapping vector slice BEFORE the first slice mutation, mirroring how the original `ref T lastVectorStart` capture worked. Also relax the loop condition from `>= * 2` to `>=` to match the convention used in #127381 / #127382 / #127386 — the one redundant final compare on exact-multiple lengths is harmless because min/max is idempotent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System.Linq/src/System/Linq/MaxMin.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs index 0bb6d81dce5e04..02645ec2509f66 100644 --- a/src/libraries/System.Linq/src/System/Linq/MaxMin.cs +++ b/src/libraries/System.Linq/src/System/Linq/MaxMin.cs @@ -48,15 +48,19 @@ private static T MinMaxInteger(this IEnumerable source) } else if (!Vector256.IsHardwareAccelerated || !Vector256.IsSupported || span.Length < Vector256.Count) { + // Capture the trailing overlapping vector before slicing, so it remains valid + // even when span.Length == Vector128.Count (in which case it equals the first vector). + ReadOnlySpan lastVec = span.Slice(span.Length - Vector128.Count); + Vector128 best = Vector128.Create(span); span = span.Slice(Vector128.Count); - while (span.Length >= Vector128.Count * 2) + while (span.Length >= Vector128.Count) { best = TMinMax.Compare(best, Vector128.Create(span)); span = span.Slice(Vector128.Count); } - best = TMinMax.Compare(best, Vector128.Create(span.Slice(span.Length - Vector128.Count))); + best = TMinMax.Compare(best, Vector128.Create(lastVec)); value = best[0]; for (int i = 1; i < Vector128.Count; i++) @@ -69,15 +73,17 @@ private static T MinMaxInteger(this IEnumerable source) } else if (!Vector512.IsHardwareAccelerated || !Vector512.IsSupported || span.Length < Vector512.Count) { + ReadOnlySpan lastVec = span.Slice(span.Length - Vector256.Count); + Vector256 best = Vector256.Create(span); span = span.Slice(Vector256.Count); - while (span.Length >= Vector256.Count * 2) + while (span.Length >= Vector256.Count) { best = TMinMax.Compare(best, Vector256.Create(span)); span = span.Slice(Vector256.Count); } - best = TMinMax.Compare(best, Vector256.Create(span.Slice(span.Length - Vector256.Count))); + best = TMinMax.Compare(best, Vector256.Create(lastVec)); value = best[0]; for (int i = 1; i < Vector256.Count; i++) @@ -90,15 +96,17 @@ private static T MinMaxInteger(this IEnumerable source) } else { + ReadOnlySpan lastVec = span.Slice(span.Length - Vector512.Count); + Vector512 best = Vector512.Create(span); span = span.Slice(Vector512.Count); - while (span.Length >= Vector512.Count * 2) + while (span.Length >= Vector512.Count) { best = TMinMax.Compare(best, Vector512.Create(span)); span = span.Slice(Vector512.Count); } - best = TMinMax.Compare(best, Vector512.Create(span.Slice(span.Length - Vector512.Count))); + best = TMinMax.Compare(best, Vector512.Create(lastVec)); value = best[0]; for (int i = 1; i < Vector512.Count; i++) From 1b56dc8ef0e8b9e7229bae307eff187c7ac7ecc5 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 26 Apr 2026 23:41:42 +0200 Subject: [PATCH 3/3] mark types as intrinsic --- .../src/System/Runtime/Intrinsics/Vector128.cs | 1 + .../src/System/Runtime/Intrinsics/Vector256.cs | 1 + .../src/System/Runtime/Intrinsics/Vector512.cs | 1 + .../src/System/Runtime/Intrinsics/Vector64.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs index 1cebc6f55a6b81..7fd489621bc4d4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs @@ -31,6 +31,7 @@ namespace System.Runtime.Intrinsics // the internal inlining limits of the JIT. /// Provides a collection of static methods for creating, manipulating, and otherwise operating on 128-bit vectors. + [Intrinsic] public static partial class Vector128 { internal const int Size = 16; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs index cd446c7646ac03..a37b61f96f3250 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs @@ -29,6 +29,7 @@ namespace System.Runtime.Intrinsics // the internal inlining limits of the JIT. /// Provides a collection of static methods for creating, manipulating, and otherwise operating on 256-bit vectors. + [Intrinsic] public static class Vector256 { internal const int Size = 32; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs index 6e71109c3ab9c5..82579167f18df6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs @@ -28,6 +28,7 @@ namespace System.Runtime.Intrinsics // the internal inlining limits of the JIT. /// Provides a collection of static methods for creating, manipulating, and otherwise operting on 512-bit vectors. + [Intrinsic] public static class Vector512 { internal const int Size = 64; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs index 8655d9778f0529..8383ef3d020154 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs @@ -11,6 +11,7 @@ namespace System.Runtime.Intrinsics { /// Provides a collection of static methods for creating, manipulating, and otherwise operating on 64-bit vectors. + [Intrinsic] public static class Vector64 { internal const int Size = 8;