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 static readonly Lazy _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); private bool _disposed; private readonly Translator _translator; 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. /// /// instance to use /// is null public TranslatorStubs(Translator translator) { ArgumentNullException.ThrowIfNull(translator); _translator = translator; _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(~_translator.FunctionTable.Mask)); context.BranchIfTrue(lblFallback, masked); Operand index = default; Operand page = Const((long)_translator.FunctionTable.Base); for (int i = 0; i < _translator.FunctionTable.Levels.Length; i++) { ref var level = ref _translator.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 < _translator.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 static 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()))); MethodInfo getFuncAddress = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)); Operand hostAddress = context.Call(getFuncAddress, 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 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(); } } }