using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using System.Runtime.Versioning; using System.Threading; using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers; namespace Ryujinx.Common.Memory.PartialUnmaps { /// /// State for partial unmaps. Intended to be used on Windows. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public partial struct PartialUnmapState { public NativeReaderWriterLock PartialUnmapLock; public int PartialUnmapsCount; public ThreadLocalMap LocalCounts; public readonly static int PartialUnmapLockOffset; public readonly static int PartialUnmapsCountOffset; public readonly static int LocalCountsOffset; public readonly static IntPtr GlobalState; [SupportedOSPlatform("windows")] [LibraryImport("kernel32.dll")] private static partial int GetCurrentThreadId(); [SupportedOSPlatform("windows")] [LibraryImport("kernel32.dll", SetLastError = true)] private static partial IntPtr OpenThread(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId); [SupportedOSPlatform("windows")] [LibraryImport("kernel32.dll", SetLastError = true)] [return: MarshalAs (UnmanagedType.Bool)] private static partial bool CloseHandle(IntPtr hObject); [SupportedOSPlatform("windows")] [LibraryImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode); /// /// Creates a global static PartialUnmapState and populates the field offsets. /// static unsafe PartialUnmapState() { PartialUnmapState instance = new PartialUnmapState(); PartialUnmapLockOffset = OffsetOf(ref instance, ref instance.PartialUnmapLock); PartialUnmapsCountOffset = OffsetOf(ref instance, ref instance.PartialUnmapsCount); LocalCountsOffset = OffsetOf(ref instance, ref instance.LocalCounts); int size = Unsafe.SizeOf(); GlobalState = Marshal.AllocHGlobal(size); Unsafe.InitBlockUnaligned((void*)GlobalState, 0, (uint)size); } /// /// Resets the global state. /// public static unsafe void Reset() { int size = Unsafe.SizeOf(); Unsafe.InitBlockUnaligned((void*)GlobalState, 0, (uint)size); } /// /// Gets a reference to the global state. /// /// A reference to the global state public static unsafe ref PartialUnmapState GetRef() { return ref Unsafe.AsRef((void*)GlobalState); } /// /// Checks if an access violation handler should retry execution due to a fault caused by partial unmap. /// /// /// Due to Windows limitations, might need to unmap more memory than requested. /// The additional memory that was unmapped is later remapped, however this leaves a time gap where the /// memory might be accessed but is unmapped. Users of the API must compensate for that by catching the /// access violation and retrying if it happened between the unmap and remap operation. /// This method can be used to decide if retrying in such cases is necessary or not. /// /// This version of the function is not used, but serves as a reference for the native /// implementation in ARMeilleure. /// /// True if execution should be retried, false otherwise [SupportedOSPlatform("windows")] public bool RetryFromAccessViolation() { PartialUnmapLock.AcquireReaderLock(); int threadID = GetCurrentThreadId(); int threadIndex = LocalCounts.GetOrReserve(threadID, 0); if (threadIndex == -1) { // Out of thread local space... try again later. PartialUnmapLock.ReleaseReaderLock(); return true; } ref int threadLocalPartialUnmapsCount = ref LocalCounts.GetValue(threadIndex); bool retry = threadLocalPartialUnmapsCount != PartialUnmapsCount; if (retry) { threadLocalPartialUnmapsCount = PartialUnmapsCount; } PartialUnmapLock.ReleaseReaderLock(); return retry; } /// /// Iterates and trims threads in the thread -> count map that /// are no longer active. /// [SupportedOSPlatform("windows")] public void TrimThreads() { const uint ExitCodeStillActive = 259; const int ThreadQueryInformation = 0x40; Span ids = LocalCounts.ThreadIds.AsSpan(); for (int i = 0; i < ids.Length; i++) { int id = ids[i]; if (id != 0) { IntPtr handle = OpenThread(ThreadQueryInformation, false, (uint)id); if (handle == IntPtr.Zero) { Interlocked.CompareExchange(ref ids[i], 0, id); } else { GetExitCodeThread(handle, out uint exitCode); if (exitCode != ExitCodeStillActive) { Interlocked.CompareExchange(ref ids[i], 0, id); } CloseHandle(handle); } } } } } }