host_memory.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #ifdef _WIN32
  4. #include <iterator>
  5. #include <unordered_map>
  6. #include <boost/icl/separate_interval_set.hpp>
  7. #include <windows.h>
  8. #include "common/dynamic_library.h"
  9. #elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
  10. #ifndef _GNU_SOURCE
  11. #define _GNU_SOURCE
  12. #endif
  13. #include <boost/icl/interval_set.hpp>
  14. #include <fcntl.h>
  15. #include <sys/mman.h>
  16. #include <unistd.h>
  17. #include "common/scope_exit.h"
  18. #endif // ^^^ Linux ^^^
  19. #include <mutex>
  20. #include "common/alignment.h"
  21. #include "common/assert.h"
  22. #include "common/host_memory.h"
  23. #include "common/logging/log.h"
  24. namespace Common {
  25. constexpr size_t PageAlignment = 0x1000;
  26. constexpr size_t HugePageSize = 0x200000;
  27. #ifdef _WIN32
  28. // Manually imported for MinGW compatibility
  29. #ifndef MEM_RESERVE_PLACEHOLDER
  30. #define MEM_RESERVE_PLACEHOLDER 0x00040000
  31. #endif
  32. #ifndef MEM_REPLACE_PLACEHOLDER
  33. #define MEM_REPLACE_PLACEHOLDER 0x00004000
  34. #endif
  35. #ifndef MEM_COALESCE_PLACEHOLDERS
  36. #define MEM_COALESCE_PLACEHOLDERS 0x00000001
  37. #endif
  38. #ifndef MEM_PRESERVE_PLACEHOLDER
  39. #define MEM_PRESERVE_PLACEHOLDER 0x00000002
  40. #endif
  41. using PFN_CreateFileMapping2 = _Ret_maybenull_ HANDLE(WINAPI*)(
  42. _In_ HANDLE File, _In_opt_ SECURITY_ATTRIBUTES* SecurityAttributes, _In_ ULONG DesiredAccess,
  43. _In_ ULONG PageProtection, _In_ ULONG AllocationAttributes, _In_ ULONG64 MaximumSize,
  44. _In_opt_ PCWSTR Name,
  45. _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters,
  46. _In_ ULONG ParameterCount);
  47. using PFN_VirtualAlloc2 = _Ret_maybenull_ PVOID(WINAPI*)(
  48. _In_opt_ HANDLE Process, _In_opt_ PVOID BaseAddress, _In_ SIZE_T Size,
  49. _In_ ULONG AllocationType, _In_ ULONG PageProtection,
  50. _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters,
  51. _In_ ULONG ParameterCount);
  52. using PFN_MapViewOfFile3 = _Ret_maybenull_ PVOID(WINAPI*)(
  53. _In_ HANDLE FileMapping, _In_opt_ HANDLE Process, _In_opt_ PVOID BaseAddress,
  54. _In_ ULONG64 Offset, _In_ SIZE_T ViewSize, _In_ ULONG AllocationType, _In_ ULONG PageProtection,
  55. _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters,
  56. _In_ ULONG ParameterCount);
  57. using PFN_UnmapViewOfFile2 = BOOL(WINAPI*)(_In_ HANDLE Process, _In_ PVOID BaseAddress,
  58. _In_ ULONG UnmapFlags);
  59. template <typename T>
  60. static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn) {
  61. if (!dll.GetSymbol(name, &pfn)) {
  62. LOG_CRITICAL(HW_Memory, "Failed to load {}", name);
  63. throw std::bad_alloc{};
  64. }
  65. }
  66. class HostMemory::Impl {
  67. public:
  68. explicit Impl(size_t backing_size_, size_t virtual_size_)
  69. : backing_size{backing_size_}, virtual_size{virtual_size_}, process{GetCurrentProcess()},
  70. kernelbase_dll("Kernelbase") {
  71. if (!kernelbase_dll.IsOpen()) {
  72. LOG_CRITICAL(HW_Memory, "Failed to load Kernelbase.dll");
  73. throw std::bad_alloc{};
  74. }
  75. GetFuncAddress(kernelbase_dll, "CreateFileMapping2", pfn_CreateFileMapping2);
  76. GetFuncAddress(kernelbase_dll, "VirtualAlloc2", pfn_VirtualAlloc2);
  77. GetFuncAddress(kernelbase_dll, "MapViewOfFile3", pfn_MapViewOfFile3);
  78. GetFuncAddress(kernelbase_dll, "UnmapViewOfFile2", pfn_UnmapViewOfFile2);
  79. // Allocate backing file map
  80. backing_handle =
  81. pfn_CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ,
  82. PAGE_READWRITE, SEC_COMMIT, backing_size, nullptr, nullptr, 0);
  83. if (!backing_handle) {
  84. LOG_CRITICAL(HW_Memory, "Failed to allocate {} MiB of backing memory",
  85. backing_size >> 20);
  86. throw std::bad_alloc{};
  87. }
  88. // Allocate a virtual memory for the backing file map as placeholder
  89. backing_base = static_cast<u8*>(pfn_VirtualAlloc2(process, nullptr, backing_size,
  90. MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
  91. PAGE_NOACCESS, nullptr, 0));
  92. if (!backing_base) {
  93. Release();
  94. LOG_CRITICAL(HW_Memory, "Failed to reserve {} MiB of virtual memory",
  95. backing_size >> 20);
  96. throw std::bad_alloc{};
  97. }
  98. // Map backing placeholder
  99. void* const ret = pfn_MapViewOfFile3(backing_handle, process, backing_base, 0, backing_size,
  100. MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0);
  101. if (ret != backing_base) {
  102. Release();
  103. LOG_CRITICAL(HW_Memory, "Failed to map {} MiB of virtual memory", backing_size >> 20);
  104. throw std::bad_alloc{};
  105. }
  106. // Allocate virtual address placeholder
  107. virtual_base = static_cast<u8*>(pfn_VirtualAlloc2(process, nullptr, virtual_size,
  108. MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
  109. PAGE_NOACCESS, nullptr, 0));
  110. if (!virtual_base) {
  111. Release();
  112. LOG_CRITICAL(HW_Memory, "Failed to reserve {} GiB of virtual memory",
  113. virtual_size >> 30);
  114. throw std::bad_alloc{};
  115. }
  116. }
  117. ~Impl() {
  118. Release();
  119. }
  120. void Map(size_t virtual_offset, size_t host_offset, size_t length) {
  121. std::unique_lock lock{placeholder_mutex};
  122. if (!IsNiechePlaceholder(virtual_offset, length)) {
  123. Split(virtual_offset, length);
  124. }
  125. ASSERT(placeholders.find({virtual_offset, virtual_offset + length}) == placeholders.end());
  126. TrackPlaceholder(virtual_offset, host_offset, length);
  127. MapView(virtual_offset, host_offset, length);
  128. }
  129. void Unmap(size_t virtual_offset, size_t length) {
  130. std::scoped_lock lock{placeholder_mutex};
  131. // Unmap until there are no more placeholders
  132. while (UnmapOnePlaceholder(virtual_offset, length)) {
  133. }
  134. }
  135. void Protect(size_t virtual_offset, size_t length, bool read, bool write) {
  136. DWORD new_flags{};
  137. if (read && write) {
  138. new_flags = PAGE_READWRITE;
  139. } else if (read && !write) {
  140. new_flags = PAGE_READONLY;
  141. } else if (!read && !write) {
  142. new_flags = PAGE_NOACCESS;
  143. } else {
  144. UNIMPLEMENTED_MSG("Protection flag combination read={} write={}", read, write);
  145. }
  146. const size_t virtual_end = virtual_offset + length;
  147. std::scoped_lock lock{placeholder_mutex};
  148. auto [it, end] = placeholders.equal_range({virtual_offset, virtual_end});
  149. while (it != end) {
  150. const size_t offset = std::max(it->lower(), virtual_offset);
  151. const size_t protect_length = std::min(it->upper(), virtual_end) - offset;
  152. DWORD old_flags{};
  153. if (!VirtualProtect(virtual_base + offset, protect_length, new_flags, &old_flags)) {
  154. LOG_CRITICAL(HW_Memory, "Failed to change virtual memory protect rules");
  155. }
  156. ++it;
  157. }
  158. }
  159. const size_t backing_size; ///< Size of the backing memory in bytes
  160. const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
  161. u8* backing_base{};
  162. u8* virtual_base{};
  163. private:
  164. /// Release all resources in the object
  165. void Release() {
  166. if (!placeholders.empty()) {
  167. for (const auto& placeholder : placeholders) {
  168. if (!pfn_UnmapViewOfFile2(process, virtual_base + placeholder.lower(),
  169. MEM_PRESERVE_PLACEHOLDER)) {
  170. LOG_CRITICAL(HW_Memory, "Failed to unmap virtual memory placeholder");
  171. }
  172. }
  173. Coalesce(0, virtual_size);
  174. }
  175. if (virtual_base) {
  176. if (!VirtualFree(virtual_base, 0, MEM_RELEASE)) {
  177. LOG_CRITICAL(HW_Memory, "Failed to free virtual memory");
  178. }
  179. }
  180. if (backing_base) {
  181. if (!pfn_UnmapViewOfFile2(process, backing_base, MEM_PRESERVE_PLACEHOLDER)) {
  182. LOG_CRITICAL(HW_Memory, "Failed to unmap backing memory placeholder");
  183. }
  184. if (!VirtualFreeEx(process, backing_base, 0, MEM_RELEASE)) {
  185. LOG_CRITICAL(HW_Memory, "Failed to free backing memory");
  186. }
  187. }
  188. if (!CloseHandle(backing_handle)) {
  189. LOG_CRITICAL(HW_Memory, "Failed to free backing memory file handle");
  190. }
  191. }
  192. /// Unmap one placeholder in the given range (partial unmaps are supported)
  193. /// Return true when there are no more placeholders to unmap
  194. bool UnmapOnePlaceholder(size_t virtual_offset, size_t length) {
  195. const auto it = placeholders.find({virtual_offset, virtual_offset + length});
  196. const auto begin = placeholders.begin();
  197. const auto end = placeholders.end();
  198. if (it == end) {
  199. return false;
  200. }
  201. const size_t placeholder_begin = it->lower();
  202. const size_t placeholder_end = it->upper();
  203. const size_t unmap_begin = std::max(virtual_offset, placeholder_begin);
  204. const size_t unmap_end = std::min(virtual_offset + length, placeholder_end);
  205. ASSERT(unmap_begin >= placeholder_begin && unmap_begin < placeholder_end);
  206. ASSERT(unmap_end <= placeholder_end && unmap_end > placeholder_begin);
  207. const auto host_pointer_it = placeholder_host_pointers.find(placeholder_begin);
  208. ASSERT(host_pointer_it != placeholder_host_pointers.end());
  209. const size_t host_offset = host_pointer_it->second;
  210. const bool split_left = unmap_begin > placeholder_begin;
  211. const bool split_right = unmap_end < placeholder_end;
  212. if (!pfn_UnmapViewOfFile2(process, virtual_base + placeholder_begin,
  213. MEM_PRESERVE_PLACEHOLDER)) {
  214. LOG_CRITICAL(HW_Memory, "Failed to unmap placeholder");
  215. }
  216. // If we have to remap memory regions due to partial unmaps, we are in a data race as
  217. // Windows doesn't support remapping memory without unmapping first. Avoid adding any extra
  218. // logic within the panic region described below.
  219. // Panic region, we are in a data race right now
  220. if (split_left || split_right) {
  221. Split(unmap_begin, unmap_end - unmap_begin);
  222. }
  223. if (split_left) {
  224. MapView(placeholder_begin, host_offset, unmap_begin - placeholder_begin);
  225. }
  226. if (split_right) {
  227. MapView(unmap_end, host_offset + unmap_end - placeholder_begin,
  228. placeholder_end - unmap_end);
  229. }
  230. // End panic region
  231. size_t coalesce_begin = unmap_begin;
  232. if (!split_left) {
  233. // Try to coalesce pages to the left
  234. coalesce_begin = it == begin ? 0 : std::prev(it)->upper();
  235. if (coalesce_begin != placeholder_begin) {
  236. Coalesce(coalesce_begin, unmap_end - coalesce_begin);
  237. }
  238. }
  239. if (!split_right) {
  240. // Try to coalesce pages to the right
  241. const auto next = std::next(it);
  242. const size_t next_begin = next == end ? virtual_size : next->lower();
  243. if (placeholder_end != next_begin) {
  244. // We can coalesce to the right
  245. Coalesce(coalesce_begin, next_begin - coalesce_begin);
  246. }
  247. }
  248. // Remove and reinsert placeholder trackers
  249. UntrackPlaceholder(it);
  250. if (split_left) {
  251. TrackPlaceholder(placeholder_begin, host_offset, unmap_begin - placeholder_begin);
  252. }
  253. if (split_right) {
  254. TrackPlaceholder(unmap_end, host_offset + unmap_end - placeholder_begin,
  255. placeholder_end - unmap_end);
  256. }
  257. return true;
  258. }
  259. void MapView(size_t virtual_offset, size_t host_offset, size_t length) {
  260. if (!pfn_MapViewOfFile3(backing_handle, process, virtual_base + virtual_offset, host_offset,
  261. length, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0)) {
  262. LOG_CRITICAL(HW_Memory, "Failed to map placeholder");
  263. }
  264. }
  265. void Split(size_t virtual_offset, size_t length) {
  266. if (!VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_base + virtual_offset), length,
  267. MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
  268. LOG_CRITICAL(HW_Memory, "Failed to split placeholder");
  269. }
  270. }
  271. void Coalesce(size_t virtual_offset, size_t length) {
  272. if (!VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_base + virtual_offset), length,
  273. MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) {
  274. LOG_CRITICAL(HW_Memory, "Failed to coalesce placeholders");
  275. }
  276. }
  277. void TrackPlaceholder(size_t virtual_offset, size_t host_offset, size_t length) {
  278. placeholders.insert({virtual_offset, virtual_offset + length});
  279. placeholder_host_pointers.emplace(virtual_offset, host_offset);
  280. }
  281. void UntrackPlaceholder(boost::icl::separate_interval_set<size_t>::iterator it) {
  282. placeholder_host_pointers.erase(it->lower());
  283. placeholders.erase(it);
  284. }
  285. /// Return true when a given memory region is a "nieche" and the placeholders don't have to be
  286. /// split.
  287. bool IsNiechePlaceholder(size_t virtual_offset, size_t length) const {
  288. const auto it = placeholders.upper_bound({virtual_offset, virtual_offset + length});
  289. if (it != placeholders.end() && it->lower() == virtual_offset + length) {
  290. return it == placeholders.begin() ? virtual_offset == 0
  291. : std::prev(it)->upper() == virtual_offset;
  292. }
  293. return false;
  294. }
  295. HANDLE process{}; ///< Current process handle
  296. HANDLE backing_handle{}; ///< File based backing memory
  297. DynamicLibrary kernelbase_dll;
  298. PFN_CreateFileMapping2 pfn_CreateFileMapping2{};
  299. PFN_VirtualAlloc2 pfn_VirtualAlloc2{};
  300. PFN_MapViewOfFile3 pfn_MapViewOfFile3{};
  301. PFN_UnmapViewOfFile2 pfn_UnmapViewOfFile2{};
  302. std::mutex placeholder_mutex; ///< Mutex for placeholders
  303. boost::icl::separate_interval_set<size_t> placeholders; ///< Mapped placeholders
  304. std::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
  305. };
  306. #elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
  307. class HostMemory::Impl {
  308. public:
  309. explicit Impl(size_t backing_size_, size_t virtual_size_)
  310. : backing_size{backing_size_}, virtual_size{virtual_size_} {
  311. bool good = false;
  312. SCOPE_EXIT({
  313. if (!good) {
  314. Release();
  315. }
  316. });
  317. long page_size = sysconf(_SC_PAGESIZE);
  318. if (page_size != 0x1000) {
  319. LOG_CRITICAL(HW_Memory, "page size {:#x} is incompatible with 4K paging", page_size);
  320. throw std::bad_alloc{};
  321. }
  322. // Backing memory initialization
  323. #if defined(__FreeBSD__) && __FreeBSD__ < 13
  324. // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30
  325. fd = shm_open(SHM_ANON, O_RDWR, 0600);
  326. #else
  327. fd = memfd_create("HostMemory", 0);
  328. #endif
  329. if (fd == -1) {
  330. LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno));
  331. throw std::bad_alloc{};
  332. }
  333. // Defined to extend the file with zeros
  334. int ret = ftruncate(fd, backing_size);
  335. if (ret != 0) {
  336. LOG_CRITICAL(HW_Memory, "ftruncate failed with {}, are you out-of-memory?",
  337. strerror(errno));
  338. throw std::bad_alloc{};
  339. }
  340. backing_base = static_cast<u8*>(
  341. mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
  342. if (backing_base == MAP_FAILED) {
  343. LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
  344. throw std::bad_alloc{};
  345. }
  346. // Virtual memory initialization
  347. #if defined(__FreeBSD__)
  348. virtual_base =
  349. static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE,
  350. MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED_SUPER, -1, 0));
  351. if (virtual_base == MAP_FAILED) {
  352. virtual_base = static_cast<u8*>(
  353. mmap(nullptr, virtual_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
  354. if (virtual_base == MAP_FAILED) {
  355. LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
  356. throw std::bad_alloc{};
  357. }
  358. }
  359. #else
  360. virtual_base = static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE,
  361. MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
  362. if (virtual_base == MAP_FAILED) {
  363. LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
  364. throw std::bad_alloc{};
  365. }
  366. madvise(virtual_base, virtual_size, MADV_HUGEPAGE);
  367. #endif
  368. placeholders.add({0, virtual_size});
  369. good = true;
  370. }
  371. ~Impl() {
  372. Release();
  373. }
  374. void Map(size_t virtual_offset, size_t host_offset, size_t length) {
  375. {
  376. std::scoped_lock lock{placeholder_mutex};
  377. placeholders.subtract({virtual_offset, virtual_offset + length});
  378. }
  379. void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE,
  380. MAP_SHARED | MAP_FIXED, fd, host_offset);
  381. ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
  382. }
  383. void Unmap(size_t virtual_offset, size_t length) {
  384. // The method name is wrong. We're still talking about the virtual range.
  385. // We don't want to unmap, we want to reserve this memory.
  386. {
  387. std::scoped_lock lock{placeholder_mutex};
  388. auto it = placeholders.find({virtual_offset - 1, virtual_offset + length + 1});
  389. if (it != placeholders.end()) {
  390. size_t prev_upper = virtual_offset + length;
  391. virtual_offset = std::min(virtual_offset, it->lower());
  392. length = std::max(it->upper(), prev_upper) - virtual_offset;
  393. }
  394. placeholders.add({virtual_offset, virtual_offset + length});
  395. }
  396. void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE,
  397. MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
  398. ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
  399. }
  400. void Protect(size_t virtual_offset, size_t length, bool read, bool write) {
  401. int flags = 0;
  402. if (read) {
  403. flags |= PROT_READ;
  404. }
  405. if (write) {
  406. flags |= PROT_WRITE;
  407. }
  408. int ret = mprotect(virtual_base + virtual_offset, length, flags);
  409. ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
  410. }
  411. const size_t backing_size; ///< Size of the backing memory in bytes
  412. const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
  413. u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
  414. u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
  415. private:
  416. /// Release all resources in the object
  417. void Release() {
  418. if (virtual_base != MAP_FAILED) {
  419. int ret = munmap(virtual_base, virtual_size);
  420. ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno));
  421. }
  422. if (backing_base != MAP_FAILED) {
  423. int ret = munmap(backing_base, backing_size);
  424. ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno));
  425. }
  426. if (fd != -1) {
  427. int ret = close(fd);
  428. ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
  429. }
  430. }
  431. int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
  432. boost::icl::interval_set<size_t> placeholders; ///< Mapped placeholders
  433. std::mutex placeholder_mutex; ///< Mutex for placeholders
  434. };
  435. #else // ^^^ Linux ^^^ vvv Generic vvv
  436. class HostMemory::Impl {
  437. public:
  438. explicit Impl(size_t /*backing_size */, size_t /* virtual_size */) {
  439. // This is just a place holder.
  440. // Please implement fastmem in a proper way on your platform.
  441. throw std::bad_alloc{};
  442. }
  443. void Map(size_t virtual_offset, size_t host_offset, size_t length) {}
  444. void Unmap(size_t virtual_offset, size_t length) {}
  445. void Protect(size_t virtual_offset, size_t length, bool read, bool write) {}
  446. u8* backing_base{nullptr};
  447. u8* virtual_base{nullptr};
  448. };
  449. #endif // ^^^ Generic ^^^
  450. HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
  451. : backing_size(backing_size_), virtual_size(virtual_size_) {
  452. try {
  453. // Try to allocate a fastmem arena.
  454. // The implementation will fail with std::bad_alloc on errors.
  455. impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment),
  456. AlignUp(virtual_size, PageAlignment) +
  457. 3 * HugePageSize);
  458. backing_base = impl->backing_base;
  459. virtual_base = impl->virtual_base;
  460. if (virtual_base) {
  461. virtual_base += 2 * HugePageSize - 1;
  462. virtual_base -= reinterpret_cast<size_t>(virtual_base) & (HugePageSize - 1);
  463. virtual_base_offset = virtual_base - impl->virtual_base;
  464. }
  465. } catch (const std::bad_alloc&) {
  466. LOG_CRITICAL(HW_Memory,
  467. "Fastmem unavailable, falling back to VirtualBuffer for memory allocation");
  468. fallback_buffer = std::make_unique<Common::VirtualBuffer<u8>>(backing_size);
  469. backing_base = fallback_buffer->data();
  470. virtual_base = nullptr;
  471. }
  472. }
  473. HostMemory::~HostMemory() = default;
  474. HostMemory::HostMemory(HostMemory&&) noexcept = default;
  475. HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
  476. void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) {
  477. ASSERT(virtual_offset % PageAlignment == 0);
  478. ASSERT(host_offset % PageAlignment == 0);
  479. ASSERT(length % PageAlignment == 0);
  480. ASSERT(virtual_offset + length <= virtual_size);
  481. ASSERT(host_offset + length <= backing_size);
  482. if (length == 0 || !virtual_base || !impl) {
  483. return;
  484. }
  485. impl->Map(virtual_offset + virtual_base_offset, host_offset, length);
  486. }
  487. void HostMemory::Unmap(size_t virtual_offset, size_t length) {
  488. ASSERT(virtual_offset % PageAlignment == 0);
  489. ASSERT(length % PageAlignment == 0);
  490. ASSERT(virtual_offset + length <= virtual_size);
  491. if (length == 0 || !virtual_base || !impl) {
  492. return;
  493. }
  494. impl->Unmap(virtual_offset + virtual_base_offset, length);
  495. }
  496. void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write) {
  497. ASSERT(virtual_offset % PageAlignment == 0);
  498. ASSERT(length % PageAlignment == 0);
  499. ASSERT(virtual_offset + length <= virtual_size);
  500. if (length == 0 || !virtual_base || !impl) {
  501. return;
  502. }
  503. impl->Protect(virtual_offset + virtual_base_offset, length, read, write);
  504. }
  505. } // namespace Common