Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,8 @@ public override void Write(ReadOnlySpan<byte> buffer)
}
}

public override unsafe void WriteByte(byte value) =>
Write(new ReadOnlySpan<byte>(&value, 1));
public override void WriteByte(byte value) =>
Write(new ReadOnlySpan<byte>(in value));

private int _closeTimeout = Socket.DefaultCloseTimeout; // -1 = respect linger options

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1691,50 +1691,43 @@ private static int ApplyMask(Span<byte> toMask, byte[] mask, int maskOffset, int
/// <param name="mask">The four-byte mask, stored as an Int32.</param>
/// <param name="maskIndex">The index into the mask.</param>
/// <returns>The next index into the mask to be used for future applications of the mask.</returns>
private static unsafe int ApplyMask(Span<byte> toMask, int mask, int maskIndex)
private static int ApplyMask(Span<byte> toMask, int mask, int maskIndex)
{
Debug.Assert(maskIndex < sizeof(int));

fixed (byte* toMaskBeg = &MemoryMarshal.GetReference(toMask))
if (toMask.Length >= sizeof(int))
{
byte* toMaskPtr = toMaskBeg;
byte* toMaskEnd = toMaskBeg + toMask.Length;
int rolledMask = BitConverter.IsLittleEndian ?
(int)BitOperations.RotateRight((uint)mask, maskIndex * 8) :
(int)BitOperations.RotateLeft((uint)mask, maskIndex * 8);

if (toMaskEnd - toMaskPtr >= sizeof(int))
// Process Vector<byte>.Count bytes at a time.
if (Vector.IsHardwareAccelerated && toMask.Length >= Vector<byte>.Count)
{
int rolledMask = BitConverter.IsLittleEndian ?
(int)BitOperations.RotateRight((uint)mask, maskIndex * 8) :
(int)BitOperations.RotateLeft((uint)mask, maskIndex * 8);

// Process Vector<byte>.Count bytes at a time.
if (Vector.IsHardwareAccelerated && (toMaskEnd - toMaskPtr) >= Vector<byte>.Count)
Vector<byte> maskVector = Vector.AsVectorByte(new Vector<int>(rolledMask));
do
{
Vector<byte> maskVector = Vector.AsVectorByte(new Vector<int>(rolledMask));
do
{
*(Vector<byte>*)toMaskPtr ^= maskVector;
toMaskPtr += Vector<byte>.Count;
}
while (toMaskEnd - toMaskPtr >= Vector<byte>.Count);
}

// Process 4 bytes at a time.
while (toMaskEnd - toMaskPtr >= sizeof(int))
{
*(int*)toMaskPtr ^= rolledMask;
toMaskPtr += sizeof(int);
(new Vector<byte>(toMask) ^ maskVector).CopyTo(toMask);
toMask = toMask.Slice(Vector<byte>.Count);
}
while (toMask.Length >= Vector<byte>.Count);
}

// Process 1 byte at a time.
byte* maskPtr = (byte*)&mask;
while (toMaskPtr != toMaskEnd)
// Process 4 bytes at a time.
while (toMask.Length >= sizeof(int))
{
*toMaskPtr++ ^= maskPtr[maskIndex];
maskIndex = (maskIndex + 1) & 3;
BitConverter.TryWriteBytes(toMask, BitConverter.ToInt32(toMask) ^ rolledMask);
toMask = toMask.Slice(sizeof(int));
}
}

// Process 1 byte at a time, using the mask byte at native memory offset maskIndex.
for (int i = 0; i < toMask.Length; i++)
{
toMask[i] ^= (byte)(mask >> ((BitConverter.IsLittleEndian ? maskIndex : 3 - maskIndex) * 8));
maskIndex = (maskIndex + 1) & 3;
}

return maskIndex;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static bool TryWriteBytes(Span<byte> destination, bool value)
if (destination.Length < sizeof(byte))
return false;

Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value ? (byte)1 : (byte)0);
destination[0] = value ? (byte)1 : (byte)0;
return true;
}

