SpanHelpers.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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.Globalization;
  6. using System.Runtime;
  7. using Internal.Runtime.CompilerServices;
  8. #if BIT64
  9. using nuint = System.UInt64;
  10. #else
  11. using nuint = System.UInt32;
  12. #endif
  13. namespace System
  14. {
  15. internal static partial class SpanHelpers
  16. {
  17. public static unsafe void ClearWithoutReferences(ref byte b, nuint byteLength)
  18. {
  19. if (byteLength == 0)
  20. return;
  21. #if CORECLR && (AMD64 || ARM64)
  22. if (byteLength > 4096)
  23. goto PInvoke;
  24. Unsafe.InitBlockUnaligned(ref b, 0, (uint)byteLength);
  25. return;
  26. #else
  27. // TODO: Optimize other platforms to be on par with AMD64 CoreCLR
  28. // Note: It's important that this switch handles lengths at least up to 22.
  29. // See notes below near the main loop for why.
  30. // The switch will be very fast since it can be implemented using a jump
  31. // table in assembly. See http://stackoverflow.com/a/449297/4077294 for more info.
  32. switch (byteLength)
  33. {
  34. case 1:
  35. b = 0;
  36. return;
  37. case 2:
  38. Unsafe.As<byte, short>(ref b) = 0;
  39. return;
  40. case 3:
  41. Unsafe.As<byte, short>(ref b) = 0;
  42. Unsafe.Add<byte>(ref b, 2) = 0;
  43. return;
  44. case 4:
  45. Unsafe.As<byte, int>(ref b) = 0;
  46. return;
  47. case 5:
  48. Unsafe.As<byte, int>(ref b) = 0;
  49. Unsafe.Add<byte>(ref b, 4) = 0;
  50. return;
  51. case 6:
  52. Unsafe.As<byte, int>(ref b) = 0;
  53. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  54. return;
  55. case 7:
  56. Unsafe.As<byte, int>(ref b) = 0;
  57. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  58. Unsafe.Add<byte>(ref b, 6) = 0;
  59. return;
  60. case 8:
  61. #if BIT64
  62. Unsafe.As<byte, long>(ref b) = 0;
  63. #else
  64. Unsafe.As<byte, int>(ref b) = 0;
  65. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  66. #endif
  67. return;
  68. case 9:
  69. #if BIT64
  70. Unsafe.As<byte, long>(ref b) = 0;
  71. #else
  72. Unsafe.As<byte, int>(ref b) = 0;
  73. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  74. #endif
  75. Unsafe.Add<byte>(ref b, 8) = 0;
  76. return;
  77. case 10:
  78. #if BIT64
  79. Unsafe.As<byte, long>(ref b) = 0;
  80. #else
  81. Unsafe.As<byte, int>(ref b) = 0;
  82. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  83. #endif
  84. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  85. return;
  86. case 11:
  87. #if BIT64
  88. Unsafe.As<byte, long>(ref b) = 0;
  89. #else
  90. Unsafe.As<byte, int>(ref b) = 0;
  91. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  92. #endif
  93. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  94. Unsafe.Add<byte>(ref b, 10) = 0;
  95. return;
  96. case 12:
  97. #if BIT64
  98. Unsafe.As<byte, long>(ref b) = 0;
  99. #else
  100. Unsafe.As<byte, int>(ref b) = 0;
  101. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  102. #endif
  103. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  104. return;
  105. case 13:
  106. #if BIT64
  107. Unsafe.As<byte, long>(ref b) = 0;
  108. #else
  109. Unsafe.As<byte, int>(ref b) = 0;
  110. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  111. #endif
  112. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  113. Unsafe.Add<byte>(ref b, 12) = 0;
  114. return;
  115. case 14:
  116. #if BIT64
  117. Unsafe.As<byte, long>(ref b) = 0;
  118. #else
  119. Unsafe.As<byte, int>(ref b) = 0;
  120. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  121. #endif
  122. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  123. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  124. return;
  125. case 15:
  126. #if BIT64
  127. Unsafe.As<byte, long>(ref b) = 0;
  128. #else
  129. Unsafe.As<byte, int>(ref b) = 0;
  130. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  131. #endif
  132. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  133. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  134. Unsafe.Add<byte>(ref b, 14) = 0;
  135. return;
  136. case 16:
  137. #if BIT64
  138. Unsafe.As<byte, long>(ref b) = 0;
  139. Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  140. #else
  141. Unsafe.As<byte, int>(ref b) = 0;
  142. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  143. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  144. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  145. #endif
  146. return;
  147. case 17:
  148. #if BIT64
  149. Unsafe.As<byte, long>(ref b) = 0;
  150. Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  151. #else
  152. Unsafe.As<byte, int>(ref b) = 0;
  153. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  154. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  155. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  156. #endif
  157. Unsafe.Add<byte>(ref b, 16) = 0;
  158. return;
  159. case 18:
  160. #if BIT64
  161. Unsafe.As<byte, long>(ref b) = 0;
  162. Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  163. #else
  164. Unsafe.As<byte, int>(ref b) = 0;
  165. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  166. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  167. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  168. #endif
  169. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
  170. return;
  171. case 19:
  172. #if BIT64
  173. Unsafe.As<byte, long>(ref b) = 0;
  174. Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  175. #else
  176. Unsafe.As<byte, int>(ref b) = 0;
  177. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  178. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  179. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  180. #endif
  181. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
  182. Unsafe.Add<byte>(ref b, 18) = 0;
  183. return;
  184. case 20:
  185. #if BIT64
  186. Unsafe.As<byte, long>(ref b) = 0;
  187. Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  188. #else
  189. Unsafe.As<byte, int>(ref b) = 0;
  190. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  191. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  192. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  193. #endif
  194. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
  195. return;
  196. case 21:
  197. #if BIT64
  198. Unsafe.As<byte, long>(ref b) = 0;
  199. Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  200. #else
  201. Unsafe.As<byte, int>(ref b) = 0;
  202. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  203. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  204. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  205. #endif
  206. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
  207. Unsafe.Add<byte>(ref b, 20) = 0;
  208. return;
  209. case 22:
  210. #if BIT64
  211. Unsafe.As<byte, long>(ref b) = 0;
  212. Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  213. #else
  214. Unsafe.As<byte, int>(ref b) = 0;
  215. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0;
  216. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0;
  217. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0;
  218. #endif
  219. Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0;
  220. Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 20)) = 0;
  221. return;
  222. }
  223. // P/Invoke into the native version for large lengths
  224. if (byteLength >= 512) goto PInvoke;
  225. nuint i = 0; // byte offset at which we're copying
  226. if (((nuint)Unsafe.AsPointer(ref b) & 3) != 0)
  227. {
  228. if (((nuint)Unsafe.AsPointer(ref b) & 1) != 0)
  229. {
  230. b = 0;
  231. i += 1;
  232. if (((nuint)Unsafe.AsPointer(ref b) & 2) != 0)
  233. goto IntAligned;
  234. }
  235. Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
  236. i += 2;
  237. }
  238. IntAligned:
  239. // On 64-bit IntPtr.Size == 8, so we want to advance to the next 8-aligned address. If
  240. // (int)b % 8 is 0, 5, 6, or 7, we will already have advanced by 0, 3, 2, or 1
  241. // bytes to the next aligned address (respectively), so do nothing. On the other hand,
  242. // if it is 1, 2, 3, or 4 we will want to copy-and-advance another 4 bytes until
  243. // we're aligned.
  244. // The thing 1, 2, 3, and 4 have in common that the others don't is that if you
  245. // subtract one from them, their 3rd lsb will not be set. Hence, the below check.
  246. if ((((nuint)Unsafe.AsPointer(ref b) - 1) & 4) == 0)
  247. {
  248. Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
  249. i += 4;
  250. }
  251. nuint end = byteLength - 16;
  252. byteLength -= i; // lower 4 bits of byteLength represent how many bytes are left *after* the unrolled loop
  253. // We know due to the above switch-case that this loop will always run 1 iteration; max
  254. // bytes we clear before checking is 23 (7 to align the pointers, 16 for 1 iteration) so
  255. // the switch handles lengths 0-22.
  256. Debug.Assert(end >= 7 && i <= end);
  257. // This is separated out into a different variable, so the i + 16 addition can be
  258. // performed at the start of the pipeline and the loop condition does not have
  259. // a dependency on the writes.
  260. nuint counter;
  261. do
  262. {
  263. counter = i + 16;
  264. // This loop looks very costly since there appear to be a bunch of temporary values
  265. // being created with the adds, but the jit (for x86 anyways) will convert each of
  266. // these to use memory addressing operands.
  267. // So the only cost is a bit of code size, which is made up for by the fact that
  268. // we save on writes to b.
  269. #if BIT64
  270. Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
  271. Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0;
  272. #else
  273. Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
  274. Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0;
  275. Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0;
  276. Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 12)) = 0;
  277. #endif
  278. i = counter;
  279. // See notes above for why this wasn't used instead
  280. // i += 16;
  281. }
  282. while (counter <= end);
  283. if ((byteLength & 8) != 0)
  284. {
  285. #if BIT64
  286. Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
  287. #else
  288. Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
  289. Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0;
  290. #endif
  291. i += 8;
  292. }
  293. if ((byteLength & 4) != 0)
  294. {
  295. Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
  296. i += 4;
  297. }
  298. if ((byteLength & 2) != 0)
  299. {
  300. Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0;
  301. i += 2;
  302. }
  303. if ((byteLength & 1) != 0)
  304. {
  305. Unsafe.AddByteOffset<byte>(ref b, i) = 0;
  306. // We're not using i after this, so not needed
  307. // i += 1;
  308. }
  309. return;
  310. #endif
  311. PInvoke:
  312. RuntimeImports.RhZeroMemory(ref b, byteLength);
  313. }
  314. public static unsafe void ClearWithReferences(ref IntPtr ip, nuint pointerSizeLength)
  315. {
  316. Debug.Assert((int)Unsafe.AsPointer(ref ip) % sizeof(IntPtr) == 0, "Should've been aligned on natural word boundary.");
  317. // First write backward 8 natural words at a time.
  318. // Writing backward allows us to get away with only simple modifications to the
  319. // mov instruction's base and index registers between loop iterations.
  320. for (; pointerSizeLength >= 8; pointerSizeLength -= 8)
  321. {
  322. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -1) = default;
  323. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -2) = default;
  324. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -3) = default;
  325. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -4) = default;
  326. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -5) = default;
  327. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -6) = default;
  328. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -7) = default;
  329. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -8) = default;
  330. }
  331. Debug.Assert(pointerSizeLength <= 7);
  332. // The logic below works by trying to minimize the number of branches taken for any
  333. // given range of lengths. For example, the lengths [ 4 .. 7 ] are handled by a single
  334. // branch, [ 2 .. 3 ] are handled by a single branch, and [ 1 ] is handled by a single
  335. // branch.
  336. //
  337. // We can write both forward and backward as a perf improvement. For example,
  338. // the lengths [ 4 .. 7 ] can be handled by zeroing out the first four natural
  339. // words and the last 3 natural words. In the best case (length = 7), there are
  340. // no overlapping writes. In the worst case (length = 4), there are three
  341. // overlapping writes near the middle of the buffer. In perf testing, the
  342. // penalty for performing duplicate writes is less expensive than the penalty
  343. // for complex branching.
  344. if (pointerSizeLength >= 4)
  345. {
  346. goto Write4To7;
  347. }
  348. else if (pointerSizeLength >= 2)
  349. {
  350. goto Write2To3;
  351. }
  352. else if (pointerSizeLength > 0)
  353. {
  354. goto Write1;
  355. }
  356. else
  357. {
  358. return; // nothing to write
  359. }
  360. Write4To7:
  361. Debug.Assert(pointerSizeLength >= 4);
  362. // Write first four and last three.
  363. Unsafe.Add(ref ip, 2) = default;
  364. Unsafe.Add(ref ip, 3) = default;
  365. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -3) = default;
  366. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -2) = default;
  367. Write2To3:
  368. Debug.Assert(pointerSizeLength >= 2);
  369. // Write first two and last one.
  370. Unsafe.Add(ref ip, 1) = default;
  371. Unsafe.Add(ref Unsafe.Add(ref ip, (IntPtr)pointerSizeLength), -1) = default;
  372. Write1:
  373. Debug.Assert(pointerSizeLength >= 1);
  374. // Write only element.
  375. ip = default;
  376. }
  377. }
  378. }