using ARMeilleure.Common; using ARMeilleure.Memory; using Ryujinx.Cpu.Jit; using Ryujinx.Cpu.LightningJit.Cache; using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; using Ryujinx.Cpu.LightningJit.State; using Ryujinx.Cpu.Signal; using Ryujinx.Memory; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; namespace Ryujinx.Cpu.LightningJit { class Translator : IDisposable { // Should be enabled on platforms that enforce W^X. private static bool IsNoWxPlatform => false; private static readonly AddressTable.Level[] _levels64Bit = new AddressTable.Level[] { new(31, 17), new(23, 8), new(15, 8), new( 7, 8), new( 2, 5), }; private static readonly AddressTable.Level[] _levels32Bit = new AddressTable.Level[] { new(23, 9), new(15, 8), new( 7, 8), new( 1, 6), }; private readonly ConcurrentQueue> _oldFuncs; private readonly NoWxCache _noWxCache; private bool _disposed; internal TranslatorCache Functions { get; } internal AddressTable FunctionTable { get; } internal TranslatorStubs Stubs { get; } internal IMemoryManager Memory { get; } public Translator(IMemoryManager memory, bool for64Bits) { Memory = memory; _oldFuncs = new ConcurrentQueue>(); if (IsNoWxPlatform) { _noWxCache = new(new JitMemoryAllocator(), CreateStackWalker(), this); } else { JitCache.Initialize(new JitMemoryAllocator(forJit: true)); } Functions = new TranslatorCache(); FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); Stubs = new TranslatorStubs(FunctionTable, _noWxCache); FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; if (memory.Type.IsHostMappedOrTracked()) { NativeSignalHandler.InitializeSignalHandler(); } } private static IStackWalker CreateStackWalker() { if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) { return new StackWalker(); } else { throw new PlatformNotSupportedException(); } } public void Execute(State.ExecutionContext context, ulong address) { ObjectDisposedException.ThrowIf(_disposed, this); NativeInterface.RegisterThread(context, Memory, this); Stubs.DispatchLoop(context.NativeContextPtr, address); NativeInterface.UnregisterThread(); _noWxCache?.ClearEntireThreadLocalCache(); } internal IntPtr GetOrTranslatePointer(IntPtr framePointer, ulong address, ExecutionMode mode) { if (_noWxCache != null) { CompiledFunction func = Compile(address, mode); return _noWxCache.Map(framePointer, func.Code, address, (ulong)func.GuestCodeLength); } return GetOrTranslate(address, mode).FuncPointer; } private TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) { if (!Functions.TryGetValue(address, out TranslatedFunction func)) { func = Translate(address, mode); TranslatedFunction oldFunc = Functions.GetOrAdd(address, func.GuestSize, func); if (oldFunc != func) { JitCache.Unmap(func.FuncPointer); func = oldFunc; } RegisterFunction(address, func); } return func; } internal void RegisterFunction(ulong guestAddress, TranslatedFunction func) { if (FunctionTable.IsValid(guestAddress)) { Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPointer); } } private TranslatedFunction Translate(ulong address, ExecutionMode mode) { CompiledFunction func = Compile(address, mode); IntPtr funcPointer = JitCache.Map(func.Code); return new TranslatedFunction(funcPointer, (ulong)func.GuestCodeLength); } private CompiledFunction Compile(ulong address, ExecutionMode mode) { return AarchCompiler.Compile(CpuPresets.CortexA57, Memory, address, FunctionTable, Stubs.DispatchStub, mode, RuntimeInformation.ProcessArchitecture); } public void InvalidateJitCacheRegion(ulong address, ulong size) { ulong[] overlapAddresses = Array.Empty(); int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses); for (int index = 0; index < overlapsCount; index++) { ulong overlapAddress = overlapAddresses[index]; if (Functions.TryGetValue(overlapAddress, out TranslatedFunction overlap)) { Functions.Remove(overlapAddress); Volatile.Write(ref FunctionTable.GetValue(overlapAddress), FunctionTable.Fill); EnqueueForDeletion(overlapAddress, overlap); } } // TODO: Remove overlapping functions from the JitCache aswell. // This should be done safely, with a mechanism to ensure the function is not being executed. } private void EnqueueForDeletion(ulong guestAddress, TranslatedFunction func) { _oldFuncs.Enqueue(new(guestAddress, func)); } private void ClearJitCache() { List functions = Functions.AsList(); foreach (var func in functions) { JitCache.Unmap(func.FuncPointer); } Functions.Clear(); while (_oldFuncs.TryDequeue(out var kv)) { JitCache.Unmap(kv.Value.FuncPointer); } } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { if (_noWxCache != null) { _noWxCache.Dispose(); } else { ClearJitCache(); } Stubs.Dispose(); FunctionTable.Dispose(); } _disposed = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } }