// This method has different signature for x64 and other platforms and is done for performance reasons. private static void Memmove(ref byte dest, ref byte src, nuint len) { // P/Invoke into the native version when the buffers are overlapping. if (((nuint)Unsafe.ByteOffset(ref src, ref dest) < len) || ((nuint)Unsafe.ByteOffset(ref dest, ref src) < len)) { goto BuffersOverlap; } // Use "(IntPtr)(nint)len" to avoid overflow checking on the explicit cast to IntPtr ref byte srcEnd = ref Unsafe.Add(ref src, (IntPtr)(nint)len);
public unsafe static int GetIndexOfFirstInvalidUtf8Sequence(ReadOnlySpan <byte> utf8Data, out bool isAscii) { fixed(byte *pUtf8Data = &MemoryMarshal.GetReference(utf8Data)) { byte *pFirstInvalidByte = GetPointerToFirstInvalidByte(pUtf8Data, utf8Data.Length, out int utf16CodeUnitCountAdjustment, out _); int index = (int)(void *)Unsafe.ByteOffset(ref *pUtf8Data, ref *pFirstInvalidByte); isAscii = (utf16CodeUnitCountAdjustment == 0); // If UTF-16 char count == UTF-8 byte count, it's ASCII. return((index < utf8Data.Length) ? index : -1); } }
private static IntPtr MeasureStringAdjustment() { string sampleString = "a"; unsafe { fixed(char *pSampleString = sampleString) { return(Unsafe.ByteOffset <char>(ref Unsafe.As <Pinnable <char> >(sampleString).Data, ref Unsafe.AsRef <char>(pSampleString))); } } }
public static void ByteOffsetStackByte4() { var byte4 = new Byte4(); Assert.Equal(new IntPtr(0), Unsafe.ByteOffset(ref byte4.B0, ref byte4.B0)); Assert.Equal(new IntPtr(1), Unsafe.ByteOffset(ref byte4.B0, ref byte4.B1)); Assert.Equal(new IntPtr(-1), Unsafe.ByteOffset(ref byte4.B1, ref byte4.B0)); Assert.Equal(new IntPtr(2), Unsafe.ByteOffset(ref byte4.B0, ref byte4.B2)); Assert.Equal(new IntPtr(-2), Unsafe.ByteOffset(ref byte4.B2, ref byte4.B0)); Assert.Equal(new IntPtr(3), Unsafe.ByteOffset(ref byte4.B0, ref byte4.B3)); Assert.Equal(new IntPtr(-3), Unsafe.ByteOffset(ref byte4.B3, ref byte4.B0)); }
public static void Run() { var z1 = (1, 2L, 'Z', "test"); var zz = "new mark test"; var z2 = (10, 20L, 'Z', "0test"); Console.WriteLine("SizeOf(...) = {0}", Unsafe.SizeOf <ValueTuple <int, int, UIntPtr> >()); Console.WriteLine("SizeOf(...) = {0}", Unsafe.SizeOf <ValueTuple <string> >()); Console.WriteLine("Offset = {0}", Unsafe.ByteOffset(ref z2.Item4, ref z1.Item4)); Console.WriteLine(Unsafe.AddByteOffset(ref z1.Item4, new IntPtr(8))); }
// Copies the 8 byte args between mem and corrects offsets dest private static void CopyArgs( ref ulong *dest, ulong *destLimit, ulong *source, uint argsLength ) { var diff = (uint)Unsafe.ByteOffset(ref *dest, ref *destLimit); Debug.Assert((int)diff > 0); Buffer.MemoryCopy(source, dest, diff, argsLength * sizeof(ulong)); dest += argsLength; }
/// <summary> /// Determines whether two sequences overlap in memory and outputs the element offset. /// </summary> public static bool Overlaps <T>(this ReadOnlySpan <T> first, ReadOnlySpan <T> second, out int elementOffset) { if (first.IsEmpty || second.IsEmpty) { elementOffset = 0; return(false); } IntPtr byteOffset = Unsafe.ByteOffset( ref MemoryMarshal.GetReference(first), ref MemoryMarshal.GetReference(second)); if (Unsafe.SizeOf <IntPtr>() == sizeof(int)) { if ((uint)byteOffset < (uint)(first.Length * Unsafe.SizeOf <T>()) || (uint)byteOffset > (uint)-(second.Length * Unsafe.SizeOf <T>())) { if ((int)byteOffset % Unsafe.SizeOf <T>() != 0) { ThrowHelper.ThrowArgumentException_OverlapAlignmentMismatch(); } elementOffset = (int)byteOffset / Unsafe.SizeOf <T>(); return(true); } else { elementOffset = 0; return(false); } } else { if ((ulong)byteOffset < (ulong)((long)first.Length * Unsafe.SizeOf <T>()) || (ulong)byteOffset > (ulong)-((long)second.Length * Unsafe.SizeOf <T>())) { if ((long)byteOffset % Unsafe.SizeOf <T>() != 0) { ThrowHelper.ThrowArgumentException_OverlapAlignmentMismatch(); } elementOffset = (int)((long)byteOffset / Unsafe.SizeOf <T>()); return(true); } else { elementOffset = 0; return(false); } } }
public static unsafe bool Overlap( ReadOnlySpan <byte> first, ReadOnlySpan <byte> second) { if (second.IsEmpty) { return(false); } IntPtr diff = Unsafe.ByteOffset(ref first.DangerousGetPinnableReference(), ref second.DangerousGetPinnableReference()); return((sizeof(IntPtr) == sizeof(int)) ? unchecked ((uint)diff < (uint)first.Length || (uint)diff > (uint)-second.Length) : unchecked ((ulong)diff < (ulong)first.Length || (ulong)diff > (ulong)-second.Length)); }
public static void ByteOffsetArray() { var a = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }; Assert.Equal(new IntPtr(0), Unsafe.ByteOffset(ref a[0], ref a[0])); Assert.Equal(new IntPtr(1), Unsafe.ByteOffset(ref a[0], ref a[1])); Assert.Equal(new IntPtr(-1), Unsafe.ByteOffset(ref a[1], ref a[0])); Assert.Equal(new IntPtr(2), Unsafe.ByteOffset(ref a[0], ref a[2])); Assert.Equal(new IntPtr(-2), Unsafe.ByteOffset(ref a[2], ref a[0])); Assert.Equal(new IntPtr(3), Unsafe.ByteOffset(ref a[0], ref a[3])); Assert.Equal(new IntPtr(4), Unsafe.ByteOffset(ref a[0], ref a[4])); Assert.Equal(new IntPtr(5), Unsafe.ByteOffset(ref a[0], ref a[5])); Assert.Equal(new IntPtr(6), Unsafe.ByteOffset(ref a[0], ref a[6])); Assert.Equal(new IntPtr(7), Unsafe.ByteOffset(ref a[0], ref a[7])); }
public static Span <T> DangerousCreate(object obj, ref T objectData, int length) { if (obj == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj); } if (length < 0) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); } Pinnable <T> pinnable = Unsafe.As <Pinnable <T> >(obj); IntPtr byteOffset = Unsafe.ByteOffset <T>(ref pinnable.Data, ref objectData); return(new Span <T>(pinnable, byteOffset, length)); }
private static void _BulkMoveWithWriteBarrier( ref byte destination, ref byte source, nuint byteCount ) { Debug.Assert(byteCount > BulkMoveWithWriteBarrierChunk); if (Unsafe.AreSame(ref source, ref destination)) { return; } // This is equivalent to: (destination - source) >= byteCount || (destination - source) < 0 if ((nuint)(nint)Unsafe.ByteOffset(ref source, ref destination) >= byteCount) { // Copy forwards do { byteCount -= BulkMoveWithWriteBarrierChunk; __BulkMoveWithWriteBarrier( ref destination, ref source, BulkMoveWithWriteBarrierChunk ); destination = ref Unsafe.AddByteOffset( ref destination, BulkMoveWithWriteBarrierChunk ); source = ref Unsafe.AddByteOffset(ref source, BulkMoveWithWriteBarrierChunk); } while (byteCount > BulkMoveWithWriteBarrierChunk); } else { // Copy backwards do { byteCount -= BulkMoveWithWriteBarrierChunk; __BulkMoveWithWriteBarrier( ref Unsafe.AddByteOffset(ref destination, byteCount), ref Unsafe.AddByteOffset(ref source, byteCount), BulkMoveWithWriteBarrierChunk ); } while (byteCount > BulkMoveWithWriteBarrierChunk); } __BulkMoveWithWriteBarrier(ref destination, ref source, byteCount); }
// This method has different signature for x64 and other platforms and is done for performance reasons. private static void Memmove(ref byte dest, ref byte src, nuint len) { #if AMD64 || (BIT32 && !ARM) const nuint CopyThreshold = 2048; #else const nuint CopyThreshold = 512; #endif // AMD64 || (BIT32 && !ARM) // P/Invoke into the native version when the buffers are overlapping. if (((nuint)Unsafe.ByteOffset(ref src, ref dest) < len) || ((nuint)Unsafe.ByteOffset(ref dest, ref src) < len)) { goto BuffersOverlap; } // Use "(IntPtr)(nint)len" to avoid overflow checking on the explicit cast to IntPtr ref byte srcEnd = ref Unsafe.Add(ref src, (IntPtr)(nint)len);
/// <summary> /// Implements the copy functionality used by Span and ReadOnlySpan. /// /// NOTE: Fast span implements TryCopyTo in corelib and therefore this implementation /// is only used by portable span. The code must live in code that only compiles /// for portable span which means either each individual span implementation /// of this shared code file. Other shared SpanHelper.X.cs files are compiled /// for both portable and fast span implementations. /// </summary> public static unsafe void CopyTo <T>(ref T dst, int dstLength, ref T src, int srcLength) { Debug.Assert(dstLength != 0); IntPtr srcByteCount = Unsafe.ByteOffset(ref src, ref Unsafe.Add(ref src, srcLength)); IntPtr dstByteCount = Unsafe.ByteOffset(ref dst, ref Unsafe.Add(ref dst, dstLength)); IntPtr diff = Unsafe.ByteOffset(ref src, ref dst); bool isOverlapped = (sizeof(IntPtr) == sizeof(int)) ? (uint)diff <(uint)srcByteCount || (uint)diff> (uint) - (int)dstByteCount : (ulong)diff <(ulong)srcByteCount || (ulong)diff> (ulong) - (long)dstByteCount; if (!isOverlapped && !SpanHelpers.IsReferenceOrContainsReferences <T>()) { ref byte dstBytes = ref Unsafe.As <T, byte>(ref dst); ref byte srcBytes = ref Unsafe.As <T, byte>(ref src);
/// <summary> /// Determines whether two sequences overlap in memory. /// </summary> public static bool Overlaps <T>(this ReadOnlySpan <T> first, ReadOnlySpan <T> second) { if (first.IsEmpty || second.IsEmpty) { return(false); } IntPtr byteOffset = Unsafe.ByteOffset( ref MemoryMarshal.GetReference(first), ref MemoryMarshal.GetReference(second)); if (Unsafe.SizeOf <IntPtr>() == sizeof(int)) { return((uint)byteOffset < (uint)(first.Length * Unsafe.SizeOf <T>()) || (uint)byteOffset > (uint)-(second.Length * Unsafe.SizeOf <T>())); } else { return((ulong)byteOffset < (ulong)((long)first.Length * Unsafe.SizeOf <T>()) || (ulong)byteOffset > (ulong)-((long)second.Length * Unsafe.SizeOf <T>())); } }
// This method has different signature for x64 and other platforms and is done for performance reasons. private static void Memmove(ByReference <byte> dest, ByReference <byte> src, nuint len) { #if AMD64 || (BIT32 && !ARM) const nuint CopyThreshold = 2048; #elif ARM64 #if PLATFORM_WINDOWS // Determined optimal value for Windows. // https://github.com/dotnet/coreclr/issues/13843 const nuint CopyThreshold = UInt64.MaxValue; #else // PLATFORM_WINDOWS // Managed code is currently faster than glibc unoptimized memmove // TODO-ARM64-UNIX-OPT revisit when glibc optimized memmove is in Linux distros // https://github.com/dotnet/coreclr/issues/13844 const nuint CopyThreshold = UInt64.MaxValue; #endif // PLATFORM_WINDOWS #else const nuint CopyThreshold = 512; #endif // AMD64 || (BIT32 && !ARM) // P/Invoke into the native version when the buffers are overlapping. if (((nuint)Unsafe.ByteOffset(ref src.Value, ref dest.Value) < len) || ((nuint)Unsafe.ByteOffset(ref dest.Value, ref src.Value) < len)) { goto BuffersOverlap; } // Use "(IntPtr)(nint)len" to avoid overflow checking on the explicit cast to IntPtr ref byte srcEnd = ref Unsafe.Add(ref src.Value, (IntPtr)(nint)len);
public static int TestStructWithNonBlittableTypesOffset(ref StructWithNonBlittableTypes a) { return(Unsafe.ByteOffset(ref a.a0, ref a.a1).ToInt32()); }
public static int LoadWav(string path) #endif { try { bool swapped = false; // First: we open the file and copy it into a single large memory buffer for processing. byte[] buffer; using (var stream = File.OpenRead(path)) { buffer = new byte[stream.Length]; stream.Read(buffer, 0, buffer.Length); } Span <byte> bufferSpan = buffer; // Second: find the RIFF chunk. Note that by searching for RIFF both normal // and reversed, we can automatically determine the endian swap situation for // this file regardless of what machine we are on. var riff = FindChunk(bufferSpan, RiffId, false); if (riff.IsEmpty) { riff = FindChunk(bufferSpan, RiffId, true); if (riff.IsEmpty) { XPlane.Trace.WriteLine("[OpenAL Sample] Could not find RIFF chunk in wave file."); return(0); } swapped = true; } // The wave chunk isn't really a chunk at all. :-( It's just a "WAVE" tag // followed by more chunks. This strikes me as totally inconsistent, but // anyway, confirm the WAVE ID and move on. if (riff[0] != (byte)'W' || riff[1] != (byte)'A' || riff[2] != (byte)'V' || riff[3] != (byte)'E') { XPlane.Trace.WriteLine("[OpenAL Sample] Could not find WAVE signature in wave file."); return(0); } bufferSpan = bufferSpan.Slice(4 + (int)Unsafe.ByteOffset(ref bufferSpan[0], ref riff[0])); var formatChunk = FindChunk(bufferSpan, FmtId, swapped); if (formatChunk.IsEmpty) { XPlane.Trace.WriteLine("[OpenAL Sample] Could not find FMT chunk in wave file."); } var format = MemoryMarshal.Read <FormatInfo>(formatChunk); if (swapped) { format.Format = BinaryPrimitives.ReverseEndianness(format.Format); format.ChannelCount = BinaryPrimitives.ReverseEndianness(format.ChannelCount); format.SampleRate = BinaryPrimitives.ReverseEndianness(format.SampleRate); format.ByteRate = BinaryPrimitives.ReverseEndianness(format.ByteRate); format.BlockAlign = BinaryPrimitives.ReverseEndianness(format.BlockAlign); format.BitsPerSample = BinaryPrimitives.ReverseEndianness(format.BitsPerSample); } // Reject things we don't understand...expand this code to support weirder audio formats. if (format.Format != 1) { XPlane.Trace.WriteLine("[OpenAL Sample] Wave file is not PCM format data."); return(0); } if (format.ChannelCount != 1 && format.ChannelCount != 2) { XPlane.Trace.WriteLine("[OpenAL Sample] Must have mono or stereo sound."); return(0); } if (format.BitsPerSample != 8 && format.BitsPerSample != 16) { XPlane.Trace.WriteLine("[OpenAL Sample] Must have 8 or 16 bit sounds."); return(0); } var data = FindChunk(bufferSpan, DataId, swapped); if (data.IsEmpty) { XPlane.Trace.WriteLine("[OpenAL Sample] I could not find the DATA chunk."); return(0); } var sampleSize = format.ChannelCount * format.BitsPerSample / 8; var dataSamples = data.Length / sampleSize; // If the file is swapped and we have 16-bit audio, we need to endian-swap the audio too or we'll // get something that sounds just astoundingly bad! if (format.BitsPerSample == 16 && swapped) { var wordSpan = MemoryMarshal.Cast <byte, ushort>(data); foreach (ref uint word in wordSpan) { word = BinaryPrimitives.ReverseEndianness(word); } } return(BufferData(data)); #if SILK_NET unsafe uint BufferData(in ReadOnlySpan <byte> audioData) { var bufferId = al.GenBuffer(); if (bufferId == 0) { XPlane.Trace.WriteLine("[OpenAL Sample] Could not generate buffer id."); return(0); } fixed(void *pData = audioData) { al.BufferData( bufferId, format.BitsPerSample == 16 ? (format.ChannelCount == 2 ? BufferFormat.Stereo16 : BufferFormat.Mono16) : (format.ChannelCount == 2 ? BufferFormat.Stereo8 : BufferFormat.Mono8), pData, audioData.Length, format.SampleRate); } return(bufferId); } #elif OPEN_TOOLKIT int BufferData(in Span <byte> audioData) { AL.GenBuffer(out var bufferId); if (bufferId == 0) { XPlane.Trace.WriteLine("[OpenAL Sample] Could not generate buffer id."); return(0); } AL.BufferData( bufferId, format.BitsPerSample == 16 ? (format.ChannelCount == 2 ? ALFormat.Stereo16 : ALFormat.Mono16) : (format.ChannelCount == 2 ? ALFormat.Stereo8 : ALFormat.Mono8), audioData, format.SampleRate); return(bufferId); } #endif } catch (Exception ex) { XPlane.Trace.WriteLine(ex.ToString()); return(0); } }
/// <summary> /// Calculates a byte offset of a given field within a struct. /// </summary> /// <typeparam name="T">Struct type</typeparam> /// <typeparam name="T2">Field type</typeparam> /// <param name="storage">Parent struct</param> /// <param name="target">Field</param> /// <returns>The byte offset of the given field in the given struct</returns> public static int OffsetOf <T, T2>(ref T2 storage, ref T target) { return((int)Unsafe.ByteOffset(ref Unsafe.As <T2, T>(ref storage), ref target)); }
// Array header sizes are a runtime implementation detail and aren't the same across all runtimes. (The CLR made a tweak after 4.5, and Mono has an extra Bounds pointer.) private static IntPtr MeasureArrayAdjustment() { T[] sampleArray = new T[1]; return(Unsafe.ByteOffset <T>(ref Unsafe.As <Pinnable <T> >(sampleArray).Data, ref sampleArray[0])); }
public static nint ByteOffset <T>(T *tgt, T *cur) where T : unmanaged => Unsafe.ByteOffset(ref *tgt, ref *cur);
public static unsafe int UnsafeByteOffset(ref byte start, ReadOnlySpan <byte> end) => (int)(void *)Unsafe.ByteOffset(ref start, ref MemoryMarshal.GetReference(end));
private static int StorageOffset <T>(ref NativeCtxStorage storage, ref T target) { return((int)Unsafe.ByteOffset(ref Unsafe.As <NativeCtxStorage, T>(ref storage), ref target)); }
public static Span <LbrEntry64> Entries(ref LbrTraceEventData64 data, int totalSize) { IntPtr entriesOffset = Unsafe.ByteOffset(ref Unsafe.As <LbrTraceEventData64, byte>(ref data), ref Unsafe.As <LbrEntry64, byte>(ref data._entries)); return(MemoryMarshal.CreateSpan(ref data._entries, (totalSize - (int)entriesOffset) / sizeof(LbrEntry64))); }
private static int OffsetOf <T>(ref SupportBuffer storage, ref T target) { return((int)Unsafe.ByteOffset(ref Unsafe.As <SupportBuffer, T>(ref storage), ref target)); }
private static unsafe SafeCertStoreHandle SelectFromStore(SafeCertStoreHandle safeSourceStoreHandle, string?title, string?message, X509SelectionFlag selectionFlags, IntPtr hwndParent) { int dwErrorCode = ERROR_SUCCESS; SafeCertStoreHandle safeCertStoreHandle = Interop.Crypt32.CertOpenStore( (IntPtr)Interop.Crypt32.CERT_STORE_PROV_MEMORY, Interop.Crypt32.X509_ASN_ENCODING | Interop.Crypt32.PKCS_7_ASN_ENCODING, IntPtr.Zero, 0, IntPtr.Zero); if (safeCertStoreHandle == null || safeCertStoreHandle.IsInvalid) { throw new CryptographicException(Marshal.GetLastWin32Error()); } Interop.CryptUI.CRYPTUI_SELECTCERTIFICATE_STRUCTW csc = default; // Older versions of CRYPTUI do not check the size correctly, // so always force it to the oldest version of the structure. #if NET7_0_OR_GREATER // Declare a local for Native to enable us to get the managed byte offset // without having a null check cause a failure. Interop.CryptUI.CRYPTUI_SELECTCERTIFICATE_STRUCTW.Marshaller.Native native; Unsafe.SkipInit(out native); csc.dwSize = (uint)Unsafe.ByteOffset(ref Unsafe.As <Interop.CryptUI.CRYPTUI_SELECTCERTIFICATE_STRUCTW.Marshaller.Native, byte>(ref native), ref Unsafe.As <IntPtr, byte>(ref native.hSelectedCertStore)); #else csc.dwSize = (uint)Marshal.OffsetOf(typeof(Interop.CryptUI.CRYPTUI_SELECTCERTIFICATE_STRUCTW), "hSelectedCertStore"); #endif csc.hwndParent = hwndParent; csc.dwFlags = (uint)selectionFlags; csc.szTitle = title; csc.dwDontUseColumn = 0; csc.szDisplayString = message; csc.pFilterCallback = IntPtr.Zero; csc.pDisplayCallback = IntPtr.Zero; csc.pvCallbackData = IntPtr.Zero; csc.cDisplayStores = 1; IntPtr hSourceCertStore = safeSourceStoreHandle.DangerousGetHandle(); csc.rghDisplayStores = new IntPtr(&hSourceCertStore); csc.cStores = 0; csc.rghStores = IntPtr.Zero; csc.cPropSheetPages = 0; csc.rgPropSheetPages = IntPtr.Zero; csc.hSelectedCertStore = safeCertStoreHandle.DangerousGetHandle(); SafeCertContextHandle safeCertContextHandle = Interop.CryptUI.CryptUIDlgSelectCertificateW(ref csc); if (safeCertContextHandle != null && !safeCertContextHandle.IsInvalid) { // Single select, so add it to our hCertStore SafeCertContextHandle ppStoreContext = SafeCertContextHandle.InvalidHandle; if (!Interop.Crypt32.CertAddCertificateLinkToStore(safeCertStoreHandle, safeCertContextHandle, Interop.Crypt32.CERT_STORE_ADD_ALWAYS, ppStoreContext)) { dwErrorCode = Marshal.GetLastWin32Error(); } } if (dwErrorCode != ERROR_SUCCESS) { throw new CryptographicException(dwErrorCode); } return(safeCertStoreHandle); }
// Returns &inputBuffer[inputLength] if the input buffer is valid. /// <summary> /// Given an input buffer <paramref name="pInputBuffer"/> of byte length <paramref name="inputLength"/>, /// returns a pointer to where the first invalid data appears in <paramref name="pInputBuffer"/>. /// </summary> /// <remarks> /// Returns a pointer to the end of <paramref name="pInputBuffer"/> if the buffer is well-formed. /// </remarks> public static byte *GetPointerToFirstInvalidByte(byte *pInputBuffer, int inputLength, out int utf16CodeUnitCountAdjustment, out int scalarCountAdjustment) { Debug.Assert(inputLength >= 0, "Input length must not be negative."); Debug.Assert(pInputBuffer != null || inputLength == 0, "Input length must be zero if input buffer pointer is null."); // First, try to drain off as many ASCII bytes as we can from the beginning. { nuint numAsciiBytesCounted = ASCIIUtility.GetIndexOfFirstNonAsciiByte(pInputBuffer, (uint)inputLength); pInputBuffer += numAsciiBytesCounted; // Quick check - did we just end up consuming the entire input buffer? // If so, short-circuit the remainder of the method. inputLength -= (int)numAsciiBytesCounted; if (inputLength == 0) { utf16CodeUnitCountAdjustment = 0; scalarCountAdjustment = 0; return(pInputBuffer); } } #if DEBUG // Keep these around for final validation at the end of the method. byte *pOriginalInputBuffer = pInputBuffer; int originalInputLength = inputLength; #endif // Enregistered locals that we'll eventually out to our caller. int tempUtf16CodeUnitCountAdjustment = 0; int tempScalarCountAdjustment = 0; if (inputLength < sizeof(uint)) { goto ProcessInputOfLessThanDWordSize; } byte *pFinalPosWhereCanReadDWordFromInputBuffer = pInputBuffer + (uint)inputLength - sizeof(uint); // Begin the main loop. #if DEBUG byte *pLastBufferPosProcessed = null; // used for invariant checking in debug builds #endif while (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) { // Read 32 bits at a time. This is enough to hold any possible UTF8-encoded scalar. uint thisDWord = Unsafe.ReadUnaligned <uint>(pInputBuffer); AfterReadDWord: #if DEBUG Debug.Assert(pLastBufferPosProcessed < pInputBuffer, "Algorithm should've made forward progress since last read."); pLastBufferPosProcessed = pInputBuffer; #endif // First, check for the common case of all-ASCII bytes. if (ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)) { // We read an all-ASCII sequence. pInputBuffer += sizeof(uint); // If we saw a sequence of all ASCII, there's a good chance a significant amount of following data is also ASCII. // Below is basically unrolled loops with poor man's vectorization. // Below check is "can I read at least five DWORDs from the input stream?" // n.b. Since we incremented pInputBuffer above the below subtraction may result in a negative value, // hence using nint instead of nuint. if ((nint)(void *)Unsafe.ByteOffset(ref *pInputBuffer, ref *pFinalPosWhereCanReadDWordFromInputBuffer) >= 4 * sizeof(uint)) { // We want reads in the inner loop to be aligned. So let's perform a quick // ASCII check of the next 32 bits (4 bytes) now, and if that succeeds bump // the read pointer up to the next aligned address. thisDWord = Unsafe.ReadUnaligned <uint>(pInputBuffer); if (!ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)) { goto AfterReadDWordSkipAllBytesAsciiCheck; } pInputBuffer = (byte *)((nuint)(pInputBuffer + 4) & ~(nuint)3); // At this point, the input buffer offset points to an aligned DWORD. We also know that there's // enough room to read at least four DWORDs from the buffer. (Heed the comment a few lines above: // the original 'if' check confirmed that there were 5 DWORDs before the alignment check, and // the alignment check consumes at most a single DWORD.) byte *pInputBufferFinalPosAtWhichCanSafelyLoop = pFinalPosWhereCanReadDWordFromInputBuffer - 3 * sizeof(uint); // can safely read 4 DWORDs here uint mask; do { if (Sse2.IsSupported && Bmi1.IsSupported) { // pInputBuffer is 32-bit aligned but not necessary 128-bit aligned, so we're // going to perform an unaligned load. We don't necessarily care about aligning // this because we pessimistically assume we'll encounter non-ASCII data at some // point in the not-too-distant future (otherwise we would've stayed entirely // within the all-ASCII vectorized code at the entry to this method). mask = (uint)Sse2.MoveMask(Sse2.LoadVector128((byte *)pInputBuffer)); if (mask != 0) { goto Sse2LoopTerminatedEarlyDueToNonAsciiData; } } else { if (!ASCIIUtility.AllBytesInUInt32AreAscii(((uint *)pInputBuffer)[0] | ((uint *)pInputBuffer)[1])) { goto LoopTerminatedEarlyDueToNonAsciiDataInFirstPair; } if (!ASCIIUtility.AllBytesInUInt32AreAscii(((uint *)pInputBuffer)[2] | ((uint *)pInputBuffer)[3])) { goto LoopTerminatedEarlyDueToNonAsciiDataInSecondPair; } } pInputBuffer += 4 * sizeof(uint); // consumed 4 DWORDs } while (pInputBuffer <= pInputBufferFinalPosAtWhichCanSafelyLoop); continue; // need to perform a bounds check because we might be running out of data Sse2LoopTerminatedEarlyDueToNonAsciiData: Debug.Assert(BitConverter.IsLittleEndian); Debug.Assert(Sse2.IsSupported); Debug.Assert(Bmi1.IsSupported); // The 'mask' value will have a 0 bit for each ASCII byte we saw and a 1 bit // for each non-ASCII byte we saw. We can count the number of ASCII bytes, // bump our input counter by that amount, and resume processing from the // "the first byte is no longer ASCII" portion of the main loop. Debug.Assert(mask != 0); pInputBuffer += Bmi1.TrailingZeroCount(mask); if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) { goto ProcessRemainingBytesSlow; } thisDWord = Unsafe.ReadUnaligned <uint>(pInputBuffer); // no longer guaranteed to be aligned goto BeforeProcessTwoByteSequence; LoopTerminatedEarlyDueToNonAsciiDataInSecondPair: pInputBuffer += 2 * sizeof(uint); // consumed 2 DWORDs LoopTerminatedEarlyDueToNonAsciiDataInFirstPair: // We know that there's *at least* two DWORDs of data remaining in the buffer. // We also know that one of them (or both of them) contains non-ASCII data somewhere. // Let's perform a quick check here to bypass the logic at the beginning of the main loop. thisDWord = *(uint *)pInputBuffer; // still aligned here if (ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)) { pInputBuffer += sizeof(uint); // consumed 1 more DWORD thisDWord = *(uint *)pInputBuffer; // still aligned here } goto AfterReadDWordSkipAllBytesAsciiCheck; } continue; // not enough data remaining to unroll loop - go back to beginning with bounds checks } AfterReadDWordSkipAllBytesAsciiCheck: Debug.Assert(!ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)); // this should have been handled earlier // Next, try stripping off ASCII bytes one at a time. // We only handle up to three ASCII bytes here since we handled the four ASCII byte case above. { uint numLeadingAsciiBytes = ASCIIUtility.CountNumberOfLeadingAsciiBytesFromUInt32WithSomeNonAsciiData(thisDWord); pInputBuffer += numLeadingAsciiBytes; if (pFinalPosWhereCanReadDWordFromInputBuffer < pInputBuffer) { goto ProcessRemainingBytesSlow; // Input buffer doesn't contain enough data to read a DWORD } else { // The input buffer at the current offset contains a non-ASCII byte. // Read an entire DWORD and fall through to multi-byte consumption logic. thisDWord = Unsafe.ReadUnaligned <uint>(pInputBuffer); } } BeforeProcessTwoByteSequence: // At this point, we suspect we're working with a multi-byte code unit sequence, // but we haven't yet validated it for well-formedness. // The masks and comparands are derived from the Unicode Standard, Table 3-6. // Additionally, we need to check for valid byte sequences per Table 3-7. // Check the 2-byte case. thisDWord -= (BitConverter.IsLittleEndian) ? 0x0000_80C0u : 0xC080_0000u; if ((thisDWord & (BitConverter.IsLittleEndian ? 0x0000_C0E0u : 0xE0C0_0000u)) == 0) { // Per Table 3-7, valid sequences are: // [ C2..DF ] [ 80..BF ] // // Due to our modification of 'thisDWord' above, this becomes: // [ 02..1F ] [ 00..3F ] // // We've already checked that the leading byte was originally in the range [ C0..DF ] // and that the trailing byte was originally in the range [ 80..BF ], so now we only need // to check that the modified leading byte is >= [ 02 ]. if ((BitConverter.IsLittleEndian && (byte)thisDWord < 0x02u) || (!BitConverter.IsLittleEndian && thisDWord < 0x0200_0000u)) { goto Error; // overlong form - leading byte was [ C0 ] or [ C1 ] } ProcessTwoByteSequenceSkipOverlongFormCheck: // Optimization: If this is a two-byte-per-character language like Cyrillic or Hebrew, // there's a good chance that if we see one two-byte run then there's another two-byte // run immediately after. Let's check that now. // On little-endian platforms, we can check for the two-byte UTF8 mask *and* validate that // the value isn't overlong using a single comparison. On big-endian platforms, we'll need // to validate the mask and validate that the sequence isn't overlong as two separate comparisons. if ((BitConverter.IsLittleEndian && UInt32EndsWithValidUtf8TwoByteSequenceLittleEndian(thisDWord)) || (!BitConverter.IsLittleEndian && (UInt32EndsWithUtf8TwoByteMask(thisDWord) && !UInt32EndsWithOverlongUtf8TwoByteSequence(thisDWord)))) { // We have two runs of two bytes each. pInputBuffer += 4; tempUtf16CodeUnitCountAdjustment -= 2; // 4 UTF-8 code units -> 2 UTF-16 code units (and 2 scalars) if (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) { // Optimization: If we read a long run of two-byte sequences, the next sequence is probably // also two bytes. Check for that first before going back to the beginning of the loop. thisDWord = Unsafe.ReadUnaligned <uint>(pInputBuffer); if (BitConverter.IsLittleEndian) { if (UInt32BeginsWithValidUtf8TwoByteSequenceLittleEndian(thisDWord)) { // The next sequence is a valid two-byte sequence. goto ProcessTwoByteSequenceSkipOverlongFormCheck; } } else { if (UInt32BeginsWithUtf8TwoByteMask(thisDWord)) { if (UInt32BeginsWithOverlongUtf8TwoByteSequence(thisDWord)) { goto Error; // The next sequence purports to be a 2-byte sequence but is overlong. } goto ProcessTwoByteSequenceSkipOverlongFormCheck; } } // If we reached this point, the next sequence is something other than a valid // two-byte sequence, so go back to the beginning of the loop. goto AfterReadDWord; } else { goto ProcessRemainingBytesSlow; // Running out of data - go down slow path } } // The buffer contains a 2-byte sequence followed by 2 bytes that aren't a 2-byte sequence. // Unlikely that a 3-byte sequence would follow a 2-byte sequence, so perhaps remaining // bytes are ASCII? tempUtf16CodeUnitCountAdjustment--; // 2-byte sequence + (some number of ASCII bytes) -> 1 UTF-16 code units (and 1 scalar) [+ trailing] if (UInt32ThirdByteIsAscii(thisDWord)) { if (UInt32FourthByteIsAscii(thisDWord)) { pInputBuffer += 4; } else { pInputBuffer += 3; // A two-byte sequence followed by an ASCII byte followed by a non-ASCII byte. // Read in the next DWORD and jump directly to the start of the multi-byte processing block. if (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) { thisDWord = Unsafe.ReadUnaligned <uint>(pInputBuffer); goto BeforeProcessTwoByteSequence; } } } else { pInputBuffer += 2; } continue; } // Check the 3-byte case. // We need to restore the C0 leading byte we stripped out earlier, then we can strip out the expected E0 byte. thisDWord -= (BitConverter.IsLittleEndian) ? (0x0080_00E0u - 0x0000_00C0u) : (0xE000_8000u - 0xC000_0000u); if ((thisDWord & (BitConverter.IsLittleEndian ? 0x00C0_C0F0u : 0xF0C0_C000u)) == 0) { ProcessThreeByteSequenceWithCheck: // We assume the caller has confirmed that the bit pattern is representative of a three-byte // sequence, but it may still be overlong or surrogate. We need to check for these possibilities. // // Per Table 3-7, valid sequences are: // [ E0 ] [ A0..BF ] [ 80..BF ] // [ E1..EC ] [ 80..BF ] [ 80..BF ] // [ ED ] [ 80..9F ] [ 80..BF ] // [ EE..EF ] [ 80..BF ] [ 80..BF ] // // Big-endian examples of using the above validation table: // E0A0 = 1110 0000 1010 0000 => invalid (overlong ) patterns are 1110 0000 100# #### // ED9F = 1110 1101 1001 1111 => invalid (surrogate) patterns are 1110 1101 101# #### // If using the bitmask ......................................... 0000 1111 0010 0000 (=0F20), // Then invalid (overlong) patterns match the comparand ......... 0000 0000 0000 0000 (=0000), // And invalid (surrogate) patterns match the comparand ......... 0000 1101 0010 0000 (=0D20). // // It's ok if the caller has manipulated 'thisDWord' (e.g., by subtracting 0xE0 or 0x80) // as long as they haven't touched the bits we're about to use in our mask checking below. if (BitConverter.IsLittleEndian) { // The "overlong or surrogate" check can be implemented using a single jump, but there's // some overhead to moving the bits into the correct locations in order to perform the // correct comparison, and in practice the processor's branch prediction capability is // good enough that we shouldn't bother. So we'll use two jumps instead. // Can't extract this check into its own helper method because JITter produces suboptimal // assembly, even with aggressive inlining. // Code below becomes 5 instructions: test, jz, lea, test, jz if (((thisDWord & 0x0000_200Fu) == 0) || (((thisDWord - 0x0000_200Du) & 0x0000_200Fu) == 0)) { goto Error; // overlong or surrogate } } else { if (((thisDWord & 0x0F20_0000u) == 0) || (((thisDWord - 0x0D20_0000u) & 0x0F20_0000u) == 0)) { goto Error; // overlong or surrogate } } ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks: // Occasionally one-off ASCII characters like spaces, periods, or newlines will make their way // in to the text. If this happens strip it off now before seeing if the next character // consists of three code units. // Branchless: consume a 3-byte UTF-8 sequence and optionally an extra ASCII byte hanging off the end nint asciiAdjustment; if (BitConverter.IsLittleEndian) { asciiAdjustment = (int)thisDWord >> 31; // smear most significant bit across entire value } else { asciiAdjustment = (nint)(sbyte)thisDWord >> 7; // smear most significant bit of least significant byte across entire value } // asciiAdjustment = 0 if fourth byte is ASCII; -1 otherwise // Please *DO NOT* reorder the below two lines. It provides extra defense in depth in case this method // is ever changed such that pInputBuffer becomes a 'ref byte' instead of a simple 'byte*'. It's valid // to add 4 before backing up since we already checked previously that the input buffer contains at // least a DWORD's worth of data, so we're not going to run past the end of the buffer where the GC can // no longer track the reference. However, we can't back up before adding 4, since we might back up to // before the start of the buffer, and the GC isn't guaranteed to be able to track this. pInputBuffer += 4; // optimistically, assume consumed a 3-byte UTF-8 sequence plus an extra ASCII byte pInputBuffer += asciiAdjustment; // back up if we didn't actually consume an ASCII byte tempUtf16CodeUnitCountAdjustment -= 2; // 3 (or 4) UTF-8 bytes -> 1 (or 2) UTF-16 code unit (and 1 [or 2] scalar) SuccessfullyProcessedThreeByteSequence: if (IntPtr.Size >= 8 && BitConverter.IsLittleEndian) { // x64 little-endian optimization: A three-byte character could indicate CJK text, // which makes it likely that the character following this one is also CJK. // We'll try to process several three-byte sequences at a time. // The check below is really "can we read 9 bytes from the input buffer?" since 'pFinalPos...' is already offset // n.b. The subtraction below could result in a negative value (since we advanced pInputBuffer above), so // use nint instead of nuint. if ((nint)(pFinalPosWhereCanReadDWordFromInputBuffer - pInputBuffer) >= 5) { ulong thisQWord = Unsafe.ReadUnaligned <ulong>(pInputBuffer); // Stage the next 32 bits into 'thisDWord' so that it's ready for us in case we need to jump backward // to a previous location in the loop. This offers defense against reading main memory again (which may // have been modified and could lead to a race condition). thisDWord = (uint)thisQWord; // Is this three 3-byte sequences in a row? // thisQWord = [ 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz ] [ 10xxxxxx ] // ---- CHAR 3 ---- --------- CHAR 2 --------- --------- CHAR 1 --------- -CHAR 3- if ((thisQWord & 0xC0F0_C0C0_F0C0_C0F0ul) == 0x80E0_8080_E080_80E0ul && IsUtf8ContinuationByte(in pInputBuffer[8])) { // Saw a proper bitmask for three incoming 3-byte sequences, perform the // overlong and surrogate sequence checking now. // Check the first character. // If the first character is overlong or a surrogate, fail immediately. if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) { goto Error; } // Check the second character. // At this point, we now know the first three bytes represent a well-formed sequence. // If there's an error beyond here, we'll jump back to the "process three known good bytes" // logic. thisQWord >>= 24; if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) { goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; } // Check the third character (we already checked that it's followed by a continuation byte). thisQWord >>= 24; if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) { goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; } pInputBuffer += 9; tempUtf16CodeUnitCountAdjustment -= 6; // 9 UTF-8 bytes -> 3 UTF-16 code units (and 3 scalars) goto SuccessfullyProcessedThreeByteSequence; } // Is this two 3-byte sequences in a row? // thisQWord = [ ######## ######## | 10xxxxxx 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz ] // --------- CHAR 2 --------- --------- CHAR 1 --------- if ((thisQWord & 0xC0C0_F0C0_C0F0ul) == 0x8080_E080_80E0ul) { // Saw a proper bitmask for two incoming 3-byte sequences, perform the // overlong and surrogate sequence checking now. // Check the first character. // If the first character is overlong or a surrogate, fail immediately. if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) { goto Error; } // Check the second character. // At this point, we now know the first three bytes represent a well-formed sequence. // If there's an error beyond here, we'll jump back to the "process three known good bytes" // logic. thisQWord >>= 24; if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) { goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; } pInputBuffer += 6; tempUtf16CodeUnitCountAdjustment -= 4; // 6 UTF-8 bytes -> 2 UTF-16 code units (and 2 scalars) // The next byte in the sequence didn't have a 3-byte marker, so it's probably // an ASCII character. Jump back to the beginning of loop processing. continue; } if (UInt32BeginsWithUtf8ThreeByteMask(thisDWord)) { // A single three-byte sequence. goto ProcessThreeByteSequenceWithCheck; } else { // Not a three-byte sequence; perhaps ASCII? goto AfterReadDWord; } } } if (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) { thisDWord = Unsafe.ReadUnaligned <uint>(pInputBuffer); // Optimization: A three-byte character could indicate CJK text, which makes it likely // that the character following this one is also CJK. We'll check for a three-byte sequence // marker now and jump directly to three-byte sequence processing if we see one, skipping // all of the logic at the beginning of the loop. if (UInt32BeginsWithUtf8ThreeByteMask(thisDWord)) { goto ProcessThreeByteSequenceWithCheck; // Found another [not yet validated] three-byte sequence; process } else { goto AfterReadDWord; // Probably ASCII punctuation or whitespace; go back to start of loop } } else { goto ProcessRemainingBytesSlow; // Running out of data } } // Assume the 4-byte case, but we need to validate. if (BitConverter.IsLittleEndian) { thisDWord &= 0xC0C0_FFFFu; // After the above modifications earlier in this method, we expect 'thisDWord' // to have the structure [ 10000000 00000000 00uuzzzz 00010uuu ]. We'll now // perform two checks to confirm this. The first will verify the // [ 10000000 00000000 00###### ######## ] structure by taking advantage of two's // complement representation to perform a single *signed* integer check. if ((int)thisDWord > unchecked ((int)0x8000_3FFF)) { goto Error; // didn't have three trailing bytes } // Now we want to confirm that 0x01 <= uuuuu (otherwise this is an overlong encoding) // and that uuuuu <= 0x10 (otherwise this is an out-of-range encoding). thisDWord = BitOperations.RotateRight(thisDWord, 8); // Now, thisDWord = [ 00010uuu 10000000 00000000 00uuzzzz ]. // The check is now a simple add / cmp / jcc combo. if (!UnicodeUtility.IsInRangeInclusive(thisDWord, 0x1080_0010u, 0x1480_000Fu)) { goto Error; // overlong or out-of-range } } else { thisDWord -= 0x80u; // After the above modifications earlier in this method, we expect 'thisDWord' // to have the structure [ 00010uuu 00uuzzzz 00yyyyyy 00xxxxxx ]. We'll now // perform two checks to confirm this. The first will verify the // [ ######## 00###### 00###### 00###### ] structure. if ((thisDWord & 0x00C0_C0C0u) != 0) { goto Error; // didn't have three trailing bytes } // Now we want to confirm that 0x01 <= uuuuu (otherwise this is an overlong encoding) // and that uuuuu <= 0x10 (otherwise this is an out-of-range encoding). // This is a simple range check. (We don't care about the low two bytes.) if (!UnicodeUtility.IsInRangeInclusive(thisDWord, 0x1010_0000u, 0x140F_FFFFu)) { goto Error; // overlong or out-of-range } } // Validation of 4-byte case complete. pInputBuffer += 4; tempUtf16CodeUnitCountAdjustment -= 2; // 4 UTF-8 bytes -> 2 UTF-16 code units tempScalarCountAdjustment--; // 2 UTF-16 code units -> 1 scalar continue; // go back to beginning of loop for processing } goto ProcessRemainingBytesSlow; ProcessInputOfLessThanDWordSize: Debug.Assert(inputLength < 4); nuint inputBufferRemainingBytes = (uint)inputLength; goto ProcessSmallBufferCommon; ProcessRemainingBytesSlow: inputBufferRemainingBytes = (nuint)(void *)Unsafe.ByteOffset(ref *pInputBuffer, ref *pFinalPosWhereCanReadDWordFromInputBuffer) + 4; ProcessSmallBufferCommon: Debug.Assert(inputBufferRemainingBytes < 4); while (inputBufferRemainingBytes > 0) { uint firstByte = pInputBuffer[0]; if ((byte)firstByte < 0x80u) { // 1-byte (ASCII) case pInputBuffer++; inputBufferRemainingBytes--; continue; } else if (inputBufferRemainingBytes >= 2) { uint secondByte = pInputBuffer[1]; // typed as 32-bit since we perform arithmetic (not just comparisons) on this value if ((byte)firstByte < 0xE0u) { // 2-byte case if ((byte)firstByte >= 0xC2u && IsLowByteUtf8ContinuationByte(secondByte)) { pInputBuffer += 2; tempUtf16CodeUnitCountAdjustment--; // 2 UTF-8 bytes -> 1 UTF-16 code unit (and 1 scalar) inputBufferRemainingBytes -= 2; continue; } } else if (inputBufferRemainingBytes >= 3) { if ((byte)firstByte < 0xF0u) { if ((byte)firstByte == 0xE0u) { if (!UnicodeUtility.IsInRangeInclusive(secondByte, 0xA0u, 0xBFu)) { goto Error; // overlong encoding } } else if ((byte)firstByte == 0xEDu) { if (!UnicodeUtility.IsInRangeInclusive(secondByte, 0x80u, 0x9Fu)) { goto Error; // would be a UTF-16 surrogate code point } } else { if (!IsLowByteUtf8ContinuationByte(secondByte)) { goto Error; // first trailing byte doesn't have proper continuation marker } } if (IsUtf8ContinuationByte(in pInputBuffer[2])) { pInputBuffer += 3; tempUtf16CodeUnitCountAdjustment -= 2; // 3 UTF-8 bytes -> 2 UTF-16 code units (and 2 scalars) inputBufferRemainingBytes -= 3; continue; } } } } // Error - no match. goto Error; } // If we reached this point, we're out of data, and we saw no bad UTF8 sequence. #if DEBUG // Quick check that for the success case we're going to fulfill our contract of returning &inputBuffer[inputLength]. Debug.Assert(pOriginalInputBuffer + originalInputLength == pInputBuffer, "About to return an unexpected value."); #endif Error: // Report back to our caller how far we got before seeing invalid data. // (Also used for normal termination when falling out of the loop above.) utf16CodeUnitCountAdjustment = tempUtf16CodeUnitCountAdjustment; scalarCountAdjustment = tempScalarCountAdjustment; return(pInputBuffer); }
public Span <LbrEntry32> Entries(int totalSize) { IntPtr entriesOffset = Unsafe.ByteOffset(ref Unsafe.As <LbrTraceEventData32, byte>(ref this), ref Unsafe.As <LbrEntry32, byte>(ref _entries)); return(MemoryMarshal.CreateSpan(ref _entries, (totalSize - (int)entriesOffset) / sizeof(LbrEntry32))); }