PartialUnmaps.cs 16 KB

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