Skip to content

Commit

Permalink
Add default shims for Unsafe, RuntimeHelpers and MemoryMarshal.
Browse files Browse the repository at this point in the history
  • Loading branch information
Washi1337 committed Apr 1, 2024
1 parent d24c5a7 commit 824eb9f
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ public static class DefaultInvokers
/// Gets the default shim for the <see cref="System.String"/> type.
/// </summary>
public static StringInvoker StringShim => StringInvoker.Instance;

/// <summary>
/// Gets the default shim for the <see cref="System.Runtime.CompilerServices.Unsafe"/> class.
/// </summary>
public static UnsafeInvoker UnsafeShim => UnsafeInvoker.Instance;

/// <summary>
/// Gets the default shim for the <see cref="System.Runtime.CompilerServices.RuntimeHelpers"/> class.
/// </summary>
public static RuntimeHelpersInvoker RuntimeHelpersShim => RuntimeHelpersInvoker.Instance;

/// <summary>
/// Gets the default shim for methods found in the <c>System.Runtime.Intrinsics</c> namespace of the BCL.
/// </summary>
public static IntrinsicsInvoker IntrinsicsShim => IntrinsicsInvoker.Instance;

/// <summary>
/// Gets the default shim for the <see cref="System.Runtime.InteropServices.MemoryMarshal"/> class.
/// </summary>
public static MemoryMarshalInvoker MemoryMarshalShim => MemoryMarshalInvoker.Instance;

/// <summary>
/// Gets the method invoker that forwards any method that is not within the resolution scope of the current
Expand All @@ -67,6 +87,17 @@ public static class DefaultInvokers
/// </summary>
public static MethodShimInvoker CreateShim() => new();

/// <summary>
/// Creates a method invoker that provides default shim implementations for various base class library methods
/// that are implemented by the runtime.
/// </summary>
/// <returns></returns>
public static IMethodInvoker CreateDefaultShims() => StringShim
.WithFallback(UnsafeShim)
.WithFallback(RuntimeHelpersShim)
.WithFallback(IntrinsicsShim)
.WithFallback(MemoryMarshalShim);

/// <summary>
/// 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Implements a method invoker that shims methods in the <c>System.Runtime.Intrinsics</c> namespace.
/// </summary>
public class IntrinsicsInvoker : IMethodInvoker
{
/// <summary>
/// Gets the singleton instance of the <see cref="IntrinsicsInvoker"/> class.
/// </summary>
public static IntrinsicsInvoker Instance { get; } = new();

/// <inheritdoc />
public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList<BitVector> 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));
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Implements a method invoker that shims methods in the <see cref="System.Runtime.InteropServices.MemoryMarshal"/> class.
/// </summary>
public class MemoryMarshalInvoker : IMethodInvoker
{
/// <summary>
/// Gets the singleton instance of the <see cref="MemoryMarshalInvoker"/> class.
/// </summary>
public static MemoryMarshalInvoker Instance { get; } = new();

/// <inheritdoc />
public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList<BitVector> 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<BitVector> arguments)
{
var arrayObject = arguments[0].AsObjectHandle(context.Machine);
long result = arrayObject.Address + context.Machine.ValueFactory.ArrayHeaderSize;
return InvocationResult.StepOver(context.Machine.ValueFactory.RentNativeInteger(result));
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Implements a method invoker that shims methods from the <see cref="RuntimeHelpers"/> class.
/// </summary>
public class RuntimeHelpersInvoker : IMethodInvoker
{
/// <summary>
/// Gets the singleton instance for the <see cref="RuntimeHelpersInvoker"/> class.
/// </summary>
public static RuntimeHelpersInvoker Instance { get; } = new();

/// <inheritdoc />
public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList<BitVector> 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<BitVector> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Implements a method invoker that shims methods from the <see cref="System.Runtime.CompilerServices.Unsafe"/> class.
/// </summary>
public class UnsafeInvoker : IMethodInvoker
{
/// <summary>
/// Gets the singleton instance of the <see cref="UnsafeInvoker"/> class.
/// </summary>
public static UnsafeInvoker Instance { get; } = new();

/// <inheritdoc />
public InvocationResult Invoke(CilExecutionContext context, IMethodDescriptor method, IList<BitVector> 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<BitVector> 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<BitVector> 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<BitVector> 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<BitVector> 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<T> 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<BitVector> 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);
}
}

0 comments on commit 824eb9f

Please sign in to comment.