WindowsPartialUnmapHandler.cs
1 using ARMeilleure.IntermediateRepresentation; 2 using ARMeilleure.Translation; 3 using Ryujinx.Common.Memory.PartialUnmaps; 4 using System; 5 using System.Runtime.InteropServices; 6 using static ARMeilleure.IntermediateRepresentation.Operand.Factory; 7 8 namespace ARMeilleure.Signal 9 { 10 /// <summary> 11 /// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods. 12 /// </summary> 13 internal static partial class WindowsPartialUnmapHandler 14 { 15 [LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")] 16 private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName); 17 18 [LibraryImport("kernel32.dll", SetLastError = true)] 19 private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName); 20 21 private static IntPtr _getCurrentThreadIdPtr; 22 23 public static IntPtr GetCurrentThreadIdFunc() 24 { 25 if (_getCurrentThreadIdPtr == IntPtr.Zero) 26 { 27 IntPtr handle = LoadLibrary("kernel32.dll"); 28 29 _getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId"); 30 } 31 32 return _getCurrentThreadIdPtr; 33 } 34 35 public static Operand EmitRetryFromAccessViolation(EmitterContext context) 36 { 37 IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState; 38 IntPtr localCountsPtr = IntPtr.Add(partialRemapStatePtr, PartialUnmapState.LocalCountsOffset); 39 40 // Get the lock first. 41 EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset)); 42 43 IntPtr getCurrentThreadId = GetCurrentThreadIdFunc(); 44 Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32); 45 Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0)); 46 47 Operand endLabel = Label(); 48 Operand retry = context.AllocateLocal(OperandType.I32); 49 Operand threadIndexValidLabel = Label(); 50 51 context.BranchIfFalse(threadIndexValidLabel, context.ICompareEqual(threadIndex, Const(-1))); 52 53 context.Copy(retry, Const(1)); // Always retry when thread local cannot be allocated. 54 55 context.Branch(endLabel); 56 57 context.MarkLabel(threadIndexValidLabel); 58 59 Operand threadLocalPartialUnmapsPtr = EmitThreadLocalMapIntGetValuePtr(context, localCountsPtr, threadIndex); 60 Operand threadLocalPartialUnmaps = context.Load(OperandType.I32, threadLocalPartialUnmapsPtr); 61 Operand partialUnmapsCount = context.Load(OperandType.I32, Const((ulong)IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapsCountOffset))); 62 63 context.Copy(retry, context.ICompareNotEqual(threadLocalPartialUnmaps, partialUnmapsCount)); 64 65 Operand noRetryLabel = Label(); 66 67 context.BranchIfFalse(noRetryLabel, retry); 68 69 // if (retry) { 70 71 context.Store(threadLocalPartialUnmapsPtr, partialUnmapsCount); 72 73 context.Branch(endLabel); 74 75 context.MarkLabel(noRetryLabel); 76 77 // } 78 79 context.MarkLabel(endLabel); 80 81 // Finally, release the lock and return the retry value. 82 EmitNativeReaderLockRelease(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset)); 83 84 return retry; 85 } 86 87 public static Operand EmitThreadLocalMapIntGetOrReserve(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand initialState) 88 { 89 Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.ThreadIdsOffset)); 90 91 Operand i = context.AllocateLocal(OperandType.I32); 92 93 context.Copy(i, Const(0)); 94 95 // (Loop 1) Check all slots for a matching Thread ID (while also trying to allocate) 96 97 Operand endLabel = Label(); 98 99 Operand loopLabel = Label(); 100 context.MarkLabel(loopLabel); 101 102 Operand offset = context.Multiply(i, Const(sizeof(int))); 103 Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset)); 104 105 // Check that this slot has the thread ID. 106 Operand existingId = context.CompareAndSwap(idPtr, threadId, threadId); 107 108 // If it was already the thread ID, then we just need to return i. 109 context.BranchIfTrue(endLabel, context.ICompareEqual(existingId, threadId)); 110 111 context.Copy(i, context.Add(i, Const(1))); 112 113 context.BranchIfTrue(loopLabel, context.ICompareLess(i, Const(ThreadLocalMap<int>.MapSize))); 114 115 // (Loop 2) Try take a slot that is 0 with our Thread ID. 116 117 context.Copy(i, Const(0)); // Reset i. 118 119 Operand loop2Label = Label(); 120 context.MarkLabel(loop2Label); 121 122 Operand offset2 = context.Multiply(i, Const(sizeof(int))); 123 Operand idPtr2 = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset2)); 124 125 // Try and swap in the thread id on top of 0. 126 Operand existingId2 = context.CompareAndSwap(idPtr2, Const(0), threadId); 127 128 Operand idNot0Label = Label(); 129 130 // If it was 0, then we need to initialize the struct entry and return i. 131 context.BranchIfFalse(idNot0Label, context.ICompareEqual(existingId2, Const(0))); 132 133 Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.StructsOffset)); 134 Operand structPtr = context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset2)); 135 context.Store(structPtr, initialState); 136 137 context.Branch(endLabel); 138 139 context.MarkLabel(idNot0Label); 140 141 context.Copy(i, context.Add(i, Const(1))); 142 143 context.BranchIfTrue(loop2Label, context.ICompareLess(i, Const(ThreadLocalMap<int>.MapSize))); 144 145 context.Copy(i, Const(-1)); // Could not place the thread in the list. 146 147 context.MarkLabel(endLabel); 148 149 return context.Copy(i); 150 } 151 152 private static Operand EmitThreadLocalMapIntGetValuePtr(EmitterContext context, IntPtr threadLocalMapPtr, Operand index) 153 { 154 Operand offset = context.Multiply(index, Const(sizeof(int))); 155 Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.StructsOffset)); 156 157 return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset)); 158 } 159 160 private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive) 161 { 162 Operand loop = Label(); 163 context.MarkLabel(loop); 164 165 Operand initial = context.Load(OperandType.I32, ptr); 166 Operand newValue = context.Add(initial, additive); 167 168 Operand replaced = context.CompareAndSwap(ptr, initial, newValue); 169 170 context.BranchIfFalse(loop, context.ICompareEqual(initial, replaced)); 171 } 172 173 private static void EmitNativeReaderLockAcquire(EmitterContext context, IntPtr nativeReaderLockPtr) 174 { 175 Operand writeLockPtr = Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.WriteLockOffset)); 176 177 // Spin until we can acquire the write lock. 178 Operand spinLabel = Label(); 179 context.MarkLabel(spinLabel); 180 181 // Old value must be 0 to continue (we gained the write lock) 182 context.BranchIfTrue(spinLabel, context.CompareAndSwap(writeLockPtr, Const(0), Const(1))); 183 184 // Increment reader count. 185 EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(1)); 186 187 // Release write lock. 188 context.CompareAndSwap(writeLockPtr, Const(1), Const(0)); 189 } 190 191 private static void EmitNativeReaderLockRelease(EmitterContext context, IntPtr nativeReaderLockPtr) 192 { 193 // Decrement reader count. 194 EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(-1)); 195 } 196 } 197 }