diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/DefaultInvokers.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/DefaultInvokers.cs index e22de72c..8d84b62c 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/DefaultInvokers.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/DefaultInvokers.cs @@ -47,6 +47,26 @@ public static class DefaultInvokers /// Gets the default shim for the type. /// public static StringInvoker StringShim => StringInvoker.Instance; + + /// + /// Gets the default shim for the class. + /// + public static UnsafeInvoker UnsafeShim => UnsafeInvoker.Instance; + + /// + /// Gets the default shim for the class. + /// + public static RuntimeHelpersInvoker RuntimeHelpersShim => RuntimeHelpersInvoker.Instance; + + /// + /// Gets the default shim for methods found in the System.Runtime.Intrinsics namespace of the BCL. + /// + public static IntrinsicsInvoker IntrinsicsShim => IntrinsicsInvoker.Instance; + + /// + /// Gets the default shim for the class. + /// + public static MemoryMarshalInvoker MemoryMarshalShim => MemoryMarshalInvoker.Instance; /// /// Gets the method invoker that forwards any method that is not within the resolution scope of the current @@ -67,6 +87,17 @@ public static class DefaultInvokers /// public static MethodShimInvoker CreateShim() => new(); + /// + /// Creates a method invoker that provides default shim implementations for various base class library methods + /// that are implemented by the runtime. + /// + /// + public static IMethodInvoker CreateDefaultShims() => StringShim + .WithFallback(UnsafeShim) + .WithFallback(RuntimeHelpersShim) + .WithFallback(IntrinsicsShim) + .WithFallback(MemoryMarshalShim); + /// /// Chains the first method invoker with the provided method invoker in such a way that if the result of the /// first invoker is inconclusive, the second invoker will be used as a fallback invoker. diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/IntrinsicsInvoker.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/IntrinsicsInvoker.cs new file mode 100644 index 00000000..9dd88d7e --- /dev/null +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/IntrinsicsInvoker.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using AsmResolver.DotNet; +using Echo.Memory; +using Echo.Platforms.AsmResolver.Emulation.Dispatch; + +namespace Echo.Platforms.AsmResolver.Emulation.Invocation; + +/// +/// Implements a method invoker that shims methods in the System.Runtime.Intrinsics namespace. +/// +public class IntrinsicsInvoker : IMethodInvoker +{ + /// + /// Gets the singleton instance of the class. + /// + public static IntrinsicsInvoker Instance { get; } = new(); + + /// + public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList arguments) + { + if (method is not { DeclaringType: {} declaringType, Name: {} name }) + return InvocationResult.Inconclusive(); + + if (declaringType.Namespace != "System.Runtime.Intrinsics") + return InvocationResult.Inconclusive(); + + return name.Value switch + { + "get_IsHardwareAccelerated" => InvokeIsHardwareAccelerated(context), + _ => InvocationResult.Inconclusive() + }; + } + + private static InvocationResult InvokeIsHardwareAccelerated(CilExecutionContext context) + { + // We assume no hardware acceleration. + return InvocationResult.StepOver(context.Machine.ValueFactory.RentBoolean(false)); + } +} \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/MemoryMarshalInvoker.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/MemoryMarshalInvoker.cs new file mode 100644 index 00000000..fb6aef81 --- /dev/null +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/MemoryMarshalInvoker.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using AsmResolver.DotNet; +using Echo.Memory; +using Echo.Platforms.AsmResolver.Emulation.Dispatch; + +namespace Echo.Platforms.AsmResolver.Emulation.Invocation; + +/// +/// Implements a method invoker that shims methods in the class. +/// +public class MemoryMarshalInvoker : IMethodInvoker +{ + /// + /// Gets the singleton instance of the class. + /// + public static MemoryMarshalInvoker Instance { get; } = new(); + + /// + public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList arguments) + { + if (method is not { DeclaringType: { } declaringType, Name: { } name }) + return InvocationResult.Inconclusive(); + + if (!declaringType.IsTypeOf("System.Runtime.InteropServices", "MemoryMarshal")) + return InvocationResult.Inconclusive(); + + return name.Value switch + { + "GetArrayDataReference" => InvokeGetArrayDataReference(context, arguments), + _ => InvocationResult.Inconclusive() + }; + } + + private static InvocationResult InvokeGetArrayDataReference(CilExecutionContext context, IList arguments) + { + var arrayObject = arguments[0].AsObjectHandle(context.Machine); + long result = arrayObject.Address + context.Machine.ValueFactory.ArrayHeaderSize; + return InvocationResult.StepOver(context.Machine.ValueFactory.RentNativeInteger(result)); + } +} \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/RuntimeHelpersInvoker.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/RuntimeHelpersInvoker.cs new file mode 100644 index 00000000..4e491b80 --- /dev/null +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/RuntimeHelpersInvoker.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using AsmResolver; +using AsmResolver.DotNet; +using Echo.Memory; +using Echo.Platforms.AsmResolver.Emulation.Dispatch; + +namespace Echo.Platforms.AsmResolver.Emulation.Invocation; + +/// +/// Implements a method invoker that shims methods from the class. +/// +public class RuntimeHelpersInvoker : IMethodInvoker +{ + /// + /// Gets the singleton instance for the class. + /// + public static RuntimeHelpersInvoker Instance { get; } = new(); + + /// + public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList arguments) + { + if (method is not {DeclaringType: {} declaringType, Name: {} name}) + return InvocationResult.Inconclusive(); + + if (!declaringType.IsTypeOf("System.Runtime.CompilerServices", "RuntimeHelpers")) + return InvocationResult.Inconclusive(); + + return name.Value switch + { + "IsReferenceOrContainsReferences" => InvokeIsReferenceOrContainsReferences(context, method), + "InitializeArray" => InvokeInitializeArray(context, arguments), + _ => InvocationResult.Inconclusive() + }; + } + + private static InvocationResult InvokeIsReferenceOrContainsReferences(CilExecutionContext context, IMethodDescriptor method) + { + if (method is not MethodSpecification { Signature.TypeArguments: { Count: 1 } typeArguments }) + return InvocationResult.Inconclusive(); + + // TODO: This is inaccurate (feature-blocked by https://github.com/Washi1337/AsmResolver/issues/530). + bool result = !typeArguments[0].IsValueType; + + return InvocationResult.StepOver(context.Machine.ValueFactory.RentBoolean(result)); + } + + private static InvocationResult InvokeInitializeArray(CilExecutionContext context, IList arguments) + { + // Read parameters. + var array = arguments[0].AsObjectHandle(context.Machine); + var fieldHandle = arguments[1].AsStructHandle(context.Machine); + + // Resolve the field handle to a field descriptor. + if (!context.Machine.ValueFactory.ClrMockMemory.Fields.TryGetObject(fieldHandle.Address, out var field)) + return InvocationResult.Inconclusive(); + + // Resole the field behind the field descriptor. + var definition = field!.Resolve(); + + // Read the data. + if (definition?.FieldRva is not IReadableSegment segment) + return InvocationResult.Inconclusive(); + array.WriteArrayData(segment.ToArray()); + + return InvocationResult.StepOver(null); + } +} \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/UnsafeInvoker.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/UnsafeInvoker.cs new file mode 100644 index 00000000..548d96c3 --- /dev/null +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Invocation/UnsafeInvoker.cs @@ -0,0 +1,128 @@ +using System.Collections.Generic; +using AsmResolver.DotNet; +using Echo.Memory; +using Echo.Platforms.AsmResolver.Emulation.Dispatch; + +namespace Echo.Platforms.AsmResolver.Emulation.Invocation; + +/// +/// Implements a method invoker that shims methods from the class. +/// +public class UnsafeInvoker : IMethodInvoker +{ + /// + /// Gets the singleton instance of the class. + /// + public static UnsafeInvoker Instance { get; } = new(); + + /// + public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList arguments) + { + if (method is not { Name: { } name, DeclaringType: { } declaringType, Signature: { } signature }) + return InvocationResult.Inconclusive(); + + if (!declaringType.IsTypeOf("System.Runtime.CompilerServices", "Unsafe") + && !declaringType.IsTypeOf("Internal.Runtime.CompilerServices", "Unsafe")) + { + return InvocationResult.Inconclusive(); + } + + // TODO: Add handlers for the remaining methods in the Unsafe class. + switch (signature.ParameterTypes.Count) + { + case 1: + return name.Value switch + { + "As" => InvokeAsOrAsRef(context, arguments), + "AsRef" => InvokeAsOrAsRef(context, arguments), + "Read" => InvokeReadUnaligned(context, method, arguments), + "ReadUnaligned" => InvokeReadUnaligned(context, method, arguments), + _ => InvocationResult.Inconclusive() + }; + + case 2: + return name.Value switch + { + "AreSame" => InvokeAreSame(context, arguments), + "Add" => InvokeAdd(context, method, arguments), + "AddByteOffset" => InvokeAddByteOffset(context, arguments), + _ => InvocationResult.Inconclusive() + }; + + default: + return InvocationResult.Inconclusive(); + } + } + + private static InvocationResult InvokeAsOrAsRef(CilExecutionContext context, IList arguments) + { + // We don't do any GC tracking, thus returning the same input reference suffices. + return InvocationResult.StepOver(arguments[0].Clone(context.Machine.ValueFactory.BitVectorPool)); + } + + private static InvocationResult InvokeAreSame(CilExecutionContext context, IList arguments) + { + var comparison = arguments[0].AsSpan().IsEqualTo(arguments[1]); + var result = context.Machine.ValueFactory.RentBoolean(comparison); + return InvocationResult.StepOver(result); + } + + private static InvocationResult InvokeAddByteOffset(CilExecutionContext context, IList arguments) + { + var result = arguments[0].Clone(context.Machine.ValueFactory.BitVectorPool); + result.AsSpan().IntegerAdd(arguments[1]); + return InvocationResult.StepOver(result); + } + + private static InvocationResult InvokeAdd(CilExecutionContext context, IMethodDescriptor method, IList arguments) + { + if (method is not MethodSpecification { Signature.TypeArguments: { Count: 1 } typeArguments }) + throw new CilEmulatorException("Expected a method specification with a single type argument."); + + var valueFactory = context.Machine.ValueFactory; + var pool = valueFactory.BitVectorPool; + + // Determine the size of a single element. + uint elementSize = valueFactory.GetTypeValueMemoryLayout(typeArguments[0]).Size; + var elementSizeVector = valueFactory.RentNativeInteger(elementSize); + + // We need to resize the offset as Unsafe.Add accepts both a nint and an int32 as offset. + var elementOffset = arguments[1].Resize((int)(valueFactory.PointerSize * 8), true, pool); + + // The offset is the index multiplied by its element size. + elementOffset.AsSpan().IntegerMultiply(elementSizeVector); + + // Add the offset to the start address. + var source = arguments[0].Clone(pool); + source.AsSpan().IntegerAdd(elementOffset); + + // Return temporary vectors. + pool.Return(elementSizeVector); + pool.Return(elementOffset); + + return InvocationResult.StepOver(source); + } + + private static InvocationResult InvokeReadUnaligned( + CilExecutionContext context, + IMethodDescriptor method, + IList arguments) + { + // Should be a generic instance method. + if (method is not MethodSpecification { Signature: { } signature }) + return InvocationResult.Inconclusive(); + + // Pointer should be known to be able to read from it. + if (!arguments[0].IsFullyKnown) + return InvocationResult.Inconclusive(); + + // Concretize pointer. + long resolvedAddress = arguments[0].AsSpan().ReadNativeInteger(context.Machine.Is32Bit); + + // Read from pointer. + var result = context.Machine.ValueFactory.CreateValue(signature.TypeArguments[0], false); + context.Machine.Memory.Read(resolvedAddress, result); + + return InvocationResult.StepOver(result); + } +} \ No newline at end of file