From 3f3db9550ee2a66b5949fa94bd51987814f96f87 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 8 Mar 2023 14:18:53 -0800 Subject: [PATCH] Adding System.Runtime.CompilerServices.Unsafe.BitCast (#82917) * Adding System.Runtime.CompilerServices.Unsafe.BitCast * Adding some basic intrinsic recognition for Unsafe.BitCast * Use ClassLayout::AreCompatible as part of Unsafe.BitCast * Fixup BitConverter to use Unsafe.BitCast * Fixup Enum to use Unsafe.BitCast * Ensure BitCast resolves the right generic type for toTypeHnd * Use Unsafe.BitCast in places using the `*(TTo*)&tfrom` pattern * Don't use BitCast in places the generic constraints disallows it * Missing semicolon * Don't use Unsafe.BitCast where it introduces additional generic instantiations * Don't regress the files that are used for both netstandard and netcoreapp * Responding to PR feedback * Fix a typo in the bitcast tests --- src/coreclr/jit/fgbasic.cpp | 1 + src/coreclr/jit/importercalls.cpp | 143 +++++++++++++++++++++ src/coreclr/jit/namedintrinsiclist.h | 1 + .../Common/System/Data/ProviderBase/DbBuffer.cs | 4 +- .../src/System/Data/ProviderBase/DbBuffer.cs | 4 +- .../ParsersAndFormatters/Parser/ValidateParser.cs | 18 +-- .../src/System/BitConverter.cs | 16 +-- .../src/System/Decimal.DecCalc.cs | 4 +- .../src/System/Runtime/CompilerServices/Unsafe.cs | 18 +++ .../src/System/Threading/Volatile.cs | 4 +- .../src/System/Xml/XmlConverter.cs | 6 +- .../Reflection/Internal/Utilities/BlobUtilities.cs | 8 ++ .../tests/UnsafeTests.cs | 71 ++++++++++ src/libraries/System.Runtime/ref/System.Runtime.cs | 1 + 14 files changed, 267 insertions(+), 32 deletions(-) diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index eac6859..360a62c 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -1485,6 +1485,7 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed #endif // FEATURE_HW_INTRINSICS case NI_SRCS_UNSAFE_As: case NI_SRCS_UNSAFE_AsRef: + case NI_SRCS_UNSAFE_BitCast: case NI_SRCS_UNSAFE_SkipInit: { // TODO-CQ: These are no-ops in that they never produce any IR diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 58e6192..a46d251 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -4027,6 +4027,145 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic, return impPopStack().val; } + case NI_SRCS_UNSAFE_BitCast: + { + assert(sig->sigInst.methInstCount == 2); + + CORINFO_CLASS_HANDLE fromTypeHnd = sig->sigInst.methInst[0]; + CORINFO_CLASS_HANDLE toTypeHnd = sig->sigInst.methInst[1]; + + if (fromTypeHnd == toTypeHnd) + { + // Handle the easy case of matching type handles, such as `int` to `int` + return impPopStack().val; + } + + unsigned fromSize = info.compCompHnd->getClassSize(fromTypeHnd); + unsigned toSize = info.compCompHnd->getClassSize(toTypeHnd); + + // Runtime requires all types to be at least 1-byte + assert((fromSize != 0) && (toSize != 0)); + + if (fromSize != toSize) + { + // Fallback to the software implementation to throw when sizes don't match + return nullptr; + } + + CorInfoType fromJitType = info.compCompHnd->asCorInfoType(fromTypeHnd); + var_types fromType = JITtype2varType(fromJitType); + + CorInfoType toJitType = info.compCompHnd->asCorInfoType(toTypeHnd); + var_types toType = JITtype2varType(toJitType); + + bool involvesStructType = false; + + if (fromType == TYP_STRUCT) + { + involvesStructType = true; + + if (toType == TYP_STRUCT) + { + ClassLayout* fromLayout = typGetObjLayout(fromTypeHnd); + ClassLayout* toLayout = typGetObjLayout(toTypeHnd); + + if (ClassLayout::AreCompatible(fromLayout, toLayout)) + { + // Handle compatible struct layouts where we can simply return op1 + return impPopStack().val; + } + } + } + else if (toType == TYP_STRUCT) + { + involvesStructType = true; + } + + if (involvesStructType) + { + // TODO-CQ: Handle this by getting the address of `op1` and then dereferencing + // that as TTo, much as `Unsafe.As(ref op1)` would work. + return nullptr; + } + + if (varTypeIsFloating(fromType)) + { + // Handle bitcasting from floating to same sized integral, such as `float` to `int` + assert(varTypeIsIntegral(toType)); + +#if !TARGET_64BIT + if ((fromType == TYP_DOUBLE) && !impStackTop().val->IsCnsFltOrDbl()) + { + // TODO-Cleanup: We should support this on 32-bit but it requires decomposition work + return nullptr; + } +#endif // !TARGET_64BIT + + GenTree* op1 = impPopStack().val; + + if (op1->IsCnsFltOrDbl()) + { + if (fromType == TYP_DOUBLE) + { + double f64Cns = static_cast(op1->AsDblCon()->DconValue()); + return gtNewLconNode(static_cast(BitOperations::DoubleToUInt64Bits(f64Cns))); + } + else + { + assert(fromType == TYP_FLOAT); + float f32Cns = static_cast(op1->AsDblCon()->DconValue()); + return gtNewIconNode(static_cast(BitOperations::SingleToUInt32Bits(f32Cns))); + } + } + else + { + toType = varTypeToSigned(toType); + op1 = impImplicitR4orR8Cast(op1, fromType); + return gtNewBitCastNode(toType, op1); + } + break; + } + + if (varTypeIsFloating(toType)) + { + // Handle bitcasting from integral to same sized floating, such as `int` to `float` + assert(varTypeIsIntegral(fromType)); + +#if !TARGET_64BIT + if ((toType == TYP_DOUBLE) && !impStackTop().val->IsIntegralConst()) + { + // TODO-Cleanup: We should support this on 32-bit but it requires decomposition work + return nullptr; + } +#endif // !TARGET_64BIT + + GenTree* op1 = impPopStack().val; + + if (op1->IsIntegralConst()) + { + if (toType == TYP_DOUBLE) + { + uint64_t u64Cns = static_cast(op1->AsIntConCommon()->LngValue()); + return gtNewDconNode(BitOperations::UInt64BitsToDouble(u64Cns), TYP_DOUBLE); + } + else + { + assert(toType == TYP_FLOAT); + + uint32_t u32Cns = static_cast(op1->AsIntConCommon()->IconValue()); + return gtNewDconNode(BitOperations::UInt32BitsToSingle(u32Cns), TYP_FLOAT); + } + } + else + { + return gtNewBitCastNode(toType, op1); + } + } + + // Handle bitcasting for same sized integrals, such as `int` to `uint` + return impPopStack().val; + } + case NI_SRCS_UNSAFE_ByteOffset: { assert(sig->sigInst.methInstCount == 1); @@ -8227,6 +8366,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_SRCS_UNSAFE_AsRef; } + else if (strcmp(methodName, "BitCast") == 0) + { + result = NI_SRCS_UNSAFE_BitCast; + } else if (strcmp(methodName, "ByteOffset") == 0) { result = NI_SRCS_UNSAFE_ByteOffset; diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 4f922bc..b80a1b3 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -179,6 +179,7 @@ enum NamedIntrinsic : unsigned short NI_SRCS_UNSAFE_As, NI_SRCS_UNSAFE_AsPointer, NI_SRCS_UNSAFE_AsRef, + NI_SRCS_UNSAFE_BitCast, NI_SRCS_UNSAFE_ByteOffset, NI_SRCS_UNSAFE_Copy, NI_SRCS_UNSAFE_CopyBlock, diff --git a/src/libraries/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbBuffer.cs b/src/libraries/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbBuffer.cs index 4c9bf45..9ef1e0f 100644 --- a/src/libraries/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbBuffer.cs +++ b/src/libraries/System.Data.Odbc/src/Common/System/Data/ProviderBase/DbBuffer.cs @@ -365,7 +365,7 @@ namespace System.Data.ProviderBase internal unsafe float ReadSingle(int offset) { int value = ReadInt32(offset); - return *(float*)&value; + return BitConverter.Int32BitsToSingle(value); } protected override bool ReleaseHandle() @@ -636,7 +636,7 @@ namespace System.Data.ProviderBase internal unsafe void WriteSingle(int offset, float value) { - WriteInt32(offset, *(int*)&value); + WriteInt32(offset, BitConverter.SingleToInt32Bits(value)); } internal void ZeroMemory() diff --git a/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbBuffer.cs b/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbBuffer.cs index dac3f18..ef399ef 100644 --- a/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbBuffer.cs +++ b/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/DbBuffer.cs @@ -344,7 +344,7 @@ namespace System.Data.ProviderBase internal unsafe float ReadSingle(int offset) { int value = ReadInt32(offset); - return *(float*)&value; + return BitConverter.Int32BitsToSingle(value); } protected override bool ReleaseHandle() @@ -615,7 +615,7 @@ namespace System.Data.ProviderBase internal unsafe void WriteSingle(int offset, float value) { - WriteInt32(offset, *(int*)&value); + WriteInt32(offset, BitConverter.SingleToInt32Bits(value)); } internal Guid ReadGuid(int offset) diff --git a/src/libraries/System.Memory/tests/ParsersAndFormatters/Parser/ValidateParser.cs b/src/libraries/System.Memory/tests/ParsersAndFormatters/Parser/ValidateParser.cs index 6ecd116..aae401f 100644 --- a/src/libraries/System.Memory/tests/ParsersAndFormatters/Parser/ValidateParser.cs +++ b/src/libraries/System.Memory/tests/ParsersAndFormatters/Parser/ValidateParser.cs @@ -106,13 +106,10 @@ namespace System.Buffers.Text.Tests double expectedDouble = (double)(object)expected; double actualDouble = (double)(object)actual; - unsafe - { - if (*((ulong*)&expectedDouble) != *((ulong*)&actualDouble)) - return false; + if (BitConverter.DoubleToUInt64Bits(expectedDouble) != BitConverter.DoubleToUInt64Bits(actualDouble)) + return false; - return true; - } + return true; } // Parsed floating points are constructed, not computed. Thus, we can do the exact compare. @@ -121,13 +118,10 @@ namespace System.Buffers.Text.Tests float expectedSingle = (float)(object)expected; float actualSingle = (float)(object)actual; - unsafe - { - if (*((uint*)&expectedSingle) != *((uint*)&actualSingle)) - return false; + if (BitConverter.SingleToUInt32Bits(expectedSingle) != BitConverter.SingleToUInt32Bits(actualSingle)) + return false; - return true; - } + return true; } return expected.Equals(actual); diff --git a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs index 18938c0..f22f7cc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs @@ -764,7 +764,7 @@ namespace System /// The number to convert. /// A 64-bit signed integer whose bits are identical to . [Intrinsic] - public static unsafe long DoubleToInt64Bits(double value) => *((long*)&value); + public static unsafe long DoubleToInt64Bits(double value) => Unsafe.BitCast(value); /// /// Converts the specified 64-bit signed integer to a double-precision floating point number. @@ -772,7 +772,7 @@ namespace System /// The number to convert. /// A double-precision floating point number whose bits are identical to . [Intrinsic] - public static unsafe double Int64BitsToDouble(long value) => *((double*)&value); + public static unsafe double Int64BitsToDouble(long value) => Unsafe.BitCast(value); /// /// Converts the specified single-precision floating point number to a 32-bit signed integer. @@ -780,7 +780,7 @@ namespace System /// The number to convert. /// A 32-bit signed integer whose bits are identical to . [Intrinsic] - public static unsafe int SingleToInt32Bits(float value) => *((int*)&value); + public static unsafe int SingleToInt32Bits(float value) => Unsafe.BitCast(value); /// /// Converts the specified 32-bit signed integer to a single-precision floating point number. @@ -788,7 +788,7 @@ namespace System /// The number to convert. /// A single-precision floating point number whose bits are identical to . [Intrinsic] - public static unsafe float Int32BitsToSingle(int value) => *((float*)&value); + public static unsafe float Int32BitsToSingle(int value) => Unsafe.BitCast(value); /// /// Converts the specified half-precision floating point number to a 16-bit signed integer. @@ -813,7 +813,7 @@ namespace System /// A 64-bit unsigned integer whose bits are identical to . [CLSCompliant(false)] [Intrinsic] - public static unsafe ulong DoubleToUInt64Bits(double value) => *((ulong*)&value); + public static unsafe ulong DoubleToUInt64Bits(double value) => Unsafe.BitCast(value); /// /// Converts the specified 64-bit unsigned integer to a double-precision floating point number. @@ -822,7 +822,7 @@ namespace System /// A double-precision floating point number whose bits are identical to . [CLSCompliant(false)] [Intrinsic] - public static unsafe double UInt64BitsToDouble(ulong value) => *((double*)&value); + public static unsafe double UInt64BitsToDouble(ulong value) => Unsafe.BitCast(value); /// /// Converts the specified single-precision floating point number to a 32-bit unsigned integer. @@ -831,7 +831,7 @@ namespace System /// A 32-bit unsigned integer whose bits are identical to . [CLSCompliant(false)] [Intrinsic] - public static unsafe uint SingleToUInt32Bits(float value) => *((uint*)&value); + public static unsafe uint SingleToUInt32Bits(float value) => Unsafe.BitCast(value); /// /// Converts the specified 32-bit unsigned integer to a single-precision floating point number. @@ -840,7 +840,7 @@ namespace System /// A single-precision floating point number whose bits are identical to . [CLSCompliant(false)] [Intrinsic] - public static unsafe float UInt32BitsToSingle(uint value) => *((float*)&value); + public static unsafe float UInt32BitsToSingle(uint value) => Unsafe.BitCast(value); /// /// Converts the specified half-precision floating point number to a 16-bit unsigned integer. diff --git a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs index 81c1937..74e2a0d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Decimal.DecCalc.cs @@ -160,7 +160,7 @@ namespace System // ULONG sign:1; // } SNGSTRUCT; - return (byte)(*(uint*)&f >> 23); + return (byte)(BitConverter.SingleToUInt32Bits(f) >> 23); } private static unsafe uint GetExponent(double d) @@ -171,7 +171,7 @@ namespace System // DWORDLONG signexp:12; // } DBLSTRUCT; - return (uint)(*(ulong*)&d >> 52) & 0x7FFu; + return (uint)(BitConverter.DoubleToUInt64Bits(d) >> 52) & 0x7FFu; } private static ulong UInt32x32To64(uint a, uint b) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs index c4c3f66..ef7a6fc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs @@ -239,6 +239,24 @@ namespace System.Runtime.CompilerServices } /// + /// Reinterprets the given value of type as a value of type . + /// + /// The size of and are not the same. + [Intrinsic] + [NonVersionable] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TTo BitCast(TFrom source) + where TFrom : struct + where TTo : struct + { + if (sizeof(TFrom) != sizeof(TTo)) + { + ThrowHelper.ThrowNotSupportedException(); + } + return As(ref source); + } + + /// /// Copies a value of type T to the given location. /// [Intrinsic] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs index 80b2a21..aeba3db 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs @@ -47,13 +47,13 @@ namespace System.Threading public static double Read(ref double location) { long result = Read(ref Unsafe.As(ref location)); - return *(double*)&result; + return BitConverter.Int64BitsToDouble(result); } [Intrinsic] [NonVersionable] public static void Write(ref double location, double value) => - Write(ref Unsafe.As(ref location), *(long*)&value); + Write(ref Unsafe.As(ref location), BitConverter.DoubleToInt64Bits(value)); #endregion #region Int16 diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlConverter.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlConverter.cs index 71867bf..3420846 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlConverter.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlConverter.cs @@ -710,15 +710,13 @@ namespace System.Xml private static unsafe bool IsNegativeZero(float value) { // Simple equals function will report that -0 is equal to +0, so compare bits instead - float negativeZero = -0e0F; - return (*(int*)&value == *(int*)&negativeZero); + return BitConverter.SingleToUInt32Bits(value) == 0x8000_0000U; } private static unsafe bool IsNegativeZero(double value) { // Simple equals function will report that -0 is equal to +0, so compare bits instead - double negativeZero = -0e0; - return (*(long*)&value == *(long*)&negativeZero); + return BitConverter.DoubleToUInt64Bits(value) == 0x8000_0000_0000_0000UL; } private static int ToInfinity(bool isNegative, byte[] buffer, int offset) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs index 8f3a132..843e95e 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs @@ -20,12 +20,20 @@ namespace System.Reflection public static void WriteDouble(this byte[] buffer, int start, double value) { +#if NETCOREAPP + WriteUInt64(buffer, start, BitConverter.DoubleToUInt64Bits(value)); +#else WriteUInt64(buffer, start, *(ulong*)&value); +#endif } public static void WriteSingle(this byte[] buffer, int start, float value) { +#if NETCOREAPP + WriteUInt32(buffer, start, BitConverter.SingleToUInt32Bits(value)); +#else WriteUInt32(buffer, start, *(uint*)&value); +#endif } public static void WriteByte(this byte[] buffer, int start, byte value) diff --git a/src/libraries/System.Runtime.CompilerServices.Unsafe/tests/UnsafeTests.cs b/src/libraries/System.Runtime.CompilerServices.Unsafe/tests/UnsafeTests.cs index 4780b41..8d59648 100644 --- a/src/libraries/System.Runtime.CompilerServices.Unsafe/tests/UnsafeTests.cs +++ b/src/libraries/System.Runtime.CompilerServices.Unsafe/tests/UnsafeTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Numerics; using System.Runtime.InteropServices; using Xunit; @@ -1079,6 +1080,60 @@ namespace System.Runtime.CompilerServices Assert.Throws(() => Unsafe.NullRef() = 42); Assert.Throws(() => Unsafe.NullRef()); } + + [Fact] + public static unsafe void BitCast() + { + // Conversion between differently sized types should fail + + Assert.Throws(() => Unsafe.BitCast(5)); + Assert.Throws(() => Unsafe.BitCast(5)); + + // Conversion between floating-point and same sized integral should succeed + + Assert.Equal(0x8000_0000u, Unsafe.BitCast(-0.0f)); + Assert.Equal(float.PositiveInfinity, Unsafe.BitCast(0x7F80_0000u)); + + // Conversion between same sized integers should succeed + + Assert.Equal(int.MinValue, Unsafe.BitCast(0x8000_0000u)); + Assert.Equal(0x8000_0000u, Unsafe.BitCast(int.MinValue)); + + // Conversion from runtime SIMD type to a custom struct should succeed + + Vector4 vector4a = new Vector4(1.0f, 2.0f, 3.0f, 4.0f); + Single4 single4a = Unsafe.BitCast(vector4a); + + Assert.Equal(1.0f, single4a.X); + Assert.Equal(2.0f, single4a.Y); + Assert.Equal(3.0f, single4a.Z); + Assert.Equal(4.0f, single4a.W); + + // Conversion from custom struct to a runtime SIMD type should succeed + + Single4 single4b = new Single4 { X = -1.0f, Y = -2.0f, Z = -3.0f, W = -4.0f }; + Vector4 vector4b = Unsafe.BitCast(single4b); + + Assert.Equal(-1.0f, vector4b.X); + Assert.Equal(-2.0f, vector4b.Y); + Assert.Equal(-3.0f, vector4b.Z); + Assert.Equal(-4.0f, vector4b.W); + + // Runtime requires that all types be at least 1-byte, so empty to empty should succeed + + EmptyA empty1 = new EmptyA(); + EmptyB empty2 = Unsafe.BitCast(empty1); + + // ..., likewise, empty to/from byte should succeed + + byte empty3 = Unsafe.BitCast(empty1); + EmptyA empty4 = Unsafe.BitCast(1); + + // ..., however, empty to/from a larger type should fail + + Assert.Throws(() => Unsafe.BitCast(5)); + Assert.Throws(() => Unsafe.BitCast(empty1)); + } } [StructLayout(LayoutKind.Explicit)] @@ -1155,4 +1210,20 @@ namespace System.Runtime.CompilerServices public string String; public int Int32; } + + public struct Single4 + { + public float X; + public float Y; + public float Z; + public float W; + } + + public struct EmptyA + { + } + + public struct EmptyB + { + } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 2fe1072..53f5e04 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -12787,6 +12787,7 @@ namespace System.Runtime.CompilerServices [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("o")] public static T? As(object? o) where T : class? { throw null; } public static ref TTo As(ref TFrom source) { throw null; } + public static TTo BitCast(TFrom source) where TFrom : struct where TTo : struct { throw null; } public static System.IntPtr ByteOffset([System.Diagnostics.CodeAnalysis.AllowNull] ref T origin, [System.Diagnostics.CodeAnalysis.AllowNull] ref T target) { throw null; } [System.CLSCompliantAttribute(false)] public static void CopyBlock(ref byte destination, ref byte source, uint byteCount) { } -- 2.7.4