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.
5 #if AMD64 || ARM64 || (BIT32 && !ARM)
6 #define HAS_CUSTOM_BLOCKS
11 //Only contains static methods. Does not require serialization
14 using System.Runtime.CompilerServices;
15 using System.Runtime.ConstrainedExecution;
16 using System.Runtime.InteropServices;
17 using System.Runtime.Versioning;
18 using System.Diagnostics;
19 using System.Security;
23 using nuint = System.UInt64;
25 using nuint = System.UInt32;
28 public static class Buffer
30 // Copies from one primitive array to another primitive array without
31 // respecting types. This calls memmove internally. The count and
32 // offset parameters here are in bytes. If you want to use traditional
33 // array element indices and counts, use Array.Copy.
34 [MethodImplAttribute(MethodImplOptions.InternalCall)]
35 public static extern void BlockCopy(Array src, int srcOffset,
36 Array dst, int dstOffset, int count);
38 // A very simple and efficient memmove that assumes all of the
39 // parameter validation has already been done. The count and offset
40 // parameters here are in bytes. If you want to use traditional
41 // array element indices and counts, use Array.Copy.
42 [MethodImplAttribute(MethodImplOptions.InternalCall)]
43 internal static extern void InternalBlockCopy(Array src, int srcOffsetBytes,
44 Array dst, int dstOffsetBytes, int byteCount);
46 // This is ported from the optimized CRT assembly in memchr.asm. The JIT generates
47 // pretty good code here and this ends up being within a couple % of the CRT asm.
48 // It is however cross platform as the CRT hasn't ported their fast version to 64-bit
51 internal unsafe static int IndexOfByte(byte* src, byte value, int index, int count)
53 Debug.Assert(src != null, "src should not be null");
55 byte* pByte = src + index;
57 // Align up the pointer to sizeof(int).
58 while (((int)pByte & 3) != 0)
62 else if (*pByte == value)
63 return (int)(pByte - src);
69 // Fill comparer with value byte for comparisons
71 // comparer = 0/0/value/value
72 uint comparer = (((uint)value << 8) + (uint)value);
73 // comparer = value/value/value/value
74 comparer = (comparer << 16) + comparer;
76 // Run through buffer until we hit a 4-byte section which contains
77 // the byte we're looking for or until we exhaust the buffer.
80 // Test the buffer for presence of value. comparer contains the byte
81 // replicated 4 times.
82 uint t1 = *(uint*)pByte;
84 uint t2 = 0x7efefeff + t1;
89 // if t1 is zero then these 4-bytes don't contain a match
92 // We've found a match for value, figure out which position it's in.
93 int foundIndex = (int)(pByte - src);
94 if (pByte[0] == value)
96 else if (pByte[1] == value)
97 return foundIndex + 1;
98 else if (pByte[2] == value)
99 return foundIndex + 2;
100 else if (pByte[3] == value)
101 return foundIndex + 3;
108 // Catch any bytes that might be left at the tail of the buffer
112 return (int)(pByte - src);
118 // If we don't have a match return -1;
122 // Returns a bool to indicate if the array is of primitive data types
124 [MethodImplAttribute(MethodImplOptions.InternalCall)]
125 private static extern bool IsPrimitiveTypeArray(Array array);
127 // Gets a particular byte out of the array. The array must be an
128 // array of primitives.
130 // This essentially does the following:
131 // return ((byte*)array) + index.
133 [MethodImplAttribute(MethodImplOptions.InternalCall)]
134 private static extern byte _GetByte(Array array, int index);
136 public static byte GetByte(Array array, int index)
138 // Is the array present?
140 throw new ArgumentNullException(nameof(array));
142 // Is it of primitive types?
143 if (!IsPrimitiveTypeArray(array))
144 throw new ArgumentException(SR.Arg_MustBePrimArray, nameof(array));
146 // Is the index in valid range of the array?
147 if (index < 0 || index >= _ByteLength(array))
148 throw new ArgumentOutOfRangeException(nameof(index));
150 return _GetByte(array, index);
153 // Sets a particular byte in an the array. The array must be an
154 // array of primitives.
156 // This essentially does the following:
157 // *(((byte*)array) + index) = value.
159 [MethodImplAttribute(MethodImplOptions.InternalCall)]
160 private static extern void _SetByte(Array array, int index, byte value);
162 public static void SetByte(Array array, int index, byte value)
164 // Is the array present?
166 throw new ArgumentNullException(nameof(array));
168 // Is it of primitive types?
169 if (!IsPrimitiveTypeArray(array))
170 throw new ArgumentException(SR.Arg_MustBePrimArray, nameof(array));
172 // Is the index in valid range of the array?
173 if (index < 0 || index >= _ByteLength(array))
174 throw new ArgumentOutOfRangeException(nameof(index));
176 // Make the FCall to do the work
177 _SetByte(array, index, value);
181 // Gets a particular byte out of the array. The array must be an
182 // array of primitives.
184 // This essentially does the following:
185 // return array.length * sizeof(array.UnderlyingElementType).
187 [MethodImplAttribute(MethodImplOptions.InternalCall)]
188 private static extern int _ByteLength(Array array);
190 public static int ByteLength(Array array)
192 // Is the array present?
194 throw new ArgumentNullException(nameof(array));
196 // Is it of primitive types?
197 if (!IsPrimitiveTypeArray(array))
198 throw new ArgumentException(SR.Arg_MustBePrimArray, nameof(array));
200 return _ByteLength(array);
203 internal unsafe static void ZeroMemory(byte* src, long len)
209 internal unsafe static void Memcpy(byte[] dest, int destIndex, byte* src, int srcIndex, int len)
211 Debug.Assert((srcIndex >= 0) && (destIndex >= 0) && (len >= 0), "Index and length must be non-negative!");
212 Debug.Assert(dest.Length - destIndex >= len, "not enough bytes in dest");
213 // If dest has 0 elements, the fixed statement will throw an
214 // IndexOutOfRangeException. Special-case 0-byte copies.
217 fixed (byte* pDest = dest)
219 Memcpy(pDest + destIndex, src + srcIndex, len);
223 internal unsafe static void Memcpy(byte* pDest, int destIndex, byte[] src, int srcIndex, int len)
225 Debug.Assert((srcIndex >= 0) && (destIndex >= 0) && (len >= 0), "Index and length must be non-negative!");
226 Debug.Assert(src.Length - srcIndex >= len, "not enough bytes in src");
227 // If dest has 0 elements, the fixed statement will throw an
228 // IndexOutOfRangeException. Special-case 0-byte copies.
231 fixed (byte* pSrc = src)
233 Memcpy(pDest + destIndex, pSrc + srcIndex, len);
237 // This is tricky to get right AND fast, so lets make it useful for the whole Fx.
238 // E.g. System.Runtime.WindowsRuntime!WindowsRuntimeBufferExtensions.MemCopy uses it.
240 // This method has a slightly different behavior on arm and other platforms.
241 // On arm this method behaves like memcpy and does not handle overlapping buffers.
242 // While on other platforms it behaves like memmove and handles overlapping buffers.
243 // This behavioral difference is unfortunate but intentional because
244 // 1. This method is given access to other internal dlls and this close to release we do not want to change it.
245 // 2. It is difficult to get this right for arm and again due to release dates we would like to visit it later.
247 [MethodImplAttribute(MethodImplOptions.InternalCall)]
248 internal unsafe static extern void Memcpy(byte* dest, byte* src, int len);
250 [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
251 internal unsafe static void Memcpy(byte* dest, byte* src, int len)
253 Debug.Assert(len >= 0, "Negative length in memcopy!");
254 Memmove(dest, src, (uint)len);
258 // This method has different signature for x64 and other platforms and is done for performance reasons.
259 internal unsafe static void Memmove(byte* dest, byte* src, nuint len)
261 #if AMD64 || (BIT32 && !ARM)
262 const nuint CopyThreshold = 2048;
265 // Determined optimal value for Windows.
266 // https://github.com/dotnet/coreclr/issues/13843
267 const nuint CopyThreshold = UInt64.MaxValue;
268 #else // PLATFORM_WINDOWS
269 // Managed code is currently faster than glibc unoptimized memmove
270 // TODO-ARM64-UNIX-OPT revisit when glibc optimized memmove is in Linux distros
271 // https://github.com/dotnet/coreclr/issues/13844
272 const nuint CopyThreshold = UInt64.MaxValue;
273 #endif // PLATFORM_WINDOWS
275 const nuint CopyThreshold = 512;
276 #endif // AMD64 || (BIT32 && !ARM)
278 // P/Invoke into the native version when the buffers are overlapping.
280 if (((nuint)dest - (nuint)src < len) || ((nuint)src - (nuint)dest < len)) goto PInvoke;
282 byte* srcEnd = src + len;
283 byte* destEnd = dest + len;
285 if (len <= 16) goto MCPY02;
286 if (len > 64) goto MCPY05;
289 // Copy bytes which are multiples of 16 and leave the remainder for MCPY01 to handle.
290 Debug.Assert(len > 16 && len <= 64);
291 #if HAS_CUSTOM_BLOCKS
292 *(Block16*)dest = *(Block16*)src; // [0,16]
294 *(long*)dest = *(long*)src;
295 *(long*)(dest + 8) = *(long*)(src + 8); // [0,16]
297 *(int*)dest = *(int*)src;
298 *(int*)(dest + 4) = *(int*)(src + 4);
299 *(int*)(dest + 8) = *(int*)(src + 8);
300 *(int*)(dest + 12) = *(int*)(src + 12); // [0,16]
302 if (len <= 32) goto MCPY01;
303 #if HAS_CUSTOM_BLOCKS
304 *(Block16*)(dest + 16) = *(Block16*)(src + 16); // [0,32]
306 *(long*)(dest + 16) = *(long*)(src + 16);
307 *(long*)(dest + 24) = *(long*)(src + 24); // [0,32]
309 *(int*)(dest + 16) = *(int*)(src + 16);
310 *(int*)(dest + 20) = *(int*)(src + 20);
311 *(int*)(dest + 24) = *(int*)(src + 24);
312 *(int*)(dest + 28) = *(int*)(src + 28); // [0,32]
314 if (len <= 48) goto MCPY01;
315 #if HAS_CUSTOM_BLOCKS
316 *(Block16*)(dest + 32) = *(Block16*)(src + 32); // [0,48]
318 *(long*)(dest + 32) = *(long*)(src + 32);
319 *(long*)(dest + 40) = *(long*)(src + 40); // [0,48]
321 *(int*)(dest + 32) = *(int*)(src + 32);
322 *(int*)(dest + 36) = *(int*)(src + 36);
323 *(int*)(dest + 40) = *(int*)(src + 40);
324 *(int*)(dest + 44) = *(int*)(src + 44); // [0,48]
328 // Unconditionally copy the last 16 bytes using destEnd and srcEnd and return.
329 Debug.Assert(len > 16 && len <= 64);
330 #if HAS_CUSTOM_BLOCKS
331 *(Block16*)(destEnd - 16) = *(Block16*)(srcEnd - 16);
333 *(long*)(destEnd - 16) = *(long*)(srcEnd - 16);
334 *(long*)(destEnd - 8) = *(long*)(srcEnd - 8);
336 *(int*)(destEnd - 16) = *(int*)(srcEnd - 16);
337 *(int*)(destEnd - 12) = *(int*)(srcEnd - 12);
338 *(int*)(destEnd - 8) = *(int*)(srcEnd - 8);
339 *(int*)(destEnd - 4) = *(int*)(srcEnd - 4);
344 // Copy the first 8 bytes and then unconditionally copy the last 8 bytes and return.
345 if ((len & 24) == 0) goto MCPY03;
346 Debug.Assert(len >= 8 && len <= 16);
348 *(long*)dest = *(long*)src;
349 *(long*)(destEnd - 8) = *(long*)(srcEnd - 8);
351 *(int*)dest = *(int*)src;
352 *(int*)(dest + 4) = *(int*)(src + 4);
353 *(int*)(destEnd - 8) = *(int*)(srcEnd - 8);
354 *(int*)(destEnd - 4) = *(int*)(srcEnd - 4);
359 // Copy the first 4 bytes and then unconditionally copy the last 4 bytes and return.
360 if ((len & 4) == 0) goto MCPY04;
361 Debug.Assert(len >= 4 && len < 8);
362 *(int*)dest = *(int*)src;
363 *(int*)(destEnd - 4) = *(int*)(srcEnd - 4);
367 // Copy the first byte. For pending bytes, do an unconditionally copy of the last 2 bytes and return.
368 Debug.Assert(len < 4);
369 if (len == 0) return;
371 if ((len & 2) == 0) return;
372 *(short*)(destEnd - 2) = *(short*)(srcEnd - 2);
376 // PInvoke to the native version when the copy length exceeds the threshold.
377 if (len > CopyThreshold)
381 // Copy 64-bytes at a time until the remainder is less than 64.
382 // If remainder is greater than 16 bytes, then jump to MCPY00. Otherwise, unconditionally copy the last 16 bytes and return.
383 Debug.Assert(len > 64 && len <= CopyThreshold);
387 #if HAS_CUSTOM_BLOCKS
388 *(Block64*)dest = *(Block64*)src;
390 *(long*)dest = *(long*)src;
391 *(long*)(dest + 8) = *(long*)(src + 8);
392 *(long*)(dest + 16) = *(long*)(src + 16);
393 *(long*)(dest + 24) = *(long*)(src + 24);
394 *(long*)(dest + 32) = *(long*)(src + 32);
395 *(long*)(dest + 40) = *(long*)(src + 40);
396 *(long*)(dest + 48) = *(long*)(src + 48);
397 *(long*)(dest + 56) = *(long*)(src + 56);
399 *(int*)dest = *(int*)src;
400 *(int*)(dest + 4) = *(int*)(src + 4);
401 *(int*)(dest + 8) = *(int*)(src + 8);
402 *(int*)(dest + 12) = *(int*)(src + 12);
403 *(int*)(dest + 16) = *(int*)(src + 16);
404 *(int*)(dest + 20) = *(int*)(src + 20);
405 *(int*)(dest + 24) = *(int*)(src + 24);
406 *(int*)(dest + 28) = *(int*)(src + 28);
407 *(int*)(dest + 32) = *(int*)(src + 32);
408 *(int*)(dest + 36) = *(int*)(src + 36);
409 *(int*)(dest + 40) = *(int*)(src + 40);
410 *(int*)(dest + 44) = *(int*)(src + 44);
411 *(int*)(dest + 48) = *(int*)(src + 48);
412 *(int*)(dest + 52) = *(int*)(src + 52);
413 *(int*)(dest + 56) = *(int*)(src + 56);
414 *(int*)(dest + 60) = *(int*)(src + 60);
419 if (n != 0) goto MCPY06;
422 if (len > 16) goto MCPY00;
423 #if HAS_CUSTOM_BLOCKS
424 *(Block16*)(destEnd - 16) = *(Block16*)(srcEnd - 16);
426 *(long*)(destEnd - 16) = *(long*)(srcEnd - 16);
427 *(long*)(destEnd - 8) = *(long*)(srcEnd - 8);
429 *(int*)(destEnd - 16) = *(int*)(srcEnd - 16);
430 *(int*)(destEnd - 12) = *(int*)(srcEnd - 12);
431 *(int*)(destEnd - 8) = *(int*)(srcEnd - 8);
432 *(int*)(destEnd - 4) = *(int*)(srcEnd - 4);
437 _Memmove(dest, src, len);
440 // Non-inlinable wrapper around the QCall that avoids poluting the fast path
441 // with P/Invoke prolog/epilog.
442 [MethodImplAttribute(MethodImplOptions.NoInlining)]
443 private unsafe static void _Memmove(byte* dest, byte* src, nuint len)
445 __Memmove(dest, src, len);
448 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
449 extern private unsafe static void __Memmove(byte* dest, byte* src, nuint len);
451 // The attributes on this method are chosen for best JIT performance.
452 // Please do not edit unless intentional.
453 [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
454 [CLSCompliant(false)]
455 public static unsafe void MemoryCopy(void* source, void* destination, long destinationSizeInBytes, long sourceBytesToCopy)
457 if (sourceBytesToCopy > destinationSizeInBytes)
459 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.sourceBytesToCopy);
461 Memmove((byte*)destination, (byte*)source, checked((nuint)sourceBytesToCopy));
465 // The attributes on this method are chosen for best JIT performance.
466 // Please do not edit unless intentional.
467 [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
468 [CLSCompliant(false)]
469 public static unsafe void MemoryCopy(void* source, void* destination, ulong destinationSizeInBytes, ulong sourceBytesToCopy)
471 if (sourceBytesToCopy > destinationSizeInBytes)
473 ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.sourceBytesToCopy);
476 Memmove((byte*)destination, (byte*)source, sourceBytesToCopy);
478 Memmove((byte*)destination, (byte*)source, checked((uint)sourceBytesToCopy));
482 #if HAS_CUSTOM_BLOCKS
483 [StructLayout(LayoutKind.Sequential, Size = 16)]
484 private struct Block16 { }
486 [StructLayout(LayoutKind.Sequential, Size = 64)]
487 private struct Block64 { }
488 #endif // HAS_CUSTOM_BLOCKS