PartialUnmaps.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. using ARMeilleure.Signal;
  2. using ARMeilleure.Translation;
  3. using NUnit.Framework;
  4. using Ryujinx.Common.Memory.PartialUnmaps;
  5. using Ryujinx.Cpu;
  6. using Ryujinx.Cpu.Jit;
  7. using Ryujinx.Memory;
  8. using Ryujinx.Memory.Tests;
  9. using Ryujinx.Memory.Tracking;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Runtime.CompilerServices;
  13. using System.Runtime.InteropServices;
  14. using System.Threading;
  15. namespace Ryujinx.Tests.Memory
  16. {
  17. [TestFixture]
  18. internal class PartialUnmaps
  19. {
  20. private static Translator _translator;
  21. private (MemoryBlock virt, MemoryBlock mirror, MemoryEhMeilleure exceptionHandler) GetVirtual(ulong asSize)
  22. {
  23. MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
  24. var addressSpace = new MemoryBlock(asSize, asFlags);
  25. var addressSpaceMirror = new MemoryBlock(asSize, asFlags);
  26. var tracking = new MemoryTracking(new MockVirtualMemoryManager(asSize, 0x1000), 0x1000);
  27. var exceptionHandler = new MemoryEhMeilleure(addressSpace, addressSpaceMirror, tracking);
  28. return (addressSpace, addressSpaceMirror, exceptionHandler);
  29. }
  30. private int CountThreads(ref PartialUnmapState state)
  31. {
  32. int count = 0;
  33. ref var ids = ref state.LocalCounts.ThreadIds;
  34. for (int i = 0; i < ids.Length; i++)
  35. {
  36. if (ids[i] != 0)
  37. {
  38. count++;
  39. }
  40. }
  41. return count;
  42. }
  43. private void EnsureTranslator()
  44. {
  45. // Create a translator, as one is needed to register the signal handler or emit methods.
  46. _translator ??= new Translator(new JitMemoryAllocator(), new MockMemoryManager(), true);
  47. }
  48. [Test]
  49. public void PartialUnmap([Values] bool readOnly)
  50. {
  51. if (OperatingSystem.IsMacOS())
  52. {
  53. // Memory aliasing tests fail on CI at the moment.
  54. return;
  55. }
  56. // Set up an address space to test partial unmapping.
  57. // Should register the signal handler to deal with this on Windows.
  58. ulong vaSize = 0x100000;
  59. // The first 0x100000 is mapped to start. It is replaced from the center with the 0x200000 mapping.
  60. var backing = new MemoryBlock(vaSize * 2, MemoryAllocationFlags.Mirrorable);
  61. (MemoryBlock unusedMainMemory, MemoryBlock memory, MemoryEhMeilleure exceptionHandler) = GetVirtual(vaSize * 2);
  62. EnsureTranslator();
  63. ref var state = ref PartialUnmapState.GetRef();
  64. try
  65. {
  66. // Globally reset the struct for handling partial unmap races.
  67. PartialUnmapState.Reset();
  68. bool shouldAccess = true;
  69. bool error = false;
  70. // Create a large mapping.
  71. memory.MapView(backing, 0, 0, vaSize);
  72. if (readOnly)
  73. {
  74. memory.Reprotect(0, vaSize, MemoryPermission.Read);
  75. }
  76. Thread testThread;
  77. if (readOnly)
  78. {
  79. // Write a value to the physical memory, then try to read it repeately from virtual.
  80. // It should not change.
  81. testThread = new Thread(() =>
  82. {
  83. int i = 12345;
  84. backing.Write(vaSize - 0x1000, i);
  85. while (shouldAccess)
  86. {
  87. if (memory.Read<int>(vaSize - 0x1000) != i)
  88. {
  89. error = true;
  90. shouldAccess = false;
  91. }
  92. }
  93. });
  94. }
  95. else
  96. {
  97. // Repeatedly write and check the value on the last page of the mapping on another thread.
  98. testThread = new Thread(() =>
  99. {
  100. int i = 0;
  101. while (shouldAccess)
  102. {
  103. memory.Write(vaSize - 0x1000, i);
  104. if (memory.Read<int>(vaSize - 0x1000) != i)
  105. {
  106. error = true;
  107. shouldAccess = false;
  108. }
  109. i++;
  110. }
  111. });
  112. }
  113. testThread.Start();
  114. // Create a smaller mapping, covering the larger mapping.
  115. // Immediately try to write to the part of the larger mapping that did not change.
  116. // Do this a lot, with the smaller mapping gradually increasing in size. Should not crash, data should not be lost.
  117. ulong pageSize = 0x1000;
  118. int mappingExpandCount = (int)(vaSize / (pageSize * 2)) - 1;
  119. ulong vaCenter = vaSize / 2;
  120. for (int i = 1; i <= mappingExpandCount; i++)
  121. {
  122. ulong start = vaCenter - (pageSize * (ulong)i);
  123. ulong size = pageSize * (ulong)i * 2;
  124. ulong startPa = start + vaSize;
  125. memory.MapView(backing, startPa, start, size);
  126. }
  127. // On Windows, this should put unmap counts on the thread local map.
  128. if (OperatingSystem.IsWindows())
  129. {
  130. // One thread should be present on the thread local map. Trimming should remove it.
  131. Assert.AreEqual(1, CountThreads(ref state));
  132. }
  133. shouldAccess = false;
  134. testThread.Join();
  135. Assert.False(error);
  136. string test = null;
  137. try
  138. {
  139. test.IndexOf('1');
  140. }
  141. catch (NullReferenceException)
  142. {
  143. // This shouldn't freeze.
  144. }
  145. if (OperatingSystem.IsWindows())
  146. {
  147. state.TrimThreads();
  148. Assert.AreEqual(0, CountThreads(ref state));
  149. }
  150. /*
  151. * Use this to test invalid access. Can't put this in the test suite unfortunately as invalid access crashes the test process.
  152. * memory.Reprotect(vaSize - 0x1000, 0x1000, MemoryPermission.None);
  153. * //memory.UnmapView(backing, vaSize - 0x1000, 0x1000);
  154. * memory.Read<int>(vaSize - 0x1000);
  155. */
  156. }
  157. finally
  158. {
  159. exceptionHandler.Dispose();
  160. unusedMainMemory.Dispose();
  161. memory.Dispose();
  162. backing.Dispose();
  163. }
  164. }
  165. [Test]
  166. public unsafe void PartialUnmapNative()
  167. {
  168. if (OperatingSystem.IsMacOS())
  169. {
  170. // Memory aliasing tests fail on CI at the moment.
  171. return;
  172. }
  173. // Set up an address space to test partial unmapping.
  174. // Should register the signal handler to deal with this on Windows.
  175. ulong vaSize = 0x100000;
  176. // The first 0x100000 is mapped to start. It is replaced from the center with the 0x200000 mapping.
  177. var backing = new MemoryBlock(vaSize * 2, MemoryAllocationFlags.Mirrorable);
  178. (MemoryBlock mainMemory, MemoryBlock unusedMirror, MemoryEhMeilleure exceptionHandler) = GetVirtual(vaSize * 2);
  179. EnsureTranslator();
  180. ref var state = ref PartialUnmapState.GetRef();
  181. // Create some state to be used for managing the native writing loop.
  182. int stateSize = Unsafe.SizeOf<NativeWriteLoopState>();
  183. var statePtr = Marshal.AllocHGlobal(stateSize);
  184. Unsafe.InitBlockUnaligned((void*)statePtr, 0, (uint)stateSize);
  185. ref NativeWriteLoopState writeLoopState = ref Unsafe.AsRef<NativeWriteLoopState>((void*)statePtr);
  186. writeLoopState.Running = 1;
  187. writeLoopState.Error = 0;
  188. try
  189. {
  190. // Globally reset the struct for handling partial unmap races.
  191. PartialUnmapState.Reset();
  192. // Create a large mapping.
  193. mainMemory.MapView(backing, 0, 0, vaSize);
  194. var writeFunc = TestMethods.GenerateDebugNativeWriteLoop();
  195. IntPtr writePtr = mainMemory.GetPointer(vaSize - 0x1000, 4);
  196. Thread testThread = new Thread(() =>
  197. {
  198. writeFunc(statePtr, writePtr);
  199. });
  200. testThread.Start();
  201. // Create a smaller mapping, covering the larger mapping.
  202. // Immediately try to write to the part of the larger mapping that did not change.
  203. // Do this a lot, with the smaller mapping gradually increasing in size. Should not crash, data should not be lost.
  204. ulong pageSize = 0x1000;
  205. int mappingExpandCount = (int)(vaSize / (pageSize * 2)) - 1;
  206. ulong vaCenter = vaSize / 2;
  207. for (int i = 1; i <= mappingExpandCount; i++)
  208. {
  209. ulong start = vaCenter - (pageSize * (ulong)i);
  210. ulong size = pageSize * (ulong)i * 2;
  211. ulong startPa = start + vaSize;
  212. mainMemory.MapView(backing, startPa, start, size);
  213. }
  214. writeLoopState.Running = 0;
  215. testThread.Join();
  216. Assert.False(writeLoopState.Error != 0);
  217. }
  218. finally
  219. {
  220. Marshal.FreeHGlobal(statePtr);
  221. exceptionHandler.Dispose();
  222. mainMemory.Dispose();
  223. unusedMirror.Dispose();
  224. backing.Dispose();
  225. }
  226. }
  227. [Test]
  228. public void ThreadLocalMap()
  229. {
  230. if (!OperatingSystem.IsWindows())
  231. {
  232. // Only test in Windows, as this is only used on Windows and uses Windows APIs for trimming.
  233. return;
  234. }
  235. PartialUnmapState.Reset();
  236. ref var state = ref PartialUnmapState.GetRef();
  237. bool running = true;
  238. var testThread = new Thread(() =>
  239. {
  240. if (!OperatingSystem.IsWindows())
  241. {
  242. // Need this here to avoid a warning.
  243. return;
  244. }
  245. PartialUnmapState.GetRef().RetryFromAccessViolation();
  246. while (running)
  247. {
  248. Thread.Sleep(1);
  249. }
  250. });
  251. testThread.Start();
  252. Thread.Sleep(200);
  253. Assert.AreEqual(1, CountThreads(ref state));
  254. // Trimming should not remove the thread as it's still active.
  255. state.TrimThreads();
  256. Assert.AreEqual(1, CountThreads(ref state));
  257. running = false;
  258. testThread.Join();
  259. // Should trim now that it's inactive.
  260. state.TrimThreads();
  261. Assert.AreEqual(0, CountThreads(ref state));
  262. }
  263. [Test]
  264. public unsafe void ThreadLocalMapNative()
  265. {
  266. if (!OperatingSystem.IsWindows())
  267. {
  268. // Only test in Windows, as this is only used on Windows and uses Windows APIs for trimming.
  269. return;
  270. }
  271. EnsureTranslator();
  272. PartialUnmapState.Reset();
  273. ref var state = ref PartialUnmapState.GetRef();
  274. fixed (void* localMap = &state.LocalCounts)
  275. {
  276. var getOrReserve = TestMethods.GenerateDebugThreadLocalMapGetOrReserve((IntPtr)localMap);
  277. for (int i = 0; i < ThreadLocalMap<int>.MapSize; i++)
  278. {
  279. // Should obtain the index matching the call #.
  280. Assert.AreEqual(i, getOrReserve(i + 1, i));
  281. // Check that this and all previously reserved thread IDs and struct contents are intact.
  282. for (int j = 0; j <= i; j++)
  283. {
  284. Assert.AreEqual(j + 1, state.LocalCounts.ThreadIds[j]);
  285. Assert.AreEqual(j, state.LocalCounts.Structs[j]);
  286. }
  287. }
  288. // Trying to reserve again when the map is full should return -1.
  289. Assert.AreEqual(-1, getOrReserve(200, 0));
  290. for (int i = 0; i < ThreadLocalMap<int>.MapSize; i++)
  291. {
  292. // Should obtain the index matching the call #, as it already exists.
  293. Assert.AreEqual(i, getOrReserve(i + 1, -1));
  294. // The struct should not be reset to -1.
  295. Assert.AreEqual(i, state.LocalCounts.Structs[i]);
  296. }
  297. // Clear one of the ids as if it were freed.
  298. state.LocalCounts.ThreadIds[13] = 0;
  299. // GetOrReserve should now obtain and return 13.
  300. Assert.AreEqual(13, getOrReserve(300, 301));
  301. Assert.AreEqual(300, state.LocalCounts.ThreadIds[13]);
  302. Assert.AreEqual(301, state.LocalCounts.Structs[13]);
  303. }
  304. }
  305. [Test]
  306. public void NativeReaderWriterLock()
  307. {
  308. var rwLock = new NativeReaderWriterLock();
  309. var threads = new List<Thread>();
  310. int value = 0;
  311. bool running = true;
  312. bool error = false;
  313. int readersAllowed = 1;
  314. for (int i = 0; i < 5; i++)
  315. {
  316. var readThread = new Thread(() =>
  317. {
  318. int count = 0;
  319. while (running)
  320. {
  321. rwLock.AcquireReaderLock();
  322. int originalValue = Thread.VolatileRead(ref value);
  323. count++;
  324. // Spin a bit.
  325. for (int i = 0; i < 100; i++)
  326. {
  327. if (Thread.VolatileRead(ref readersAllowed) == 0)
  328. {
  329. error = true;
  330. running = false;
  331. }
  332. }
  333. // Should not change while the lock is held.
  334. if (Thread.VolatileRead(ref value) != originalValue)
  335. {
  336. error = true;
  337. running = false;
  338. }
  339. rwLock.ReleaseReaderLock();
  340. }
  341. });
  342. threads.Add(readThread);
  343. }
  344. for (int i = 0; i < 2; i++)
  345. {
  346. var writeThread = new Thread(() =>
  347. {
  348. int count = 0;
  349. while (running)
  350. {
  351. rwLock.AcquireReaderLock();
  352. rwLock.UpgradeToWriterLock();
  353. Thread.Sleep(2);
  354. count++;
  355. Interlocked.Exchange(ref readersAllowed, 0);
  356. for (int i = 0; i < 10; i++)
  357. {
  358. Interlocked.Increment(ref value);
  359. }
  360. Interlocked.Exchange(ref readersAllowed, 1);
  361. rwLock.DowngradeFromWriterLock();
  362. rwLock.ReleaseReaderLock();
  363. Thread.Sleep(1);
  364. }
  365. });
  366. threads.Add(writeThread);
  367. }
  368. foreach (var thread in threads)
  369. {
  370. thread.Start();
  371. }
  372. Thread.Sleep(1000);
  373. running = false;
  374. foreach (var thread in threads)
  375. {
  376. thread.Join();
  377. }
  378. Assert.False(error);
  379. }
  380. }
  381. }