From 3721f70782971b4b951030f9d2e4d924a2ff462e Mon Sep 17 00:00:00 2001 From: Lucas Trzesniewski Date: Mon, 16 Dec 2024 21:53:40 +0100 Subject: [PATCH] Add Unsafe example from .NET 9 --- README.md | 4 +- .../InlineIL.Examples.csproj | 2 +- src/InlineIL.Examples/UnsafeNet9.cs | 823 ++++++++++++++++++ src/InlineIL.Tests/Examples/ExamplesTests.cs | 6 +- src/InlineIL.Tests/InlineIL.Tests.csproj | 2 +- 5 files changed, 833 insertions(+), 4 deletions(-) create mode 100644 src/InlineIL.Examples/UnsafeNet9.cs diff --git a/README.md b/README.md index 4c9f61e..f38caf1 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,9 @@ The `` element in `FodyWeavers.xml` accepts the following attributes ## Examples -- A [reimplementation of the `System.Runtime.CompilerServices.Unsafe` class](src/InlineIL.Examples/UnsafeNet6.cs) using InlineIL is provided as an example (compare to [the original IL code](https://github.com/dotnet/runtime/blob/release/6.0/src/libraries/System.Runtime.CompilerServices.Unsafe/src/System.Runtime.CompilerServices.Unsafe.il) in .NET 6). +- Two implementations of the `System.Runtime.CompilerServices.Unsafe` class are provided as examples: + - [.NET 6 version](src/InlineIL.Examples/UnsafeNet6.cs) (compare to [the original IL code](https://github.com/dotnet/runtime/blob/release/6.0/src/libraries/System.Runtime.CompilerServices.Unsafe/src/System.Runtime.CompilerServices.Unsafe.il)). + - [.NET 9 version](src/InlineIL.Examples/UnsafeNet9.cs), with newer API. - Unit tests can also serve as examples of API usage. See [verifiable](https://github.com/ltrzesniewski/InlineIL.Fody/tree/master/src/InlineIL.Tests.AssemblyToProcess) and [unverifiable](https://github.com/ltrzesniewski/InlineIL.Fody/tree/master/src/InlineIL.Tests.UnverifiableAssemblyToProcess) test cases. diff --git a/src/InlineIL.Examples/InlineIL.Examples.csproj b/src/InlineIL.Examples/InlineIL.Examples.csproj index aa096d1..e61a609 100644 --- a/src/InlineIL.Examples/InlineIL.Examples.csproj +++ b/src/InlineIL.Examples/InlineIL.Examples.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + net9.0 diff --git a/src/InlineIL.Examples/UnsafeNet9.cs b/src/InlineIL.Examples/UnsafeNet9.cs new file mode 100644 index 0000000..a34a4f8 --- /dev/null +++ b/src/InlineIL.Examples/UnsafeNet9.cs @@ -0,0 +1,823 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using static InlineIL.IL.Emit; + +#pragma warning disable CS8500 + +namespace InlineIL.Examples; + +/// +/// Contains generic, low-level functionality for manipulating pointers. +/// +[SuppressMessage("ReSharper", "UnusedType.Global")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] +[SuppressMessage("ReSharper", "EntityNameCapturedOnly.Global")] +public static unsafe class UnsafeNet9 +{ + // This is the InlineIL equivalent of System.Runtime.CompilerServices.Unsafe in .NET 9, based on the following file: + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs + // Last update: db32911d71075a81b50ad07bfcf10194212bda20 + + // The InlineIL code is based upon the commented IL code when present in the original method, which should be the same as in the .NET 6 version. + // Otherwise, the original method body is kept unchanged. + + /// + /// Returns a pointer to the given by-ref parameter. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void* AsPointer(ref T value) + where T : allows ref struct + { + // ldarg.0 + // conv.u + // ret + + Ldarg(nameof(value)); + Conv_U(); + return IL.ReturnPointer(); + } + + /// + /// Returns the size of an object of the given type parameter. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int SizeOf() + where T : allows ref struct + { + return sizeof(T); + } + + /// + /// Casts the given object to the specified type, performs no dynamic type checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [return: NotNullIfNotNull(nameof(o))] + public static T As(object? o) + where T : class? + { + // ldarg.0 + // ret + + Ldarg(nameof(o)); + return IL.Return(); + } + + /// + /// Reinterprets the given reference as a reference to a value of type . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TTo As(ref TFrom source) + where TFrom : allows ref struct + where TTo : allows ref struct + { + // ldarg.0 + // ret + + Ldarg(nameof(source)); + return ref IL.ReturnRef(); + } + + /// + /// Adds an element offset to the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Add(ref T source, int elementOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sizeof !!T + // conv.i + // mul + // add + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Conv_I(); + Mul(); + IL.Emit.Add(); + return ref IL.ReturnRef(); + } + + /// + /// Adds an element offset to the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Add(ref T source, IntPtr elementOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sizeof !!T + // mul + // add + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Mul(); + IL.Emit.Add(); + return ref IL.ReturnRef(); + } + + /// + /// Adds an element offset to the given pointer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void* Add(void* source, int elementOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sizeof !!T + // conv.i + // mul + // add + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Conv_I(); + Mul(); + IL.Emit.Add(); + return IL.ReturnPointer(); + } + + /// + /// Adds an element offset to the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Add(ref T source, nuint elementOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sizeof !!T + // mul + // add + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Mul(); + IL.Emit.Add(); + return ref IL.ReturnRef(); + } + + /// + /// Adds a byte offset to the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AddByteOffset(ref T source, nuint byteOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // add + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(byteOffset)); + IL.Emit.Add(); + return ref IL.ReturnRef(); + } + + /// + /// Determines whether the specified references point to the same location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool AreSame([AllowNull] ref readonly T left, [AllowNull] ref readonly T right) + where T : allows ref struct + { + // ldarg.0 + // ldarg.1 + // ceq + // ret + + Ldarg(nameof(left)); + Ldarg(nameof(right)); + Ceq(); + return IL.Return(); + } + + /// + /// Reinterprets the given value of type as a value of type . + /// + /// The sizes of and are not the same + /// or the type parameters are not s. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TTo BitCast(TFrom source) + where TFrom : allows ref struct + where TTo : allows ref struct + { + if (sizeof(TFrom) != sizeof(TTo) || default(TFrom) is null || default(TTo) is null) + { + ThrowHelper.ThrowNotSupportedException(); + } + + return ReadUnaligned(ref As(ref source)); + } + + /// + /// Copies a value of type T to the given location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(void* destination, ref readonly T source) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // ldobj !!T + // stobj !!T + // ret + + Ldarg(nameof(destination)); + Ldarg(nameof(source)); + Ldobj(typeof(T)); + Stobj(typeof(T)); + } + + /// + /// Copies a value of type T to the given location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(ref T destination, void* source) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // ldobj !!T + // stobj !!T + // ret + + Ldarg(nameof(destination)); + Ldarg(nameof(source)); + Ldobj(typeof(T)); + Stobj(typeof(T)); + } + + /// + /// Copies bytes from the source address to the destination address. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyBlock(void* destination, void* source, uint byteCount) + { + // ldarg .0 + // ldarg .1 + // ldarg .2 + // cpblk + // ret + + Ldarg(nameof(destination)); + Ldarg(nameof(source)); + Ldarg(nameof(byteCount)); + Cpblk(); + } + + /// + /// Copies bytes from the source address to the destination address. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyBlock(ref byte destination, ref readonly byte source, uint byteCount) + { + // ldarg .0 + // ldarg .1 + // ldarg .2 + // cpblk + // ret + + Ldarg(nameof(destination)); + Ldarg(nameof(source)); + Ldarg(nameof(byteCount)); + Cpblk(); + } + + /// + /// Copies bytes from the source address to the destination address without assuming architecture dependent alignment of the addresses. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyBlockUnaligned(void* destination, void* source, uint byteCount) + { + // ldarg .0 + // ldarg .1 + // ldarg .2 + // unaligned. 0x1 + // cpblk + // ret + + Ldarg(nameof(destination)); + Ldarg(nameof(source)); + Ldarg(nameof(byteCount)); + Unaligned(1); + Cpblk(); + } + + /// + /// Copies bytes from the source address to the destination address without assuming architecture dependent alignment of the addresses. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyBlockUnaligned(ref byte destination, ref readonly byte source, uint byteCount) + { + // ldarg .0 + // ldarg .1 + // ldarg .2 + // unaligned. 0x1 + // cpblk + // ret + + Ldarg(nameof(destination)); + Ldarg(nameof(source)); + Ldarg(nameof(byteCount)); + Unaligned(1); + Cpblk(); + } + + /// + /// Determines whether the memory address referenced by is greater than + /// the memory address referenced by . + /// + /// + /// This check is conceptually similar to "(void*)(&left) > (void*)(&right)". + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAddressGreaterThan([AllowNull] ref readonly T left, [AllowNull] ref readonly T right) + where T : allows ref struct + { + // ldarg.0 + // ldarg.1 + // cgt.un + // ret + + Ldarg(nameof(left)); + Ldarg(nameof(right)); + Cgt_Un(); + return IL.Return(); + } + + /// + /// Determines whether the memory address referenced by is less than + /// the memory address referenced by . + /// + /// + /// This check is conceptually similar to "(void*)(&left) < (void*)(&right)". + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAddressLessThan([AllowNull] ref readonly T left, [AllowNull] ref readonly T right) + where T : allows ref struct + { + // ldarg.0 + // ldarg.1 + // clt.un + // ret + + Ldarg(nameof(left)); + Ldarg(nameof(right)); + Clt_Un(); + return IL.Return(); + } + + /// + /// Initializes a block of memory at the given location with a given initial value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitBlock(void* startAddress, byte value, uint byteCount) + { + // ldarg .0 + // ldarg .1 + // ldarg .2 + // initblk + // ret + + Ldarg(nameof(startAddress)); + Ldarg(nameof(value)); + Ldarg(nameof(byteCount)); + Initblk(); + } + + /// + /// Initializes a block of memory at the given location with a given initial value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitBlock(ref byte startAddress, byte value, uint byteCount) + { + // ldarg .0 + // ldarg .1 + // ldarg .2 + // initblk + // ret + + Ldarg(nameof(startAddress)); + Ldarg(nameof(value)); + Ldarg(nameof(byteCount)); + Initblk(); + } + + /// + /// Initializes a block of memory at the given location with a given initial value + /// without assuming architecture dependent alignment of the address. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitBlockUnaligned(void* startAddress, byte value, uint byteCount) + { + // ldarg .0 + // ldarg .1 + // ldarg .2 + // unaligned. 0x1 + // initblk + // ret + + Ldarg(nameof(startAddress)); + Ldarg(nameof(value)); + Ldarg(nameof(byteCount)); + Unaligned(1); + Initblk(); + } + + /// + /// Initializes a block of memory at the given location with a given initial value + /// without assuming architecture dependent alignment of the address. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitBlockUnaligned(ref byte startAddress, byte value, uint byteCount) + { + // ldarg .0 + // ldarg .1 + // ldarg .2 + // unaligned. 0x1 + // initblk + // ret + + Ldarg(nameof(startAddress)); + Ldarg(nameof(value)); + Ldarg(nameof(byteCount)); + Unaligned(1); + Initblk(); + } + + /// + /// Reads a value of type from the given location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadUnaligned(void* source) + where T : allows ref struct + { + // ldarg.0 + // unaligned. 0x1 + // ldobj !!T + // ret + + Ldarg(nameof(source)); + Unaligned(1); + Ldobj(typeof(T)); + return IL.Return(); + } + + /// + /// Reads a value of type from the given location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadUnaligned(scoped ref readonly byte source) + where T : allows ref struct + { + // ldarg.0 + // unaligned. 0x1 + // ldobj !!T + // ret + + Ldarg(nameof(source)); + Unaligned(1); + Ldobj(typeof(T)); + return IL.Return(); + } + + /// + /// Writes a value of type to the given location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUnaligned(void* destination, T value) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // unaligned. 0x01 + // stobj !!T + // ret + + Ldarg(nameof(destination)); + Ldarg(nameof(value)); + Unaligned(1); + Stobj(typeof(T)); + } + + /// + /// Writes a value of type to the given location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUnaligned(ref byte destination, T value) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // unaligned. 0x01 + // stobj !!T + // ret + + Ldarg(nameof(destination)); + Ldarg(nameof(value)); + Unaligned(1); + Stobj(typeof(T)); + } + + /// + /// Adds a byte offset to the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AddByteOffset(ref T source, IntPtr byteOffset) + where T : allows ref struct + { + // ldarg.0 + // ldarg.1 + // add + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(byteOffset)); + IL.Emit.Add(); + return ref IL.ReturnRef(); + } + + /// + /// Reads a value of type from the given location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Read(void* source) + where T : allows ref struct + { + return *(T*)source; + } + + /// + /// Writes a value of type to the given location. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(void* destination, T value) + where T : allows ref struct + { + *(T*)destination = value; + } + + /// + /// Reinterprets the given location as a reference to a value of type . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(void* source) + where T : allows ref struct + { + return ref *(T*)source; + } + + /// + /// Reinterprets the given location as a reference to a value of type . + /// + /// The lifetime of the reference will not be validated when using this API. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(scoped ref readonly T source) + where T : allows ref struct + { + //ldarg .0 + //ret + + Ldarg(nameof(source)); + return ref IL.ReturnRef(); + } + + /// + /// Determines the byte offset from origin to target from the given references. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr ByteOffset([AllowNull] ref readonly T origin, [AllowNull] ref readonly T target) + where T : allows ref struct + { + // ldarg .1 + // ldarg .0 + // sub + // ret + + Ldarg(nameof(target)); + Ldarg(nameof(origin)); + Sub(); + return IL.Return(); + } + + /// + /// Returns a by-ref to type that is a null reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T NullRef() + where T : allows ref struct + { + // ldc.i4.0 + // conv.u + // ret + + Ldc_I4_0(); + Conv_U(); + return ref IL.ReturnRef(); + } + + /// + /// Returns if a given by-ref to type is a null reference. + /// + /// + /// This check is conceptually similar to "(void*)(&source) == nullptr". + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullRef(ref readonly T source) + where T : allows ref struct + { + // ldarg.0 + // ldc.i4.0 + // conv.u + // ceq + // ret + + Ldarg(nameof(source)); + Ldc_I4_0(); + Conv_U(); + Ceq(); + return IL.Return(); + } + + /// + /// Bypasses definite assignment rules by taking advantage of out semantics. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SkipInit(out T value) + where T : allows ref struct + { + // ret + + Ret(); + throw IL.Unreachable(); + } + + /// + /// Subtracts an element offset from the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Subtract(ref T source, int elementOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sizeof !!T + // conv.i + // mul + // sub + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Conv_I(); + Mul(); + Sub(); + return ref IL.ReturnRef(); + } + + /// + /// Subtracts an element offset from the given void pointer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void* Subtract(void* source, int elementOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sizeof !!T + // conv.i + // mul + // sub + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Conv_I(); + Mul(); + Sub(); + return IL.ReturnPointer(); + } + + /// + /// Subtracts an element offset from the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Subtract(ref T source, IntPtr elementOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sizeof !!T + // mul + // sub + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Mul(); + Sub(); + return ref IL.ReturnRef(); + } + + /// + /// Subtracts an element offset from the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Subtract(ref T source, nuint elementOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sizeof !!T + // mul + // sub + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(elementOffset)); + Sizeof(typeof(T)); + Mul(); + Sub(); + return ref IL.ReturnRef(); + } + + /// + /// Subtracts a byte offset from the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T SubtractByteOffset(ref T source, IntPtr byteOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sub + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(byteOffset)); + Sub(); + return ref IL.ReturnRef(); + } + + /// + /// Subtracts a byte offset from the given reference. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T SubtractByteOffset(ref T source, nuint byteOffset) + where T : allows ref struct + { + // ldarg .0 + // ldarg .1 + // sub + // ret + + Ldarg(nameof(source)); + Ldarg(nameof(byteOffset)); + Sub(); + return ref IL.ReturnRef(); + } + + /// + /// Returns a mutable ref to a boxed value + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Unbox(object box) + where T : struct + { + // ldarg .0 + // unbox !!T + // ret + + IL.Push(box); + IL.Emit.Unbox(typeof(T)); + return ref IL.ReturnRef(); + } + + private static class ThrowHelper + { + [DoesNotReturn] + internal static void ThrowNotSupportedException() + { + throw new NotSupportedException(); + } + } +} diff --git a/src/InlineIL.Tests/Examples/ExamplesTests.cs b/src/InlineIL.Tests/Examples/ExamplesTests.cs index abdc1d7..f35c123 100644 --- a/src/InlineIL.Tests/Examples/ExamplesTests.cs +++ b/src/InlineIL.Tests/Examples/ExamplesTests.cs @@ -1,4 +1,6 @@ -using System; +#if NET + +using System; using InlineIL.Examples; using Xunit; @@ -33,3 +35,5 @@ public void DebugInfo() DebugInfoExamples.LocalVariables(); } } + +#endif diff --git a/src/InlineIL.Tests/InlineIL.Tests.csproj b/src/InlineIL.Tests/InlineIL.Tests.csproj index 64f0013..685a253 100644 --- a/src/InlineIL.Tests/InlineIL.Tests.csproj +++ b/src/InlineIL.Tests/InlineIL.Tests.csproj @@ -20,7 +20,7 @@ - +