WindowsPartialUnmapHandler.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. using ARMeilleure.IntermediateRepresentation;
  2. using ARMeilleure.Translation;
  3. using Ryujinx.Common.Memory.PartialUnmaps;
  4. using System;
  5. using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
  6. namespace ARMeilleure.Signal
  7. {
  8. /// <summary>
  9. /// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods.
  10. /// </summary>
  11. internal static class WindowsPartialUnmapHandler
  12. {
  13. public static Operand EmitRetryFromAccessViolation(EmitterContext context)
  14. {
  15. IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState;
  16. IntPtr localCountsPtr = IntPtr.Add(partialRemapStatePtr, PartialUnmapState.LocalCountsOffset);
  17. // Get the lock first.
  18. EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
  19. IntPtr getCurrentThreadId = WindowsSignalHandlerRegistration.GetCurrentThreadIdFunc();
  20. Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32);
  21. Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0));
  22. Operand endLabel = Label();
  23. Operand retry = context.AllocateLocal(OperandType.I32);
  24. Operand threadIndexValidLabel = Label();
  25. context.BranchIfFalse(threadIndexValidLabel, context.ICompareEqual(threadIndex, Const(-1)));
  26. context.Copy(retry, Const(1)); // Always retry when thread local cannot be allocated.
  27. context.Branch(endLabel);
  28. context.MarkLabel(threadIndexValidLabel);
  29. Operand threadLocalPartialUnmapsPtr = EmitThreadLocalMapIntGetValuePtr(context, localCountsPtr, threadIndex);
  30. Operand threadLocalPartialUnmaps = context.Load(OperandType.I32, threadLocalPartialUnmapsPtr);
  31. Operand partialUnmapsCount = context.Load(OperandType.I32, Const((ulong)IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapsCountOffset)));
  32. context.Copy(retry, context.ICompareNotEqual(threadLocalPartialUnmaps, partialUnmapsCount));
  33. Operand noRetryLabel = Label();
  34. context.BranchIfFalse(noRetryLabel, retry);
  35. // if (retry) {
  36. context.Store(threadLocalPartialUnmapsPtr, partialUnmapsCount);
  37. context.Branch(endLabel);
  38. context.MarkLabel(noRetryLabel);
  39. // }
  40. context.MarkLabel(endLabel);
  41. // Finally, release the lock and return the retry value.
  42. EmitNativeReaderLockRelease(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
  43. return retry;
  44. }
  45. public static Operand EmitThreadLocalMapIntGetOrReserve(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand initialState)
  46. {
  47. Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.ThreadIdsOffset));
  48. Operand i = context.AllocateLocal(OperandType.I32);
  49. context.Copy(i, Const(0));
  50. // (Loop 1) Check all slots for a matching Thread ID (while also trying to allocate)
  51. Operand endLabel = Label();
  52. Operand loopLabel = Label();
  53. context.MarkLabel(loopLabel);
  54. Operand offset = context.Multiply(i, Const(sizeof(int)));
  55. Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset));
  56. // Check that this slot has the thread ID.
  57. Operand existingId = context.CompareAndSwap(idPtr, threadId, threadId);
  58. // If it was already the thread ID, then we just need to return i.
  59. context.BranchIfTrue(endLabel, context.ICompareEqual(existingId, threadId));
  60. context.Copy(i, context.Add(i, Const(1)));
  61. context.BranchIfTrue(loopLabel, context.ICompareLess(i, Const(ThreadLocalMap<int>.MapSize)));
  62. // (Loop 2) Try take a slot that is 0 with our Thread ID.
  63. context.Copy(i, Const(0)); // Reset i.
  64. Operand loop2Label = Label();
  65. context.MarkLabel(loop2Label);
  66. Operand offset2 = context.Multiply(i, Const(sizeof(int)));
  67. Operand idPtr2 = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset2));
  68. // Try and swap in the thread id on top of 0.
  69. Operand existingId2 = context.CompareAndSwap(idPtr2, Const(0), threadId);
  70. Operand idNot0Label = Label();
  71. // If it was 0, then we need to initialize the struct entry and return i.
  72. context.BranchIfFalse(idNot0Label, context.ICompareEqual(existingId2, Const(0)));
  73. Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.StructsOffset));
  74. Operand structPtr = context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset2));
  75. context.Store(structPtr, initialState);
  76. context.Branch(endLabel);
  77. context.MarkLabel(idNot0Label);
  78. context.Copy(i, context.Add(i, Const(1)));
  79. context.BranchIfTrue(loop2Label, context.ICompareLess(i, Const(ThreadLocalMap<int>.MapSize)));
  80. context.Copy(i, Const(-1)); // Could not place the thread in the list.
  81. context.MarkLabel(endLabel);
  82. return context.Copy(i);
  83. }
  84. private static Operand EmitThreadLocalMapIntGetValuePtr(EmitterContext context, IntPtr threadLocalMapPtr, Operand index)
  85. {
  86. Operand offset = context.Multiply(index, Const(sizeof(int)));
  87. Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.StructsOffset));
  88. return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset));
  89. }
  90. private static void EmitThreadLocalMapIntRelease(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand index)
  91. {
  92. Operand offset = context.Multiply(index, Const(sizeof(int)));
  93. Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.ThreadIdsOffset));
  94. Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset));
  95. context.CompareAndSwap(idPtr, threadId, Const(0));
  96. }
  97. private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive)
  98. {
  99. Operand loop = Label();
  100. context.MarkLabel(loop);
  101. Operand initial = context.Load(OperandType.I32, ptr);
  102. Operand newValue = context.Add(initial, additive);
  103. Operand replaced = context.CompareAndSwap(ptr, initial, newValue);
  104. context.BranchIfFalse(loop, context.ICompareEqual(initial, replaced));
  105. }
  106. private static void EmitNativeReaderLockAcquire(EmitterContext context, IntPtr nativeReaderLockPtr)
  107. {
  108. Operand writeLockPtr = Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.WriteLockOffset));
  109. // Spin until we can acquire the write lock.
  110. Operand spinLabel = Label();
  111. context.MarkLabel(spinLabel);
  112. // Old value must be 0 to continue (we gained the write lock)
  113. context.BranchIfTrue(spinLabel, context.CompareAndSwap(writeLockPtr, Const(0), Const(1)));
  114. // Increment reader count.
  115. EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(1));
  116. // Release write lock.
  117. context.CompareAndSwap(writeLockPtr, Const(1), Const(0));
  118. }
  119. private static void EmitNativeReaderLockRelease(EmitterContext context, IntPtr nativeReaderLockPtr)
  120. {
  121. // Decrement reader count.
  122. EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(-1));
  123. }
  124. }
  125. }