SpanHelpers.Char.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  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.Runtime.CompilerServices;
  6. using System.Numerics;
  7. using System.Runtime.Intrinsics.X86;
  8. using Internal.Runtime.CompilerServices;
  9. #if BIT64
  10. using nuint = System.UInt64;
  11. #else
  12. using nuint = System.UInt32;
  13. #endif
  14. namespace System
  15. {
  16. internal static partial class SpanHelpers // .Char
  17. {
  18. public static int IndexOf(ref char searchSpace, int searchSpaceLength, ref char value, int valueLength)
  19. {
  20. Debug.Assert(searchSpaceLength >= 0);
  21. Debug.Assert(valueLength >= 0);
  22. if (valueLength == 0)
  23. return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
  24. char valueHead = value;
  25. ref char valueTail = ref Unsafe.Add(ref value, 1);
  26. int valueTailLength = valueLength - 1;
  27. int remainingSearchSpaceLength = searchSpaceLength - valueTailLength;
  28. int index = 0;
  29. while (remainingSearchSpaceLength > 0)
  30. {
  31. // Do a quick search for the first element of "value".
  32. int relativeIndex = IndexOf(ref Unsafe.Add(ref searchSpace, index), valueHead, remainingSearchSpaceLength);
  33. if (relativeIndex == -1)
  34. break;
  35. remainingSearchSpaceLength -= relativeIndex;
  36. index += relativeIndex;
  37. if (remainingSearchSpaceLength <= 0)
  38. break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.
  39. // Found the first element of "value". See if the tail matches.
  40. if (SequenceEqual(
  41. ref Unsafe.As<char, byte>(ref Unsafe.Add(ref searchSpace, index + 1)),
  42. ref Unsafe.As<char, byte>(ref valueTail),
  43. (nuint)valueTailLength * 2))
  44. {
  45. return index; // The tail matched. Return a successful find.
  46. }
  47. remainingSearchSpaceLength--;
  48. index++;
  49. }
  50. return -1;
  51. }
  52. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  53. public static unsafe int SequenceCompareTo(ref char first, int firstLength, ref char second, int secondLength)
  54. {
  55. Debug.Assert(firstLength >= 0);
  56. Debug.Assert(secondLength >= 0);
  57. int lengthDelta = firstLength - secondLength;
  58. if (Unsafe.AreSame(ref first, ref second))
  59. goto Equal;
  60. IntPtr minLength = (IntPtr)((firstLength < secondLength) ? firstLength : secondLength);
  61. IntPtr i = (IntPtr)0; // Use IntPtr for arithmetic to avoid unnecessary 64->32->64 truncations
  62. if ((byte*)minLength >= (byte*)(sizeof(UIntPtr) / sizeof(char)))
  63. {
  64. if (Vector.IsHardwareAccelerated && (byte*)minLength >= (byte*)Vector<ushort>.Count)
  65. {
  66. IntPtr nLength = minLength - Vector<ushort>.Count;
  67. do
  68. {
  69. if (Unsafe.ReadUnaligned<Vector<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
  70. Unsafe.ReadUnaligned<Vector<ushort>>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
  71. {
  72. break;
  73. }
  74. i += Vector<ushort>.Count;
  75. }
  76. while ((byte*)nLength >= (byte*)i);
  77. }
  78. while ((byte*)minLength >= (byte*)(i + sizeof(UIntPtr) / sizeof(char)))
  79. {
  80. if (Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
  81. Unsafe.ReadUnaligned<UIntPtr>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
  82. {
  83. break;
  84. }
  85. i += sizeof(UIntPtr) / sizeof(char);
  86. }
  87. }
  88. if (sizeof(UIntPtr) > sizeof(int) && (byte*)minLength >= (byte*)(i + sizeof(int) / sizeof(char)))
  89. {
  90. if (Unsafe.ReadUnaligned<int>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) ==
  91. Unsafe.ReadUnaligned<int>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
  92. {
  93. i += sizeof(int) / sizeof(char);
  94. }
  95. }
  96. while ((byte*)i < (byte*)minLength)
  97. {
  98. int result = Unsafe.Add(ref first, i).CompareTo(Unsafe.Add(ref second, i));
  99. if (result != 0)
  100. return result;
  101. i += 1;
  102. }
  103. Equal:
  104. return lengthDelta;
  105. }
  106. // Adapted from IndexOf(...)
  107. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  108. public static unsafe bool Contains(ref char searchSpace, char value, int length)
  109. {
  110. Debug.Assert(length >= 0);
  111. fixed (char* pChars = &searchSpace)
  112. {
  113. char* pCh = pChars;
  114. char* pEndCh = pCh + length;
  115. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  116. {
  117. // Figure out how many characters to read sequentially until we are vector aligned
  118. // This is equivalent to:
  119. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  120. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  121. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  122. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  123. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  124. }
  125. SequentialScan:
  126. while (length >= 4)
  127. {
  128. length -= 4;
  129. if (value == *pCh ||
  130. value == *(pCh + 1) ||
  131. value == *(pCh + 2) ||
  132. value == *(pCh + 3))
  133. {
  134. goto Found;
  135. }
  136. pCh += 4;
  137. }
  138. while (length > 0)
  139. {
  140. length -= 1;
  141. if (value == *pCh)
  142. goto Found;
  143. pCh += 1;
  144. }
  145. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  146. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  147. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  148. {
  149. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  150. // That will be how many times we iterate in the loop below.
  151. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  152. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  153. // Get comparison Vector
  154. Vector<ushort> vComparison = new Vector<ushort>(value);
  155. while (length > 0)
  156. {
  157. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  158. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  159. Vector<ushort> vMatches = Vector.Equals(vComparison, Unsafe.Read<Vector<ushort>>(pCh));
  160. if (Vector<ushort>.Zero.Equals(vMatches))
  161. {
  162. pCh += Vector<ushort>.Count;
  163. length -= Vector<ushort>.Count;
  164. continue;
  165. }
  166. goto Found;
  167. }
  168. if (pCh < pEndCh)
  169. {
  170. length = (int)(pEndCh - pCh);
  171. goto SequentialScan;
  172. }
  173. }
  174. return false;
  175. Found:
  176. return true;
  177. }
  178. }
  179. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  180. public static unsafe int IndexOf(ref char searchSpace, char value, int length)
  181. {
  182. Debug.Assert(length >= 0);
  183. fixed (char* pChars = &searchSpace)
  184. {
  185. char* pCh = pChars;
  186. char* pEndCh = pCh + length;
  187. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  188. {
  189. // Figure out how many characters to read sequentially until we are vector aligned
  190. // This is equivalent to:
  191. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  192. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  193. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  194. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  195. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  196. }
  197. SequentialScan:
  198. while (length >= 4)
  199. {
  200. length -= 4;
  201. if (pCh[0] == value)
  202. goto Found;
  203. if (pCh[1] == value)
  204. goto Found1;
  205. if (pCh[2] == value)
  206. goto Found2;
  207. if (pCh[3] == value)
  208. goto Found3;
  209. pCh += 4;
  210. }
  211. while (length > 0)
  212. {
  213. length--;
  214. if (pCh[0] == value)
  215. goto Found;
  216. pCh++;
  217. }
  218. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  219. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  220. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  221. {
  222. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  223. // That will be how many times we iterate in the loop below.
  224. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  225. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  226. // Get comparison Vector
  227. Vector<ushort> vComparison = new Vector<ushort>(value);
  228. while (length > 0)
  229. {
  230. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  231. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  232. Vector<ushort> vMatches = Vector.Equals(vComparison, Unsafe.Read<Vector<ushort>>(pCh));
  233. if (Vector<ushort>.Zero.Equals(vMatches))
  234. {
  235. pCh += Vector<ushort>.Count;
  236. length -= Vector<ushort>.Count;
  237. continue;
  238. }
  239. // Find offset of first match
  240. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  241. }
  242. if (pCh < pEndCh)
  243. {
  244. length = (int)(pEndCh - pCh);
  245. goto SequentialScan;
  246. }
  247. }
  248. return -1;
  249. Found3:
  250. pCh++;
  251. Found2:
  252. pCh++;
  253. Found1:
  254. pCh++;
  255. Found:
  256. return (int)(pCh - pChars);
  257. }
  258. }
  259. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  260. public static unsafe int IndexOfAny(ref char searchSpace, char value0, char value1, int length)
  261. {
  262. Debug.Assert(length >= 0);
  263. fixed (char* pChars = &searchSpace)
  264. {
  265. char* pCh = pChars;
  266. char* pEndCh = pCh + length;
  267. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  268. {
  269. // Figure out how many characters to read sequentially until we are vector aligned
  270. // This is equivalent to:
  271. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  272. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  273. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  274. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  275. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  276. }
  277. SequentialScan:
  278. while (length >= 4)
  279. {
  280. length -= 4;
  281. if (pCh[0] == value0 || pCh[0] == value1)
  282. goto Found;
  283. if (pCh[1] == value0 || pCh[1] == value1)
  284. goto Found1;
  285. if (pCh[2] == value0 || pCh[2] == value1)
  286. goto Found2;
  287. if (pCh[3] == value0 || pCh[3] == value1)
  288. goto Found3;
  289. pCh += 4;
  290. }
  291. while (length > 0)
  292. {
  293. length--;
  294. if (pCh[0] == value0 || pCh[0] == value1)
  295. goto Found;
  296. pCh++;
  297. }
  298. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  299. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  300. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  301. {
  302. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  303. // That will be how many times we iterate in the loop below.
  304. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  305. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  306. // Get comparison Vector
  307. Vector<ushort> values0 = new Vector<ushort>(value0);
  308. Vector<ushort> values1 = new Vector<ushort>(value1);
  309. while (length > 0)
  310. {
  311. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  312. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  313. Vector<ushort> vData = Unsafe.Read<Vector<ushort>>(pCh);
  314. var vMatches = Vector.BitwiseOr(
  315. Vector.Equals(vData, values0),
  316. Vector.Equals(vData, values1));
  317. if (Vector<ushort>.Zero.Equals(vMatches))
  318. {
  319. pCh += Vector<ushort>.Count;
  320. length -= Vector<ushort>.Count;
  321. continue;
  322. }
  323. // Find offset of first match
  324. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  325. }
  326. if (pCh < pEndCh)
  327. {
  328. length = (int)(pEndCh - pCh);
  329. goto SequentialScan;
  330. }
  331. }
  332. return -1;
  333. Found3:
  334. pCh++;
  335. Found2:
  336. pCh++;
  337. Found1:
  338. pCh++;
  339. Found:
  340. return (int)(pCh - pChars);
  341. }
  342. }
  343. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  344. public static unsafe int IndexOfAny(ref char searchSpace, char value0, char value1, char value2, int length)
  345. {
  346. Debug.Assert(length >= 0);
  347. fixed (char* pChars = &searchSpace)
  348. {
  349. char* pCh = pChars;
  350. char* pEndCh = pCh + length;
  351. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  352. {
  353. // Figure out how many characters to read sequentially until we are vector aligned
  354. // This is equivalent to:
  355. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  356. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  357. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  358. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  359. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  360. }
  361. SequentialScan:
  362. while (length >= 4)
  363. {
  364. length -= 4;
  365. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2)
  366. goto Found;
  367. if (pCh[1] == value0 || pCh[1] == value1 || pCh[1] == value2)
  368. goto Found1;
  369. if (pCh[2] == value0 || pCh[2] == value1 || pCh[2] == value2)
  370. goto Found2;
  371. if (pCh[3] == value0 || pCh[3] == value1 || pCh[3] == value2)
  372. goto Found3;
  373. pCh += 4;
  374. }
  375. while (length > 0)
  376. {
  377. length--;
  378. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2)
  379. goto Found;
  380. pCh++;
  381. }
  382. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  383. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  384. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  385. {
  386. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  387. // That will be how many times we iterate in the loop below.
  388. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  389. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  390. // Get comparison Vector
  391. Vector<ushort> values0 = new Vector<ushort>(value0);
  392. Vector<ushort> values1 = new Vector<ushort>(value1);
  393. Vector<ushort> values2 = new Vector<ushort>(value2);
  394. while (length > 0)
  395. {
  396. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  397. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  398. Vector<ushort> vData = Unsafe.Read<Vector<ushort>>(pCh);
  399. var vMatches = Vector.BitwiseOr(
  400. Vector.BitwiseOr(
  401. Vector.Equals(vData, values0),
  402. Vector.Equals(vData, values1)),
  403. Vector.Equals(vData, values2));
  404. if (Vector<ushort>.Zero.Equals(vMatches))
  405. {
  406. pCh += Vector<ushort>.Count;
  407. length -= Vector<ushort>.Count;
  408. continue;
  409. }
  410. // Find offset of first match
  411. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  412. }
  413. if (pCh < pEndCh)
  414. {
  415. length = (int)(pEndCh - pCh);
  416. goto SequentialScan;
  417. }
  418. }
  419. return -1;
  420. Found3:
  421. pCh++;
  422. Found2:
  423. pCh++;
  424. Found1:
  425. pCh++;
  426. Found:
  427. return (int)(pCh - pChars);
  428. }
  429. }
  430. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  431. public static unsafe int IndexOfAny(ref char searchSpace, char value0, char value1, char value2, char value3, int length)
  432. {
  433. Debug.Assert(length >= 0);
  434. fixed (char* pChars = &searchSpace)
  435. {
  436. char* pCh = pChars;
  437. char* pEndCh = pCh + length;
  438. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  439. {
  440. // Figure out how many characters to read sequentially until we are vector aligned
  441. // This is equivalent to:
  442. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  443. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  444. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  445. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  446. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  447. }
  448. SequentialScan:
  449. while (length >= 4)
  450. {
  451. length -= 4;
  452. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2 || pCh[0] == value3)
  453. goto Found;
  454. if (pCh[1] == value0 || pCh[1] == value1 || pCh[1] == value2 || pCh[1] == value3)
  455. goto Found1;
  456. if (pCh[2] == value0 || pCh[2] == value1 || pCh[2] == value2 || pCh[2] == value3)
  457. goto Found2;
  458. if (pCh[3] == value0 || pCh[3] == value1 || pCh[3] == value2 || pCh[3] == value3)
  459. goto Found3;
  460. pCh += 4;
  461. }
  462. while (length > 0)
  463. {
  464. length--;
  465. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2 || pCh[0] == value3)
  466. goto Found;
  467. pCh++;
  468. }
  469. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  470. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  471. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  472. {
  473. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  474. // That will be how many times we iterate in the loop below.
  475. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  476. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  477. // Get comparison Vector
  478. Vector<ushort> values0 = new Vector<ushort>(value0);
  479. Vector<ushort> values1 = new Vector<ushort>(value1);
  480. Vector<ushort> values2 = new Vector<ushort>(value2);
  481. Vector<ushort> values3 = new Vector<ushort>(value3);
  482. while (length > 0)
  483. {
  484. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  485. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  486. Vector<ushort> vData = Unsafe.Read<Vector<ushort>>(pCh);
  487. var vMatches = Vector.BitwiseOr(
  488. Vector.BitwiseOr(
  489. Vector.BitwiseOr(Vector.Equals(vData, values0), Vector.Equals(vData, values1)),
  490. Vector.Equals(vData, values2)),
  491. Vector.Equals(vData, values3));
  492. if (Vector<ushort>.Zero.Equals(vMatches))
  493. {
  494. pCh += Vector<ushort>.Count;
  495. length -= Vector<ushort>.Count;
  496. continue;
  497. }
  498. // Find offset of first match
  499. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  500. }
  501. if (pCh < pEndCh)
  502. {
  503. length = (int)(pEndCh - pCh);
  504. goto SequentialScan;
  505. }
  506. }
  507. return -1;
  508. Found3:
  509. pCh++;
  510. Found2:
  511. pCh++;
  512. Found1:
  513. pCh++;
  514. Found:
  515. return (int)(pCh - pChars);
  516. }
  517. }
  518. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  519. public static unsafe int IndexOfAny(ref char searchSpace, char value0, char value1, char value2, char value3, char value4, int length)
  520. {
  521. Debug.Assert(length >= 0);
  522. fixed (char* pChars = &searchSpace)
  523. {
  524. char* pCh = pChars;
  525. char* pEndCh = pCh + length;
  526. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  527. {
  528. // Figure out how many characters to read sequentially until we are vector aligned
  529. // This is equivalent to:
  530. // unaligned = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  531. // length = (Vector<ushort>.Count - unaligned) % Vector<ushort>.Count
  532. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  533. int unaligned = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  534. length = (Vector<ushort>.Count - unaligned) & (Vector<ushort>.Count - 1);
  535. }
  536. SequentialScan:
  537. while (length >= 4)
  538. {
  539. length -= 4;
  540. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2 || pCh[0] == value3 || pCh[0] == value4)
  541. goto Found;
  542. if (pCh[1] == value0 || pCh[1] == value1 || pCh[1] == value2 || pCh[1] == value3 || pCh[1] == value4)
  543. goto Found1;
  544. if (pCh[2] == value0 || pCh[2] == value1 || pCh[2] == value2 || pCh[2] == value3 || pCh[2] == value4)
  545. goto Found2;
  546. if (pCh[3] == value0 || pCh[3] == value1 || pCh[3] == value2 || pCh[3] == value3 || pCh[3] == value4)
  547. goto Found3;
  548. pCh += 4;
  549. }
  550. while (length > 0)
  551. {
  552. length--;
  553. if (pCh[0] == value0 || pCh[0] == value1 || pCh[0] == value2 || pCh[0] == value3 || pCh[0] == value4)
  554. goto Found;
  555. pCh++;
  556. }
  557. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  558. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  559. if (Vector.IsHardwareAccelerated && pCh < pEndCh)
  560. {
  561. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  562. // That will be how many times we iterate in the loop below.
  563. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pEndCh - pCh) / Vector<ushort>.Count)
  564. length = (int)((pEndCh - pCh) & ~(Vector<ushort>.Count - 1));
  565. // Get comparison Vector
  566. Vector<ushort> values0 = new Vector<ushort>(value0);
  567. Vector<ushort> values1 = new Vector<ushort>(value1);
  568. Vector<ushort> values2 = new Vector<ushort>(value2);
  569. Vector<ushort> values3 = new Vector<ushort>(value3);
  570. Vector<ushort> values4 = new Vector<ushort>(value4);
  571. while (length > 0)
  572. {
  573. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh is always vector aligned
  574. Debug.Assert(((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  575. Vector<ushort> vData = Unsafe.Read<Vector<ushort>>(pCh);
  576. var vMatches = Vector.BitwiseOr(
  577. Vector.BitwiseOr(
  578. Vector.BitwiseOr(
  579. Vector.BitwiseOr(Vector.Equals(vData, values0), Vector.Equals(vData, values1)),
  580. Vector.Equals(vData, values2)),
  581. Vector.Equals(vData, values3)),
  582. Vector.Equals(vData, values4));
  583. if (Vector<ushort>.Zero.Equals(vMatches))
  584. {
  585. pCh += Vector<ushort>.Count;
  586. length -= Vector<ushort>.Count;
  587. continue;
  588. }
  589. // Find offset of first match
  590. return (int)(pCh - pChars) + LocateFirstFoundChar(vMatches);
  591. }
  592. if (pCh < pEndCh)
  593. {
  594. length = (int)(pEndCh - pCh);
  595. goto SequentialScan;
  596. }
  597. }
  598. return -1;
  599. Found3:
  600. pCh++;
  601. Found2:
  602. pCh++;
  603. Found1:
  604. pCh++;
  605. Found:
  606. return (int)(pCh - pChars);
  607. }
  608. }
  609. [MethodImpl(MethodImplOptions.AggressiveOptimization)]
  610. public static unsafe int LastIndexOf(ref char searchSpace, char value, int length)
  611. {
  612. Debug.Assert(length >= 0);
  613. fixed (char* pChars = &searchSpace)
  614. {
  615. char* pCh = pChars + length;
  616. char* pEndCh = pChars;
  617. if (Vector.IsHardwareAccelerated && length >= Vector<ushort>.Count * 2)
  618. {
  619. // Figure out how many characters to read sequentially from the end until we are vector aligned
  620. // This is equivalent to: length = ((int)pCh % Unsafe.SizeOf<Vector<ushort>>()) / elementsPerByte
  621. const int elementsPerByte = sizeof(ushort) / sizeof(byte);
  622. length = ((int)pCh & (Unsafe.SizeOf<Vector<ushort>>() - 1)) / elementsPerByte;
  623. }
  624. SequentialScan:
  625. while (length >= 4)
  626. {
  627. length -= 4;
  628. pCh -= 4;
  629. if (*(pCh + 3) == value)
  630. goto Found3;
  631. if (*(pCh + 2) == value)
  632. goto Found2;
  633. if (*(pCh + 1) == value)
  634. goto Found1;
  635. if (*pCh == value)
  636. goto Found;
  637. }
  638. while (length > 0)
  639. {
  640. length -= 1;
  641. pCh -= 1;
  642. if (*pCh == value)
  643. goto Found;
  644. }
  645. // We get past SequentialScan only if IsHardwareAccelerated is true. However, we still have the redundant check to allow
  646. // the JIT to see that the code is unreachable and eliminate it when the platform does not have hardware accelerated.
  647. if (Vector.IsHardwareAccelerated && pCh > pEndCh)
  648. {
  649. // Get the highest multiple of Vector<ushort>.Count that is within the search space.
  650. // That will be how many times we iterate in the loop below.
  651. // This is equivalent to: length = Vector<ushort>.Count * ((int)(pCh - pEndCh) / Vector<ushort>.Count)
  652. length = (int)((pCh - pEndCh) & ~(Vector<ushort>.Count - 1));
  653. // Get comparison Vector
  654. Vector<ushort> vComparison = new Vector<ushort>(value);
  655. while (length > 0)
  656. {
  657. char* pStart = pCh - Vector<ushort>.Count;
  658. // Using Unsafe.Read instead of ReadUnaligned since the search space is pinned and pCh (and hence pSart) is always vector aligned
  659. Debug.Assert(((int)pStart & (Unsafe.SizeOf<Vector<ushort>>() - 1)) == 0);
  660. Vector<ushort> vMatches = Vector.Equals(vComparison, Unsafe.Read<Vector<ushort>>(pStart));
  661. if (Vector<ushort>.Zero.Equals(vMatches))
  662. {
  663. pCh -= Vector<ushort>.Count;
  664. length -= Vector<ushort>.Count;
  665. continue;
  666. }
  667. // Find offset of last match
  668. return (int)(pStart - pEndCh) + LocateLastFoundChar(vMatches);
  669. }
  670. if (pCh > pEndCh)
  671. {
  672. length = (int)(pCh - pEndCh);
  673. goto SequentialScan;
  674. }
  675. }
  676. return -1;
  677. Found:
  678. return (int)(pCh - pEndCh);
  679. Found1:
  680. return (int)(pCh - pEndCh) + 1;
  681. Found2:
  682. return (int)(pCh - pEndCh) + 2;
  683. Found3:
  684. return (int)(pCh - pEndCh) + 3;
  685. }
  686. }
  687. // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
  688. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  689. private static int LocateFirstFoundChar(Vector<ushort> match)
  690. {
  691. var vector64 = Vector.AsVectorUInt64(match);
  692. ulong candidate = 0;
  693. int i = 0;
  694. // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
  695. for (; i < Vector<ulong>.Count; i++)
  696. {
  697. candidate = vector64[i];
  698. if (candidate != 0)
  699. {
  700. break;
  701. }
  702. }
  703. // Single LEA instruction with jitted const (using function result)
  704. return i * 4 + LocateFirstFoundChar(candidate);
  705. }
  706. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  707. private static int LocateFirstFoundChar(ulong match)
  708. {
  709. // TODO: Arm variants
  710. if (Bmi1.X64.IsSupported)
  711. {
  712. return (int)(Bmi1.X64.TrailingZeroCount(match) >> 4);
  713. }
  714. else
  715. {
  716. unchecked
  717. {
  718. // Flag least significant power of two bit
  719. var powerOfTwoFlag = match ^ (match - 1);
  720. // Shift all powers of two into the high byte and extract
  721. return (int)((powerOfTwoFlag * XorPowerOfTwoToHighChar) >> 49);
  722. }
  723. }
  724. }
  725. private const ulong XorPowerOfTwoToHighChar = (0x03ul |
  726. 0x02ul << 16 |
  727. 0x01ul << 32) + 1;
  728. // Vector sub-search adapted from https://github.com/aspnet/KestrelHttpServer/pull/1138
  729. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  730. private static int LocateLastFoundChar(Vector<ushort> match)
  731. {
  732. var vector64 = Vector.AsVectorUInt64(match);
  733. ulong candidate = 0;
  734. int i = Vector<ulong>.Count - 1;
  735. // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001
  736. for (; i >= 0; i--)
  737. {
  738. candidate = vector64[i];
  739. if (candidate != 0)
  740. {
  741. break;
  742. }
  743. }
  744. // Single LEA instruction with jitted const (using function result)
  745. return i * 4 + LocateLastFoundChar(candidate);
  746. }
  747. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  748. private static int LocateLastFoundChar(ulong match)
  749. {
  750. return 3 - (BitOps.LeadingZeroCount(match) >> 4);
  751. }
  752. }
  753. }