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

Commit baec140

Browse files
committed
Align buffers so String.wcslen can use it
1 parent 07a282a commit baec140

File tree

2 files changed

+30
-98
lines changed

2 files changed

+30
-98
lines changed

src/System.Private.CoreLib/shared/System/SpanHelpers.Char.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ public static bool Contains(ref char searchSpace, char value, int length)
328328
}
329329

330330
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
331-
public static int IndexOf(ref char searchSpace, char value, int length)
331+
public unsafe static int IndexOf(ref char searchSpace, char value, int length)
332332
{
333333
Debug.Assert(length >= 0);
334334

@@ -385,6 +385,29 @@ public static int IndexOf(ref char searchSpace, char value, int length)
385385
{
386386
if (offset < length)
387387
{
388+
if ((((nint)Unsafe.AsPointer(ref searchSpace) + (nint)offset) & (nint)(Vector256<ushort>.Count - 1)) != 0)
389+
{
390+
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
391+
// with no upper bound e.g. String.wcslen.
392+
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
393+
// This ensures we do not fault across memory pages while searching for an end of string.
394+
Vector128<ushort> values = Vector128.Create(value);
395+
Vector128<ushort> search = LoadVector128(ref searchSpace, offset);
396+
397+
// Same method as below
398+
int matches = Sse2.MoveMask(Sse2.CompareEqual(values, search).AsByte());
399+
if (matches == 0)
400+
{
401+
// Zero flags set so no matches
402+
offset += Vector128<ushort>.Count;
403+
}
404+
else
405+
{
406+
// Find bitflag offset of first match and add to current offset
407+
return (int)(offset + BitOps.TrailingZeroCount(matches));
408+
}
409+
}
410+
388411
lengthToExamine = GetCharVector256SpanLength(offset, length);
389412
if (lengthToExamine > offset)
390413
{

src/System.Private.CoreLib/shared/System/String.cs

Lines changed: 6 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -561,110 +561,19 @@ public StringRuneEnumerator EnumerateRunes()
561561
return new StringRuneEnumerator(this);
562562
}
563563

564+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
564565
internal static unsafe int wcslen(char* ptr)
565566
{
566-
char* end = ptr;
567-
568-
// First make sure our pointer is aligned on a word boundary
569-
int alignment = IntPtr.Size - 1;
570-
571-
// If ptr is at an odd address (e.g. 0x5), this loop will simply iterate all the way
572-
while (((uint)end & (uint)alignment) != 0)
573-
{
574-
if (*end == 0) goto FoundZero;
575-
end++;
576-
}
577-
578-
#if !BIT64
579-
// The following code is (somewhat surprisingly!) significantly faster than a naive loop,
580-
// at least on x86 and the current jit.
581-
582-
// The loop condition below works because if "end[0] & end[1]" is non-zero, that means
583-
// neither operand can have been zero. If is zero, we have to look at the operands individually,
584-
// but we hope this going to fairly rare.
585-
586-
// In general, it would be incorrect to access end[1] if we haven't made sure
587-
// end[0] is non-zero. However, we know the ptr has been aligned by the loop above
588-
// so end[0] and end[1] must be in the same word (and therefore page), so they're either both accessible, or both not.
589-
590-
while ((end[0] & end[1]) != 0 || (end[0] != 0 && end[1] != 0))
591-
{
592-
end += 2;
593-
}
594-
595-
Debug.Assert(end[0] == 0 || end[1] == 0);
596-
if (end[0] != 0) end++;
597-
#else // !BIT64
598-
// Based on https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
599-
600-
// 64-bit implementation: process 1 ulong (word) at a time
601-
602-
// What we do here is add 0x7fff from each of the
603-
// 4 individual chars within the ulong, using MagicMask.
604-
// If the char > 0 and < 0x8001, it will have its high bit set.
605-
// We then OR with MagicMask, to set all the other bits.
606-
// This will result in all bits set (ulong.MaxValue) for any
607-
// char that fits the above criteria, and something else otherwise.
608-
609-
// Note that for any char > 0x8000, this will be a false
610-
// positive and we will fallback to the slow path and
611-
// check each char individually. This is OK though, since
612-
// we optimize for the common case (ASCII chars, which are < 0x80).
613-
614-
// NOTE: We can access a ulong a time since the ptr is aligned,
615-
// and therefore we're only accessing the same word/page. (See notes
616-
// for the 32-bit version above.)
617-
618-
const ulong MagicMask = 0x7fff7fff7fff7fff;
619-
620-
while (true)
567+
// IndexOf processes memory in aligned chunks, and thus it won't crash even if it accesses memory beyond the null terminator.
568+
int length = SpanHelpers.IndexOf(ref *ptr, '\0', int.MaxValue);
569+
if (length < 0)
621570
{
622-
ulong word = *(ulong*)end;
623-
word += MagicMask; // cause high bit to be set if not zero, and <= 0x8000
624-
word |= MagicMask; // set everything besides the high bits
625-
626-
if (word == ulong.MaxValue) // 0xffff...
627-
{
628-
// all of the chars have their bits set (and therefore none can be 0)
629-
end += 4;
630-
continue;
631-
}
632-
633-
// at least one of them didn't have their high bit set!
634-
// go through each char and check for 0.
635-
636-
if (end[0] == 0) goto EndAt0;
637-
if (end[1] == 0) goto EndAt1;
638-
if (end[2] == 0) goto EndAt2;
639-
if (end[3] == 0) goto EndAt3;
640-
641-
// if we reached here, it was a false positive-- just continue
642-
end += 4;
571+
ThrowMustBeNullTerminatedString();
643572
}
644573

645-
EndAt3: end++;
646-
EndAt2: end++;
647-
EndAt1: end++;
648-
EndAt0:
649-
#endif // !BIT64
650-
651-
FoundZero:
652-
Debug.Assert(*end == 0);
653-
654-
int count = (int)(end - ptr);
655-
656-
#if BIT64
657-
// Check for overflow
658-
if (ptr + count != end)
659-
throw new ArgumentException(SR.Arg_MustBeNullTerminatedString);
660-
#else
661-
Debug.Assert(ptr + count == end);
662-
#endif
663-
664-
return count;
574+
return length;
665575
}
666576

667-
668577
[MethodImpl(MethodImplOptions.AggressiveInlining)]
669578
internal static unsafe int strlen(byte* ptr)
670579
{

0 commit comments

Comments
 (0)