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