SpanHelpers.Char.cs 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Diagnostics;
  5. using System.Numerics;
  6. using System.Runtime.CompilerServices;
  7. using System.Runtime.Intrinsics;
  8. using System.Runtime.Intrinsics.X86;
  9. using Internal.Runtime.CompilerServices;
  10. #if BIT64
  11. using nuint = System.UInt64;
  12. using nint = System.Int64;
  13. #else
  14. using nuint = System.UInt32;
  15. using nint = System.Int32;
  16. #endif
  17. namespace System
  18. {
  19. internal static partial class SpanHelpers // .Char
  20. {
  21. public static int IndexOf(ref char searchSpace, int searchSpaceLength, ref char value, int valueLength)
  22. {
  23. Debug.Assert(searchSpaceLength >= 0);
  24. Debug.Assert(valueLength >= 0);
  25. if (valueLength == 0)
  26. return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
  27. char valueHead = value;
  28. ref char valueTail = ref Unsafe.Add(ref value, 1);
  29. int valueTailLength = valueLength - 1;
  30. int remainingSearchSpaceLength = searchSpaceLength - valueTailLength;
  31. int index = 0;
  32. while (remainingSearchSpaceLength > 0)
  33. {
  34. // Do a quick search for the first element of "value".
  35. int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, index), valueHead, remainingSearchSpaceLength);
  36. if (relativeIndex == -1)
  37. break;
  38. remainingSearchSpaceLength -= relativeIndex;
  39. index += relativeIndex;
  40. if (remainingSearchSpaceLength <= 0)
  41. break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
  42. // Found the first element of "value". See if the tail matches.
  43. if (SequenceEqual(
  44. ref Unsafe.As<char, byte>(ref Unsafe.Add(ref searchSpace, index + 1)),
  45. ref Unsafe.As<char, byte>(ref valueTail),
  46. (nuint)valueTailLength * 2))
  47. {
  48. return index; // The tail matched. Return a successful find.
  49. }
  50. remainingSearchSpaceLength--;
  51. index++;
  52. }
  53. return -1;
  54. }
  55. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  56. public static unsafe int SequenceCompareTo(ref char first, int firstLength, ref char second, int secondLength)
  57. {
  58. Debug.Assert(firstLength >= 0);
  59. Debug.Assert(secondLength >= 0);
  60. int lengthDelta = firstLength - secondLength;
  61. if (Unsafe.AreSame(ref first, ref second))
  62. goto Equal;
  63. IntPtr minLength = (IntPtr)((firstLength < secondLength) ? firstLength : secondLength);
  64. IntPtr i = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
  65. if ((byte*)minLength >= (byte*)(sizeof(UIntPtr) / sizeof(char)))
  66. {
  67. if (Vector.IsHardwareAccelerated && (byte*)minLength >= (byte*)Vector<ushort>.Count)
  68. {
  69. IntPtr nLength = minLength - Vector<ushort>.Count;
  70. do
  71. {
  72. if (Unsafe.ReadUnaligned<Vector<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
  73. Unsafe.ReadUnaligned<Vector<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
  74. {
  75. break;
  76. }
  77. i += Vector<ushort>.Count;
  78. }
  79. while ((byte*)nLength >= (byte*)i);
  80. }
  81. while ((byte*)minLength >= (byte*)(i + sizeof(UIntPtr) / sizeof(char)))
  82. {
  83. if (Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
  84. Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
  85. {
  86. break;
  87. }
  88. i += sizeof(UIntPtr) / sizeof(char);
  89. }
  90. }
  91. if (sizeof(UIntPtr) > sizeof(int) && (byte*)minLength >= (byte*)(i + sizeof(int) / sizeof(char)))
  92. {
  93. if (Unsafe.ReadUnaligned<int>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) ==
  94. Unsafe.ReadUnaligned<int>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
  95. {
  96. i += sizeof(int) / sizeof(char);
  97. }
  98. }
  99. while ((byte*)i < (byte*)minLength)
  100. {
  101. int result = Unsafe.Add(ref first, i).CompareTo(Unsafe.Add(ref second, i));
  102. if (result != 0)
  103. return result;
  104. i += 1;
  105. }
  106. Equal:
  107. return lengthDelta;
  108. }
  109. // Adapted from IndexOf(...)
  110. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  111. public static unsafe bool Contains(ref char searchSpace, char value, int length)
  112. {
  113. Debug.Assert(length >= 0);
  114. fixed (char* pChars = &searchSpace)
  115. {
  116. char* pCh = pChars;
  117. char* pEndCh = pCh + length;
  118. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  119. {
  120. // Figure out how many characters to read sequentially until we are vector aligned
  121. // This is equivalent to:
  122. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  123. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  124. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  125. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  126. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  127. }
  128. SequentialScan:
  129. while (length >= 4)
  130. {
  131. length -= 4;
  132. if (value == *pCh ||
  133. value == *(pCh + 1) ||
  134. value == *(pCh + 2) ||
  135. value == *(pCh + 3))
  136. {
  137. goto Found;
  138. }
  139. pCh += 4;
  140. }
  141. while (length > 0)
  142. {
  143. length -= 1;
  144. if (value == *pCh)
  145. goto Found;
  146. pCh += 1;
  147. }
  148. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  149. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  150. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  151. {
  152. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  153. // That will be how many times we iterate in the loop below.
  154. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  155. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  156. // Get comparison Vector
  157. Vector<ushort> vComparison = new Vector<ushort>(value);
  158. while (length > 0)
  159. {
  160. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  161. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  162. Vector<ushort> vMatches = Vector.Equals(vComparison, Unsafe.Read<Vector<ushort>>(pCh));
  163. if (Vector<ushort>.Zero.Equals(vMatches))
  164. {
  165. pCh += Vector<ushort>.Count;
  166. length -= Vector<ushort>.Count;
  167. continue;
  168. }
  169. goto Found;
  170. }
  171. if (pCh < pEndCh)
  172. {
  173. length = (int)(pEndCh - pCh);
  174. goto SequentialScan;
  175. }
  176. }
  177. return false;
  178. Found:
  179. return true;
  180. }
  181. }
  182. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  183. public static unsafe int IndexOf(ref char searchSpace, char value, int length)
  184. {
  185. Debug.Assert(length >= 0);
  186. nint offset = 0;
  187. nint lengthToExamine = length;
  188. if (((int)Unsafe.AsPointer(ref searchSpace) & 1) != 0)
  189. {
  190. // Input isn't char aligned, we won't be able to align it to a Vector
  191. }
  192. else if (Sse2.IsSupported)
  193. {
  194. // Avx2 branch also operates on Sse2 sizes, so check is combined.
  195. // Needs to be double length to allow us to align the data first.
  196. if (length >= Vector128<ushort>.Count * 2)
  197. {
  198. lengthToExamine = UnalignedCountVector128(ref searchSpace);
  199. }
  200. }
  201. else if (Vector.IsHardwareAccelerated)
  202. {
  203. // Needs to be double length to allow us to align the data first.
  204. if (length >= Vector<ushort>.Count * 2)
  205. {
  206. lengthToExamine = UnalignedCountVector(ref searchSpace);
  207. }
  208. }
  209. SequentialScan:
  210. // In the non-vector case lengthToExamine is the total length.
  211. // In the vector case lengthToExamine first aligns to Vector,
  212. // then in a second pass after the Vector lengths is the
  213. // remaining data that is shorter than a Vector length.
  214. while (lengthToExamine >= 4)
  215. {
  216. ref char current = ref Add(ref searchSpace, offset);
  217. if (value == current)
  218. goto Found;
  219. if (value == Add(ref current, 1))
  220. goto Found1;
  221. if (value == Add(ref current, 2))
  222. goto Found2;
  223. if (value == Add(ref current, 3))
  224. goto Found3;
  225. offset += 4;
  226. lengthToExamine -= 4;
  227. }
  228. while (lengthToExamine > 0)
  229. {
  230. if (value == Add(ref searchSpace, offset))
  231. goto Found;
  232. offset += 1;
  233. lengthToExamine -= 1;
  234. }
  235. // We get past SequentialScan only if IsHardwareAccelerated or intrinsic .IsSupported is true. However, we still have the redundant check to allow
  236. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  237. if (Avx2.IsSupported)
  238. {
  239. if (offset < length)
  240. {
  241. Debug.Assert(length - offset >= Vector128<ushort>.Count);
  242. if (((nint)Unsafe.AsPointer(ref Unsafe.Add(ref searchSpace, (IntPtr)offset)) & (nint)(Vector256<byte>.Count - 1)) != 0)
  243. {
  244. // Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
  245. // with no upper bound e.g. String.wcslen. Start with a check on Vector128 to align to Vector256,
  246. // before moving to processing Vector256.
  247. // If the input searchSpan has been fixed or pinned, this ensures we do not fault across memory pages
  248. // while searching for an end of string. Specifically that this assumes that the length is either correct
  249. // or that the data is pinned otherwise it may cause an AccessViolation from crossing a page boundary into an
  250. // unowned page. If the search is unbounded (e.g. null terminator in wcslen) and the search value is not found,
  251. // again this will likely cause an AccessViolation. However, correctly bounded searches will return -1 rather
  252. // than ever causing an AV.
  253. // If the searchSpan has not been fixed or pinned the GC can relocate it during the execution of this
  254. // method, so the alignment only acts as best endeavour. The GC cost is likely to dominate over
  255. // the misalignment that may occur after; to we default to giving the GC a free hand to relocate and
  256. // its up to the caller whether they are operating over fixed data.
  257. Vector128<ushort> values = Vector128.Create((ushort)value);
  258. Vector128<ushort> search = LoadVector128(ref searchSpace, offset);
  259. // Same method as below
  260. int matches = Sse2.MoveMask(Sse2.CompareEqual(values, search).AsByte());
  261. if (matches == 0)
  262. {
  263. // Zero flags set so no matches
  264. offset += Vector128<ushort>.Count;
  265. }
  266. else
  267. {
  268. // Find bitflag offset of first match and add to current offset
  269. return (int)(offset + (BitOperations.TrailingZeroCount(matches) / sizeof(char)));
  270. }
  271. }
  272. lengthToExamine = GetCharVector256SpanLength(offset, length);
  273. if (lengthToExamine > 0)
  274. {
  275. Vector256<ushort> values = Vector256.Create((ushort)value);
  276. do
  277. {
  278. Debug.Assert(lengthToExamine >= Vector256<ushort>.Count);
  279. Vector256<ushort> search = LoadVector256(ref searchSpace, offset);
  280. int matches = Avx2.MoveMask(Avx2.CompareEqual(values, search).AsByte());
  281. // Note that MoveMask has converted the equal vector elements into a set of bit flags,
  282. // So the bit position in 'matches' corresponds to the element offset.
  283. if (matches == 0)
  284. {
  285. // Zero flags set so no matches
  286. offset += Vector256<ushort>.Count;
  287. lengthToExamine -= Vector256<ushort>.Count;
  288. continue;
  289. }
  290. // Find bitflag offset of first match and add to current offset,
  291. // flags are in bytes so divide for chars
  292. return (int)(offset + (BitOperations.TrailingZeroCount(matches) / sizeof(char)));
  293. } while (lengthToExamine > 0);
  294. }
  295. lengthToExamine = GetCharVector128SpanLength(offset, length);
  296. if (lengthToExamine > 0)
  297. {
  298. Debug.Assert(lengthToExamine >= Vector128<ushort>.Count);
  299. Vector128<ushort> values = Vector128.Create((ushort)value);
  300. Vector128<ushort> search = LoadVector128(ref searchSpace, offset);
  301. // Same method as above
  302. int matches = Sse2.MoveMask(Sse2.CompareEqual(values, search).AsByte());
  303. if (matches == 0)
  304. {
  305. // Zero flags set so no matches
  306. offset += Vector128<ushort>.Count;
  307. // Don't need to change lengthToExamine here as we don't use its current value again.
  308. }
  309. else
  310. {
  311. // Find bitflag offset of first match and add to current offset,
  312. // flags are in bytes so divide for chars
  313. return (int)(offset + (BitOperations.TrailingZeroCount(matches) / sizeof(char)));
  314. }
  315. }
  316. if (offset < length)
  317. {
  318. lengthToExamine = length - offset;
  319. goto SequentialScan;
  320. }
  321. }
  322. }
  323. else if (Sse2.IsSupported)
  324. {
  325. if (offset < length)
  326. {
  327. Debug.Assert(length - offset >= Vector128<ushort>.Count);
  328. lengthToExamine = GetCharVector128SpanLength(offset, length);
  329. if (lengthToExamine > 0)
  330. {
  331. Vector128<ushort> values = Vector128.Create((ushort)value);
  332. do
  333. {
  334. Debug.Assert(lengthToExamine >= Vector128<ushort>.Count);
  335. Vector128<ushort> search = LoadVector128(ref searchSpace, offset);
  336. // Same method as above
  337. int matches = Sse2.MoveMask(Sse2.CompareEqual(values, search).AsByte());
  338. if (matches == 0)
  339. {
  340. // Zero flags set so no matches
  341. offset += Vector128<ushort>.Count;
  342. lengthToExamine -= Vector128<ushort>.Count;
  343. continue;
  344. }
  345. // Find bitflag offset of first match and add to current offset,
  346. // flags are in bytes so divide for chars
  347. return (int)(offset + (BitOperations.TrailingZeroCount(matches) / sizeof(char)));
  348. } while (lengthToExamine > 0);
  349. }
  350. if (offset < length)
  351. {
  352. lengthToExamine = length - offset;
  353. goto SequentialScan;
  354. }
  355. }
  356. }
  357. else if (Vector.IsHardwareAccelerated)
  358. {
  359. if (offset < length)
  360. {
  361. Debug.Assert(length - offset >= Vector<ushort>.Count);
  362. lengthToExamine = GetCharVectorSpanLength(offset, length);
  363. if (lengthToExamine > 0)
  364. {
  365. Vector<ushort> values = new Vector<ushort>((ushort)value);
  366. do
  367. {
  368. Debug.Assert(lengthToExamine >= Vector<ushort>.Count);
  369. var matches = Vector.Equals(values, LoadVector(ref searchSpace, offset));
  370. if (Vector<ushort>.Zero.Equals(matches))
  371. {
  372. offset += Vector<ushort>.Count;
  373. lengthToExamine -= Vector<ushort>.Count;
  374. continue;
  375. }
  376. // Find offset of first match
  377. return (int)(offset + LocateFirstFoundChar(matches));
  378. } while (lengthToExamine > 0);
  379. }
  380. if (offset < length)
  381. {
  382. lengthToExamine = length - offset;
  383. goto SequentialScan;
  384. }
  385. }
  386. }
  387. return -1;
  388. Found3:
  389. return (int)(offset + 3);
  390. Found2:
  391. return (int)(offset + 2);
  392. Found1:
  393. return (int)(offset + 1);
  394. Found:
  395. return (int)(offset);
  396. }
  397. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  398. public static unsafe int IndexOfAny(ref char searchSpace, char value0, char value1, int length)
  399. {
  400. Debug.Assert(length >= 0);
  401. fixed (char* pChars = &searchSpace)
  402. {
  403. char* pCh = pChars;
  404. char* pEndCh = pCh + length;
  405. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  406. {
  407. // Figure out how many characters to read sequentially until we are vector aligned
  408. // This is equivalent to:
  409. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  410. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  411. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  412. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  413. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  414. }
  415. SequentialScan:
  416. while (length >= 4)
  417. {
  418. length -= 4;
  419. if (pCh[0] == value0 || pCh[0] == value1)
  420. goto Found;
  421. if (pCh[1] == value0 || pCh[1] == value1)
  422. goto Found1;
  423. if (pCh[2] == value0 || pCh[2] == value1)
  424. goto Found2;
  425. if (pCh[3] == value0 || pCh[3] == value1)
  426. goto Found3;
  427. pCh += 4;
  428. }
  429. while (length > 0)
  430. {
  431. length--;
  432. if (pCh[0] == value0 || pCh[0] == value1)
  433. goto Found;
  434. pCh++;
  435. }
  436. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  437. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  438. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  439. {
  440. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  441. // That will be how many times we iterate in the loop below.
  442. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  443. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  444. // Get comparison Vector
  445. Vector<ushort> values0 = new Vector<ushort>(value0);
  446. Vector<ushort> values1 = new Vector<ushort>(value1);
  447. while (length > 0)
  448. {
  449. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  450. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  451. Vector<ushort> vData = Unsafe.Read<Vector<ushort>>(pCh);
  452. var vMatches = Vector.BitwiseOr(
  453. Vector.Equals(vData, values0),
  454. Vector.Equals(vData, values1));
  455. if (Vector<ushort>.Zero.Equals(vMatches))
  456. {
  457. pCh += Vector<ushort>.Count;
  458. length -= Vector<ushort>.Count;
  459. continue;
  460. }
  461. // Find offset of first match
  462. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  463. }
  464. if (pCh < pEndCh)
  465. {
  466. length = (int)(pEndCh - pCh);
  467. goto SequentialScan;
  468. }
  469. }
  470. return -1;
  471. Found3:
  472. pCh++;
  473. Found2:
  474. pCh++;
  475. Found1:
  476. pCh++;
  477. Found:
  478. return (int)(pCh - pChars);
  479. }
  480. }
  481. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  482. public static unsafe int IndexOfAny(ref char searchSpace, char value0, char value1, char value2, int length)
  483. {
  484. Debug.Assert(length >= 0);
  485. fixed (char* pChars = &searchSpace)
  486. {
  487. char* pCh = pChars;
  488. char* pEndCh = pCh + length;
  489. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  490. {
  491. // Figure out how many characters to read sequentially until we are vector aligned
  492. // This is equivalent to:
  493. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  494. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  495. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  496. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  497. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  498. }
  499. SequentialScan:
  500. while (length >= 4)
  501. {
  502. length -= 4;
  503. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2)
  504. goto Found;
  505. if (pCh[1] == value0 || pCh[1] == value1 || pCh[1] == value2)
  506. goto Found1;
  507. if (pCh[2] == value0 || pCh[2] == value1 || pCh[2] == value2)
  508. goto Found2;
  509. if (pCh[3] == value0 || pCh[3] == value1 || pCh[3] == value2)
  510. goto Found3;
  511. pCh += 4;
  512. }
  513. while (length > 0)
  514. {
  515. length--;
  516. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2)
  517. goto Found;
  518. pCh++;
  519. }
  520. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  521. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  522. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  523. {
  524. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  525. // That will be how many times we iterate in the loop below.
  526. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  527. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  528. // Get comparison Vector
  529. Vector<ushort> values0 = new Vector<ushort>(value0);
  530. Vector<ushort> values1 = new Vector<ushort>(value1);
  531. Vector<ushort> values2 = new Vector<ushort>(value2);
  532. while (length > 0)
  533. {
  534. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  535. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  536. Vector<ushort> vData = Unsafe.Read<Vector<ushort>>(pCh);
  537. var vMatches = Vector.BitwiseOr(
  538. Vector.BitwiseOr(
  539. Vector.Equals(vData, values0),
  540. Vector.Equals(vData, values1)),
  541. Vector.Equals(vData, values2));
  542. if (Vector<ushort>.Zero.Equals(vMatches))
  543. {
  544. pCh += Vector<ushort>.Count;
  545. length -= Vector<ushort>.Count;
  546. continue;
  547. }
  548. // Find offset of first match
  549. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  550. }
  551. if (pCh < pEndCh)
  552. {
  553. length = (int)(pEndCh - pCh);
  554. goto SequentialScan;
  555. }
  556. }
  557. return -1;
  558. Found3:
  559. pCh++;
  560. Found2:
  561. pCh++;
  562. Found1:
  563. pCh++;
  564. Found:
  565. return (int)(pCh - pChars);
  566. }
  567. }
  568. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  569. public static unsafe int IndexOfAny(ref char searchSpace, char value0, char value1, char value2, char value3, int length)
  570. {
  571. Debug.Assert(length >= 0);
  572. fixed (char* pChars = &searchSpace)
  573. {
  574. char* pCh = pChars;
  575. char* pEndCh = pCh + length;
  576. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  577. {
  578. // Figure out how many characters to read sequentially until we are vector aligned
  579. // This is equivalent to:
  580. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  581. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  582. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  583. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  584. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  585. }
  586. SequentialScan:
  587. while (length >= 4)
  588. {
  589. length -= 4;
  590. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2 || pCh[0] == value3)
  591. goto Found;
  592. if (pCh[1] == value0 || pCh[1] == value1 || pCh[1] == value2 || pCh[1] == value3)
  593. goto Found1;
  594. if (pCh[2] == value0 || pCh[2] == value1 || pCh[2] == value2 || pCh[2] == value3)
  595. goto Found2;
  596. if (pCh[3] == value0 || pCh[3] == value1 || pCh[3] == value2 || pCh[3] == value3)
  597. goto Found3;
  598. pCh += 4;
  599. }
  600. while (length > 0)
  601. {
  602. length--;
  603. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2 || pCh[0] == value3)
  604. goto Found;
  605. pCh++;
  606. }
  607. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  608. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  609. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  610. {
  611. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  612. // That will be how many times we iterate in the loop below.
  613. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  614. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  615. // Get comparison Vector
  616. Vector<ushort> values0 = new Vector<ushort>(value0);
  617. Vector<ushort> values1 = new Vector<ushort>(value1);
  618. Vector<ushort> values2 = new Vector<ushort>(value2);
  619. Vector<ushort> values3 = new Vector<ushort>(value3);
  620. while (length > 0)
  621. {
  622. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  623. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  624. Vector<ushort> vData = Unsafe.Read<Vector<ushort>>(pCh);
  625. var vMatches = Vector.BitwiseOr(
  626. Vector.BitwiseOr(
  627. Vector.BitwiseOr(Vector.Equals(vData, values0), Vector.Equals(vData, values1)),
  628. Vector.Equals(vData, values2)),
  629. Vector.Equals(vData, values3));
  630. if (Vector<ushort>.Zero.Equals(vMatches))
  631. {
  632. pCh += Vector<ushort>.Count;
  633. length -= Vector<ushort>.Count;
  634. continue;
  635. }
  636. // Find offset of first match
  637. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  638. }
  639. if (pCh < pEndCh)
  640. {
  641. length = (int)(pEndCh - pCh);
  642. goto SequentialScan;
  643. }
  644. }
  645. return -1;
  646. Found3:
  647. pCh++;
  648. Found2:
  649. pCh++;
  650. Found1:
  651. pCh++;
  652. Found:
  653. return (int)(pCh - pChars);
  654. }
  655. }
  656. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  657. public static unsafe int IndexOfAny(ref char searchSpace, char value0, char value1, char value2, char value3, char value4, int length)
  658. {
  659. Debug.Assert(length >= 0);
  660. fixed (char* pChars = &searchSpace)
  661. {
  662. char* pCh = pChars;
  663. char* pEndCh = pCh + length;
  664. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  665. {
  666. // Figure out how many characters to read sequentially until we are vector aligned
  667. // This is equivalent to:
  668. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  669. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  670. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  671. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  672. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  673. }
  674. SequentialScan:
  675. while (length >= 4)
  676. {
  677. length -= 4;
  678. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2 || pCh[0] == value3 || pCh[0] == value4)
  679. goto Found;
  680. if (pCh[1] == value0 || pCh[1] == value1 || pCh[1] == value2 || pCh[1] == value3 || pCh[1] == value4)
  681. goto Found1;
  682. if (pCh[2] == value0 || pCh[2] == value1 || pCh[2] == value2 || pCh[2] == value3 || pCh[2] == value4)
  683. goto Found2;
  684. if (pCh[3] == value0 || pCh[3] == value1 || pCh[3] == value2 || pCh[3] == value3 || pCh[3] == value4)
  685. goto Found3;
  686. pCh += 4;
  687. }
  688. while (length > 0)
  689. {
  690. length--;
  691. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2 || pCh[0] == value3 || pCh[0] == value4)
  692. goto Found;
  693. pCh++;
  694. }
  695. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  696. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  697. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  698. {
  699. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  700. // That will be how many times we iterate in the loop below.
  701. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  702. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  703. // Get comparison Vector
  704. Vector<ushort> values0 = new Vector<ushort>(value0);
  705. Vector<ushort> values1 = new Vector<ushort>(value1);
  706. Vector<ushort> values2 = new Vector<ushort>(value2);
  707. Vector<ushort> values3 = new Vector<ushort>(value3);
  708. Vector<ushort> values4 = new Vector<ushort>(value4);
  709. while (length > 0)
  710. {
  711. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  712. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  713. Vector<ushort> vData = Unsafe.Read<Vector<ushort>>(pCh);
  714. var vMatches = Vector.BitwiseOr(
  715. Vector.BitwiseOr(
  716. Vector.BitwiseOr(
  717. Vector.BitwiseOr(Vector.Equals(vData, values0), Vector.Equals(vData, values1)),
  718. Vector.Equals(vData, values2)),
  719. Vector.Equals(vData, values3)),
  720. Vector.Equals(vData, values4));
  721. if (Vector<ushort>.Zero.Equals(vMatches))
  722. {
  723. pCh += Vector<ushort>.Count;
  724. length -= Vector<ushort>.Count;
  725. continue;
  726. }
  727. // Find offset of first match
  728. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  729. }
  730. if (pCh < pEndCh)
  731. {
  732. length = (int)(pEndCh - pCh);
  733. goto SequentialScan;
  734. }
  735. }
  736. return -1;
  737. Found3:
  738. pCh++;
  739. Found2:
  740. pCh++;
  741. Found1:
  742. pCh++;
  743. Found:
  744. return (int)(pCh - pChars);
  745. }
  746. }
  747. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  748. public static unsafe int LastIndexOf(ref char searchSpace, char value, int length)
  749. {
  750. Debug.Assert(length >= 0);
  751. fixed (char* pChars = &searchSpace)
  752. {
  753. char* pCh = pChars + length;
  754. char* pEndCh = pChars;
  755. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  756. {
  757. // Figure out how many characters to read sequentially from the end until we are vector aligned
  758. // This is equivalent to: length = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  759. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  760. length = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  761. }
  762. SequentialScan:
  763. while (length >= 4)
  764. {
  765. length -= 4;
  766. pCh -= 4;
  767. if (*(pCh + 3) == value)
  768. goto Found3;
  769. if (*(pCh + 2) == value)
  770. goto Found2;
  771. if (*(pCh + 1) == value)
  772. goto Found1;
  773. if (*pCh == value)
  774. goto Found;
  775. }
  776. while (length > 0)
  777. {
  778. length -= 1;
  779. pCh -= 1;
  780. if (*pCh == value)
  781. goto Found;
  782. }
  783. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  784. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  785. if (Vector.IsHardwareAccelerated && pCh > pEndCh)
  786. {
  787. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  788. // That will be how many times we iterate in the loop below.
  789. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pCh - pEndCh) / Vector<ushort>.Count)
  790. length = (int)((pCh - pEndCh) & ~(Vector<ushort>.Count - 1));
  791. // Get comparison Vector
  792. Vector<ushort> vComparison = new Vector<ushort>(value);
  793. while (length > 0)
  794. {
  795. char* pStart = pCh - Vector<ushort>.Count;
  796. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh (and hence pSart) is always vector aligned
  797. Debug.Assert(((int)pStart & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  798. Vector<ushort> vMatches = Vector.Equals(vComparison, Unsafe.Read<Vector<ushort>>(pStart));
  799. if (Vector<ushort>.Zero.Equals(vMatches))
  800. {
  801. pCh -= Vector<ushort>.Count;
  802. length -= Vector<ushort>.Count;
  803. continue;
  804. }
  805. // Find offset of last match
  806. return (int)(pStart - pEndCh) + LocateLastFoundChar(vMatches);
  807. }
  808. if (pCh > pEndCh)
  809. {
  810. length = (int)(pCh - pEndCh);
  811. goto SequentialScan;
  812. }
  813. }
  814. return -1;
  815. Found:
  816. return (int)(pCh - pEndCh);
  817. Found1:
  818. return (int)(pCh - pEndCh) + 1;
  819. Found2:
  820. return (int)(pCh - pEndCh) + 2;
  821. Found3:
  822. return (int)(pCh - pEndCh) + 3;
  823. }
  824. }
  825. // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
  826. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  827. private static int LocateFirstFoundChar(Vector<ushort> match)
  828. {
  829. var vector64 = Vector.AsVectorUInt64(match);
  830. ulong candidate = 0;
  831. int i = 0;
  832. // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
  833. for (; i < Vector<ulong>.Count; i++)
  834. {
  835. candidate = vector64[i];
  836. if (candidate != 0)
  837. {
  838. break;
  839. }
  840. }
  841. // Single LEA instruction with jitted const (using function result)
  842. return i * 4 + LocateFirstFoundChar(candidate);
  843. }
  844. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  845. private static int LocateFirstFoundChar(ulong match)
  846. {
  847. // TODO: Arm variants
  848. if (Bmi1.X64.IsSupported)
  849. {
  850. return (int)(Bmi1.X64.TrailingZeroCount(match) >> 4);
  851. }
  852. else
  853. {
  854. unchecked
  855. {
  856. // Flag least significant power of two bit
  857. var powerOfTwoFlag = match ^ (match - 1);
  858. // Shift all powers of two into the high byte and extract
  859. return (int)((powerOfTwoFlag * XorPowerOfTwoToHighChar) >> 49);
  860. }
  861. }
  862. }
  863. private const ulong XorPowerOfTwoToHighChar = (0x03ul |
  864. 0x02ul << 16 |
  865. 0x01ul << 32) + 1;
  866. // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
  867. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  868. private static int LocateLastFoundChar(Vector<ushort> match)
  869. {
  870. var vector64 = Vector.AsVectorUInt64(match);
  871. ulong candidate = 0;
  872. int i = Vector<ulong>.Count - 1;
  873. // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
  874. for (; i >= 0; i--)
  875. {
  876. candidate = vector64[i];
  877. if (candidate != 0)
  878. {
  879. break;
  880. }
  881. }
  882. // Single LEA instruction with jitted const (using function result)
  883. return i * 4 + LocateLastFoundChar(candidate);
  884. }
  885. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  886. private static int LocateLastFoundChar(ulong match)
  887. {
  888. return 3 - (BitOperations.LeadingZeroCount(match) >> 4);
  889. }
  890. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  891. public static ref char Add(ref char source, nint elementOffset)
  892. => ref Unsafe.Add(ref source, (IntPtr)elementOffset);
  893. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  894. private static unsafe Vector<ushort> LoadVector(ref char start, nint offset)
  895. => Unsafe.ReadUnaligned<Vector<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref start, (IntPtr)offset)));
  896. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  897. private static unsafe Vector128<ushort> LoadVector128(ref char start, nint offset)
  898. => Unsafe.ReadUnaligned<Vector128<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref start, (IntPtr)offset)));
  899. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  900. private static unsafe Vector256<ushort> LoadVector256(ref char start, nint offset)
  901. => Unsafe.ReadUnaligned<Vector256<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref start, (IntPtr)offset)));
  902. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  903. private static unsafe UIntPtr LoadUIntPtr(ref char start, nint offset)
  904. => Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref start, (IntPtr)offset)));
  905. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  906. private static unsafe nint GetCharVectorSpanLength(nint offset, nint length)
  907. => ((length - offset) & ~(Vector<ushort>.Count - 1));
  908. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  909. private static unsafe nint GetCharVector128SpanLength(nint offset, nint length)
  910. => ((length - offset) & ~(Vector128<ushort>.Count - 1));
  911. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  912. private static nint GetCharVector256SpanLength(nint offset, nint length)
  913. => ((length - offset) & ~(Vector256<ushort>.Count - 1));
  914. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  915. private static unsafe nint UnalignedCountVector(ref char searchSpace)
  916. {
  917. const int ElementsPerByte = sizeof(ushort) / sizeof(byte);
  918. // Figure out how many characters to read sequentially until we are vector aligned
  919. // This is equivalent to:
  920. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / ElementsPerByte
  921. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  922. // This alignment is only valid if the GC does not relocate; so we use ReadUnaligned to get the data.
  923. // If a GC does occur and alignment is lost, the GC cost will outweigh any gains from alignment so it
  924. // isn't too important to pin to maintain the alignment.
  925. return (nint)(uint)(-(int)Unsafe.AsPointer(ref searchSpace) / ElementsPerByte ) & (Vector<ushort>.Count - 1);
  926. }
  927. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  928. private static unsafe nint UnalignedCountVector128(ref char searchSpace)
  929. {
  930. const int ElementsPerByte = sizeof(ushort) / sizeof(byte);
  931. // This alignment is only valid if the GC does not relocate; so we use ReadUnaligned to get the data.
  932. // If a GC does occur and alignment is lost, the GC cost will outweigh any gains from alignment so it
  933. // isn't too important to pin to maintain the alignment.
  934. return (nint)(uint)(-(int)Unsafe.AsPointer(ref searchSpace) / ElementsPerByte ) & (Vector128<ushort>.Count - 1);
  935. }
  936. }
  937. }