using ARMeilleure.Common;
using ARMeilleure.Instructions;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using ARMeilleure.Translation.Cache;
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.Translation
{
///
/// Represents a stub manager.
///
class TranslatorStubs : IDisposable
{
private readonly Lazy _slowDispatchStub;
private bool _disposed;
private readonly AddressTable _functionTable;
private readonly Lazy _dispatchStub;
private readonly Lazy _dispatchLoop;
private readonly Lazy _contextWrapper;
///
/// Gets the dispatch stub.
///
/// instance was disposed
public IntPtr DispatchStub
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _dispatchStub.Value;
}
}
///
/// Gets the slow dispatch stub.
///
/// instance was disposed
public IntPtr SlowDispatchStub
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _slowDispatchStub.Value;
}
}
///
/// Gets the dispatch loop function.
///
/// instance was disposed
public DispatcherFunction DispatchLoop
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _dispatchLoop.Value;
}
}
///
/// Gets the context wrapper function.
///
/// instance was disposed
public WrapperFunction ContextWrapper
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _contextWrapper.Value;
}
}
///
/// Initializes a new instance of the class with the specified
/// instance.
///
/// Function table used to store pointers to the functions that the guest code will call
/// is null
public TranslatorStubs(AddressTable functionTable)
{
ArgumentNullException.ThrowIfNull(functionTable);
_functionTable = functionTable;
_slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
_contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
}
///
/// Releases all resources used by the instance.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases all unmanaged and optionally managed resources used by the instance.
///
/// to dispose managed resources also; otherwise just unmanaged resouces
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (_dispatchStub.IsValueCreated)
{
JitCache.Unmap(_dispatchStub.Value);
}
if (_dispatchLoop.IsValueCreated)
{
JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
}
_disposed = true;
}
}
///
/// Frees resources used by the instance.
///
~TranslatorStubs()
{
Dispose(false);
}
///
/// Generates a .
///
/// Generated
private IntPtr GenerateDispatchStub()
{
var context = new EmitterContext();
Operand lblFallback = Label();
Operand lblEnd = Label();
// Load the target guest address from the native context.
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand guestAddress = context.Load(OperandType.I64,
context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())));
// Check if guest address is within range of the AddressTable.
Operand masked = context.BitwiseAnd(guestAddress, Const(~_functionTable.Mask));
context.BranchIfTrue(lblFallback, masked);
Operand index = default;
Operand page = Const((long)_functionTable.Base);
for (int i = 0; i < _functionTable.Levels.Length; i++)
{
ref var level = ref _functionTable.Levels[i];
// level.Mask is not used directly because it is more often bigger than 32-bits, so it will not
// be encoded as an immediate on x86's bitwise and operation.
Operand mask = Const(level.Mask >> level.Index);
index = context.BitwiseAnd(context.ShiftRightUI(guestAddress, Const(level.Index)), mask);
if (i < _functionTable.Levels.Length - 1)
{
page = context.Load(OperandType.I64, context.Add(page, context.ShiftLeft(index, Const(3))));
context.BranchIfFalse(lblFallback, page);
}
}
Operand hostAddress;
Operand hostAddressAddr = context.Add(page, context.ShiftLeft(index, Const(3)));
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
context.Tailcall(hostAddress, nativeContext);
context.MarkLabel(lblFallback);
hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress);
context.Tailcall(hostAddress, nativeContext);
var cfg = context.GetControlFlowGraph();
var retType = OperandType.I64;
var argTypes = new[] { OperandType.I64 };
var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map();
return Marshal.GetFunctionPointerForDelegate(func);
}
///
/// Generates a .
///
/// Generated
private IntPtr GenerateSlowDispatchStub()
{
var context = new EmitterContext();
// Load the target guest address from the native context.
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand guestAddress = context.Load(OperandType.I64,
context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())));
Operand hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress);
context.Tailcall(hostAddress, nativeContext);
var cfg = context.GetControlFlowGraph();
var retType = OperandType.I64;
var argTypes = new[] { OperandType.I64 };
var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map();
return Marshal.GetFunctionPointerForDelegate(func);
}
///
/// Emits code that syncs FP state before executing guest code, or returns it to normal.
///
/// Emitter context for the method
/// Pointer to the native context
/// True if entering guest code, false otherwise
private static void EmitSyncFpContext(EmitterContext context, Operand nativeContext, bool enter)
{
if (enter)
{
InstEmitSimdHelper.EnterArmFpMode(context, (flag) =>
{
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
return context.Load(OperandType.I32, flagAddress);
});
}
else
{
InstEmitSimdHelper.ExitArmFpMode(context, (flag, value) =>
{
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
context.Store(flagAddress, value);
});
}
}
///
/// Generates a function.
///
/// function
private DispatcherFunction GenerateDispatchLoop()
{
var context = new EmitterContext();
Operand beginLbl = Label();
Operand endLbl = Label();
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand guestAddress = context.Copy(
context.AllocateLocal(OperandType.I64),
context.LoadArgument(OperandType.I64, 1));
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
EmitSyncFpContext(context, nativeContext, true);
context.MarkLabel(beginLbl);
context.Store(dispatchAddress, guestAddress);
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
context.BranchIfFalse(endLbl, guestAddress);
context.BranchIfFalse(endLbl, context.Load(OperandType.I32, runningAddress));
context.Branch(beginLbl);
context.MarkLabel(endLbl);
EmitSyncFpContext(context, nativeContext, false);
context.Return();
var cfg = context.GetControlFlowGraph();
var retType = OperandType.None;
var argTypes = new[] { OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map();
}
///
/// Generates a function.
///
/// function
private WrapperFunction GenerateContextWrapper()
{
var context = new EmitterContext();
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand guestMethod = context.LoadArgument(OperandType.I64, 1);
EmitSyncFpContext(context, nativeContext, true);
Operand returnValue = context.Call(guestMethod, OperandType.I64, nativeContext);
EmitSyncFpContext(context, nativeContext, false);
context.Return(returnValue);
var cfg = context.GetControlFlowGraph();
var retType = OperandType.I64;
var argTypes = new[] { OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map();
}
}
}