aboutsummaryrefslogtreecommitdiff
path: root/src/common/host_memory.cpp
diff options
context:
space:
mode:
authorkraibse <33869366+kraibse@users.noreply.github.com>2026-11-02 16:01:57 +0100
committerkraibse <33869366+kraibse@users.noreply.github.com>2026-11-02 16:01:57 +0100
commitb374709aa202d586dd09f81f3389da8d73fea0d7 (patch)
tree5c4033d827c517aca9cc73dc497fbb3e81291bf1 /src/common/host_memory.cpp
parent021c9724371e45fa694c9ee3106b9011364d2853 (diff)
parentce1b1de6f1beed36958368f89cda466937ff9716 (diff)
Merge remote-tracking branch 'origin/nx2' into codex/upstream-loading-hotkeys
Diffstat (limited to 'src/common/host_memory.cpp')
-rw-r--r--src/common/host_memory.cpp104
1 files changed, 103 insertions, 1 deletions
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index 75a9da80e9..2ef1982cbc 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -51,11 +51,13 @@
#endif // ^^^ POSIX ^^^
+#include <cstdlib>
#include <mutex>
#include <random>
#include "common/alignment.h"
#include "common/assert.h"
+#include "common/div_ceil.h"
#include "common/free_region_manager.h"
#include "common/host_memory.h"
#include "common/logging.h"
@@ -517,6 +519,14 @@ public:
, virtual_size{virtual_size_}
{}
+#ifdef __linux__
+ bool PunchHole(size_t offset, size_t length) {
+ return fd >= 0 &&
+ fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, static_cast<off_t>(offset),
+ static_cast<off_t>(length)) == 0;
+ }
+#endif
+
bool Init() {
long page_size = sysconf(_SC_PAGESIZE);
ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging", page_size);
@@ -707,7 +717,9 @@ private:
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
: backing_size(backing_size_)
, virtual_size(virtual_size_)
+ , num_granules(Common::DivCeil(backing_size_, size_t{1} << GranuleShift))
{
+ granules = std::make_unique<std::atomic<u8>[]>(num_granules);
#if defined(__OPENORBIS__) || defined(__managarm__)
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
@@ -790,8 +802,98 @@ void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission
#endif
}
+void HostMemory::HintCold(size_t physical_offset, size_t length,
+ std::optional<size_t> virtual_offset) const {
+ if (length == 0 || backing_base == nullptr) {
+ return;
+ }
+ const auto hint = [length](u8* address) {
+#ifdef _WIN32
+ // Unlocking pages that are not locked takes them out of the working set.
+ VirtualUnlock(address, length);
+#elif defined(__linux__) && defined(MADV_PAGEOUT)
+ madvise(address, length, MADV_PAGEOUT);
+#else
+ static_cast<void>(address);
+#endif
+ };
+ hint(backing_base + physical_offset);
+ if (virtual_offset && virtual_base != nullptr && *virtual_offset + length <= virtual_size) {
+ hint(virtual_base + *virtual_offset);
+ }
+}
+
void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value) {
- std::memset(backing_base + physical_offset, fill_value, length);
+ if (length == 0) {
+ return;
+ }
+ MarkUsed(physical_offset, length);
+ if (fill_value != 0) {
+ std::memset(backing_base + physical_offset, fill_value, length);
+ MarkDirty(physical_offset, length);
+ return;
+ }
+ // Fresh backing memory is zero and the host gives it no RAM until it's touched: clear only
+ // the granules that may hold data (title 1's heap made the host commit every page of it).
+ // A granule cleared in full is zero again.
+#ifdef __linux__
+ // Handing a whole granule back to the kernel frees its RAM; a memset would commit it.
+ static const bool punch = [] {
+ const char* value = std::getenv("EDEN_PUNCH_HOLE");
+ return value == nullptr || value[0] != '0';
+ }();
+#endif
+ const size_t end = physical_offset + length;
+ for (size_t offset = physical_offset; offset < end;) {
+ const size_t index = offset >> GranuleShift;
+ const size_t granule_begin = index << GranuleShift;
+ const size_t granule_end = granule_begin + (size_t{1} << GranuleShift);
+ const size_t clear_end = (std::min)(granule_end, end);
+ if ((granules[index].load(std::memory_order_acquire) & GranuleDirty) != 0) {
+ const bool whole = offset == granule_begin && clear_end == granule_end;
+ bool cleared = false;
+#ifdef __linux__
+ cleared = whole && punch && impl && impl->PunchHole(granule_begin, granule_end - granule_begin);
+#endif
+ if (!cleared) {
+ std::memset(backing_base + offset, 0, clear_end - offset);
+ }
+ if (whole) {
+ granules[index].fetch_and(static_cast<u8>(~GranuleDirty), std::memory_order_release);
+ }
+ }
+ offset = clear_end;
+ }
+}
+
+void HostMemory::MarkUsed(size_t physical_offset, size_t length) {
+ if (length == 0) {
+ return;
+ }
+ const size_t last = (physical_offset + length - 1) >> GranuleShift;
+ for (size_t index = physical_offset >> GranuleShift; index <= last; ++index) {
+ if ((granules[index].load(std::memory_order_relaxed) & GranuleUsed) == 0) {
+ granules[index].fetch_or(GranuleUsed, std::memory_order_release);
+ }
+ }
+}
+
+void HostMemory::MarkDirty(size_t physical_offset, size_t length) {
+ if (length == 0) {
+ return;
+ }
+ const size_t last = (physical_offset + length - 1) >> GranuleShift;
+ for (size_t index = physical_offset >> GranuleShift; index <= last; ++index) {
+ granules[index].fetch_or(GranuleDirty, std::memory_order_release);
+ }
+}
+
+void HostMemory::ForgetContents() {
+ for (size_t index = 0; index < num_granules; ++index) {
+ if ((granules[index].load(std::memory_order_relaxed) & GranuleUsed) != 0) {
+ granules[index].fetch_or(GranuleDirty, std::memory_order_release);
+ }
+ }
}
void HostMemory::EnableDirectMappedAddress() {