SpanHelpers.Char.cs 37 KB

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