/ src / ARMeilleure / Signal / WindowsPartialUnmapHandler.cs
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  }