Expand Down Expand Up @@ -950,7 +950,7 @@ public static bool ToBoolean(ReadOnlySpan<byte> value)
{
if (value.Length < sizeof(byte))
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
return Unsafe.ReadUnaligned<byte>(ref MemoryMarshal.GetReference(value)) != 0;
return value[0] != 0;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,8 @@ public static void ReverseEndianness(ReadOnlySpan<Int128> source, Span<Int128> d
ThrowDestinationTooSmall();
}

if (Unsafe.AreSame(ref MemoryMarshal.GetReference(source), ref MemoryMarshal.GetReference(destination)) ||
!source.Overlaps(destination, out int elementOffset) ||
elementOffset < 0)
if (!source.Overlaps(destination, out int elementOffset) ||
elementOffset <= 0)
{
// Iterate from beginning to end
for (int i = 0; i < source.Length; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Buffers
{
Expand Down Expand Up @@ -53,49 +52,46 @@ private static bool Contains(uint[] bitmap, int value)

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal override int IndexOfAny(ReadOnlySpan<char> span) =>
IndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(ref MemoryMarshal.GetReference(span), span.Length);
IndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(span);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal override int IndexOfAnyExcept(ReadOnlySpan<char> span) =>
IndexOfAny<IndexOfAnyAsciiSearcher.Negate>(ref MemoryMarshal.GetReference(span), span.Length);
IndexOfAny<IndexOfAnyAsciiSearcher.Negate>(span);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal override int LastIndexOfAny(ReadOnlySpan<char> span) =>
LastIndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(ref MemoryMarshal.GetReference(span), span.Length);
LastIndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(span);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal override int LastIndexOfAnyExcept(ReadOnlySpan<char> span) =>
LastIndexOfAny<IndexOfAnyAsciiSearcher.Negate>(ref MemoryMarshal.GetReference(span), span.Length);
LastIndexOfAny<IndexOfAnyAsciiSearcher.Negate>(span);

private int IndexOfAny<TNegator>(ref char searchSpace, int searchSpaceLength)
private int IndexOfAny<TNegator>(ReadOnlySpan<char> span)
where TNegator : struct, IndexOfAnyAsciiSearcher.INegator
{
ref char searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength);
ref char cur = ref searchSpace;
uint[] bitmap = _bitmap;

while (!Unsafe.AreSame(ref cur, ref searchSpaceEnd))
for (int i = 0; i < span.Length; i++)
{
char c = cur;
char c = span[i];
if (TNegator.NegateIfNeeded(Contains(bitmap, c)))
{
return (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref cur) / sizeof(char));
return i;
}

cur = ref Unsafe.Add(ref cur, 1);
}

return -1;
}

private int LastIndexOfAny<TNegator>(ref char searchSpace, int searchSpaceLength)
private int LastIndexOfAny<TNegator>(ReadOnlySpan<char> span)
where TNegator : struct, IndexOfAnyAsciiSearcher.INegator
{
uint[] bitmap = _bitmap;
int searchSpaceLength = span.Length;

while (--searchSpaceLength >= 0)
{
char c = Unsafe.Add(ref searchSpace, searchSpaceLength);
char c = span[searchSpaceLength];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will add an extra bounds check in the loop
https://godbolt.org/z/n9a99Tjn1

@EgorBo EgorBo Sep 30, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm.. let me see if it's fixable in JIT. The loop looks trivial enough

@EgorBo EgorBo Oct 1, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MihaZupan it seems like it's more or less trivial to remove in jit, will do there 🙂 #135008

if (TNegator.NegateIfNeeded(Contains(bitmap, c)))
{
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,22 @@ public readonly struct AnyByteState(Vector128<byte> bitmap0, Vector128<byte> bit
internal static bool IsVectorizationSupported => Ssse3.IsSupported || AdvSimd.Arm64.IsSupported || PackedSimd.IsSupported;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void SetBitmapBit(byte* bitmap, int value)
private static void SetBitmapBit(ref InlineArray16<byte> bitmap, int value)
{
Debug.Assert((uint)value <= 127);

int highNibble = value >> 4;
int lowNibble = value & 0xF;
bitmap[(uint)lowNibble] |= (byte)(1 << highNibble);
bitmap[lowNibble] |= (byte)(1 << highNibble);
}

internal static unsafe void ComputeAnyByteState(ReadOnlySpan<byte> values, out AnyByteState state)
internal static void ComputeAnyByteState(ReadOnlySpan<byte> values, out AnyByteState state)
{
// The exact format of these bitmaps differs from the other ComputeBitmap overloads as it's meant for the full [0, 255] range algorithm.
// See http://0x80.pl/articles/simd-byte-lookup.html#universal-algorithm

Vector128<byte> bitmapSpace0 = default;
Vector128<byte> bitmapSpace1 = default;
byte* bitmapLocal0 = (byte*)&bitmapSpace0;
byte* bitmapLocal1 = (byte*)&bitmapSpace1;
InlineArray16<byte> bitmapSpace0 = default;
InlineArray16<byte> bitmapSpace1 = default;
BitVector256 lookupLocal = default;

foreach (byte b in values)
Expand All @@ -60,24 +58,23 @@ internal static unsafe void ComputeAnyByteState(ReadOnlySpan<byte> values, out A

if (b < 128)
{
SetBitmapBit(bitmapLocal0, b);
SetBitmapBit(ref bitmapSpace0, b);
}
else
{
SetBitmapBit(bitmapLocal1, b - 128);
SetBitmapBit(ref bitmapSpace1, b - 128);
}
}

state = new AnyByteState(bitmapSpace0, bitmapSpace1, lookupLocal);
state = new AnyByteState(Vector128.Create<byte>(bitmapSpace0), Vector128.Create<byte>(bitmapSpace1), lookupLocal);
}

internal static unsafe void ComputeAsciiState<T>(ReadOnlySpan<T> values, out AsciiState state)
internal static void ComputeAsciiState<T>(ReadOnlySpan<T> values, out AsciiState state)
where T : struct, IUnsignedNumber<T>
{
Debug.Assert(typeof(T) == typeof(byte) || typeof(T) == typeof(char));

Vector128<byte> bitmapSpace = default;
byte* bitmapLocal = (byte*)&bitmapSpace;
InlineArray16<byte> bitmapSpace = default;
BitVector256 lookupLocal = default;

foreach (T tValue in values)
Expand All @@ -90,10 +87,10 @@ internal static unsafe void ComputeAsciiState<T>(ReadOnlySpan<T> values, out Asc
}

lookupLocal.Set(value);
SetBitmapBit(bitmapLocal, value);
SetBitmapBit(ref bitmapSpace, value);
}

state = new AsciiState(bitmapSpace, lookupLocal);
state = new AsciiState(Vector128.Create<byte>(bitmapSpace), lookupLocal);
}

public static bool CanUseUniqueLowNibbleSearch<T>(ReadOnlySpan<T> values, int maxInclusive)
Expand Down Expand Up @@ -143,14 +140,14 @@ public static void ComputeUniqueLowNibbleState<T>(ReadOnlySpan<T> values, out As
{
Debug.Assert(typeof(T) == typeof(byte) || typeof(T) == typeof(char));

Vector128<byte> valuesByLowNibble = default;
InlineArray16<byte> valuesByLowNibble = default;
BitVector256 lookup = default;

foreach (T tValue in values)
{
byte value = byte.CreateTruncating(tValue);
lookup.Set(value);
valuesByLowNibble.SetElementUnsafe(value & 0xF, value);
valuesByLowNibble[value & 0xF] = value;
}

// Elements of 'valuesByLowNibble' where no value had that low nibble will be left uninitialized at 0.
Expand All @@ -161,12 +158,12 @@ public static void ComputeUniqueLowNibbleState<T>(ReadOnlySpan<T> values, out As
// To avoid that, we can replace the 0th element with any other byte that has a non-zero low nibble.
// The zero character will no longer match, and the new value we pick won't match either as
// it will be mapped to a different element in 'valuesByLowNibble' given its non-zero low nibble.
if (valuesByLowNibble.GetElement(0) == 0 && !lookup.Contains(0))
if (valuesByLowNibble[0] == 0 && !lookup.Contains(0))
{
valuesByLowNibble.SetElementUnsafe(0, (byte)1);
valuesByLowNibble[0] = 1;
}

state = new AsciiState(valuesByLowNibble, lookup);
state = new AsciiState(Vector128.Create<byte>(valuesByLowNibble), lookup);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand All @@ -182,7 +179,7 @@ private static unsafe bool TryComputeBitmap(ReadOnlySpan<char> values, byte* bit
return false;
}

SetBitmapBit(bitmapLocal, c);
bitmapLocal[c & 0xF] |= (byte)(1 << (c >> 4));
}

needleContainsZero = (bitmap[0] & 1) != 0;
Expand Down
Loading